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