1 module database.mysql.type;
2 
3 import std.algorithm;
4 import std.array : appender;
5 import std.conv : parse, to;
6 import std.datetime;
7 import std.format: format, formattedWrite;
8 import std.traits;
9 import std.typecons;
10 import std.variant;
11 
12 import database.mysql.protocol;
13 import database.mysql.packet;
14 import database.mysql.exception;
15 import database.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 {
25     return TableNameAttribute(name);
26 }
27 
28 @property IgnoreAttribute ignore()
29 {
30     return IgnoreAttribute();
31 }
32 
33 @property OptionalAttribute optional()
34 {
35     return OptionalAttribute();
36 }
37 
38 @property NameAttribute as(const(char)[] name)
39 {
40     return NameAttribute(name);
41 }
42 
43 @property UnCamelCaseAttribute uncamel()
44 {
45     return UnCamelCaseAttribute();
46 }
47 
48 template Unnull(U)
49 {
50     alias impl(N : Nullable!T, T) = T;
51     alias impl(T) = T;
52     alias Unnull = impl!U;
53 }
54 
55 alias Unboth(T) = Unqual!(Unnull!T);
56 enum isSomeDuration(T) = is(Unboth!T == Date) || is(Unboth!T == DateTime) || is(Unboth!T == SysTime) || is(Unboth!T == Duration) || is(Unboth!T == TimeOfDay);
57 enum isValueType(T) = isSomeDuration!(Unboth!T) || is(Unboth!T == MySQLValue) || (!is(Unboth!T == struct) && !is(Unboth!T == class));
58 
59 template isWritableDataMember(T, string Member)
60 {
61     static if (is(TypeTuple!(__traits(getMember, T, Member))))
62     {
63         enum isWritableDataMember = false;
64     }
65     else static if (!is(typeof(__traits(getMember, T, Member))))
66     {
67         enum isWritableDataMember = false;
68     }
69     else static if (is(typeof(__traits(getMember, T, Member)) == void))
70     {
71         enum isWritableDataMember = false;
72     }
73     else static if (is(typeof(__traits(getMember, T, Member)) == enum))
74     {
75         enum isWritableDataMember = true;
76     }
77     else static if (hasUDA!(__traits(getMember, T, Member), IgnoreAttribute))
78     {
79         enum isWritableDataMember = false;
80     }
81     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))
82     {
83         enum isWritableDataMember = false;
84     }
85     else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member))))
86     {
87         enum isWritableDataMember = false;
88     }
89     else static if (isSomeFunction!(typeof(__traits(getMember, T, Member))))
90     {
91         enum isWritableDataMember = false;
92     }
93     else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }())))
94     {
95         enum isWritableDataMember = false;
96     }
97     else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export"))
98     {
99         enum isWritableDataMember = false;
100     }
101     else
102     {
103         enum isWritableDataMember = true;
104     }
105 }
106 
107 template isReadableDataMember(T, string Member)
108 {
109     static if (is(TypeTuple!(__traits(getMember, T, Member))))
110     {
111         enum isReadableDataMember = false;
112     }
113     else static if (!is(typeof(__traits(getMember, T, Member))))
114     {
115         enum isReadableDataMember = false;
116     }
117     else static if (is(typeof(__traits(getMember, T, Member)) == void))
118     {
119         enum isReadableDataMember = false;
120     }
121     else static if (is(typeof(__traits(getMember, T, Member)) == enum))
122     {
123         enum isReadableDataMember = true;
124     }
125     else static if (hasUDA!(__traits(getMember, T, Member), IgnoreAttribute))
126     {
127         enum isReadableDataMember = false;
128     }
129     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))
130     {
131         enum isReadableDataMember = false;
132     }
133     else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member))))
134     {
135         enum isReadableDataMember = false;
136     }
137     else static if (isSomeFunction!(typeof(__traits(getMember, T, Member)))  /* && return type is valueType*/ )
138     {
139         enum isReadableDataMember = true;
140     }
141     else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }())))
142     {
143         enum isReadableDataMember = false;
144     }
145     else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export"))
146     {
147         enum isReadableDataMember = false;
148     }
149     else
150     {
151         enum isReadableDataMember = true;
152     }
153 }
154 
155 struct MySQLRawString
156 {
157     @disable this();
158 
159     this(const(char)[] data)
160     {
161         data_ = data;
162     }
163 
164     @property auto length() const
165     {
166         return data_.length;
167     }
168 
169     @property auto data() const
170     {
171         return data_;
172     }
173 
174     private const(char)[] data_;
175 }
176 
177 struct MySQLFragment
178 {
179     @disable this();
180 
181     this(const(char)[] data)
182     {
183         data_ = data;
184     }
185 
186     @property auto length() const
187     {
188         return data_.length;
189     }
190 
191     @property auto data() const
192     {
193         return data_;
194     }
195 
196     private const(char)[] data_;
197 }
198 
199 struct MySQLBinary
200 {
201     this(T)(T[] data)
202     {
203         data_ = (cast(ubyte*)data.ptr)[0..typeof(T[].init[0]).sizeof * data.length];
204     }
205 
206     @property auto length() const
207     {
208         return data_.length;
209     }
210 
211     @property auto data() const
212     {
213         return data_;
214     }
215 
216     private const(ubyte)[] data_;
217 }
218 
219 struct MySQLValue
220 {
221     package enum BufferSize = max(ulong.sizeof, (ulong[]).sizeof, MySQLDateTime.sizeof, MySQLTime.sizeof);
222 
223     package this(const(char)[] name, ColumnTypes type, bool signed, void* ptr, size_t size)
224     {
225         assert(size <= BufferSize);
226         type_ = type;
227         sign_ = signed ? 0x00 : 0x80;
228         if (type != ColumnTypes.MYSQL_TYPE_NULL)
229             buffer_[0..size] = (cast(ubyte*)ptr)[0..size];
230         name_ = name;
231     }
232 
233     this(T)(T) if (is(Unqual!T == typeof(null)))
234     {
235         type_ = ColumnTypes.MYSQL_TYPE_NULL;
236         sign_ = 0x00;
237     }
238 
239     this(T)(T value) if (is(Unqual!T == MySQLValue))
240     {
241         this = value;
242     }
243 
244     this(T)(T value) if (std.traits.isFloatingPoint!T)
245     {
246         alias UT = Unqual!T;
247 
248         sign_ = 0x00;
249         static if (is(UT == float))
250         {
251             type_ = ColumnTypes.MYSQL_TYPE_FLOAT;
252             buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
253         }
254         else static if (is(UT == double))
255         {
256             type_ = ColumnTypes.MYSQL_TYPE_DOUBLE;
257             buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
258         }
259         else
260         {
261             type_ = ColumnTypes.MYSQL_TYPE_DOUBLE;
262             auto data = cast(double)value;
263             buffer_[0..typeof(data).sizeof] = (cast(ubyte*)&data)[0..typeof(data).sizeof];
264         }
265     }
266 
267     this(T)(T value) if (isIntegral!T || isBoolean!T)
268     {
269         alias UT = Unqual!T;
270 
271         static if (is(UT == long) || is(UT == ulong))
272         {
273             type_ = ColumnTypes.MYSQL_TYPE_LONGLONG;
274         }
275         else static if (is(UT == int) || is(UT == uint) || is(UT == dchar))
276         {
277             type_ = ColumnTypes.MYSQL_TYPE_LONG;
278         }
279         else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar))
280         {
281             type_ = ColumnTypes.MYSQL_TYPE_SHORT;
282         }
283         else
284         {
285             type_ = ColumnTypes.MYSQL_TYPE_TINY;
286         }
287 
288         sign_ = isUnsigned!UT ? 0x80 : 0x00;
289         buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof];
290     }
291 
292     this(T)(T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime))
293     {
294         type_ = ColumnTypes.MYSQL_TYPE_TIMESTAMP;
295         sign_ = 0x00;
296         (*cast(MySQLDateTime*)buffer_) = MySQLDateTime.from(value);
297     }
298 
299     this(T)(T value) if (is(Unqual!T == Duration) || is(Unqual!T == TimeOfDay))
300     {
301         type_ = ColumnTypes.MYSQL_TYPE_TIME;
302         sign_ = 0x00;
303         (*cast(MySQLTime*)buffer_) = MySQLTime.from(value);
304     }
305 
306     this(T)(T value) if (isSomeString!(OriginalType!T))
307     {
308         static assert(typeof(T.init[0]).sizeof == 1, format("Unsupported string type: %s", T.stringof));
309 
310         type_ = ColumnTypes.MYSQL_TYPE_STRING;
311         sign_ = 0x80;
312 
313         auto slice = value[0..$];
314         buffer_.ptr[0..typeof(slice).sizeof] = (cast(ubyte*)&slice)[0..typeof(slice).sizeof];
315     }
316 
317     this(T)(T value) if (is(Unqual!T == MySQLBinary))
318     {
319         type_ = ColumnTypes.MYSQL_TYPE_BLOB;
320         sign_ = 0x80;
321         buffer_.ptr[0..(ubyte[]).sizeof] = (cast(ubyte*)&value.data_)[0..(ubyte[]).sizeof];
322     }
323 
324     void toString(Appender)(ref Appender app) const
325     {
326         final switch(type_) with (ColumnTypes)
327         {
328             case MYSQL_TYPE_NULL:
329                 break;
330             case MYSQL_TYPE_TINY:
331                 if (isSigned) formattedWrite(&app, "%d", *cast(ubyte*)buffer_.ptr);
332                 else formattedWrite(&app, "%d", *cast(byte*)buffer_.ptr);
333                 break;
334             case MYSQL_TYPE_YEAR:
335             case MYSQL_TYPE_SHORT:
336                 if (isSigned) formattedWrite(&app, "%d", *cast(short*)buffer_.ptr);
337                 else formattedWrite(&app, "%d", *cast(ushort*)buffer_.ptr);
338                 break;
339             case MYSQL_TYPE_INT24:
340             case MYSQL_TYPE_LONG:
341                 if (isSigned) formattedWrite(&app, "%d", *cast(int*)buffer_.ptr);
342                 else formattedWrite(&app, "%d", *cast(uint*)buffer_.ptr);
343                 break;
344             case MYSQL_TYPE_LONGLONG:
345                 if (isSigned) formattedWrite(&app, "%d", *cast(long*)buffer_.ptr);
346                 else formattedWrite(&app, "%d", *cast(ulong*)buffer_.ptr);
347                 break;
348             case MYSQL_TYPE_FLOAT:
349                 float f = *cast(float*)buffer_.ptr;
350                 long l = cast(long)f;
351                 f -= l;
352                 string str = to!string(l) ~ ((f > 0) ? f.to!string[1 .. $] : "");
353                 app.put(str);
354                 //formattedWrite(&app, "%g", *cast(float*)buffer_.ptr);
355                 break;
356             case MYSQL_TYPE_DOUBLE:
357                 double d = *cast(double*)buffer_.ptr;
358                 long l = cast(long)d;
359                 d -= l;
360                 string str = to!string(l) ~ ((d > 0) ? d.to!string[1 .. $] : "");
361                 app.put(str);
362                 //formattedWrite(&app, "%g", *cast(double*)buffer_.ptr);
363                 break;
364             case MYSQL_TYPE_SET:
365             case MYSQL_TYPE_ENUM:
366             case MYSQL_TYPE_VARCHAR:
367             case MYSQL_TYPE_VAR_STRING:
368             case MYSQL_TYPE_STRING:
369             case MYSQL_TYPE_JSON:
370             case MYSQL_TYPE_NEWDECIMAL:
371             case MYSQL_TYPE_DECIMAL:
372             case MYSQL_TYPE_TINY_BLOB:
373             case MYSQL_TYPE_MEDIUM_BLOB:
374             case MYSQL_TYPE_LONG_BLOB:
375             case MYSQL_TYPE_BLOB:
376                 app.put(*cast(string*)buffer_.ptr);
377                 break;
378             case MYSQL_TYPE_BIT:
379             case MYSQL_TYPE_GEOMETRY:
380                 formattedWrite(&app, "%s", *cast(ubyte[]*)buffer_.ptr);
381                 break;
382             case MYSQL_TYPE_TIME:
383             case MYSQL_TYPE_TIME2:
384                 formattedWrite(&app, "%s", (*cast(MySQLTime*)buffer_.ptr).to!Duration());
385                 break;
386             case MYSQL_TYPE_DATE:
387             case MYSQL_TYPE_NEWDATE:
388             case MYSQL_TYPE_DATETIME:
389             case MYSQL_TYPE_DATETIME2:
390             case MYSQL_TYPE_TIMESTAMP:
391             case MYSQL_TYPE_TIMESTAMP2:
392                 DateTime dt = (*cast(MySQLDateTime*)buffer_.ptr).to!DateTime();
393                 app.put(dt.date().toISOExtString() ~ " " ~ dt.timeOfDay().toISOExtString());
394                 //formattedWrite(&app, "%s", (*cast(MySQLDateTime*)buffer_.ptr).to!DateTime());
395                 break;
396         }
397     }
398 
399     string toString() const
400     {
401         auto app = appender!string;
402         toString(app);
403         return app.data;
404     }
405 
406     bool opEquals(MySQLValue other) const
407     {
408         if (isString && other.isString)
409         {
410             return peek!string == other.peek!string;
411         }
412         else if (isScalar && other.isScalar)
413         {
414             if (isFloatingPoint || other.isFloatingPoint)
415                 return get!double == other.get!double;
416             if (isSigned || other.isSigned)
417                 return get!long == other.get!long;
418             return get!ulong == other.get!ulong;
419         }
420         else if (isTime && other.isTime)
421         {
422             return get!Duration == other.get!Duration;
423         }
424         else if (isTimestamp && other.isTimestamp)
425         {
426             return get!SysTime == other.get!SysTime;
427         }
428         else if (isNull && other.isNull)
429         {
430             return true;
431         }
432 
433         return false;
434     }
435 
436     T get(T)(lazy T def) const
437     {
438         return !isNull ? get!T : def;
439     }
440 
441     T get(T)() const if (isScalarType!T && !is(T == enum))
442     {
443         switch(type_) with (ColumnTypes)
444         {
445             case MYSQL_TYPE_TINY:
446                 return cast(T)(*cast(ubyte*)buffer_.ptr);
447             case MYSQL_TYPE_YEAR:
448             case MYSQL_TYPE_SHORT:
449                 return cast(T)(*cast(ushort*)buffer_.ptr);
450             case MYSQL_TYPE_INT24:
451             case MYSQL_TYPE_LONG:
452                 return cast(T)(*cast(uint*)buffer_.ptr);
453             case MYSQL_TYPE_LONGLONG:
454                 return cast(T)(*cast(ulong*)buffer_.ptr);
455             case MYSQL_TYPE_FLOAT:
456                 return cast(T)(*cast(float*)buffer_.ptr);
457             case MYSQL_TYPE_DOUBLE:
458                 return cast(T)(*cast(double*)buffer_.ptr);
459             default:
460                 throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
461         }
462     }
463 
464     T get(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) || is(Unqual!T == Date))
465     {
466         switch(type_) with (ColumnTypes)
467         {
468             case MYSQL_TYPE_DATE:
469             case MYSQL_TYPE_NEWDATE:
470             case MYSQL_TYPE_DATETIME:
471             case MYSQL_TYPE_DATETIME2:
472             case MYSQL_TYPE_TIMESTAMP:
473             case MYSQL_TYPE_TIMESTAMP2:
474                 return (*cast(MySQLDateTime*)buffer_.ptr).to!T;
475             default:
476                 throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
477         }
478     }
479 
480     T get(T)() const if (is(Unqual!T == TimeOfDay))
481     {
482         switch(type_) with (ColumnTypes)
483         {
484             case MYSQL_TYPE_DATE:
485             case MYSQL_TYPE_NEWDATE:
486             case MYSQL_TYPE_DATETIME:
487             case MYSQL_TYPE_DATETIME2:
488             case MYSQL_TYPE_TIMESTAMP:
489             case MYSQL_TYPE_TIMESTAMP2:
490                 return (*cast(MySQLDateTime*)buffer_.ptr).to!T;
491             case MYSQL_TYPE_TIME:
492             case MYSQL_TYPE_TIME2:
493                 return (*cast(MySQLTime*)buffer_.ptr).to!T;
494             default:
495                 throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
496         }
497     }
498 
499     T get(T)() const if (is(Unqual!T == Duration))
500     {
501         switch(type_) with (ColumnTypes)
502         {
503             case MYSQL_TYPE_TIME:
504             case MYSQL_TYPE_TIME2:
505                 return (*cast(MySQLTime*)buffer_.ptr).to!T;
506             default:
507                 throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
508         }
509     }
510 
511     T get(T)() const if (is(Unqual!T == enum))
512     {
513         return cast(T)get!(OriginalType!T);
514     }
515 
516     T get(T)() const if (isArray!T && !is(T == enum))
517     {
518         switch(type_) with (ColumnTypes)
519         {
520             case MYSQL_TYPE_SET:
521             case MYSQL_TYPE_ENUM:
522             case MYSQL_TYPE_VARCHAR:
523             case MYSQL_TYPE_VAR_STRING:
524             case MYSQL_TYPE_STRING:
525             case MYSQL_TYPE_JSON:
526             case MYSQL_TYPE_NEWDECIMAL:
527             case MYSQL_TYPE_DECIMAL:
528                 return (*cast(T*)buffer_.ptr).dup;
529             case MYSQL_TYPE_BIT:
530             case MYSQL_TYPE_TINY_BLOB:
531             case MYSQL_TYPE_MEDIUM_BLOB:
532             case MYSQL_TYPE_LONG_BLOB:
533             case MYSQL_TYPE_BLOB:
534             case MYSQL_TYPE_GEOMETRY:
535                 return (*cast(T*)buffer_.ptr).dup;
536             default:
537                 throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
538         }
539     }
540 
541     T get(T)() const if(isInstanceOf!(Nullable, T))
542     {
543         if (type_ == ColumnTypes.MYSQL_TYPE_NULL)
544             return T.init;
545         return T(get!(typeof(T.init.get)));
546     }
547 
548     T peek(T)(lazy T def) const
549     {
550         return !isNull ? peek!(T) : def;
551     }
552 
553     T peek(T)() const if (isScalarType!T)
554     {
555         return get!(T);
556     }
557 
558     T peek(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) || is(Unqual!T == Date) || is(Unqual!T == TimeOfDay))
559     {
560         return get!(T);
561     }
562 
563     T peek(T)() const if (is(Unqual!T == Duration))
564     {
565         return get!(T);
566     }
567 
568     T peek(T)() const if (isArray!T)
569     {
570         switch(type_) with (ColumnTypes)
571         {
572             case MYSQL_TYPE_SET:
573             case MYSQL_TYPE_ENUM:
574             case MYSQL_TYPE_VARCHAR:
575             case MYSQL_TYPE_VAR_STRING:
576             case MYSQL_TYPE_STRING:
577             case MYSQL_TYPE_JSON:
578             case MYSQL_TYPE_NEWDECIMAL:
579             case MYSQL_TYPE_DECIMAL:
580                 return (*cast(T*)buffer_.ptr);
581             case MYSQL_TYPE_BIT:
582             case MYSQL_TYPE_TINY_BLOB:
583             case MYSQL_TYPE_MEDIUM_BLOB:
584             case MYSQL_TYPE_LONG_BLOB:
585             case MYSQL_TYPE_BLOB:
586             case MYSQL_TYPE_GEOMETRY:
587                 return (*cast(T*)buffer_.ptr);
588             default:
589                 throw new MySQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof));
590         }
591     }
592 
593     bool isNull() const
594     {
595         return type_ == ColumnTypes.MYSQL_TYPE_NULL;
596     }
597 
598     ColumnTypes type() const
599     {
600         return type_;
601     }
602 
603     bool isSigned() const
604     {
605         return sign_ == 0x00;
606     }
607 
608     bool isString() const
609     {
610         final switch(type_) with (ColumnTypes)
611         {
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 true;
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 false;
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     bool isScalar() const
653     {
654         final switch(type_) with (ColumnTypes)
655         {
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 true;
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 false;
693         }
694     }
695 
696     bool isFloatingPoint() const
697     {
698         final switch(type_) with (ColumnTypes)
699         {
700             case MYSQL_TYPE_NULL:
701                 return false;
702             case MYSQL_TYPE_TINY:
703             case MYSQL_TYPE_YEAR:
704             case MYSQL_TYPE_SHORT:
705             case MYSQL_TYPE_INT24:
706             case MYSQL_TYPE_LONG:
707             case MYSQL_TYPE_LONGLONG:
708                 return false;
709             case MYSQL_TYPE_FLOAT:
710             case MYSQL_TYPE_DOUBLE:
711                 return true;
712             case MYSQL_TYPE_SET:
713             case MYSQL_TYPE_ENUM:
714             case MYSQL_TYPE_VARCHAR:
715             case MYSQL_TYPE_VAR_STRING:
716             case MYSQL_TYPE_STRING:
717             case MYSQL_TYPE_JSON:
718             case MYSQL_TYPE_NEWDECIMAL:
719             case MYSQL_TYPE_DECIMAL:
720             case MYSQL_TYPE_TINY_BLOB:
721             case MYSQL_TYPE_MEDIUM_BLOB:
722             case MYSQL_TYPE_LONG_BLOB:
723             case MYSQL_TYPE_BLOB:
724                 return false;
725             case MYSQL_TYPE_BIT:
726             case MYSQL_TYPE_GEOMETRY:
727                 return false;
728             case MYSQL_TYPE_TIME:
729             case MYSQL_TYPE_TIME2:
730                 return false;
731             case MYSQL_TYPE_DATE:
732             case MYSQL_TYPE_NEWDATE:
733             case MYSQL_TYPE_DATETIME:
734             case MYSQL_TYPE_DATETIME2:
735             case MYSQL_TYPE_TIMESTAMP:
736             case MYSQL_TYPE_TIMESTAMP2:
737                 return false;
738         }
739     }
740 
741     bool isTime() const
742     {
743         final switch(type_) with (ColumnTypes)
744         {
745             case MYSQL_TYPE_NULL:
746                 return false;
747             case MYSQL_TYPE_TINY:
748             case MYSQL_TYPE_YEAR:
749             case MYSQL_TYPE_SHORT:
750             case MYSQL_TYPE_INT24:
751             case MYSQL_TYPE_LONG:
752             case MYSQL_TYPE_LONGLONG:
753             case MYSQL_TYPE_FLOAT:
754             case MYSQL_TYPE_DOUBLE:
755                 return false;
756             case MYSQL_TYPE_SET:
757             case MYSQL_TYPE_ENUM:
758             case MYSQL_TYPE_VARCHAR:
759             case MYSQL_TYPE_VAR_STRING:
760             case MYSQL_TYPE_STRING:
761             case MYSQL_TYPE_JSON:
762             case MYSQL_TYPE_NEWDECIMAL:
763             case MYSQL_TYPE_DECIMAL:
764             case MYSQL_TYPE_TINY_BLOB:
765             case MYSQL_TYPE_MEDIUM_BLOB:
766             case MYSQL_TYPE_LONG_BLOB:
767             case MYSQL_TYPE_BLOB:
768                 return false;
769             case MYSQL_TYPE_BIT:
770             case MYSQL_TYPE_GEOMETRY:
771                 return false;
772             case MYSQL_TYPE_TIME:
773             case MYSQL_TYPE_TIME2:
774                 return true;
775             case MYSQL_TYPE_DATE:
776             case MYSQL_TYPE_NEWDATE:
777             case MYSQL_TYPE_DATETIME:
778             case MYSQL_TYPE_DATETIME2:
779             case MYSQL_TYPE_TIMESTAMP:
780             case MYSQL_TYPE_TIMESTAMP2:
781                 return false;
782         }
783     }
784 
785     alias isDuration = isTime;
786 
787     bool isDateTime() const {
788         final switch(type_) with (ColumnTypes)
789         {
790             case MYSQL_TYPE_NULL:
791                 return false;
792             case MYSQL_TYPE_TINY:
793             case MYSQL_TYPE_YEAR:
794             case MYSQL_TYPE_SHORT:
795             case MYSQL_TYPE_INT24:
796             case MYSQL_TYPE_LONG:
797             case MYSQL_TYPE_LONGLONG:
798             case MYSQL_TYPE_FLOAT:
799             case MYSQL_TYPE_DOUBLE:
800                 return false;
801             case MYSQL_TYPE_SET:
802             case MYSQL_TYPE_ENUM:
803             case MYSQL_TYPE_VARCHAR:
804             case MYSQL_TYPE_VAR_STRING:
805             case MYSQL_TYPE_STRING:
806             case MYSQL_TYPE_JSON:
807             case MYSQL_TYPE_NEWDECIMAL:
808             case MYSQL_TYPE_DECIMAL:
809             case MYSQL_TYPE_TINY_BLOB:
810             case MYSQL_TYPE_MEDIUM_BLOB:
811             case MYSQL_TYPE_LONG_BLOB:
812             case MYSQL_TYPE_BLOB:
813                 return false;
814             case MYSQL_TYPE_BIT:
815             case MYSQL_TYPE_GEOMETRY:
816                 return false;
817             case MYSQL_TYPE_TIME:
818             case MYSQL_TYPE_TIME2:
819                 return false;
820             case MYSQL_TYPE_DATE:
821             case MYSQL_TYPE_NEWDATE:
822             case MYSQL_TYPE_DATETIME:
823             case MYSQL_TYPE_DATETIME2:
824             case MYSQL_TYPE_TIMESTAMP:
825             case MYSQL_TYPE_TIMESTAMP2:
826                 return true;
827         }
828     }
829 
830     alias isTimestamp = isDateTime;
831 
832 private:
833 
834     ColumnTypes type_ = ColumnTypes.MYSQL_TYPE_NULL;
835     ubyte sign_;
836     ubyte[6] pad_;
837     ubyte[BufferSize] buffer_;
838     const(char)[] name_;
839 }
840 
841 struct MySQLColumn
842 {
843     uint length;
844     ushort flags;
845     ubyte decimals;
846     ColumnTypes type;
847     const(char)[] name;
848 }
849 
850 alias MySQLHeader = MySQLColumn[];
851 
852 struct MySQLTime
853 {
854     uint days;
855     ubyte negative;
856     ubyte hours;
857     ubyte mins;
858     ubyte secs;
859     uint usecs;
860 
861     auto to(T)() const if (is(Unqual!T == Duration))
862     {
863         auto total = days * 86400_000_000L +
864             hours * 3600_000_000L +
865             mins * 60_000_000L +
866             secs * 1_000_000L +
867             usecs;
868         return cast(T)dur!"usecs"(negative ? -total : total);
869     }
870 
871     auto to(T)() const if (is(Unqual!T == TimeOfDay))
872     {
873         return cast(T)TimeOfDay(hours, mins, secs);
874      }
875 
876     static MySQLTime from(Duration duration)
877     {
878         MySQLTime time;
879         duration.abs.split!("days", "hours", "minutes", "seconds", "usecs")(time.days, time.hours, time.mins, time.secs, time.usecs);
880         time.negative = duration.isNegative ? 1 : 0;
881         return time;
882     }
883 
884     static MySQLTime from(TimeOfDay tod)
885     {
886         MySQLTime time;
887         time.hours = tod.hour;
888         time.mins = tod.minute;
889         time.secs = tod.second;
890         return time;
891     }
892 }
893 
894 void putMySQLTime(ref OutputPacket packet, in MySQLTime time)
895 {
896     if (time.days || time.hours || time.mins || time.mins || time.usecs)
897     {
898         auto usecs = time.usecs != 0;
899         packet.put!ubyte(usecs ? 12 : 8);
900         packet.put!ubyte(time.negative);
901         packet.put!uint(time.days);
902         packet.put!ubyte(time.hours);
903         packet.put!ubyte(time.mins);
904         packet.put!ubyte(time.secs);
905         if (usecs)
906             packet.put!uint(time.usecs);
907     }
908     else
909     {
910         packet.put!ubyte(0);
911     }
912 }
913 
914 auto eatMySQLTime(ref InputPacket packet)
915 {
916     MySQLTime time;
917     switch(packet.eat!ubyte)
918     {
919         case 12:
920             time.negative = packet.eat!ubyte;
921             time.days = packet.eat!uint;
922             time.hours = packet.eat!ubyte;
923             time.mins = packet.eat!ubyte;
924             time.secs = packet.eat!ubyte;
925             time.usecs = packet.eat!uint;
926             break;
927         case 8:
928             time.negative = packet.eat!ubyte;
929             time.days = packet.eat!uint;
930             time.hours = packet.eat!ubyte;
931             time.mins = packet.eat!ubyte;
932             time.secs = packet.eat!ubyte;
933             break;
934         case 0:
935             break;
936         default:
937             throw new MySQLProtocolException("Bad time struct format");
938     }
939 
940     return time;
941 }
942 
943 struct MySQLDateTime
944 {
945     ushort year;
946     ubyte month;
947     ubyte day;
948     ubyte hour;
949     ubyte min;
950     ubyte sec;
951     uint usec;
952 
953     bool valid() const
954     {
955         return month != 0;
956     }
957 
958     T to(T)() const if (is(Unqual!T == SysTime))
959     {
960         assert(valid());
961         return cast(T)SysTime(DateTime(year, month, day, hour, min, sec), usec.dur!"usecs", UTC());
962     }
963 
964     T to(T)() const if (is(Unqual!T == DateTime))
965     {
966         assert(valid());
967         return cast(T)DateTime(year, month, day, hour, min, sec);
968     }
969 
970     T to(T)() const if (is(T == Date))
971     {
972         assert(valid());
973         return cast(T)Date(year, month, day);
974     }
975 
976     T to(T)() const if (is(Unqual!T == TimeOfDay))
977     {
978         return cast(T)TimeOfDay(hour, min, sec);
979     }
980 
981     static MySQLDateTime from(SysTime sysTime)
982     {
983         MySQLDateTime time;
984 
985         auto dateTime = cast(DateTime)sysTime;
986         time.year = dateTime.year;
987         time.month = dateTime.month;
988         time.day = dateTime.day;
989         time.hour = dateTime.hour;
990         time.min = dateTime.minute;
991         time.sec = dateTime.second;
992         time.usec = cast(int)sysTime.fracSecs.total!"usecs";
993 
994         return time;
995     }
996 
997     static MySQLDateTime from(DateTime dateTime)
998     {
999         MySQLDateTime time;
1000 
1001         time.year = dateTime.year;
1002         time.month = dateTime.month;
1003         time.day = dateTime.day;
1004         time.hour = dateTime.hour;
1005         time.min = dateTime.minute;
1006         time.sec = dateTime.second;
1007 
1008         return time;
1009     }
1010 
1011     static MySQLDateTime from(Date date)
1012     {
1013         MySQLDateTime time;
1014 
1015         time.year = date.year;
1016         time.month = date.month;
1017         time.day = date.day;
1018 
1019         return time;
1020     }
1021 }
1022 
1023 void putMySQLDateTime(ref OutputPacket packet, in MySQLDateTime time)
1024 {
1025     auto marker = packet.marker!ubyte;
1026     ubyte length;
1027 
1028     if (time.year || time.month || time.day)
1029     {
1030         length = 4;
1031         packet.put!ushort(time.year);
1032         packet.put!ubyte(time.month);
1033         packet.put!ubyte(time.day);
1034 
1035         if (time.hour || time.min || time.sec || time.usec)
1036         {
1037             length = 7;
1038             packet.put!ubyte(time.hour);
1039             packet.put!ubyte(time.min);
1040             packet.put!ubyte(time.sec);
1041 
1042             if (time.usec)
1043             {
1044                 length = 11;
1045                 packet.put!uint(time.usec);
1046             }
1047         }
1048     }
1049 
1050     packet.put!ubyte(marker, length);
1051 }
1052 
1053 auto eatMySQLDateTime(ref InputPacket packet)
1054 {
1055     MySQLDateTime time;
1056     switch(packet.eat!ubyte)
1057     {
1058         case 11:
1059             time.year = packet.eat!ushort;
1060             time.month = packet.eat!ubyte;
1061             time.day = packet.eat!ubyte;
1062             time.hour = packet.eat!ubyte;
1063             time.min = packet.eat!ubyte;
1064             time.sec = packet.eat!ubyte;
1065             time.usec = packet.eat!uint;
1066             break;
1067         case 7:
1068             time.year = packet.eat!ushort;
1069             time.month = packet.eat!ubyte;
1070             time.day = packet.eat!ubyte;
1071             time.hour = packet.eat!ubyte;
1072             time.min = packet.eat!ubyte;
1073             time.sec = packet.eat!ubyte;
1074             break;
1075         case 4:
1076             time.year = packet.eat!ushort;
1077             time.month = packet.eat!ubyte;
1078             time.day = packet.eat!ubyte;
1079             break;
1080         case 0:
1081             break;
1082         default:
1083             throw new MySQLProtocolException("Bad datetime struct format");
1084     }
1085 
1086     return time;
1087 }
1088 
1089 private void skip(ref const(char)[] x, char ch)
1090 {
1091     if (x.length && (x.ptr[0] == ch))
1092     {
1093         x = x[1..$];
1094     }
1095     else
1096     {
1097         throw new MySQLProtocolException("Bad datetime string format");
1098     }
1099 }
1100 
1101 auto parseMySQLTime(const(char)[] x)
1102 {
1103     MySQLTime time;
1104 
1105     auto hours = x.parse!int;
1106     if (hours < 0)
1107     {
1108         time.negative = 1;
1109         hours = -hours;
1110     }
1111     time.days = hours / 24;
1112     time.hours = cast(ubyte)(hours % 24);
1113     x.skip(':');
1114     time.mins = x.parse!ubyte;
1115     x.skip(':');
1116     time.secs = x.parse!ubyte;
1117     if (x.length)
1118     {
1119         x.skip('.');
1120         time.usecs = x.parse!uint;
1121         switch (6 - max(6, x.length))
1122         {
1123             case 0: break;
1124             case 1: time.usecs *= 10; break;
1125             case 2: time.usecs *= 100; break;
1126             case 3: time.usecs *= 1_000; break;
1127             case 4: time.usecs *= 10_000; break;
1128             case 5: time.usecs *= 100_000; break;
1129             default: assert("Bad datetime string format"); break;
1130         }
1131     }
1132 
1133     return time;
1134 }
1135 
1136 auto parseMySQLDateTime(const(char)[] x)
1137 {
1138     MySQLDateTime time;
1139 
1140     time.year = x.parse!ushort;
1141     x.skip('-');
1142     time.month = x.parse!ubyte;
1143     x.skip('-');
1144     time.day = x.parse!ubyte;
1145     if (x.length)
1146     {
1147         x.skip(' ');
1148         time.hour = x.parse!ubyte;
1149         x.skip(':');
1150         time.min = x.parse!ubyte;
1151         x.skip(':');
1152         time.sec = x.parse!ubyte;
1153 
1154         if (x.length)
1155         {
1156             x.skip('.');
1157             time.usec = x.parse!uint;
1158             switch (6 - max(6, x.length))
1159             {
1160                 case 0: break;
1161                 case 1: time.usec *= 10; break;
1162                 case 2: time.usec *= 100; break;
1163                 case 3: time.usec *= 1_000; break;
1164                 case 4: time.usec *= 10_000; break;
1165                 case 5: time.usec *= 100_000; break;
1166                 default: assert("Bad datetime string format"); break;
1167             }
1168         }
1169     }
1170 
1171     return time;
1172 }
1173 
1174 void eatValue(ref InputPacket packet, ref const MySQLColumn column, ref MySQLValue value)
1175 {
1176     auto signed = (column.flags & FieldFlags.UNSIGNED_FLAG) == 0;
1177     final switch(column.type) with (ColumnTypes)
1178     {
1179         case MYSQL_TYPE_NULL:
1180             value = MySQLValue(column.name, column.type, signed, null, 0);
1181             break;
1182         case MYSQL_TYPE_TINY:
1183             auto x = packet.eat!ubyte;
1184             value = MySQLValue(column.name, column.type, signed, &x, 1);
1185             break;
1186         case MYSQL_TYPE_YEAR:
1187         case MYSQL_TYPE_SHORT:
1188             auto x = packet.eat!ushort;
1189             value = MySQLValue(column.name, column.type, signed, &x, 2);
1190             break;
1191         case MYSQL_TYPE_INT24:
1192         case MYSQL_TYPE_LONG:
1193             auto x = packet.eat!uint;
1194             value = MySQLValue(column.name, column.type, signed, &x, 4);
1195             break;
1196         case MYSQL_TYPE_DOUBLE:
1197         case MYSQL_TYPE_LONGLONG:
1198             auto x = packet.eat!ulong;
1199             value = MySQLValue(column.name, column.type, signed, &x, 8);
1200             break;
1201         case MYSQL_TYPE_FLOAT:
1202             auto x = packet.eat!float;
1203             value = MySQLValue(column.name, column.type, signed, &x, 4);
1204             break;
1205         case MYSQL_TYPE_SET:
1206         case MYSQL_TYPE_ENUM:
1207         case MYSQL_TYPE_VARCHAR:
1208         case MYSQL_TYPE_VAR_STRING:
1209         case MYSQL_TYPE_STRING:
1210         case MYSQL_TYPE_JSON:
1211         case MYSQL_TYPE_NEWDECIMAL:
1212         case MYSQL_TYPE_DECIMAL:
1213             auto x = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc());
1214             value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1215             break;
1216         case MYSQL_TYPE_BIT:
1217         case MYSQL_TYPE_TINY_BLOB:
1218         case MYSQL_TYPE_MEDIUM_BLOB:
1219         case MYSQL_TYPE_LONG_BLOB:
1220         case MYSQL_TYPE_BLOB:
1221         case MYSQL_TYPE_GEOMETRY:
1222             auto x = packet.eat!(const(ubyte)[])(cast(size_t)packet.eatLenEnc());
1223             value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1224             break;
1225         case MYSQL_TYPE_TIME:
1226         case MYSQL_TYPE_TIME2:
1227             auto x = eatMySQLTime(packet);
1228             value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1229             break;
1230         case MYSQL_TYPE_DATE:
1231         case MYSQL_TYPE_NEWDATE:
1232         case MYSQL_TYPE_DATETIME:
1233         case MYSQL_TYPE_DATETIME2:
1234         case MYSQL_TYPE_TIMESTAMP:
1235         case MYSQL_TYPE_TIMESTAMP2:
1236             auto x = eatMySQLDateTime(packet);
1237             value = x.valid() ? MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof) : MySQLValue(column.name, ColumnTypes.MYSQL_TYPE_NULL, signed, null, 0);
1238             break;
1239     }
1240 }
1241 
1242 void eatValueText(ref InputPacket packet, ref const MySQLColumn column, ref MySQLValue value)
1243 {
1244     auto signed = (column.flags & FieldFlags.UNSIGNED_FLAG) == 0;
1245     auto svalue = (column.type != ColumnTypes.MYSQL_TYPE_NULL) ? cast(string)(packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc())) : string.init;
1246     final switch(column.type) with (ColumnTypes)
1247     {
1248         case MYSQL_TYPE_NULL:
1249             value = MySQLValue(column.name, column.type, signed, null, 0);
1250             break;
1251         case MYSQL_TYPE_TINY:
1252             auto x = (svalue.ptr[0] == '-') ? cast(ubyte)(-1 * svalue[1..$].to!byte) : svalue.to!ubyte;
1253             value = MySQLValue(column.name, column.type, signed, &x, 1);
1254             break;
1255         case MYSQL_TYPE_YEAR:
1256         case MYSQL_TYPE_SHORT:
1257             auto x = (svalue.ptr[0] == '-') ? cast(ushort)(-1 * svalue[1..$].to!short) : svalue.to!ushort;
1258             value = MySQLValue(column.name, column.type, signed, &x, 2);
1259             break;
1260         case MYSQL_TYPE_INT24:
1261         case MYSQL_TYPE_LONG:
1262             auto x = (svalue.ptr[0] == '-') ? cast(uint)(-svalue[1..$].to!int) : svalue.to!uint;
1263             value = MySQLValue(column.name, column.type, signed, &x, 4);
1264             break;
1265         case MYSQL_TYPE_LONGLONG:
1266             auto x = (svalue.ptr[0] == '-') ? cast(ulong)(-svalue[1..$].to!long) : svalue.to!ulong;
1267             value = MySQLValue(column.name, column.type, signed, &x, 8);
1268             break;
1269         case MYSQL_TYPE_DOUBLE:
1270             auto x = svalue.to!double;
1271             value = MySQLValue(column.name, column.type, signed, &x, 8);
1272             break;
1273         case MYSQL_TYPE_FLOAT:
1274             auto x = svalue.to!float;
1275             value = MySQLValue(column.name, column.type, signed, &x, 4);
1276             break;
1277         case MYSQL_TYPE_SET:
1278         case MYSQL_TYPE_ENUM:
1279         case MYSQL_TYPE_VARCHAR:
1280         case MYSQL_TYPE_VAR_STRING:
1281         case MYSQL_TYPE_STRING:
1282         case MYSQL_TYPE_JSON:
1283         case MYSQL_TYPE_NEWDECIMAL:
1284         case MYSQL_TYPE_DECIMAL:
1285             value = MySQLValue(column.name, column.type, signed, &svalue, typeof(svalue).sizeof);
1286             break;
1287         case MYSQL_TYPE_BIT:
1288         case MYSQL_TYPE_TINY_BLOB:
1289         case MYSQL_TYPE_MEDIUM_BLOB:
1290         case MYSQL_TYPE_LONG_BLOB:
1291         case MYSQL_TYPE_BLOB:
1292         case MYSQL_TYPE_GEOMETRY:
1293             value = MySQLValue(column.name, column.type, signed, &svalue, typeof(svalue).sizeof);
1294             break;
1295         case MYSQL_TYPE_TIME:
1296         case MYSQL_TYPE_TIME2:
1297             auto x = parseMySQLTime(svalue);
1298             value = MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof);
1299             break;
1300         case MYSQL_TYPE_DATE:
1301         case MYSQL_TYPE_NEWDATE:
1302         case MYSQL_TYPE_DATETIME:
1303         case MYSQL_TYPE_DATETIME2:
1304         case MYSQL_TYPE_TIMESTAMP:
1305         case MYSQL_TYPE_TIMESTAMP2:
1306             auto x = parseMySQLDateTime(svalue);
1307             value = x.valid() ? MySQLValue(column.name, column.type, signed, &x, typeof(x).sizeof) : MySQLValue(column.name, ColumnTypes.MYSQL_TYPE_NULL, signed, null, 0);
1308             break;
1309     }
1310 }
1311 
1312 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Variant))
1313 {
1314     if (!value.hasValue)
1315     {
1316         putValueType(packet, MySQLValue(null));
1317     }
1318     else if (value.type == typeid(string))
1319     {
1320         putValueType(packet, value.get!string);
1321     }
1322     else if (value.type == typeid(dstring))
1323     {
1324         putValueType(packet, value.get!dstring);
1325     }
1326     else if (value.type == typeid(wstring))
1327     {
1328         putValueType(packet, value.get!wstring);
1329     }
1330     else if (value.type == typeid(short))
1331     {
1332         putValueType(packet, value.get!short);
1333     }
1334     else if (value.type == typeid(int))
1335     {
1336         putValueType(packet, value.get!int);
1337     }
1338     else if (value.type == typeid(long))
1339     {
1340         putValueType(packet, value.get!long);
1341     }
1342     else if (value.type == typeid(ushort))
1343     {
1344         putValueType(packet, value.get!ushort);
1345     }
1346     else if (value.type == typeid(uint))
1347     {
1348         putValueType(packet, value.get!uint);
1349     }
1350     else if (value.type == typeid(ulong))
1351     {
1352         putValueType(packet, value.get!ulong);
1353     }
1354     else if (value.type == typeid(float))
1355     {
1356         putValueType(packet, value.get!float);
1357     }
1358     else if (value.type == typeid(double))
1359     {
1360         putValueType(packet, value.get!double);
1361     }
1362     else if (value.type == typeid(byte))
1363     {
1364         putValueType(packet, value.get!byte);
1365     }
1366     else if (value.type == typeid(ubyte))
1367     {
1368         putValueType(packet, value.get!ubyte);
1369     }
1370     else if (value.type == typeid(bool))
1371     {
1372         putValueType(packet, value.get!bool);
1373     }
1374     else if (value.type == typeid(Date))
1375     {
1376         putValueType(packet, value.get!Date);
1377     }
1378     else if (value.type == typeid(DateTime))
1379     {
1380         putValueType(packet, value.get!DateTime);
1381     }
1382     else if (value.type == typeid(SysTime))
1383     {
1384         putValueType(packet, value.get!SysTime);
1385     }
1386     else if (value.type == typeid(Duration))
1387     {
1388         putValueType(packet, value.get!Duration);
1389     }
1390     else if (value.type == typeid(MySQLBinary))
1391     {
1392         putValueType(packet, value.get!MySQLBinary);
1393     }
1394     else if (value.type == typeid(MySQLValue))
1395     {
1396         putValueType(packet, value.get!MySQLValue);
1397     }
1398     else
1399     {
1400         throw new Exception("exists unkown type at Variant[]: " ~ value.type.toString());
1401     }
1402 }
1403 
1404 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Variant))
1405 {
1406     if (!value.hasValue)
1407     {
1408         putValue(packet, MySQLValue(null));
1409     }
1410     else if (value.type == typeid(string))
1411     {
1412         putValue(packet, value.get!string);
1413     }
1414     else if (value.type == typeid(dstring))
1415     {
1416         putValue(packet, value.get!dstring);
1417     }
1418     else if (value.type == typeid(wstring))
1419     {
1420         putValue(packet, value.get!wstring);
1421     }
1422     else if (value.type == typeid(short))
1423     {
1424         putValue(packet, value.get!short);
1425     }
1426     else if (value.type == typeid(int))
1427     {
1428         putValue(packet, value.get!int);
1429     }
1430     else if (value.type == typeid(long))
1431     {
1432         putValue(packet, value.get!long);
1433     }
1434     else if (value.type == typeid(ushort))
1435     {
1436         putValue(packet, value.get!ushort);
1437     }
1438     else if (value.type == typeid(uint)){
1439         putValue(packet, value.get!uint);
1440     }
1441     else if (value.type == typeid(ulong))
1442     {
1443         putValue(packet, value.get!ulong);
1444     }
1445     else if (value.type == typeid(float))
1446     {
1447         putValue(packet, value.get!float);
1448     }
1449     else if (value.type == typeid(double))
1450     {
1451         putValue(packet, value.get!double);
1452     }
1453     else if (value.type == typeid(byte))
1454     {
1455         putValue(packet, value.get!byte);
1456     }
1457     else if (value.type == typeid(ubyte))
1458     {
1459         putValue(packet, value.get!ubyte);
1460     }
1461     else if (value.type == typeid(bool))
1462     {
1463         putValue(packet, value.get!bool);
1464     }
1465     else if (value.type == typeid(Date))
1466     {
1467         putValue(packet, value.get!Date);
1468     }
1469     else if (value.type == typeid(DateTime))
1470     {
1471         putValue(packet, value.get!DateTime);
1472     }
1473     else if (value.type == typeid(SysTime))
1474     {
1475         putValue(packet, value.get!SysTime);
1476     }
1477     else if (value.type == typeid(Duration))
1478     {
1479         putValue(packet, value.get!Duration);
1480     }
1481     else if (value.type == typeid(MySQLBinary))
1482     {
1483         putValue(packet, value.get!MySQLBinary);
1484     }
1485     else if (value.type == typeid(MySQLValue))
1486     {
1487         putValue(packet, value.get!MySQLValue);
1488     }
1489     else
1490     {
1491         throw new Exception("exists unkown type at Variant[]: " ~ value.type.toString());
1492     }
1493 }
1494 
1495 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime))
1496 {
1497     packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIMESTAMP);
1498     packet.put!ubyte(0x80);
1499 }
1500 
1501 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Date) || is(Unqual!T == DateTime) || is(Unqual!T == SysTime))
1502 {
1503     putMySQLDateTime(packet, MySQLDateTime.from(value));
1504 }
1505 
1506 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Duration))
1507 {
1508     packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TIME);
1509     packet.put!ubyte(0x00);
1510 }
1511 
1512 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == Duration))
1513 {
1514     putMySQLTime(packet, MySQLTime.from(value));
1515 }
1516 
1517 void putValueType(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T)
1518 {
1519     alias UT = Unqual!T;
1520 
1521     enum ubyte sign = isUnsigned!UT ? 0x80 : 0x00;
1522 
1523     static if (is(UT == long) || is(UT == ulong))
1524     {
1525         packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONGLONG);
1526         packet.put!ubyte(sign);
1527     }
1528     else static if (is(UT == int) || is(UT == uint) || is(UT == dchar))
1529     {
1530         packet.put!ubyte(ColumnTypes.MYSQL_TYPE_LONG);
1531         packet.put!ubyte(sign);
1532     }
1533     else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar))
1534     {
1535         packet.put!ubyte(ColumnTypes.MYSQL_TYPE_SHORT);
1536         packet.put!ubyte(sign);
1537     }
1538     else {
1539         packet.put!ubyte(ColumnTypes.MYSQL_TYPE_TINY);
1540         packet.put!ubyte(sign);
1541     }
1542 }
1543 
1544 void putValue(T)(ref OutputPacket packet, T value) if (isIntegral!T || isBoolean!T)
1545 {
1546     alias UT = Unqual!T;
1547 
1548     static if (is(UT == long) || is(UT == ulong))
1549     {
1550         packet.put!ulong(value);
1551     }
1552     else static if (is(UT == int) || is(UT == uint) || is(UT == dchar))
1553     {
1554         packet.put!uint(value);
1555     }
1556     else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar))
1557     {
1558         packet.put!ushort(value);
1559     }
1560     else
1561     {
1562         packet.put!ubyte(value);
1563     }
1564 }
1565 
1566 void putValueType(T)(ref OutputPacket packet, T value) if (isFloatingPoint!T)
1567 {
1568     alias UT = Unqual!T;
1569 
1570     enum ubyte sign = 0x00;
1571 
1572     static if (is(UT == float))
1573     {
1574         packet.put!ubyte(ColumnTypes.MYSQL_TYPE_FLOAT);
1575         packet.put!ubyte(sign);
1576     }
1577     else
1578     {
1579         packet.put!ubyte(ColumnTypes.MYSQL_TYPE_DOUBLE);
1580         packet.put!ubyte(sign);
1581     }
1582 }
1583 
1584 void putValue(T)(ref OutputPacket packet, T value) if (isFloatingPoint!T)
1585 {
1586     alias UT = Unqual!T;
1587 
1588     static if (is(UT == float))
1589     {
1590         packet.put!float(value);
1591     }
1592     else
1593     {
1594         packet.put!double(cast(double)value);
1595     }
1596 }
1597 
1598 void putValueType(T)(ref OutputPacket packet, T value) if (isSomeString!(OriginalType!T))
1599 {
1600     packet.put!ubyte(ColumnTypes.MYSQL_TYPE_STRING);
1601     packet.put!ubyte(0x80);
1602 }
1603 
1604 void putValue(T)(ref OutputPacket packet, T value) if (isSomeString!(OriginalType!T))
1605 {
1606     ulong size = value.length * T.init[0].sizeof;
1607     packet.putLenEnc(size);
1608     packet.put(value);
1609 }
1610 
1611 void putValueType(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!(OriginalType!T))
1612 {
1613     foreach(ref item; value)
1614         putValueType(packet, item);
1615 }
1616 
1617 void putValue(T)(ref OutputPacket packet, T value) if (isArray!T && !isSomeString!(OriginalType!T))
1618 {
1619     foreach(ref item; value)
1620         putValue(packet, item);
1621 }
1622 
1623 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLBinary))
1624 {
1625     packet.put!ubyte(ColumnTypes.MYSQL_TYPE_BLOB);
1626     packet.put!ubyte(0x80);
1627 }
1628 
1629 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLBinary))
1630 {
1631     ulong size = value.length;
1632     packet.putLenEnc(size);
1633     packet.put(value.data);
1634 }
1635 
1636 void putValueType(T)(ref OutputPacket packet, T value) if(is(Unqual!T == MySQLValue))
1637 {
1638     packet.put!ubyte(value.type_);
1639     packet.put!ubyte(value.sign_);
1640 }
1641 
1642 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == MySQLValue))
1643 {
1644     final switch(value.type) with (ColumnTypes)
1645     {
1646         case MYSQL_TYPE_NULL:
1647             break;
1648         case MYSQL_TYPE_TINY:
1649             packet.put!ubyte(*cast(ubyte*)value.buffer_.ptr);
1650             break;
1651         case MYSQL_TYPE_YEAR:
1652         case MYSQL_TYPE_SHORT:
1653             packet.put!ushort(*cast(ushort*)value.buffer_.ptr);
1654             break;
1655         case MYSQL_TYPE_INT24:
1656         case MYSQL_TYPE_LONG:
1657             packet.put!uint(*cast(uint*)value.buffer_.ptr);
1658             break;
1659         case MYSQL_TYPE_LONGLONG:
1660             packet.put!ulong(*cast(ulong*)value.buffer_.ptr);
1661             break;
1662         case MYSQL_TYPE_DOUBLE:
1663             packet.put!double(*cast(double*)value.buffer_.ptr);
1664             break;
1665         case MYSQL_TYPE_FLOAT:
1666             packet.put!float(*cast(float*)value.buffer_.ptr);
1667             break;
1668         case MYSQL_TYPE_SET:
1669         case MYSQL_TYPE_ENUM:
1670         case MYSQL_TYPE_VARCHAR:
1671         case MYSQL_TYPE_VAR_STRING:
1672         case MYSQL_TYPE_STRING:
1673         case MYSQL_TYPE_JSON:
1674         case MYSQL_TYPE_NEWDECIMAL:
1675         case MYSQL_TYPE_DECIMAL:
1676         case MYSQL_TYPE_BIT:
1677         case MYSQL_TYPE_TINY_BLOB:
1678         case MYSQL_TYPE_MEDIUM_BLOB:
1679         case MYSQL_TYPE_LONG_BLOB:
1680         case MYSQL_TYPE_BLOB:
1681         case MYSQL_TYPE_GEOMETRY:
1682             packet.putLenEnc((*cast(ubyte[]*)value.buffer_.ptr).length);
1683             packet.put(*cast(ubyte[]*)value.buffer_.ptr);
1684             break;
1685         case MYSQL_TYPE_TIME:
1686         case MYSQL_TYPE_TIME2:
1687             packet.putMySQLTime(*cast(MySQLTime*)value.buffer_.ptr);
1688             break;
1689         case MYSQL_TYPE_DATE:
1690         case MYSQL_TYPE_NEWDATE:
1691         case MYSQL_TYPE_DATETIME:
1692         case MYSQL_TYPE_DATETIME2:
1693         case MYSQL_TYPE_TIMESTAMP:
1694         case MYSQL_TYPE_TIMESTAMP2:
1695             packet.putMySQLDateTime(*cast(MySQLDateTime*)value.buffer_.ptr);
1696             break;
1697     }
1698 }
1699 
1700 void putValueType(T)(ref OutputPacket packet, T value) if (is(Unqual!T == typeof(null)))
1701 {
1702     packet.put!ubyte(ColumnTypes.MYSQL_TYPE_NULL);
1703     packet.put!ubyte(0x00);
1704 }
1705 
1706 void putValue(T)(ref OutputPacket packet, T value) if (is(Unqual!T == typeof(null)))
1707 {
1708 }
1709 
1710 void putValueType(T)(ref OutputPacket packet, T value) if (isInstanceOf!(Nullable, T) || isInstanceOf!(NullableRef, T))
1711 {
1712     if (value.isNull)
1713     {
1714         putValueType(packet, null);
1715     }
1716     else
1717     {
1718         putValueType(packet, value.get);
1719     }
1720 }
1721 
1722 void putValue(T)(ref OutputPacket packet, T value) if (isInstanceOf!(Nullable, T) || isInstanceOf!(NullableRef, T))
1723 {
1724     if (value.isNull)
1725     {
1726         putValue(packet, null);
1727     }
1728     else
1729     {
1730         putValue(packet, value.get);
1731     }
1732 }