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 }