1 module tests.d;
2 
3 version (unittest):
4 
5 import d2sqlite3;
6 import std.algorithm;
7 import std.exception : assertThrown, assertNotThrown;
8 import std..string : format;
9 import std.typecons : Nullable;
10 import std.conv : hexString;
11 
12 unittest // Test version of SQLite library
13 {
14     import std..string : startsWith;
15     assert(versionString.startsWith("3."));
16     assert(versionNumber >= 3_008_007);
17 }
18 
19 unittest // COV
20 {
21     auto ts = threadSafe;
22 }
23 
24 unittest // Configuration logging and db.close()
25 {
26     static extern (C) void loggerCallback(void* arg, int code, const(char)* msg) nothrow
27     {
28         ++*(cast(int*) arg);
29     }
30 
31     int marker = 42;
32 
33     shutdown();
34     config(SQLITE_CONFIG_MULTITHREAD);
35     config(SQLITE_CONFIG_LOG, &loggerCallback, &marker);
36     initialize();
37 
38     {
39         auto db = Database(":memory:");
40         try
41         {
42             db.run("DROP TABLE wtf");
43         }
44         catch (Exception e)
45         {
46         }
47         db.close();
48     }
49     assert(marker == 43);
50 
51     shutdown();
52     config(SQLITE_CONFIG_LOG, null, null);
53     initialize();
54 
55     {
56         auto db = Database(":memory:");
57         try
58         {
59             db.run("DROP TABLE wtf");
60         }
61         catch (Exception e)
62         {
63         }
64     }
65     assert(marker == 43);
66 }
67 
68 unittest // Database.tableColumnMetadata()
69 {
70     auto db = Database(":memory:");
71     db.run("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT,
72             val FLOAT NOT NULL)");
73     assert(db.tableColumnMetadata("test", "id") ==
74             TableColumnMetadata("INTEGER", "BINARY", false, true, true));
75     assert(db.tableColumnMetadata("test", "val") ==
76             TableColumnMetadata("FLOAT", "BINARY", true, false, false));
77 }
78 
79 unittest // Database.run()
80 {
81     auto db = Database(":memory:");
82     int i;
83     db.run(`SELECT 1; SELECT 2;`, (ResultRange r) { i = r.oneValue!int; return false; });
84     assert(i == 1);
85 }
86 
87 unittest // Database.errorCode()
88 {
89     auto db = Database(":memory:");
90     db.run(`SELECT 1;`);
91     assert(db.errorCode == SQLITE_OK);
92     try
93         db.run(`DROP TABLE non_existent`);
94     catch (SqliteException e)
95         assert(db.errorCode == SQLITE_ERROR);
96 }
97 
98 unittest // Database.config
99 {
100     auto db = Database(":memory:");
101     db.run(`
102         CREATE TABLE test (val INTEGER);
103         CREATE TRIGGER test_trig BEFORE INSERT ON test
104         BEGIN
105             SELECT RAISE(FAIL, 'Test failed');
106         END;
107     `);
108     int res = 42;
109     db.config(SQLITE_DBCONFIG_ENABLE_TRIGGER, 0, &res);
110     assert(res == 0);
111     db.execute("INSERT INTO test (val) VALUES (1)");
112 }
113 
114 unittest // Database.createFunction(ColumnData[]...)
115 {
116     string myList(ColumnData[] args...)
117     {
118         import std.array : appender;
119         import std..string : format, join;
120 
121         auto app = appender!(string[]);
122         foreach (arg; args)
123         {
124             if (arg.type == SqliteType.TEXT)
125                 app.put(`"%s"`.format(arg));
126             else
127                 app.put("%s".format(arg));
128         }
129         return app.data.join(", ");
130     }
131     auto db = Database(":memory:");
132     db.createFunction("my_list", &myList);
133     auto list = db.execute("SELECT my_list(42, 3.14, 'text', x'00FF', NULL)").oneValue!string;
134     assert(list == `42, 3.14, "text", [0, 255], null`, list);
135 }
136 
137 unittest // Database.createFunction() exceptions
138 {
139     import std.exception : assertThrown;
140 
141     int myFun(int a, int b = 1)
142     {
143         return a * b;
144     }
145 
146     auto db = Database(":memory:");
147     db.createFunction("myFun", &myFun);
148     assertThrown!SqliteException(db.execute("SELECT myFun()"));
149     assertThrown!SqliteException(db.execute("SELECT myFun(1, 2, 3)"));
150     assert(db.execute("SELECT myFun(5)").oneValue!int == 5);
151     assert(db.execute("SELECT myFun(5, 2)").oneValue!int == 10);
152 
153     db.createFunction("myFun", null);
154     assertThrown!SqliteException(db.execute("SELECT myFun(5)"));
155     assertThrown!SqliteException(db.execute("SELECT myFun(5, 2)"));
156 }
157 
158 unittest // Database.setUpdateHook()
159 {
160     int i;
161     auto db = Database(":memory:");
162     db.setUpdateHook((int type, string dbName, string tableName, long rowid) {
163         assert(type == SQLITE_INSERT);
164         assert(dbName == "main");
165         assert(tableName == "test");
166         assert(rowid == 1);
167         i = 42;
168     });
169     db.run("CREATE TABLE test (val INTEGER);
170             INSERT INTO test VALUES (100)");
171     assert(i == 42);
172     db.setUpdateHook(null);
173 }
174 
175 unittest // Database commit and rollback hooks
176 {
177     int i;
178     auto db = Database(":memory:");
179     db.setCommitHook({ i = 42; return SQLITE_OK; });
180     db.setRollbackHook({ i = 666; });
181     db.begin();
182     db.execute("CREATE TABLE test (val INTEGER)");
183     db.rollback();
184     assert(i == 666);
185     db.begin();
186     db.execute("CREATE TABLE test (val INTEGER)");
187     db.commit();
188     assert(i == 42);
189     db.setCommitHook(null);
190     db.setRollbackHook(null);
191 }
192 
193 unittest // Miscellaneous functions
194 {
195     auto db = Database(":memory:");
196     assert(db.attachedFilePath("main") is null);
197     assert(!db.isReadOnly);
198     db.close();
199 }
200 
201 unittest // Execute an SQL statement
202 {
203     auto db = Database(":memory:");
204     db.run("");
205     db.run("-- This is a comment!");
206     db.run(";");
207     db.run("ANALYZE; VACUUM;");
208 }
209 
210 unittest // Unexpected multiple statements
211 {
212     auto db = Database(":memory:");
213     db.execute("BEGIN; CREATE TABLE test (val INTEGER); ROLLBACK;");
214     assertThrown(db.execute("DROP TABLE test"));
215 
216     db.execute("CREATE TABLE test (val INTEGER); DROP TABLE test;");
217     assertNotThrown(db.execute("DROP TABLE test"));
218 
219     db.execute("SELECT 1; CREATE TABLE test (val INTEGER); DROP TABLE test;");
220     assertThrown(db.execute("DROP TABLE test"));
221 }
222 
223 unittest // Multiple statements with callback
224 {
225     import std.array : appender;
226     auto db = Database(":memory:");
227     auto test = appender!string;
228     db.run("SELECT 1, 2, 3; SELECT 'A', 'B', 'C';", (ResultRange r) {
229         foreach (col; r.front)
230             test.put(col.as!string);
231         return true;
232     });
233     assert(test.data == "123ABC");
234 }
235 
236 unittest // Different arguments and result types with createFunction
237 {
238     auto db = Database(":memory:");
239 
240     T display(T)(T value)
241     {
242         return value;
243     }
244 
245     db.createFunction("display_integer", &display!int);
246     db.createFunction("display_float", &display!double);
247     db.createFunction("display_text", &display!string);
248     db.createFunction("display_blob", &display!Blob);
249 
250     assert(db.execute("SELECT display_integer(42)").oneValue!int == 42);
251     assert(db.execute("SELECT display_float(3.14)").oneValue!double == 3.14);
252     assert(db.execute("SELECT display_text('ABC')").oneValue!string == "ABC");
253     assert(db.execute("SELECT display_blob(x'ABCD')").oneValue!Blob == cast(Blob) hexString!"ABCD");
254 
255     assert(db.execute("SELECT display_integer(NULL)").oneValue!int == 0);
256     assert(db.execute("SELECT display_float(NULL)").oneValue!double == 0.0);
257     assert(db.execute("SELECT display_text(NULL)").oneValue!string is null);
258     assert(db.execute("SELECT display_blob(NULL)").oneValue!(Blob) is null);
259 }
260 
261 unittest // Different Nullable argument types with createFunction
262 {
263     auto db = Database(":memory:");
264 
265     auto display(T : Nullable!U, U...)(T value)
266     {
267         if (value.isNull)
268             return T.init;
269         return value;
270     }
271 
272     db.createFunction("display_integer", &display!(Nullable!int));
273     db.createFunction("display_float", &display!(Nullable!double));
274     db.createFunction("display_text", &display!(Nullable!string));
275     db.createFunction("display_blob", &display!(Nullable!Blob));
276 
277     assert(db.execute("SELECT display_integer(42)").oneValue!(Nullable!int) == 42);
278     assert(db.execute("SELECT display_float(3.14)").oneValue!(Nullable!double) == 3.14);
279     assert(db.execute("SELECT display_text('ABC')").oneValue!(Nullable!string) == "ABC");
280     assert(db.execute("SELECT display_blob(x'ABCD')").oneValue!(Nullable!Blob) == cast(Blob) hexString!"ABCD");
281 
282     assert(db.execute("SELECT display_integer(NULL)").oneValue!(Nullable!int).isNull);
283     assert(db.execute("SELECT display_float(NULL)").oneValue!(Nullable!double).isNull);
284     assert(db.execute("SELECT display_text(NULL)").oneValue!(Nullable!string).isNull);
285     assert(db.execute("SELECT display_blob(NULL)").oneValue!(Nullable!Blob).isNull);
286 }
287 
288 unittest // Callable struct with createFunction
289 {
290     import std.functional : toDelegate;
291 
292     struct Fun
293     {
294         int factor;
295 
296         this(int factor)
297         {
298             this.factor = factor;
299         }
300 
301         int opCall(int value)
302         {
303             return value * factor;
304         }
305     }
306 
307     auto f = Fun(2);
308     auto db = Database(":memory:");
309     db.createFunction("my_fun", toDelegate(f));
310     assert(db.execute("SELECT my_fun(4)").oneValue!int == 8);
311 }
312 
313 unittest // Callbacks
314 {
315     bool wasTraced = false;
316     bool wasProfiled = false;
317     bool hasProgressed = false;
318 
319     auto db = Database(":memory:");
320     db.setTraceCallback((string s) { wasTraced = true; });
321     db.execute("SELECT * FROM sqlite_master;");
322     assert(wasTraced);
323     db.setProfileCallback((string s, ulong t) { wasProfiled = true; });
324     db.execute("SELECT * FROM sqlite_master;");
325     assert(wasProfiled);
326 
327     db.setProgressHandler(1, { hasProgressed = true; return 0; });
328     db.execute("SELECT * FROM sqlite_master;");
329     assert(hasProgressed);
330 }
331 
332 unittest // Statement.oneValue()
333 {
334     Statement statement;
335     {
336         auto db = Database(":memory:");
337         statement = db.prepare(" SELECT 42 ");
338     }
339     assert(statement.execute.oneValue!int == 42);
340 }
341 
342 unittest // Statement.finalize()
343 {
344     auto db = Database(":memory:");
345     auto statement = db.prepare(" SELECT 42 ");
346     statement.finalize();
347 }
348 
349 unittest // Simple parameters binding
350 {
351     auto db = Database(":memory:");
352     db.execute("CREATE TABLE test (val INTEGER)");
353 
354     auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
355     statement.bind(1, 36);
356     statement.clearBindings();
357     statement.bind(1, 42);
358     statement.execute();
359     statement.reset();
360     statement.bind(1, 42);
361     statement.execute();
362 
363     assert(db.lastInsertRowid == 2);
364     assert(db.changes == 1);
365     assert(db.totalChanges == 2);
366 
367     auto results = db.execute("SELECT * FROM test");
368     foreach (row; results)
369         assert(row.peek!int(0) == 42);
370 }
371 
372 unittest // Multiple parameters binding
373 {
374     auto db = Database(":memory:");
375     db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
376     auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (:i, @f, $t)");
377 
378     assert(statement.parameterCount == 3);
379     assert(statement.parameterName(2) == "@f");
380     assert(statement.parameterIndex("$t") == 3);
381     assert(statement.parameterIndex(":foo") == 0);
382 
383     statement.bind("$t", "TEXT");
384     statement.bind(":i", 42);
385     statement.bind("@f", 3.14);
386     statement.execute();
387     statement.reset();
388     statement.bind(1, 42);
389     statement.bind(2, 3.14);
390     statement.bind(3, "TEXT");
391     statement.execute();
392 
393     auto results = db.execute("SELECT * FROM test");
394     foreach (row; results)
395     {
396         assert(row.length == 3);
397         assert(row.peek!int("i") == 42);
398         assert(row.peek!double("f") == 3.14);
399         assert(row.peek!string("t") == "TEXT");
400     }
401 }
402 
403 // Binding/peeking structs with `toString` and `fromString`
404 unittest
405 {
406     auto db = Database(":memory:");
407     db.execute("CREATE TABLE test (val TEXT)");
408 
409     static struct ToStringSink {
410         string value;
411         void toString(scope void delegate(in char[]) sink) const
412         {
413             sink(this.value);
414         }
415     }
416 
417     static struct ToStringMethod {
418         string value;
419         string toString() const
420         {
421             return this.value;
422         }
423     }
424 
425     auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
426     statement.bind(1, ToStringMethod("oldmethod"));
427     statement.clearBindings();
428     statement.bind(1, ToStringMethod("method"));
429     statement.execute();
430     statement.reset();
431     statement.bind(1, ToStringSink("sink"));
432     statement.execute();
433 
434     assert(db.lastInsertRowid == 2);
435     assert(db.changes == 1);
436     assert(db.totalChanges == 2);
437 
438     auto results = db.execute("SELECT * FROM test");
439     results.equal!((a, b) => a.peek!string(0) == b)(["method", "sink"]);
440 }
441 
442 unittest // Multiple parameters binding: tuples
443 {
444     auto db = Database(":memory:");
445     db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
446     auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)");
447     statement.bindAll(42, 3.14, "TEXT");
448     statement.execute();
449 
450     auto results = db.execute("SELECT * FROM test");
451     foreach (row; results)
452     {
453         assert(row.length == 3);
454         assert(row.peek!int(0) == 42);
455         assert(row.peek!double(1) == 3.14);
456         assert(row.peek!string(2) == "TEXT");
457     }
458 }
459 
460 unittest // Binding/peeking integral values
461 {
462     auto db = Database(":memory:");
463     db.run("CREATE TABLE test (val INTEGER)");
464 
465     auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
466     statement.inject(cast(byte) 42);
467     statement.inject(42U);
468     statement.inject(42UL);
469     statement.inject('\x2A');
470 
471     auto results = db.execute("SELECT * FROM test");
472     foreach (row; results)
473         assert(row.peek!long(0) == 42);
474 }
475 
476 void foobar() // Binding/peeking floating point values
477 {
478     auto db = Database(":memory:");
479     db.run("CREATE TABLE test (val FLOAT)");
480 
481     auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
482     statement.inject(42.0F);
483     statement.inject(42.0);
484     statement.inject(42.0L);
485     statement.inject("42");
486 
487     auto results = db.execute("SELECT * FROM test");
488     foreach (row; results)
489         assert(row.peek!double(0) == 42.0);
490 }
491 
492 unittest // Binding/peeking text values
493 {
494     auto db = Database(":memory:");
495     db.run("CREATE TABLE test (val TEXT);
496             INSERT INTO test (val) VALUES ('I am a text.')");
497 
498     auto results = db.execute("SELECT * FROM test");
499     assert(results.front.peek!(string, PeekMode.slice)(0) == "I am a text.");
500     assert(results.front.peek!(string, PeekMode.copy)(0) == "I am a text.");
501 
502     import std.exception : assertThrown;
503     import std.variant : VariantException;
504     assertThrown!VariantException(results.front[0].as!Blob);
505 }
506 
507 unittest // Binding/peeking blob values
508 {
509     auto db = Database(":memory:");
510     db.execute("CREATE TABLE test (val BLOB)");
511 
512     auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
513     auto array = cast(Blob) [1, 2, 3];
514     statement.inject(array);
515     ubyte[3] sarray = [1, 2, 3];
516     statement.inject(sarray);
517 
518     auto results = db.execute("SELECT * FROM test");
519     foreach (row; results)
520     {
521         assert(row.peek!(Blob, PeekMode.slice)(0) ==  [1, 2, 3]);
522         assert(row[0].as!Blob == [1, 2, 3]);
523     }
524 }
525 
526 unittest // Struct injecting
527 {
528     static struct Test
529     {
530         int i;
531         double f;
532         string t;
533     }
534 
535     auto db = Database(":memory:");
536     db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
537     auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)");
538     auto test = Test(42, 3.14, "TEXT");
539     statement.inject(test);
540     statement.inject(Test(42, 3.14, "TEXT"));
541     auto itest = cast(immutable) Test(42, 3.14, "TEXT");
542     statement.inject(itest);
543 
544     auto results = db.execute("SELECT * FROM test");
545     assert(!results.empty);
546     foreach (row; results)
547     {
548         assert(row.length == 3);
549         assert(row.peek!int(0) == 42);
550         assert(row.peek!double(1) == 3.14);
551         assert(row.peek!string(2) == "TEXT");
552     }
553 }
554 
555 unittest // Iterable struct injecting
556 {
557     import std.range : iota;
558 
559     auto db = Database(":memory:");
560     db.execute("CREATE TABLE test (a INTEGER, b INTEGER, c INTEGER)");
561     auto statement = db.prepare("INSERT INTO test (a, b, c) VALUES (?, ?, ?)");
562     statement.inject(iota(0, 3));
563 
564     auto results = db.execute("SELECT * FROM test");
565     assert(!results.empty);
566     foreach (row; results)
567     {
568         assert(row.length == 3);
569         assert(row.peek!int(0) == 0);
570         assert(row.peek!int(1) == 1);
571         assert(row.peek!int(2) == 2);
572     }
573 }
574 
575 unittest // Injecting nullable
576 {
577     import std.array : array;
578 
579     auto db = Database(":memory:");
580     db.execute("CREATE TABLE test (i INTEGER, s TEXT)");
581     auto statement = db.prepare("INSERT INTO test (i, s) VALUES (?, ?)");
582     statement.inject(Nullable!int(1), "one");
583     statement = db.prepare("INSERT INTO test (i) VALUES (?)");
584     statement.inject(Nullable!int.init);
585 
586     auto results = db.execute("SELECT i FROM test ORDER BY rowid");
587     assert(results.equal!((a, b) => a.peek!(Nullable!int)(0) == b)(
588                [ Nullable!int(1), Nullable!int.init ] ));
589 }
590 
591 unittest // Injecting tuple
592 {
593     import std.typecons : tuple;
594 
595     auto db = Database(":memory:");
596     db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
597     auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)");
598     statement.inject(tuple(42, 3.14, "TEXT"));
599 
600     auto results = db.execute("SELECT * FROM test");
601     foreach (row; results)
602     {
603         assert(row.length == 3);
604         assert(row.peek!int(0) == 42);
605         assert(row.peek!double(1) == 3.14);
606         assert(row.peek!string(2) == "TEXT");
607     }
608 }
609 
610 unittest // Injecting dict
611 {
612     auto db = Database(":memory:");
613     db.execute("CREATE TABLE test (a TEXT, b TEXT, c TEXT)");
614     auto statement = db.prepare("INSERT INTO test (c, b, a) VALUES (:c, :b, :a)");
615     statement.inject([":a":"a", ":b":"b", ":c":"c"]);
616 
617     auto results = db.execute("SELECT * FROM test");
618     foreach (row; results)
619     {
620         assert(row.length == 3);
621         assert(row.peek!string(0) == "a");
622         assert(row.peek!string(1) == "b");
623         assert(row.peek!string(2) == "c");
624     }
625 }
626 
627 unittest // Binding Nullable
628 {
629     auto db = Database(":memory:");
630     db.execute("CREATE TABLE test (a, b, c, d, e);");
631 
632     auto statement = db.prepare("INSERT INTO test (a,b,c,d,e) VALUES (?,?,?,?,?)");
633     statement.bind(1, Nullable!int(123));
634     statement.bind(2, Nullable!int());
635     statement.bind(3, Nullable!(uint, 0)(42));
636     statement.bind(4, Nullable!(uint, 0)());
637     statement.bind(5, Nullable!bool(false));
638     statement.execute();
639 
640     auto results = db.execute("SELECT * FROM test");
641     foreach (row; results)
642     {
643         assert(row.length == 5);
644         assert(row.peek!int(0) == 123);
645         assert(row.columnType(1) == SqliteType.NULL);
646         assert(row.peek!int(2) == 42);
647         assert(row.columnType(3) == SqliteType.NULL);
648         assert(!row.peek!bool(4));
649     }
650 }
651 
652 unittest // Peeking Nullable
653 {
654     auto db = Database(":memory:");
655     auto results = db.execute("SELECT 1, NULL, 8.5, NULL");
656     foreach (row; results)
657     {
658         assert(row.length == 4);
659         assert(row.peek!(Nullable!double)(2).get == 8.5);
660         assert(row.peek!(Nullable!double)(3).isNull);
661         assert(row.peek!(Nullable!(int, 0))(0).get == 1);
662         assert(row.peek!(Nullable!(int, 0))(1).isNull);
663     }
664 }
665 
666 unittest // GC anchoring test
667 {
668     import core.memory : GC;
669 
670     auto db = Database(":memory:");
671     auto stmt = db.prepare("SELECT ?");
672 
673     auto str = ("I am test string").dup;
674     stmt.bind(1, str);
675     str = null;
676 
677     foreach (_; 0..3)
678     {
679         GC.collect();
680         GC.minimize();
681     }
682 
683     ResultRange results = stmt.execute();
684     foreach(row; results)
685     {
686         assert(row.length == 1);
687         assert(row.peek!string(0) == "I am test string");
688     }
689 }
690 
691 version (unittest) // ResultRange is an input range of Row
692 {
693     import std.range.primitives : isInputRange, ElementType;
694     static assert(isInputRange!ResultRange);
695     static assert(is(ElementType!ResultRange == Row));
696 }
697 
698 unittest // Statement error
699 {
700     auto db = Database(":memory:");
701     db.execute("CREATE TABLE test (val INTEGER NOT NULL)");
702     auto stmt = db.prepare("INSERT INTO test (val) VALUES (?)");
703     stmt.bind(1, null);
704     import std.exception : assertThrown;
705     assertThrown!SqliteException(stmt.execute());
706 }
707 
708 version (unittest) // Row is a random access range of ColumnData
709 {
710     import std.range.primitives : isRandomAccessRange, ElementType;
711     static assert(isRandomAccessRange!Row);
712     static assert(is(ElementType!Row == ColumnData));
713 }
714 
715 unittest // Row.init
716 {
717     import core.exception : AssertError;
718 
719     Row row;
720     assert(row.empty);
721     assertThrown!AssertError(row.front);
722     assertThrown!AssertError(row.back);
723     assertThrown!AssertError(row.popFront);
724     assertThrown!AssertError(row.popBack);
725     assertThrown!AssertError(row[""]);
726     assertThrown!AssertError(row.peek!long(0));
727 }
728 
729 unittest // Peek
730 {
731     auto db = Database(":memory:");
732     db.run("CREATE TABLE test (value);
733             INSERT INTO test VALUES (NULL);
734             INSERT INTO test VALUES (42);
735             INSERT INTO test VALUES (3.14);
736             INSERT INTO test VALUES ('ABC');
737             INSERT INTO test VALUES (x'DEADBEEF');");
738 
739     import std.math : isNaN;
740     auto results = db.execute("SELECT * FROM test");
741     auto row = results.front;
742     assert(row.peek!long(0) == 0);
743     assert(row.peek!double(0) == 0);
744     assert(row.peek!string(0) is null);
745     assert(row.peek!Blob(0) is null);
746     results.popFront();
747     row = results.front;
748     assert(row.peek!long(0) == 42);
749     assert(row.peek!double(0) == 42);
750     assert(row.peek!string(0) == "42");
751     assert(row.peek!Blob(0) == cast(Blob) "42");
752     results.popFront();
753     row = results.front;
754     assert(row.peek!long(0) == 3);
755     assert(row.peek!double(0) == 3.14);
756     assert(row.peek!string(0) == "3.14");
757     assert(row.peek!Blob(0) == cast(Blob) "3.14");
758     results.popFront();
759     row = results.front;
760     assert(row.peek!long(0) == 0);
761     assert(row.peek!double(0) == 0.0);
762     assert(row.peek!string(0) == "ABC");
763     assert(row.peek!Blob(0) == cast(Blob) "ABC");
764     results.popFront();
765     row = results.front;
766     assert(row.peek!long(0) == 0);
767     assert(row.peek!double(0) == 0.0);
768     assert(row.peek!string(0) == hexString!"DEADBEEF");
769     assert(row.peek!Blob(0) == cast(Blob) hexString!"DEADBEEF");
770 }
771 
772 unittest // Peeking NULL values
773 {
774     auto db = Database(":memory:");
775     db.run("CREATE TABLE test (val TEXT);
776             INSERT INTO test (val) VALUES (NULL)");
777 
778     auto results = db.execute("SELECT * FROM test");
779     assert(results.front.peek!bool(0) == false);
780     assert(results.front.peek!long(0) == 0);
781     assert(results.front.peek!double(0) == 0);
782     assert(results.front.peek!string(0) is null);
783     assert(results.front.peek!Blob(0) is null);
784 }
785 
786 unittest // Row life-time
787 {
788     auto db = Database(":memory:");
789     auto row = db.execute("SELECT 1 AS one").front;
790     assert(row[0].as!long == 1);
791     assert(row["one"].as!long == 1);
792 }
793 
794 unittest // PeekMode
795 {
796     auto db = Database(":memory:");
797     db.run("CREATE TABLE test (value);
798             INSERT INTO test VALUES (x'01020304');
799             INSERT INTO test VALUES (x'0A0B0C0D');");
800 
801     auto results = db.execute("SELECT * FROM test");
802     auto row = results.front;
803     auto b1 = row.peek!(Blob, PeekMode.copy)(0);
804     auto b2 = row.peek!(Blob, PeekMode.slice)(0);
805     results.popFront();
806     row = results.front;
807     auto b3 = row.peek!(Blob, PeekMode.slice)(0);
808     auto b4 = row.peek!(Nullable!Blob, PeekMode.copy)(0);
809     assert(b1 == cast(Blob) hexString!"01020304");
810     // assert(b2 != cast(Blob) x"01020304"); // PASS if SQLite reuses internal buffer
811     // assert(b2 == cast(Blob) x"0A0B0C0D"); // PASS (idem)
812     assert(b3 == cast(Blob) hexString!"0A0B0C0D");
813     assert(!b4.isNull && b4 == cast(Blob) hexString!"0A0B0C0D");
814 }
815 
816 unittest // Row random-access range interface
817 {
818     import std.array : front, popFront;
819 
820     auto db = Database(":memory:");
821     db.run("CREATE TABLE test (a INTEGER, b INTEGER, c INTEGER, d INTEGER);
822         INSERT INTO test VALUES (1, 2, 3, 4);
823         INSERT INTO test VALUES (5, 6, 7, 8);");
824 
825     {
826         auto results = db.execute("SELECT * FROM test");
827         auto values = [1, 2, 3, 4, 5, 6, 7, 8];
828         foreach (row; results)
829         {
830             while (!row.empty)
831             {
832                 assert(row.front.as!int == values.front);
833                 row.popFront();
834                 values.popFront();
835             }
836         }
837     }
838 
839     {
840         auto results = db.execute("SELECT * FROM test");
841         auto values = [4, 3, 2, 1, 8, 7, 6, 5];
842         foreach (row; results)
843         {
844             while (!row.empty)
845             {
846                 assert(row.back.as!int == values.front);
847                 row.popBack();
848                 values.popFront();
849             }
850         }
851     }
852 
853     {
854         auto row = db.execute("SELECT * FROM test").front;
855         row.popFront();
856         auto copy = row.save();
857         row.popFront();
858         assert(row.front.as!int == 3);
859         assert(copy.front.as!int == 2);
860     }
861 }
862 
863 unittest // ColumnData.init
864 {
865     import core.exception : AssertError;
866     ColumnData data;
867     assertThrown!AssertError(data.type);
868     assertThrown!AssertError(data.as!string);
869 }
870 
871 unittest // ColumnData-compatible types
872 {
873     import std.meta : AliasSeq;
874 
875     alias AllCases = AliasSeq!(bool, true, int, int.max, float, float.epsilon,
876         real, 42.0L, string, "おはよう!", const(ubyte)[], [0x00, 0xFF],
877         string, "", Nullable!byte, 42);
878 
879     void test(Cases...)()
880     {
881         auto cd = ColumnData(Cases[1]);
882         assert(cd.as!(Cases[0]) == Cases[1]);
883         static if (Cases.length > 2)
884             test!(Cases[2..$])();
885     }
886 
887     test!AllCases();
888 }
889 
890 unittest // ColumnData.toString
891 {
892     auto db = Database(":memory:");
893     auto rc = db.execute("SELECT 42, 3.14, 'foo_bar', x'00FF', NULL").cached;
894     assert("%(%s%)".format(rc) == "[42, 3.14, foo_bar, [0, 255], null]");
895 }
896 
897 unittest // CachedResults copies
898 {
899     auto db = Database(":memory:");
900     db.run("CREATE TABLE test (msg TEXT);
901             INSERT INTO test (msg) VALUES ('ABC')");
902 
903     static getdata(Database db)
904     {
905         return db.execute("SELECT * FROM test").cached;
906     }
907 
908     auto data = getdata(db);
909     assert(data.length == 1);
910     assert(data[0][0].as!string == "ABC");
911 }
912 
913 unittest // UTF-8
914 {
915     auto db = Database(":memory:");
916     bool ran = false;
917     db.run("SELECT '\u2019\u2019';", (ResultRange r) {
918         assert(r.oneValue!string == "\u2019\u2019");
919         ran = true;
920         return true;
921     });
922     assert(ran);
923 }
924 
925 unittest // loadExtension failure test
926 {
927     import std.exception : collectExceptionMsg;
928     auto db = Database(":memory:");
929     auto msg = collectExceptionMsg(db.loadExtension("foobar"));
930     //assert(msg.canFind("(not authorized)"));
931 }