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-1301 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 | */ |
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 | */ |
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 | */ |
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 | */ |
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 | */ |
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 | */ |
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 | */ |
253 | void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp) |
254 | { |
255 | longlong ymd, hms; |
256 | longlong ymdhms, ym; |
257 | if ((ltime->neg= (tmp < 0))) |
258 | tmp= -tmp; |
259 | |
260 | ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp); |
261 | ymdhms= MY_PACKED_TIME_GET_INT_PART(tmp); |
262 | |
263 | ymd= ymdhms >> 17; |
264 | ym= ymd >> 5; |
265 | hms= ymdhms % (1 << 17); |
266 | |
267 | ltime->day= ymd % (1 << 5); |
268 | ltime->month= ym % 13; |
269 | ltime->year= (uint) (ym / 13); |
270 | |
271 | ltime->second= hms % (1 << 6); |
272 | ltime->minute= (hms >> 6) % (1 << 6); |
273 | ltime->hour= (uint) (hms >> 12); |
274 | |
275 | ltime->time_type= MYSQL_TIMESTAMP_DATETIME; |
276 | } |
277 | |
278 | |
279 | /** |
280 | Calculate binary size of MySQL56 packed datetime representation. |
281 | @param dec Precision. |
282 | */ |
283 | uint my_datetime_binary_length(uint dec) |
284 | { |
285 | DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); |
286 | return 5 + (dec + 1) / 2; |
287 | } |
288 | |
289 | |
290 | /* |
291 | On disk we store as unsigned number with DATETIMEF_INT_OFS offset, |
292 | for HA_KETYPE_BINARY compatibilty purposes. |
293 | */ |
294 | #define DATETIMEF_INT_OFS 0x8000000000LL |
295 | |
296 | |
297 | /** |
298 | Convert MySQL56 on-disk datetime representation |
299 | to in-memory packed numeric representation. |
300 | |
301 | @param ptr The pointer to read value at. |
302 | @param dec Precision. |
303 | @return In-memory packed numeric datetime representation. |
304 | */ |
305 | longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec) |
306 | { |
307 | longlong intpart= mi_uint5korr(ptr) - DATETIMEF_INT_OFS; |
308 | int frac; |
309 | DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); |
310 | switch (dec) |
311 | { |
312 | case 0: |
313 | default: |
314 | return MY_PACKED_TIME_MAKE_INT(intpart); |
315 | case 1: |
316 | case 2: |
317 | frac= ((int) (signed char) ptr[5]) * 10000; |
318 | break; |
319 | case 3: |
320 | case 4: |
321 | frac= mi_sint2korr(ptr + 5) * 100; |
322 | break; |
323 | case 5: |
324 | case 6: |
325 | frac= mi_sint3korr(ptr + 5); |
326 | break; |
327 | } |
328 | return MY_PACKED_TIME_MAKE(intpart, frac); |
329 | } |
330 | |
331 | |
332 | /** |
333 | Store MySQL56 in-memory numeric packed datetime representation to disk. |
334 | |
335 | @param nr In-memory numeric packed datetime representation. |
336 | @param OUT ptr The pointer to store at. |
337 | @param dec Precision, 1-6. |
338 | */ |
339 | void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec) |
340 | { |
341 | DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); |
342 | /* The value being stored must have been properly rounded or truncated */ |
343 | DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) % |
344 | (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); |
345 | |
346 | mi_int5store(ptr, MY_PACKED_TIME_GET_INT_PART(nr) + DATETIMEF_INT_OFS); |
347 | switch (dec) |
348 | { |
349 | case 0: |
350 | default: |
351 | break; |
352 | case 1: |
353 | case 2: |
354 | ptr[5]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000); |
355 | break; |
356 | case 3: |
357 | case 4: |
358 | mi_int2store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100); |
359 | break; |
360 | case 5: |
361 | case 6: |
362 | mi_int3store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr)); |
363 | } |
364 | } |
365 | |
366 | |
367 | /*** MySQL56 TIMESTAMP low-level memory and disk representation routines ***/ |
368 | |
369 | /** |
370 | Calculate on-disk size of a timestamp value. |
371 | |
372 | @param dec Precision. |
373 | */ |
374 | uint my_timestamp_binary_length(uint dec) |
375 | { |
376 | DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); |
377 | return 4 + (dec + 1) / 2; |
378 | } |
379 | |
380 | |
381 | /** |
382 | Convert MySQL56 binary timestamp representation to in-memory representation. |
383 | |
384 | @param OUT tm The variable to convert to. |
385 | @param ptr The pointer to read the value from. |
386 | @param dec Precision. |
387 | */ |
388 | void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec) |
389 | { |
390 | DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); |
391 | tm->tv_sec= mi_uint4korr(ptr); |
392 | switch (dec) |
393 | { |
394 | case 0: |
395 | default: |
396 | tm->tv_usec= 0; |
397 | break; |
398 | case 1: |
399 | case 2: |
400 | tm->tv_usec= ((int) ptr[4]) * 10000; |
401 | break; |
402 | case 3: |
403 | case 4: |
404 | tm->tv_usec= mi_sint2korr(ptr + 4) * 100; |
405 | break; |
406 | case 5: |
407 | case 6: |
408 | tm->tv_usec= mi_sint3korr(ptr + 4); |
409 | } |
410 | } |
411 | |
412 | |
413 | /** |
414 | Convert MySQL56 in-memory timestamp representation to on-disk representation. |
415 | |
416 | @param tm The value to convert. |
417 | @param OUT ptr The pointer to store the value to. |
418 | @param dec Precision. |
419 | */ |
420 | void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec) |
421 | { |
422 | DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); |
423 | /* Stored value must have been previously properly rounded or truncated */ |
424 | DBUG_ASSERT((tm->tv_usec % |
425 | (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); |
426 | mi_int4store(ptr, tm->tv_sec); |
427 | switch (dec) |
428 | { |
429 | case 0: |
430 | default: |
431 | break; |
432 | case 1: |
433 | case 2: |
434 | ptr[4]= (unsigned char) (char) (tm->tv_usec / 10000); |
435 | break; |
436 | case 3: |
437 | case 4: |
438 | mi_int2store(ptr + 4, tm->tv_usec / 100); |
439 | break; |
440 | /* Impossible second precision. Fall through */ |
441 | case 5: |
442 | case 6: |
443 | mi_int3store(ptr + 4, tm->tv_usec); |
444 | } |
445 | } |
446 | |
447 | /****************************************/ |
448 | |