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