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