1 module mysql.protocol.packet_helpers;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.datetime;
6 import std.digest.sha;
7 import std.exception;
8 import std.range;
9 import std.socket;
10 import std.stdio;
11 import std.string;
12 import std.traits;
13 import std.variant;
14 
15 import mysql.common;
16 import mysql.protocol.constants;
17 import mysql.protocol.extra_types;
18 
19 /**
20  * Function to extract a time difference from a binary encoded row.
21  *
22  * Time/date structures are packed by the server into a byte sub-packet
23  * with a leading length byte, and a minimal number of bytes to embody the data.
24  *
25  * Params: a = slice of a protocol packet beginning at the length byte for a chunk of time data
26  *
27  * Returns: A populated or default initialized TimeDiff struct.
28  */
29 TimeDiff toTimeDiff(in ubyte[] a) pure
30 {
31     enforceEx!MYXProtocol(a.length, "Supplied byte array is zero length");
32     TimeDiff td;
33     uint l = a[0];
34     enforceEx!MYXProtocol(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row.");
35     if (l >= 5)
36     {
37         td.negative = (a[1]  != 0);
38         td.days     = (a[5] << 24) + (a[4] << 16) + (a[3] << 8) + a[2];
39     }
40     if (l >= 8)
41     {
42         td.hours    = a[6];
43         td.minutes  = a[7];
44         td.seconds  = a[8];
45     }
46     // Note that the fractional seconds part is not stored by MySQL
47     return td;
48 }
49 
50 /**
51  * Function to extract a time difference from a text encoded column value.
52  *
53  * Text representations of a time difference are like -750:12:02 - 750 hours
54  * 12 minutes and two seconds ago.
55  *
56  * Params: s = A string representation of the time difference.
57  * Returns: A populated or default initialized TimeDiff struct.
58  */
59 TimeDiff toTimeDiff(string s)
60 {
61     TimeDiff td;
62     int t = parse!int(s);
63     if (t < 0)
64     {
65         td.negative = true;
66         t = -t;
67     }
68     td.hours    = cast(ubyte) t%24;
69     td.days     = cast(ubyte) t/24;
70     munch(s, ":");
71     td.minutes  = parse!ubyte(s);
72     munch(s, ":");
73     td.seconds  = parse!ubyte(s);
74     return td;
75 }
76 
77 /**
78  * Function to extract a TimeOfDay from a binary encoded row.
79  *
80  * Time/date structures are packed by the server into a byte sub-packet
81  * with a leading length byte, and a minimal number of bytes to embody the data.
82  *
83  * Params: a = slice of a protocol packet beginning at the length byte for a
84  *             chunk of time data.
85  * Returns: A populated or default initialized std.datetime.TimeOfDay struct.
86  */
87 TimeOfDay toTimeOfDay(in ubyte[] a) pure
88 {
89     enforceEx!MYXProtocol(a.length, "Supplied byte array is zero length");
90     uint l = a[0];
91     enforceEx!MYXProtocol(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row.");
92     enforceEx!MYXProtocol(l >= 8, "Time column value is not in a time-of-day format");
93 
94     TimeOfDay tod;
95     tod.hour    = a[6];
96     tod.minute  = a[7];
97     tod.second  = a[8];
98     return tod;
99 }
100 
101 /**
102  * Function to extract a TimeOfDay from a text encoded column value.
103  *
104  * Text representations of a time of day are as in 14:22:02
105  *
106  * Params: s = A string representation of the time.
107  * Returns: A populated or default initialized std.datetime.TimeOfDay struct.
108  */
109 TimeOfDay toTimeOfDay(string s)
110 {
111     TimeOfDay tod;
112     tod.hour = parse!int(s);
113     enforceEx!MYXProtocol(tod.hour <= 24 && tod.hour >= 0, "Time column value is in time difference form");
114     munch(s, ":");
115     tod.minute = parse!ubyte(s);
116     munch(s, ":");
117     tod.second = parse!ubyte(s);
118     return tod;
119 }
120 
121 /**
122  * Function to pack a TimeOfDay into a binary encoding for transmission to the server.
123  *
124  * Time/date structures are packed into a string of bytes with a leading length
125  * byte, and a minimal number of bytes to embody the data.
126  *
127  * Params: tod = TimeOfDay struct.
128  * Returns: Packed ubyte[].
129  */
130 ubyte[] pack(in TimeOfDay tod) pure nothrow
131 {
132     ubyte[] rv;
133     if (tod == TimeOfDay.init)
134     {
135         rv.length = 1;
136         rv[0] = 0;
137     }
138     else
139     {
140         rv.length = 9;
141         rv[0] = 8;
142         rv[6] = tod.hour;
143         rv[7] = tod.minute;
144         rv[8] = tod.second;
145     }
146     return rv;
147 }
148 
149 /**
150  * Function to extract a Date from a binary encoded row.
151  *
152  * Time/date structures are packed by the server into a byte sub-packet
153  * with a leading length byte, and a minimal number of bytes to embody the data.
154  *
155  * Params: a = slice of a protocol packet beginning at the length byte for a
156  *             chunk of Date data.
157  * Returns: A populated or default initialized std.datetime.Date struct.
158  */
159 Date toDate(in ubyte[] a) pure
160 {
161     enforceEx!MYXProtocol(a.length, "Supplied byte array is zero length");
162     if (a[0] == 0)
163         return Date(0,0,0);
164 
165     enforceEx!MYXProtocol(a[0] >= 4, "Binary date representation is too short");
166     int year    = (a[2]  << 8) + a[1];
167     int month   = cast(int) a[3];
168     int day     = cast(int) a[4];
169     return Date(year, month, day);
170 }
171 
172 /**
173  * Function to extract a Date from a text encoded column value.
174  *
175  * Text representations of a Date are as in 2011-11-11
176  *
177  * Params: s = A string representation of the time difference.
178  * Returns: A populated or default initialized std.datetime.Date struct.
179  */
180 Date toDate(string s)
181 {
182     int year = parse!(ushort)(s);
183     munch(s, "-");
184     int month = parse!(ubyte)(s);
185     munch(s, "-");
186     int day = parse!(ubyte)(s);
187     return Date(year, month, day);
188 }
189 
190 /**
191  * Function to pack a Date into a binary encoding for transmission to the server.
192  *
193  * Time/date structures are packed into a string of bytes with a leading length
194  * byte, and a minimal number of bytes to embody the data.
195  *
196  * Params: dt = std.datetime.Date struct.
197  * Returns: Packed ubyte[].
198  */
199 ubyte[] pack(in Date dt) pure nothrow
200 {
201     ubyte[] rv;
202     if (dt.year < 0)
203     {
204         rv.length = 1;
205         rv[0] = 0;
206     }
207     else
208     {
209         rv.length = 5;
210         rv[0] = 4;
211         rv[1] = cast(ubyte) ( dt.year       & 0xff);
212         rv[2] = cast(ubyte) ((dt.year >> 8) & 0xff);
213         rv[3] = cast(ubyte)   dt.month;
214         rv[4] = cast(ubyte)   dt.day;
215     }
216     return rv;
217 }
218 
219 /**
220  * Function to extract a DateTime from a binary encoded row.
221  *
222  * Time/date structures are packed by the server into a byte sub-packet
223  * with a leading length byte, and a minimal number of bytes to embody the data.
224  *
225  * Params: a = slice of a protocol packet beginning at the length byte for a
226  *             chunk of DateTime data
227  * Returns: A populated or default initialized std.datetime.DateTime struct.
228  */
229 DateTime toDateTime(in ubyte[] a) pure
230 {
231     enforceEx!MYXProtocol(a.length, "Supplied byte array is zero length");
232     if (a[0] == 0)
233         return DateTime();
234 
235     enforceEx!MYXProtocol(a[0] >= 4, "Supplied ubyte[] is not long enough");
236     int year    = (a[2] << 8) + a[1];
237     int month   =  a[3];
238     int day     =  a[4];
239     DateTime dt;
240     if (a[0] == 4)
241     {
242         dt = DateTime(year, month, day);
243     }
244     else
245     {
246         enforceEx!MYXProtocol(a[0] >= 7, "Supplied ubyte[] is not long enough");
247         int hour    = a[5];
248         int minute  = a[6];
249         int second  = a[7];
250         dt = DateTime(year, month, day, hour, minute, second);
251     }
252     return dt;
253 }
254 
255 /**
256  * Function to extract a DateTime from a text encoded column value.
257  *
258  * Text representations of a DateTime are as in 2011-11-11 12:20:02
259  *
260  * Params: s = A string representation of the time difference.
261  * Returns: A populated or default initialized std.datetime.DateTime struct.
262  */
263 DateTime toDateTime(string s)
264 {
265     int year = parse!(ushort)(s);
266     munch(s, "-");
267     int month = parse!(ubyte)(s);
268     munch(s, "-");
269     int day = parse!(ubyte)(s);
270     munch(s, " ");
271     int hour = parse!(ubyte)(s);
272     munch(s, ":");
273     int minute = parse!(ubyte)(s);
274     munch(s, ":");
275     int second = parse!(ubyte)(s);
276     return DateTime(year, month, day, hour, minute, second);
277 }
278 
279 /**
280  * Function to extract a DateTime from a ulong.
281  *
282  * This is used to support the TimeStamp  struct.
283  *
284  * Params: x = A ulong e.g. 20111111122002UL.
285  * Returns: A populated std.datetime.DateTime struct.
286  */
287 DateTime toDateTime(ulong x) pure
288 {
289     int second = cast(int) x%100;
290     x /= 100;
291     int minute = cast(int) x%100;
292     x /= 100;
293     int hour   = cast(int) x%100;
294     x /= 100;
295     int day    = cast(int) x%100;
296     x /= 100;
297     int month  = cast(int) x%100;
298     x /= 100;
299     int year   = cast(int) x%10000;
300     // 2038-01-19 03:14:07
301     enforceEx!MYXProtocol(year >= 1970 &&  year < 2039, "Date/time out of range for 2 bit timestamp");
302     enforceEx!MYXProtocol(year == 2038 && (month > 1 || day > 19 || hour > 3 || minute > 14 || second > 7),
303             "Date/time out of range for 2 bit timestamp");
304     return DateTime(year, month, day, hour, minute, second);
305 }
306 
307 /**
308  * Function to pack a DateTime into a binary encoding for transmission to the server.
309  *
310  * Time/date structures are packed into a string of bytes with a leading length byte,
311  * and a minimal number of bytes to embody the data.
312  *
313  * Params: dt = std.datetime.DateTime struct.
314  * Returns: Packed ubyte[].
315  */
316 ubyte[] pack(in DateTime dt) pure nothrow
317 {
318     uint len = 1;
319     if (dt.year || dt.month || dt.day) len = 5;
320     if (dt.hour || dt.minute|| dt.second) len = 8;
321     ubyte[] rv;
322     rv.length = len;
323     rv[0] =  cast(ubyte)(rv.length - 1); // num bytes
324     if(len >= 5)
325     {
326         rv[1] = cast(ubyte) ( dt.year       & 0xff);
327         rv[2] = cast(ubyte) ((dt.year >> 8) & 0xff);
328         rv[3] = cast(ubyte)   dt.month;
329         rv[4] = cast(ubyte)   dt.day;
330     }
331     if(len == 8)
332     {
333         rv[5] = cast(ubyte) dt.hour;
334         rv[6] = cast(ubyte) dt.minute;
335         rv[7] = cast(ubyte) dt.second;
336     }
337     return rv;
338 }
339 
340 
341 T consume(T)(MySQLSocket conn) pure {
342     ubyte[T.sizeof] buffer;
343     conn.read(buffer);
344     ubyte[] rng = buffer;
345     return consume!T(rng);
346 }
347 
348 string consume(T:string, ubyte N=T.sizeof)(ref ubyte[] packet) pure
349 {
350     return packet.consume!string(N);
351 }
352 
353 string consume(T:string)(ref ubyte[] packet, size_t N) pure
354 in
355 {
356     assert(packet.length >= N);
357 }
358 body
359 {
360     return cast(string)packet.consume(N);
361 }
362 
363 /// Returns N number of bytes from the packet and advances the array
364 ubyte[] consume()(ref ubyte[] packet, size_t N) pure nothrow
365 in
366 {
367     assert(packet.length >= N);
368 }
369 body
370 {
371     auto result = packet[0..N];
372     packet = packet[N..$];
373     return result;
374 }
375 
376 T decode(T:ulong)(in ubyte[] packet, size_t n) pure nothrow
377 {
378     switch(n)
379     {
380         case 8: return packet.decode!(T, 8)();
381         case 4: return packet.decode!(T, 4)();
382         case 3: return packet.decode!(T, 3)();
383         case 2: return packet.decode!(T, 2)();
384         case 1: return packet.decode!(T, 1)();
385         default: assert(0);
386     }
387 }
388 
389 T consume(T)(ref ubyte[] packet, int n) pure nothrow
390 if(isIntegral!T)
391 {
392     switch(n)
393     {
394         case 8: return packet.consume!(T, 8)();
395         case 4: return packet.consume!(T, 4)();
396         case 3: return packet.consume!(T, 3)();
397         case 2: return packet.consume!(T, 2)();
398         case 1: return packet.consume!(T, 1)();
399         default: assert(0);
400     }
401 }
402 
403 TimeOfDay consume(T:TimeOfDay, ubyte N=T.sizeof)(ref ubyte[] packet) pure
404 in
405 {
406     static assert(N == T.sizeof);
407 }
408 body
409 {
410     enforceEx!MYXProtocol(packet.length, "Supplied byte array is zero length");
411     uint length = packet.front;
412     enforceEx!MYXProtocol(length == 0 || length == 5 || length == 8 || length == 12, "Bad Time length in binary row.");
413     enforceEx!MYXProtocol(length >= 8, "Time column value is not in a time-of-day format");
414 
415     packet.popFront(); // length
416     auto bytes = packet.consume(length);
417 
418     // TODO: What are the fields in between!?! Blank Date?
419     TimeOfDay tod;
420     tod.hour    = bytes[5];
421     tod.minute  = bytes[6];
422     tod.second  = bytes[7];
423     return tod;
424 }
425 
426 Date consume(T:Date, ubyte N=T.sizeof)(ref ubyte[] packet) pure
427 in
428 {
429     static assert(N == T.sizeof);
430 }
431 body
432 {
433     return toDate(packet.consume(5));
434 }
435 
436 DateTime consume(T:DateTime, ubyte N=T.sizeof)(ref ubyte[] packet) pure
437 in
438 {
439     assert(packet.length);
440     assert(N == T.sizeof);
441 }
442 body
443 {
444     auto numBytes = packet.consume!ubyte();
445     if(numBytes == 0)
446         return DateTime();
447 
448     enforceEx!MYXProtocol(numBytes >= 4, "Supplied packet is not large enough to store DateTime");
449 
450     int year    = packet.consume!ushort();
451     int month   = packet.consume!ubyte();
452     int day     = packet.consume!ubyte();
453     int hour    = 0;
454     int minute  = 0;
455     int second  = 0;
456     if(numBytes > 4)
457     {
458         enforceEx!MYXProtocol(numBytes >= 7, "Supplied packet is not large enough to store a DateTime with TimeOfDay");
459         hour    = packet.consume!ubyte();
460         minute  = packet.consume!ubyte();
461         second  = packet.consume!ubyte();
462     }
463     return DateTime(year, month, day, hour, minute, second);
464 }
465 
466 
467 @property bool hasEnoughBytes(T, ubyte N=T.sizeof)(in ubyte[] packet) pure
468 in
469 {
470     static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes");
471 }
472 body
473 {
474     return packet.length >= N;
475 }
476 
477 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow
478 if(isIntegral!T)
479 in
480 {
481     static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot decode integral value. Invalid size: "~N.stringof);
482     static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes");
483     assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof);
484 }
485 body
486 {
487     T value = 0;
488     static if(N == 8) // 64 bit
489     {
490         value |= cast(T)(packet[7]) << (8*7);
491         value |= cast(T)(packet[6]) << (8*6);
492         value |= cast(T)(packet[5]) << (8*5);
493         value |= cast(T)(packet[4]) << (8*4);
494     }
495     static if(N >= 4) // 32 bit
496     {
497         value |= cast(T)(packet[3]) << (8*3);
498     }
499     static if(N >= 3) // 24 bit
500     {
501         value |= cast(T)(packet[2]) << (8*2);
502     }
503     static if(N >= 2) // 16 bit
504     {
505         value |= cast(T)(packet[1]) << (8*1);
506     }
507     static if(N >= 1) // 8 bit
508     {
509         value |= cast(T)(packet[0]) << (8*0);
510     }
511     return value;
512 }
513 
514 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow
515 if(isIntegral!T)
516 in
517 {
518     static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot consume integral value. Invalid size: "~N.stringof);
519     static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes");
520     assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof);
521 }
522 body
523 {
524     // The uncommented line triggers a template deduction error,
525     // so we need to store a temporary first
526     // could the problem be method chaining?
527     //return packet.consume(N).decode!(T, N)();
528     auto bytes = packet.consume(N);
529     return bytes.decode!(T, N)();
530 }
531 
532 
533 T myto(T)(string value)
534 {
535     static if(is(T == DateTime))
536         return toDateTime(value);
537     else static if(is(T == Date))
538         return toDate(value);
539     else static if(is(T == TimeOfDay))
540         return toTimeOfDay(value);
541     else
542         return to!T(value);
543 }
544 
545 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow
546 if(isFloatingPoint!T)
547 in
548 {
549     static assert((is(T == float) && N == float.sizeof)
550             || is(T == double) && N == double.sizeof);
551 }
552 body
553 {
554     T result = 0;
555     (cast(ubyte*)&result)[0..T.sizeof] = packet[0..T.sizeof];
556     return result;
557 }
558 
559 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow
560 if(isFloatingPoint!T)
561 in
562 {
563     static assert((is(T == float) && N == float.sizeof)
564             || is(T == double) && N == double.sizeof);
565 }
566 body
567 {
568     return packet.consume(T.sizeof).decode!T();
569 }
570 
571 
572 SQLValue consumeBinaryValueIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool unsigned)
573 {
574     SQLValue result;
575     
576     // Length of DateTime packet is NOT DateTime.sizeof, it can be 1, 5 or 8 bytes
577     static if(is(T==DateTime))
578         result.isIncomplete = packet.length < 1;
579     else
580         result.isIncomplete = packet.length < N;
581 
582     // isNull should have been handled by the caller as the binary format uses a
583     // null bitmap, and we don't have access to that information at this point
584     assert(!result.isNull);
585     if(!result.isIncomplete)
586     {
587         // only integral types is unsigned
588         static if(isIntegral!T)
589         {
590             if(unsigned)
591                 result.value = packet.consume!(Unsigned!T)();
592             else
593                 result.value = packet.consume!(Signed!T)();
594         }
595         else
596         {
597             // TODO: DateTime values etc might be incomplete!
598             result.value = packet.consume!(T, N)();
599         }
600     }
601     return result;
602 }
603 
604 SQLValue consumeNonBinaryValueIfComplete(T)(ref ubyte[] packet, bool unsigned)
605 {
606     SQLValue result;
607     auto lcb = packet.decode!LCB();
608     result.isIncomplete = lcb.isIncomplete || packet.length < (lcb.value+lcb.totalBytes);
609     result.isNull = lcb.isNull;
610     if(!result.isIncomplete)
611     {
612         // The packet has all the data we need, so we'll remove the LCB
613         // and convert the data
614         packet.skip(lcb.totalBytes);
615         assert(packet.length >= lcb.value);
616         auto value = cast(string) packet.consume(cast(size_t)lcb.value);
617 
618         if(!result.isNull)
619         {
620             assert(!result.isIncomplete);
621             assert(!result.isNull);
622             static if(isIntegral!T)
623             {
624                 if(unsigned)
625                     result.value = to!(Unsigned!T)(value);
626                 else
627                     result.value = to!(Signed!T)(value);
628             }
629             else
630             {
631                 static if(isArray!T)
632                 {
633                     // to!() crashes when trying to convert empty strings
634                     // to arrays, so we have this hack to just store any
635                     // empty array in those cases
636                     if(!value.length)
637                         result.value = T.init;
638                     else
639                         result.value = cast(T)value.dup;
640 
641                 }
642                 else
643                 {
644                     // TODO: DateTime values etc might be incomplete!
645                     result.value = myto!T(value);
646                 }
647             }
648         }
649     }
650     return result;
651 }
652 
653 SQLValue consumeIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool binary, bool unsigned)
654 {
655     return binary
656         ? packet.consumeBinaryValueIfComplete!(T, N)(unsigned)
657         : packet.consumeNonBinaryValueIfComplete!T(unsigned);
658 }
659 
660 SQLValue consumeIfComplete()(ref ubyte[] packet, SQLType sqlType, bool binary, bool unsigned, ushort charSet)
661 {
662     switch(sqlType)
663     {
664         default: assert(false, "Unsupported SQL type "~to!string(sqlType));
665         case SQLType.NULL:
666             SQLValue result;
667             result.isIncomplete = false;
668             result.isNull = true;
669             return result;
670         case SQLType.TINY:
671             return packet.consumeIfComplete!byte(binary, unsigned);
672         case SQLType.SHORT:
673             return packet.consumeIfComplete!short(binary, unsigned);
674         case SQLType.INT24:
675             return packet.consumeIfComplete!(int, 3)(binary, unsigned);
676         case SQLType.INT:
677             return packet.consumeIfComplete!int(binary, unsigned);
678         case SQLType.LONGLONG:
679             return packet.consumeIfComplete!long(binary, unsigned);
680         case SQLType.FLOAT:
681             return packet.consumeIfComplete!float(binary, unsigned);
682         case SQLType.DOUBLE:
683         case SQLType.NEWDECIMAL:
684             return packet.consumeIfComplete!double(binary, unsigned);
685         case SQLType.TIMESTAMP:
686             return packet.consumeIfComplete!DateTime(binary, unsigned);
687         case SQLType.TIME:
688             return packet.consumeIfComplete!TimeOfDay(binary, unsigned);
689         case SQLType.YEAR:
690             return packet.consumeIfComplete!ushort(binary, unsigned);
691         case SQLType.DATE:
692             return packet.consumeIfComplete!Date(binary, unsigned);
693         case SQLType.DATETIME:
694             return packet.consumeIfComplete!DateTime(binary, unsigned);
695         case SQLType.VARCHAR:
696         case SQLType.ENUM:
697         case SQLType.SET:
698         case SQLType.VARSTRING:
699         case SQLType.STRING:
700             return packet.consumeIfComplete!string(false, unsigned);
701         case SQLType.TINYBLOB:
702         case SQLType.MEDIUMBLOB:
703         case SQLType.BLOB:
704         case SQLType.LONGBLOB:
705         case SQLType.BIT:  // Yes, BIT. See note below.
706 
707             // TODO: This line should work. Why doesn't it?
708             //return packet.consumeIfComplete!(ubyte[])(binary, unsigned);
709 
710             auto lcb = packet.consumeIfComplete!LCB();
711             assert(!lcb.isIncomplete);
712             SQLValue result;
713             result.isIncomplete = false;
714             result.isNull = lcb.isNull;
715             if(result.isNull)
716             {
717                 // TODO: consumeIfComplete!LCB should be adjusted to do
718                 //       this itself, but not until I'm certain that nothing
719                 //       is reliant on the current behavior.
720                 packet.popFront(); // LCB length
721             }
722             else
723             {
724                 auto data = packet.consume(cast(size_t)lcb.value);
725                 if(charSet == 0x3F) // CharacterSet == binary
726                     result.value = data; // BLOB-ish
727                 else
728                     result.value = cast(string)data; // TEXT-ish
729             }
730             
731             // Type BIT is treated as a length coded binary (like a BLOB or VARCHAR),
732             // not like an integral type. So convert the binary data to a bool.
733             // See: http://dev.mysql.com/doc/internals/en/binary-protocol-value.html
734             if(sqlType == SQLType.BIT)
735             {
736                 enforceEx!MYXProtocol(result.value.length == 1,
737                     "Expected BIT to arrive as an LCB with length 1, but got length "~to!string(result.value.length));
738                 
739                 result.value = result.value[0] == 1;
740             }
741             
742             return result;
743     }
744 }
745 
746 /**
747  * Extract number of bytes used for this LCB
748  *
749  * Returns the number of bytes required to store this LCB
750  *
751  * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements
752  *
753  * Returns: 0 if it's a null value, or number of bytes in other cases
754  * */
755 byte getNumLCBBytes(in ubyte lcbHeader) pure nothrow
756 {
757     switch(lcbHeader)
758     {
759         case 251: return 0; // null
760         case 0: .. case 250: return 1; // 8-bit
761         case 252: return 2;  // 16-bit
762         case 253: return 3;  // 24-bit
763         case 254: return 8;  // 64-bit
764 
765         case 255:
766         default:
767             assert(0);
768     }
769     assert(0);
770 }
771 
772 
773 /**
774  * Decodes a Length Coded Binary from a packet
775  *
776  * See_Also: struct LCB
777  *
778  * Parameters:
779  *  packet = A packet that starts with a LCB. The bytes is popped off
780  *           iff the packet is complete. See LCB.
781  *
782  * Returns: A decoded LCB value
783  * */
784 T consumeIfComplete(T:LCB)(ref ubyte[] packet) pure nothrow
785 in
786 {
787     assert(packet.length >= 1, "packet has to include at least the LCB length byte");
788 }
789 body
790 {
791     auto lcb = packet.decodeLCBHeader();
792     if(lcb.isNull || lcb.isIncomplete)
793         return lcb;
794 
795     if(lcb.numBytes > 1)
796     {
797         // We know it's complete, so we have to start consuming the LCB
798         // Single byte values doesn't have a length
799         packet.popFront(); // LCB length
800     }
801 
802     assert(packet.length >= lcb.numBytes);
803 
804     lcb.value = packet.consume!ulong(lcb.numBytes);
805     return lcb;
806 }
807 
808 LCB decodeLCBHeader(in ubyte[] packet) pure nothrow
809 in
810 {
811     assert(packet.length >= 1, "packet has to include at least the LCB length byte");
812 }
813 body
814 {
815     LCB lcb;
816     lcb.numBytes = getNumLCBBytes(packet.front);
817     if(lcb.numBytes == 0)
818     {
819         lcb.isNull = true;
820         return lcb;
821     }
822 
823     assert(!lcb.isNull);
824     // -1 for LCB length as we haven't popped it off yet
825     lcb.isIncomplete = (lcb.numBytes > 1) && (packet.length-1 < lcb.numBytes);
826     if(lcb.isIncomplete)
827     {
828         // Not enough bytes to store data. We don't remove any data, and expect
829         // the caller to check isIncomplete and take action to fetch more data
830         // and call this method at a later time
831         return lcb;
832     }
833 
834     assert(!lcb.isIncomplete);
835     return lcb;
836 }
837 
838 /**
839  * Decodes a Length Coded Binary from a packet
840  *
841  * See_Also: struct LCB
842  *
843  * Parameters:
844  *  packet = A packet that starts with a LCB. See LCB.
845  *
846  * Returns: A decoded LCB value
847  * */
848 LCB decode(T:LCB)(in ubyte[] packet) pure nothrow
849 in
850 {
851     assert(packet.length >= 1, "packet has to include at least the LCB length byte");
852 }
853 body
854 {
855     auto lcb = packet.decodeLCBHeader();
856     if(lcb.isNull || lcb.isIncomplete)
857         return lcb;
858     assert(packet.length >= lcb.totalBytes);
859 
860     if(lcb.numBytes == 0)
861         lcb.value = 0;
862     else if(lcb.numBytes == 1)
863         lcb.value = packet.decode!ulong(lcb.numBytes);
864     else
865     {
866         // Skip the throwaway byte that indicated "at least 2 more bytes coming"
867         lcb.value = packet[1..$].decode!ulong(lcb.numBytes);
868     }
869 
870     return lcb;
871 }
872 
873 /** Parse Length Coded String
874  *
875  * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements
876  * */
877 string consume(T:LCS)(ref ubyte[] packet) pure
878 in
879 {
880     assert(packet.length >= 1, "LCS packet needs to store at least the LCB header");
881 }
882 body
883 {
884     auto lcb = packet.consumeIfComplete!LCB();
885     assert(!lcb.isIncomplete);
886     if(lcb.isNull)
887         return null;
888     enforceEx!MYXProtocol(lcb.value <= uint.max, "Protocol Length Coded String is too long");
889     return cast(string)packet.consume(cast(size_t)lcb.value).idup;
890 }
891 
892 /**
893  * Skips over n items, advances the array, and return the newly advanced array
894  * to allow method chaining.
895  * */
896 T[] skip(T)(ref T[] array, size_t n) pure nothrow
897 in
898 {
899     assert(n <= array.length);
900 }
901 body
902 {
903     array = array[n..$];
904     return array;
905 }
906 
907 /**
908  * Converts a value into a sequence of bytes and fills the supplied array
909  *
910  * Parameters:
911  * IsInt24 = If only the most significant 3 bytes from the value should be used
912  * value = The value to add to array
913  * array = The array we should add the values for. It has to be large enough,
914  *         and the values are packed starting index 0
915  */
916 void packInto(T, bool IsInt24 = false)(T value, ubyte[] array) pure nothrow
917 in
918 {
919     static if(IsInt24)
920         assert(array.length >= 3);
921     else
922         assert(array.length >= T.sizeof, "Not enough space to unpack "~T.stringof);
923 }
924 body
925 {
926     static if(T.sizeof >= 1)
927     {
928         array[0] = cast(ubyte) (value >> 8*0) & 0xff;
929     }
930     static if(T.sizeof >= 2)
931     {
932         array[1] = cast(ubyte) (value >> 8*1) & 0xff;
933     }
934     static if(!IsInt24)
935     {
936         static if(T.sizeof >= 4)
937         {
938             array[2] = cast(ubyte) (value >> 8*2) & 0xff;
939             array[3] = cast(ubyte) (value >> 8*3) & 0xff;
940         }
941         static if(T.sizeof >= 8)
942         {
943             array[4] = cast(ubyte) (value >> 8*4) & 0xff;
944             array[5] = cast(ubyte) (value >> 8*5) & 0xff;
945             array[6] = cast(ubyte) (value >> 8*6) & 0xff;
946             array[7] = cast(ubyte) (value >> 8*7) & 0xff;
947         }
948     }
949     else
950     {
951         array[2] = cast(ubyte) (value >> 8*2) & 0xff;
952     }
953 }
954 
955 ubyte[] packLength(size_t l, out size_t offset) pure nothrow
956 out(result)
957 {
958     assert(result.length >= 1);
959 }
960 body
961 {
962     ubyte[] t;
963     if (!l)
964     {
965         t.length = 1;
966         t[0] = 0;
967     }
968     else if (l <= 250)
969     {
970         t.length = 1+l;
971         t[0] = cast(ubyte) l;
972         offset = 1;
973     }
974     else if (l <= 0xffff) // 16-bit
975     {
976         t.length = 3+l;
977         t[0] = 252;
978         packInto(cast(ushort)l, t[1..3]);
979         offset = 3;
980     }
981     else if (l < 0xffffff) // 24-bit
982     {
983         t.length = 4+l;
984         t[0] = 253;
985         packInto!(uint, true)(cast(uint)l, t[1..4]);
986         offset = 4;
987     }
988     else // 64-bit
989     {
990         ulong u = cast(ulong) l;
991         t.length = 9+l;
992         t[0] = 254;
993         u.packInto(t[1..9]);
994         offset = 9;
995     }
996     return t;
997 }
998 
999 ubyte[] packLCS(void[] a) pure nothrow
1000 {
1001     size_t offset;
1002     ubyte[] t = packLength(a.length, offset);
1003     if (t[0])
1004         t[offset..$] = (cast(ubyte[]) a)[0..$];
1005     return t;
1006 }
1007 
1008 
1009 debug(MYSQL_INTEGRATION_TESTS)
1010 unittest
1011 {
1012     static void testLCB(string parseLCBFunc)(bool shouldConsume)
1013     {
1014         ubyte[] buf = [ 0xde, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x01, 0x00 ];
1015         ubyte[] bufCopy;
1016         
1017         bufCopy = buf;
1018         LCB lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1019         assert(lcb.value == 0xde && !lcb.isNull && lcb.totalBytes == 1);
1020         assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1021 
1022         buf[0] = 251;
1023         bufCopy = buf;
1024         lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1025         assert(lcb.value == 0 && lcb.isNull && lcb.totalBytes == 1);
1026         //TODO: This test seems to fail for consumeIfComplete, need to investigate.
1027         //      Don't know if fixing it might cause a problem, or if I simple misunderstood
1028         //      the function's intent.
1029         if(parseLCBFunc != "consumeIfComplete")
1030             assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1031 
1032         buf[0] = 252;
1033         bufCopy = buf;
1034         lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1035         assert(lcb.value == 0xbbcc && !lcb.isNull && lcb.totalBytes == 3);
1036         assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1037 
1038         buf[0] = 253;
1039         bufCopy = buf;
1040         lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1041         assert(lcb.value == 0xaabbcc && !lcb.isNull && lcb.totalBytes == 4);
1042         assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1043 
1044         buf[0] = 254;
1045         bufCopy = buf;
1046         lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1047         assert(lcb.value == 0x5566778899aabbcc && !lcb.isNull && lcb.totalBytes == 9);
1048         assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1049     }
1050     
1051     //TODO: Merge 'consumeIfComplete(T:LCB)' and 'decode(T:LCB)', they do
1052     //      basically the same thing, only one consumes input and the other
1053     //      doesn't. Just want a better idea of where/how/why they're both
1054     //      used, and maybe more tests, before I go messing with them.
1055     testLCB!"consumeIfComplete"(true);
1056     testLCB!"decode"(false);
1057 }
1058 
1059 debug(MYSQL_INTEGRATION_TESTS)
1060 unittest
1061 {
1062     ubyte[] buf;
1063     ubyte[] bufCopy;
1064 
1065     buf.length = 0x2000200;
1066     buf[] = '\x01';
1067     buf[0] = 250;
1068     buf[1] = '<';
1069     buf[249] = '!';
1070     buf[250] = '>';
1071     bufCopy = buf;
1072     string x = consume!LCS(bufCopy);
1073     assert(x.length == 250 && x[0] == '<' && x[249] == '>');
1074 
1075     buf[] = '\x01';
1076     buf[0] = 252;
1077     buf[1] = 0xff;
1078     buf[2] = 0xff;
1079     buf[3] = '<';
1080     buf[0x10000] = '*';
1081     buf[0x10001] = '>';
1082     bufCopy = buf;
1083     x = consume!LCS(bufCopy);
1084     assert(x.length == 0xffff && x[0] == '<' && x[0xfffe] == '>');
1085 
1086     buf[] = '\x01';
1087     buf[0] = 253;
1088     buf[1] = 0xff;
1089     buf[2] = 0xff;
1090     buf[3] = 0xff;
1091     buf[4] = '<';
1092     buf[0x1000001] = '*';
1093     buf[0x1000002] = '>';
1094     bufCopy = buf;
1095     x = consume!LCS(bufCopy);
1096     assert(x.length == 0xffffff && x[0] == '<' && x[0xfffffe] == '>');
1097 
1098     buf[] = '\x01';
1099     buf[0] = 254;
1100     buf[1] = 0xff;
1101     buf[2] = 0x00;
1102     buf[3] = 0x00;
1103     buf[4] = 0x02;
1104     buf[5] = 0x00;
1105     buf[6] = 0x00;
1106     buf[7] = 0x00;
1107     buf[8] = 0x00;
1108     buf[9] = '<';
1109     buf[0x2000106] = '!';
1110     buf[0x2000107] = '>';
1111     bufCopy = buf;
1112     x = consume!LCS(bufCopy);
1113     assert(x.length == 0x20000ff && x[0] == '<' && x[0x20000fe] == '>');
1114 }
1115 
1116 /// Set packet length and number. It's important that the length of packet has
1117 /// already been set to the final state as its length is used
1118 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber) pure nothrow
1119 in
1120 {
1121     // packet should include header, and possibly data
1122     assert(packet.length >= 4);
1123 }
1124 body
1125 {
1126     auto dataLength = packet.length - 4; // don't include header in calculated size
1127     assert(dataLength <= uint.max);
1128     packet.setPacketHeader(packetNumber, cast(uint)dataLength);
1129 }
1130 
1131 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber, uint dataLength) pure nothrow
1132 in
1133 {
1134     // packet should include header
1135     assert(packet.length >= 4);
1136     // Length is always a 24-bit int
1137     assert(dataLength <= 0xffff_ffff_ffff);
1138 }
1139 body
1140 {
1141     dataLength.packInto!(uint, true)(packet);
1142     packet[3] = packetNumber;
1143 }