| 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 | |