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