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