1 module sqlbuilder.dialect.sqlite;
2 
3 public import sqlbuilder.dialect.common : where, changed, limit, orderBy,
4            groupBy, exprCol, as, withoutAs, concat, count, ascend, descend,
5            Parameter, simplifyConditions;
6 
7 private import sqlbuilder.dialect.common : SQLImpl, append;
8 
9 import sqlbuilder.types;
10 import sqlbuilder.traits;
11 import sqlbuilder.util;
12 
13 import std.typecons : Nullable, nullable;
14 import std.datetime : DateTime, Date, SysTime;
15 import std.traits;
16 import std.conv;
17 
18 alias Blob = const(ubyte)[];
19 
20 // define a simple tagged union for the parameters. We don't need anything
21 // complex, because none of the types are complex.
22 struct PType
23 {
24     private import std.range;
25     enum Tag
26     {
27         Integer,
28         Float,
29         Text,
30         Blob,
31         Null
32     }
33 
34     private
35     {
36         Tag _tag;
37         union _Value
38         {
39             long longData; // tag 0
40             double doubleData; // tag 1
41             string stringData; // tag 2
42             Blob blobData; // tag 3
43         }
44         _Value _value;
45     }
46 
47     this(T)(T val)
48     {
49         opAssign(val);
50     }
51 
52     Tag tag()
53     {
54         return _tag;
55     }
56 
57     void opAssign(PType val)
58     {
59         this._tag = val._tag;
60         this._value = val._value;
61     }
62 
63     void opAssign(T)(T val)
64     {
65         static if(is(T == Nullable!U, U))
66         {
67             if(val.isNull)
68             {
69                 _value.blobData = null;
70                 _tag = Tag.Null;
71             }
72             else
73                 opAssign(val.get);
74         }
75         else static if(is(T == typeof(null)))
76         {
77             // zero out the union
78             _value.blobData = null;
79             _tag = Tag.Null;
80         }
81         else static if(is(T == DateTime) || is(T == SysTime) || is(T == Date))
82         {
83             _value.stringData = val.toISOExtString;
84             _tag = Tag.Text;
85         }
86         else static if(canStringMarshal!T || isSomeString!T)
87         {
88             _value.stringData = val.to!string;
89             _tag = Tag.Text;
90         }
91         else static if (isIntegral!T || isSomeChar!T || isBoolean!T)
92         {
93             _value.longData = val.to!long;
94             _tag = Tag.Integer;
95         }
96         else static if (isFloatingPoint!T)
97         {
98             _value.doubleData = val;
99             _tag = Tag.Float;
100         }
101         else static if(is(immutable(T) == immutable(Blob)))
102         {
103             _value.blobData = val;
104             _tag = Tag.Blob;
105         }
106         else
107         {
108             static assert(false, "Cannot assign type " ~ T.stringof ~ " to PType");
109         }
110     }
111 
112     bool isNull()
113     {
114         return _tag == Tag.Null;
115     }
116 
117     T get(T)()
118     {
119         static if(is(T == Nullable!U, U))
120         {
121             if(isNull)
122                 return T.init;
123             else
124                 return T(get!U());
125         }
126         else
127         {
128             void enforceTag(Tag required)
129             {
130                 if(_tag != required)
131                     throw new Exception("Inappropriate tag type for " ~ T.stringof ~ ": " ~ _tag.to!string);
132             }
133             static if(canStringMarshal!T || isSomeString!T)
134             {
135                 enforceTag(Tag.Text);
136                 return parseFromString!T(_value.stringData);
137             }
138             else static if (isIntegral!T || isSomeChar!T || isBoolean!T)
139             {
140                 enforceTag(Tag.Integer);
141                 return _value.longData.to!T;
142             }
143             else static if (isFloatingPoint!T)
144             {
145                 // TODO: should we support integer values also?
146                 enforceTag(Tag.Float);
147                 return _value.doubleData;
148             }
149             else static if(is(T == Blob))
150             {
151                 enforceTag(Tag.Blob);
152                 return _value.blobData;
153             }
154             else
155                 static assert(false, "Cannot get type " ~ T.stringof ~ " from PType");
156         }
157     }
158 
159     void toString(Out)(ref Out output) if (isOutputRange!(Out, dchar))
160     {
161         import std.format;
162         with(Tag) final switch(_tag)
163         {
164             case Integer:
165                 formattedWrite(output, "%s", _value.longData);
166                 break;
167             case Float:
168                 formattedWrite(output, "%s", _value.doubleData);
169                 break;
170             case Text:
171                 formattedWrite(output, "%s", _value.stringData);
172                 break;
173             case Blob:
174                 formattedWrite(output, "[%(0x%02x, %)]", _value.blobData);
175                 break;
176             case Null:
177                 put(output, "null");
178                 break;
179         }
180     }
181 
182     string toString()
183     {
184         import std.array;
185         Appender!string app;
186         toString(app);
187         return app.data;
188     }
189 }
190 
191 private PType toPType(T)(T val)
192 {
193     static if(is(T : Nullable!U, U))
194     {
195         if(val.isNull)
196             return PType(null);
197         return toPType(val.get);
198     }
199     else static if(is(typeof(val.dbValue)))
200     {
201         return toPType(val.dbValue);
202     }
203     else static if(is(T == enum))
204     {
205         import std.traits : OriginalType;
206         return toPType(cast(OriginalType!T)val);
207     }
208     else
209     {
210         return PType(val);
211     }
212 }
213 
214 auto param(T)(T val)
215 {
216     import std.range : only;
217     return Parameter!PType(only(val.toPType));
218 }
219 
220 auto optional(T)(T val, bool isValid)
221 {
222     import std.range : only;
223     return Parameter!(PType, true)(only(val.toPType), isValid);
224 }
225 
226 alias _impl = SQLImpl!(PType, param, true);
227 
228 static foreach(f; __traits(allMembers, _impl))
229     mixin("alias " ~ f ~ " = _impl." ~ f ~ ";");
230 
231 alias UntypedQuery = typeof(select());
232 
233 private void sqlPut(bool includeObjectSeparators, bool includeTableQualifiers, App)(ref App app, ExprString expr)
234 {
235     BitStack andor;
236     andor.push(true); // default to and
237     foreach(x; expr.data)
238         sqlPut!(includeObjectSeparators, includeTableQualifiers)(app, x, andor);
239 }
240 
241 private void sqlPut(bool includeObjectSeparators, bool includeTableQualifiers, App)(ref App app, ExprString expr, ref BitStack andor)
242 {
243     foreach(x; expr.data)
244         sqlPut!(includeObjectSeparators, includeTableQualifiers)(app, x, andor);
245 }
246 
247 private void sqlPut(bool includeObjectSeparators, bool includeTableQualifiers, App)(ref App app, string x)
248 {
249     BitStack andor;
250     andor.push(true); // default to and
251     sqlPut!(includeObjectSeparators, includeTableQualifiers)(app, x, andor);
252 }
253 
254 private void sqlPut(bool includeObjectSeparators, bool includeTableQualifiers, App)(ref App app, string x, ref BitStack andor)
255 {
256     import std.range : put;
257     with(Spec) switch(getSpec(x))
258     {
259     case id:
260         put(app, '"');
261         put(app, x[2 .. $]);
262         put(app, '"');
263         break;
264     case tableid:
265         static if(includeTableQualifiers)
266         {
267             put(app, '"');
268             put(app, x[2 .. $]);
269             put(app, `".`);
270         }
271         break;
272     case leftJoin:
273         put(app, " LEFT JOIN ");
274         break;
275     case rightJoin:
276         put(app, " RIGHT JOIN ");
277         break;
278     case innerJoin:
279         put(app, " INNER JOIN ");
280         break;
281     case outerJoin:
282         put(app, " OUTER JOIN ");
283         break;
284     case param:
285         put(app, '?');
286         break;
287     case none:
288         put(app, x);
289         break;
290     case objend:
291         static if(includeObjectSeparators)
292             put(app, `, 1 AS "_objend"`);
293         break;
294     case separator:
295         if(andor.peek)
296             put(app, ") AND (");
297         else
298             put(app, ") OR (");
299         break;
300     case beginAnd:
301         andor.push(true);
302         // 2 parentheses, one for the group, and one for the first term
303         put(app, "((");
304         break;
305     case beginOr:
306         andor.push(false);
307         put(app, "((");
308         break;
309     case endGroup:
310         if(!andor.length)
311             throw new Exception("Error, inconsistent groupings");
312         andor.pop;
313         put(app, "))");
314         break;
315     default:
316         throw new Exception("Unknown spec in: " ~ x);
317     }
318 }
319 
320 auto params(T)(T t)
321 {
322     static if(isQuery!T)
323     {
324         return paramsImpl!("fields", "joins", "conditions", "groups", "orders")(t);
325     }
326     else static if(is(T : Insert!P, P))
327     {
328         return paramsImpl!("colNames", "colValues")(t);
329     }
330     else static if(is(T : Update!P, P))
331     {
332         return paramsImpl!("joins", "settings", "conditions")(t);
333     }
334     else static if(is(T : Delete!P, P))
335     {
336         return paramsImpl!("joins", "conditions")(t);
337     }
338     else static assert("Unsupported type for params property: " ~ T.stringof);
339 }
340 
341 string sql(bool includeObjectSeparators = false, QP...)(Query!(QP) q)
342 {
343     import std.array : Appender;
344     import std.range : put;
345     import std.format : formattedWrite;
346     Appender!string app;
347     assert(q.fields.expr);
348     assert(q.joins.expr);
349 
350     // add a set of fragments given the prefix and the separator
351     void addFragment(SQLFragment!(q.ItemType) item, string prefix, string postfix = null)
352     {
353         BitStack bits;
354         bits.push(true);
355         if(item.expr)
356         {
357             put(app, prefix);
358             sqlPut!(includeObjectSeparators, true)(app, item.expr, bits);
359             if(postfix.length)
360                 put(app, postfix);
361         }
362     }
363 
364     // fields
365     addFragment(q.fields, "SELECT ");
366     // joins
367     addFragment(q.joins, " FROM ");
368     // CONDITIONS
369     addFragment(q.conditions, " WHERE (", ")");
370     // GROUP BY
371     addFragment(q.groups, " GROUP BY ");
372     // ORDER BY
373     addFragment(q.orders, " ORDER BY ");
374 
375     if(q.limitQty)
376     {
377         if(q.limitOffset)
378             formattedWrite(app, " LIMIT %s OFFSET %s", q.limitQty, q.limitOffset);
379         else
380             formattedWrite(app, " LIMIT %s", q.limitQty);
381     }
382 
383     return app.data;
384 }
385 
386 // return an sql statement that queries the total items from the given query
387 // (no limits, no offsets)
388 //
389 // The resulting query can be sent to fetch or fetchOne, and this will get a
390 // single ulong that counts the total rows.
391 
392 string sql(Item)(Insert!Item ins)
393 {
394     import std.array : Appender;
395     import std.range : put;
396     Appender!string app;
397     assert(ins.tableid.length);
398     assert(ins.colNames.expr);
399     assert(ins.colValues.expr);
400 
401     put(app, `INSERT INTO "`);
402     put(app, ins.tableid);
403     put(app, `" (`);
404     sqlPut!(false, false)(app, ins.colNames.expr);
405     put(app, ") VALUES (");
406     sqlPut!(false, true)(app, ins.colValues.expr);
407     put(app, ")");
408 
409     return app.data;
410 }
411 
412 string sql(Item)(Update!Item upd)
413 {
414     // MySQL is quite forgiving, so just include everything as it is written.
415     import std.array : Appender;
416     import std.range : put;
417     Appender!string app;
418     assert(upd.settings.expr);
419     assert(upd.joins.expr);
420 
421     // add a set of fragments given the prefix and the separator
422     void addFragment(SQLFragment!(upd.ItemType) item, string prefix, string postfix = null)
423     {
424         if(item.expr)
425         {
426             put(app, prefix);
427             sqlPut!(false, true)(app, item.expr);
428             if(postfix.length)
429                 put(app, postfix);
430         }
431     }
432 
433     // first table
434     put(app, "UPDATE ");
435     app.sqlPut!(false, true)(upd.joins.expr.data[0]); // add the existing table
436     // fields
437     addFragment(upd.settings, " SET ");
438 
439     // extra joins
440     if(upd.joins.expr.data.length > 1)
441     {
442         import std.algorithm : map;
443         // need to build an alternate expression string with "_" as the main table name.
444         static immutable string altTableName = "_".makeSpec(Spec.tableid);
445         auto altFrag = upd.joins;
446         auto origTable = upd.joins.expr.data[0];
447         altFrag.expr.data = [origTable, ` AS "_"`];
448         altFrag.expr.data.append(upd.joins.expr.data[1 .. $]
449                 .map!(x => getSpec(x) == Spec.tableid && x[2 .. $] == origTable[2 .. $] ? altTableName : x));
450         addFragment(altFrag, " FROM ");
451         put(app, ` WHERE "_"."id"=`);
452         app.sqlPut!(false, true)(origTable);
453         put(app, `."id"`);
454 
455         // CONDITIONS
456         addFragment(upd.conditions, " AND (", ")");
457     }
458     else
459     {
460         // CONDITIONS
461         addFragment(upd.conditions, " WHERE (", ")");
462     }
463 
464     return app.data;
465 }
466 
467 string sql(Item)(Delete!Item del)
468 {
469     // DELETE FROM table LEFT JOIN other tables.
470     import std.array : Appender;
471     import std.range : put;
472     Appender!string app;
473     assert(del.joins.expr);
474 
475     // add a set of fragments given the prefix and the separator
476     void addFragment(SQLFragment!(del.ItemType) item, string prefix, string postfix = null)
477     {
478         if(item.expr)
479         {
480             put(app, prefix);
481             sqlPut!(false, true)(app, item.expr);
482             if(postfix.length)
483                 put(app, postfix);
484         }
485     }
486 
487     put(app, "DELETE FROM ");
488     sqlPut!(false, true)(app, ExprString(del.joins.expr.data[0 .. 1]));
489     // if there is at least one join, we have to change the syntax
490     import std.algorithm : canFind;
491     if(del.joins.expr.data.canFind!(s => s.getSpec.isJoin))
492     {
493         // there are joins. Use a where select statement to find the rows to
494         // delete. Map based on rowids.
495         put(app, " WHERE rowid IN (SELECT ");
496         sqlPut!(false, true)(app, ExprString(del.joins.expr.data[0 .. 1]));
497         put(app, ".rowid ");
498         addFragment(del.joins, " FROM ");
499         addFragment(del.conditions, " WHERE (", ")");
500         put(app, ")");
501     }
502     else
503     {
504         // simple delete, no joins.
505         addFragment(del.conditions, " WHERE (", ")");
506     }
507 
508     return app.data;
509 }
510 
511 private template dbValueType(T)
512 {
513     static if(is(T : Nullable!U, U))
514         alias dbValueType = .dbValueType!U;
515     else static if(is(T : AllowNullType!Args, Args...))
516         alias dbValueType = .dbValueType!(T.type);
517     else static if(is(typeof(T.init.dbValue)))
518     {
519         alias dbValueType = typeof((){T val = T.init; return val.dbValue;}());
520     }
521     else static if(is(T == enum))
522     {
523         alias dbValueType = OriginalType!T;
524     }
525     else
526         alias dbValueType = T;
527 }
528 
529 private enum canStringMarshal(T) =
530   (is(typeof(T.init.toString((in char[]) {}))) || is(typeof(T.init.toString())))
531     && is(typeof(parseFromString!T));
532 
533 private T parseFromString(T)(string textData)
534 {
535     static if(isSomeString!T)
536         return textData.to!T;
537     // handle the timestamp types specially, these are very common DB types.
538     // TODO: handle all the time types
539     else static if(is(T == DateTime) || is(T == SysTime) || is(T == Date))
540         return T.fromISOExtString(textData);
541     else static if(is(typeof(T.fromString(textData))))
542         return T.fromString(textData);
543     else static if(is(typeof(to!T(textData))))
544         return textData.to!T;
545     else
546         static assert(false, "Cannot parse ", T, " from a string");
547 }
548 
549 // match the D type to a specific MySQL type
550 private template getFieldType(T)
551 {
552     alias RT = dbValueType!T;
553     static if(is(RT == T))
554     {
555         static if (isIntegral!T || isSomeChar!T || isBoolean!T)
556             enum getFieldType = "INTEGER";
557         else static if(canStringMarshal!T || isSomeString!T)
558             enum getFieldType = "TEXT";
559         else static if(isFloatingPoint!T)
560             enum getFieldType = "REAL";
561         else static if(is(immutable(T) == immutable(Blob)))
562             enum getFieldType = "BLOB";
563         else
564             static assert(0, "Unknown mapping for type " ~ T.stringof);
565     }
566     else
567         alias getFieldType = .getFieldType!RT;
568 }
569 
570 
571 // generate an SQL statement to insert a table definition.
572 template createTableSql(T, bool doForeignKeys = false, bool ifNotExists = false)
573 {
574     string generate()
575     {
576         import sqlbuilder.uda;
577         import sqlbuilder.traits;
578         auto result = `CREATE TABLE ` ~ (ifNotExists ? `IF NOT EXISTS ` : ``) ~
579             `"` ~ getTableName!T ~ `" (`;
580         int autoIncFields = 0;
581         foreach(field; FieldNameTuple!T)
582         {
583             alias fieldType = typeof(__traits(getMember, T, field));
584             // Relations are not columns to store in the DB.
585             static if(!hasUDA!(__traits(getMember, T, field), ignore) &&
586                       !is(fieldType == Relation))
587             {
588                 string name = result ~= `"`
589                     ~ getColumnName!(__traits(getMember, T, field)) ~ `" `;
590                 alias colTypeUDAs = getUDAs!(__traits(getMember, T, field), colType);
591                 static if(colTypeUDAs.length > 0)
592                     result ~= colTypeUDAs[0].type;
593                 else
594                     result ~= getFieldType!fieldType;
595                 static if(!possibleNullColumn!(__traits(getMember, T, field)))
596                     result ~= " NOT NULL";
597                 static if(hasUDA!(__traits(getMember, T, field), unique))
598                     result ~= " UNIQUE";
599                 static if(hasUDA!(__traits(getMember, T, field), autoIncrement))
600                 {
601                     static assert(hasUDA!(__traits(getMember, T, field), primaryKey));
602                     result ~= " PRIMARY KEY AUTOINCREMENT";
603                     if(++autoIncFields > 1)
604                     {
605                         assert(0, "Auto increment only allowed on one field!");
606                     }
607                 }
608 
609                 result ~= ",";
610             }
611         }
612         // add primary key
613         alias keyFields = getSymbolsByUDA!(T, primaryKey);
614         static if(keyFields.length > 0)
615         {
616             if(autoIncFields > 0)
617             {
618                 assert(keyFields.length == 1, "Only one primary key allowed when autoIncrement is used");
619             }
620             else
621             {
622                 result ~= " PRIMARY KEY (";
623                 static foreach(i, alias kf; keyFields)
624                 {
625                     static if(i != 0)
626                         result ~= ",";
627                     result ~= `"` ~ getColumnName!(kf) ~ `"`;
628                 }
629                 result ~= "),";
630             }
631         }
632 
633         static if(doForeignKeys)
634         {
635             import sqlbuilder.uda;
636             import sqlbuilder.traits;
637             foreach(field; FieldNameTuple!T)
638             {
639                 // only look for field relations, not Relation items which have no
640                 // local field.
641                 static if(isField!(T, field) && isRelationField!(__traits(getMember, T, field)))
642                 {
643                     // get the relation name
644                     alias mappings = getMappingsFor!(__traits(getMember, T, field));
645                     enum relation = getRelationFor!(__traits(getMember, T, field));
646                     result ~= `FOREIGN KEY ("`;
647                     foreach(i, m; mappings)
648                     {
649                         static if(i != 0)
650                             result ~= `", "`;
651                         result ~= getColumnName!(__traits(getMember, T, m.key));
652                     }
653                     result ~= `") REFERENCES "` ~ getTableName!(relation.foreign_table) ~ `" ("`;
654                     foreach(i, m; mappings)
655                     {
656                         static if(i != 0)
657                             result ~= `", "`;
658                         result ~= getColumnName!(__traits(getMember, relation.foreign_table, m.foreign_key));
659                     }
660                     result ~= `"),`;
661                 }
662             }
663         }
664 
665         return result[0 .. $-1] ~ ")";
666     }
667     enum createTableSql = generate();
668 }
669 
670 enum dropTableSql(T) = `DROP TABLE IF EXISTS "` ~ getTableName!T ~ `"`;
671 
672 // if we have sqlite as a dependency, provide direct serialization from a ResultRange
673 version(Have_d2sqlite3)
674 {
675     private import sqlbuilder.dialect.impl : objFieldNames;
676     private import d2sqlite3: Row, SqliteType, ResultRange, Database, Statement;
677 
678     PType getPType(Row r, size_t idx)
679     {
680         PType result;
681         final switch(r.columnType(idx))
682         {
683             case SqliteType.INTEGER:
684                 result = r.peek!long(idx);
685                 break;
686             case SqliteType.FLOAT:
687                 result = r.peek!double(idx);
688                 break;
689             case SqliteType.TEXT:
690                 result = r.peek!string(idx);
691                 break;
692             case SqliteType.BLOB:
693                 result = r.peek!(immutable(ubyte)[])(idx);
694                 break;
695             case SqliteType.NULL:
696                 result = null;
697                 break;
698         }
699         return result;
700     }
701 
702     private auto getLeaf(T)(PType v)
703     {
704         static if(is(T : Nullable!U, U))
705         {
706             if(v.isNull)
707             {
708                 return T.init;
709             }
710             return T(getLeaf!U(v));
711         }
712         else static if(is(T == AllowNullType!Args, Args...))
713         {
714             if(v.isNull)
715                 return T.nullVal;
716             return getLeaf!(T.type)(v);
717         }
718         else static if(is(T == PType))
719         {
720             // just return as-is
721             return v;
722         }
723         else
724         {
725             alias RT = dbValueType!T;
726             // null not tolerated
727             static if(is(RT == T))
728                 return v.get!T;
729             else static if(is(typeof(T.fromDbValue(RT.init))))
730                 return T.fromDbValue(v.get!RT);
731             else static if(is(typeof(T(RT.init))))
732                 return T(v.get!RT);
733             else static if(is(typeof(RT.init.to!T)))
734                 return v.get!RT.to!T;
735             else
736                 static assert(0, "Cannot figure out how to convert database value of type " ~ RT.stringof ~ " to D type " ~ T.stringof);
737         }
738     }
739 
740     struct DefaultObjectDeserializer(T)
741     {
742         static if(is(T == Nullable!U, U))
743         {
744             private alias RT = U;
745             private enum isNullType = true;
746         }
747         else
748         {
749             private alias RT = T;
750             private enum isNullType = false;
751         }
752         size_t[objFieldNames!RT.length] colIds = size_t.max;
753         // returns true if we know about this columm otherwise false.
754         bool mapColumnId(string colname, size_t idx)
755         {
756 objSwitch:
757             switch(colname)
758             {
759                 static foreach(fnum, fname; objFieldNames!RT)
760                 {
761                     case getColumnName!(__traits(getMember, RT, fname)):
762                         colIds[fnum] = idx;
763                         return true;
764                 }
765                 default:
766                     // unhandled.
767                     return false;
768             }
769         }
770         string[] unmappedColumns()
771         {
772             string[] result;
773             static foreach(fnum, fname; objFieldNames!RT)
774             {
775                 if(colIds[fnum] == size_t.max)
776                     result ~= fname;
777             }
778             return result;
779         }
780 
781         auto deserializeRow(Row r)
782         {
783             // deserialize all the data, but if any are null that can't be,
784             // make the whole thing null.
785             RT result;
786             static if(isNullType)
787                 bool wholeObjNull = false;
788             foreach(idx, n; objFieldNames!RT)
789             {
790                 if(colIds[idx] != size_t.max)
791                 {
792                     import sqlbuilder.uda;
793                     alias mem = __traits(getMember, RT, n);
794 
795                     // TODO: figure out to merge this code with the specific
796                     // AllowNullType code.
797                     static foreach(alias att; __traits(getAttributes, mem))
798                     {
799                         static if(__traits(isSame, att, allowNull))
800                             enum nullValue = typeof(mem).init;
801                         else static if(is(typeof(att) : AllowNull!U, U))
802                                                         enum nullValue = att.nullValue;
803 
804                     }
805 
806                     static if(is(typeof(nullValue)))
807                     {
808                         // use Nullable!X to get the data
809                         auto v = getLeaf!(Nullable!(typeof(mem)))(r.getPType(colIds[idx]));
810                         __traits(getMember, result, n) = v.get(nullValue);
811                     }
812                     else static if(isNullType && !(is(typeof(mem) == Nullable!N, N)))
813                     {
814                         // get as nullable, then set the whole object
815                         // to null if it is null.
816                         static if(is(typeof(mem) == void))
817                             pragma(msg, n);
818                         auto v = getLeaf!(Nullable!(typeof(mem)))(r.getPType(colIds[idx]));
819                         if(v.isNull)
820                         {
821                             wholeObjNull = true;
822                             break;
823                         }
824                         else
825                             __traits(getMember, result, n) = v.get;
826                     }
827                     else
828                     {
829                         __traits(getMember, result, n) = getLeaf!(typeof(mem))(r.getPType(colIds[idx]));
830                     }
831                 }
832             }
833 
834             static if(isNullType)
835             {
836                 if(wholeObjNull)
837                     return Nullable!RT.init; // return a null value
838                 return result.nullable;
839             }
840             else
841                 return result;
842         }
843     }
844 
845     struct NonObjectDeserializer(T)
846     {
847         size_t colId = size_t.max;
848         bool mapColumnId(string colname, size_t idx)
849         {
850             if(colId == size_t.max)
851             {
852                 // ignore the name.
853                 colId = idx;
854                 return true;
855             }
856             // only have one column
857             return false;
858         }
859 
860         auto deserializeRow(Row r)
861         {
862             return getLeaf!T(r.getPType(colId));
863         }
864     }
865 
866     struct ChangeDeserializer(T)
867     {
868         static if(is(T == Changed!(ColTypes), ColTypes...))
869             alias CT = ColTypes;
870         else
871             static assert(0, "ChangeDeserializer must only be used with a Changed type, not ", T);
872         size_t[CT.length] colIds = 0;
873         size_t filled = 0;
874         bool mapColumnId(string colname, size_t idx)
875         {
876             if(filled == colIds.length)
877                 return false;
878             colIds[filled++] = idx;
879             return true;
880         }
881 
882         auto deserializeRow(Row r)
883         {
884             T result;
885             static foreach(idx, U; CT)
886                 result.val[idx] = getLeaf!U(r.getPType(colIds[idx]));
887             return result;
888         }
889     }
890 
891     private void setArg(Statement p, size_t idx, PType arg)
892     {
893         // note sqlite parameter indexes are 1-based
894         int idxi = cast(int)idx + 1;
895         with(PType.Tag) final switch(arg.tag)
896         {
897             case Integer:
898                 p.bind(idxi, arg.get!long);
899                 break;
900             case Float:
901                 p.bind(idxi, arg.get!double);
902                 break;
903             case Text:
904                 // work around null pointer bug.
905                 // See https://github.com/dlang-community/d2sqlite3/issues/77
906                 auto str = arg.get!string;
907                 if(str.ptr is null)
908                     str = "";
909                 p.bind(idxi, str);
910                 break;
911             case Blob:
912                 // work around null pointer bug.
913                 // See https://github.com/dlang-community/d2sqlite3/issues/77
914                 auto data = arg.get!(.Blob);
915                 if(data.ptr is null)
916                     data = (data.ptr + 1)[0 .. 0];
917                 p.bind(idxi, data);
918                 break;
919             case Null:
920                 p.bind(idxi, null);
921                 break;
922         }
923     }
924 
925     // fetch a range of serialized items
926     auto fetch(bool throwOnExtraColumns = false, QD...)(Database conn, Query!(QD) q)
927     {
928         import std.range;
929         scope(failure)
930         {
931             import std.stdio;
932             writeln("Failed SQL is: ", q.sql!true);
933         }
934         auto p = conn.prepare(q.sql!true);
935         import std.range : enumerate;
936         foreach(idx, arg; q.params.enumerate)
937             p.setArg(idx, arg);
938         auto seq = p.execute();
939 
940         static if(q.QueryTypes.length == 0 ||  is(q.QueryTypes[0] == void))
941         {
942             // just return the range generated from the query, types weren't provided.
943             return seq;
944         }
945         else
946         {
947             // do deserialization.
948             // custom types can define a sqlbuilderDeserializer static member
949             // to initialize a custom deserializer.
950             template getDeserializer(T) {
951                 static if(is(T == RowObj!U, U))
952                 {
953                     static if(__traits(hasMember, U, "initialColumnIds"))
954                         static assert(0, "initialColumnIds is no longer the correct way to hook custom deserialization. please use sqlbuilderDeserializer.");
955                     static if(__traits(hasMember, U, "sqlbuilderDeserializer"))
956                         alias getDeserializer = typeof(() {return U.sqlbuilderDeserializer;}());
957                     else
958                         alias getDeserializer = DefaultObjectDeserializer!U;
959                 }
960                 else static if(is(T == Changed!(Args), Args...))
961                     alias getDeserializer = ChangeDeserializer!T;
962                 else
963                     alias getDeserializer = NonObjectDeserializer!T;
964             }
965 
966             import std.meta : staticMap;
967             alias colT = staticMap!(getDeserializer, q.QueryTypes);
968             static struct SerializedRange
969             {
970                 private ResultRange seq;
971                 private q.RowTypes row; // here is where we store the front element
972                 private colT deserializers;
973 
974                 auto front()
975                 {
976                     static if(row.length == 1)
977                         return row[0];
978                     else
979                     {
980                         import std.typecons : tuple;
981                         return tuple(row);
982                     }
983                 }
984 
985                 bool empty() { return seq.empty; }
986 
987                 private void loadItem(bool isfirst)
988                 {
989                     if(!seq.empty)
990                     {
991                         auto oldRow = row;
992                         auto sqlRow = seq.front;
993                         static foreach(i; 0 .. row.length)
994                         {
995                             row[i] = deserializers[i].deserializeRow(sqlRow);
996                             static if(isInstanceOf!(Changed, typeof(row[i])))
997                                 // the changed flag needs to be set based on if
998                                 // the previous row is equal to this row
999 
1000                                 row[i]._changed = isfirst || oldRow[i].val != row[i].val;
1001                         }
1002                     }
1003                 }
1004 
1005                 void popFront()
1006                 {
1007                     seq.popFront;
1008                     loadItem(false);
1009                 }
1010             }
1011 
1012             SerializedRange result;
1013             result.seq = seq;
1014             if(seq.empty)
1015                 return result;
1016 
1017             string[] colNames = new string[seq.front.length];
1018             foreach(i; 0 .. seq.front.length)
1019                 colNames[i] = seq.front.columnName(i);
1020             size_t colIdx;
1021 
1022             foreach(idx, T; q.QueryTypes)
1023             {
1024                 static if(is(T == RowObj!U, U))
1025                 {
1026                     // handle this differently, because this could have a
1027                     // custom deserializer, and we want to avoid passing in the
1028                     // object end marker.
1029                     static if(__traits(hasMember, U, "sqlbuilderDeserializer"))
1030                         result.deserializers[idx] = U.sqlbuilderDeserializer;
1031                     // serialize columns until we hit _objEnd
1032                     while(colNames[colIdx] != "_objend")
1033                     {
1034                         if(!result.deserializers[idx].mapColumnId(colNames[colIdx], colIdx))
1035                         {
1036                             static if(throwOnExtraColumns)
1037                                 throw new Exception("Unknown column name found: " ~ colNames[colIdx]);
1038                         }
1039                         ++colIdx;
1040                     }
1041                     ++colIdx; // skip the object end.
1042                 }
1043                 else
1044                 {
1045                     // Let the deserializer decide how many columns to accept
1046                     while(colIdx < colNames.length &&
1047                             result.deserializers[idx].mapColumnId(colNames[colIdx], colIdx))
1048                         ++colIdx;
1049                 }
1050             }
1051 
1052             result.loadItem(true);
1053             return result;
1054         }
1055     }
1056 
1057     long fetchTotal(QP...)(Database conn, Query!(QP) q)
1058     {
1059         q.limitQty = 0;
1060         q.limitOffset = 0;
1061         q.orders = q.orders.init;
1062         q.fields = SQLFragment!(q.ItemType)(ExprString("1"));
1063         auto sql = "SELECT COUNT(*) FROM(" ~ q.sql ~ ") `counter`";
1064         ResultRange seq;
1065         auto p = conn.prepare(sql);
1066         import std.range : enumerate;
1067         foreach(idx, arg; q.params.enumerate)
1068             p.setArg(idx, arg);
1069         seq = p.execute();
1070         return seq.empty ? 0 : seq.front.peek!long(0);
1071     }
1072 
1073     auto fetchOne(bool throwOnExtraColumns = false, QD...)(Database conn, Query!(QD) q)
1074     {
1075         auto r = fetch!throwOnExtraColumns(conn, q.limit(1));
1076         import std.range : ElementType;
1077         if(r.empty)
1078             throw new Exception("No items of type " ~ ElementType!(typeof(r)).stringof ~ " retreived from query");
1079         return r.front;
1080     }
1081 
1082     auto fetchOne(bool throwOnExtraColumns = false, T, QD...)(Database conn, Query!(QD) q, T defaultValue)
1083     {
1084         auto r = fetch!throwOnExtraColumns(conn, q.limit(1));
1085         return r.empty ? defaultValue : r.front;
1086     }
1087 
1088     template fetchUsingKey(T, bool throwOnExtraColumns = false) if (hasPrimaryKey!T)
1089     {
1090         auto fetchUsingKey(Args...)(Database conn, Args args) if (Args.length == primaryKeyFields!T.length)
1091         {
1092             import sqlbuilder.dataset;
1093             DataSet!T ds;
1094             return conn.fetchOne!throwOnExtraColumns(select(ds).havingKey(ds, args));
1095         }
1096     }
1097 
1098     auto fetchUsingKey(T, bool throwOnExtraColumns = false, Args...)(Database conn, T defaultValue, Args args) if (hasPrimaryKey!T && Args.length == primaryKeyFields!T.length)
1099     {
1100         import sqlbuilder.dataset;
1101         DataSet!T ds;
1102         return conn.fetchOne!throwOnExtraColumns(select(ds).havingKey(ds, args), defaultValue);
1103     }
1104 
1105     // returns the rows affected
1106     long perform(Q)(Database conn, Q stmt) if(is(Q : Insert!P, P) ||
1107                                                 is(Q : Update!P, P) ||
1108                                                 is(Q : Delete!P, P))
1109     {
1110         import std.range : enumerate;
1111         import std.stdio;
1112         scope(failure) writeln("failed statement is ", stmt.sql, " fields are ", stmt.params);
1113         auto p = conn.prepare(stmt.sql);
1114         foreach(idx, arg; stmt.params.enumerate)
1115             p.setArg(idx, arg);
1116         auto result = p.execute;
1117         return conn.changes();
1118     }
1119 
1120     auto ref T create(T)(Database conn, auto ref T blueprint)
1121     {
1122         import sqlbuilder.uda;
1123 
1124         // use insert to create it
1125         auto ins = insert(blueprint);
1126         auto rowsAffected = conn.perform(ins);
1127         // check for an autoInc field
1128         foreach(fname; __traits(allMembers, T))
1129             static if(isField!(T, fname) &&
1130                       hasUDA!(__traits(getMember, T, fname), autoIncrement))
1131             {
1132                 __traits(getMember, blueprint, fname) =
1133                      cast(typeof(__traits(getMember, blueprint, fname)))conn.lastInsertRowid;
1134             }
1135         return blueprint;
1136     }
1137 
1138     // returns true if the item was present. Only valid for records that have a
1139     // primary key.
1140     bool save(T)(Database conn, T item) if (hasPrimaryKey!T)
1141     {
1142         return conn.perform(update(item)) == 1;
1143     }
1144 
1145     // returns true if the item was erased from the db.
1146     bool erase(T)(Database conn, T item) if (hasPrimaryKey!T)
1147     {
1148         return conn.perform(remove(item)) == 1;
1149     }
1150 
1151     unittest
1152     {
1153         import sqlbuilder.dataset;
1154         import std.stdio;
1155         import std.range;
1156         import std.algorithm;
1157         static import std.file;
1158         auto tmpfilename = "./testDB.sqlite";
1159         if(std.file.exists(tmpfilename))
1160             std.file.remove(tmpfilename);
1161         auto conn = Database(tmpfilename);
1162 
1163         conn.execute(createTableSql!(Author, true));
1164         conn.execute(createTableSql!(book, true));
1165 
1166         conn.execute(createTableSql!(review, true));
1167         auto steve = conn.create(Author("Steven", "Schveighoffer"));
1168         auto ds = DataSet!Author();
1169         conn.perform(insert(ds.tableDef).set(ds.firstName, "Andrei".param).set(ds.lastName, Expr(`"Alexandrescu"`)).set(ds.ynAnswer, MyBool(true).param));
1170         auto andreiId = cast(int)conn.lastInsertRowid();
1171         conn.create(book("This Module", steve.id));
1172         conn.create(book("The D Programming Language", andreiId));
1173         auto book3 = conn.create(book("Modern C++ design", andreiId));
1174         // always a change if we update, as long as the book is found.
1175         assert(conn.perform(update(book3)) == 1);
1176         book3.title = "Not so Modern C++ Design";
1177         book3.book_type = BookType.Fiction;
1178         assert(conn.save(book3));
1179         foreach(auth, book; conn.fetch(select(ds, ds.books).where(ds.lastName, " = ", "Alexandrescu".param)))
1180         {
1181             writeln("author: ", auth, ", book: ", book);
1182         }
1183 
1184         foreach(newauth, auth, book; conn.fetch(select(ds.changed, ds, ds.books).orderBy(ds.id)))
1185         {
1186             if(newauth)
1187                 writeln("Author: ", auth);
1188             writeln("    book: ", book);
1189         }
1190 
1191         foreach(newauth, auth, book; conn.fetch(select(ds.id.changed, ds, ds.books).orderBy(ds.id)))
1192         {
1193             if(newauth)
1194                 writeln("Author: ", auth);
1195             writeln("    book: ", book);
1196         }
1197         auto book4 = conn.create(book("Remove Me", steve.id));
1198         writeln(book4);
1199         assert(conn.erase(book4) == 1);
1200         DataSet!book ds2;
1201         //assert(conn.perform(removeFrom(ds2.tableDef).where(ds2.author.lastName, " = ", "Alexandrescu".param)) == 2);
1202         assert(conn.perform(removeFrom(ds2.tableDef).havingKey(ds2.author, andreiId)) == 2);
1203         assert(conn.perform(removeFrom(ds2.tableDef).havingKey(ds2.author, steve)) == 1);
1204 
1205         // generate a table with a null value
1206         import sqlbuilder.uda;
1207         static struct foo
1208         {
1209             @primaryKey int id;
1210             Nullable!int col1;
1211         }
1212 
1213         conn.execute(createTableSql!foo);
1214         auto rds = DataSet!review.init;
1215         conn.perform(insert(rds.tableDef).set(rds.book_id, 1.param));
1216         writeln(conn.fetch(select(rds)));
1217 
1218         // test allowNull columns
1219         auto nullrating = conn.fetchOne(select(rds.rating).where(rds.book_id, " = ", 1.param));
1220         assert(nullrating == -1);
1221 
1222         // try a custom object
1223         static struct CustomObj
1224         {
1225             PType[] things;
1226             string[] colnames;
1227 
1228             // custom serialization
1229             struct CustomObjSerializer {
1230                 size_t startColId = -1;
1231                 size_t endColId = -1;
1232                 string[] names;
1233                 bool mapColumnId(string colname, size_t idx)
1234                 {
1235                     if(startColId == -1)
1236                         startColId = idx;
1237                     endColId = idx + 1;
1238                     names ~= colname;
1239                     return true;
1240                 }
1241 
1242                 CustomObj deserializeRow(Row r)
1243                 {
1244                     import std.array;
1245                     CustomObj result;
1246                     result.colnames = names;
1247                     result.things = zip(cycle(only(r)), iota(startColId, endColId)).map!(t => t[0].getPType(t[1])).array;
1248                     return result;
1249                 }
1250             }
1251             enum sqlbuilderDeserializer = CustomObjSerializer.init;
1252         }
1253 
1254         // fetch the custom row from the table
1255         auto customrow = ColumnDef!(RowObj!CustomObj)(TableDef("author", ExprString("author".makeSpec(Spec.id))), ExprString("*", objEndSpec));
1256         auto query3 = select(customrow);
1257         writeln("query3 is ", query3.sql);
1258         foreach(co; conn.fetch(select(customrow)))
1259         {
1260             static assert(is(typeof(co) == CustomObj));
1261             writefln("Got author row (column names = %-(%s, %)): %s", co.colnames, co.things);
1262         }
1263     }
1264 }