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