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