1 module mysql.type; 2 3 4 import std.algorithm; 5 import std.datetime; 6 import std.traits; 7 8 import mysql.protocol; 9 import mysql.packet; 10 import mysql.exception; 11 public import mysql.row; 12 13 14 struct MySQLBinary { 15 this(T)(T[] data) { 16 data_ = (cast(ubyte*)data.ptr)[0..typeof(T[].init[0]).sizeof * data.length]; 17 } 18 19 @property size_t length() const { 20 return data_.length; 21 } 22 23 @property const(ubyte)[] data() const { 24 return data_; 25 } 26 27 private ubyte[] data_; 28 } 29 30 31 struct MySQLValue { 32 package enum BufferSize = max(ulong.sizeof, (ulong[]).sizeof, MySQLDateTime.sizeof, MySQLTime.sizeof); 33 package this(ColumnTypes type, void* ptr, size_t size) { 34 assert(size <= BufferSize); 35 type_ = type; 36 if (type != ColumnTypes.MYSQL_TYPE_NULL) 37 buffer_[0..size] = (cast(ubyte*)ptr)[0..size]; 38 } 39 40 string toString() const { 41 import std.conv; 42 43 final switch(type_) { 44 case ColumnTypes.MYSQL_TYPE_NULL: 45 return "null"; 46 case ColumnTypes.MYSQL_TYPE_TINY: 47 return to!string(*cast(ubyte*)buffer_.ptr); 48 case ColumnTypes.MYSQL_TYPE_YEAR: 49 case ColumnTypes.MYSQL_TYPE_SHORT: 50 return to!string(*cast(ushort*)buffer_.ptr); 51 case ColumnTypes.MYSQL_TYPE_INT24: 52 case ColumnTypes.MYSQL_TYPE_LONG: 53 return to!string(*cast(uint*)buffer_.ptr); 54 case ColumnTypes.MYSQL_TYPE_LONGLONG: 55 return to!string(*cast(ulong*)buffer_.ptr); 56 case ColumnTypes.MYSQL_TYPE_FLOAT: 57 return to!string(*cast(float*)buffer_.ptr); 58 case ColumnTypes.MYSQL_TYPE_DOUBLE: 59 return to!string(*cast(double*)buffer_.ptr); 60 case ColumnTypes.MYSQL_TYPE_SET: 61 case ColumnTypes.MYSQL_TYPE_ENUM: 62 case ColumnTypes.MYSQL_TYPE_VARCHAR: 63 case ColumnTypes.MYSQL_TYPE_VAR_STRING: 64 case ColumnTypes.MYSQL_TYPE_STRING: 65 case ColumnTypes.MYSQL_TYPE_NEWDECIMAL: 66 case ColumnTypes.MYSQL_TYPE_DECIMAL: 67 return to!string(*cast(const(char)[]*)buffer_.ptr); 68 case ColumnTypes.MYSQL_TYPE_BIT: 69 case ColumnTypes.MYSQL_TYPE_TINY_BLOB: 70 case ColumnTypes.MYSQL_TYPE_MEDIUM_BLOB: 71 case ColumnTypes.MYSQL_TYPE_LONG_BLOB: 72 case ColumnTypes.MYSQL_TYPE_BLOB: 73 case ColumnTypes.MYSQL_TYPE_GEOMETRY: 74 return to!string(*cast(ubyte[]*)buffer_.ptr); 75 case ColumnTypes.MYSQL_TYPE_TIME: 76 case ColumnTypes.MYSQL_TYPE_TIME2: 77 return (*cast(MySQLTime*)buffer_.ptr).toDuration().toString(); 78 case ColumnTypes.MYSQL_TYPE_DATE: 79 case ColumnTypes.MYSQL_TYPE_NEWDATE: 80 case ColumnTypes.MYSQL_TYPE_DATETIME: 81 case ColumnTypes.MYSQL_TYPE_DATETIME2: 82 case ColumnTypes.MYSQL_TYPE_TIMESTAMP: 83 case ColumnTypes.MYSQL_TYPE_TIMESTAMP2: 84 return (*cast(MySQLDateTime*)buffer_.ptr).to!DateTime().toString(); 85 } 86 } 87 88 const T get(T)() const if (isScalarType!T) { 89 switch(type_) { 90 case ColumnTypes.MYSQL_TYPE_NULL: 91 throw new MySQLErrorException("Cannot convert NULL to scalar"); 92 case ColumnTypes.MYSQL_TYPE_TINY: 93 return cast(T)(*cast(ubyte*)buffer_.ptr); 94 case ColumnTypes.MYSQL_TYPE_YEAR: 95 case ColumnTypes.MYSQL_TYPE_SHORT: 96 return cast(T)(*cast(ushort*)buffer_.ptr); 97 case ColumnTypes.MYSQL_TYPE_INT24: 98 case ColumnTypes.MYSQL_TYPE_LONG: 99 return cast(T)(*cast(uint*)buffer_.ptr); 100 case ColumnTypes.MYSQL_TYPE_LONGLONG: 101 return cast(T)(*cast(ulong*)buffer_.ptr); 102 case ColumnTypes.MYSQL_TYPE_FLOAT: 103 return cast(T)(*cast(float*)buffer_.ptr); 104 case ColumnTypes.MYSQL_TYPE_DOUBLE: 105 return cast(T)(*cast(double*)buffer_.ptr); 106 default: 107 throw new MySQLErrorException("Cannot convert MySQL value to scalar"); 108 } 109 } 110 111 const T get(T)() const if (is(T == SysTime) || is(T == DateTime) || is(T == Date) || is(T == TimeOfDay)) { 112 switch(type_) { 113 case ColumnTypes.MYSQL_TYPE_NULL: 114 throw new MySQLErrorException("Cannot convert NULL to timestamp"); 115 case ColumnTypes.MYSQL_TYPE_DATE: 116 case ColumnTypes.MYSQL_TYPE_NEWDATE: 117 case ColumnTypes.MYSQL_TYPE_DATETIME: 118 case ColumnTypes.MYSQL_TYPE_DATETIME2: 119 case ColumnTypes.MYSQL_TYPE_TIMESTAMP: 120 case ColumnTypes.MYSQL_TYPE_TIMESTAMP2: 121 return (*cast(MySQLDateTime*)buffer_.ptr).to!T; 122 default: 123 throw new MySQLErrorException("Cannot convert MySQL value to timestamp"); 124 } 125 } 126 127 const T get(T)() const if (is(T == Duration)) { 128 switch(type_) { 129 case ColumnTypes.MYSQL_TYPE_NULL: 130 throw new MySQLErrorException("Cannot convert NULL to time"); 131 case ColumnTypes.MYSQL_TYPE_TIME: 132 case ColumnTypes.MYSQL_TYPE_TIME2: 133 return (*cast(MySQLTime*)buffer_.ptr).toDuration; 134 default: 135 throw new MySQLErrorException("Cannot convert MySQL value to time"); 136 } 137 } 138 139 const T get(T)() const if (isArray!T) { 140 switch(type_) { 141 case ColumnTypes.MYSQL_TYPE_NULL: 142 throw new MySQLErrorException("Cannot convert NULL to array"); 143 case ColumnTypes.MYSQL_TYPE_SET: 144 case ColumnTypes.MYSQL_TYPE_ENUM: 145 case ColumnTypes.MYSQL_TYPE_VARCHAR: 146 case ColumnTypes.MYSQL_TYPE_VAR_STRING: 147 case ColumnTypes.MYSQL_TYPE_STRING: 148 case ColumnTypes.MYSQL_TYPE_NEWDECIMAL: 149 case ColumnTypes.MYSQL_TYPE_DECIMAL: 150 return (*cast(T*)buffer_.ptr).dup; 151 case ColumnTypes.MYSQL_TYPE_BIT: 152 case ColumnTypes.MYSQL_TYPE_TINY_BLOB: 153 case ColumnTypes.MYSQL_TYPE_MEDIUM_BLOB: 154 case ColumnTypes.MYSQL_TYPE_LONG_BLOB: 155 case ColumnTypes.MYSQL_TYPE_BLOB: 156 case ColumnTypes.MYSQL_TYPE_GEOMETRY: 157 return (*cast(T*)buffer_.ptr).dup; 158 default: 159 throw new MySQLErrorException("Cannot convert MySQL value to array"); 160 } 161 } 162 163 const T peek(T)() const if (isScalarType!T) { 164 return get!T; 165 } 166 167 const T peek(T)() const if (is(T == SysTime) || is(T == DateTime) || is(T == Date) || is(T == TimeOfDay)) { 168 return get!T; 169 } 170 171 const T peek(T)() const if (is(T == Duration)) { 172 return get!T; 173 } 174 175 const T peek(T)() const if (isArray!T) { 176 switch(type_) { 177 case ColumnTypes.MYSQL_TYPE_NULL: 178 throw new MySQLErrorException("Cannot convert NULL to array"); 179 case ColumnTypes.MYSQL_TYPE_SET: 180 case ColumnTypes.MYSQL_TYPE_ENUM: 181 case ColumnTypes.MYSQL_TYPE_VARCHAR: 182 case ColumnTypes.MYSQL_TYPE_VAR_STRING: 183 case ColumnTypes.MYSQL_TYPE_STRING: 184 case ColumnTypes.MYSQL_TYPE_NEWDECIMAL: 185 case ColumnTypes.MYSQL_TYPE_DECIMAL: 186 return (*cast(T*)buffer_.ptr); 187 case ColumnTypes.MYSQL_TYPE_BIT: 188 case ColumnTypes.MYSQL_TYPE_TINY_BLOB: 189 case ColumnTypes.MYSQL_TYPE_MEDIUM_BLOB: 190 case ColumnTypes.MYSQL_TYPE_LONG_BLOB: 191 case ColumnTypes.MYSQL_TYPE_BLOB: 192 case ColumnTypes.MYSQL_TYPE_GEOMETRY: 193 return (*cast(T*)buffer_.ptr); 194 default: 195 throw new MySQLErrorException("Cannot convert MySQL value to array"); 196 } 197 } 198 199 bool isNull() const { 200 return type_ == ColumnTypes.MYSQL_TYPE_NULL; 201 } 202 203 ColumnTypes type() const { 204 return type_; 205 } 206 207 package void nullify() { 208 type_ = ColumnTypes.MYSQL_TYPE_NULL; 209 } 210 211 private: 212 ColumnTypes type_ = ColumnTypes.MYSQL_TYPE_NULL; 213 ubyte[BufferSize] buffer_; 214 } 215 216 217 struct MySQLColumn { 218 uint length; 219 ushort flags; 220 ubyte decimals; 221 ColumnTypes type; 222 string name; // todo: fix allocation 223 } 224 225 226 alias MySQLHeader = MySQLColumn[]; 227 228 229 struct MySQLTime { 230 uint days; 231 ubyte negative; 232 ubyte hours; 233 ubyte mins; 234 ubyte secs; 235 uint usecs; 236 237 Duration toDuration() { 238 auto total = days * 86400_000_000L + 239 hours * 3600_000_000L + 240 mins * 60_000_000L + 241 secs * 1_000_000L + 242 usecs; 243 return dur!"usecs"(negative ? -total : total); 244 } 245 246 static MySQLTime from(Duration duration) { 247 MySQLTime time; 248 duration.abs.split!("days", "hours", "minutes", "seconds", "usecs")(time.days, time.hours, time.mins, time.secs, time.usecs); 249 time.negative = duration.isNegative ? 1 : 0; 250 return time; 251 } 252 } 253 254 void putMySQLTime(ref OutputPacket packet, in MySQLTime time) { 255 if (time.days || time.hours || time.mins || time.mins || time.usecs) { 256 auto usecs = time.usecs != 0; 257 packet.put!ubyte(usecs ? 12 : 8); 258 packet.put!ubyte(time.negative); 259 packet.put!uint(time.days); 260 packet.put!ubyte(time.hours); 261 packet.put!ubyte(time.mins); 262 packet.put!ubyte(time.secs); 263 if (usecs) 264 packet.put!uint(time.usecs); 265 } else { 266 packet.put!ubyte(0); 267 } 268 } 269 270 auto eatMySQLTime(ref InputPacket packet) { 271 MySQLTime time; 272 switch(packet.eat!ubyte) { 273 case 12: 274 time.negative = packet.eat!ubyte; 275 time.days = packet.eat!uint; 276 time.hours = packet.eat!ubyte; 277 time.mins = packet.eat!ubyte; 278 time.secs = packet.eat!ubyte; 279 time.usecs = packet.eat!uint; 280 break; 281 case 8: 282 time.negative = packet.eat!ubyte; 283 time.days = packet.eat!uint; 284 time.hours = packet.eat!ubyte; 285 time.mins = packet.eat!ubyte; 286 time.secs = packet.eat!ubyte; 287 break; 288 case 0: 289 break; 290 default: 291 throw new MySQLProtocolException("Bad time struct format"); 292 } 293 294 return time; 295 } 296 297 298 struct MySQLDateTime { 299 ushort year = 0; 300 ubyte month = 0; 301 ubyte day = 0; 302 ubyte hour = 0; 303 ubyte min = 0; 304 ubyte sec = 0; 305 uint usec = 0; 306 307 bool valid() const { 308 return month != 0; 309 } 310 311 T to(T)() if (is(T == SysTime)) { 312 return SysTime(DateTime(year, month, day, hour, min, sec), FracSec.from!"usecs"(usec), UTC()); 313 } 314 315 T to(T)() if (is(T == DateTime)) { 316 return DateTime(year, month, day, hour, min, sec); 317 } 318 319 T to(T)() if (is(T == Date)) { 320 return Date(year, month, day); 321 } 322 323 T to(T)() if (is(T == TimeOfDay)) { 324 return TimeOfDay(hour, min, sec); 325 } 326 327 static MySQLDateTime from(SysTime sysTime) { 328 MySQLDateTime time; 329 330 auto dateTime = cast(DateTime)sysTime; 331 time.year = dateTime.year; 332 time.month = dateTime.month; 333 time.day = dateTime.day; 334 time.hour = dateTime.hour; 335 time.min = dateTime.minute; 336 time.sec = dateTime.second; 337 time.usec = sysTime.fracSec.usecs; 338 339 return time; 340 } 341 342 static MySQLDateTime from(DateTime dateTime) { 343 MySQLDateTime time; 344 345 time.year = dateTime.year; 346 time.month = dateTime.month; 347 time.day = dateTime.day; 348 time.hour = dateTime.hour; 349 time.min = dateTime.minute; 350 time.sec = dateTime.second; 351 352 return time; 353 } 354 355 static MySQLDateTime from(Date date) { 356 MySQLDateTime time; 357 358 time.year = date.year; 359 time.month = date.month; 360 time.day = date.day; 361 362 return time; 363 } 364 } 365 366 void putMySQLDateTime(ref OutputPacket packet, in MySQLDateTime time) { 367 auto marker = packet.marker!ubyte; 368 ubyte length = 0; 369 370 if (time.year || time.month || time.day) { 371 length = 4; 372 packet.put!ushort(time.year); 373 packet.put!ubyte(time.month); 374 packet.put!ubyte(time.day); 375 376 if (time.hour || time.min || time.sec || time.usec) { 377 length = 7; 378 packet.put!ubyte(time.hour); 379 packet.put!ubyte(time.min); 380 packet.put!ubyte(time.sec); 381 382 if (time.usec) { 383 length = 11; 384 packet.put!uint(time.usec); 385 } 386 } 387 } 388 389 packet.put!ubyte(marker, length); 390 } 391 392 auto eatMySQLDateTime(ref InputPacket packet) { 393 MySQLDateTime time; 394 switch(packet.eat!ubyte) { 395 case 11: 396 time.year = packet.eat!ushort; 397 time.month = packet.eat!ubyte; 398 time.day = packet.eat!ubyte; 399 time.hour = packet.eat!ubyte; 400 time.min = packet.eat!ubyte; 401 time.sec = packet.eat!ubyte; 402 time.usec = packet.eat!uint; 403 break; 404 case 7: 405 time.year = packet.eat!ushort; 406 time.month = packet.eat!ubyte; 407 time.day = packet.eat!ubyte; 408 time.hour = packet.eat!ubyte; 409 time.min = packet.eat!ubyte; 410 time.sec = packet.eat!ubyte; 411 break; 412 case 4: 413 time.year = packet.eat!ushort; 414 time.month = packet.eat!ubyte; 415 time.day = packet.eat!ubyte; 416 break; 417 case 0: 418 break; 419 default: 420 throw new MySQLProtocolException("Bad datetime struct format"); 421 } 422 423 return time; 424 } 425 426 427 MySQLValue eatValue(ref InputPacket packet, in MySQLColumn column) { 428 MySQLValue value; 429 430 final switch(column.type) { 431 case ColumnTypes.MYSQL_TYPE_NULL: 432 value = MySQLValue(column.type, null, 0); 433 break; 434 case ColumnTypes.MYSQL_TYPE_TINY: 435 auto x = packet.eat!ubyte; 436 value = MySQLValue(column.type, &x, 1); 437 break; 438 case ColumnTypes.MYSQL_TYPE_YEAR: 439 case ColumnTypes.MYSQL_TYPE_SHORT: 440 auto x = packet.eat!ushort; 441 value = MySQLValue(column.type, &x, 2); 442 break; 443 case ColumnTypes.MYSQL_TYPE_INT24: 444 case ColumnTypes.MYSQL_TYPE_LONG: 445 auto x = packet.eat!uint; 446 value = MySQLValue(column.type, &x, 4); 447 break; 448 case ColumnTypes.MYSQL_TYPE_DOUBLE: 449 case ColumnTypes.MYSQL_TYPE_LONGLONG: 450 auto x = packet.eat!ulong; 451 value = MySQLValue(column.type, &x, 8); 452 break; 453 case ColumnTypes.MYSQL_TYPE_FLOAT: 454 auto x = packet.eat!float; 455 value = MySQLValue(column.type, &x, 4); 456 break; 457 case ColumnTypes.MYSQL_TYPE_SET: 458 case ColumnTypes.MYSQL_TYPE_ENUM: 459 case ColumnTypes.MYSQL_TYPE_VARCHAR: 460 case ColumnTypes.MYSQL_TYPE_VAR_STRING: 461 case ColumnTypes.MYSQL_TYPE_STRING: 462 case ColumnTypes.MYSQL_TYPE_NEWDECIMAL: 463 case ColumnTypes.MYSQL_TYPE_DECIMAL: 464 auto x = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 465 value = MySQLValue(column.type, &x, typeof(x).sizeof); 466 break; 467 case ColumnTypes.MYSQL_TYPE_BIT: 468 case ColumnTypes.MYSQL_TYPE_TINY_BLOB: 469 case ColumnTypes.MYSQL_TYPE_MEDIUM_BLOB: 470 case ColumnTypes.MYSQL_TYPE_LONG_BLOB: 471 case ColumnTypes.MYSQL_TYPE_BLOB: 472 case ColumnTypes.MYSQL_TYPE_GEOMETRY: 473 auto x = packet.eat!(const(ubyte)[])(cast(size_t)packet.eatLenEnc()); 474 value = MySQLValue(column.type, &x, typeof(x).sizeof); 475 break; 476 case ColumnTypes.MYSQL_TYPE_TIME: 477 case ColumnTypes.MYSQL_TYPE_TIME2: 478 auto x = eatMySQLTime(packet); 479 value = MySQLValue(column.type, &x, typeof(x).sizeof); 480 break; 481 case ColumnTypes.MYSQL_TYPE_DATE: 482 case ColumnTypes.MYSQL_TYPE_NEWDATE: 483 case ColumnTypes.MYSQL_TYPE_DATETIME: 484 case ColumnTypes.MYSQL_TYPE_DATETIME2: 485 case ColumnTypes.MYSQL_TYPE_TIMESTAMP: 486 case ColumnTypes.MYSQL_TYPE_TIMESTAMP2: 487 auto x = eatMySQLDateTime(packet); 488 if (x.valid()) 489 value = MySQLValue(column.type, &x, typeof(x).sizeof); 490 else 491 value = MySQLValue(ColumnTypes.MYSQL_TYPE_NULL, null, 0); 492 break; 493 } 494 495 return value; 496 } 497 498 void putValueType(T)(ref OutputPacket packet, T value) if (is(T == Date) || is(T == DateTime) || is(T == SysTime)) { 499 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIMESTAMP); 500 packet.put!ubyte(0x80); 501 } 502 503 void putValue(T)(ref OutputPacket packet, T value) if (is(T == Date) || is(T == DateTime) || is(T == SysTime)) { 504 putMySQLDateTime(packet, MySQLDateTime.from(value)); 505 } 506 507 void putValueType(T)(ref OutputPacket packet, T value) if (is(T == Duration)) { 508 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIME); 509 packet.put!ubyte(0x00); 510 } 511 512 void putValue(T)(ref OutputPacket packet, T value) if (is(T == Duration)) { 513 putMySQLTime(packet, MySQLTime.from(value)); 514 } 515 516 void putValueType(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T) { 517 static if (isUnsigned!T) { 518 const ubyte signbyte = 0x80; 519 } else { 520 const ubyte signbyte = 0x00; 521 } 522 523 static if (is(T == long) || is(T == ulong)) { 524 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONGLONG); 525 packet.put!ubyte(signbyte); 526 } else static if (is(T == int) || is(T == uint) || is(T == dchar)) { 527 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONG); 528 packet.put!ubyte(signbyte); 529 } else static if (is(T == short) || is(T == ushort) || is(T == wchar)) { 530 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_SHORT); 531 packet.put!ubyte(signbyte); 532 } else static if (is(T == byte) || is(T == ubyte) || is(T == char) || is(T == bool)) { 533 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TINY); 534 packet.put!ubyte(signbyte); 535 } 536 } 537 538 void putValueType(T)(ref OutputPacket packet, T value) if (is(T == typeof(null))) { 539 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_NULL); 540 packet.put!ubyte(0x00); 541 } 542 543 void putValue(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T) { 544 static if (is(T == long) || is(T == ulong)) { 545 packet.put!ulong(value); 546 } else static if (is(T == int) || is(T == uint) || is(T == dchar)) { 547 packet.put!uint(value); 548 } else static if (is(T == short) || is(T == ushort) || is(T == wchar)) { 549 packet.put!ushort(value); 550 } else static if (is(T == byte) || is(T == ubyte) || is(T == char) || is(T == bool)) { 551 packet.put!ubyte(value); 552 } 553 } 554 555 void putValueType(T)(ref OutputPacket packet, T value) if (isSomeString!T) { 556 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_STRING); 557 packet.put!ubyte(0x80); 558 } 559 560 void putValue(T)(ref OutputPacket packet, T value) if (isSomeString!T) { 561 ulong size = value.length * ValueType.sizeof; 562 packet.putLenEnc(size); 563 packet.put(value); 564 } 565 566 void putValueType(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!T) { 567 foreach(ref item; value) 568 putValueType(packet, item); 569 } 570 571 void putValue(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!T) { 572 foreach(ref item; value) 573 putValue(packet, item); 574 } 575 576 void putValueType(T)(ref OutputPacket packet, T value) if (is(T == MySQLBinary)) { 577 packet.put!ubyte(ColumnTypes.MYSQL_TYPE_BLOB); 578 packet.put!ubyte(0x80); 579 } 580 581 void putValue(T)(ref OutputPacket packet, T value) if (is(T == MySQLBinary)) { 582 ulong size = value.length; 583 packet.putLenEnc(size); 584 packet.put(value.data); 585 }