1 module mysql.type;
2 
3 
4 import std.algorithm;
5 import std.datetime;
6 import std.traits;
7 
8 import mysql.protocol;
9 import mysql.packet;
10 import mysql.exception;
11 public import mysql.row;
12 
13 
14 struct MySQLRawString {
15 	@disable this();
16 
17 	this(const(char)[] data) {
18 		data_ = data;
19 	}
20 
21 	@property auto length() const {
22 		return data_.length;
23 	}
24 
25 	@property auto data() const {
26 		return data_;
27 	}
28 
29 	private const(char)[] data_;
30 }
31 
32 
33 struct MySQLBinary {
34 	this(T)(T[] data) {
35 		data_ = (cast(ubyte*)data.ptr)[0..typeof(T[].init[0]).sizeof * data.length];
36 	}
37 
38 	@property auto length() const {
39 		return data_.length;
40 	}
41 
42 	@property auto data() const {
43 		return data_;
44 	}
45 
46 	private const(ubyte)[] data_;
47 }
48 
49 
50 struct MySQLValue {
51 	package enum BufferSize = max(ulong.sizeof, (ulong[]).sizeof, MySQLDateTime.sizeof, MySQLTime.sizeof);
52 	package this(ColumnTypes type, bool signed, void* ptr, size_t size) {
53 		assert(size <= BufferSize);
54 		type_ = type;
55 		sign_ = signed ? 0x00 : 0x80;
56 		if (type != ColumnTypes.MYSQL_TYPE_NULL)
57 			buffer_[0..size] = (cast(ubyte*)ptr)[0..size];
58 	}
59 
60 	this(T)(T) if (is(Unqual!T == typeof(null))) {
61 		type_ = ColumnTypes.MYSQL_TYPE_NULL;
62 		sign_ = 0x00;
63 	}
64 
65 	this(T)(T value) if (isIntegral!T || isBoolean!T) {
66 		alias UT = Unqual!T;
67 
68 		static if (is(UT == long) || is(UT == ulong)) {
69 			type_ = ColumnTypes.MYSQL_TYPE_LONGLONG;
70 		} else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) {
71 			type_ = ColumnTypes.MYSQL_TYPE_LONG;
72 		} else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) {
73 			type_ = ColumnTypes.MYSQL_TYPE_SHORT;
74 		} else {
75 			type_ = ColumnTypes.MYSQL_TYPE_TINY;
76 		}
77 
78 		sign_ = isUnsigned!UT ? 0x80 : 0x00;
79 		buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
80 	}
81 
82 	this(T)(T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime)) {
83 		type_ = ColumnTypes.MYSQL_TYPE_TIMESTAMP;
84 		sign_ = 0x00;
85 		(*cast(MySQLDateTime*)buffer_.ptr).from(value);
86 	}
87 
88 	this(T)(T value) if (is(Unqual!T == Duration)) {
89 		type_ = ColumnTypes.MYSQL_TYPE_TIME;
90 		sign_ = 0x00;
91 		(*cast(MySQLTime*)buffer_.ptr).from(value);
92 	}
93 
94 	this(T)(T value) if (isSomeString!T) {
95 		static assert(typeof(T.init[0]).sizeof == 1, "Unsupported string type: " ~ T);
96 
97 		type_ = ColumnTypes.MYSQL_TYPE_STRING;
98 		sign_ = 0x80;
99 
100 		auto slice = value[0..$];
101 		buffer_.ptr[0..typeof(slice).sizeof] = (cast(ubyte*)&slice)[0..typeof(slice).sizeof];
102 	}
103 
104 	this(T)(T value) if (is(Unqual!T == MySQLBinary)) {
105 		type_ = ColumnTypes.MYSQL_TYPE_BLOB;
106 		sign_ = 0x80;
107 		buffer_.ptr[0..(ubyte[]).sizeof] = (cast(ubyte*)&value.data_)[0..(ubyte[]).sizeof];
108 	}
109 
110 	void toString(Appender)(ref Appender app) const {
111 		import std.format : formattedWrite;
112 
113 		final switch(type_) with (ColumnTypes) {
114 			case MYSQL_TYPE_NULL:
115 				break;
116 			case MYSQL_TYPE_TINY:
117 				formattedWrite(&app, "%d", *cast(ubyte*)buffer_.ptr);
118 				break;
119 			case MYSQL_TYPE_YEAR:
120 			case MYSQL_TYPE_SHORT:
121 				formattedWrite(&app, "%d", *cast(ushort*)buffer_.ptr);
122 				break;
123 			case MYSQL_TYPE_INT24:
124 			case MYSQL_TYPE_LONG:
125 				formattedWrite(&app, "%d", *cast(uint*)buffer_.ptr);
126 				break;
127 			case MYSQL_TYPE_LONGLONG:
128 				formattedWrite(&app, "%d", *cast(ulong*)buffer_.ptr);
129 				break;
130 			case MYSQL_TYPE_FLOAT:
131 				formattedWrite(&app, "%g", *cast(float*)buffer_.ptr);
132 				break;
133 			case MYSQL_TYPE_DOUBLE:
134 				formattedWrite(&app, "%g", *cast(double*)buffer_.ptr);
135 				break;
136 			case MYSQL_TYPE_SET:
137 			case MYSQL_TYPE_ENUM:
138 			case MYSQL_TYPE_VARCHAR:
139 			case MYSQL_TYPE_VAR_STRING:
140 			case MYSQL_TYPE_STRING:
141 			case MYSQL_TYPE_NEWDECIMAL:
142 			case MYSQL_TYPE_DECIMAL:
143 			case MYSQL_TYPE_TINY_BLOB:
144 			case MYSQL_TYPE_MEDIUM_BLOB:
145 			case MYSQL_TYPE_LONG_BLOB:
146 			case MYSQL_TYPE_BLOB:
147 				app.put(*cast(string*)buffer_.ptr);
148 				break;
149 			case MYSQL_TYPE_BIT:
150 			case MYSQL_TYPE_GEOMETRY:
151 				formattedWrite(&app, "%s", *cast(ubyte[]*)buffer_.ptr);
152 				break;
153 			case MYSQL_TYPE_TIME:
154 			case MYSQL_TYPE_TIME2:
155 				formattedWrite(&app, "%s", (*cast(MySQLTime*)buffer_.ptr).toDuration());
156 				break;
157 			case MYSQL_TYPE_DATE:
158 			case MYSQL_TYPE_NEWDATE:
159 			case MYSQL_TYPE_DATETIME:
160 			case MYSQL_TYPE_DATETIME2:
161 			case MYSQL_TYPE_TIMESTAMP:
162 			case MYSQL_TYPE_TIMESTAMP2:
163 				formattedWrite(&app, "%s", (*cast(MySQLDateTime*)buffer_.ptr).to!DateTime());
164 				break;
165 		}
166 	}
167 
168 	string toString() const {
169 		import std.conv : to;
170 
171 		final switch(type_) with (ColumnTypes) {
172 			case MYSQL_TYPE_NULL:
173 				return null;
174 			case MYSQL_TYPE_TINY:
175 				return to!string(*cast(ubyte*)buffer_.ptr);
176 			case MYSQL_TYPE_YEAR:
177 			case MYSQL_TYPE_SHORT:
178 				return to!string(*cast(ushort*)buffer_.ptr);
179 			case MYSQL_TYPE_INT24:
180 			case MYSQL_TYPE_LONG:
181 				return to!string(*cast(uint*)buffer_.ptr);
182 			case MYSQL_TYPE_LONGLONG:
183 				return to!string(*cast(ulong*)buffer_.ptr);
184 			case MYSQL_TYPE_FLOAT:
185 				return to!string(*cast(float*)buffer_.ptr);
186 			case MYSQL_TYPE_DOUBLE:
187 				return to!string(*cast(double*)buffer_.ptr);
188 			case MYSQL_TYPE_SET:
189 			case MYSQL_TYPE_ENUM:
190 			case MYSQL_TYPE_VARCHAR:
191 			case MYSQL_TYPE_VAR_STRING:
192 			case MYSQL_TYPE_STRING:
193 			case MYSQL_TYPE_NEWDECIMAL:
194 			case MYSQL_TYPE_DECIMAL:
195 			case MYSQL_TYPE_TINY_BLOB:
196 			case MYSQL_TYPE_MEDIUM_BLOB:
197 			case MYSQL_TYPE_LONG_BLOB:
198 			case MYSQL_TYPE_BLOB:
199 				return (*cast(string*)buffer_.ptr).idup;
200 			case MYSQL_TYPE_BIT:
201 			case MYSQL_TYPE_GEOMETRY:
202 				return to!string(*cast(ubyte[]*)buffer_.ptr);
203 			case MYSQL_TYPE_TIME:
204 			case MYSQL_TYPE_TIME2:
205 				return (*cast(MySQLTime*)buffer_.ptr).toDuration().toString();
206 			case MYSQL_TYPE_DATE:
207 			case MYSQL_TYPE_NEWDATE:
208 			case MYSQL_TYPE_DATETIME:
209 			case MYSQL_TYPE_DATETIME2:
210 			case MYSQL_TYPE_TIMESTAMP:
211 			case MYSQL_TYPE_TIMESTAMP2:
212 				return (*cast(MySQLDateTime*)buffer_.ptr).to!DateTime().toString();
213 		}
214 	}
215 
216 	T get(T)() const if (isScalarType!T) {
217 		switch(type_) with (ColumnTypes) {
218 			case MYSQL_TYPE_NULL:
219 				throw new MySQLErrorException("Cannot convert NULL to scalar");
220 			case MYSQL_TYPE_TINY:
221 				return cast(T)(*cast(ubyte*)buffer_.ptr);
222 			case MYSQL_TYPE_YEAR:
223 			case MYSQL_TYPE_SHORT:
224 				return cast(T)(*cast(ushort*)buffer_.ptr);
225 			case MYSQL_TYPE_INT24:
226 			case MYSQL_TYPE_LONG:
227 				return cast(T)(*cast(uint*)buffer_.ptr);
228 			case MYSQL_TYPE_LONGLONG:
229 				return cast(T)(*cast(ulong*)buffer_.ptr);
230 			case MYSQL_TYPE_FLOAT:
231 				return cast(T)(*cast(float*)buffer_.ptr);
232 			case MYSQL_TYPE_DOUBLE:
233 				return cast(T)(*cast(double*)buffer_.ptr);
234 			default:
235 				throw new MySQLErrorException("Cannot convert MySQL value to scalar");
236 		}
237 	}
238 
239 	T get(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) ||  is(Unqual!T == Date) || is(Unqual!T == TimeOfDay)) {
240 		switch(type_) with (ColumnTypes) {
241 			case MYSQL_TYPE_NULL:
242 				throw new MySQLErrorException("Cannot convert NULL to timestamp");
243 			case MYSQL_TYPE_DATE:
244 			case MYSQL_TYPE_NEWDATE:
245 			case MYSQL_TYPE_DATETIME:
246 			case MYSQL_TYPE_DATETIME2:
247 			case MYSQL_TYPE_TIMESTAMP:
248 			case MYSQL_TYPE_TIMESTAMP2:
249 				return (*cast(MySQLDateTime*)buffer_.ptr).to!T;
250 			default:
251 				throw new MySQLErrorException("Cannot convert MySQL value to timestamp");
252 		}
253 	}
254 
255 	T get(T)() const if (is(Unqual!T == Duration)) {
256 		switch(type_) with (ColumnTypes) {
257 			case MYSQL_TYPE_NULL:
258 				throw new MySQLErrorException("Cannot convert NULL to time");
259 			case MYSQL_TYPE_TIME:
260 			case MYSQL_TYPE_TIME2:
261 				return (*cast(MySQLTime*)buffer_.ptr).toDuration;
262 			default:
263 				throw new MySQLErrorException("Cannot convert MySQL value to time");
264 		}
265 	}
266 
267 	T get(T)() const if (isArray!T) {
268 		switch(type_) with (ColumnTypes) {
269 			case MYSQL_TYPE_NULL:
270 				throw new MySQLErrorException("Cannot convert NULL to array");
271 			case MYSQL_TYPE_SET:
272 			case MYSQL_TYPE_ENUM:
273 			case MYSQL_TYPE_VARCHAR:
274 			case MYSQL_TYPE_VAR_STRING:
275 			case MYSQL_TYPE_STRING:
276 			case MYSQL_TYPE_NEWDECIMAL:
277 			case MYSQL_TYPE_DECIMAL:
278 				return (*cast(T*)buffer_.ptr).dup;
279 			case MYSQL_TYPE_BIT:
280 			case MYSQL_TYPE_TINY_BLOB:
281 			case MYSQL_TYPE_MEDIUM_BLOB:
282 			case MYSQL_TYPE_LONG_BLOB:
283 			case MYSQL_TYPE_BLOB:
284 			case MYSQL_TYPE_GEOMETRY:
285 				return (*cast(T*)buffer_.ptr).dup;
286 			default:
287 				throw new MySQLErrorException("Cannot convert MySQL value to array");
288 		}
289 	}
290 
291 	T peek(T)() const if (isScalarType!T) {
292 		return get!T;
293 	}
294 
295 	T peek(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) ||  is(Unqual!T == Date) || is(Unqual!T == TimeOfDay)) {
296 		return get!T;
297 	}
298 
299 	T peek(T)() const if (is(Unqual!T == Duration)) {
300 		return get!T;
301 	}
302 
303 	T peek(T)() const if (isArray!T) {
304 		switch(type_) with (ColumnTypes) {
305 			case MYSQL_TYPE_NULL:
306 				throw new MySQLErrorException("Cannot convert NULL to array");
307 			case MYSQL_TYPE_SET:
308 			case MYSQL_TYPE_ENUM:
309 			case MYSQL_TYPE_VARCHAR:
310 			case MYSQL_TYPE_VAR_STRING:
311 			case MYSQL_TYPE_STRING:
312 			case MYSQL_TYPE_NEWDECIMAL:
313 			case MYSQL_TYPE_DECIMAL:
314 				return (*cast(T*)buffer_.ptr);
315 			case MYSQL_TYPE_BIT:
316 			case MYSQL_TYPE_TINY_BLOB:
317 			case MYSQL_TYPE_MEDIUM_BLOB:
318 			case MYSQL_TYPE_LONG_BLOB:
319 			case MYSQL_TYPE_BLOB:
320 			case MYSQL_TYPE_GEOMETRY:
321 				return (*cast(T*)buffer_.ptr);
322 			default:
323 				throw new MySQLErrorException("Cannot convert MySQL value to array");
324 		}
325 	}
326 
327 	bool isNull() const {
328 		return type_ == ColumnTypes.MYSQL_TYPE_NULL;
329 	}
330 
331 	ColumnTypes type() const {
332 		return type_;
333 	}
334 
335 	bool isSigned() const {
336 		return sign_ == 0x00;
337 	}
338 
339 	package void nullify() {
340 		type_ = ColumnTypes.MYSQL_TYPE_NULL;
341 	}
342 
343 private:
344 	ColumnTypes type_ = ColumnTypes.MYSQL_TYPE_NULL;
345 	ubyte sign_;
346 	ubyte[6] pad_;
347 	ubyte[BufferSize] buffer_;
348 }
349 
350 
351 struct MySQLColumn {
352 	uint length;
353 	ushort flags;
354 	ubyte decimals;
355 	ColumnTypes type;
356 	const(char)[] name;
357 }
358 
359 
360 alias MySQLHeader = MySQLColumn[];
361 
362 
363 struct MySQLTime {
364 	uint days;
365 	ubyte negative;
366 	ubyte hours;
367 	ubyte mins;
368 	ubyte secs;
369 	uint usecs;
370 
371 	Duration toDuration() {
372 		auto total = days * 86400_000_000L +
373 			hours * 3600_000_000L +
374 			mins * 60_000_000L +
375 			secs * 1_000_000L +
376 			usecs;
377 		return dur!"usecs"(negative ? -total : total);
378 	}
379 
380 	static MySQLTime from(Duration duration) {
381 		MySQLTime time;
382 		duration.abs.split!("days", "hours", "minutes", "seconds", "usecs")(time.days, time.hours, time.mins, time.secs, time.usecs);
383 		time.negative = duration.isNegative ? 1 : 0;
384 		return time;
385 	}
386 }
387 
388 void putMySQLTime(ref OutputPacket packet, in MySQLTime time) {
389 	if (time.days || time.hours || time.mins || time.mins || time.usecs) {
390 		auto usecs = time.usecs != 0;
391 		packet.put!ubyte(usecs ? 12 : 8);
392 		packet.put!ubyte(time.negative);
393 		packet.put!uint(time.days);
394 		packet.put!ubyte(time.hours);
395 		packet.put!ubyte(time.mins);
396 		packet.put!ubyte(time.secs);
397 		if (usecs)
398 			packet.put!uint(time.usecs);
399 	} else {
400 		packet.put!ubyte(0);
401 	}
402 }
403 
404 auto eatMySQLTime(ref InputPacket packet) {
405 	MySQLTime time;
406 	switch(packet.eat!ubyte) {
407 		case 12:
408 			time.negative = packet.eat!ubyte;
409 			time.days = packet.eat!uint;
410 			time.hours = packet.eat!ubyte;
411 			time.mins = packet.eat!ubyte;
412 			time.secs = packet.eat!ubyte;
413 			time.usecs = packet.eat!uint;
414 			break;
415 		case 8:
416 			time.negative = packet.eat!ubyte;
417 			time.days = packet.eat!uint;
418 			time.hours = packet.eat!ubyte;
419 			time.mins = packet.eat!ubyte;
420 			time.secs = packet.eat!ubyte;
421 			break;
422 		case 0:
423 			break;
424 		default:
425 			throw new MySQLProtocolException("Bad time struct format");
426 	}
427 
428 	return time;
429 }
430 
431 
432 struct MySQLDateTime {
433 	ushort year = 0;
434 	ubyte month = 0;
435 	ubyte day = 0;
436 	ubyte hour = 0;
437 	ubyte min = 0;
438 	ubyte sec = 0;
439 	uint usec = 0;
440 
441 	bool valid() const {
442 		return month != 0;
443 	}
444 
445 	T to(T)() if (is(T == SysTime)) {
446 		return SysTime(DateTime(year, month, day, hour, min, sec), FracSec.from!"usecs"(usec), UTC());
447 	}
448 
449 	T to(T)() if (is(T == DateTime)) {
450 		return DateTime(year, month, day, hour, min, sec);
451 	}
452 
453 	T to(T)() if (is(T == Date)) {
454 		return Date(year, month, day);
455 	}
456 
457 	T to(T)() if (is(T == TimeOfDay)) {
458 		return TimeOfDay(hour, min, sec);
459 	}
460 
461 	static MySQLDateTime from(SysTime sysTime) {
462 		MySQLDateTime time;
463 
464 		auto dateTime = cast(DateTime)sysTime;
465 		time.year = dateTime.year;
466 		time.month = dateTime.month;
467 		time.day = dateTime.day;
468 		time.hour = dateTime.hour;
469 		time.min = dateTime.minute;
470 		time.sec = dateTime.second;
471 		time.usec = sysTime.fracSec.usecs;
472 
473 		return time;
474 	}
475 
476 	static MySQLDateTime from(DateTime dateTime) {
477 		MySQLDateTime time;
478 
479 		time.year = dateTime.year;
480 		time.month = dateTime.month;
481 		time.day = dateTime.day;
482 		time.hour = dateTime.hour;
483 		time.min = dateTime.minute;
484 		time.sec = dateTime.second;
485 
486 		return time;
487 	}
488 
489 	static MySQLDateTime from(Date date) {
490 		MySQLDateTime time;
491 
492 		time.year = date.year;
493 		time.month = date.month;
494 		time.day = date.day;
495 
496 		return time;
497 	}
498 }
499 
500 void putMySQLDateTime(ref OutputPacket packet, in MySQLDateTime time) {
501 	auto marker = packet.marker!ubyte;
502 	ubyte length = 0;
503 
504 	if (time.year || time.month || time.day) {
505 		length = 4;
506 		packet.put!ushort(time.year);
507 		packet.put!ubyte(time.month);
508 		packet.put!ubyte(time.day);
509 
510 		if (time.hour || time.min || time.sec || time.usec) {
511 			length = 7;
512 			packet.put!ubyte(time.hour);
513 			packet.put!ubyte(time.min);
514 			packet.put!ubyte(time.sec);
515 
516 			if (time.usec) {
517 				length = 11;
518 				packet.put!uint(time.usec);
519 			}
520 		}
521 	}
522 
523 	packet.put!ubyte(marker, length);
524 }
525 
526 auto eatMySQLDateTime(ref InputPacket packet) {
527 	MySQLDateTime time;
528 	switch(packet.eat!ubyte) {
529 		case 11:
530 			time.year = packet.eat!ushort;
531 			time.month = packet.eat!ubyte;
532 			time.day = packet.eat!ubyte;
533 			time.hour = packet.eat!ubyte;
534 			time.min = packet.eat!ubyte;
535 			time.sec = packet.eat!ubyte;
536 			time.usec = packet.eat!uint;
537 			break;
538 		case 7:
539 			time.year = packet.eat!ushort;
540 			time.month = packet.eat!ubyte;
541 			time.day = packet.eat!ubyte;
542 			time.hour = packet.eat!ubyte;
543 			time.min = packet.eat!ubyte;
544 			time.sec = packet.eat!ubyte;
545 			break;
546 		case 4:
547 			time.year = packet.eat!ushort;
548 			time.month = packet.eat!ubyte;
549 			time.day = packet.eat!ubyte;
550 			break;
551 		case 0:
552 			break;
553 		default:
554 			throw new MySQLProtocolException("Bad datetime struct format");
555 	}
556 
557 	return time;
558 }
559 
560 
561 MySQLValue eatValue(ref InputPacket packet, ref const MySQLColumn column) {
562 	MySQLValue value;
563 
564 	// todo: avoid unnecessary copying packet->stack->value - copy directly packet->value
565 	auto signed = (column.flags & FieldFlags.UNSIGNED_FLAG) == 0;
566 	final switch(column.type) with (ColumnTypes) {
567 		case MYSQL_TYPE_NULL:
568 			value = MySQLValue(column.type, signed, null, 0);
569 			break;
570 		case MYSQL_TYPE_TINY:
571 			auto x = packet.eat!ubyte;
572 			value = MySQLValue(column.type, signed, &x, 1);
573 			break;
574 		case MYSQL_TYPE_YEAR:
575 		case MYSQL_TYPE_SHORT:
576 			auto x = packet.eat!ushort;
577 			value = MySQLValue(column.type, signed, &x, 2);
578 			break;
579 		case MYSQL_TYPE_INT24:
580 		case MYSQL_TYPE_LONG:
581 			auto x = packet.eat!uint;
582 			value = MySQLValue(column.type, signed, &x, 4);
583 			break;
584 		case MYSQL_TYPE_DOUBLE:
585 		case MYSQL_TYPE_LONGLONG:
586 			auto x = packet.eat!ulong;
587 			value = MySQLValue(column.type, signed, &x, 8);
588 			break;
589 		case MYSQL_TYPE_FLOAT:
590 			auto x = packet.eat!float;
591 			value = MySQLValue(column.type, signed, &x, 4);
592 			break;
593 		case MYSQL_TYPE_SET:
594 		case MYSQL_TYPE_ENUM:
595 		case MYSQL_TYPE_VARCHAR:
596 		case MYSQL_TYPE_VAR_STRING:
597 		case MYSQL_TYPE_STRING:
598 		case MYSQL_TYPE_NEWDECIMAL:
599 		case MYSQL_TYPE_DECIMAL:
600 			auto x = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc());
601 			value = MySQLValue(column.type, signed, &x, typeof(x).sizeof);
602 			break;
603 		case MYSQL_TYPE_BIT:
604 		case MYSQL_TYPE_TINY_BLOB:
605 		case MYSQL_TYPE_MEDIUM_BLOB:
606 		case MYSQL_TYPE_LONG_BLOB:
607 		case MYSQL_TYPE_BLOB:
608 		case MYSQL_TYPE_GEOMETRY:
609 			auto x = packet.eat!(const(ubyte)[])(cast(size_t)packet.eatLenEnc());
610 			value = MySQLValue(column.type, signed, &x, typeof(x).sizeof);
611 			break;
612 		case MYSQL_TYPE_TIME:
613 		case MYSQL_TYPE_TIME2:
614 			auto x = eatMySQLTime(packet);
615 			value = MySQLValue(column.type, signed, &x, typeof(x).sizeof);
616 			break;
617 		case MYSQL_TYPE_DATE:
618 		case MYSQL_TYPE_NEWDATE:
619 		case MYSQL_TYPE_DATETIME:
620 		case MYSQL_TYPE_DATETIME2:
621 		case MYSQL_TYPE_TIMESTAMP:
622 		case MYSQL_TYPE_TIMESTAMP2:
623 			auto x = eatMySQLDateTime(packet);
624 			if (x.valid())
625 				value = MySQLValue(column.type, signed, &x, typeof(x).sizeof);
626 			else
627 				value = MySQLValue(ColumnTypes.MYSQL_TYPE_NULL, signed, null, 0);
628 			break;
629 	}
630 
631 	return value;
632 }
633 
634 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime)) {
635 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIMESTAMP);
636 	packet.put!ubyte(0x80);
637 }
638 
639 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime)) {
640 	putMySQLDateTime(packet, MySQLDateTime.from(value));
641 }
642 
643 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Duration)) {
644 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIME);
645 	packet.put!ubyte(0x00);
646 }
647 
648 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Duration)) {
649 	putMySQLTime(packet, MySQLTime.from(value));
650 }
651 
652 void putValueType(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T) {
653 	alias UT = Unqual!T;
654 
655 	enum ubyte sign = isUnsigned!UT ? 0x80 : 0x00;
656 
657 	static if (is(UT == long) || is(UT == ulong)) {
658 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONGLONG);
659 		packet.put!ubyte(sign);
660 	} else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) {
661 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONG);
662 		packet.put!ubyte(sign);
663 	} else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) {
664 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_SHORT);
665 		packet.put!ubyte(sign);
666 	} else {
667 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TINY);
668 		packet.put!ubyte(sign);
669 	}
670 }
671 
672 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == typeof(null))) {
673 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_NULL);
674 	packet.put!ubyte(0x00);
675 }
676 
677 void putValue(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T) {
678 	alias UT = Unqual!T;
679 
680 	static if (is(UT == long) || is(UT == ulong)) {
681 		packet.put!ulong(value);
682 	} else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) {
683 		packet.put!uint(value);
684 	} else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) {
685 		packet.put!ushort(value);
686 	} else {
687 		packet.put!ubyte(value);
688 	}
689 }
690 
691 void putValueType(T)(ref OutputPacket packet, T value) if (isSomeString!T) {
692 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_STRING);
693 	packet.put!ubyte(0x80);
694 }
695 
696 void putValue(T)(ref OutputPacket packet, T value) if (isSomeString!T) {
697 	ulong size = value.length * ValueType.sizeof;
698 	packet.putLenEnc(size);
699 	packet.put(value);
700 }
701 
702 void putValueType(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!T) {
703 	foreach(ref item; value)
704 		putValueType(packet, item);
705 }
706 
707 void putValue(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!T) {
708 	foreach(ref item; value)
709 		putValue(packet, item);
710 }
711 
712 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLBinary)) {
713 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_BLOB);
714 	packet.put!ubyte(0x80);
715 }
716 
717 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLBinary)) {
718 	ulong size = value.length;
719 	packet.putLenEnc(size);
720 	packet.put(value.data);
721 }
722 
723 void putValueType(T)(ref OutputPacket packet, T value) if(is(Unqual!T == MySQLValue)) {
724 	packet.put!ubyte(value.type_);
725 	packet.put!ubyte(value.sign_);
726 }
727 
728 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLValue)) {
729 	final switch(value.type) with (ColumnTypes) {
730 		case MYSQL_TYPE_NULL:
731 			break;
732 		case MYSQL_TYPE_TINY:
733 			packet.put!ubyte(*cast(ubyte*)value.buffer_.ptr);
734 			break;
735 		case MYSQL_TYPE_YEAR:
736 		case MYSQL_TYPE_SHORT:
737 			packet.put!ushort(*cast(ushort*)value.buffer_.ptr);
738 			break;
739 		case MYSQL_TYPE_INT24:
740 		case MYSQL_TYPE_LONG:
741 			packet.put!uint(*cast(uint*)value.buffer_.ptr);
742 			break;
743 		case MYSQL_TYPE_LONGLONG:
744 			packet.put!ulong(*cast(ulong*)value.buffer_.ptr);
745 			break;
746 		case MYSQL_TYPE_DOUBLE:
747 			packet.put!double(*cast(double*)value.buffer_.ptr);
748 			break;
749 		case MYSQL_TYPE_FLOAT:
750 			packet.put!double(*cast(float*)value.buffer_.ptr);
751 			break;
752 		case MYSQL_TYPE_SET:
753 		case MYSQL_TYPE_ENUM:
754 		case MYSQL_TYPE_VARCHAR:
755 		case MYSQL_TYPE_VAR_STRING:
756 		case MYSQL_TYPE_STRING:
757 		case MYSQL_TYPE_NEWDECIMAL:
758 		case MYSQL_TYPE_DECIMAL:
759 		case MYSQL_TYPE_BIT:
760 		case MYSQL_TYPE_TINY_BLOB:
761 		case MYSQL_TYPE_MEDIUM_BLOB:
762 		case MYSQL_TYPE_LONG_BLOB:
763 		case MYSQL_TYPE_BLOB:
764 		case MYSQL_TYPE_GEOMETRY:
765 			packet.putLenEnc((*cast(ubyte[]*)value.buffer_.ptr).length);
766 			packet.put(*cast(ubyte[]*)value.buffer_.ptr);
767 			break;
768 		case MYSQL_TYPE_TIME:
769 		case MYSQL_TYPE_TIME2:
770 			packet.putMySQLTime(*cast(MySQLTime*)value.buffer_.ptr);
771 			break;
772 		case MYSQL_TYPE_DATE:
773 		case MYSQL_TYPE_NEWDATE:
774 		case MYSQL_TYPE_DATETIME:
775 		case MYSQL_TYPE_DATETIME2:
776 		case MYSQL_TYPE_TIMESTAMP:
777 		case MYSQL_TYPE_TIMESTAMP2:
778 			packet.putMySQLDateTime(*cast(MySQLDateTime*)value.buffer_.ptr);
779 			break;
780 	}
781 }