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