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