1 /** 2 * A native D driver for the MySQL database system. Source file mysql.d. 3 * 4 * This module attempts to provide composite objects and methods that will allow a wide range of common database 5 * operations, but be relatively easy to use. The design is a first attempt to illustrate the structure of a set of modules 6 * to cover popular database systems and ODBC. 7 * 8 * It has no dependecies on GPL header files or libraries, instead communicating directly with the server via the 9 * published client/server protocol. 10 * 11 * $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol)$(BR) 12 * $(LINK http://forge.mysql.com/w/index.php?title=MySQL_Internals_ClientServer_Protocol&diff=5078&oldid=4374) 13 * 14 * This version is not by any means comprehensive, and there is still a good deal of work to do. As a general design 15 * position it avoids providing wrappers for operations that can be accomplished by simple SQL sommands, unless 16 * the command produces a result set. There are some instances of the latter category to provide simple meta-data 17 * for the database, 18 * 19 * Its primary objects are: 20 * $(UL 21 * $(LI Connection: $(UL $(LI Connection to the server, and querying and setting of server parameters.))) 22 * $(LI Command: Handling of SQL requests/queries/commands, with principal methods: 23 * $(UL 24 $(LI execSQL() - plain old SQL query.) 25 * $(LI execTuple() - get a set of values from a select or similar query into a matching tuple of D variables.) 26 * $(LI execPrepared() - execute a prepared statement.) 27 * $(LI execResult() - execute a raw SQL statement and get a complete result set.) 28 * $(LI execSequence() - execute a raw SQL statement and handle the rows one at a time.) 29 * $(LI execPreparedResult() - execute a prepared statement and get a complete result set.) 30 * $(LI execPreparedSequence() - execute a prepared statement and handle the rows one at a time.) 31 * $(LI execFunction() - execute a stored function with D variables as input and output.) 32 * $(LI execProcedure() - execute a stored procedure with D variables as input.) 33 * ) 34 * ) 35 * $(LI ResultSet: $(UL $(LI A random access range of rows, where a Row is basically an array of variant.))) 36 * $(LI ResultSequence: $(UL $(LIAn input range of similar rows.))) 37 * ) 38 * 39 * It has currently only been compiled and unit tested on Ubuntu with D2.055 using a TCP loopback connection 40 * to a server on the local machine. 41 * 42 * There are numerous examples of usage in the unittest sections. 43 * 44 * The file mysqld.sql, included with the module source code, can be used to generate the tables required by the unit tests. 45 * 46 * There is an outstanding issue with Connections. Normally MySQL clients sonnect to a server on the same machine 47 * via a Unix socket on *nix systems, and through a named pipe on Windows. Neither of these conventions is 48 * currently supported. TCP must be used for all connections. 49 * 50 * Since there is currently no SHA1 support on Phobos, a simple translation of the NIST example C code for SHA1 51 * is also included with this module. 52 * 53 * Copyright: Copyright 2011 54 * License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 55 * Author: Steve Teale 56 */ 57 module mysql; 58 59 import sha1; 60 61 import vibe.core.tcp; 62 63 import std.exception; 64 import std.range; 65 import std.stdio; 66 import std.string; 67 import std.conv; 68 import std.variant; 69 import std.datetime; 70 71 /** 72 * An exception type to distinguish exceptions thrown by this module. 73 */ 74 class MySQLException: Exception 75 { 76 this(string msg, string file, size_t line) { super(msg, file, line); } 77 } 78 alias MySQLException MYX; 79 80 /** 81 * A simple struct to represent time difference. 82 * 83 * D's std.datetime does not have a type that is closely compatible with the MySQL 84 * interpretation of a time difference, so we define a struct here to hold such 85 * values. 86 * 87 */ 88 struct TimeDiff 89 { 90 bool negative; 91 int days; 92 ubyte hours, minutes, seconds; 93 } 94 95 /** 96 * Function to extract a time difference from a binary encoded row. 97 * 98 * Time/date structures are packed by the server into a byte sub-packet 99 * with a leading length byte, and a minimal number of bytes to embody the data. 100 * 101 * Params: a = slice of a protocol packet beginning at the length byte for a chunk of time data 102 * 103 * Returns: A populated or default initialized TimeDiff struct. 104 */ 105 TimeDiff toTimeDiff(ubyte[] a) 106 { 107 enforceEx!MYX(a.length, "Supplied byte array is zero length"); 108 TimeDiff td; 109 uint l = a[0]; 110 enforceEx!MYX(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row."); 111 if (l == 5) 112 { 113 td.negative = (a[1] != 0); 114 td.days = (a[5] << 24) + (a[4] << 16) + (a[3] << 8) + a[2]; 115 } 116 else if (l > 5) 117 { 118 td.negative = (a[1] != 0); 119 td.days = (a[5] << 24) + (a[4] << 16) + (a[3] << 8) + a[2]; 120 td.hours = a[6]; 121 td.minutes = a[7]; 122 td.seconds = a[8]; 123 } 124 // Note that the fractional seconds part is not stored by MtSQL 125 return td; 126 } 127 128 /** 129 * Function to extract a time difference from a text encoded column value. 130 * 131 * Text representations of a time difference are like -750:12:02 - 750 hours 132 * 12 minutes and two seconds ago. 133 * 134 * Params: s = A string representation of the time difference. 135 * Returns: A populated or default initialized TimeDiff struct. 136 */ 137 TimeDiff toTimeDiff(string s) 138 { 139 TimeDiff td; 140 int t = parse!int(s); 141 if (t < 0) 142 { 143 td.negative = true; 144 t = -t; 145 } 146 td.hours = t%24; 147 td.days = t/24; 148 munch(s, ":"); 149 td.minutes = parse!ubyte(s); 150 munch(s, ":"); 151 td.seconds = parse!ubyte(s); 152 return td; 153 } 154 155 /** 156 * Function to extract a TimeOfDay from a binary encoded row. 157 * 158 * Time/date structures are packed by the server into a byte sub-packet 159 * with a leading length byte, and a minimal number of bytes to embody the data. 160 * 161 * Params: a = slice of a protocol packet beginning at the length byte for a chunk of time data 162 * Returns: A populated or default initialized std.datetime.TimeOfDay struct. 163 */ 164 TimeOfDay toTimeOfDay(ubyte[] a) 165 { 166 enforceEx!MYX(a.length, "Supplied byte array is zero length"); 167 TimeOfDay tod; 168 uint l = a[0]; 169 enforceEx!MYX(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row."); 170 171 enforceEx!MYX(l >= 8, "Time column value is not in a time-of-day format"); 172 tod.hour = a[6]; 173 tod.minute = a[7]; 174 tod.second = a[8]; 175 return tod; 176 } 177 178 /** 179 * Function to extract a TimeOfDay from a text encoded column value. 180 * 181 * Text representations of a time of day are as in 14:22:02 182 * 183 * Params: s = A string representation of the time. 184 * Returns: A populated or default initialized std.datetine.TimeOfDay struct. 185 */ 186 TimeOfDay toTimeOfDay(string s) 187 { 188 TimeOfDay tod; 189 tod.hour = parse!int(s); 190 enforceEx!MYX(tod.hour <= 24 && tod.hour >= 0, "Time column value is in time difference form"); 191 munch(s, ":"); 192 tod.minute = parse!ubyte(s); 193 munch(s, ":"); 194 tod.second = parse!ubyte(s); 195 return tod; 196 } 197 198 /** 199 * Function to pack a TimeOfDay into a binary encoding for transmission to the server. 200 * 201 * Time/date structures are packed into a string of bytes with a leading length byte, 202 * and a minimal number of bytes to embody the data. 203 * 204 * Params: tod = TimeOfDay struct. 205 * Returns: Packed ubyte[]. 206 */ 207 ubyte[] pack(TimeOfDay tod) 208 { 209 ubyte[] rv; 210 if (tod == TimeOfDay.init) 211 { 212 rv.length = 1; 213 rv[0] = 0; 214 return rv; 215 } 216 rv.length = 9; 217 rv[0] = 8; 218 rv[6] = tod.hour; 219 rv[7] = tod.minute; 220 rv[8] = tod.second; 221 return rv; 222 } 223 224 /** 225 * Function to extract a Date from a binary encoded row. 226 * 227 * Time/date structures are packed by the server into a byte sub-packet 228 * with a leading length byte, and a minimal number of bytes to embody the data. 229 * 230 * Params: a = slice of a protocol packet beginning at the length byte for a chunk of Date data 231 * Returns: A populated or default initialized std.datetime.Date struct. 232 */ 233 Date toDate(ubyte[] a) 234 { 235 enforceEx!MYX(a.length, "Supplied byte array is zero length"); 236 if (a[0] == 0) 237 return Date(0,0,0); 238 enforceEx!MYX(a[0] >= 4, "Binary date representation is too short"); 239 int year = (a[2] << 8) + a[1]; 240 int month = cast(int) a[3]; 241 int day = cast(int) a[4]; 242 return Date(year, month, day); 243 } 244 245 /** 246 * Function to extract a Date from a text encoded column value. 247 * 248 * Text representations of a Date are as in 2011-11-11 249 * 250 * Params: a = A string representation of the time difference. 251 * Returns: A populated or default initialized std.datetime.Date struct. 252 */ 253 Date toDate(string s) 254 { 255 int year = parse!(ushort)(s); 256 munch(s, "-"); 257 int month = parse!(ubyte)(s); 258 munch(s, "-"); 259 int day = parse!(ubyte)(s); 260 return Date(year, month, day); 261 } 262 263 /** 264 * Function to pack a Date into a binary encoding for transmission to the server. 265 * 266 * Time/date structures are packed into a string of bytes with a leading length byte, 267 * and a minimal number of bytes to embody the data. 268 * 269 * Params: dt = std.datetime.Date struct. 270 * Returns: Packed ubyte[]. 271 */ 272 ubyte[] pack(Date dt) 273 { 274 ubyte[] rv; 275 if (dt.year < 0) 276 { 277 rv.length = 1; 278 rv[0] = 0; 279 return rv; 280 } 281 rv.length = 4; 282 rv[1] = cast(ubyte) (dt.year & 0xff); 283 rv[2] = cast(ubyte) ((dt.year >> 8) & 0xff); 284 rv[3] = cast(ubyte) dt.month; 285 rv[4] = cast(ubyte) dt.day; 286 rv[0] = 4; 287 return rv; 288 } 289 290 /** 291 * Function to extract a DateTime from a binary encoded row. 292 * 293 * Time/date structures are packed by the server into a byte sub-packet 294 * with a leading length byte, and a minimal number of bytes to embody the data. 295 * 296 * Params: a = slice of a protocol packet beginning at the length byte for a chunk of 297 * DateTime data 298 * Returns: A populated or default initialized std.datetime.DateTime struct. 299 */ 300 DateTime toDateTime(ubyte[] a) 301 { 302 enforceEx!MYX(a.length, "Supplied byte array is zero length"); 303 DateTime dt; 304 if (a[0] == 0) 305 return dt; 306 enforceEx!MYX(a[0] >= 4, "Supplied ubyte[] is not long enough"); 307 int year = (a[2] << 8) + a[1]; 308 int month = a[3]; 309 int day = a[4]; 310 if (a[0] == 4) 311 { 312 dt = DateTime(year, month, day); 313 return dt; 314 } 315 enforceEx!MYX(a[0] >= 7, "Supplied ubyte[] is not long enough"); 316 int hour = a[5]; 317 int minute = a[6]; 318 int second = a[7]; 319 dt = DateTime(year, month, day, hour, minute, second); 320 return dt; 321 } 322 323 /** 324 * Function to extract a DateTime from a text encoded column value. 325 * 326 * Text representations of a DateTime are as in 2011-11-11 12:20:02 327 * 328 * Params: a = A string representation of the time difference. 329 * Returns: A populated or default initialized std.datetime.DateTime struct. 330 */ 331 DateTime toDateTime(string s) 332 { 333 int year = parse!(ushort)(s); 334 munch(s, "-"); 335 int month = parse!(ubyte)(s); 336 munch(s, "-"); 337 int day = parse!(ubyte)(s); 338 munch(s, " "); 339 int hour = parse!(ubyte)(s); 340 munch(s, ":"); 341 int minute = parse!(ubyte)(s); 342 munch(s, ":"); 343 int second = parse!(ubyte)(s); 344 return DateTime(year, month, day, hour, minute, second); 345 } 346 347 /** 348 * Function to extract a DateTime from a ulong. 349 * 350 * This is used to support the TimeStamp struct. 351 * 352 * Params: x = A ulong e.g. 20111111122002UL. 353 * Returns: A populated std.datetime.DateTime struct. 354 */ 355 DateTime toDateTime(ulong x) 356 { 357 int second = cast(int) x%100; 358 x /= 100; 359 int minute = cast(int) x%100; 360 x /=100; 361 int hour = cast(int) x%100; 362 x /=100; 363 int day = cast(int) x%100; 364 x /=100; 365 int month = cast(int) x%100; 366 x /=100; 367 int year = cast(int) x%10000; 368 // 2038-01-19 03:14:07 369 enforceEx!MYX(year >= 1970 && year < 2039, "Date/time out of range for 2 bit timestamp"); 370 enforceEx!MYX(year == 2038 && (month > 1 || day > 19 || hour > 3 || minute > 14 || second > 7), 371 "Date/time out of range for 2 bit timestamp"); 372 return DateTime(year, month, day, hour, minute, second); 373 } 374 375 /** 376 * Function to pack a DateTime into a binary encoding for transmission to the server. 377 * 378 * Time/date structures are packed into a string of bytes with a leading length byte, 379 * and a minimal number of bytes to embody the data. 380 * 381 * Params: dt = std.datetime.DateTime struct. 382 * Returns: Packed ubyte[]. 383 */ 384 ubyte[] pack(DateTime dt) 385 { 386 uint len = 1; 387 if (dt.year || dt.month || dt.day) len = 5; 388 if (dt.hour || dt.minute|| dt.second) len = 8; 389 ubyte[] rv; 390 rv.length = len; 391 if (len == 1) 392 { 393 rv[0] = 0; 394 return rv; 395 } 396 rv[1] = cast(ubyte) (dt.year & 0xff); 397 rv[2] = cast(ubyte) ((dt.year >> 8) & 0xff); 398 rv[3] = cast(ubyte) dt.month; 399 rv[4] = cast(ubyte) dt.day; 400 if (len == 5) 401 { 402 rv[0] = 4; 403 return rv; 404 } 405 rv[5] = cast(ubyte) dt.hour; 406 rv[6] = cast(ubyte) dt.minute; 407 rv[7] = cast(ubyte) dt.second; 408 rv[0] = 7; 409 return rv; 410 } 411 412 /** 413 * A D struct to stand for a TIMESTAMP 414 * 415 * It is assumed that insertion of TIMESTAMP values will not be common, since in general, 416 * such columns are used for recording the time of a row insertion, and are filled in 417 * automatically by the server. If you want to force a timestamp value in a prepared insert, 418 * set it into a timestamp struct as an unsigned long in the format YYYYMMDDHHMMSS 419 * and use that for the approriate parameter. When TIMESTAMPs are retrieved as part of 420 * a result set it will be as DateTime structs. 421 */ 422 struct Timestamp 423 { 424 ulong rep; 425 } 426 427 /** 428 * Server capability flags. 429 * 430 * During the connection handshake process, the server sends a uint of flags describing its 431 * capabilities 432 */ 433 enum SvrCapFlags: uint 434 { 435 SECURE_PWD = 1, /// Long passwords 436 FOUND_NOT_AFFECTED = 2, /// Report rows found rather than rows affected 437 ALL_COLUMN_FLAGS = 4, /// Send all column flags 438 WITH_DB = 8, /// Can take database as part of login 439 NO_SCHEMA = 16, /// Can disallow database name as part of column name database.table.column 440 CAN_COMPRESS = 32, /// Can compress packets 441 ODBC = 64, /// Can handle ODBC 442 LOCAL_FILES = 128, /// Can use LOAD DATA LOCAL 443 IGNORE_SPACE = 256, /// Can ignore spaces before '(' 444 PROTOCOL41 = 512, /// Can use 4.1+ protocol 445 INTERACTIVE = 1024, /// Interactive client? 446 SSL = 2048, /// Can switch to SSL after handshake 447 IGNORE_SIGPIPE = 4096, /// Ignore sigpipes? 448 TRANSACTIONS = 8192, /// Transaction support 449 RESERVED = 16384, 450 SECURE_CONNECTION = 32768, /// 4.1+ authentication 451 MULTI_STATEMENTS = 65536, /// Multiple statement support 452 MULTI_RESULTS = 131072 /// Multiple result set support 453 } 454 // 000000001111011111111111 455 immutable uint defaultClientFlags = 456 SvrCapFlags.SECURE_PWD | SvrCapFlags.ALL_COLUMN_FLAGS | 457 SvrCapFlags.WITH_DB | SvrCapFlags.PROTOCOL41 | 458 SvrCapFlags.SECURE_CONNECTION;// | SvrCapFlags.MULTI_STATEMENTS | 459 //SvrCapFlags.MULTI_RESULTS; 460 461 /** 462 * Column type codes 463 * 464 * DEFAULT means infer parameter type or column type from D variable type. 465 * 466 */ 467 enum SQLType 468 { 469 DEFAULT = -1, 470 DECIMAL = 0x00, 471 TINY = 0x01, 472 SHORT = 0x02, 473 INT = 0x03, 474 FLOAT = 0x04, 475 DOUBLE = 0x05, 476 NULL = 0x06, 477 TIMESTAMP = 0x07, 478 LONGLONG = 0x08, 479 INT24 = 0x09, 480 DATE = 0x0a, 481 TIME = 0x0b, 482 DATETIME = 0x0c, 483 YEAR = 0x0d, 484 NEWDATE = 0x0e, 485 VARCHAR = 0x0f, // new in MySQL 5.0 486 BIT = 0x10, // new in MySQL 5.0 487 NEWDECIMAL = 0xf6, // new in MYSQL 5.0 488 ENUM = 0xf7, 489 SET = 0xf8, 490 TINYBLOB = 0xf9, 491 MEDIUMBLOB = 0xfa, 492 LONGBLOB = 0xfb, 493 BLOB = 0xfc, 494 VARSTRING = 0xfd, 495 STRING = 0xfe, 496 GEOMETRY = 0xff 497 } 498 499 /** 500 * Server refresh flags 501 */ 502 enum RefreshFlags 503 { 504 GRANT = 1, 505 LOG = 2, 506 TABLES = 4, 507 HOSTS = 8, 508 STATUS = 16, 509 THREADS = 32, 510 SLAVE = 64, 511 MASTER = 128 512 } 513 514 ushort getShort(ref ubyte* ubp) 515 { 516 ushort us; 517 us |= ubp[1]; 518 us <<= 8; 519 us |= ubp[0]; 520 ubp += 2; 521 return us; 522 } 523 524 525 uint getInt(ref ubyte* ubp) 526 { 527 uint rv = (ubp[3] << 24) + (ubp[2] << 16) + (ubp[1] << 8) + ubp[0]; 528 ubp += 4; 529 return rv; 530 } 531 532 uint getInt24(ref ubyte* ubp) 533 { 534 uint rv = (ubp[2] << 16) + (ubp[1] << 8) + ubp[0]; 535 ubp += 3; 536 return rv; 537 } 538 539 ulong parseLCB(ref ubyte* ubp, out bool nullFlag) 540 { 541 nullFlag = false; 542 if (*ubp < 251) 543 { 544 return cast(ulong) *ubp++; 545 } 546 ulong t; 547 switch (*ubp) 548 { 549 case 251: 550 nullFlag = true; 551 ubp++; 552 return 0; 553 case 252: 554 t |= ubp[2]; 555 t <<= 8; 556 t |= ubp[1]; 557 ubp += 3; 558 return t; 559 case 253: 560 t |= ubp[3]; 561 t <<= 8; 562 t |= ubp[2]; 563 t <<= 8; 564 t |= ubp[1]; 565 ubp += 4; 566 return t; 567 case 254: 568 t |= ubp[8]; 569 t <<= 8; 570 t |= ubp[7]; 571 t <<= 8; 572 t |= ubp[6]; 573 t <<= 8; 574 t |= ubp[5]; 575 t <<= 8; 576 t |= ubp[4]; 577 t <<= 8; 578 t |= ubp[3]; 579 t <<= 8; 580 t |= ubp[2]; 581 t <<= 8; 582 t |= ubp[1]; 583 ubp += 9; 584 return t; 585 case 255: 586 default: 587 throw new MYX("The input value corresponds to an error packet.", __FILE__, __LINE__); 588 } 589 } 590 591 ubyte[] parseLCS(ref ubyte* ubp, out bool nullFlag) 592 { 593 ubyte[] mt; 594 ulong ul = parseLCB(ubp, nullFlag); 595 if (nullFlag) 596 return null; 597 if (ul == 0) 598 return mt; 599 enforceEx!MYX(ul <= uint.max, "Protocol Length Coded String is too long"); 600 uint len = cast(uint) ul; 601 ubyte* t = ubp; 602 ubp += len; 603 return t[0..len].dup; 604 } 605 606 ubyte[] packLength(size_t l, out size_t offset) 607 { 608 ubyte[] t; 609 if (!l) 610 { 611 t.length = 1; 612 t[0] = 0; 613 return t; 614 } 615 if (l <= 250) 616 { 617 t.length = 1+l; 618 t[0] = cast(ubyte) l; 619 offset = 1; 620 return t; 621 } 622 else if (l <= 0xffff) 623 { 624 t.length = 3+l; 625 t[0] = 252; 626 t[1] = cast(ubyte) (l & 0xff); 627 t[2] = cast(ubyte) ((l >> 8) & 0xff); 628 offset = 3; 629 return t; 630 } 631 else if (l < 0xffffff) 632 { 633 t.length = 4+l; 634 t[0] = 253; 635 t[1] = cast(ubyte) (l & 0xff); 636 t[2] = cast(ubyte) ((l >> 8) & 0xff); 637 t[3] = cast(ubyte) ((l >> 16) & 0xff); 638 offset = 4; 639 return t; 640 } 641 else 642 { 643 ulong u = cast(ulong) l; 644 t.length = 9+l; 645 t[0] = 254; 646 t[1] = cast(ubyte) (u & 0xff); 647 t[2] = cast(ubyte) ((u >> 8) & 0xff); 648 t[3] = cast(ubyte) ((u >> 16) & 0xff); 649 t[4] = cast(ubyte) ((u >> 24) & 0xff); 650 t[5] = cast(ubyte) ((u >> 32) & 0xff); 651 t[6] = cast(ubyte) ((u >> 40) & 0xff); 652 t[7] = cast(ubyte) ((u >> 48) & 0xff); 653 t[8] = cast(ubyte) ((u >> 56) & 0xff); 654 offset = 9; 655 return t; 656 } 657 } 658 659 ubyte[] packLCS(void[] a) 660 { 661 size_t offset; 662 ubyte[] t = packLength(a.length, offset); 663 if (t[0]) 664 t[offset..$] = (cast(ubyte[]) a)[0..$]; 665 return t; 666 } 667 668 unittest 669 { 670 bool isnull; 671 ubyte[] uba = [ 0xde, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x01, 0x00 ]; 672 ubyte* ps = uba.ptr; 673 ubyte* ubp = uba.ptr; 674 ulong ul = parseLCB(ubp, isnull); 675 assert(ul == 0xde && !isnull && ubp == ps+1); 676 ubp = ps; 677 uba[0] = 251; 678 ul = parseLCB(ubp, isnull); 679 assert(ul == 0 && isnull && ubp == ps+1); 680 ubp = ps; 681 uba[0] = 252; 682 ul = parseLCB(ubp, isnull); 683 assert(ul == 0xbbcc && !isnull && ubp == ps+3); 684 ubp = ps; 685 uba[0] = 253; 686 ul = parseLCB(ubp, isnull); 687 assert(ul == 0xaabbcc && !isnull && ubp == ps+4); 688 ubp = ps; 689 uba[0] = 254; 690 ul = parseLCB(ubp, isnull); 691 assert(ul == 0x5566778899aabbcc && !isnull && ubp == ps+9); 692 ubyte[] buf; 693 buf.length = 0x2000200; 694 buf[] = '\x01'; 695 buf[0] = 250; 696 buf[1] = '<'; 697 buf[249] = '!'; 698 buf[250] = '>'; 699 ubp = buf.ptr; 700 ubyte[] x = parseLCS(ubp, isnull); 701 assert(x.length == 250 && x[0] == '<' && x[249] == '>'); 702 buf[] = '\x01'; 703 buf[0] = 252; 704 buf[1] = 0xff; 705 buf[2] = 0xff; 706 buf[3] = '<'; 707 buf[0x10000] = '*'; 708 buf[0x10001] = '>'; 709 ubp = buf.ptr; 710 x = parseLCS(ubp, isnull); 711 assert(x.length == 0xffff && x[0] == '<' && x[0xfffe] == '>'); 712 buf[] = '\x01'; 713 buf[0] = 253; 714 buf[1] = 0xff; 715 buf[2] = 0xff; 716 buf[3] = 0xff; 717 buf[4] = '<'; 718 buf[0x1000001] = '*'; 719 buf[0x1000002] = '>'; 720 ubp = buf.ptr; 721 x = parseLCS(ubp, isnull); 722 assert(x.length == 0xffffff && x[0] == '<' && x[0xfffffe] == '>'); 723 buf[] = '\x01'; 724 buf[0] = 254; 725 buf[1] = 0xff; 726 buf[2] = 0x00; 727 buf[3] = 0x00; 728 buf[4] = 0x02; 729 buf[5] = 0x00; 730 buf[6] = 0x00; 731 buf[7] = 0x00; 732 buf[8] = 0x00; 733 buf[9] = '<'; 734 buf[0x2000106] = '!'; 735 buf[0x2000107] = '>'; 736 ubp = buf.ptr; 737 x = parseLCS(ubp, isnull); 738 assert(x.length == 0x20000ff && x[0] == '<' && x[0x20000fe] == '>'); 739 } 740 741 742 /** 743 * A struct representing an OK or Error packet 744 * 745 * OK packets begin with a zero byte - Error packets with 0xff 746 * 747 */ 748 struct OKPacket 749 { 750 bool error; 751 bool nullFlag; 752 ulong affected; 753 ulong insertID; 754 ushort serverStatus; 755 ushort warnings; 756 char[5] sqlState; 757 char[] message; 758 759 this(ubyte* ubp, uint length) 760 { 761 ubyte* ps = ubp; // note packet start 762 ubyte* pe = ps+length; 763 if (*ubp) 764 { 765 error = true; 766 // it's not OK 767 enforceEx!MYX(*ubp == 255, "Malformed OK/Error packet"); 768 ubp++; 769 770 enforceEx!MYX(ubp+2 < pe, "Malformed OK/Error packet"); 771 serverStatus = getShort(ubp); // error code into server state 772 if (*ubp == cast(ubyte) '#') 773 { 774 //4.1+ error packet 775 ubp++; 776 enforceEx!MYX(ubp+5 < pe, "Malformed OK/Error packet"); 777 sqlState[] = cast(char[]) ubp[0..5]; 778 ubp += 5; 779 } 780 size_t rem = pe-ubp; 781 if (rem) 782 { 783 message.length = rem; 784 message[] = cast(char[]) ubp[0..rem]; 785 } 786 } 787 else 788 { 789 // It's OK - get supplied data 790 bool gash; 791 enforceEx!MYX(ubp+1 < pe, "Malformed OK/Error packet"); 792 ubp++; 793 affected = parseLCB(ubp, gash); 794 enforceEx!MYX(ubp+1 < pe, "Malformed OK/Error packet"); 795 insertID = parseLCB(ubp, gash); 796 enforceEx!MYX(ubp+2 < pe, "Malformed OK/Error packet"); 797 serverStatus = getShort(ubp); 798 enforceEx!MYX(ubp+2 <= pe, "Malformed OK/Error packet"); 799 warnings = getShort(ubp); 800 size_t rem = pe-ubp; 801 if (rem) 802 { 803 message.length = rem; 804 message[] = cast(char[]) ubp[0..rem]; 805 } 806 } 807 } 808 } 809 810 /** 811 * A struct representing a field (column) description packet 812 * 813 * These packets, one for each column are sent before the data of a result set, 814 * followed by an EOF packet. 815 */ 816 struct FieldDescription 817 { 818 private: 819 string _db; 820 string _table; 821 string _originalTable; 822 string _name; 823 string _originalName; 824 ushort _charSet; 825 uint _length; 826 uint _actualLength; 827 ushort _type; 828 ushort _flags; 829 ubyte _scale; 830 ulong _deflt; 831 uint chunkSize; 832 void delegate(ubyte[], bool) chunkDelegate; 833 834 public: 835 /** 836 * Construct a FieldDescription from the raw data packet 837 * 838 * Parameters: packet = The packet contents excluding the 4 byte packet header 839 */ 840 this(ubyte[] packet) 841 { 842 ubyte* sp = packet.ptr; 843 ubyte* ep = sp+packet.length; 844 ubyte* ubp = sp+4; // Skip catalog - it's always 'def' 845 bool isnull; 846 _db = cast(string) parseLCS(ubp, isnull); 847 _table = cast(string) parseLCS(ubp, isnull); 848 _originalTable = cast(string) parseLCS(ubp, isnull); 849 _name = cast(string) parseLCS(ubp, isnull); 850 _originalName = cast(string) parseLCS(ubp, isnull); 851 enforceEx!MYX(ep-ubp >= 13, "Malformed field specification packet"); 852 ubp++; // one byte of filler here 853 _charSet = getShort(ubp); 854 _length = getInt(ubp); 855 _type = *ubp++; 856 _flags = getShort(ubp); 857 _scale = *ubp++; 858 ubp += 2; // 2 bytes filler here 859 if (ubp < ep) 860 { 861 ubp++; // one byte filler 862 _deflt = parseLCB(ubp, isnull); 863 } 864 } 865 /// Database name for column as string 866 @property string db() { return _db; } 867 /// Table name for column as string - this could be an alias as in 'from tablename as foo' 868 @property string table() { return _table; } 869 /// Real table name for column as string 870 @property string originalTable() { return _originalTable; } 871 /// Column name as string - this could be an alias 872 @property string name() { return _name; } 873 /// Real column name as string 874 @property string originalName() { return _originalName; } 875 /// The character set in force 876 @property ushort charSet() { return _charSet; } 877 /// The 'length' of the column as defined at table creation 878 @property uint length() { return _length; } 879 /// The type of the column hopefully (but not always) corresponding to enum SQLType. Only the low byte currently used 880 @property ushort type() { return _type; } 881 /// Column flags - unsigned, binary, null and so on 882 @property ushort flags() { return _flags; } 883 /// Precision for floating point values 884 @property ubyte scale() { return _scale; } 885 /// NotNull from flags 886 @property bool notNull() { return (_flags & 1) != 0; } 887 /// Unsigned from flags 888 @property bool unsigned() { return (_flags & 0x20) != 0; } 889 /// Binary from flags 890 @property bool binary() { return (_flags & 0x80) != 0; } 891 /// Is-enum from flags 892 @property bool isenum() { return (_flags & 0x100) != 0; } 893 /// Is-set (a SET column that is) from flags 894 @property bool isset() { return (_flags & 0x800) != 0; } 895 896 void show() 897 { 898 writefln("%s %d %x %016b", _name, _length, _type, _flags); 899 } 900 } 901 902 /** 903 * A struct representing a prepared statement parameter description packet 904 * 905 * These packets, one for each parameter are sent in response to the prepare command, 906 * followed by an EOF packet. 907 * 908 * Sadly it seems that this facility is only a stub. The correct number of packets is sent, 909 * but they contain no useful information and are all the same. 910 */ 911 struct ParamDescription 912 { 913 private: 914 ushort _type; 915 ushort _flags; 916 ubyte _scale; 917 uint _length; 918 public: 919 this(ubyte[] packet) 920 { 921 ubyte* ubp = packet.ptr; 922 _type = getShort(ubp); 923 _flags = getShort(ubp); 924 _scale = *ubp++; 925 _length = getInt(ubp); 926 } 927 @property uint length() { return _length; } 928 @property ushort type() { return _type; } 929 @property ushort flags() { return _flags; } 930 @property ubyte scale() { return _scale; } 931 @property bool notNull() { return (_flags & 1) != 0; } 932 @property bool unsigned() { return (_flags & 0x20) != 0; } 933 } 934 935 /** 936 * A struct representing an EOF packet 937 * 938 * an EOF packet is sent after each sequence of field description and parameter description 939 * packets, and after a sequence of result set row packets. 940 * 941 * These EOF packets contain a server status and a warning count. 942 */ 943 struct EOFPacket 944 { 945 private: 946 ushort _warnings; 947 ushort _serverStatus; 948 public: 949 950 /** 951 * Construct an EOFPacket struct from the raw data packet 952 * 953 * Parameters: packet = The packet contents excluding the 4 byte packet header 954 */ 955 this(ubyte[] packet) 956 { 957 ubyte* ubp = packet.ptr; 958 ubyte* ep = ubp+packet.length; 959 assert(*ubp == 0xfe && ep-ubp == 5); 960 _warnings = getShort(ubp); 961 _serverStatus = getShort(ubp); 962 } 963 964 /// Retrieve the warning count 965 @property ushort warnings() { return _warnings; } 966 /// Retrieve the server status 967 @property ushort serverStatus() { return _serverStatus; } 968 } 969 970 /** 971 * A struct representing the collation of a sequence of FieldDescription packets. 972 * 973 * This data gets filled in after a query (prepared or otherwise) that creates a result set completes. 974 * All the FD packets, and an EOF packet must be eaten before the row data packets can be read. 975 */ 976 struct ResultSetHeaders 977 { 978 private: 979 uint _fieldCount; 980 FieldDescription[] _fieldDescriptions; 981 string[] _fieldNames; 982 ushort _warnings; 983 984 public: 985 986 /** 987 * Construct a ResultSetHeaders struct from a sequence of FieldDescription packets and an EOF packet. 988 * 989 * Parameters: 990 * con = A Connection via which the packets are read 991 * fieldCount = the number of fields/columns generated by the query 992 */ 993 this(Connection con, uint fieldCount) 994 { 995 ubyte[] packet; 996 ubyte* ubp; 997 uint pl, n; 998 ubyte pn; 999 _fieldCount = fieldCount; 1000 _fieldDescriptions.length = _fieldCount; 1001 _fieldNames.length = _fieldCount; 1002 foreach (uint i; 0.._fieldCount) 1003 { 1004 packet = con.getPacket(pl); 1005 FieldDescription t = FieldDescription(packet); 1006 _fieldDescriptions[i] = FieldDescription(packet); 1007 _fieldNames[i] = _fieldDescriptions[i]._name; 1008 } 1009 packet = con.getPacket(pl); 1010 ubp = packet.ptr; 1011 enforceEx!MYX(*ubp == 0xfe && pl < 9, "Expected EOF packet in result header sequence"); // check signature for EOF packet 1012 EOFPacket eof = EOFPacket(packet); 1013 con._serverStatus = eof._serverStatus; 1014 _warnings = eof._warnings; 1015 } 1016 1017 /** 1018 * Add specialization information to one or more field descriptions. 1019 * 1020 * Currently the only specialization supported is the capability to deal with long data 1021 * e.g. BLOB or TEXT data in chunks by stipulating a chunkSize and a delegate to sink 1022 * the data. 1023 * 1024 * Parameters: 1025 * csa = An array of ColumnSpecialization structs 1026 */ 1027 void addSpecializations(ColumnSpecialization[] csa) 1028 { 1029 foreach(CSN csn; csa) 1030 { 1031 enforceEx!MYX(csn.cIndex < _fieldCount && _fieldDescriptions[csn.cIndex].type == csn.type, 1032 "Column specialization index or type does not match the corresponding column."); 1033 _fieldDescriptions[csn.cIndex].chunkSize = csn.chunkSize; 1034 _fieldDescriptions[csn.cIndex].chunkDelegate = csn.chunkDelegate; 1035 } 1036 } 1037 1038 /// Index into the set of field descriptions 1039 FieldDescription opIndex(size_t i) { return _fieldDescriptions[i]; } 1040 /// Get the number of fields in a result row. 1041 @property fieldCount() { return _fieldCount; } 1042 /// Get the warning count as per the EOF packet 1043 @property ushort warnings() { return _warnings; } 1044 /// Get an array of strings representing the column names 1045 @property string[] fieldNames() { return _fieldNames; } 1046 1047 void show() 1048 { 1049 foreach (FieldDescription fd; _fieldDescriptions) 1050 fd.show(); 1051 } 1052 } 1053 1054 /** 1055 * A struct representing the collation of a prepared statement parameter description sequence 1056 * 1057 * As noted above - parameter descriptions are not fully implemented by MySQL. 1058 */ 1059 struct PreparedStmtHeaders 1060 { 1061 private: 1062 Connection _con; 1063 ushort _colCount, _paramCount; 1064 FieldDescription[] _colDescriptions; 1065 ParamDescription[] _paramDescriptions; 1066 ushort _warnings; 1067 1068 bool getEOFPacket() 1069 { 1070 ubyte[] packet; 1071 uint pl; 1072 packet = _con.getPacket(pl); 1073 ubyte* ubp = packet.ptr; 1074 if (*ubp != 0xfe) //signature for EOF packet 1075 return false; 1076 EOFPacket eof = EOFPacket(packet); 1077 _con._serverStatus = eof._serverStatus; 1078 _warnings += eof._warnings; 1079 return true; 1080 } 1081 1082 public: 1083 this(Connection con, ushort cols, ushort params) 1084 { 1085 _con = con; 1086 _colCount = cols; 1087 _paramCount = params; 1088 _colDescriptions.length = cols; 1089 _paramDescriptions.length = params; 1090 ubyte[] packet; 1091 // The order in which fields are sent is params first, followed by EOF, then cols followed by EOF 1092 // The parameter specs are useless - they are all the same. This observation is coroborated 1093 // by the fact that the C API does not have any information about parameter types either. 1094 // WireShark gives up on these records also. 1095 foreach (uint i; 0.._paramCount) 1096 { 1097 uint pl; 1098 _con.getPacket(pl); // just eat them - they are not useful 1099 } 1100 if (_paramCount) 1101 enforceEx!MYX(getEOFPacket(), "Expected EOF packet in result header sequence"); 1102 foreach(uint i; 0.._colCount) 1103 { 1104 uint pl; 1105 packet = _con.getPacket(pl); 1106 _colDescriptions[i] = FieldDescription(packet); 1107 } 1108 if (_colCount) 1109 enforceEx!MYX(getEOFPacket(), "Expected EOF packet in result header sequence"); 1110 } 1111 1112 ParamDescription param(size_t i) { return _paramDescriptions[i]; } 1113 FieldDescription col(size_t i) { return _colDescriptions[i]; } 1114 1115 @property paramCount() { return _paramCount; } 1116 @property ushort warnings() { return _warnings; } 1117 1118 void showCols() 1119 { 1120 writefln("%d columns", _colCount); 1121 foreach (FieldDescription fd; _colDescriptions) 1122 { 1123 writefln("%10s %10s %10s %10s %10s %d %d %02x %016b %d", 1124 fd._db, fd._table, fd._originalTable, fd._name, fd._originalName, fd._charSet, fd._length, fd._type, fd._flags, fd._scale); 1125 } 1126 } 1127 } 1128 1129 /** 1130 * A struct representing a database connection. 1131 * 1132 * The Connection is responsible for handshaking with the server to establish authentication. 1133 * It then passes client preferences to the server, and subsequently is the channel for all 1134 * command packets that are sent, and all response packets received. 1135 * 1136 * Uncompressed packets consist of a 4 byte header - 3 bytes of length, and one byte as a packet 1137 * number. Connection deals with the headers and ensures that packet numbers are sequential. 1138 * 1139 * The initial packet is sent by the server - esentially a 'hello' packet inviting login. That packet 1140 * has a sequence number of zero. That sequence number is the incremented by cliemt and server 1141 * packets thruogh the handshake sequence. 1142 * 1143 * After login all further sequences are initialized by the client sending a command packet with a 1144 * zero sequence number, to which the server replies with zero or more packets with sequential 1145 * sequence numbers. 1146 */ 1147 class Connection 1148 { 1149 protected: 1150 TcpConnection _socket; 1151 int _open; 1152 ubyte[] _packet; 1153 ubyte[4] _hdr; 1154 uint _sCaps, _sThread, _cCaps; 1155 ushort _serverStatus; 1156 ubyte _sCharSet, _protocol; 1157 ubyte[] _authBuf; 1158 ushort _pl; 1159 1160 // This tiny thing here is pretty critical. Pay great attention to it's maintenance, otherwise 1161 // you'll get the dreaded "packet out of order" message. It, and the socket connection are 1162 // the reason why most other objects require a connection object for their construction. 1163 ubyte _cpn; 1164 string _serverVersion; 1165 string _host, _user, _pwd, _db; 1166 ushort _port; 1167 ubyte[] _token; 1168 1169 @property pktNumber() { return _cpn; } 1170 void bumpPacket() { _cpn++; } 1171 void resetPacket() { _cpn = 0; } 1172 1173 ubyte[] getPacket(out uint pl) 1174 { 1175 _socket.read(_hdr); 1176 pl = (_hdr[2] << 16) + (_hdr[1] << 8) + _hdr[0]; 1177 ubyte pn = _hdr[3]; 1178 enforceEx!MYX(pn == _cpn, "Server packet out of order"); 1179 _cpn++; 1180 _packet.length = pl; 1181 _socket.read(_packet); 1182 return _packet.dup; 1183 } 1184 1185 void send(ubyte[] packet) 1186 { 1187 _socket.write(packet); 1188 } 1189 1190 void sendCmd(ubyte cmd, string s) 1191 { 1192 _cpn = 0; 1193 size_t pl =s.length+1; 1194 _packet.length = pl+4; 1195 _packet[0] = cast(ubyte) (pl & 0xff); 1196 _packet[1] = cast(ubyte) ((pl >> 8) & 0xff); 1197 _packet[2] = cast(ubyte) ((pl >> 16) & 0xff); 1198 _packet[3] = 0; 1199 _packet[4] = cmd; 1200 _packet[5 .. s.length+5] = (cast(ubyte[]) s)[0..$]; 1201 _cpn++; 1202 _socket.write(_packet); 1203 } 1204 1205 OKPacket getCmdResponse(bool asString = false) 1206 { 1207 uint pl; 1208 getPacket(pl); 1209 ubyte* ubp = _packet.ptr; 1210 OKPacket okp = OKPacket(ubp, pl); 1211 enforceEx!MYX(!okp.error, "MySQL error: " ~ cast(string) okp.message); 1212 _serverStatus = okp.serverStatus; 1213 return okp; 1214 } 1215 1216 uint buildAuthPacket() 1217 { 1218 _packet[] = 0; 1219 ubyte* p = _packet.ptr+4; 1220 // Set the default capabilities required by the client into the first four bytes 1221 *p++ = cast(ubyte) (_cCaps & 0xff); 1222 *p++ = cast(ubyte) ((_cCaps >> 8) & 0xff); 1223 *p++ = cast(ubyte) ((_cCaps >> 16) & 0xff); 1224 *p++ = cast(ubyte) ((_cCaps >> 24) & 0xff); 1225 // Request a conventional maximum packet length. 1226 *p++ = 0; 1227 *p++ = 0; 1228 *p++ = 0; 1229 *p++ = 1; 1230 // Request utf-8 as default charSet 1231 *p++ = 33; 1232 // There's a statutory block of zero bytes here - fill them in. 1233 foreach (int i; 0..23) 1234 *p++ = 0; 1235 // Add the user name as a null terminated string 1236 foreach (i; 0.._user.length) 1237 *p++ = _user[i]; 1238 *p++ = 0; 1239 // Add our calculated authentication token as a length prefixed string. It is basically a 1240 // SHA1 hash, so we know how long is is. 1241 *p++ = 20; 1242 foreach (uint i; 0..20) 1243 *p++ = _token[i]; 1244 // if the default database is being set, add this finally as a null terminated string. 1245 if (_db.length) 1246 { 1247 foreach (i; 0.._db.length) 1248 *p++ = _db[i]; 1249 *p++ = 0; 1250 } 1251 // Now we can determine the size of the packet and trim the array to that length. 1252 size_t pl = p - _packet.ptr; 1253 _packet.length = pl; 1254 // Back to the beginning of the packet 1255 p = _packet.ptr; 1256 // The calculated length is from the packet start as opposed to the content start, 1257 // so allow for the packet header before we fill it in. 1258 pl -= 4; 1259 // The server sent us a greeting with packet number 0, so we send the auth packet 1260 // back with the next number. 1261 p[3] = _cpn++; 1262 // Fill in the logical packet length. We can skip the most significant byte, since at the worst 1263 // it is a short packet. 1264 p[2] = 0; 1265 p[1] = cast(ubyte) ((pl >> 8) & 0xff); 1266 p[0] = cast(ubyte) (pl & 0xff); 1267 // Hopefully at this point it is ready to send. 1268 assert(pl <= uint.max); 1269 return cast(uint)pl; 1270 } 1271 1272 void getServerInfo(ref ubyte* p) 1273 { 1274 _sCaps = (p[6] << 24) + (p[5] << 16) + (p[1] << 8) + p[0]; 1275 _sCharSet = p[2]; 1276 _serverStatus = (p[4] << 8) + p[3]; 1277 p += 7; 1278 } 1279 1280 void parseGreeting() 1281 { 1282 // TODO: make the following code work instead of the leastSize workaround below: 1283 // read the handshake message from the socket 1284 /*_packet.length = 255; 1285 ubyte[] dst = _packet; 1286 _socket.read(dst[0 .. 1]); 1287 dst.popFront(); 1288 1289 // read server version string 1290 while(true){ 1291 _socket.read(dst[0 .. 1]); 1292 bool end = dst.front == 0; 1293 dst.popFront(); 1294 if( end ) break; 1295 } 1296 1297 // read the fields in the mid of the packet 1298 size_t fields_size = 4+8+1+2+1+2+2+1+10; 1299 _socket.read(dst[0 .. fields_size]); 1300 ubyte scramble_length = dst[$-11]; 1301 dst.popFrontN(fields_size); 1302 1303 // read the scramble and the terminating null byte 1304 _socket.read(dst[0 .. scramble_length+1]); 1305 enforce(dst[scramble_length] == 0, "Handshake packet must be zero terminated."); 1306 1307 dst.popFrontN(scramble_length+1); 1308 _packet.length = _packet.length - dst.length;*/ 1309 1310 // for now we leave the original behavior to use the network to determine the packet size 1311 _packet.length = cast(size_t)_socket.leastSize; 1312 _socket.read(_packet); 1313 1314 _cpn++; 1315 1316 // parse the read buffer 1317 ubyte* p = _packet.ptr+4; 1318 _protocol = *p++; 1319 size_t len, offset; 1320 ubyte* q = p; 1321 offset = q-_packet.ptr; 1322 while (*p) p++; 1323 len = p-q; 1324 _serverVersion = cast(string) _packet[offset..offset+len].idup; 1325 p++; 1326 _sThread = getInt(p); 1327 _authBuf.length = 255; 1328 foreach (uint i; 0..8) 1329 _authBuf[i] = *p++; 1330 assert(*p == 0); 1331 p++; 1332 getServerInfo(p); 1333 p++; // this byte supposed to be scramble length, but is actually zero 1334 p += 10; // skip 10 bytes of filler 1335 len = 8; 1336 for (uint i = 0; *p; i++, len++) 1337 _authBuf[8+i] = *p++; 1338 _authBuf.length = len; 1339 assert(*p == 0); 1340 } 1341 1342 void init_connection() 1343 { 1344 _socket = connectTcp(_host, _port); 1345 //_socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVBUF, (1 << 24)-1); 1346 int rbs; 1347 //_socket.getOption(SocketOptionLevel.SOCKET, SocketOption.RCVBUF, rbs); 1348 //_rbs = rbs; 1349 } 1350 1351 ubyte[] makeToken() 1352 { 1353 SHA1 sha1; 1354 sha1.reset(); 1355 ubyte[] pass1; 1356 ubyte[] pass2; 1357 ubyte[] cat; 1358 ubyte[] result; 1359 sha1.input(cast(const(ubyte)*) _pwd.ptr, _pwd.length); 1360 pass1 = sha1.result(); 1361 sha1.reset(); 1362 sha1.input(pass1.ptr, pass1.length); 1363 pass2 = sha1.result(); 1364 sha1.reset(); 1365 sha1.input(_authBuf.ptr, _authBuf.length); 1366 sha1.input(pass2.ptr, pass2.length); 1367 result = sha1.result(); 1368 foreach (uint i; 0..20) 1369 result[i] = result[i] ^ pass1[i]; 1370 return result; 1371 } 1372 1373 void setClientFlags(uint capFlags) 1374 { 1375 uint filter = 1; 1376 uint sCaps = _sCaps; 1377 uint cCaps = 0; 1378 foreach (uint i; 0..24) 1379 { 1380 if (filter & _sCaps) // can the server do this capability? 1381 { 1382 if (filter & capFlags) 1383 cCaps |= filter; 1384 } 1385 filter <<= 1; 1386 } 1387 _cCaps = cCaps; 1388 } 1389 1390 static string[] parseConnectionString(string cs) 1391 { 1392 string[] rv; 1393 rv.length = 4; 1394 string[] a = split(cs, ";"); 1395 foreach (s; a) 1396 { 1397 string[] a2 = split(s, "="); 1398 if (a2.length != 2) 1399 throw new Exception("Bad connection string: " ~ cs); 1400 string name = strip(a2[0]); 1401 string val = strip(a2[1]); 1402 switch (name) 1403 { 1404 case "host": 1405 rv[0] = val; 1406 break; 1407 case "user": 1408 rv[1] = val; 1409 break; 1410 case "pwd": 1411 rv[2] = val; 1412 break; 1413 case "db": 1414 rv[3] = val; 1415 break; 1416 case "port": 1417 rv[4] = val; 1418 break; 1419 default: 1420 throw new Exception("Bad connection string: " ~ cs); 1421 } 1422 } 1423 return rv; 1424 } 1425 1426 bool open() 1427 { 1428 _token = makeToken(); 1429 buildAuthPacket(); 1430 _socket.write(_packet); 1431 uint pl; 1432 getPacket(pl); 1433 ubyte* ubp = _packet.ptr; 1434 OKPacket okp = OKPacket(ubp, pl); 1435 enforceEx!MYX(!okp.error, "Authentication failure: " ~ cast(string) okp.message); 1436 _open = 2; 1437 return true; 1438 } 1439 1440 ~this() { if (_open) close(); } 1441 1442 public: 1443 1444 /** 1445 * Construct opened connection. 1446 * 1447 * After the connection is created, and the initial invitation is received from the server 1448 * client preferences can be set, and authentication can then be attempted. 1449 * 1450 * Parameters: 1451 * host = An IP address in numeric dotted form, or as a host name. 1452 * user = The user name to authenticate. 1453 * password = Users password. 1454 * db = Desired initial database. 1455 * capFlags = The set of flag bits from the server's capabilities that the client requires 1456 */ 1457 this(string host, string user, string pwd, string db, ushort port = 3306, uint capFlags = defaultClientFlags) 1458 { 1459 _host = host; 1460 _user = user; 1461 _pwd = pwd; 1462 _db = db; 1463 _port = port; 1464 init_connection(); 1465 parseGreeting(); 1466 _open = 1; 1467 setClientFlags(capFlags); 1468 open(); 1469 } 1470 1471 /** 1472 * Construct opened connection. 1473 * 1474 * After the connection is created, and the initial invitation is received from the server 1475 * client preferences are set, and authentication can then be attempted. 1476 * 1477 * TBD The connection string needs work to allow for semicolons in its parts! 1478 * 1479 * Parameters: 1480 * cs = A connetion string of the form "host=localhost;user=user;pwd=password;db=mysqld" 1481 * capFlags = The set of flag bits from the server's capabilities that the client requires 1482 */ 1483 this(string cs, uint capFlags = defaultClientFlags) 1484 { 1485 string[] a = parseConnectionString(cs); 1486 _host = a[0]; 1487 _user = a[1]; 1488 _pwd = a[2]; 1489 _db = a[3]; 1490 _port = to!(ushort)(a[4]); 1491 init_connection(); 1492 parseGreeting(); 1493 _open = 1; 1494 setClientFlags(capFlags); 1495 open(); 1496 } 1497 /** 1498 * Explicitly close the connection. 1499 * 1500 * This is a two-stage process. First tell the server we are quitting this connection, and 1501 * then close the socket. 1502 * 1503 * Idiomatic use as follows is suggested: 1504 ------------------ 1505 { 1506 auto con = Connection("localhost:user:password:mysqld"); 1507 scope(exit) con.close(); 1508 // Use the connection 1509 ... 1510 } 1511 ------------------ 1512 */ 1513 void close() 1514 { 1515 if (_open > 1) 1516 { 1517 _packet.length = 5; 1518 _packet[] = [1, 0, 0, 0, 1 ]; 1519 _socket.write(_packet); 1520 _open--; 1521 } 1522 if (_open) 1523 { 1524 _socket.close(); 1525 } 1526 _open = 0; 1527 _cpn = 0; 1528 } 1529 1530 /** 1531 * Select a current database. 1532 * 1533 * Params: dbName = Name of the requested database 1534 * Throws: MySQLEcception 1535 */ 1536 void selectDB(string dbName) 1537 { 1538 sendCmd(2, dbName); 1539 getCmdResponse(); 1540 _db = dbName; 1541 } 1542 1543 /** 1544 * Check the server status 1545 * 1546 * Returns: An OKPacket from which server status can be determined 1547 * Throws: MySQLEcception 1548 */ 1549 OKPacket pingServer() 1550 { 1551 sendCmd(0x0e, ""); 1552 return getCmdResponse(); 1553 } 1554 1555 /** 1556 * Refresh some feature[s] of he server. 1557 * 1558 * Returns: An OKPacket from which server status can be determined 1559 * Throws: MySQLEcception 1560 */ 1561 OKPacket refreshServer(int flags) 1562 { 1563 ubyte[] t; 1564 t.length = 1; 1565 t[0] = cast(ubyte) flags; 1566 sendCmd(0x07, cast(string) t); 1567 return getCmdResponse(); 1568 } 1569 1570 /** 1571 * Get a textual report on the servr status. 1572 * 1573 */ 1574 string serverStats() 1575 { 1576 sendCmd(0x09, ""); 1577 uint pl; 1578 getPacket(pl); 1579 return cast(string) _packet; 1580 } 1581 1582 /** 1583 * Enable multiple statement commands 1584 * 1585 * This can be used later if this feature was not requested in the client capability flags. 1586 * 1587 * Params: on = Boolean value to turn th capability on or off. 1588 */ 1589 void enableMultiStatements(bool on) 1590 { 1591 ubyte[] t; 1592 t.length = 2; 1593 t[0] = on? 0: 1; 1594 t[1] = 0; 1595 sendCmd(0x1b, cast(string) t); 1596 1597 // For some reason this command gets an EOF packet as response 1598 uint pl; 1599 getPacket(pl); 1600 enforceEx!MYX(_packet[0] == 254 && pl == 5, "Unexpected response to SET_OPTION command"); 1601 } 1602 1603 /// Return the in-force protocol number 1604 @property ubyte protocol() { return _protocol; } 1605 /// Server version 1606 @property string serverVersion() { return _serverVersion; } 1607 /// Server capability flags 1608 @property uint serverCapabilities() { return _sCaps; } 1609 /// Server status 1610 @property ushort serverStatus() { return _serverStatus; } 1611 /// Current character set 1612 @property ubyte charSet() { return _sCharSet; } 1613 /// Current database 1614 @property string currentDB() { return _db; } 1615 } 1616 1617 unittest 1618 { 1619 bool ok = true; 1620 try 1621 { 1622 auto c = new Connection("host=localhost;user=user;pwd=password;db=mysqld"); 1623 scope(exit) c.close(); 1624 // These may vary according to the server setup 1625 assert(c.protocol == 10); 1626 assert(c.serverVersion == "5.1.54-1ubuntu4"); 1627 assert(c.serverCapabilities == 0b1111011111111111); 1628 assert(c.serverStatus == 2); 1629 assert(c.charSet == 8); 1630 try { 1631 c.selectDB("rabbit"); 1632 } 1633 catch (Exception x) 1634 { 1635 assert(x.msg.indexOf("Access denied") > 0); 1636 } 1637 OKPacket okp = c.pingServer(); 1638 assert(okp.serverStatus == 2); 1639 try { 1640 okp = c.refreshServer(RefreshFlags.GRANT); 1641 } 1642 catch (Exception x) 1643 { 1644 assert(x.msg.indexOf("Access denied") > 0); 1645 } 1646 string stats = c.serverStats(); 1647 assert(stats.indexOf("Uptime") == 0); 1648 c.enableMultiStatements(true); // Need to be tested later with a prepared "CALL" 1649 c.enableMultiStatements(false); 1650 } 1651 catch (Exception x) 1652 { 1653 writefln("(%s: %s) %s", x.file, x.line, x.msg); 1654 ok = false; 1655 } 1656 assert(ok); 1657 } 1658 1659 /** 1660 * A struct to represent specializations of prepared statement parameters. 1661 * 1662 * There are two specializations. First you can set an isNull flag to indicate that the 1663 * parameter is to have the SQL NULL value. 1664 * 1665 * Second, if you need to send large objects to the database it might be convenient to 1666 * send them in pieces. These two variables allow for this. If both are provided 1667 * then the corresponding column will be populated by calling the delegate repeatedly. 1668 * the source should fill the indicated slice with data and arrange for the delegate to 1669 * return the length of the data supplied. Af that is less than the chunkSize 1670 * then the chunk will be assumed to be the last one. 1671 */ 1672 struct ParameterSpecialization 1673 { 1674 uint pIndex; //parameter number 0 - number of params-1 1675 bool isNull; 1676 SQLType type = SQLType.DEFAULT; 1677 uint chunkSize; 1678 uint delegate(ubyte[]) chunkDelegate; 1679 bool dummy; 1680 } 1681 alias ParameterSpecialization PSN; 1682 1683 /** 1684 * A struct to represent specializations of prepared statement parameters. 1685 * 1686 * If you are executing a query that will include result columns that are large objects 1687 * it may be expedient to deal with the data as it is received rather than first buffering 1688 * it to some sort of byte array. These two variables allow for this. If both are provided 1689 * then the corresponding column will be fed to the stipulated delegate in chunks of 1690 * chunkSize, with the possible exception of the last chunk, which may be smaller. 1691 * The 'finished' argument will be set to true when the last chunk is set. 1692 * 1693 * Be aware when specifying types for column specializations that for some reason the 1694 * field descriptions returned for a resultset have all of the types TINYTEXT, MEDIUMTEXT, 1695 * TEXT, LONGTEXT, TINYBLOB, MEDIUMBLOB, BLOB, and LONGBLOB lumped as type 0xfc 1696 * contrary to what it says in the protocol documentation. 1697 */ 1698 struct ColumnSpecialization 1699 { 1700 uint cIndex; // parameter number 0 - number of params-1 1701 ushort type; 1702 uint chunkSize; 1703 void delegate(ubyte[] chunk, bool finished) chunkDelegate; 1704 } 1705 alias ColumnSpecialization CSN; 1706 1707 /** 1708 * A struct to represent a single row of a result set. 1709 * 1710 * The row struct is used for both 'traditional' and 'prepared' result sets. It consists of parallel arrays 1711 * of Variant and bool, with the bool array indicating which of the result set columns are NULL. 1712 * 1713 * I have been agitating for some kind of null indicator that can be set for a Variant without destroying 1714 * its inherent type information. If this were the case, then the bool array could disappear. 1715 */ 1716 struct Row 1717 { 1718 private: 1719 Variant[] _uva; 1720 bool[] _nulls; 1721 bool _valid; 1722 1723 T fromBytes(T, int N = 0)(ref uint p, ubyte[] packet, out bool incomplete) if (is(T: ulong)) 1724 { 1725 ulong ac = 0; 1726 uint len = N? N: T.sizeof; 1727 if (p+len >= packet.length-1) 1728 incomplete = true; 1729 else 1730 { 1731 for (uint i = p+len-1; i >= p; i--) { ac <<= 8; ac |= packet[i]; } 1732 p += len; 1733 } 1734 return cast(T) ac; 1735 } 1736 1737 // This is to decode the bitmap in a binary result row. First two bits are skipped 1738 static bool[] decodeBitmap(ubyte[] bits, uint cols) 1739 { 1740 bool[] rv; 1741 rv.length = cols; 1742 uint next = 1; 1743 uint rem = 6; 1744 ubyte src = bits[0] >> 2; 1745 uint n = 0; 1746 for (;;) 1747 { 1748 if (n >= cols) 1749 break; 1750 if (!rem) 1751 { 1752 src = bits[next++]; 1753 rem = 8; 1754 } 1755 rv[n++] = (src & 1) != 0; 1756 src >>= 1; 1757 rem--; 1758 } 1759 return rv; 1760 } 1761 1762 public: 1763 1764 /** 1765 * A constructor to extract the column data from a row data packet. 1766 * 1767 * If the data for the row exceeds the server's maximum packet size, then several packets will be 1768 * sent for the row that taken together constitute a logical row data packet. The logic of the data 1769 * recovery for a Row attempts to minimize the quantity of data that is bufferred. Users can assist 1770 * in this by specifying chunked data transfer in cases where results sets can include long 1771 * column values. 1772 * 1773 * The row struct is used for both 'traditional' and 'prepared' result sets. It consists of parallel arrays 1774 * of Variant and bool, with the bool array indicating which of the result set columns are NULL. 1775 * 1776 * I have been agitating for some kind of null indicator that can be set for a Variant without destroying 1777 * its inherent type information. If this were the case, then the bool array could disappear. 1778 */ 1779 this(Connection con, ubyte[] packet, ResultSetHeaders rh, bool binary) 1780 { 1781 uint fc = rh._fieldCount; 1782 _uva.length = fc; 1783 _nulls.length = fc; 1784 uint p = 0; 1785 size_t pl = packet.length; 1786 uint tl; 1787 bool nullFlag, incomplete, gotPrefix; 1788 ulong lc; 1789 1790 // This is used to decode byte array length prefixes, which may be from 1 to 9 bytes. 1791 // Either the prefix or the following bytes may be incomplete - truncated by the end of the packet. 1792 // We distinguish between these cases. 1793 ulong parseLCB() 1794 { 1795 incomplete = gotPrefix = false; 1796 nullFlag = false; 1797 lc = 0; 1798 if (p > pl-1) 1799 { 1800 incomplete = true; 1801 return 0; 1802 } 1803 switch (packet[p]) 1804 { 1805 case 251: 1806 nullFlag = true; 1807 p++; 1808 break; 1809 case 252: 1810 if (pl-p < 3) 1811 { 1812 incomplete = true; 1813 return 0; 1814 } 1815 for (uint i = p+2; i > p; i--) { lc <<= 8; lc |= packet[i]; } 1816 p += 3; 1817 break; 1818 case 253: 1819 if (pl-p < 4) 1820 { 1821 incomplete = true; 1822 return 0; 1823 } 1824 for (uint i = p+3; i > p; i--) { lc <<= 8; lc |= packet[i]; } 1825 p += 4; 1826 break; 1827 case 254: 1828 if (pl-p < 9) 1829 { 1830 incomplete = true; 1831 return 0; 1832 } 1833 for (uint i = p+8; i > p; i--) { lc <<= 8; lc |= packet[i]; } 1834 p += 9; 1835 break; 1836 case 255: 1837 throw new MYX("Unexpected error packet prefix.", __FILE__, __LINE__); 1838 default: 1839 lc = packet[p++]; 1840 break; 1841 } 1842 gotPrefix = true; 1843 if (pl-p < lc) 1844 incomplete = true; 1845 return lc; 1846 } 1847 1848 1849 if (binary) 1850 { 1851 // There's a null byte header on a binary result sequence, followed by some bytes of bitmap 1852 // indicating which columns are null 1853 enforceEx!MYX(packet[p++] == 0, "Expected null header byte for binary result row"); 1854 uint bml = (fc+7+2)/8; 1855 _nulls = decodeBitmap(packet[p..p+bml], fc); 1856 p += bml; 1857 } 1858 1859 foreach (int i; 0..fc) 1860 { 1861 FieldDescription fd = rh[i]; 1862 bool isnull = false; 1863 bool chunked = (fd.chunkSize > 0); 1864 bool uns = fd.unsigned; 1865 uint checkpoint = p; 1866 1867 if (binary) 1868 { 1869 // Prepared statement result sets are packed differently than traditional MySQL result sets. 1870 // Most types come in their normal memory layout (little-endian). Strings are prefixed by a coded length 1871 // that could in theory be up to ulong.max, but since the largest blob is currently uint.max 1872 // that's all we'll use. 1873 if (_nulls[i]) 1874 continue; // The bitmap said this column is NULL, so there will be no data for it and we skip to the next. 1875 switch (fd.type) 1876 { 1877 case 0x01: // TINYINT 1878 ubyte ub = fromBytes!ubyte(p, packet, incomplete); 1879 if (incomplete) break; // The packet we ar looking at does not contain the entire result row - we must fetch another 1880 if (uns) 1881 _uva[i] = ub; 1882 else 1883 _uva[i] = cast(byte) ub; 1884 break; 1885 case 0x02: // SHORT 1886 ushort us = fromBytes!ushort(p, packet, incomplete); 1887 if (incomplete) break; 1888 if (uns) 1889 _uva[i] = us; 1890 else 1891 _uva[i] = cast(short) us; 1892 break; 1893 case 0x03: // INT 1894 uint ui = fromBytes!uint(p, packet, incomplete); 1895 if (incomplete) break; 1896 if (uns) 1897 _uva[i] = ui; 1898 else 1899 _uva[i] = cast(int) ui; 1900 break; 1901 case 0x04: // FLOAT 1902 if (pl-p < 4) 1903 { 1904 incomplete = true; 1905 break; 1906 } 1907 float f = 0; 1908 ubyte* fp = cast(ubyte*) &f; 1909 fp[0..4] = packet[p..p+4]; 1910 p += 4; 1911 _uva[i] = f; 1912 break; 1913 case 0x05: // DOUBLE 1914 if (pl-p < 8) 1915 { 1916 incomplete = true; 1917 break; 1918 } 1919 double d = 0; 1920 ubyte* dp = cast(ubyte*) &d; 1921 dp[0..8] = packet[p..p+8]; 1922 p += 8; 1923 _uva[i] = d; 1924 break; 1925 case 0x06: // NULL 1926 _nulls[i] = true; 1927 break; 1928 case 0x07: // TIMESTAMP 1929 // The length of all the time/date types can be indicated by a byte 1930 tl = packet[p]; 1931 if (pl-p < tl+1) 1932 { 1933 incomplete = true; 1934 break; 1935 } 1936 _uva[i] = toDateTime(packet[p..p+tl+1]); 1937 p += tl+1; 1938 break; 1939 case 0x08: // LONGLONG 1940 ulong ul = fromBytes!ulong(p, packet, incomplete); 1941 if (uns) 1942 _uva[i] = ul; 1943 else 1944 _uva[i] = cast(long) ul; 1945 break; 1946 case 0x09: // INT24 1947 uint ui24 = fromBytes!(uint, 3)(p, packet, incomplete); 1948 if (incomplete) break; 1949 if (uns) 1950 _uva[i] = ui24; 1951 else 1952 _uva[i] = cast(int) ui24; 1953 break; 1954 case 0x10: // BIT - equated here to bool 1955 if (pl-p < 2) 1956 { 1957 incomplete = true; 1958 break; 1959 } 1960 enforceEx!MYX(packet[p++] == 1, "Expected single bit for bool column"); 1961 bool bv = (packet[p++] == 1); 1962 _uva[i] = bv; 1963 break; 1964 case 0x0a: // DATE 1965 tl = packet[p]; 1966 if (pl-p < tl+1) 1967 { 1968 incomplete = true; 1969 break; 1970 } 1971 _uva[i] = toDate(packet[p..p+tl+1]); 1972 p += tl+1; 1973 break; 1974 case 0x0b: // TIME 1975 tl = packet[p]; 1976 if (pl-p < packet[p]+1) 1977 { 1978 incomplete = true; 1979 break; 1980 } 1981 _uva[i] = toTimeOfDay(packet[p..p+tl+1]); 1982 p += tl+1; 1983 break; 1984 case 0x0c: // DATETIME 1985 tl = packet[p]; 1986 if (pl-p < tl+1) 1987 { 1988 incomplete = true; 1989 break; 1990 } 1991 _uva[i] = toDateTime(packet[p..p+tl+1]); 1992 p += tl+1; 1993 break; 1994 case 0x0d: // YEAR - appears to be an unsigned short 1995 ushort y = fromBytes!ushort(p, packet, incomplete); 1996 if (incomplete) break; 1997 _uva[i] = y; 1998 break; 1999 case 0x0f: // VARCHAR 2000 case 0xf7: // ENUM both this and SET actually get sent by the protocol as BLOB - 0xfe 2001 case 0xf8: // SET 2002 case 0xfd: // VAR_STRING 2003 case 0xfe: // STRING 2004 uint sl = cast(uint) parseLCB(); 2005 if (incomplete) break; 2006 _uva[i] = cast(string) packet[p..p+sl]; 2007 p += sl; 2008 break; 2009 case 0xf9: // TINY_BLOB 2010 case 0xfa: // MEDIUM_BLOB 2011 case 0xfb: // LONG_BLOB 2012 uint sl = cast(uint) parseLCB(); 2013 if (incomplete) break; 2014 _uva[i] = cast(ubyte[]) packet[p..p+sl]; 2015 p += sl; 2016 break; 2017 case 0xfc: // BLOB 2018 uint sl = cast(uint) parseLCB(); 2019 if (incomplete) break; 2020 if (fd.binary) 2021 _uva[i] = cast(ubyte[]) packet[p..p+sl]; 2022 else 2023 _uva[i] = packet[p..p+sl]; 2024 p += sl; 2025 break; 2026 default: 2027 throw new MYX("Unsupported type in row - " ~ to!string(fd.type), __FILE__, __LINE__); 2028 break; 2029 } 2030 } 2031 else 2032 { 2033 // This is a traditional MySQL row with all the data represented as byte strings. Ideally it should be possible 2034 // to tell a V5.x server to send all results in the same format, but this does not appear to be possible at this point 2035 string val; 2036 // We make the test for sufficient data remaining in the packet as we get the string length. 2037 uint sl = cast(uint) parseLCB(); 2038 if (incomplete) goto Incomplete; 2039 val = cast(string) packet[p..p+sl]; 2040 p += sl; 2041 if (nullFlag) 2042 { 2043 // In this data sequence, null columns are indicated by a single 251 byte, so we construct the _nulls array 2044 // as we go along, skipping the null columnns 2045 _nulls[i] = true; 2046 continue; 2047 } 2048 switch (fd.type) 2049 { 2050 case 0x01: // TINYINT 2051 if (uns) 2052 _uva[i] = to!ubyte(val); 2053 else 2054 _uva[i] = to!byte(val); 2055 break; 2056 case 0x02: // SHORT 2057 if (uns) 2058 _uva[i] = to!ushort(val); 2059 else 2060 _uva[i] = to!short(val); 2061 break; 2062 case 0x03: // INT 2063 case 0x09: 2064 if (uns) 2065 _uva[i] = to!uint(val); 2066 else 2067 _uva[i] = to!int(val); 2068 break; 2069 case 0x04: // FLOAT 2070 _uva[i] = to!float(val); 2071 break; 2072 case 0x05: // DOUBLE 2073 _uva[i] = to!double(val); 2074 break; 2075 case 0x06: // NULL 2076 _nulls[i] = true; 2077 break; 2078 case 0x07: // TIMESTAMP 2079 _uva[i] = toDateTime(val); 2080 break; 2081 case 0x08: // LONGLONG 2082 if (uns) 2083 _uva[i] = to!ulong(val); 2084 else 2085 _uva[i] = to!long(val); 2086 break; 2087 case 0x10: // BIT - equated here to bool 2088 val = val[0]? "true": "false"; 2089 _uva[i] = to!bool(val); 2090 break; 2091 case 0x0a: // DATE 2092 _uva[i] = toDate(val); 2093 break; 2094 case 0x0b: // TIME 2095 _uva[i] = toTimeOfDay(val); 2096 break; 2097 case 0x0c: // DATETIME 2098 _uva[i] = toDateTime(val); 2099 break; 2100 case 0x0d: // YEAR - treat as unsigned short to match the prepared case 2101 _uva[i] = to!ushort(val); 2102 break; 2103 case 0x0f: // VARCHAR - new in 5.0, but does not appear for a VARCHAR column 2104 case 0xf7: // ENUM both this and SET actually get sent by the protocol as BLOB - 0xfe 2105 case 0xf8: // SET 2106 // For some reason column type TINYTEXT TEXT MEDIUMTEXT LONGTEXT all appear as BLOB - 0xfc 2107 // They don't even have values in the list of protocol types 2108 case 0xfd: // VAR_STRING 2109 case 0xfe: // STRING 2110 _uva[i] = val; 2111 break; 2112 case 0xf9: // TINY_BLOB 2113 case 0xfa: // MEDIUM_BLOB 2114 case 0xfb: // LONG_BLOB 2115 _uva[i] = cast(ubyte[]) val; 2116 break; 2117 case 0xfc: // Covers a multitude of sins - TINYTEXT TEXT MEDIUMTEXT LONGTEXT all appear as BLOB - 0xfc 2118 if (fd.binary) 2119 _uva[i] = cast(ubyte[]) val; 2120 else 2121 _uva[i] = val; 2122 break; 2123 default: 2124 throw new MYX("Unsupported type in row - " ~ to!string(fd.type), __FILE__, __LINE__); 2125 break; 2126 } 2127 } 2128 Incomplete: 2129 if (incomplete) 2130 { 2131 // The server wil have sent the maximum logical packet, and will follow that with further 2132 // packets containing the remaining data for the row. So if we have determined that the data 2133 // for a column is incomplete, we must now now fetch at least the next one. 2134 // We take the opportunity to save memory here by chopping off the part of the current packet 2135 // that we have already used. If the column is specialized to be chunked, we also nibble off 2136 // as much as possible before getting the next packet 2137 uint npl; 2138 if (gotPrefix && chunked) 2139 { 2140 // We know how long the byte stream is, and we are going to chunk it, so we can read packets 2141 // until we've got the required length and dispose of the bytes via the delegate. 2142 uint remaining = cast(uint) lc; 2143 uint cs = fd.chunkSize; 2144 checkpoint = p; 2145 void delegate(ubyte[], bool) dg = fd.chunkDelegate; 2146 2147 // First get rid of the bytes in the existing packet 2148 while (pl-checkpoint > cs) 2149 { 2150 dg(packet[checkpoint..checkpoint+cs], false); 2151 checkpoint += cs; 2152 remaining -= cs; 2153 } 2154 ubyte[] more = con.getPacket(npl); 2155 packet = packet[checkpoint..pl] ~ more; 2156 pl = packet.length; 2157 p = 0; 2158 2159 eatPackets: 2160 for (;;) 2161 { 2162 while (pl-p >= cs) 2163 { 2164 if (remaining <= cs) 2165 { 2166 dg(packet[p..p+remaining], true); 2167 p += remaining; 2168 break eatPackets; 2169 } 2170 dg(packet[p..p+cs], false); 2171 p += cs; 2172 remaining -= cs; 2173 } 2174 more = con.getPacket(npl); 2175 packet = packet[p..pl] ~ more; 2176 pl = packet.length; 2177 p = 0; 2178 } 2179 // At this point we should be good to process the next column 2180 } 2181 else 2182 { 2183 // Process without chunking - pull in more packets uintil we have enough data 2184 // to get the incomplete column. 2185 ubyte[] more = con.getPacket(npl); 2186 packet = packet[checkpoint..pl] ~ more; 2187 p = 0; // previous stuff now gone 2188 checkpoint = p; 2189 pl = packet.length; 2190 i--; // backtrack and try again to get the column where we failed. This could cause 2191 // further logical packets to be fetched in the case of a long blob. 2192 } 2193 incomplete = false; 2194 } 2195 } 2196 _valid = true; 2197 } 2198 2199 /** 2200 * Simplify retrieval of a column value by index. 2201 * 2202 * If the table you are working with does not allow NULL columns, this may be all you need. Otherwise 2203 * you will have to use isNull(i) as well. 2204 * 2205 * Params: i = the zero based index of the column whose value is required. 2206 * Returns: A Variant holding the column value. 2207 */ 2208 Variant opIndex(uint i) { return _uva[i]; } 2209 /** 2210 * Check if a column in the result row was NULL 2211 * 2212 * Params: i = The zero based column index. 2213 */ 2214 @property bool isNull(uint i) { return _nulls[i]; } 2215 2216 /** 2217 * Move the content of the row into a compatible struct 2218 * 2219 * This method takes no account of NULL column values. If a column was NULL, the corresponding 2220 * Variant value would be unchanged in those cases. 2221 * 2222 * The method will throw if the type of the Variant is not implicitly convertible to the corresponding 2223 * struct member 2224 * 2225 * Params: S = a struct type. 2226 * s = an ref instance of the type 2227 */ 2228 void toStruct(S)(ref S s) if (is(S == struct)) 2229 { 2230 foreach (i, dummy; s.tupleof) 2231 { 2232 enforceEx!MYX(_uva[i].convertsTo!(typeof(s.tupleof[i]))(), 2233 "At col "~to!string(i)~" the value is not implicitly convertible to the structure type"); 2234 s.tupleof[i] = _nulls[i]? typeof(s.tupleof[i]).init: _uva[i].get!(typeof(s.tupleof[i])); 2235 } 2236 } 2237 2238 void show() 2239 { 2240 foreach(Variant v; _uva) 2241 writef("%s, ", v.toString()); 2242 writeln(""); 2243 } 2244 } 2245 2246 /** 2247 * Composite representation of a column value 2248 * 2249 * Another case where a null flag on Variant would simplify matters. 2250 */ 2251 struct Column 2252 { 2253 Variant val; 2254 bool isNull; 2255 } 2256 2257 /** 2258 * A Random access range of Rows. 2259 * 2260 * This is the entity that is returned by the Command methods execSQLResult and 2261 * execPreparedResult 2262 * 2263 * MySQL result sets can be up to 2^^64 rows, and the 32 bit implementation of the 2264 * MySQL C API accomodates such potential massive result sets by storing the rows in 2265 * a doubly linked list. I have taken the view that users who have a need for result sets 2266 * up to this size should be working with a 64 bit system, and as such the 32 bit 2267 * implementation will throw if the number of rows exceeds the 32 bit size_t.max. 2268 */ 2269 struct ResultSet 2270 { 2271 private: 2272 Row[] _ra; 2273 string[] _colNames; 2274 size_t[] _rb; // Current span of the range 2275 size_t _rc; 2276 size_t _cr; 2277 2278 this (Row[] ra, string[] colNames) 2279 { 2280 _ra = ra; 2281 _rc = ra.length; 2282 _colNames = colNames; 2283 if(_rc) 2284 { 2285 _rb.length = _ra.length; 2286 foreach (size_t i; 0.._ra.length) 2287 _rb[i] = i; 2288 _cr = _rb[0]; 2289 } 2290 } 2291 2292 public: 2293 /** 2294 * Make the ResultSet behave as a random access range - empty 2295 * 2296 */ 2297 @property bool empty() { return (_rb.length == 0); } 2298 /** 2299 * Make the ResultSet behave as a random access range - save 2300 * 2301 */ 2302 @property ResultSet save() 2303 { 2304 return this; 2305 } 2306 /** 2307 * Make the ResultSet behave as a random access range - front 2308 * 2309 * Gets the first row in whatever remains of the Range. 2310 */ 2311 @property Row front() 2312 { 2313 enforceEx!MYX(_rb.length, "Attempted 'front' on empty ResultSet range."); 2314 _cr = _rb[0]; 2315 return _ra[_cr]; 2316 } 2317 /** 2318 * Make the ResultSet behave as a random access range - back 2319 * 2320 * Gets the last row in whatever remains of the Range. 2321 */ 2322 @property Row back() 2323 { 2324 enforceEx!MYX(_rb.length, "Attempted 'back' on empty ResultSet range."); 2325 _cr = _rb[$-1]; 2326 return _ra[_cr]; 2327 } 2328 /** 2329 * Make the ResultSet behave as a random access range - popFront() 2330 * 2331 */ 2332 void popFront() 2333 { 2334 enforceEx!MYX(_rb.length, "Attempted 'popFront' on empty ResultSet range."); 2335 bool updateCr = (_cr == _rb[0]); 2336 _rb = _rb[1 .. $]; 2337 if (updateCr && _rb.length) 2338 _cr = _rb[0]; 2339 } 2340 /** 2341 * Make the ResultSet behave as a random access range - popBack 2342 * 2343 */ 2344 void popBack() 2345 { 2346 enforceEx!MYX(_rb.length, "Attempted 'popBack' on empty ResultSet range."); 2347 // Fetch the required row 2348 bool updateCr = (_cr == _rb[$-1]); 2349 _rb= _rb[0 .. $-1]; 2350 if (updateCr && _rb.length) 2351 _cr = _rb[$-1]; 2352 } 2353 /** 2354 * Make the ResultSet behave as a random access range - opIndex 2355 * 2356 * Gets the i'th row of whatever remains of the range 2357 */ 2358 Row opIndex(size_t i) 2359 { 2360 enforceEx!MYX(_rb.length, "Attempted to index into an empty ResultSet range."); 2361 enforceEx!MYX(i < _rb.length, "Requested range index out of range"); 2362 _cr = _rb[i]; 2363 return _ra[_cr]; 2364 } 2365 /** 2366 * Make the ResultSet behave as a random access range - length 2367 * 2368 */ 2369 @property size_t length() { return _rb.length; } 2370 2371 /** 2372 * Restore the range to its original span. 2373 * 2374 * Since the range is just a view of the data, we can easily revert to the 2375 * initial state. 2376 */ 2377 void revert() 2378 { 2379 _rb.length = _ra.length; 2380 foreach (size_t i; 0.._ra.length) 2381 _rb[i] = i; 2382 } 2383 2384 /** 2385 * Get a row as an associative array by column name 2386 * 2387 * The row in question will be that which was the most recent subject of 2388 * front, back, or opIndex. If there have been no such references it will be front. 2389 */ 2390 Column[string] asAA() 2391 { 2392 enforceEx!MYX(_rb.length, "Attempted use of empty ResultSet as an associative array."); 2393 Row r = _ra[_cr]; 2394 Column[string] aa; 2395 foreach (uint i, string s; _colNames) 2396 { 2397 Column c; 2398 c.val = r._uva[i]; 2399 c.isNull = r._nulls[i]; 2400 aa[s] = c; 2401 } 2402 return aa; 2403 } 2404 } 2405 2406 /** 2407 * An input range of Rows. 2408 * 2409 * This is the entity that is returned by the Command methods execSQLSequence and 2410 * execPreparedSequence 2411 * 2412 * MySQL result sets can be up to 2^^64 rows. This interface allows for iteration through 2413 * a result set of that size. 2414 */ 2415 struct ResultSequence 2416 { 2417 private: 2418 Command* _cmd; 2419 Row _cr; 2420 string[] _colNames; 2421 ulong _rc; 2422 bool _empty; 2423 2424 2425 2426 this (Command* cmd, string[] colNames) 2427 { 2428 _cmd = cmd; 2429 _colNames = colNames; 2430 _cr = _cmd.getNextRow(); 2431 if (!_cr._valid) 2432 _empty = true; 2433 else 2434 _rc++; 2435 } 2436 2437 ~this() 2438 { 2439 close(); 2440 } 2441 2442 public: 2443 2444 2445 /** 2446 * Make the ResultSequence behave as an input range - empty 2447 * 2448 */ 2449 @property bool empty() { return _empty; } 2450 /** 2451 * Make the ResultSequence behave as an input range - front 2452 * 2453 * Gets the current row 2454 */ 2455 @property Row front() 2456 { 2457 enforceEx!MYX(!_empty, "Attempted 'front' on exhausted result sequence."); 2458 return _cr; 2459 } 2460 /** 2461 * Make the ResultSequence behave as am input range - popFront() 2462 * 2463 * Progresses to the next row of the result set - that will then be 'front' 2464 */ 2465 void popFront() 2466 { 2467 enforceEx!MYX(!_empty, "Attempted 'popFront' when no more rows available"); 2468 _cr = _cmd.getNextRow(); 2469 if (!_cr._valid) 2470 _empty = true; 2471 } 2472 2473 /** 2474 * Get the current row as an associative array by column name 2475 */ 2476 Column[string] asAA() 2477 { 2478 enforceEx!MYX(!_empty, "Attempted 'front' on exhausted result sequence."); 2479 Column[string] aa; 2480 foreach (uint i, string s; _colNames) 2481 { 2482 Column c; 2483 c.val = _cr._uva[i]; 2484 c.isNull = _cr._nulls[i]; 2485 aa[s] = c; 2486 } 2487 return aa; 2488 } 2489 2490 /** 2491 * Explicitly clean up the MySQL resources and cancel pending results 2492 * 2493 */ 2494 void close() 2495 { 2496 _cmd.purgeResult(); 2497 } 2498 2499 /** 2500 * Get the number of currently retrieved. 2501 * 2502 * Note that this is not neccessarlly the same as the length of the range. 2503 */ 2504 @property ulong rowCount() { return _rc; } 2505 } 2506 2507 /** 2508 * Encapsulation of an SQL command or query. 2509 * 2510 * A Command be be either a one-off SQL query, or may use a prepared statement. 2511 * Commands that are expected to return a result set - queries - have distinctive methods 2512 * that are enforced. That is it will be an error to call such a method with an SQL command 2513 * that does not produce a result set. 2514 */ 2515 struct Command 2516 { 2517 private: 2518 Connection _con; 2519 string _sql; 2520 uint _hStmt; 2521 ulong _insertID; 2522 bool _rowsPending, _headersPending, _pendingBinary, _rebound; 2523 ushort _psParams, _psWarnings, _fieldCount; 2524 ResultSetHeaders _rsh; 2525 PreparedStmtHeaders _psh; 2526 Variant[] _inParams; 2527 ParameterSpecialization[] _psa; 2528 string _prevFunc; 2529 2530 bool sendCmd(ubyte cmd) 2531 { 2532 enforceEx!MYX(!(_headersPending || _rowsPending), "There are result set elements pending - purgeResult() required."); 2533 _con.resetPacket(); 2534 ubyte[] packet; 2535 size_t pl = _sql.length+1; 2536 packet.length = pl+4; 2537 ubyte* ubp = packet.ptr; 2538 packet[0] = cast(ubyte) (pl & 0xff); 2539 packet[1] = cast(ubyte) ((pl >> 8) & 0xff); 2540 packet[2] = cast(ubyte) ((pl >> 16) & 0xff); 2541 packet[3] = 0; 2542 packet[4] = cmd; 2543 packet[5 .. _sql.length+5] = (cast(ubyte[]) _sql)[0..$]; 2544 _fieldCount = 0; 2545 _con.bumpPacket(); 2546 _con.send(packet); 2547 return true; 2548 } 2549 2550 static ubyte[] makeBitmap(ParameterSpecialization[] psa) 2551 { 2552 size_t bml = (psa.length+7)/8; 2553 ubyte[] bma; 2554 bma.length = bml; 2555 foreach (uint i, PSN psn; psa) 2556 { 2557 if (!psn.isNull) 2558 continue; 2559 uint bn = i/8; 2560 uint bb = i%8; 2561 ubyte sr = 1; 2562 sr <<= bb; 2563 bma[bn] |= sr; 2564 } 2565 return bma; 2566 } 2567 2568 ubyte[] makePSPrefix(ubyte flags = 0) 2569 { 2570 ubyte[] prefix; 2571 prefix.length = 14; 2572 2573 prefix[4] = 0x17; 2574 prefix[5] = cast(ubyte) (_hStmt & 0xff); 2575 prefix[6] = cast(ubyte) ((_hStmt >> 8) & 0xff); 2576 prefix[7] = cast(ubyte) ((_hStmt >> 16) & 0xff); 2577 prefix[8] = cast(ubyte) ((_hStmt >> 24) & 0xff); 2578 prefix[9] = flags; // flags, no cursor 2579 prefix[10] = 1; // iteration count - currently always 1 2580 prefix[11] = 0; 2581 prefix[12] = 0; 2582 prefix[13] = 0; 2583 2584 return prefix; 2585 } 2586 2587 ubyte[] analyseParams(out ubyte[] vals, out bool longData) 2588 { 2589 size_t pc = _inParams.length; 2590 ubyte[] types; 2591 types.length = pc*2; 2592 size_t alloc = pc*20; 2593 vals.length = alloc; 2594 uint vcl = 0, len; 2595 int ct = 0; 2596 2597 void reAlloc(size_t n) 2598 { 2599 if (vcl+n < alloc) 2600 return; 2601 size_t inc = (alloc*3)/2; 2602 if (inc < n) 2603 inc = n; 2604 alloc += inc; 2605 vals.length = alloc; 2606 } 2607 2608 foreach (size_t i; 0..pc) 2609 { 2610 if (_psa[i].chunkSize) 2611 longData= true; 2612 bool isnull = _psa[i].isNull; 2613 Variant v = _inParams[i]; 2614 SQLType ext = _psa[i].type; 2615 string ts = v.type.toString(); 2616 bool isRef; 2617 if (ts[$-1] == '*') 2618 { 2619 ts.length = ts.length-1; 2620 isRef= true; 2621 } 2622 switch (ts) 2623 { 2624 case "bool": 2625 if (ext == SQLType.DEFAULT) 2626 types[ct++] = 0x10; // BIT 2627 else 2628 types[ct++] = cast(ubyte) ext; 2629 types[ct++] = 0; 2630 if (isnull) break; 2631 reAlloc(2); 2632 bool bv = isRef? *(v.get!(bool*)): v.get!(bool); 2633 vals[vcl++] = 1; 2634 vals[vcl++] = bv? 0x31: 0x30; 2635 break; 2636 case "byte": // TINY 2637 types[ct++] = 0x01; 2638 types[ct++] = 0; 2639 if (isnull) break; 2640 reAlloc(1); 2641 vals[vcl++] = isRef? *(v.get!(byte*)): v.get!(byte); 2642 break; 2643 case "ubyte": // TINY UNSIGNED 2644 types[ct++] = 0x01; 2645 types[ct++] = 0x80; 2646 if (isnull) break; 2647 reAlloc(1); 2648 vals[vcl++] = isRef? *(v.get!(ubyte*)): v.get!(ubyte); 2649 break; 2650 case "short": 2651 types[ct++] = 0x02; 2652 types[ct++] = 0; 2653 if (isnull) break; 2654 reAlloc(2); 2655 short si = isRef? *(v.get!(short*)): v.get!(short); 2656 vals[vcl++] = cast(ubyte) (si & 0xff); 2657 vals[vcl++] = cast(ubyte) ((si >> 8) & 0xff); 2658 break; 2659 case "ushort": 2660 types[ct++] = 0x02; 2661 types[ct++] = 0x80; 2662 reAlloc(2); 2663 ushort us = isRef? *(v.get!(ushort*)): v.get!(ushort); 2664 vals[vcl++] = cast(ubyte) (us & 0xff); 2665 vals[vcl++] = cast(ubyte) ((us >> 8) & 0xff); 2666 break; 2667 case "int": 2668 types[ct++] = 0x03; 2669 types[ct++] = 0; 2670 if (isnull) break; 2671 reAlloc(4); 2672 int ii = isRef? *(v.get!(int*)): v.get!(int); 2673 vals[vcl++] = cast(ubyte) (ii & 0xff); 2674 vals[vcl++] = cast(ubyte) ((ii >> 8) & 0xff); 2675 vals[vcl++] = cast(ubyte) ((ii >> 16) & 0xff); 2676 vals[vcl++] = cast(ubyte) ((ii >> 24) & 0xff); 2677 break; 2678 case "uint": 2679 types[ct++] = 0x03; 2680 types[ct++] = 0x80; 2681 if (isnull) break; 2682 reAlloc(4); 2683 uint ui = isRef? *(v.get!(uint*)): v.get!(uint); 2684 vals[vcl++] = cast(ubyte) (ui & 0xff); 2685 vals[vcl++] = cast(ubyte) ((ui >> 8) & 0xff); 2686 vals[vcl++] = cast(ubyte) ((ui >> 16) & 0xff); 2687 vals[vcl++] = cast(ubyte) ((ui >> 24) & 0xff); 2688 break; 2689 case "long": 2690 types[ct++] = 0x08; 2691 types[ct++] = 0; 2692 if (isnull) break; 2693 reAlloc(8); 2694 long li = isRef? *(v.get!(long*)): v.get!(long); 2695 vals[vcl++] = cast(ubyte) (li & 0xff); 2696 vals[vcl++] = cast(ubyte) ((li >> 8) & 0xff); 2697 vals[vcl++] = cast(ubyte) ((li >> 16) & 0xff); 2698 vals[vcl++] = cast(ubyte) ((li >> 24) & 0xff); 2699 vals[vcl++] = cast(ubyte) ((li >> 32) & 0xff); 2700 vals[vcl++] = cast(ubyte) ((li >> 40) & 0xff); 2701 vals[vcl++] = cast(ubyte) ((li >> 48) & 0xff); 2702 vals[vcl++] = cast(ubyte) ((li >> 56) & 0xff); 2703 break; 2704 case "ulong": 2705 types[ct++] = 0x08; 2706 types[ct++] = 0x80; 2707 if (isnull) break; 2708 reAlloc(8); 2709 ulong ul = isRef? *(v.get!(ulong*)): v.get!(ulong); 2710 vals[vcl++] = cast(ubyte) (ul & 0xff); 2711 vals[vcl++] = cast(ubyte) ((ul >> 8) & 0xff); 2712 vals[vcl++] = cast(ubyte) ((ul >> 16) & 0xff); 2713 vals[vcl++] = cast(ubyte) ((ul >> 24) & 0xff); 2714 vals[vcl++] = cast(ubyte) ((ul >> 32) & 0xff); 2715 vals[vcl++] = cast(ubyte) ((ul >> 40) & 0xff); 2716 vals[vcl++] = cast(ubyte) ((ul >> 48) & 0xff); 2717 vals[vcl++] = cast(ubyte) ((ul >> 56) & 0xff); 2718 break; 2719 case "float": 2720 types[ct++] = 0x04; 2721 types[ct++] = 0; 2722 if (isnull) break; 2723 reAlloc(4); 2724 float f = isRef? *(v.get!(float*)): v.get!(float); 2725 ubyte* ubp = cast(ubyte*) &f; 2726 vals[vcl++] = *ubp++; 2727 vals[vcl++] = *ubp++; 2728 vals[vcl++] = *ubp++; 2729 vals[vcl++] = *ubp; 2730 break; 2731 case "double": 2732 types[ct++] = 0x05; 2733 types[ct++] = 0; 2734 if (isnull) break; 2735 reAlloc(8); 2736 double d = isRef? *(v.get!(double*)): v.get!(double); 2737 ubyte* ubp = cast(ubyte*) &d; 2738 vals[vcl++] = *ubp++; 2739 vals[vcl++] = *ubp++; 2740 vals[vcl++] = *ubp++; 2741 vals[vcl++] = *ubp++; 2742 vals[vcl++] = *ubp++; 2743 vals[vcl++] = *ubp++; 2744 vals[vcl++] = *ubp++; 2745 vals[vcl++] = *ubp; 2746 break; 2747 case "std.datetime.Date": 2748 types[ct++] = 0x0a; 2749 types[ct++] = 0; 2750 Date date = isRef? *(v.get!(Date*)): v.get!(Date); 2751 ubyte[] da = pack(date); 2752 size_t l = da.length; 2753 if (isnull) break; 2754 reAlloc(l); 2755 vals[vcl..vcl+l] = da[]; 2756 vcl += l; 2757 break; 2758 case "std.datetime.Time": 2759 types[ct++] = 0x0b; 2760 types[ct++] = 0; 2761 TimeOfDay time = isRef? *(v.get!(TimeOfDay*)): v.get!(TimeOfDay); 2762 ubyte[] ta = pack(time); 2763 size_t l = ta.length; 2764 if (isnull) break; 2765 reAlloc(l); 2766 vals[vcl..vcl+l] = ta[]; 2767 vcl += l; 2768 break; 2769 case "std.datetime.DateTime": 2770 types[ct++] = 0x0c; 2771 types[ct++] = 0; 2772 DateTime dt = isRef? *(v.get!(DateTime*)): v.get!(DateTime); 2773 ubyte[] da = pack(dt); 2774 size_t l = da.length; 2775 if (isnull) break; 2776 reAlloc(l); 2777 vals[vcl..vcl+l] = da[]; 2778 vcl += l; 2779 break; 2780 case "connect.Timestamp": 2781 types[ct++] = 0x07; 2782 types[ct++] = 0; 2783 Timestamp tms = isRef? *(v.get!(Timestamp*)): v.get!(Timestamp); 2784 DateTime dt = toDateTime(tms.rep); 2785 ubyte[] da = pack(dt); 2786 size_t l = da.length; 2787 if (isnull) break; 2788 reAlloc(l); 2789 vals[vcl..vcl+l] = da[]; 2790 vcl += l; 2791 break; 2792 case "immutable(char)[]": 2793 if (ext == SQLType.DEFAULT) 2794 types[ct++] = 0x0f; 2795 else 2796 types[ct++] = cast(ubyte) ext; 2797 types[ct++] = 0; 2798 if (isnull) break; 2799 string s = isRef? *(v.get!(string*)): v.get!(string); 2800 ubyte[] packed = packLCS(cast(void[]) s); 2801 reAlloc(packed.length); 2802 vals[vcl..vcl+packed.length] = packed[]; 2803 vcl += packed.length; 2804 break; 2805 case "char[]": 2806 if (ext == SQLType.DEFAULT) 2807 types[ct++] = 0x0f; 2808 else 2809 types[ct++] = cast(ubyte) ext; 2810 types[ct++] = 0; 2811 if (isnull) break; 2812 char[] ca = isRef? *(v.get!(char[]*)): v.get!(char[]); 2813 ubyte[] packed = packLCS(cast(void[]) ca); 2814 reAlloc(packed.length); 2815 vals[vcl..vcl+packed.length] = packed[]; 2816 vcl += packed.length; 2817 break; 2818 case "byte[]": 2819 if (ext == SQLType.DEFAULT) 2820 types[ct++] = 0x59; // TINYBLOB by default 2821 else 2822 types[ct++] = cast(ubyte) ext; 2823 types[ct++] = 0; 2824 if (isnull) break; 2825 byte[] ba = isRef? *(v.get!(byte[]*)): v.get!(byte[]); 2826 ubyte[] packed = packLCS(cast(void[]) ba); 2827 reAlloc(packed.length); 2828 vals[vcl..vcl+packed.length] = packed[]; 2829 vcl += packed.length; 2830 break; 2831 case "ubyte[]": 2832 if (ext == SQLType.DEFAULT) 2833 types[ct++] = 0xf9; // TINYBLOB by default 2834 else 2835 types[ct++] = cast(ubyte) ext; 2836 types[ct++] = 0; 2837 if (isnull) break; 2838 ubyte[] uba = isRef? *(v.get!(ubyte[]*)): v.get!(ubyte[]); 2839 ubyte[] packed = packLCS(cast(void[]) uba); 2840 reAlloc(packed.length); 2841 vals[vcl..vcl+packed.length] = packed[]; 2842 vcl += packed.length; 2843 break; 2844 default: 2845 throw new MYX("Unsupported parameter type", __FILE__, __LINE__); 2846 } 2847 } 2848 vals.length = vcl; 2849 return types; 2850 } 2851 2852 void sendLongData() 2853 { 2854 foreach (uint i, PSN psn; _psa) 2855 { 2856 if (!psn.chunkSize) continue; 2857 uint cs = psn.chunkSize; 2858 uint delegate(ubyte[]) dg = psn.chunkDelegate; 2859 ubyte[] chunk; 2860 chunk.length = cs+11; 2861 uint pl = cs+7; 2862 // Packet header 2863 chunk[0] = cast(ubyte) (pl & 0xff); 2864 chunk[1] = cast(ubyte) ((pl >> 8) & 0xff); 2865 chunk[2] = cast(ubyte) ((pl >> 16) & 0xff); 2866 chunk[3] = 0; // Each long data chunk is a separate command 2867 2868 // Data chunk 2869 chunk[4] = 0x18; // command 2870 chunk[5] = cast(ubyte) (_hStmt & 0xff); // statement handle - 4 bytes 2871 chunk[6] = cast(ubyte) ((_hStmt >> 8) & 0xff); 2872 chunk[7] = cast(ubyte) ((_hStmt >> 16) & 0xff); 2873 chunk[8] = cast(ubyte) ((_hStmt >> 24) & 0xff); 2874 chunk[9] = cast(ubyte) (i & 0xff); // parameter number - 2 bytes 2875 chunk[10] = cast(ubyte) ((i >> 8) & 0xff); 2876 // byte 11 on is payload 2877 for (;;) 2878 { 2879 uint sent = dg(chunk[11..cs+11]); 2880 if (sent < cs) 2881 { 2882 if (sent == 0) // data was exact multiple of chunk size - all sent 2883 break; 2884 sent += 7; // adjust for non-payload bytes 2885 chunk.length = chunk.length - (cs-sent); // trim the chunk 2886 chunk[0] = cast(ubyte) (sent & 0xff); 2887 chunk[1] = cast(ubyte) ((sent >> 8) & 0xff); 2888 chunk[2] = cast(ubyte) ((sent >> 16) & 0xff); 2889 _con.send(chunk); 2890 break; 2891 } 2892 _con.send(chunk); 2893 } 2894 } 2895 } 2896 2897 public: 2898 2899 /** 2900 * Construct a naked Command object 2901 * 2902 * Params: con = A Connection object to communicate with the server 2903 */ 2904 this(Connection con) 2905 { 2906 _con = con; 2907 _con.resetPacket(); 2908 } 2909 2910 /** 2911 * Construct a Command object complete with SQL 2912 * 2913 * Params: con = A Connection object to communicate with the server 2914 * sql = SQL command string. 2915 */ 2916 this(Connection con, string sql) 2917 { 2918 _sql = sql; 2919 this(con); 2920 } 2921 2922 @property 2923 { 2924 /// Get the current SQL for the Command 2925 string sql() { return _sql; } 2926 /** 2927 * Set a new SQL command. 2928 * 2929 * This can have quite profound side effects. It resets the Command to an initial state. 2930 * If a query has been issued on the Command that produced a result set, then all of the 2931 * result set packets - field description sequence, EOF packet, result rows sequence, EOF packet 2932 * must be flushed from the server before any further operation can be performed on 2933 * the Connection. If you want to write speedy and efficient MySQL programs, you should 2934 * bear this in mind when designing your queries so that you are not requesting many 2935 * rows when one would do. 2936 * 2937 * Params: sql = SQL command string. 2938 */ 2939 string sql(string sql) 2940 { 2941 purgeResult(); 2942 releaseStatement(); 2943 _con.resetPacket(); 2944 return _sql = sql; 2945 } 2946 } 2947 2948 /** 2949 * Submit an SQL command to the server to be compiled into a prepared statement. 2950 * 2951 * The result of a successful outcome will be a statement handle - an ID - for the prepared statement, 2952 * a count of the parameters required for excution of the statement, and a count of the columns 2953 * that will be present in any result set that the command generates. Thes values will be stored in 2954 * in the Command struct. 2955 * 2956 * The server will then proceed to send prepared statement headers, including parameter descriptions, 2957 * and result set field descriptions, followed by an EOF packet. 2958 * 2959 * If there is an existing statement handle in the Command struct, that prepared statement is released. 2960 * 2961 * Throws: MySQLEXception if there are pending result set items, or if the server has a problem. 2962 */ 2963 void prepare() 2964 { 2965 enforceEx!MYX(!(_headersPending || _rowsPending), "There are result set elements pending - purgeResult() required."); 2966 if (_hStmt) 2967 releaseStatement(); 2968 _con.sendCmd(0x16, _sql); 2969 _fieldCount = 0; 2970 uint pl; 2971 ubyte[] packet = _con.getPacket(pl); 2972 ubyte* ubp = packet.ptr; 2973 if (*ubp == 0) 2974 { 2975 ubp++; 2976 _hStmt = getInt(ubp); 2977 _fieldCount = getShort(ubp); 2978 _psParams = getShort(ubp); 2979 _inParams.length = _psParams; 2980 _psa.length = _psParams; 2981 ubp++; // one byte of filler 2982 _psWarnings = getShort(ubp); 2983 2984 // At this point the server also sends field specs for parameters and columns if there were any of each 2985 _psh = PreparedStmtHeaders(_con, _fieldCount, _psParams); 2986 } 2987 else 2988 { 2989 OKPacket okp = OKPacket(ubp, pl); 2990 throw new MYX("MySQL Error: " ~ cast(string) okp.message, __FILE__, __LINE__); 2991 } 2992 } 2993 2994 /** 2995 * Release a prepared statement. 2996 * 2997 * This method tells the server that it can dispose of the information it holds about the 2998 * current prepared statement, and resets the Command object to an initial state in 2999 * that respect. 3000 */ 3001 void releaseStatement() 3002 { 3003 ubyte[] packet; 3004 packet.length = 9; 3005 packet[0] = 5; 3006 packet[1] = 0; 3007 packet[2] = 0; 3008 packet[3] = 0;//_con.pktNumber; 3009 _con.bumpPacket(); 3010 packet[4] = 0x19; 3011 packet[5] = cast(ubyte) (_hStmt & 0xff); 3012 packet[6] = cast(ubyte) ((_hStmt >> 8) & 0xff); 3013 packet[7] = cast(ubyte) ((_hStmt >> 16) & 0xff); 3014 packet[8] = cast(ubyte) ((_hStmt >> 24) & 0xff); 3015 purgeResult(); 3016 _con.send(packet); 3017 // It seems that the server does not find it necessary to send a response 3018 // for this command. 3019 _hStmt = 0; 3020 } 3021 3022 /** 3023 * Flush any outstanding result set elements. 3024 * 3025 * When the server responds to a command that produces a result set, it queues the whole set 3026 * of corresponding packets over the current connection. Before that Connection can embark on 3027 * any new command, it must receive all of those packets and junk them. 3028 * http://www.mysqlperformanceblog.com/2007/07/08/mysql-net_write_timeout-vs-wait_timeout-and-protocol-notes/ 3029 */ 3030 ulong purgeResult() 3031 { 3032 ulong rows = 0; 3033 if (_fieldCount) 3034 { 3035 if (_headersPending) 3036 { 3037 for (uint i = 0;; i++) 3038 { 3039 uint pl; 3040 ubyte[] packet = _con.getPacket(pl); 3041 ubyte* ubp = packet.ptr; 3042 if (pl < 9 && *ubp == 0xfe) 3043 { 3044 // Found an EOF packet 3045 _headersPending = false; 3046 break; 3047 } 3048 enforceEx!MYX(i < _fieldCount, "Field header count exceeded but no EOF packet found."); 3049 } 3050 } 3051 if (_rowsPending) 3052 { 3053 for (;; rows++) 3054 { 3055 uint pl; 3056 ubyte[] packet = _con.getPacket(pl); 3057 ubyte* ubp = packet.ptr; 3058 if (pl < 9 && *ubp == 0xfe) 3059 { 3060 // Found an EOF packet 3061 _rowsPending = _pendingBinary = false; 3062 break; 3063 } 3064 } 3065 } 3066 } 3067 _fieldCount = 0; 3068 _con.resetPacket(); 3069 return rows; 3070 } 3071 3072 /** 3073 * Bind a D variable to a prepared statement parameter. 3074 * 3075 * In this implementation, binding comprises setting a value into the appropriate element of 3076 * an array of Variants which represent the parameters, and setting any required specializations. 3077 * 3078 * To bind to some D variable, we set the corrsponding variant with its address, so there is no 3079 * need to rebind between calls to execPreparedXXX. 3080 */ 3081 void bindParameter(T)(ref T val, uint pIndex, ParameterSpecialization psn = PSN(0, false, SQLType.DEFAULT, 0, null, true)) 3082 { 3083 // Now in theory we should be able to check the parameter type here, since the protocol is supposed 3084 // to send us type information for the parameters, but this capability seems to be broken. This assertion 3085 // is supported by the fact that the same information is not available via the MySQL C API either. It is up 3086 // to the programmer to ensure that appropriate type information is embodied in the variant array, or 3087 // provided explicitly. This sucks, but short of having a client side SQL parser I don't see what can be done. 3088 // 3089 // We require that the statement be prepared at this point so we can at least check that the parameter 3090 // number is within the required range 3091 enforceEx!MYX(_hStmt, "The statement must be prepared before parameters are bound."); 3092 enforceEx!MYX(pIndex < _psParams, "Parameter number is out of range for the prepared statement."); 3093 _inParams[pIndex] = &val; 3094 if (!psn.dummy) 3095 { 3096 psn.pIndex = pIndex; 3097 _psa[pIndex] = psn; 3098 } 3099 } 3100 3101 /** 3102 * Bind a tuple of D variables to the parameters of a prepared statement. 3103 * 3104 * You can use this method to bind a set of variables if you don't need any specialization, 3105 * that is there will be no null values, and chunked transfer is not neccessary. 3106 * 3107 * The tuple must match the required number of parameters, and it is the programmer's responsibility 3108 * to ensure that they are of appropriate types. 3109 */ 3110 void bindParameterTuple(T...)(ref T args) 3111 { 3112 enforceEx!MYX(_hStmt, "The statement must be prepared before parameters are bound."); 3113 enforceEx!MYX(args.length == _psParams, "Argument list supplied does not match the number of parameters."); 3114 foreach (uint i, dummy; args) 3115 _inParams[i] = &args[i]; 3116 } 3117 3118 /** 3119 * Bind a Variant[] as the parameters of a prepared statement. 3120 * 3121 * You can use this method to bind a set of variables in Variant form to the parameters of a prepared statement. 3122 * 3123 * Parameter specializations can be added if required. This method could be used to add records from a data 3124 * entry form along the lines of 3125 ------------ 3126 auto c = Command(con, "insert into table42 values(?, ?, ?)"); 3127 c.prepare(); 3128 Variant[] va; 3129 va.length = 3; 3130 c.bindParameters(va); 3131 DataRecord dr; // Some data input facility 3132 ulong ra; 3133 do 3134 { 3135 dr.get(); 3136 va[0] = dr("Name"); 3137 va[1] = dr("City"); 3138 va[2] = dr("Whatever"); 3139 c.execPrepared(ra); 3140 } while(tod < "17:30"); 3141 ------------ 3142 * Params: va = External list of Variants to be used as parameters 3143 * psnList = any required specializations 3144 */ 3145 void bindParameters(Variant[] va, ParameterSpecialization[] psnList= null) 3146 { 3147 enforceEx!MYX(_hStmt, "The statement must be prepared before parameters are bound."); 3148 enforceEx!MYX(va.length == _psParams, "Param count supplied does not match prepared statement"); 3149 _inParams[] = va[]; 3150 if (psnList !is null) 3151 { 3152 foreach (PSN psn; psnList) 3153 _psa[psn.pIndex] = psn; 3154 } 3155 } 3156 3157 /** 3158 * Access a prepared statement parameter for update. 3159 * 3160 * Another style of usage would simply update the parameter Variant directly 3161 * 3162 ------------ 3163 c.param(0) = 42; 3164 c.param(1) = "The answer"; 3165 ------------ 3166 * Params: index = The zero based index 3167 */ 3168 ref Variant param(uint index) 3169 { 3170 enforceEx!MYX(_hStmt, "The statement must be prepared before parameters are bound."); 3171 enforceEx!MYX(index < _psParams, "Parameter index out of range."); 3172 return _inParams[index]; 3173 } 3174 3175 /** 3176 * Execute a one-off SQL command. 3177 * 3178 * Use this method when you are not going to be using the same command repeatedly. 3179 * It can be used with commands that don't produce a result set, or those that do. If there is a result 3180 * set its existence will be indicated by the return value. 3181 * 3182 * Any result set can be accessed vis getNextRow(), but you should really be using execSQLResult() 3183 * or execSQLSequence() for such queries. 3184 * 3185 * Params: ra = An out parameter to receive the number of rows affected. 3186 * Returns: true if there was a (possibly empty) result set. 3187 */ 3188 bool execSQL(out ulong ra) 3189 { 3190 _con.sendCmd(0x03, _sql); 3191 _fieldCount = 0; 3192 uint pl; 3193 ubyte[] packet = _con.getPacket(pl); 3194 ubyte* ubp = packet.ptr; 3195 bool rv; 3196 if (*ubp == 0 || *ubp == 255) 3197 { 3198 _con.resetPacket(); 3199 OKPacket okp = OKPacket(ubp, pl); 3200 enforceEx!MYX(!okp.error, "MySQL Error: " ~ cast(string) okp.message); 3201 ra = okp.affected; 3202 _con._serverStatus = okp.serverStatus; 3203 _insertID = okp.insertID; 3204 rv = false; 3205 } 3206 else 3207 { 3208 // There was presumably a result set 3209 _headersPending = _rowsPending = true; 3210 _pendingBinary = false; 3211 bool gash; 3212 _fieldCount = cast(ushort) parseLCB(ubp, gash); 3213 rv = true; 3214 ra = 0; 3215 } 3216 return rv; 3217 } 3218 3219 /** 3220 * Execute a one-off SQL command for the case where you expect a result set, and want it all at once. 3221 * 3222 * Use this method when you are not going to be using the same command repeatedly. 3223 * This method will throw if the SQL command does not produce a result set. 3224 * 3225 * If there are long data items among the expected result columns you can specify that they are to be 3226 * subject to chunked transfer via a delegate. 3227 * 3228 * Params: csa = An optional array of ColumnSpecialization structs. 3229 * Returns: A (possibly empty) ResultSet. 3230 */ 3231 ResultSet execSQLResult(ColumnSpecialization[] csa = null) 3232 { 3233 uint alloc = 20; 3234 Row[] rra; 3235 rra.length = alloc; 3236 uint cr = 0; 3237 ulong ra; 3238 enforceEx!MYX(execSQL(ra), "The executed query did not produce a result set."); 3239 _rsh = ResultSetHeaders(_con, _fieldCount); 3240 if (csa !is null) 3241 _rsh.addSpecializations(csa); 3242 3243 _headersPending = false; 3244 ubyte[] packet; 3245 for (uint i = 0;; i++) 3246 { 3247 uint pl; 3248 packet = _con.getPacket(pl); 3249 ubyte* ubp = packet.ptr; 3250 if (*ubp == 0xfe && pl < 9) // EOF packet 3251 break; 3252 3253 Row row = Row(_con, packet, _rsh, false); 3254 if (cr >= alloc) 3255 { 3256 alloc = (alloc*3)/2; 3257 rra.length = alloc; 3258 } 3259 rra[cr++] = row; 3260 } 3261 _rowsPending = _pendingBinary = false; 3262 rra.length = cr; 3263 ResultSet rs = ResultSet(rra, _rsh.fieldNames); 3264 return rs; 3265 } 3266 3267 /** 3268 * Execute a one-off SQL command for the case where you expect a result set, and want 3269 * to deal with it a row at a time. 3270 * 3271 * Use this method when you are not going to be using the same command repeatedly. 3272 * This method will throw if the SQL command does not produce a result set. 3273 * 3274 * If there are long data items among the expected result columns you can specify that they are to be 3275 * subject to chunked transfer via a delegate. 3276 * 3277 * Params: csa = An optional array of ColumnSpecialization structs. 3278 * Returns: A (possibly empty) ResultSequence. 3279 */ 3280 ResultSequence execSQLSequence(ColumnSpecialization[] csa = null) 3281 { 3282 uint alloc = 20; 3283 Row[] rra; 3284 rra.length = alloc; 3285 uint cr = 0; 3286 ulong ra; 3287 enforceEx!MYX(execSQL(ra), "The executed query did not produce a result set."); 3288 _rsh = ResultSetHeaders(_con, _fieldCount); 3289 if (csa !is null) 3290 _rsh.addSpecializations(csa); 3291 3292 _headersPending = false; 3293 return ResultSequence(&this, _rsh.fieldNames); 3294 } 3295 3296 /** 3297 * Execute a one-off SQL command to place result values into a set of D variables. 3298 * 3299 * Use this method when you are not going to be using the same command repeatedly. 3300 * It will throw if the specified command does not produce a result set, or if any column 3301 * type is incompatible with the corresponding D variable 3302 * 3303 * Params: args = A tuple of D variables to receive the results. 3304 * Returns: true if there was a (possibly empty) result set. 3305 */ 3306 void execSQLTuple(T...)(ref T args) 3307 { 3308 ulong ra; 3309 enforceEx!MYX(execSQL(ra), "The executed query did not produce a result set."); 3310 Row rr = getNextRow(); 3311 if (!rr._valid) // The result set was empty - not a crime. 3312 return; 3313 enforceEx!MYX(rr._uva.length == args.length, "Result column count does not match the target tuple."); 3314 foreach (uint i, dummy; args) 3315 { 3316 enforceEx!MYX(typeid(args[i]).toString() == rr._uva[i].type.toString(), 3317 "Tuple "~to!string(i)~" type and column type are not compatible."); 3318 args[i] = rr._uva[i].get!(typeof(args[i])); 3319 } 3320 // If there were more rows, flush them away 3321 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 3322 // allow sloppy SQL that does not ensure just one row! 3323 purgeResult(); 3324 } 3325 3326 /** 3327 * Execute a prepared command. 3328 * 3329 * Use this method when you will use the same SQL command repeatedly. 3330 * It can be used with commands that don't produce a result set, or those that do. If there is a result 3331 * set its existence will be indicated by the return value. 3332 * 3333 * Any result set can be accessed vis getNextRow(), but you should really be using execPreparedResult() 3334 * or execPreparedSequence() for such queries. 3335 * 3336 * Params: ra = An out parameter to receive the number of rows affected. 3337 * Returns: true if there was a (possibly empty) result set. 3338 */ 3339 bool execPrepared(out ulong ra) 3340 { 3341 if (!_hStmt) 3342 throw new Exception("The statement has not been prepared."); 3343 ubyte[] packet; 3344 _con.resetPacket(); 3345 3346 ubyte[] prefix = makePSPrefix(0); 3347 size_t len = prefix.length; 3348 bool longData; 3349 3350 if (_psh._paramCount) 3351 { 3352 ubyte[] one = [ 1 ]; 3353 ubyte[] vals; 3354 ubyte[] types = analyseParams(vals, longData); 3355 ubyte[] nbm = makeBitmap(_psa); 3356 packet = prefix ~ nbm ~ one ~ types ~ vals; 3357 } 3358 else 3359 packet = prefix; 3360 3361 if (longData) 3362 sendLongData(); 3363 3364 assert(packet.length <= uint.max); 3365 uint pl = cast(uint)packet.length - 4; 3366 packet[0] = cast(ubyte) (pl & 0xff); 3367 packet[1] = cast(ubyte) ((pl >> 8) & 0xff); 3368 packet[2] = cast(ubyte) ((pl >> 16) & 0xff); 3369 packet[3] = _con.pktNumber; 3370 _con.bumpPacket(); 3371 _con.send(packet); 3372 packet = _con.getPacket(pl); 3373 ubyte* ubp = packet.ptr; 3374 bool rv; 3375 if (*ubp == 0 || *ubp == 255) 3376 { 3377 _con.resetPacket(); 3378 OKPacket okp = OKPacket(ubp, pl); 3379 if (okp.error) 3380 { 3381 throw new Exception("MySQL Error: " ~ cast(string) okp.message); 3382 } 3383 ra = okp.affected; 3384 _con._serverStatus = okp.serverStatus; 3385 _insertID = okp.insertID; 3386 rv = false; 3387 } 3388 else 3389 { 3390 // There was presumably a result set 3391 _headersPending = _rowsPending = _pendingBinary = true; 3392 bool gash; 3393 _fieldCount = cast(ushort) parseLCB(ubp, gash); 3394 rv = true; 3395 } 3396 return rv; 3397 } 3398 3399 /** 3400 * Execute a prepared SQL command for the case where you expect a result set, and want it all at once. 3401 * 3402 * Use this method when you will use the same command repeatedly. 3403 * This method will throw if the SQL command does not produce a result set. 3404 * 3405 * If there are long data items among the expected result columns you can specify that they are to be 3406 * subject to chunked transfer via a delegate. 3407 * 3408 * Params: csa = An optional array of ColumnSpecialization structs. 3409 * Returns: A (possibly empty) ResultSet. 3410 */ 3411 ResultSet execPreparedResult(ColumnSpecialization[] csa = null) 3412 { 3413 ulong ra; 3414 enforceEx!MYX(execPrepared(ra), "The executed query did not produce a result set."); 3415 uint alloc = 20; 3416 Row[] rra; 3417 rra.length = alloc; 3418 uint cr = 0; 3419 _rsh = ResultSetHeaders(_con, _fieldCount); 3420 if (csa !is null) 3421 _rsh.addSpecializations(csa); 3422 _headersPending = false; 3423 ubyte[] packet; 3424 for (uint i = 0;; i++) 3425 { 3426 uint pl; 3427 packet = _con.getPacket(pl); 3428 ubyte* ubp = packet.ptr; 3429 if (*ubp == 0xfe && pl < 9) // EOF packet 3430 break; 3431 Row row = Row(_con, packet, _rsh, true); 3432 if (cr >= alloc) 3433 { 3434 alloc = (alloc*3)/2; 3435 rra.length = alloc; 3436 } 3437 rra[cr++] = row; 3438 } 3439 _rowsPending = _pendingBinary = false; 3440 rra.length = cr; 3441 ResultSet rs = ResultSet(rra, _rsh.fieldNames); 3442 return rs; 3443 } 3444 3445 /** 3446 * Execute a prepared SQL command for the case where you expect a result set, and want 3447 * to deal with it one row at a time. 3448 * 3449 * Use this method when you will use the same command repeatedly. 3450 * This method will throw if the SQL command does not produce a result set. 3451 * 3452 * If there are long data items among the expected result columns you can specify that they are to be 3453 * subject to chunked transfer via a delegate. 3454 * 3455 * Params: csa = An optional array of ColumnSpecialization structs. 3456 * Returns: A (possibly empty) ResultSequence. 3457 */ 3458 ResultSequence execPreparedSequence(ColumnSpecialization[] csa = null) 3459 { 3460 ulong ra; 3461 enforceEx!MYX(execPrepared(ra), "The executed query did not produce a result set."); 3462 uint alloc = 20; 3463 Row[] rra; 3464 rra.length = alloc; 3465 uint cr = 0; 3466 _rsh = ResultSetHeaders(_con, _fieldCount); 3467 if (csa !is null) 3468 _rsh.addSpecializations(csa); 3469 _headersPending = false; 3470 return ResultSequence(&this, _rsh.fieldNames); 3471 } 3472 3473 /** 3474 * Execute a prepared SQL command to place result values into a set of D variables. 3475 * 3476 * Use this method when you will use the same command repeatedly. 3477 * It will throw if the specified command does not produce a result set, or if any column 3478 * type is incompatible with the corresponding D variable 3479 * 3480 * Params: args = A tuple of D variables to receive the results. 3481 * Returns: true if there was a (possibly empty) result set. 3482 */ 3483 void execPreparedTuple(T...)(ref T args) 3484 { 3485 ulong ra; 3486 enforceEx!MYX(execPrepared(ra), "The executed query did not produce a result set."); 3487 Row rr = getNextRow(); 3488 enforceEx!MYX(rr._valid, "The result set was empty."); 3489 enforceEx!MYX(rr._uva.length == args.length, "Result column count does not match the target tuple."); 3490 foreach (uint i, dummy; args) 3491 { 3492 enforceEx!MYX(typeid(args[i]).toString() == rr._uva[i].type.toString(), 3493 "Tuple "~to!string(i)~" type and column type are not compatible."); 3494 args[i] = rr._uva[i].get!(typeof(args[i])); 3495 } 3496 // If there were more rows, flush them away 3497 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 3498 // allow sloppy SQL that does not ensure just one row! 3499 purgeResult(); 3500 } 3501 3502 /** 3503 * Get the next Row of a pending result set. 3504 * 3505 * This method can be used after either execSQL() or execPrepared() have returned true 3506 * to retrieve result set rows sequentially. 3507 * 3508 * Similar functionality is available via execSQLSequence() and execPreparedSequence() in 3509 * which case the interface is presented as a forward range of Rows. 3510 * 3511 * This method allows you to deal with very large result sets either a row at a time, or by 3512 * feeding the rows into some suitable container such as a linked list. 3513 * 3514 * Returns: A Row object. 3515 */ 3516 Row getNextRow() 3517 { 3518 if (_headersPending) 3519 { 3520 _rsh = ResultSetHeaders(_con, _fieldCount); 3521 _headersPending = false; 3522 } 3523 ubyte[] packet; 3524 Row rr; 3525 uint pl; 3526 packet = _con.getPacket(pl); 3527 ubyte* ubp = packet.ptr; 3528 if (*ubp == 0xfe && pl < 9) // EOF packet 3529 { 3530 _rowsPending = _pendingBinary = false; 3531 return rr; 3532 } 3533 if (_pendingBinary) 3534 rr = Row(_con, packet, _rsh, true); 3535 else 3536 rr = Row(_con, packet, _rsh, false); 3537 rr._valid = true; 3538 return rr; 3539 } 3540 3541 /** 3542 * Execute a stored function, with any required input variables, and store the return value into a D variable. 3543 * 3544 * For this method, no query string is to be provided. The required one is of the form "select foo(?, ? ...)". 3545 * The method generates it and the appropriate bindings - in, and out. Chunked transfers are not supported 3546 * in either direction. If you need them, create the parameters separately, then use execPreparedResult() 3547 * to get a one-row, one-column result set. 3548 * 3549 * If it is not possible to convert the column value to the type of target, then execFunction will throw. 3550 * If the result is NULL, that is indicated by a false return value, and target is unchanged. 3551 * 3552 * In the interest of performance, this method assumes that the user has the required information about 3553 * the number and types of IN parameters and the type of the output variable. In the same interest, if the 3554 * method is called repeatedly for the same stored function, prepare() is omitted after the first call. 3555 * 3556 * Params: 3557 * T = The type of the variable to receive the return result. 3558 * U = type tuple of arguments 3559 * name = The name of the stored function. 3560 * target = the D variable to receive the stored function return result. 3561 * args = The list of D variables to act as IN arguments to the stored function. 3562 * 3563 */ 3564 bool execFunction(T, U...)(string name, ref T target, U args) 3565 { 3566 bool repeatCall = (name == _prevFunc); 3567 enforceEx!MYX(repeatCall || _hStmt == 0, "You must not prepare the statement before calling execFunction"); 3568 if (!repeatCall) 3569 { 3570 _sql = "select " ~ name ~ "("; 3571 bool comma = false; 3572 foreach (arg; args) 3573 { 3574 if (comma) 3575 _sql ~= ",?"; 3576 else 3577 { 3578 _sql ~= "?"; 3579 comma = true; 3580 } 3581 } 3582 _sql ~= ")"; 3583 prepare(); 3584 _prevFunc = name; 3585 } 3586 bindParameterTuple(args); 3587 ulong ra; 3588 enforceEx!MYX(execPrepared(ra), "The executed query did not produce a result set."); 3589 Row rr = getNextRow(); 3590 enforceEx!MYX(rr._valid, "The result set was empty."); 3591 enforceEx!MYX(rr._uva.length == 1, "Result was not a single column."); 3592 enforceEx!MYX(typeid(target).toString() == rr._uva[0].type.toString(), 3593 "Target type and column type are not compatible."); 3594 if (!rr.isNull(0)) 3595 target = rr._uva[0].get!(T); 3596 // If there were more rows, flush them away 3597 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 3598 // allow sloppy SQL that does not ensure just one row! 3599 purgeResult(); 3600 return !rr.isNull(0); 3601 } 3602 3603 /** 3604 * Execute a stored procedure, with any required input variables. 3605 * 3606 * For this method, no query string is to be provided. The required one is of the form "call proc(?, ? ...)". 3607 * The method generates it and the appropriate in bindings. Chunked transfers are not supported. 3608 * If you need them, create the parameters separately, then use execPrepared() or execPreparedResult(). 3609 * 3610 * In the interest of performance, this method assumes that the user has the required information about 3611 * the number and types of IN parameters. In the same interest, if the method is called repeatedly for the 3612 * same stored function, prepare() and other redundant operations are omitted after the first call. 3613 * 3614 * OUT parameters are not currently supported. It should generally be possible with MySQL to present 3615 * them as a result set. 3616 * 3617 * Params: 3618 * T = Type tuple 3619 * name = The name of the stored procedure. 3620 * args = Tuple of args 3621 * Returns: True if the SP created a result set. 3622 */ 3623 bool execProcedure(T...)(string name, ref T args) 3624 { 3625 bool repeatCall = (name == _prevFunc); 3626 enforceEx!MYX(repeatCall || _hStmt == 0, "You must not prepare a statement before calling execProcedure"); 3627 if (!repeatCall) 3628 { 3629 _sql = "call " ~ name ~ "("; 3630 bool comma = false; 3631 foreach (arg; args) 3632 { 3633 if (comma) 3634 _sql ~= ",?"; 3635 else 3636 { 3637 _sql ~= "?"; 3638 comma = true; 3639 } 3640 } 3641 _sql ~= ")"; 3642 prepare(); 3643 _prevFunc = name; 3644 } 3645 bindParameterTuple(args); 3646 ulong ra; 3647 return execPrepared(ra); 3648 } 3649 3650 /// After a command that inserted a row into a table with an auto-increment ID 3651 /// column, this method allows you to retrieve the last insert ID. 3652 @ property ulong lastInsertID() { return _insertID; } 3653 } 3654 3655 unittest 3656 { 3657 struct X 3658 { 3659 int a, b, c; 3660 string s; 3661 double d; 3662 } 3663 bool ok = true; 3664 auto c = new Connection("localhost", "user", "password", "mysqld"); 3665 scope(exit) c.close(); 3666 try 3667 { 3668 3669 ulong ra; 3670 auto c1 = Command(c); 3671 3672 c1.sql = "delete from basetest"; 3673 c1.execSQL(ra); 3674 3675 c1.sql = "insert into basetest values(" ~ 3676 "1, -128, 255, -32768, 65535, 42, 4294967295, -9223372036854775808, 18446744073709551615, 'ABC', " ~ 3677 "'The quick brown fox', 0x000102030405060708090a0b0c0d0e0f, '2007-01-01', " ~ 3678 "'12:12:12', '2007-01-01 12:12:12', 1.234567890987654, 22.4, NULL)"; 3679 c1.execSQL(ra); 3680 3681 c1.sql = "select bytecol from basetest limit 1"; 3682 ResultSet rs = c1.execSQLResult(); 3683 assert(rs.length == 1); 3684 assert(rs[0][0] == -128); 3685 c1.sql = "select ubytecol from basetest limit 1"; 3686 rs = c1.execSQLResult(); 3687 assert(rs.length == 1); 3688 assert(rs.front[0] == 255); 3689 c1.sql = "select shortcol from basetest limit 1"; 3690 rs = c1.execSQLResult(); 3691 assert(rs.length == 1); 3692 assert(rs[0][0] == short.min); 3693 c1.sql = "select ushortcol from basetest limit 1"; 3694 rs = c1.execSQLResult(); 3695 assert(rs.length == 1); 3696 assert(rs[0][0] == ushort.max); 3697 c1.sql = "select intcol from basetest limit 1"; 3698 rs = c1.execSQLResult(); 3699 assert(rs.length == 1); 3700 assert(rs[0][0] == 42); 3701 c1.sql = "select uintcol from basetest limit 1"; 3702 rs = c1.execSQLResult(); 3703 assert(rs.length == 1); 3704 assert(rs[0][0] == uint.max); 3705 c1.sql = "select longcol from basetest limit 1"; 3706 rs = c1.execSQLResult(); 3707 assert(rs.length == 1); 3708 assert(rs[0][0] == long.min); 3709 c1.sql = "select ulongcol from basetest limit 1"; 3710 rs = c1.execSQLResult(); 3711 assert(rs.length == 1); 3712 assert(rs[0][0] == ulong.max); 3713 c1.sql = "select charscol from basetest limit 1"; 3714 rs = c1.execSQLResult(); 3715 assert(rs.length == 1); 3716 assert(rs[0][0].toString() == "ABC"); 3717 c1.sql = "select stringcol from basetest limit 1"; 3718 rs = c1.execSQLResult(); 3719 assert(rs.length == 1); 3720 assert(rs[0][0].toString() == "The quick brown fox"); 3721 c1.sql = "select bytescol from basetest limit 1"; 3722 rs = c1.execSQLResult(); 3723 assert(rs.length == 1); 3724 assert(rs[0][0].toString() == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]"); 3725 c1.sql = "select datecol from basetest limit 1"; 3726 rs = c1.execSQLResult(); 3727 assert(rs.length == 1); 3728 Date d = rs[0][0].get!(Date); 3729 assert(d.year == 2007 && d.month == 1 && d.day == 1); 3730 c1.sql = "select timecol from basetest limit 1"; 3731 rs = c1.execSQLResult(); 3732 assert(rs.length == 1); 3733 TimeOfDay t = rs[0][0].get!(TimeOfDay); 3734 assert(t.hour == 12 && t.minute == 12 && t.second == 12); 3735 c1.sql = "select dtcol from basetest limit 1"; 3736 rs = c1.execSQLResult(); 3737 assert(rs.length == 1); 3738 DateTime dt = rs[0][0].get!(DateTime); 3739 assert(dt.year == 2007 && dt.month == 1 && dt.day == 1 && dt.hour == 12 && dt.minute == 12 && dt.second == 12); 3740 c1.sql = "select doublecol from basetest limit 1"; 3741 rs = c1.execSQLResult(); 3742 assert(rs.length == 1); 3743 assert(rs[0][0].toString() == "1.23457"); 3744 c1.sql = "select floatcol from basetest limit 1"; 3745 rs = c1.execSQLResult(); 3746 assert(rs.length == 1); 3747 assert(rs[0][0].toString() == "22.4"); 3748 3749 c1.sql = "select * from basetest limit 1"; 3750 rs = c1.execSQLResult(); 3751 assert(rs.length == 1); 3752 assert(rs[0][0] == true); 3753 assert(rs[0][1] == -128); 3754 assert(rs[0][2] == 255); 3755 assert(rs[0][3] == short.min); 3756 assert(rs[0][4] == ushort.max); 3757 assert(rs[0][5] == 42); 3758 assert(rs[0][6] == uint.max); 3759 assert(rs[0][7] == long.min); 3760 assert(rs[0][8] == ulong.max); 3761 assert(rs[0][9].toString() == "ABC"); 3762 assert(rs[0][10].toString() == "The quick brown fox"); 3763 assert(rs[0][11].toString() == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]"); 3764 d = rs[0][12].get!(Date); 3765 assert(d.year == 2007 && d.month == 1 && d.day == 1); 3766 t = rs[0][13].get!(TimeOfDay); 3767 assert(t.hour == 12 && t.minute == 12 && t.second == 12); 3768 dt = rs[0][14].get!(DateTime); 3769 assert(dt.year == 2007 && dt.month == 1 && dt.day == 1 && dt.hour == 12 && dt.minute == 12 && dt.second == 12); 3770 assert(rs[0][15].toString() == "1.23457"); 3771 assert(rs[0]._uva[16].toString() == "22.4"); 3772 assert(rs[0].isNull(17) == true); 3773 3774 c1.sql = "select bytecol, ushortcol, intcol, charscol, floatcol from basetest limit 1"; 3775 rs = c1.execSQLResult(); 3776 X x; 3777 rs[0].toStruct(x); 3778 assert(x.a == -128 && x.b == 65535 && x.c == 42 && x.s == "ABC" && to!string(x.d) == "22.4"); 3779 3780 c1.sql = "select * from basetest limit 1"; 3781 c1.prepare(); 3782 rs = c1.execPreparedResult(); 3783 assert(rs.length == 1); 3784 assert(rs[0][0] == true); 3785 assert(rs[0][1] == -128); 3786 assert(rs[0][2] == 255); 3787 assert(rs[0][3] == short.min); 3788 assert(rs[0][4] == ushort.max); 3789 assert(rs[0][5] == 42); 3790 assert(rs[0][6] == uint.max); 3791 assert(rs[0][7] == long.min); 3792 assert(rs[0][8] == ulong.max); 3793 assert(rs[0][9].toString() == "ABC"); 3794 assert(rs[0][10].toString() == "The quick brown fox"); 3795 assert(rs[0][11].toString() == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]"); 3796 d = rs[0][12].get!(Date); 3797 assert(d.year == 2007 && d.month == 1 && d.day == 1); 3798 t = rs[0][13].get!(TimeOfDay); 3799 assert(t.hour == 12 && t.minute == 12 && t.second == 12); 3800 dt = rs[0][14].get!(DateTime); 3801 assert(dt.year == 2007 && dt.month == 1 && dt.day == 1 && dt.hour == 12 && dt.minute == 12 && dt.second == 12); 3802 assert(rs[0][15].toString() == "1.23457"); 3803 assert(rs[0][16].toString() == "22.4"); 3804 assert(rs[0]._nulls[17] == true); 3805 3806 c1.sql = "insert into basetest (intcol, stringcol) values(?, ?)"; 3807 c1.prepare(); 3808 Variant[] va; 3809 va.length = 2; 3810 va[0] = 42; 3811 va[1] = "The quick brown fox x"; 3812 c1.bindParameters(va); 3813 foreach (int i; 0..20) 3814 { 3815 c1.execPrepared(ra); 3816 c1.param(0) += 1; 3817 c1.param(1) ~= "x"; 3818 } 3819 3820 int a; 3821 string b; 3822 c1.sql = "select intcol, stringcol from basetest where bytecol=-128 limit 1"; 3823 c1.execSQLTuple(a, b); 3824 assert(a == 42 && b == "The quick brown fox"); 3825 3826 c1.sql = "select intcol, stringcol from basetest where bytecol=? limit 1"; 3827 c1.prepare(); 3828 Variant[] va2; 3829 va2.length = 1; 3830 va2[0] = cast(byte) -128; 3831 c1.bindParameters(va2); 3832 a = 0; 3833 b = ""; 3834 c1.execPreparedTuple(a, b); 3835 assert(a == 42 && b == "The quick brown fox"); 3836 3837 c1.sql = "update basetest set intcol=? where bytecol=-128"; 3838 c1.prepare(); 3839 int referred = 555; 3840 c1.bindParameter(referred, 0); 3841 c1.execPrepared(ra); 3842 referred = 666; 3843 c1.execPrepared(ra); 3844 c1.sql = "select intcol from basetest where bytecol = -128"; 3845 int referredBack; 3846 c1.execSQLTuple(referredBack); 3847 assert(referredBack == 666); 3848 3849 // Test execFunction() 3850 string g = "Gorgeous"; 3851 string reply; 3852 c1.sql = ""; 3853 bool nonNull = c1.execFunction("hello", reply, g); 3854 assert(nonNull && reply == "Hello Gorgeous!"); 3855 g = "Hotlips"; 3856 nonNull = c1.execFunction("hello", reply, g); 3857 assert(nonNull && reply == "Hello Hotlips!"); 3858 3859 // Test execProcedure() 3860 g = "inserted string 1"; 3861 int m = 2001; 3862 c1.sql = ""; 3863 c1.execProcedure("insert2", m, g); 3864 3865 c1.sql = "select stringcol from basetest where intcol=2001"; 3866 c1.execSQLTuple(reply); 3867 assert(reply == g); 3868 3869 c1.sql = "delete from tblob"; 3870 c1.execSQL(ra); 3871 c1.sql = "insert into tblob values(321, NULL, 22.4, NULL, '2011-11-05 11:52:00')"; 3872 c1.execSQL(ra); 3873 3874 uint delegate(ubyte[]) foo() 3875 { 3876 uint n = 20000000; 3877 uint cp = 0; 3878 3879 void fill(ubyte[] a, uint m) 3880 { 3881 foreach (uint i; 0..m) 3882 { 3883 a[i] = cast(ubyte) (cp & 0xff); 3884 cp++; 3885 } 3886 } 3887 3888 uint dg(ubyte[] dest) 3889 { 3890 uint len = dest.length; 3891 if (n >= len) 3892 { 3893 fill(dest, len); 3894 n -= len; 3895 return len; 3896 } 3897 fill(dest, n); 3898 return n; 3899 } 3900 3901 return &dg; 3902 } 3903 /+ 3904 c1.sql = "update tblob set lob=?, lob2=? where ikey=321"; 3905 c1.prepare(); 3906 ubyte[] uba; 3907 ubyte[] uba2; 3908 c1.bindParameter(uba, 0, PSN(0, false, SQLType.LONGBLOB, 10000, foo())); 3909 c1.bindParameter(uba2, 1, PSN(1, false, SQLType.LONGBLOB, 10000, foo())); 3910 c1.execPrepared(ra); 3911 3912 uint got1, got2; 3913 bool verified1, verified2; 3914 void delegate(ubyte[], bool) bar1(ref uint got, ref bool verified) 3915 { 3916 got = 0; 3917 verified = true; 3918 3919 void dg(ubyte[] ba, bool finished) 3920 { 3921 foreach (uint; 0..ba.length) 3922 { 3923 if (verified && ba[i] != ((got+i) & 0xff)) 3924 verified = false; 3925 } 3926 got += ba.length; 3927 } 3928 return &dg; 3929 } 3930 3931 void delegate(ubyte[], bool) bar2(ref uint got, ref bool verified) 3932 { 3933 got = 0; 3934 verified = true; 3935 3936 void dg(ubyte[] ba, bool finished) 3937 { 3938 foreach (uint i; 0..ba.length) 3939 { 3940 if (verified && ba[i] != ((got+i) & 0xff)) 3941 verified = false; 3942 } 3943 got += ba.length; 3944 } 3945 return &dg; 3946 } 3947 3948 c1.sql = "select * from tblob limit 1"; 3949 rs = c1.execSQLResult(); 3950 ubyte[] blob = rs[0][1].get!(ubyte[]); 3951 ubyte[] blob2 = rs[0][3].get!(ubyte[]); 3952 DateTime dt4 = rs[0][4].get!(DateTime); 3953 writefln("blob. lengths %d %d", blob.length, blob2.length); 3954 writeln(to!string(dt4)); 3955 3956 3957 c1.sql = "select * from tblob limit 1"; 3958 CSN[] csa = [ CSN(1, 0xfc, 100000, bar1(got1, verified1)), CSN(3, 0xfc, 100000, bar2(got2, verified2)) ]; 3959 rs = c1.execSQLResult(csa); 3960 writefln("1) %d, %s", got1, verified1); 3961 writefln("2) %d, %s", got2, verified2); 3962 DateTime dt4 = rs[0][4].get!(DateTime); 3963 writeln(to!string(dt4)); 3964 +/ 3965 } 3966 catch (Exception x) 3967 { 3968 writefln("(%s: %s) %s", x.file, x.line, x.msg); 3969 ok = false; 3970 } 3971 assert(ok); 3972 writeln("Command unit tests completed OK."); 3973 } 3974 3975 /** 3976 * A struct to hold column metadata 3977 * 3978 */ 3979 struct MySQLColumn 3980 { 3981 /// The database that the table having this column belongs to. 3982 string schema; 3983 /// The table that this column belongs to. 3984 string table; 3985 /// The name of the column. 3986 string name; 3987 /// Zero based index of the column within a table row. 3988 uint index; 3989 /// Is the default value NULL? 3990 bool defaultNull; 3991 /// The default value as a string if not NULL 3992 string defaultValue; 3993 /// Can the column value be set to NULL 3994 bool nullable; 3995 /// What type is the column - tinyint, char, varchar, blob, date etc 3996 string type; // varchar tinyint etc 3997 /// Capacity in characters, -1L if not applicable 3998 long charsMax; 3999 /// Capacity in bytes - same as chars if not a unicode table definition, -1L if not applicable. 4000 long octetsMax; 4001 /// Presentation information for numerics, -1L if not applicable. 4002 short numericPrecision; 4003 /// Scale information for numerics or NULL, -1L if not applicable. 4004 short numericScale; 4005 /// Character set, "<NULL>" if not applicable. 4006 string charSet; 4007 /// Collation, "<NULL>" if not applicable. 4008 string collation; 4009 /// More detail about the column type, e.g. "int(10) unsigned". 4010 string colType; 4011 /// Information about the column's key status, blank if none. 4012 string key; 4013 /// Extra information. 4014 string extra; 4015 /// Privileges for logged in user. 4016 string privileges; 4017 /// Any comment that was set at table definition time. 4018 string comment; 4019 } 4020 4021 /** 4022 * A struct to hold stored function metadata 4023 * 4024 */ 4025 struct MySQLProcedure 4026 { 4027 string db; 4028 string name; 4029 string type; 4030 string definer; 4031 DateTime modified; 4032 DateTime created; 4033 string securityType; 4034 string comment; 4035 string charSetClient; 4036 string collationConnection; 4037 string collationDB; 4038 } 4039 4040 /** 4041 * Facilities to recover meta-data from a connection 4042 * 4043 * It is important to bear in mind that the methods provided will only return the 4044 * information that is available to the connected user. This may well be quite limited. 4045 */ 4046 struct MetaData 4047 { 4048 private: 4049 Connection _con; 4050 ResultSet _rs; 4051 Command _mdc; 4052 4053 MySQLProcedure[] stored(bool procs) 4054 { 4055 enforceEx!MYX(_con.currentDB.length, "There is no selected database"); 4056 string query = procs? "show procedure status where db='": "show function status where db='"; 4057 query ~= _con.currentDB ~ "'"; 4058 4059 _mdc = Command(_con, query); 4060 _rs = _mdc.execSQLResult(); 4061 MySQLProcedure[] pa; 4062 size_t n = _rs.length; 4063 pa.length = n; 4064 foreach (size_t i; 0..n) 4065 { 4066 MySQLProcedure foo; 4067 Row r = _rs[i]; 4068 foreach (int j; 0..11) 4069 { 4070 string t; 4071 bool isNull = r.isNull(j); 4072 if (!isNull) 4073 t = r[j].toString(); 4074 else 4075 continue; 4076 switch (j) 4077 { 4078 case 0: 4079 foo.db = t; 4080 break; 4081 case 1: 4082 foo.name = t; 4083 break; 4084 case 2: 4085 foo.type = t; 4086 break; 4087 case 3: 4088 foo.definer = t; 4089 break; 4090 case 4: 4091 foo.modified = r[j].get!(DateTime); 4092 break; 4093 case 5: 4094 foo.created = r[j].get!(DateTime); 4095 break; 4096 case 6: 4097 foo.securityType = t; 4098 break; 4099 case 7: 4100 foo.comment = t; 4101 break; 4102 case 8: 4103 foo.charSetClient = t; 4104 break; 4105 case 9: 4106 foo.collationConnection = t; 4107 break; 4108 case 10: 4109 foo.collationDB = t; 4110 break; 4111 default: 4112 break; 4113 } 4114 } 4115 pa[i] = foo; 4116 } 4117 return pa; 4118 } 4119 4120 public: 4121 this(Connection con) 4122 { 4123 _con = con; 4124 } 4125 4126 /** 4127 * List the available databases 4128 * 4129 * Note that if you have connected using the credentials of a user with limited permissions 4130 * you may not get many results. 4131 * 4132 * Returns: 4133 * An array of strings 4134 */ 4135 string[] databases() 4136 { 4137 string[] rv; 4138 _mdc = Command(_con, "show databases"); 4139 _rs = _mdc.execSQLResult(); 4140 size_t n = _rs.length; 4141 foreach (size_t i; 0..n) 4142 { 4143 string s; 4144 Row r = _rs[i]; 4145 rv ~= r[0].toString(); 4146 } 4147 return rv; 4148 } 4149 4150 /** 4151 * List the tables in the current database 4152 * 4153 * Returns: 4154 * An array of strings 4155 */ 4156 string[] tables() 4157 { 4158 string[] rv; 4159 _mdc = Command(_con, "show tables"); 4160 _rs = _mdc.execSQLResult(); 4161 size_t n = _rs.length; 4162 foreach (size_t i; 0..n) 4163 { 4164 string s; 4165 Row r = _rs[i]; 4166 rv ~= r[0].toString(); 4167 } 4168 return rv; 4169 } 4170 4171 /** 4172 * Get column metadata for a table in the current database 4173 * 4174 * Params: 4175 * table = The table name 4176 * Returns: 4177 * An array of MySQLColumn structs 4178 */ 4179 MySQLColumn[] columns(string table) 4180 { 4181 string query = "select * from information_schema.COLUMNS where table_name='" ~ table ~ "'"; 4182 _mdc = Command(_con, query); 4183 _rs = _mdc.execSQLResult(); 4184 size_t n = _rs.length; 4185 MySQLColumn[] ca; 4186 ca.length = n; 4187 foreach (size_t i; 0..n) 4188 { 4189 MySQLColumn col; 4190 Row r = _rs[i]; 4191 for (int j = 1; j < 19; j++) 4192 { 4193 string t; 4194 bool isNull = r.isNull(j); 4195 if (!isNull) 4196 t = to!string(r[j]); 4197 switch (j) 4198 { 4199 case 1: 4200 col.schema = t; 4201 break; 4202 case 2: 4203 col.table = t; 4204 break; 4205 case 3: 4206 col.name = t; 4207 break; 4208 case 4: 4209 col.index = cast(uint)(r[j].get!(ulong)-1); 4210 break; 4211 case 5: 4212 if (isNull) 4213 col.defaultNull = true; 4214 else 4215 col.defaultValue = t; 4216 break; 4217 case 6: 4218 if (t == "YES") 4219 col.nullable = true; 4220 break; 4221 case 7: 4222 col.type = t; 4223 break; 4224 case 8: 4225 col.charsMax = cast(long)(isNull? -1L: r[j].get!(ulong)); 4226 break; 4227 case 9: 4228 col.octetsMax = cast(long)(isNull? -1L: r[j].get!(ulong)); 4229 break; 4230 case 10: 4231 col.numericPrecision = cast(short) (isNull? -1: r[j].get!(ulong)); 4232 break; 4233 case 11: 4234 col.numericScale = cast(short) (isNull? -1: r[j].get!(ulong)); 4235 break; 4236 case 12: 4237 col.charSet = isNull? "<NULL>": t; 4238 break; 4239 case 13: 4240 col.collation = isNull? "<NULL>": t; 4241 break; 4242 case 14: 4243 col.colType = t; 4244 break; 4245 case 15: 4246 col.key = t; 4247 break; 4248 case 16: 4249 col.extra = t; 4250 break; 4251 case 17: 4252 col.privileges = t; 4253 break; 4254 case 18: 4255 col.comment = t; 4256 break; 4257 default: 4258 break; 4259 } 4260 } 4261 ca[i] = col; 4262 } 4263 return ca; 4264 } 4265 4266 /** 4267 * Get list of stored functions in the current database, and their properties 4268 * 4269 */ 4270 MySQLProcedure[] functions() 4271 { 4272 return stored(false); 4273 } 4274 4275 /** 4276 * Get list of stored procedures in the current database, and their properties 4277 * 4278 */ 4279 MySQLProcedure[] procedures() 4280 { 4281 return stored(true); 4282 } 4283 } 4284 4285 unittest 4286 { 4287 auto c = new Connection("localhost", "user", "password", "mysqld"); 4288 scope(exit) c.close(); 4289 MetaData md = MetaData(c); 4290 string[] dbList = md.databases(); 4291 int count = 0; 4292 foreach (string db; dbList) 4293 { 4294 if (db == "mysqld" || db == "information_schema") 4295 count++; 4296 } 4297 assert(count == 2); 4298 string[] tList = md.tables(); 4299 count = 0; 4300 foreach (string t; tList) 4301 { 4302 if (t == "basetest" || t == "tblob") 4303 count++; 4304 } 4305 assert(count == 2); 4306 4307 MySQLColumn[] ca = md.columns("basetest"); 4308 assert(ca[0].schema == "mysqld" && ca[0].table == "basetest" && ca[0].name == "boolcol" && ca[0].index == 0 && 4309 ca[0].defaultNull && ca[0].nullable && ca[0].type == "bit" && ca[0].charsMax == -1 && ca[0].octetsMax == -1 && 4310 ca[0].numericPrecision == 1 && ca[0].numericScale == -1 && ca[0].charSet == "<NULL>" && ca[0].collation == "<NULL>" && 4311 ca[0].colType == "bit(1)"); 4312 assert(ca[1].schema == "mysqld" && ca[1].table == "basetest" && ca[1].name == "bytecol" && ca[1].index == 1 && 4313 ca[1].defaultNull && ca[1].nullable && ca[1].type == "tinyint" && ca[1].charsMax == -1 && ca[1].octetsMax == -1 && 4314 ca[1].numericPrecision == 3 && ca[1].numericScale == 0 && ca[1].charSet == "<NULL>" && ca[1].collation == "<NULL>" && 4315 ca[1].colType == "tinyint(4)"); 4316 assert(ca[2].schema == "mysqld" && ca[2].table == "basetest" && ca[2].name == "ubytecol" && ca[2].index == 2 && 4317 ca[2].defaultNull && ca[2].nullable && ca[2].type == "tinyint" && ca[2].charsMax == -1 && ca[2].octetsMax == -1 && 4318 ca[2].numericPrecision == 3 && ca[2].numericScale == 0 && ca[2].charSet == "<NULL>" && ca[2].collation == "<NULL>" && 4319 ca[2].colType == "tinyint(3) unsigned"); 4320 assert(ca[3].schema == "mysqld" && ca[3].table == "basetest" && ca[3].name == "shortcol" && ca[3].index == 3 && 4321 ca[3].defaultNull && ca[3].nullable && ca[3].type == "smallint" && ca[3].charsMax == -1 && ca[3].octetsMax == -1 && 4322 ca[3].numericPrecision == 5 && ca[3].numericScale == 0 && ca[3].charSet == "<NULL>" && ca[3].collation == "<NULL>" && 4323 ca[3].colType == "smallint(6)"); 4324 assert(ca[4].schema == "mysqld" && ca[4].table == "basetest" && ca[4].name == "ushortcol" && ca[4].index == 4 && 4325 ca[4].defaultNull && ca[4].nullable && ca[4].type == "smallint" && ca[4].charsMax == -1 && ca[4].octetsMax == -1 && 4326 ca[4].numericPrecision == 5 && ca[4].numericScale == 0 && ca[4].charSet == "<NULL>" && ca[4].collation == "<NULL>" && 4327 ca[4].colType == "smallint(5) unsigned"); 4328 assert(ca[5].schema == "mysqld" && ca[5].table == "basetest" && ca[5].name == "intcol" && ca[5].index == 5 && 4329 ca[5].defaultNull && ca[5].nullable && ca[5].type == "int" && ca[5].charsMax == -1 && ca[5].octetsMax == -1 && 4330 ca[5].numericPrecision == 10 && ca[5].numericScale == 0 && ca[5].charSet == "<NULL>" && ca[5].collation == "<NULL>" && 4331 ca[5].colType == "int(11)"); 4332 assert(ca[6].schema == "mysqld" && ca[6].table == "basetest" && ca[6].name == "uintcol" && ca[6].index == 6 && 4333 ca[6].defaultNull && ca[6].nullable && ca[6].type == "int" && ca[6].charsMax == -1 && ca[6].octetsMax == -1 && 4334 ca[6].numericPrecision == 10 && ca[6].numericScale == 0 && ca[6].charSet == "<NULL>" && ca[6].collation == "<NULL>" && 4335 ca[6].colType == "int(10) unsigned"); 4336 assert(ca[7].schema == "mysqld" && ca[7].table == "basetest" && ca[7].name == "longcol" && ca[7].index == 7 && 4337 ca[7].defaultNull && ca[7].nullable && ca[7].type == "bigint" && ca[7].charsMax == -1 && ca[7].octetsMax == -1 && 4338 ca[7].numericPrecision == 19 && ca[7].numericScale == 0 && ca[7].charSet == "<NULL>" && ca[7].collation == "<NULL>" && 4339 ca[7].colType == "bigint(20)"); 4340 assert(ca[8].schema == "mysqld" && ca[8].table == "basetest" && ca[8].name == "ulongcol" && ca[8].index == 8 && 4341 ca[8].defaultNull && ca[8].nullable && ca[8].type == "bigint" && ca[8].charsMax == -1 && ca[8].octetsMax == -1 && 4342 ca[8].numericPrecision == 20 && ca[8].numericScale == 0 && ca[8].charSet == "<NULL>" && ca[8].collation == "<NULL>" && 4343 ca[8].colType == "bigint(20) unsigned"); 4344 assert(ca[9].schema == "mysqld" && ca[9].table == "basetest" && ca[9].name == "charscol" && ca[9].index == 9 && 4345 ca[9].defaultNull && ca[9].nullable && ca[9].type == "char" && ca[9].charsMax == 10 && ca[9].octetsMax == 10 && 4346 ca[9].numericPrecision == -1 && ca[9].numericScale == -1 && ca[9].charSet == "latin1" && ca[9].collation == "latin1_swedish_ci" && 4347 ca[9].colType == "char(10)"); 4348 assert(ca[10].schema == "mysqld" && ca[10].table == "basetest" && ca[10].name == "stringcol" && ca[10].index == 10 && 4349 ca[10].defaultNull && ca[10].nullable && ca[10].type == "varchar" && ca[10].charsMax == 50 && ca[10].octetsMax == 50 && 4350 ca[10].numericPrecision == -1 && ca[10].numericScale == -1 && ca[10].charSet == "latin1" && ca[10].collation == "latin1_swedish_ci" && 4351 ca[10].colType == "varchar(50)"); 4352 assert(ca[11].schema == "mysqld" && ca[11].table == "basetest" && ca[11].name == "bytescol" && ca[11].index == 11 && 4353 ca[11].defaultNull && ca[11].nullable && ca[11].type == "tinyblob" && ca[11].charsMax == 255 && ca[11].octetsMax == 255 && 4354 ca[11].numericPrecision == -1 && ca[11].numericScale == -1 && ca[11].charSet == "<NULL>" && ca[11].collation == "<NULL>" && 4355 ca[11].colType == "tinyblob"); 4356 assert(ca[12].schema == "mysqld" && ca[12].table == "basetest" && ca[12].name == "datecol" && ca[12].index == 12 && 4357 ca[12].defaultNull && ca[12].nullable && ca[12].type == "date" && ca[12].charsMax == -1 && ca[12].octetsMax == -1 && 4358 ca[12].numericPrecision == -1 && ca[12].numericScale == -1 && ca[12].charSet == "<NULL>" && ca[12].collation == "<NULL>" && 4359 ca[12].colType == "date"); 4360 assert(ca[13].schema == "mysqld" && ca[13].table == "basetest" && ca[13].name == "timecol" && ca[13].index == 13 && 4361 ca[13].defaultNull && ca[13].nullable && ca[13].type == "time" && ca[13].charsMax == -1 && ca[13].octetsMax == -1 && 4362 ca[13].numericPrecision == -1 && ca[13].numericScale == -1 && ca[13].charSet == "<NULL>" && ca[13].collation == "<NULL>" && 4363 ca[13].colType == "time"); 4364 assert(ca[14].schema == "mysqld" && ca[14].table == "basetest" && ca[14].name == "dtcol" && ca[14].index == 14 && 4365 ca[14].defaultNull && ca[14].nullable && ca[14].type == "datetime" && ca[14].charsMax == -1 && ca[14].octetsMax == -1 && 4366 ca[14].numericPrecision == -1 && ca[14].numericScale == -1 && ca[14].charSet == "<NULL>" && ca[14].collation == "<NULL>" && 4367 ca[14].colType == "datetime"); 4368 assert(ca[15].schema == "mysqld" && ca[15].table == "basetest" && ca[15].name == "doublecol" && ca[15].index == 15 && 4369 ca[15].defaultNull && ca[15].nullable && ca[15].type == "double" && ca[15].charsMax == -1 && ca[15].octetsMax == -1 && 4370 ca[15].numericPrecision == 22 && ca[15].numericScale == -1 && ca[15].charSet == "<NULL>" && ca[15].collation == "<NULL>" && 4371 ca[15].colType == "double"); 4372 assert(ca[16].schema == "mysqld" && ca[16].table == "basetest" && ca[16].name == "floatcol" && ca[16].index == 16 && 4373 ca[16].defaultNull && ca[16].nullable && ca[16].type == "float" && ca[16].charsMax == -1 && ca[16].octetsMax == -1 && 4374 ca[16].numericPrecision == 12 && ca[16].numericScale == -1 && ca[16].charSet == "<NULL>" && ca[16].collation == "<NULL>" && 4375 ca[16].colType == "float"); 4376 assert(ca[17].schema == "mysqld" && ca[17].table == "basetest" && ca[17].name == "nullcol" && ca[17].index == 17 && 4377 ca[17].defaultNull && ca[17].nullable && ca[17].type == "int" && ca[17].charsMax == -1 && ca[17].octetsMax == -1 && 4378 ca[17].numericPrecision == 10 && ca[17].numericScale == 0 && ca[17].charSet == "<NULL>" && ca[17].collation == "<NULL>" && 4379 ca[17].colType == "int(11)"); 4380 MySQLProcedure[] pa = md.functions(); 4381 assert(pa[0].db == "mysqld" && pa[0].name == "hello" && pa[0].type == "FUNCTION"); 4382 pa = md.procedures(); 4383 assert(pa[0].db == "mysqld" && pa[0].name == "insert2" && pa[0].type == "PROCEDURE"); 4384 } 4385