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