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 import std.typecons;
11 
12 import mysql.protocol;
13 import mysql.packet;
14 import mysql.exception;
15 public import mysql.row;
16 
17 struct IgnoreAttribute {}
18 struct OptionalAttribute {}
19 struct NameAttribute { const(char)[] name; }
20 struct UnCamelCaseAttribute {}
21 struct TableNameAttribute {const(char)[] name;}
22 
23 @property TableNameAttribute tableName(const(char)[] name) {
24 	return TableNameAttribute(name);
25 }
26 
27 @property IgnoreAttribute ignore() {
28 	return IgnoreAttribute();
29 }
30 
31 
32 @property OptionalAttribute optional() {
33 	return OptionalAttribute();
34 }
35 
36 
37 @property NameAttribute as(const(char)[] name)  {
38 	return NameAttribute(name);
39 }
40 
41 
42 @property UnCamelCaseAttribute uncamel() {
43 	return UnCamelCaseAttribute();
44 }
45 
46 
47 template Unnull(U) {
48 	alias impl(N : Nullable!T, T) = T;
49 	alias impl(T) = T;
50 	alias Unnull = impl!U;
51 }
52 
53 alias Unboth(T) = Unqual!(Unnull!T);
54 enum isSomeDuration(T) = is(Unboth!T == Date) || is(Unboth!T == DateTime) || is(Unboth!T == SysTime) || is(Unboth!T == Duration) || is(Unboth!T == TimeOfDay);
55 enum isValueType(T) = isSomeDuration!(Unboth!T) || is(Unboth!T == MySQLValue) || (!is(Unboth!T == struct) && !is(Unboth!T == class));
56 
57 template isWritableDataMember(T, string Member) {
58 	static if (is(TypeTuple!(__traits(getMember, T, Member)))) {
59 		enum isWritableDataMember = false;
60 	} else static if (!is(typeof(__traits(getMember, T, Member)))) {
61 		enum isWritableDataMember = false;
62 	} else static if (is(typeof(__traits(getMember, T, Member)) == void)) {
63 		enum isWritableDataMember = false;
64 	} else static if (is(typeof(__traits(getMember, T, Member)) == enum)) {
65 		enum isWritableDataMember = true;
66 	} else static if (hasUDA!(__traits(getMember, T, Member), IgnoreAttribute)) {
67 		enum isWritableDataMember = false;
68 	} else static if (isArray!(typeof(__traits(getMember, T, Member))) && !is(typeof(typeof(__traits(getMember, T, Member)).init[0]) == ubyte) && !is(typeof(__traits(getMember, T, Member)) == string)) {
69 		enum isWritableDataMember = false;
70 	} else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member)))) {
71 		enum isWritableDataMember = false;
72 	} else static if (isSomeFunction!(typeof(__traits(getMember, T, Member)))) {
73 		enum isWritableDataMember = false;
74 	} else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }()))) {
75 		enum isWritableDataMember = false;
76 	} else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export")) {
77 		enum isWritableDataMember = false;
78 	} else {
79 		enum isWritableDataMember = true;
80 	}
81 }
82 
83 template isReadableDataMember(T, string Member) {
84 	static if (is(TypeTuple!(__traits(getMember, T, Member)))) {
85 		enum isReadableDataMember = false;
86 	} else static if (!is(typeof(__traits(getMember, T, Member)))) {
87 		enum isReadableDataMember = false;
88 	} else static if (is(typeof(__traits(getMember, T, Member)) == void)) {
89 		enum isReadableDataMember = false;
90 	} else static if (is(typeof(__traits(getMember, T, Member)) == enum)) {
91 		enum isReadableDataMember = true;
92 	} else static if (hasUDA!(__traits(getMember, T, Member), IgnoreAttribute)) {
93 		enum isReadableDataMember = false;
94 	} else static if (isArray!(typeof(__traits(getMember, T, Member))) && !is(typeof(typeof(__traits(getMember, T, Member)).init[0]) == ubyte) && !is(typeof(__traits(getMember, T, Member)) == string)) {
95 		enum isReadableDataMember = false;
96 	} else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member)))) {
97 		enum isReadableDataMember = false;
98 	} else static if (isSomeFunction!(typeof(__traits(getMember, T, Member)))  /* && return type is valueType*/ ) {
99 		enum isReadableDataMember = true;
100 	} else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }()))) {
101 		enum isReadableDataMember = false;
102 	} else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export")) {
103 		enum isReadableDataMember = false;
104 	} else {
105 		enum isReadableDataMember = true;
106 	}
107 }
108 
109 struct MySQLRawString {
110 	@disable this();
111 
112 	this(const(char)[] data) {
113 		data_ = data;
114 	}
115 
116 	@property auto length() const {
117 		return data_.length;
118 	}
119 
120 	@property auto data() const {
121 		return data_;
122 	}
123 
124 	private const(char)[] data_;
125 }
126 
127 
128 
129 struct MySQLFragment {
130 	@disable this();
131 
132 	this(const(char)[] data) {
133 		data_ = data;
134 	}
135 
136 	@property auto length() const {
137 		return data_.length;
138 	}
139 
140 	@property auto data() const {
141 		return data_;
142 	}
143 
144 	private const(char)[] data_;
145 }
146 
147 
148 struct MySQLBinary {
149 	this(T)(T[] data) {
150 		data_ = (cast(ubyte*)data.ptr)[0..typeof(T[].init[0]).sizeof * data.length];
151 	}
152 
153 	@property auto length() const {
154 		return data_.length;
155 	}
156 
157 	@property auto data() const {
158 		return data_;
159 	}
160 
161 	private const(ubyte)[] data_;
162 }
163 
164 
165 struct MySQLValue {
166 	package enum BufferSize = max(ulong.sizeof, (ulong[]).sizeof, MySQLDateTime.sizeof, MySQLTime.sizeof);
167 	package this(const(char)[] name, ColumnTypes type, bool signed, void* ptr, size_t size) {
168 		assert(size <= BufferSize);
169 		type_ = type;
170 		sign_ = signed ? 0x00 : 0x80;
171 		if (type != ColumnTypes.MYSQL_TYPE_NULL)
172 			buffer_[0..size] = (cast(ubyte*)ptr)[0..size];
173 		name_ = name;
174 	}
175 
176 	this(T)(T) if (is(Unqual!T == typeof(null))) {
177 		type_ = ColumnTypes.MYSQL_TYPE_NULL;
178 		sign_ = 0x00;
179 	}
180 
181 	this(T)(T value) if (is(Unqual!T == MySQLValue)) {
182 		this = value;
183 	}
184 
185 	this(T)(T value) if (std.traits.isFloatingPoint!T) {
186 		alias UT = Unqual!T;
187 
188 		sign_ = 0x00;
189 		static if (is(UT == float)) {
190 			type_ = ColumnTypes.MYSQL_TYPE_FLOAT;
191 			buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
192 		} else static if (is(UT == double)) {
193 			type_ = ColumnTypes.MYSQL_TYPE_DOUBLE;
194 			buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
195 		} else {
196 			type_ = ColumnTypes.MYSQL_TYPE_DOUBLE;
197 			auto data = cast(double)value;
198 			buffer_[0..typeof(data).sizeof] = (cast(ubyte*)&data)[0..typeof(data).sizeof];
199 		}
200 	}
201 
202 	this(T)(T value) if (isIntegral!T || isBoolean!T) {
203 		alias UT = Unqual!T;
204 
205 		static if (is(UT == long) || is(UT == ulong)) {
206 			type_ = ColumnTypes.MYSQL_TYPE_LONGLONG;
207 		} else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) {
208 			type_ = ColumnTypes.MYSQL_TYPE_LONG;
209 		} else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) {
210 			type_ = ColumnTypes.MYSQL_TYPE_SHORT;
211 		} else {
212 			type_ = ColumnTypes.MYSQL_TYPE_TINY;
213 		}
214 
215 		sign_ = isUnsigned!UT ? 0x80 : 0x00;
216 		buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
217 	}
218 
219 	this(T)(T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime)) {
220 		type_ = ColumnTypes.MYSQL_TYPE_TIMESTAMP;
221 		sign_ = 0x00;
222 		(*cast(MySQLDateTime*)buffer_) = MySQLDateTime.from(value);
223 	}
224 
225 	this(T)(T value) if (is(Unqual!T == Duration) || is(Unqual!T == TimeOfDay)) {
226 		type_ = ColumnTypes.MYSQL_TYPE_TIME;
227 		sign_ = 0x00;
228 		(*cast(MySQLTime*)buffer_) = MySQLTime.from(value);
229 	}
230 
231 	this(T)(T value) if (isSomeString!(OriginalType!T)) {
232 		static assert(typeof(T.init[0]).sizeof == 1, format("Unsupported string type: %s", T.stringof));
233 
234 		type_ = ColumnTypes.MYSQL_TYPE_STRING;
235 		sign_ = 0x80;
236 
237 		auto slice = value[0..$];
238 		buffer_.ptr[0..typeof(slice).sizeof] = (cast(ubyte*)&slice)[0..typeof(slice).sizeof];
239 	}
240 
241 	this(T)(T value) if (is(Unqual!T == MySQLBinary)) {
242 		type_ = ColumnTypes.MYSQL_TYPE_BLOB;
243 		sign_ = 0x80;
244 		buffer_.ptr[0..(ubyte[]).sizeof] = (cast(ubyte*)&value.data_)[0..(ubyte[]).sizeof];
245 	}
246 
247 	void toString(Appender)(ref Appender app) const {
248 		final switch(type_) with (ColumnTypes) {
249 		case MYSQL_TYPE_NULL:
250 			break;
251 		case MYSQL_TYPE_TINY:
252 			if (isSigned) formattedWrite(&app, "%d", *cast(ubyte*)buffer_.ptr);
253 			else formattedWrite(&app, "%d", *cast(byte*)buffer_.ptr);
254 			break;
255 		case MYSQL_TYPE_YEAR:
256 		case MYSQL_TYPE_SHORT:
257 			if (isSigned) formattedWrite(&app, "%d", *cast(short*)buffer_.ptr);
258 			else formattedWrite(&app, "%d", *cast(ushort*)buffer_.ptr);
259 			break;
260 		case MYSQL_TYPE_INT24:
261 		case MYSQL_TYPE_LONG:
262 			if (isSigned) formattedWrite(&app, "%d", *cast(int*)buffer_.ptr);
263 			else formattedWrite(&app, "%d", *cast(uint*)buffer_.ptr);
264 			break;
265 		case MYSQL_TYPE_LONGLONG:
266 			if (isSigned) formattedWrite(&app, "%d", *cast(long*)buffer_.ptr);
267 			else formattedWrite(&app, "%d", *cast(ulong*)buffer_.ptr);
268 			break;
269 		case MYSQL_TYPE_FLOAT:
270 			formattedWrite(&app, "%g", *cast(float*)buffer_.ptr);
271 			break;
272 		case MYSQL_TYPE_DOUBLE:
273 			formattedWrite(&app, "%g", *cast(double*)buffer_.ptr);
274 			break;
275 		case MYSQL_TYPE_SET:
276 		case MYSQL_TYPE_ENUM:
277 		case MYSQL_TYPE_VARCHAR:
278 		case MYSQL_TYPE_VAR_STRING:
279 		case MYSQL_TYPE_STRING:
280 		case MYSQL_TYPE_JSON:
281 		case MYSQL_TYPE_NEWDECIMAL:
282 		case MYSQL_TYPE_DECIMAL:
283 		case MYSQL_TYPE_TINY_BLOB:
284 		case MYSQL_TYPE_MEDIUM_BLOB:
285 		case MYSQL_TYPE_LONG_BLOB:
286 		case MYSQL_TYPE_BLOB:
287 			app.put(*cast(string*)buffer_.ptr);
288 			break;
289 		case MYSQL_TYPE_BIT:
290 		case MYSQL_TYPE_GEOMETRY:
291 			formattedWrite(&app, "%s", *cast(ubyte[]*)buffer_.ptr);
292 			break;
293 		case MYSQL_TYPE_TIME:
294 		case MYSQL_TYPE_TIME2:
295 			formattedWrite(&app, "%s", (*cast(MySQLTime*)buffer_.ptr).to!Duration());
296 			break;
297 		case MYSQL_TYPE_DATE:
298 		case MYSQL_TYPE_NEWDATE:
299 		case MYSQL_TYPE_DATETIME:
300 		case MYSQL_TYPE_DATETIME2:
301 		case MYSQL_TYPE_TIMESTAMP:
302 		case MYSQL_TYPE_TIMESTAMP2:
303 			formattedWrite(&app, "%s", (*cast(MySQLDateTime*)buffer_.ptr).to!DateTime());
304 			break;
305 		}
306 	}
307 
308 	string toString() const {
309 		auto app = appender!string;
310 		toString(app);
311 		return app.data;
312 	}
313 
314 	bool opEquals(MySQLValue other) const {
315 		if (isString && other.isString) {
316 			return peek!string == other.peek!string;
317 		} else if (isScalar && other.isScalar) {
318 			if (isFloatingPoint || other.isFloatingPoint)
319 				return get!double == other.get!double;
320 			if (isSigned || other.isSigned)
321 				return get!long == other.get!long;
322 			return get!ulong == other.get!ulong;
323 		} else if (isTime && other.isTime) {
324 			return get!Duration == other.get!Duration;
325 		} else if (isTimestamp && other.isTimestamp) {
326 			return get!SysTime == other.get!SysTime;
327 		} else if (isNull && other.isNull) {
328 			return true;
329 		}
330 		return false;
331 	}
332 
333 	T get(T)(lazy T def) const {
334 		return !isNull ? get!T : def;
335 	}
336 
337 	T get(T)() const if (isScalarType!T && !is(T == enum)) {
338 		switch(type_) with (ColumnTypes) {
339 		case MYSQL_TYPE_TINY:
340 			return cast(T)(*cast(ubyte*)buffer_.ptr);
341 		case MYSQL_TYPE_YEAR:
342 		case MYSQL_TYPE_SHORT:
343 			return cast(T)(*cast(ushort*)buffer_.ptr);
344 		case MYSQL_TYPE_INT24:
345 		case MYSQL_TYPE_LONG:
346 			return cast(T)(*cast(uint*)buffer_.ptr);
347 		case MYSQL_TYPE_LONGLONG:
348 			return cast(T)(*cast(ulong*)buffer_.ptr);
349 		case MYSQL_TYPE_FLOAT:
350 			return cast(T)(*cast(float*)buffer_.ptr);
351 		case MYSQL_TYPE_DOUBLE:
352 			return cast(T)(*cast(double*)buffer_.ptr);
353 		default:
354 			throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
355 		}
356 	}
357 
358 	T get(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) || is(Unqual!T == Date)) {
359 		switch(type_) with (ColumnTypes) {
360 		case MYSQL_TYPE_DATE:
361 		case MYSQL_TYPE_NEWDATE:
362 		case MYSQL_TYPE_DATETIME:
363 		case MYSQL_TYPE_DATETIME2:
364 		case MYSQL_TYPE_TIMESTAMP:
365 		case MYSQL_TYPE_TIMESTAMP2:
366 			return (*cast(MySQLDateTime*)buffer_.ptr).to!T;
367 		default:
368 			throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
369 		}
370 	}
371 
372 	T get(T)() const if (is(Unqual!T == TimeOfDay)) {
373 		switch(type_) with (ColumnTypes) {
374 		case MYSQL_TYPE_DATE:
375 		case MYSQL_TYPE_NEWDATE:
376 		case MYSQL_TYPE_DATETIME:
377 		case MYSQL_TYPE_DATETIME2:
378 		case MYSQL_TYPE_TIMESTAMP:
379 		case MYSQL_TYPE_TIMESTAMP2:
380 			return (*cast(MySQLDateTime*)buffer_.ptr).to!T;
381 		case MYSQL_TYPE_TIME:
382 		case MYSQL_TYPE_TIME2:
383 			return (*cast(MySQLTime*)buffer_.ptr).to!T;
384 		default:
385 			throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
386 		}
387 	}
388 
389 	T get(T)() const if (is(Unqual!T == Duration)) {
390 		switch(type_) with (ColumnTypes) {
391 		case MYSQL_TYPE_TIME:
392 		case MYSQL_TYPE_TIME2:
393 			return (*cast(MySQLTime*)buffer_.ptr).to!T;
394 		default:
395 			throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
396 		}
397 	}
398 
399 	T get(T)() const if (is(Unqual!T == enum)) {
400 		return cast(T)get!(OriginalType!T);
401 	}
402 
403 	T get(T)() const if (isArray!T && !is(T == enum)) {
404 		switch(type_) with (ColumnTypes) {
405 		case MYSQL_TYPE_SET:
406 		case MYSQL_TYPE_ENUM:
407 		case MYSQL_TYPE_VARCHAR:
408 		case MYSQL_TYPE_VAR_STRING:
409 		case MYSQL_TYPE_STRING:
410 		case MYSQL_TYPE_JSON:
411 		case MYSQL_TYPE_NEWDECIMAL:
412 		case MYSQL_TYPE_DECIMAL:
413 			return (*cast(T*)buffer_.ptr).dup;
414 		case MYSQL_TYPE_BIT:
415 		case MYSQL_TYPE_TINY_BLOB:
416 		case MYSQL_TYPE_MEDIUM_BLOB:
417 		case MYSQL_TYPE_LONG_BLOB:
418 		case MYSQL_TYPE_BLOB:
419 		case MYSQL_TYPE_GEOMETRY:
420 			return (*cast(T*)buffer_.ptr).dup;
421 		default:
422 			throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
423 		}
424 	}
425 
426 	T get(T)() const if(isInstanceOf!(Nullable, T)) {
427 		if (type_ == ColumnTypes.MYSQL_TYPE_NULL)
428 			return T.init;
429 		return T(get!(typeof(T.init.get)));
430 	}
431 
432 	T peek(T)(lazy T def) const {
433 		return !isNull ? peek!(T) : def;
434 	}
435 
436 	T peek(T)() const if (isScalarType!T) {
437 		return get!(T);
438 	}
439 
440 	T peek(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) || is(Unqual!T == Date) || is(Unqual!T == TimeOfDay)) {
441 		return get!(T);
442 	}
443 
444 	T peek(T)() const if (is(Unqual!T == Duration)) {
445 		return get!(T);
446 	}
447 
448 	T peek(T)() const if (isArray!T) {
449 		switch(type_) with (ColumnTypes) {
450 		case MYSQL_TYPE_SET:
451 		case MYSQL_TYPE_ENUM:
452 		case MYSQL_TYPE_VARCHAR:
453 		case MYSQL_TYPE_VAR_STRING:
454 		case MYSQL_TYPE_STRING:
455 		case MYSQL_TYPE_JSON:
456 		case MYSQL_TYPE_NEWDECIMAL:
457 		case MYSQL_TYPE_DECIMAL:
458 			return (*cast(T*)buffer_.ptr);
459 		case MYSQL_TYPE_BIT:
460 		case MYSQL_TYPE_TINY_BLOB:
461 		case MYSQL_TYPE_MEDIUM_BLOB:
462 		case MYSQL_TYPE_LONG_BLOB:
463 		case MYSQL_TYPE_BLOB:
464 		case MYSQL_TYPE_GEOMETRY:
465 			return (*cast(T*)buffer_.ptr);
466 		default:
467 			throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
468 		}
469 	}
470 
471 	bool isNull() const {
472 		return type_ == ColumnTypes.MYSQL_TYPE_NULL;
473 	}
474 
475 	ColumnTypes type() const {
476 		return type_;
477 	}
478 
479 	bool isSigned() const {
480 		return sign_ == 0x00;
481 	}
482 
483 	bool isString() const {
484 		final switch(type_) with (ColumnTypes) {
485 		case MYSQL_TYPE_NULL:
486 			return false;
487 		case MYSQL_TYPE_TINY:
488 		case MYSQL_TYPE_YEAR:
489 		case MYSQL_TYPE_SHORT:
490 		case MYSQL_TYPE_INT24:
491 		case MYSQL_TYPE_LONG:
492 		case MYSQL_TYPE_LONGLONG:
493 		case MYSQL_TYPE_FLOAT:
494 		case MYSQL_TYPE_DOUBLE:
495 			return false;
496 		case MYSQL_TYPE_SET:
497 		case MYSQL_TYPE_ENUM:
498 		case MYSQL_TYPE_VARCHAR:
499 		case MYSQL_TYPE_VAR_STRING:
500 		case MYSQL_TYPE_STRING:
501 		case MYSQL_TYPE_JSON:
502 		case MYSQL_TYPE_NEWDECIMAL:
503 		case MYSQL_TYPE_DECIMAL:
504 		case MYSQL_TYPE_TINY_BLOB:
505 		case MYSQL_TYPE_MEDIUM_BLOB:
506 		case MYSQL_TYPE_LONG_BLOB:
507 		case MYSQL_TYPE_BLOB:
508 			return true;
509 		case MYSQL_TYPE_BIT:
510 		case MYSQL_TYPE_GEOMETRY:
511 			return false;
512 		case MYSQL_TYPE_TIME:
513 		case MYSQL_TYPE_TIME2:
514 			return false;
515 		case MYSQL_TYPE_DATE:
516 		case MYSQL_TYPE_NEWDATE:
517 		case MYSQL_TYPE_DATETIME:
518 		case MYSQL_TYPE_DATETIME2:
519 		case MYSQL_TYPE_TIMESTAMP:
520 		case MYSQL_TYPE_TIMESTAMP2:
521 			return false;
522 		}
523 	}
524 
525 	bool isScalar() const {
526 		final switch(type_) with (ColumnTypes) {
527 		case MYSQL_TYPE_NULL:
528 			return false;
529 		case MYSQL_TYPE_TINY:
530 		case MYSQL_TYPE_YEAR:
531 		case MYSQL_TYPE_SHORT:
532 		case MYSQL_TYPE_INT24:
533 		case MYSQL_TYPE_LONG:
534 		case MYSQL_TYPE_LONGLONG:
535 		case MYSQL_TYPE_FLOAT:
536 		case MYSQL_TYPE_DOUBLE:
537 			return true;
538 		case MYSQL_TYPE_SET:
539 		case MYSQL_TYPE_ENUM:
540 		case MYSQL_TYPE_VARCHAR:
541 		case MYSQL_TYPE_VAR_STRING:
542 		case MYSQL_TYPE_STRING:
543 		case MYSQL_TYPE_JSON:
544 		case MYSQL_TYPE_NEWDECIMAL:
545 		case MYSQL_TYPE_DECIMAL:
546 		case MYSQL_TYPE_TINY_BLOB:
547 		case MYSQL_TYPE_MEDIUM_BLOB:
548 		case MYSQL_TYPE_LONG_BLOB:
549 		case MYSQL_TYPE_BLOB:
550 			return false;
551 		case MYSQL_TYPE_BIT:
552 		case MYSQL_TYPE_GEOMETRY:
553 			return false;
554 		case MYSQL_TYPE_TIME:
555 		case MYSQL_TYPE_TIME2:
556 			return false;
557 		case MYSQL_TYPE_DATE:
558 		case MYSQL_TYPE_NEWDATE:
559 		case MYSQL_TYPE_DATETIME:
560 		case MYSQL_TYPE_DATETIME2:
561 		case MYSQL_TYPE_TIMESTAMP:
562 		case MYSQL_TYPE_TIMESTAMP2:
563 			return false;
564 		}
565 	}
566 
567 	bool isFloatingPoint() const {
568 		final switch(type_) with (ColumnTypes) {
569 		case MYSQL_TYPE_NULL:
570 			return false;
571 		case MYSQL_TYPE_TINY:
572 		case MYSQL_TYPE_YEAR:
573 		case MYSQL_TYPE_SHORT:
574 		case MYSQL_TYPE_INT24:
575 		case MYSQL_TYPE_LONG:
576 		case MYSQL_TYPE_LONGLONG:
577 			return false;
578 		case MYSQL_TYPE_FLOAT:
579 		case MYSQL_TYPE_DOUBLE:
580 			return true;
581 		case MYSQL_TYPE_SET:
582 		case MYSQL_TYPE_ENUM:
583 		case MYSQL_TYPE_VARCHAR:
584 		case MYSQL_TYPE_VAR_STRING:
585 		case MYSQL_TYPE_STRING:
586 		case MYSQL_TYPE_JSON:
587 		case MYSQL_TYPE_NEWDECIMAL:
588 		case MYSQL_TYPE_DECIMAL:
589 		case MYSQL_TYPE_TINY_BLOB:
590 		case MYSQL_TYPE_MEDIUM_BLOB:
591 		case MYSQL_TYPE_LONG_BLOB:
592 		case MYSQL_TYPE_BLOB:
593 			return false;
594 		case MYSQL_TYPE_BIT:
595 		case MYSQL_TYPE_GEOMETRY:
596 			return false;
597 		case MYSQL_TYPE_TIME:
598 		case MYSQL_TYPE_TIME2:
599 			return false;
600 		case MYSQL_TYPE_DATE:
601 		case MYSQL_TYPE_NEWDATE:
602 		case MYSQL_TYPE_DATETIME:
603 		case MYSQL_TYPE_DATETIME2:
604 		case MYSQL_TYPE_TIMESTAMP:
605 		case MYSQL_TYPE_TIMESTAMP2:
606 			return false;
607 		}
608 	}
609 
610 	bool isTime() const {
611 		final switch(type_) with (ColumnTypes) {
612 		case MYSQL_TYPE_NULL:
613 			return false;
614 		case MYSQL_TYPE_TINY:
615 		case MYSQL_TYPE_YEAR:
616 		case MYSQL_TYPE_SHORT:
617 		case MYSQL_TYPE_INT24:
618 		case MYSQL_TYPE_LONG:
619 		case MYSQL_TYPE_LONGLONG:
620 		case MYSQL_TYPE_FLOAT:
621 		case MYSQL_TYPE_DOUBLE:
622 			return false;
623 		case MYSQL_TYPE_SET:
624 		case MYSQL_TYPE_ENUM:
625 		case MYSQL_TYPE_VARCHAR:
626 		case MYSQL_TYPE_VAR_STRING:
627 		case MYSQL_TYPE_STRING:
628 		case MYSQL_TYPE_JSON:
629 		case MYSQL_TYPE_NEWDECIMAL:
630 		case MYSQL_TYPE_DECIMAL:
631 		case MYSQL_TYPE_TINY_BLOB:
632 		case MYSQL_TYPE_MEDIUM_BLOB:
633 		case MYSQL_TYPE_LONG_BLOB:
634 		case MYSQL_TYPE_BLOB:
635 			return false;
636 		case MYSQL_TYPE_BIT:
637 		case MYSQL_TYPE_GEOMETRY:
638 			return false;
639 		case MYSQL_TYPE_TIME:
640 		case MYSQL_TYPE_TIME2:
641 			return true;
642 		case MYSQL_TYPE_DATE:
643 		case MYSQL_TYPE_NEWDATE:
644 		case MYSQL_TYPE_DATETIME:
645 		case MYSQL_TYPE_DATETIME2:
646 		case MYSQL_TYPE_TIMESTAMP:
647 		case MYSQL_TYPE_TIMESTAMP2:
648 			return false;
649 		}
650 	}
651 
652 	alias isDuration = isTime;
653 
654 	bool isDateTime() const {
655 		final switch(type_) with (ColumnTypes) {
656 		case MYSQL_TYPE_NULL:
657 			return false;
658 		case MYSQL_TYPE_TINY:
659 		case MYSQL_TYPE_YEAR:
660 		case MYSQL_TYPE_SHORT:
661 		case MYSQL_TYPE_INT24:
662 		case MYSQL_TYPE_LONG:
663 		case MYSQL_TYPE_LONGLONG:
664 		case MYSQL_TYPE_FLOAT:
665 		case MYSQL_TYPE_DOUBLE:
666 			return false;
667 		case MYSQL_TYPE_SET:
668 		case MYSQL_TYPE_ENUM:
669 		case MYSQL_TYPE_VARCHAR:
670 		case MYSQL_TYPE_VAR_STRING:
671 		case MYSQL_TYPE_STRING:
672 		case MYSQL_TYPE_JSON:
673 		case MYSQL_TYPE_NEWDECIMAL:
674 		case MYSQL_TYPE_DECIMAL:
675 		case MYSQL_TYPE_TINY_BLOB:
676 		case MYSQL_TYPE_MEDIUM_BLOB:
677 		case MYSQL_TYPE_LONG_BLOB:
678 		case MYSQL_TYPE_BLOB:
679 			return false;
680 		case MYSQL_TYPE_BIT:
681 		case MYSQL_TYPE_GEOMETRY:
682 			return false;
683 		case MYSQL_TYPE_TIME:
684 		case MYSQL_TYPE_TIME2:
685 			return false;
686 		case MYSQL_TYPE_DATE:
687 		case MYSQL_TYPE_NEWDATE:
688 		case MYSQL_TYPE_DATETIME:
689 		case MYSQL_TYPE_DATETIME2:
690 		case MYSQL_TYPE_TIMESTAMP:
691 		case MYSQL_TYPE_TIMESTAMP2:
692 			return true;
693 		}
694 	}
695 
696 	alias isTimestamp = isDateTime;
697 
698 private:
699 	ColumnTypes type_ = ColumnTypes.MYSQL_TYPE_NULL;
700 	ubyte sign_;
701 	ubyte[6] pad_;
702 	ubyte[BufferSize] buffer_;
703 	const(char)[] name_;
704 }
705 
706 
707 struct MySQLColumn {
708 	uint length;
709 	ushort flags;
710 	ubyte decimals;
711 	ColumnTypes type;
712 	const(char)[] name;
713 }
714 
715 
716 alias MySQLHeader = MySQLColumn[];
717 
718 
719 struct MySQLTime {
720 	uint days;
721 	ubyte negative;
722 	ubyte hours;
723 	ubyte mins;
724 	ubyte secs;
725 	uint usecs;
726 
727 	auto to(T)() const if (is(Unqual!T == Duration)) {
728 		auto total = days * 86400_000_000L +
729 			hours * 3600_000_000L +
730 			mins * 60_000_000L +
731 			secs * 1_000_000L +
732 			usecs;
733 		return cast(T)dur!"usecs"(negative ? -total : total);
734 	}
735 
736 	auto to(T)() const if (is(Unqual!T == TimeOfDay)) {
737 		return cast(T)TimeOfDay(hours, mins, secs);
738 	}
739 
740 	static MySQLTime from(Duration duration) {
741 		MySQLTime time;
742 		duration.abs.split!("days", "hours", "minutes", "seconds", "usecs")(time.days, time.hours, time.mins, time.secs, time.usecs);
743 		time.negative = duration.isNegative ? 1 : 0;
744 		return time;
745 	}
746 
747 	static MySQLTime from(TimeOfDay tod) {
748 		MySQLTime time;
749 		time.hours = tod.hour;
750 		time.mins = tod.minute;
751 		time.secs = tod.second;
752 		return time;
753 	}
754 }
755 
756 void putMySQLTime(ref OutputPacket packet, in MySQLTime time) {
757 	if (time.days || time.hours || time.mins || time.mins || time.usecs) {
758 		auto usecs = time.usecs != 0;
759 		packet.put!ubyte(usecs ? 12 : 8);
760 		packet.put!ubyte(time.negative);
761 		packet.put!uint(time.days);
762 		packet.put!ubyte(time.hours);
763 		packet.put!ubyte(time.mins);
764 		packet.put!ubyte(time.secs);
765 		if (usecs)
766 			packet.put!uint(time.usecs);
767 	} else {
768 		packet.put!ubyte(0);
769 	}
770 }
771 
772 auto eatMySQLTime(ref InputPacket packet) {
773 	MySQLTime time;
774 	switch(packet.eat!ubyte) {
775 	case 12:
776 		time.negative = packet.eat!ubyte;
777 		time.days = packet.eat!uint;
778 		time.hours = packet.eat!ubyte;
779 		time.mins = packet.eat!ubyte;
780 		time.secs = packet.eat!ubyte;
781 		time.usecs = packet.eat!uint;
782 		break;
783 	case 8:
784 		time.negative = packet.eat!ubyte;
785 		time.days = packet.eat!uint;
786 		time.hours = packet.eat!ubyte;
787 		time.mins = packet.eat!ubyte;
788 		time.secs = packet.eat!ubyte;
789 		break;
790 	case 0:
791 		break;
792 	default:
793 		throw new MySQLProtocolException("Bad time struct format");
794 	}
795 
796 	return time;
797 }
798 
799 
800 struct MySQLDateTime {
801 	ushort year;
802 	ubyte month;
803 	ubyte day;
804 	ubyte hour;
805 	ubyte min;
806 	ubyte sec;
807 	uint usec;
808 
809 	bool valid() const {
810 		return month != 0;
811 	}
812 
813 	T to(T)() const if (is(Unqual!T == SysTime)) {
814 		assert(valid());
815 		return cast(T)SysTime(DateTime(year, month, day, hour, min, sec), usec.dur!"usecs", UTC());
816 	}
817 
818 	T to(T)() const if (is(Unqual!T == DateTime)) {
819 		assert(valid());
820 		return cast(T)DateTime(year, month, day, hour, min, sec);
821 	}
822 
823 	T to(T)() const if (is(T == Date)) {
824 		assert(valid());
825 		return cast(T)Date(year, month, day);
826 	}
827 
828 	T to(T)() const if (is(Unqual!T == TimeOfDay)) {
829 		return cast(T)TimeOfDay(hour, min, sec);
830 	}
831 
832 	static MySQLDateTime from(SysTime sysTime) {
833 		MySQLDateTime time;
834 
835 		auto dateTime = cast(DateTime)sysTime;
836 		time.year = dateTime.year;
837 		time.month = dateTime.month;
838 		time.day = dateTime.day;
839 		time.hour = dateTime.hour;
840 		time.min = dateTime.minute;
841 		time.sec = dateTime.second;
842 		time.usec = cast(int)sysTime.fracSecs.total!"usecs";
843 
844 		return time;
845 	}
846 
847 	static MySQLDateTime from(DateTime dateTime) {
848 		MySQLDateTime time;
849 
850 		time.year = dateTime.year;
851 		time.month = dateTime.month;
852 		time.day = dateTime.day;
853 		time.hour = dateTime.hour;
854 		time.min = dateTime.minute;
855 		time.sec = dateTime.second;
856 
857 		return time;
858 	}
859 
860 	static MySQLDateTime from(Date date) {
861 		MySQLDateTime time;
862 
863 		time.year = date.year;
864 		time.month = date.month;
865 		time.day = date.day;
866 
867 		return time;
868 	}
869 }
870 
871 void putMySQLDateTime(ref OutputPacket packet, in MySQLDateTime time) {
872 	auto marker = packet.marker!ubyte;
873 	ubyte length;
874 
875 	if (time.year || time.month || time.day) {
876 		length = 4;
877 		packet.put!ushort(time.year);
878 		packet.put!ubyte(time.month);
879 		packet.put!ubyte(time.day);
880 
881 		if (time.hour || time.min || time.sec || time.usec) {
882 			length = 7;
883 			packet.put!ubyte(time.hour);
884 			packet.put!ubyte(time.min);
885 			packet.put!ubyte(time.sec);
886 
887 			if (time.usec) {
888 				length = 11;
889 				packet.put!uint(time.usec);
890 			}
891 		}
892 	}
893 
894 	packet.put!ubyte(marker, length);
895 }
896 
897 auto eatMySQLDateTime(ref InputPacket packet) {
898 	MySQLDateTime time;
899 	switch(packet.eat!ubyte) {
900 	case 11:
901 		time.year = packet.eat!ushort;
902 		time.month = packet.eat!ubyte;
903 		time.day = packet.eat!ubyte;
904 		time.hour = packet.eat!ubyte;
905 		time.min = packet.eat!ubyte;
906 		time.sec = packet.eat!ubyte;
907 		time.usec = packet.eat!uint;
908 		break;
909 	case 7:
910 		time.year = packet.eat!ushort;
911 		time.month = packet.eat!ubyte;
912 		time.day = packet.eat!ubyte;
913 		time.hour = packet.eat!ubyte;
914 		time.min = packet.eat!ubyte;
915 		time.sec = packet.eat!ubyte;
916 		break;
917 	case 4:
918 		time.year = packet.eat!ushort;
919 		time.month = packet.eat!ubyte;
920 		time.day = packet.eat!ubyte;
921 		break;
922 	case 0:
923 		break;
924 	default:
925 		throw new MySQLProtocolException("Bad datetime struct format");
926 	}
927 
928 	return time;
929 }
930 
931 private void skip(ref const(char)[] x, char ch) {
932 	if (x.length && (x.ptr[0] == ch)) {
933 		x = x[1..$];
934 	} else {
935 		throw new MySQLProtocolException("Bad datetime string format");
936 	}
937 }
938 
939 auto parseMySQLTime(const(char)[] x) {
940 	MySQLTime time;
941 
942 	auto hours = x.parse!int;
943 	if (hours < 0) {
944 		time.negative = 1;
945 		hours = -hours;
946 	}
947 	time.days = hours / 24;
948 	time.hours = cast(ubyte)(hours % 24);
949 	x.skip(':');
950 	time.mins = x.parse!ubyte;
951 	x.skip(':');
952 	time.secs = x.parse!ubyte;
953 	if (x.length) {
954 		x.skip('.');
955 		time.usecs = x.parse!uint;
956 		switch (6 - max(6, x.length)) {
957 		case 0: break;
958 		case 1: time.usecs *= 10; break;
959 		case 2: time.usecs *= 100; break;
960 		case 3: time.usecs *= 1_000; break;
961 		case 4: time.usecs *= 10_000; break;
962 		case 5: time.usecs *= 100_000; break;
963 		default: assert("Bad datetime string format"); break;
964 		}
965 	}
966 
967 	return time;
968 }
969 
970 auto parseMySQLDateTime(const(char)[] x) {
971 	MySQLDateTime time;
972 
973 	time.year = x.parse!ushort;
974 	x.skip('-');
975 	time.month = x.parse!ubyte;
976 	x.skip('-');
977 	time.day = x.parse!ubyte;
978 	if (x.length) {
979 		x.skip(' ');
980 		time.hour = x.parse!ubyte;
981 		x.skip(':');
982 		time.min = x.parse!ubyte;
983 		x.skip(':');
984 		time.sec = x.parse!ubyte;
985 
986 		if (x.length) {
987 			x.skip('.');
988 			time.usec = x.parse!uint;
989 			switch (6 - max(6, x.length)) {
990 			case 0: break;
991 			case 1: time.usec *= 10; break;
992 			case 2: time.usec *= 100; break;
993 			case 3: time.usec *= 1_000; break;
994 			case 4: time.usec *= 10_000; break;
995 			case 5: time.usec *= 100_000; break;
996 			default: assert("Bad datetime string format"); break;
997 			}
998 		}
999 	}
1000 
1001 	return time;
1002 }
1003 
1004 void eatValue(ref InputPacket packet, ref const MySQLColumn column, ref MySQLValue value) {
1005 	auto signed = (column.flags & FieldFlags.UNSIGNED_FLAG) == 0;
1006 	final switch(column.type) with (ColumnTypes) {
1007 	case MYSQL_TYPE_NULL:
1008 		value = MySQLValue(column.name, column.type, signed, null, 0);
1009 		break;
1010 	case MYSQL_TYPE_TINY:
1011 		auto x = packet.eat!ubyte;
1012 		value = MySQLValue(column.name, column.type, signed, &x, 1);
1013 		break;
1014 	case MYSQL_TYPE_YEAR:
1015 	case MYSQL_TYPE_SHORT:
1016 		auto x = packet.eat!ushort;
1017 		value = MySQLValue(column.name, column.type, signed, &x, 2);
1018 		break;
1019 	case MYSQL_TYPE_INT24:
1020 	case MYSQL_TYPE_LONG:
1021 		auto x = packet.eat!uint;
1022 		value = MySQLValue(column.name, column.type, signed, &x, 4);
1023 		break;
1024 	case MYSQL_TYPE_DOUBLE:
1025 	case MYSQL_TYPE_LONGLONG:
1026 		auto x = packet.eat!ulong;
1027 		value = MySQLValue(column.name, column.type, signed, &x, 8);
1028 		break;
1029 	case MYSQL_TYPE_FLOAT:
1030 		auto x = packet.eat!float;
1031 		value = MySQLValue(column.name, column.type, signed, &x, 4);
1032 		break;
1033 	case MYSQL_TYPE_SET:
1034 	case MYSQL_TYPE_ENUM:
1035 	case MYSQL_TYPE_VARCHAR:
1036 	case MYSQL_TYPE_VAR_STRING:
1037 	case MYSQL_TYPE_STRING:
1038 	case MYSQL_TYPE_JSON:
1039 	case MYSQL_TYPE_NEWDECIMAL:
1040 	case MYSQL_TYPE_DECIMAL:
1041 		auto x = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc());
1042 		value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1043 		break;
1044 	case MYSQL_TYPE_BIT:
1045 	case MYSQL_TYPE_TINY_BLOB:
1046 	case MYSQL_TYPE_MEDIUM_BLOB:
1047 	case MYSQL_TYPE_LONG_BLOB:
1048 	case MYSQL_TYPE_BLOB:
1049 	case MYSQL_TYPE_GEOMETRY:
1050 		auto x = packet.eat!(const(ubyte)[])(cast(size_t)packet.eatLenEnc());
1051 		value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1052 		break;
1053 	case MYSQL_TYPE_TIME:
1054 	case MYSQL_TYPE_TIME2:
1055 		auto x = eatMySQLTime(packet);
1056 		value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1057 		break;
1058 	case MYSQL_TYPE_DATE:
1059 	case MYSQL_TYPE_NEWDATE:
1060 	case MYSQL_TYPE_DATETIME:
1061 	case MYSQL_TYPE_DATETIME2:
1062 	case MYSQL_TYPE_TIMESTAMP:
1063 	case MYSQL_TYPE_TIMESTAMP2:
1064 		auto x = eatMySQLDateTime(packet);
1065 		value = x.valid() ? MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof) : MySQLValue(column.name, ColumnTypes.MYSQL_TYPE_NULL, signed, null, 0);
1066 		break;
1067 	}
1068 }
1069 
1070 void eatValueText(ref InputPacket packet, ref const MySQLColumn column, ref MySQLValue value) {
1071 	auto signed = (column.flags & FieldFlags.UNSIGNED_FLAG) == 0;
1072 	auto svalue = (column.type != ColumnTypes.MYSQL_TYPE_NULL) ? cast(string)(packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc())) : string.init;
1073 	final switch(column.type) with (ColumnTypes) {
1074 	case MYSQL_TYPE_NULL:
1075 		value = MySQLValue(column.name, column.type, signed, null, 0);
1076 		break;
1077 	case MYSQL_TYPE_TINY:
1078 		auto x = (svalue.ptr[0] == '-') ? cast(ubyte)(-svalue[1..$].to!byte) : svalue.to!ubyte;
1079 		value = MySQLValue(column.name, column.type, signed, &x, 1);
1080 		break;
1081 	case MYSQL_TYPE_YEAR:
1082 	case MYSQL_TYPE_SHORT:
1083 		auto x = (svalue.ptr[0] == '-') ? cast(ushort)(-svalue[1..$].to!short) : svalue.to!ushort;
1084 		value = MySQLValue(column.name, column.type, signed, &x, 2);
1085 		break;
1086 	case MYSQL_TYPE_INT24:
1087 	case MYSQL_TYPE_LONG:
1088 		auto x = (svalue.ptr[0] == '-') ? cast(uint)(-svalue[1..$].to!int) : svalue.to!uint;
1089 		value = MySQLValue(column.name, column.type, signed, &x, 4);
1090 		break;
1091 	case MYSQL_TYPE_LONGLONG:
1092 		auto x = (svalue.ptr[0] == '-') ? cast(ulong)(-svalue[1..$].to!long) : svalue.to!ulong;
1093 		value = MySQLValue(column.name, column.type, signed, &x, 8);
1094 		break;
1095 	case MYSQL_TYPE_DOUBLE:
1096 		auto x = svalue.to!double;
1097 		value = MySQLValue(column.name, column.type, signed, &x, 8);
1098 		break;
1099 	case MYSQL_TYPE_FLOAT:
1100 		auto x = svalue.to!float;
1101 		value = MySQLValue(column.name, column.type, signed, &x, 4);
1102 		break;
1103 	case MYSQL_TYPE_SET:
1104 	case MYSQL_TYPE_ENUM:
1105 	case MYSQL_TYPE_VARCHAR:
1106 	case MYSQL_TYPE_VAR_STRING:
1107 	case MYSQL_TYPE_STRING:
1108 	case MYSQL_TYPE_JSON:
1109 	case MYSQL_TYPE_NEWDECIMAL:
1110 	case MYSQL_TYPE_DECIMAL:
1111 		value = MySQLValue(column.name, column.type, signed, &svalue, typeof(svalue).sizeof);
1112 		break;
1113 	case MYSQL_TYPE_BIT:
1114 	case MYSQL_TYPE_TINY_BLOB:
1115 	case MYSQL_TYPE_MEDIUM_BLOB:
1116 	case MYSQL_TYPE_LONG_BLOB:
1117 	case MYSQL_TYPE_BLOB:
1118 	case MYSQL_TYPE_GEOMETRY:
1119 		value = MySQLValue(column.name, column.type, signed, &svalue, typeof(svalue).sizeof);
1120 		break;
1121 	case MYSQL_TYPE_TIME:
1122 	case MYSQL_TYPE_TIME2:
1123 		auto x = parseMySQLTime(svalue);
1124 		value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1125 		break;
1126 	case MYSQL_TYPE_DATE:
1127 	case MYSQL_TYPE_NEWDATE:
1128 	case MYSQL_TYPE_DATETIME:
1129 	case MYSQL_TYPE_DATETIME2:
1130 	case MYSQL_TYPE_TIMESTAMP:
1131 	case MYSQL_TYPE_TIMESTAMP2:
1132 		auto x = parseMySQLDateTime(svalue);
1133 		value = x.valid() ? MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof) : MySQLValue(column.name, ColumnTypes.MYSQL_TYPE_NULL, signed, null, 0);
1134 		break;
1135 	}
1136 }
1137 
1138 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime)) {
1139 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIMESTAMP);
1140 	packet.put!ubyte(0x80);
1141 }
1142 
1143 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime)) {
1144 	putMySQLDateTime(packet, MySQLDateTime.from(value));
1145 }
1146 
1147 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Duration)) {
1148 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIME);
1149 	packet.put!ubyte(0x00);
1150 }
1151 
1152 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Duration)) {
1153 	putMySQLTime(packet, MySQLTime.from(value));
1154 }
1155 
1156 void putValueType(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T) {
1157 	alias UT = Unqual!T;
1158 
1159 	enum ubyte sign = isUnsigned!UT ? 0x80 : 0x00;
1160 
1161 	static if (is(UT == long) || is(UT == ulong)) {
1162 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONGLONG);
1163 		packet.put!ubyte(sign);
1164 	} else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) {
1165 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONG);
1166 		packet.put!ubyte(sign);
1167 	} else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) {
1168 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_SHORT);
1169 		packet.put!ubyte(sign);
1170 	} else {
1171 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TINY);
1172 		packet.put!ubyte(sign);
1173 	}
1174 }
1175 
1176 void putValueType(T)(ref OutputPacket packet, T value) if (isFloatingPoint!T) {
1177 	alias UT = Unqual!T;
1178 
1179 	enum ubyte sign = 0x00;
1180 
1181 	static if (is(UT == float)) {
1182 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_FLOAT);
1183 		packet.put!ubyte(sign);
1184 	} else {
1185 		packet.put!ubyte(ColumnTypes.MYSQL_TYPE_DOUBLE);
1186 		packet.put!ubyte(sign);
1187 	}
1188 }
1189 
1190 void putValue(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T) {
1191 	alias UT = Unqual!T;
1192 
1193 	static if (is(UT == long) || is(UT == ulong)) {
1194 		packet.put!ulong(value);
1195 	} else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) {
1196 		packet.put!uint(value);
1197 	} else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) {
1198 		packet.put!ushort(value);
1199 	} else {
1200 		packet.put!ubyte(value);
1201 	}
1202 }
1203 
1204 void putValue(T)(ref OutputPacket packet, T value) if (isFloatingPoint!T) {
1205 	alias UT = Unqual!T;
1206 
1207 	static if (is(UT == float)) {
1208 		packet.put!float(value);
1209 	} else {
1210 		packet.put!double(cast(double)value);
1211 	}
1212 }
1213 
1214 void putValueType(T)(ref OutputPacket packet, T value) if (isSomeString!(OriginalType!T)) {
1215 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_STRING);
1216 	packet.put!ubyte(0x80);
1217 }
1218 
1219 void putValue(T)(ref OutputPacket packet, T value) if (isSomeString!(OriginalType!T)) {
1220 	ulong size = value.length * T.init[0].sizeof;
1221 	packet.putLenEnc(size);
1222 	packet.put(value);
1223 }
1224 
1225 void putValueType(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!(OriginalType!T)) {
1226 	foreach(ref item; value)
1227 		putValueType(packet, item);
1228 }
1229 
1230 void putValue(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!(OriginalType!T)) {
1231 	foreach(ref item; value)
1232 		putValue(packet, item);
1233 }
1234 
1235 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLBinary)) {
1236 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_BLOB);
1237 	packet.put!ubyte(0x80);
1238 }
1239 
1240 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLBinary)) {
1241 	ulong size = value.length;
1242 	packet.putLenEnc(size);
1243 	packet.put(value.data);
1244 }
1245 
1246 void putValueType(T)(ref OutputPacket packet, T value) if(is(Unqual!T == MySQLValue)) {
1247 	packet.put!ubyte(value.type_);
1248 	packet.put!ubyte(value.sign_);
1249 }
1250 
1251 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLValue)) {
1252 	final switch(value.type) with (ColumnTypes) {
1253 	case MYSQL_TYPE_NULL:
1254 		break;
1255 	case MYSQL_TYPE_TINY:
1256 		packet.put!ubyte(*cast(ubyte*)value.buffer_.ptr);
1257 		break;
1258 	case MYSQL_TYPE_YEAR:
1259 	case MYSQL_TYPE_SHORT:
1260 		packet.put!ushort(*cast(ushort*)value.buffer_.ptr);
1261 		break;
1262 	case MYSQL_TYPE_INT24:
1263 	case MYSQL_TYPE_LONG:
1264 		packet.put!uint(*cast(uint*)value.buffer_.ptr);
1265 		break;
1266 	case MYSQL_TYPE_LONGLONG:
1267 		packet.put!ulong(*cast(ulong*)value.buffer_.ptr);
1268 		break;
1269 	case MYSQL_TYPE_DOUBLE:
1270 		packet.put!double(*cast(double*)value.buffer_.ptr);
1271 		break;
1272 	case MYSQL_TYPE_FLOAT:
1273 		packet.put!float(*cast(float*)value.buffer_.ptr);
1274 		break;
1275 	case MYSQL_TYPE_SET:
1276 	case MYSQL_TYPE_ENUM:
1277 	case MYSQL_TYPE_VARCHAR:
1278 	case MYSQL_TYPE_VAR_STRING:
1279 	case MYSQL_TYPE_STRING:
1280 	case MYSQL_TYPE_JSON:
1281 	case MYSQL_TYPE_NEWDECIMAL:
1282 	case MYSQL_TYPE_DECIMAL:
1283 	case MYSQL_TYPE_BIT:
1284 	case MYSQL_TYPE_TINY_BLOB:
1285 	case MYSQL_TYPE_MEDIUM_BLOB:
1286 	case MYSQL_TYPE_LONG_BLOB:
1287 	case MYSQL_TYPE_BLOB:
1288 	case MYSQL_TYPE_GEOMETRY:
1289 		packet.putLenEnc((*cast(ubyte[]*)value.buffer_.ptr).length);
1290 		packet.put(*cast(ubyte[]*)value.buffer_.ptr);
1291 		break;
1292 	case MYSQL_TYPE_TIME:
1293 	case MYSQL_TYPE_TIME2:
1294 		packet.putMySQLTime(*cast(MySQLTime*)value.buffer_.ptr);
1295 		break;
1296 	case MYSQL_TYPE_DATE:
1297 	case MYSQL_TYPE_NEWDATE:
1298 	case MYSQL_TYPE_DATETIME:
1299 	case MYSQL_TYPE_DATETIME2:
1300 	case MYSQL_TYPE_TIMESTAMP:
1301 	case MYSQL_TYPE_TIMESTAMP2:
1302 		packet.putMySQLDateTime(*cast(MySQLDateTime*)value.buffer_.ptr);
1303 		break;
1304 	}
1305 }
1306 
1307 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == typeof(null))) {
1308 	packet.put!ubyte(ColumnTypes.MYSQL_TYPE_NULL);
1309 	packet.put!ubyte(0x00);
1310 }
1311 
1312 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == typeof(null))) {
1313 }
1314 
1315 void putValueType(T)(ref OutputPacket packet, T value) if (isInstanceOf!(Nullable, T) || isInstanceOf!(NullableRef, T)) {
1316 	if (value.isNull) {
1317 		putValueType(packet, null);
1318 	} else {
1319 		putValueType(packet, value.get);
1320 	}
1321 }
1322 
1323 void putValue(T)(ref OutputPacket packet, T value) if (isInstanceOf!(Nullable, T) || isInstanceOf!(NullableRef, T)) {
1324 	if (value.isNull) {
1325 		putValue(packet, null);
1326 	} else {
1327 		putValue(packet, value.get);
1328 	}
1329 }