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