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