1 /*
2    Copyright (c) 2004, 2012, Oracle and/or its affiliates.
3    Copyright (c) 2013, MariaDB Foundation.
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; version 2 of the License.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
17 
18 #include "mariadb.h"
19 #include "compat56.h"
20 #include "myisampack.h"
21 #include "my_time.h"
22 
23 
24 static const int my_max_usec_value[7]
25 {
26   0,
27   900000,
28   990000,
29   999000,
30   999900,
31   999990,
32   999999
33 };
34 
35 
36 /*** MySQL56 TIME low-level memory and disk representation routines ***/
37 
38 /*
39   In-memory format:
40 
41    1  bit sign          (Used for sign, when on disk)
42    1  bit unused        (Reserved for wider hour range, e.g. for intervals)
43    10 bit hour          (0-836)
44    6  bit minute        (0-59)
45    6  bit second        (0-59)
46   24  bits microseconds (0-999999)
47 
48  Total: 48 bits = 6 bytes
49    Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
50 */
51 
52 
53 /**
54   Convert time value to MySQL56 numeric packed representation.
55 
56   @param    ltime   The value to convert.
57   @return           Numeric packed representation.
58 */
TIME_to_longlong_time_packed(const MYSQL_TIME * ltime)59 longlong TIME_to_longlong_time_packed(const MYSQL_TIME *ltime)
60 {
61   DBUG_ASSERT(ltime->year == 0);
62   DBUG_ASSERT(ltime->month == 0);
63   // Mix days with hours: "1 00:10:10" -> "24:10:10"
64   long hms= ((ltime->day * 24 + ltime->hour) << 12) |
65             (ltime->minute << 6) | ltime->second;
66   longlong tmp= MY_PACKED_TIME_MAKE(hms, ltime->second_part);
67   return ltime->neg ? -tmp : tmp;
68 }
69 
70 
71 
72 /**
73   Convert MySQL56 time packed numeric representation to time.
74 
75   @param  OUT ltime  The MYSQL_TIME variable to set.
76   @param      tmp    The packed numeric representation.
77 */
TIME_from_longlong_time_packed(MYSQL_TIME * ltime,longlong tmp)78 void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong tmp)
79 {
80   long hms;
81   if ((ltime->neg= (tmp < 0)))
82     tmp= -tmp;
83   hms= (long) MY_PACKED_TIME_GET_INT_PART(tmp);
84   ltime->year=   (uint) 0;
85   ltime->month=  (uint) 0;
86   ltime->day=    (uint) 0;
87   ltime->hour=   (uint) (hms >> 12) % (1 << 10); /* 10 bits starting at 12th */
88   ltime->minute= (uint) (hms >> 6)  % (1 << 6);  /* 6 bits starting at 6th   */
89   ltime->second= (uint)  hms        % (1 << 6);  /* 6 bits starting at 0th   */
90   ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp);
91   ltime->time_type= MYSQL_TIMESTAMP_TIME;
92 }
93 
94 
95 /**
96   Calculate binary size of MySQL56 packed numeric time representation.
97 
98   @param   dec   Precision.
99 */
my_time_binary_length(uint dec)100 uint my_time_binary_length(uint dec)
101 {
102   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
103   return 3 + (dec + 1) / 2;
104 }
105 
106 
107 /*
108   On disk we convert from signed representation to unsigned
109   representation using TIMEF_OFS, so all values become binary comparable.
110 */
111 #define TIMEF_OFS 0x800000000000LL
112 #define TIMEF_INT_OFS 0x800000LL
113 
114 
115 /**
116   Convert MySQL56 in-memory numeric time representation to on-disk representation
117 
118   @param       nr   Value in packed numeric time format.
119   @param   OUT ptr  The buffer to put value at.
120   @param       dec  Precision.
121 */
my_time_packed_to_binary(longlong nr,uchar * ptr,uint dec)122 void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec)
123 {
124   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
125   /* Make sure the stored value was previously properly rounded or truncated */
126   DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) %
127               (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
128 
129   switch (dec)
130   {
131   case 0:
132   default:
133     mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
134     break;
135 
136   case 1:
137   case 2:
138     mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
139     ptr[3]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000);
140     break;
141 
142   case 4:
143   case 3:
144     mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
145     mi_int2store(ptr + 3, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100);
146     break;
147 
148   case 5:
149   case 6:
150     mi_int6store(ptr, nr + TIMEF_OFS);
151     break;
152   }
153 }
154 
155 
156 /**
157   Convert MySQL56 on-disk time representation to in-memory packed numeric
158   representation.
159 
160   @param   ptr  The pointer to read the value at.
161   @param   dec  Precision.
162   @return       Packed numeric time representation.
163 */
my_time_packed_from_binary(const uchar * ptr,uint dec)164 longlong my_time_packed_from_binary(const uchar *ptr, uint dec)
165 {
166   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
167 
168   switch (dec)
169   {
170   case 0:
171   default:
172     {
173       longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
174       return MY_PACKED_TIME_MAKE_INT(intpart);
175     }
176   case 1:
177   case 2:
178     {
179       longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
180       int frac= (uint) ptr[3];
181       if (intpart < 0 && frac)
182       {
183         /*
184           Negative values are stored with reverse fractional part order,
185           for binary sort compatibility.
186 
187             Disk value  intpart frac   Time value   Memory value
188             800000.00    0      0      00:00:00.00  0000000000.000000
189             7FFFFF.FF   -1      255   -00:00:00.01  FFFFFFFFFF.FFD8F0
190             7FFFFF.9D   -1      99    -00:00:00.99  FFFFFFFFFF.F0E4D0
191             7FFFFF.00   -1      0     -00:00:01.00  FFFFFFFFFF.000000
192             7FFFFE.FF   -1      255   -00:00:01.01  FFFFFFFFFE.FFD8F0
193             7FFFFE.F6   -2      246   -00:00:01.10  FFFFFFFFFE.FE7960
194 
195             Formula to convert fractional part from disk format
196             (now stored in "frac" variable) to absolute value: "0x100 - frac".
197             To reconstruct in-memory value, we shift
198             to the next integer value and then substruct fractional part.
199         */
200         intpart++;    /* Shift to the next integer value */
201         frac-= 0x100; /* -(0x100 - frac) */
202       }
203       return MY_PACKED_TIME_MAKE(intpart, frac * 10000);
204     }
205 
206   case 3:
207   case 4:
208     {
209       longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
210       int frac= mi_uint2korr(ptr + 3);
211       if (intpart < 0 && frac)
212       {
213         /*
214           Fix reverse fractional part order: "0x10000 - frac".
215           See comments for FSP=1 and FSP=2 above.
216         */
217         intpart++;      /* Shift to the next integer value */
218         frac-= 0x10000; /* -(0x10000-frac) */
219       }
220       return MY_PACKED_TIME_MAKE(intpart, frac * 100);
221     }
222 
223   case 5:
224   case 6:
225     return ((longlong) mi_uint6korr(ptr)) - TIMEF_OFS;
226   }
227 }
228 
229 
230 /*** MySQL56 DATETIME low-level memory and disk representation routines ***/
231 
232 /*
233     1 bit  sign            (used when on disk)
234    17 bits year*13+month   (year 0-9999, month 0-12)
235     5 bits day             (0-31)
236     5 bits hour            (0-23)
237     6 bits minute          (0-59)
238     6 bits second          (0-59)
239    24 bits microseconds    (0-999999)
240 
241    Total: 64 bits = 8 bytes
242 
243    SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
244 */
245 
246 /**
247   Convert datetime to MySQL56 packed numeric datetime representation.
248   @param ltime  The value to convert.
249   @return       Packed numeric representation of ltime.
250 */
TIME_to_longlong_datetime_packed(const MYSQL_TIME * ltime)251 longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *ltime)
252 {
253   longlong ymd= ((ltime->year * 13 + ltime->month) << 5) | ltime->day;
254   longlong hms= (ltime->hour << 12) | (ltime->minute << 6) | ltime->second;
255   longlong tmp= MY_PACKED_TIME_MAKE(((ymd << 17) | hms), ltime->second_part);
256   DBUG_ASSERT(!check_datetime_range(ltime)); /* Make sure no overflow */
257   return ltime->neg ? -tmp : tmp;
258 }
259 
260 
261 /**
262   Convert MySQL56 packed numeric datetime representation to MYSQL_TIME.
263   @param OUT  ltime The datetime variable to convert to.
264   @param      tmp   The packed numeric datetime value.
265 */
TIME_from_longlong_datetime_packed(MYSQL_TIME * ltime,longlong tmp)266 void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp)
267 {
268   longlong ymd, hms;
269   longlong ymdhms, ym;
270 
271   DBUG_ASSERT(tmp != LONGLONG_MIN);
272 
273   if ((ltime->neg= (tmp < 0)))
274     tmp= -tmp;
275 
276   ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp);
277   ymdhms= MY_PACKED_TIME_GET_INT_PART(tmp);
278 
279   ymd= ymdhms >> 17;
280   ym= ymd >> 5;
281   hms= ymdhms % (1 << 17);
282 
283   ltime->day= ymd % (1 << 5);
284   ltime->month= ym % 13;
285   ltime->year= (uint) (ym / 13);
286 
287   ltime->second= hms % (1 << 6);
288   ltime->minute= (hms >> 6) % (1 << 6);
289   ltime->hour= (uint) (hms >> 12);
290 
291   ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
292 }
293 
294 
295 /**
296   Calculate binary size of MySQL56 packed datetime representation.
297   @param dec  Precision.
298 */
my_datetime_binary_length(uint dec)299 uint my_datetime_binary_length(uint dec)
300 {
301   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
302   return 5 + (dec + 1) / 2;
303 }
304 
305 
306 /*
307   On disk we store as unsigned number with DATETIMEF_INT_OFS offset,
308   for HA_KETYPE_BINARY compatibility purposes.
309 */
310 #define DATETIMEF_INT_OFS 0x8000000000LL
311 
312 
313 /**
314   Convert MySQL56 on-disk datetime representation
315   to in-memory packed numeric representation.
316 
317   @param ptr   The pointer to read value at.
318   @param dec   Precision.
319   @return      In-memory packed numeric datetime representation.
320 */
my_datetime_packed_from_binary(const uchar * ptr,uint dec)321 longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec)
322 {
323   longlong intpart= mi_uint5korr(ptr) - DATETIMEF_INT_OFS;
324   int frac;
325   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
326   switch (dec)
327   {
328   case 0:
329   default:
330     return MY_PACKED_TIME_MAKE_INT(intpart);
331   case 1:
332   case 2:
333     frac= ((int) (signed char) ptr[5]) * 10000;
334     break;
335   case 3:
336   case 4:
337     frac= mi_sint2korr(ptr + 5) * 100;
338     break;
339   case 5:
340   case 6:
341     frac= mi_sint3korr(ptr + 5);
342     break;
343   }
344   return MY_PACKED_TIME_MAKE(intpart, frac);
345 }
346 
347 
348 /**
349   Store MySQL56 in-memory numeric packed datetime representation to disk.
350 
351   @param      nr  In-memory numeric packed datetime representation.
352   @param OUT  ptr The pointer to store at.
353   @param      dec Precision, 1-6.
354 */
my_datetime_packed_to_binary(longlong nr,uchar * ptr,uint dec)355 void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec)
356 {
357   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
358   /* The value being stored must have been properly rounded or truncated */
359   DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) %
360               (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
361 
362   mi_int5store(ptr, MY_PACKED_TIME_GET_INT_PART(nr) + DATETIMEF_INT_OFS);
363   switch (dec)
364   {
365   case 0:
366   default:
367     break;
368   case 1:
369   case 2:
370     ptr[5]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000);
371     break;
372   case 3:
373   case 4:
374     mi_int2store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100);
375     break;
376   case 5:
377   case 6:
378     mi_int3store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr));
379   }
380 }
381 
382 
383 /*** MySQL56 TIMESTAMP low-level memory and disk representation routines ***/
384 
385 /**
386   Calculate on-disk size of a timestamp value.
387 
388   @param  dec  Precision.
389 */
my_timestamp_binary_length(uint dec)390 uint my_timestamp_binary_length(uint dec)
391 {
392   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
393   return 4 + (dec + 1) / 2;
394 }
395 
396 
397 /**
398   Convert MySQL56 binary timestamp representation to in-memory representation.
399 
400   @param  OUT tm  The variable to convert to.
401   @param      ptr The pointer to read the value from.
402   @param      dec Precision.
403 */
my_timestamp_from_binary(struct timeval * tm,const uchar * ptr,uint dec)404 void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec)
405 {
406   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
407   tm->tv_sec= mi_uint4korr(ptr);
408   switch (dec)
409   {
410     case 0:
411     default:
412       tm->tv_usec= 0;
413       return;
414     case 1:
415     case 2:
416       tm->tv_usec= ((int) ptr[4]) * 10000;
417       break;
418     case 3:
419     case 4:
420       tm->tv_usec= (uint) mi_uint2korr(ptr + 4) * 100;
421       break;
422     case 5:
423     case 6:
424       tm->tv_usec= (uint) mi_uint3korr(ptr + 4);
425   }
426   // The binary data my be corrupt. Cut fractional seconds to the valid range.
427   set_if_smaller(tm->tv_usec, my_max_usec_value[dec]);
428 }
429 
430 
431 /**
432   Convert MySQL56 in-memory timestamp representation to on-disk representation.
433 
434   @param        tm   The value to convert.
435   @param  OUT   ptr  The pointer to store the value to.
436   @param        dec  Precision.
437 */
my_timestamp_to_binary(const struct timeval * tm,uchar * ptr,uint dec)438 void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec)
439 {
440   DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
441   /* Stored value must have been previously properly rounded or truncated */
442   DBUG_ASSERT((tm->tv_usec %
443                (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
444   mi_int4store(ptr, tm->tv_sec);
445   switch (dec)
446   {
447     case 0:
448     default:
449       break;
450     case 1:
451     case 2:
452       ptr[4]= (unsigned char) (char) (tm->tv_usec / 10000);
453       break;
454     case 3:
455     case 4:
456       mi_int2store(ptr + 4, tm->tv_usec / 100);
457       break;
458       /* Impossible second precision. Fall through */
459     case 5:
460     case 6:
461       mi_int3store(ptr + 4, tm->tv_usec);
462   }
463 }
464 
465 /****************************************/
466