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