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