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 	enforce!MYXProtocol(a.length, "Supplied byte array is zero length");
31 	TimeDiff td;
32 	uint l = a[0];
33 	enforce!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 	enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`);
70 	td.minutes  = parse!ubyte(s);
71 	enforce!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 	enforce!MYXProtocol(a.length, "Supplied byte array is zero length");
89 	uint l = a[0];
90 	enforce!MYXProtocol(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row.");
91 	enforce!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 	enforce!MYXProtocol(tod.hour <= 24 && tod.hour >= 0, "Time column value is in time difference form");
113 	enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`);
114 	tod.minute = parse!ubyte(s);
115 	enforce!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 MySQLDate toDate(in ubyte[] a) pure
159 {
160 	enforce!MYXProtocol(a.length, "Supplied byte array is zero length");
161 	if (a[0] == 0)
162 		return MySQLDate(0,0,0);
163 
164 	enforce!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 MySQLDate(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 MySQLDate toDate(string s)
180 {
181 	int year = parse!(ushort)(s);
182 	enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`);
183 	int month = parse!(ubyte)(s);
184 	enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`);
185 	int day = parse!(ubyte)(s);
186 	return MySQLDate(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 MySQLDateTime toDateTime(in ubyte[] a) pure
229 {
230 	enforce!MYXProtocol(a.length, "Supplied byte array is zero length");
231 	if (a[0] == 0)
232 		return MySQLDateTime();  //TODO: Is this right??
233 
234 	enforce!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 	MySQLDateTime dt;
239 	if (a[0] == 4)
240 	{
241 		dt = MySQLDateTime(year, month, day);
242 	}
243 	else
244 	{
245 		enforce!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 = MySQLDateTime(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 MySQLDateTime toDateTime(string s)
263 {
264 	int year = parse!(ushort)(s);
265 	enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`);
266 	int month = parse!(ubyte)(s);
267 	enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`);
268 	int day = parse!(ubyte)(s);
269 	enforce!MYXProtocol(s.skipOver(" "), `Expected: " "`);
270 	int hour = parse!(ubyte)(s);
271 	enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`);
272 	int minute = parse!(ubyte)(s);
273 	enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`);
274 	int second = parse!(ubyte)(s);
275 	return MySQLDateTime(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 MySQLDateTime 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 	enforce!MYXProtocol(year >= 1970 &&  year < 2039, "Date/time out of range for 2 bit timestamp");
301 	enforce!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 MySQLDateTime(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 	enforce!MYXProtocol(packet.length, "Supplied byte array is zero length");
410 	uint length = packet.front;
411 	enforce!MYXProtocol(length == 0 || length == 5 || length == 8 || length == 12, "Bad Time length in binary row.");
412 	enforce!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 MySQLDate consume(T:MySQLDate, 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 MySQLDateTime consume(T:MySQLDateTime, 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 MySQLDateTime();  //TODO: Is this right??
446 
447 	enforce!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 		enforce!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 MySQLDateTime(year, month, day, hour, minute, second);
463 }
464 
465 /+
466 // Make sure the dummy functions below actually do get chosen by the compiler
467 // and correctly halt compilation.
468 void foo()
469 {
470 	ubyte[] pak;
471 	auto a = consume!Date(pak);
472 	auto b = consume!DateTime(pak);
473 	auto c = consumeIfComplete!Date(pak, false, false);
474 	auto d = consumeIfComplete!DateTime(pak, false, false);
475 	
476 }+/
477 // Make sure consume doesn't get called with Date/DateTime,
478 // it should only get called with MySQLDate/MySQLDateTime
479 Date consume(T:Date, ubyte N=T.sizeof)(ref ubyte[] packet) pure
480 {
481 	static assert(false, "consume!Date");
482 }
483 DateTime consume(T:DateTime, ubyte N=T.sizeof)(ref ubyte[] packet) pure
484 {
485 	static assert(false, "consume!DateTime");
486 }
487 
488 @property bool hasEnoughBytes(T, ubyte N=T.sizeof)(in ubyte[] packet) pure
489 in
490 {
491 	static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes");
492 }
493 body
494 {
495 	return packet.length >= N;
496 }
497 
498 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow
499 if(isIntegral!T)
500 in
501 {
502 	static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot decode integral value. Invalid size: "~N.stringof);
503 	static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes");
504 	assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof);
505 }
506 body
507 {
508 	T value = 0;
509 	static if(N == 8) // 64 bit
510 	{
511 		value |= cast(T)(packet[7]) << (8*7);
512 		value |= cast(T)(packet[6]) << (8*6);
513 		value |= cast(T)(packet[5]) << (8*5);
514 		value |= cast(T)(packet[4]) << (8*4);
515 	}
516 	static if(N >= 4) // 32 bit
517 	{
518 		value |= cast(T)(packet[3]) << (8*3);
519 	}
520 	static if(N >= 3) // 24 bit
521 	{
522 		value |= cast(T)(packet[2]) << (8*2);
523 	}
524 	static if(N >= 2) // 16 bit
525 	{
526 		value |= cast(T)(packet[1]) << (8*1);
527 	}
528 	static if(N >= 1) // 8 bit
529 	{
530 		value |= cast(T)(packet[0]) << (8*0);
531 	}
532 	return value;
533 }
534 
535 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow
536 if(isIntegral!T)
537 in
538 {
539 	static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot consume integral value. Invalid size: "~N.stringof);
540 	static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes");
541 	assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof);
542 }
543 body
544 {
545 	// The uncommented line triggers a template deduction error,
546 	// so we need to store a temporary first
547 	// could the problem be method chaining?
548 	//return packet.consume(N).decode!(T, N)();
549 	auto bytes = packet.consume(N);
550 	return bytes.decode!(T, N)();
551 }
552 
553 
554 T myto(T)(string value)
555 {
556 	static if(is(T == MySQLDateTime))
557 		return toDateTime(value);
558 	else static if(is(T == MySQLDate))
559 		return toDate(value);
560 	else static if(is(T == TimeOfDay))
561 		return toTimeOfDay(value);
562 	// Make sure myto doesn't get called with Date/DateTime,
563 	// it should only get called with MySQLDate/MySQLDateTime
564 	else static if(is(T == Date))
565 		static assert(false, "myto!Date");
566 	else static if(is(T == DateTime))
567 		static assert(false, "myto!DateTime");
568 	else
569 		return to!T(value);
570 }
571 
572 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow
573 if(isFloatingPoint!T)
574 in
575 {
576 	static assert((is(T == float) && N == float.sizeof)
577 			|| is(T == double) && N == double.sizeof);
578 }
579 body
580 {
581 	T result = 0;
582 	(cast(ubyte*)&result)[0..T.sizeof] = packet[0..T.sizeof];
583 	return result;
584 }
585 
586 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow
587 if(isFloatingPoint!T)
588 in
589 {
590 	static assert((is(T == float) && N == float.sizeof)
591 			|| is(T == double) && N == double.sizeof);
592 }
593 body
594 {
595 	return packet.consume(T.sizeof).decode!T();
596 }
597 
598 
599 SQLValue consumeBinaryValueIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool unsigned)
600 {
601 	SQLValue result;
602 
603 	// Length of DateTime packet is NOT DateTime.sizeof, it can be 1, 5 or 8 bytes
604 	static if(is(T==DateTime))
605 		result.isIncomplete = packet.length < 1;
606 	else
607 		result.isIncomplete = packet.length < N;
608 
609 	// isNull should have been handled by the caller as the binary format uses a
610 	// null bitmap, and we don't have access to that information at this point
611 	assert(!result.isNull);
612 	if(!result.isIncomplete)
613 	{
614 		// only integral types is unsigned
615 		static if(isIntegral!T)
616 		{
617 			if(unsigned)
618 				result.value = packet.consume!(Unsigned!T)();
619 			else
620 				result.value = packet.consume!(Signed!T)();
621 		}
622 		else
623 		{
624 			// TODO: DateTime values etc might be incomplete!
625 			result.value = packet.consume!(T, N)();
626 		}
627 	}
628 	return result;
629 }
630 
631 SQLValue consumeNonBinaryValueIfComplete(T)(ref ubyte[] packet, bool unsigned)
632 {
633 	SQLValue result;
634 	auto lcb = packet.decode!LCB();
635 	result.isIncomplete = lcb.isIncomplete || packet.length < (lcb.value+lcb.totalBytes);
636 	result.isNull = lcb.isNull;
637 	if(!result.isIncomplete)
638 	{
639 		// The packet has all the data we need, so we'll remove the LCB
640 		// and convert the data
641 		packet.skip(lcb.totalBytes);
642 		assert(packet.length >= lcb.value);
643 		auto value = cast(string) packet.consume(cast(size_t)lcb.value);
644 
645 		if(!result.isNull)
646 		{
647 			assert(!result.isIncomplete);
648 			assert(!result.isNull);
649 			static if(isIntegral!T)
650 			{
651 				if(unsigned)
652 					result.value = to!(Unsigned!T)(value);
653 				else
654 					result.value = to!(Signed!T)(value);
655 			}
656 			else
657 			{
658 				static if(isArray!T)
659 				{
660 					// to!() crashes when trying to convert empty strings
661 					// to arrays, so we have this hack to just store any
662 					// empty array in those cases
663 					if(!value.length)
664 						result.value = T.init;
665 					else
666 						result.value = cast(T)value.dup;
667 
668 				}
669 				else
670 				{
671 					// TODO: DateTime values etc might be incomplete!
672 					result.value = myto!T(value);
673 				}
674 			}
675 		}
676 	}
677 	return result;
678 }
679 
680 SQLValue consumeIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool binary, bool unsigned)
681 {
682 	return binary
683 		? packet.consumeBinaryValueIfComplete!(T, N)(unsigned)
684 		: packet.consumeNonBinaryValueIfComplete!T(unsigned);
685 }
686 
687 // Make sure consumeIfComplete doesn't get called with Date/DateTime,
688 // it should only get called with MySQLDate/MySQLDateTime
689 SQLValue consumeIfComplete(T)(ref ubyte[] packet, bool binary, bool unsigned)
690 	if(is(T==Date))
691 {
692 	static assert(false, "consumeIfComplete!Date");
693 }
694 SQLValue consumeIfComplete(T)(ref ubyte[] packet, bool binary, bool unsigned)
695 	if(is(T==DateTime))
696 {
697 	static assert(false, "consumeIfComplete!DateTime");
698 }
699 
700 SQLValue consumeIfComplete()(ref ubyte[] packet, SQLType sqlType, bool binary, bool unsigned, ushort charSet)
701 {
702 	switch(sqlType)
703 	{
704 		default: assert(false, "Unsupported SQL type "~to!string(sqlType));
705 		case SQLType.NULL:
706 			SQLValue result;
707 			result.isIncomplete = false;
708 			result.isNull = true;
709 			return result;
710 		case SQLType.TINY:
711 			return packet.consumeIfComplete!byte(binary, unsigned);
712 		case SQLType.SHORT:
713 			return packet.consumeIfComplete!short(binary, unsigned);
714 		case SQLType.INT24:
715 			return packet.consumeIfComplete!(int, 3)(binary, unsigned);
716 		case SQLType.INT:
717 			return packet.consumeIfComplete!int(binary, unsigned);
718 		case SQLType.LONGLONG:
719 			return packet.consumeIfComplete!long(binary, unsigned);
720 		case SQLType.FLOAT:
721 			return packet.consumeIfComplete!float(binary, unsigned);
722 		case SQLType.DOUBLE:
723 			return packet.consumeIfComplete!double(binary, unsigned);
724 		case SQLType.TIMESTAMP:
725 			return packet.consumeIfComplete!MySQLDateTime(binary, unsigned);
726 		case SQLType.TIME:
727 			return packet.consumeIfComplete!TimeOfDay(binary, unsigned);
728 		case SQLType.YEAR:
729 			return packet.consumeIfComplete!ushort(binary, unsigned);
730 		case SQLType.DATE:
731 			return packet.consumeIfComplete!MySQLDate(binary, unsigned);
732 		case SQLType.DATETIME:
733 			return packet.consumeIfComplete!MySQLDateTime(binary, unsigned);
734 		case SQLType.VARCHAR:
735 		case SQLType.ENUM:
736 		case SQLType.SET:
737 		case SQLType.VARSTRING:
738 		case SQLType.STRING:
739 		case SQLType.NEWDECIMAL:
740 			return packet.consumeIfComplete!string(false, unsigned);
741 		case SQLType.TINYBLOB:
742 		case SQLType.MEDIUMBLOB:
743 		case SQLType.BLOB:
744 		case SQLType.LONGBLOB:
745 		case SQLType.BIT:  // Yes, BIT. See note below.
746 
747 			// TODO: This line should work. Why doesn't it?
748 			//return packet.consumeIfComplete!(ubyte[])(binary, unsigned);
749 
750 			auto lcb = packet.consumeIfComplete!LCB();
751 			assert(!lcb.isIncomplete);
752 			SQLValue result;
753 			result.isIncomplete = false;
754 			result.isNull = lcb.isNull;
755 			if(result.isNull)
756 			{
757 				// TODO: consumeIfComplete!LCB should be adjusted to do
758 				//       this itself, but not until I'm certain that nothing
759 				//       is reliant on the current behavior.
760 				packet.popFront(); // LCB length
761 			}
762 			else
763 			{
764 				auto data = packet.consume(cast(size_t)lcb.value);
765 				if(charSet == 0x3F) // CharacterSet == binary
766 					result.value = data; // BLOB-ish
767 				else
768 					result.value = cast(string)data; // TEXT-ish
769 			}
770 
771 			// Type BIT is treated as a length coded binary (like a BLOB or VARCHAR),
772 			// not like an integral type. So convert the binary data to a bool.
773 			// See: http://dev.mysql.com/doc/internals/en/binary-protocol-value.html
774 			if(sqlType == SQLType.BIT)
775 			{
776 				enforce!MYXProtocol(result.value.length == 1,
777 					"Expected BIT to arrive as an LCB with length 1, but got length "~to!string(result.value.length));
778 
779 				result.value = result.value[0] == 1;
780 			}
781 
782 			return result;
783 	}
784 }
785 
786 /++
787 Extract number of bytes used for this LCB
788 
789 Returns the number of bytes required to store this LCB
790 
791 See_Also: $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements)
792 
793 Returns: 0 if it's a null value, or number of bytes in other cases
794 +/
795 byte getNumLCBBytes(in ubyte lcbHeader) pure nothrow
796 {
797 	switch(lcbHeader)
798 	{
799 		case 251: return 0; // null
800 		case 0: .. case 250: return 1; // 8-bit
801 		case 252: return 2;  // 16-bit
802 		case 253: return 3;  // 24-bit
803 		case 254: return 8;  // 64-bit
804 
805 		case 255:
806 		default:
807 			assert(0);
808 	}
809 	assert(0);
810 }
811 
812 
813 /++
814 Decodes a Length Coded Binary from a packet
815 
816 See_Also: struct `mysql.protocol.extra_types.LCB`
817 
818 Params:
819 packet = A packet that starts with a LCB. The bytes is popped off
820          iff the packet is complete. See `mysql.protocol.extra_types.LCB`.
821 
822 Returns: A decoded LCB value
823 +/
824 T consumeIfComplete(T:LCB)(ref ubyte[] packet) pure nothrow
825 in
826 {
827 	assert(packet.length >= 1, "packet has to include at least the LCB length byte");
828 }
829 body
830 {
831 	auto lcb = packet.decodeLCBHeader();
832 	if(lcb.isNull || lcb.isIncomplete)
833 		return lcb;
834 
835 	if(lcb.numBytes > 1)
836 	{
837 		// We know it's complete, so we have to start consuming the LCB
838 		// Single byte values doesn't have a length
839 		packet.popFront(); // LCB length
840 	}
841 
842 	assert(packet.length >= lcb.numBytes);
843 
844 	lcb.value = packet.consume!ulong(lcb.numBytes);
845 	return lcb;
846 }
847 
848 LCB decodeLCBHeader(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 	LCB lcb;
856 	lcb.numBytes = getNumLCBBytes(packet.front);
857 	if(lcb.numBytes == 0)
858 	{
859 		lcb.isNull = true;
860 		return lcb;
861 	}
862 
863 	assert(!lcb.isNull);
864 	// -1 for LCB length as we haven't popped it off yet
865 	lcb.isIncomplete = (lcb.numBytes > 1) && (packet.length-1 < lcb.numBytes);
866 	if(lcb.isIncomplete)
867 	{
868 		// Not enough bytes to store data. We don't remove any data, and expect
869 		// the caller to check isIncomplete and take action to fetch more data
870 		// and call this method at a later time
871 		return lcb;
872 	}
873 
874 	assert(!lcb.isIncomplete);
875 	return lcb;
876 }
877 
878 /++
879 Decodes a Length Coded Binary from a packet
880 
881 See_Also: struct `mysql.protocol.extra_types.LCB`
882 
883 Params:
884 packet = A packet that starts with a LCB. See `mysql.protocol.extra_types.LCB`.
885 
886 Returns: A decoded LCB value
887 +/
888 LCB decode(T:LCB)(in ubyte[] packet) pure nothrow
889 in
890 {
891 	assert(packet.length >= 1, "packet has to include at least the LCB length byte");
892 }
893 body
894 {
895 	auto lcb = packet.decodeLCBHeader();
896 	if(lcb.isNull || lcb.isIncomplete)
897 		return lcb;
898 	assert(packet.length >= lcb.totalBytes);
899 
900 	if(lcb.numBytes == 0)
901 		lcb.value = 0;
902 	else if(lcb.numBytes == 1)
903 		lcb.value = packet.decode!ulong(lcb.numBytes);
904 	else
905 	{
906 		// Skip the throwaway byte that indicated "at least 2 more bytes coming"
907 		lcb.value = packet[1..$].decode!ulong(lcb.numBytes);
908 	}
909 
910 	return lcb;
911 }
912 
913 /++
914 Parse Length Coded String
915 
916 See_Also: $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements)
917 +/
918 string consume(T:LCS)(ref ubyte[] packet) pure
919 in
920 {
921 	assert(packet.length >= 1, "LCS packet needs to store at least the LCB header");
922 }
923 body
924 {
925 	auto lcb = packet.consumeIfComplete!LCB();
926 	assert(!lcb.isIncomplete);
927 	if(lcb.isNull)
928 		return null;
929 	enforce!MYXProtocol(lcb.value <= uint.max, "Protocol Length Coded String is too long");
930 	return cast(string)packet.consume(cast(size_t)lcb.value).idup;
931 }
932 
933 /++
934 Skips over n items, advances the array, and return the newly advanced array
935 to allow method chaining.
936 +/
937 T[] skip(T)(ref T[] array, size_t n) pure nothrow
938 in
939 {
940 	assert(n <= array.length);
941 }
942 body
943 {
944 	array = array[n..$];
945 	return array;
946 }
947 
948 /++
949 Converts a value into a sequence of bytes and fills the supplied array.
950 
951 Params:
952 IsInt24 = If only the most significant 3 bytes from the value should be used.
953 value = The value to add to array.
954 array = The array we should add the values for. It has to be large enough,
955         and the values are packed starting index 0.
956 +/
957 void packInto(T, bool IsInt24 = false)(T value, ubyte[] array) pure nothrow
958 in
959 {
960 	static if(IsInt24)
961 		assert(array.length >= 3);
962 	else
963 		assert(array.length >= T.sizeof, "Not enough space to unpack "~T.stringof);
964 }
965 body
966 {
967 	static if(T.sizeof >= 1)
968 	{
969 		array[0] = cast(ubyte) (value >> 8*0) & 0xff;
970 	}
971 	static if(T.sizeof >= 2)
972 	{
973 		array[1] = cast(ubyte) (value >> 8*1) & 0xff;
974 	}
975 	static if(!IsInt24)
976 	{
977 		static if(T.sizeof >= 4)
978 		{
979 			array[2] = cast(ubyte) (value >> 8*2) & 0xff;
980 			array[3] = cast(ubyte) (value >> 8*3) & 0xff;
981 		}
982 		static if(T.sizeof >= 8)
983 		{
984 			array[4] = cast(ubyte) (value >> 8*4) & 0xff;
985 			array[5] = cast(ubyte) (value >> 8*5) & 0xff;
986 			array[6] = cast(ubyte) (value >> 8*6) & 0xff;
987 			array[7] = cast(ubyte) (value >> 8*7) & 0xff;
988 		}
989 	}
990 	else
991 	{
992 		array[2] = cast(ubyte) (value >> 8*2) & 0xff;
993 	}
994 }
995 
996 ubyte[] packLength(size_t l, out size_t offset) pure nothrow
997 out(result)
998 {
999 	assert(result.length >= 1);
1000 }
1001 body
1002 {
1003 	ubyte[] t;
1004 	if (!l)
1005 	{
1006 		t.length = 1;
1007 		t[0] = 0;
1008 	}
1009 	else if (l <= 250)
1010 	{
1011 		t.length = 1+l;
1012 		t[0] = cast(ubyte) l;
1013 		offset = 1;
1014 	}
1015 	else if (l <= 0xffff) // 16-bit
1016 	{
1017 		t.length = 3+l;
1018 		t[0] = 252;
1019 		packInto(cast(ushort)l, t[1..3]);
1020 		offset = 3;
1021 	}
1022 	else if (l < 0xffffff) // 24-bit
1023 	{
1024 		t.length = 4+l;
1025 		t[0] = 253;
1026 		packInto!(uint, true)(cast(uint)l, t[1..4]);
1027 		offset = 4;
1028 	}
1029 	else // 64-bit
1030 	{
1031 		ulong u = cast(ulong) l;
1032 		t.length = 9+l;
1033 		t[0] = 254;
1034 		u.packInto(t[1..9]);
1035 		offset = 9;
1036 	}
1037 	return t;
1038 }
1039 
1040 ubyte[] packLCS(void[] a) pure nothrow
1041 {
1042 	size_t offset;
1043 	ubyte[] t = packLength(a.length, offset);
1044 	if (t[0])
1045 		t[offset..$] = (cast(ubyte[]) a)[0..$];
1046 	return t;
1047 }
1048 
1049 @("testLCB")
1050 debug(MYSQLN_TESTS)
1051 unittest
1052 {
1053 	static void testLCB(string parseLCBFunc)(bool shouldConsume)
1054 	{
1055 		ubyte[] buf = [ 0xde, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x01, 0x00 ];
1056 		ubyte[] bufCopy;
1057 
1058 		bufCopy = buf;
1059 		LCB lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1060 		assert(lcb.value == 0xde && !lcb.isNull && lcb.totalBytes == 1);
1061 		assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1062 
1063 		buf[0] = 251;
1064 		bufCopy = buf;
1065 		lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1066 		assert(lcb.value == 0 && lcb.isNull && lcb.totalBytes == 1);
1067 		//TODO: This test seems to fail for consumeIfComplete, need to investigate.
1068 		//      Don't know if fixing it might cause a problem, or if I simple misunderstood
1069 		//      the function's intent.
1070 		if(parseLCBFunc != "consumeIfComplete")
1071 			assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1072 
1073 		buf[0] = 252;
1074 		bufCopy = buf;
1075 		lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1076 		assert(lcb.value == 0xbbcc && !lcb.isNull && lcb.totalBytes == 3);
1077 		assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1078 
1079 		buf[0] = 253;
1080 		bufCopy = buf;
1081 		lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1082 		assert(lcb.value == 0xaabbcc && !lcb.isNull && lcb.totalBytes == 4);
1083 		assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1084 
1085 		buf[0] = 254;
1086 		bufCopy = buf;
1087 		lcb = mixin(parseLCBFunc~"!LCB(bufCopy)");
1088 		assert(lcb.value == 0x5566778899aabbcc && !lcb.isNull && lcb.totalBytes == 9);
1089 		assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0));
1090 	}
1091 
1092 	//TODO: Merge 'consumeIfComplete(T:LCB)' and 'decode(T:LCB)', they do
1093 	//      basically the same thing, only one consumes input and the other
1094 	//      doesn't. Just want a better idea of where/how/why they're both
1095 	//      used, and maybe more tests, before I go messing with them.
1096 	testLCB!"consumeIfComplete"(true);
1097 	testLCB!"decode"(false);
1098 }
1099 
1100 @("consume!LCS")
1101 debug(MYSQLN_TESTS)
1102 unittest
1103 {
1104 	ubyte[] buf;
1105 	ubyte[] bufCopy;
1106 
1107 	buf.length = 0x2000200;
1108 	buf[] = '\x01';
1109 	buf[0] = 250;
1110 	buf[1] = '<';
1111 	buf[249] = '!';
1112 	buf[250] = '>';
1113 	bufCopy = buf;
1114 	string x = consume!LCS(bufCopy);
1115 	assert(x.length == 250 && x[0] == '<' && x[249] == '>');
1116 
1117 	buf[] = '\x01';
1118 	buf[0] = 252;
1119 	buf[1] = 0xff;
1120 	buf[2] = 0xff;
1121 	buf[3] = '<';
1122 	buf[0x10000] = '*';
1123 	buf[0x10001] = '>';
1124 	bufCopy = buf;
1125 	x = consume!LCS(bufCopy);
1126 	assert(x.length == 0xffff && x[0] == '<' && x[0xfffe] == '>');
1127 
1128 	buf[] = '\x01';
1129 	buf[0] = 253;
1130 	buf[1] = 0xff;
1131 	buf[2] = 0xff;
1132 	buf[3] = 0xff;
1133 	buf[4] = '<';
1134 	buf[0x1000001] = '*';
1135 	buf[0x1000002] = '>';
1136 	bufCopy = buf;
1137 	x = consume!LCS(bufCopy);
1138 	assert(x.length == 0xffffff && x[0] == '<' && x[0xfffffe] == '>');
1139 
1140 	buf[] = '\x01';
1141 	buf[0] = 254;
1142 	buf[1] = 0xff;
1143 	buf[2] = 0x00;
1144 	buf[3] = 0x00;
1145 	buf[4] = 0x02;
1146 	buf[5] = 0x00;
1147 	buf[6] = 0x00;
1148 	buf[7] = 0x00;
1149 	buf[8] = 0x00;
1150 	buf[9] = '<';
1151 	buf[0x2000106] = '!';
1152 	buf[0x2000107] = '>';
1153 	bufCopy = buf;
1154 	x = consume!LCS(bufCopy);
1155 	assert(x.length == 0x20000ff && x[0] == '<' && x[0x20000fe] == '>');
1156 }
1157 
1158 /// Set packet length and number. It's important that the length of packet has
1159 /// already been set to the final state as its length is used.
1160 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber) pure nothrow
1161 in
1162 {
1163 	// packet should include header, and possibly data
1164 	assert(packet.length >= 4);
1165 }
1166 body
1167 {
1168 	auto dataLength = packet.length - 4; // don't include header in calculated size
1169 	assert(dataLength <= uint.max);
1170 	packet.setPacketHeader(packetNumber, cast(uint)dataLength);
1171 }
1172 
1173 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber, uint dataLength) pure nothrow
1174 in
1175 {
1176 	// packet should include header
1177 	assert(packet.length >= 4);
1178 	// Length is always a 24-bit int
1179 	assert(dataLength <= 0xffff_ffff_ffff);
1180 }
1181 body
1182 {
1183 	dataLength.packInto!(uint, true)(packet);
1184 	packet[3] = packetNumber;
1185 }