aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends/d2sqlite3/source/tests.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext_depends/d2sqlite3/source/tests.d')
-rw-r--r--src/ext_depends/d2sqlite3/source/tests.d931
1 files changed, 931 insertions, 0 deletions
diff --git a/src/ext_depends/d2sqlite3/source/tests.d b/src/ext_depends/d2sqlite3/source/tests.d
new file mode 100644
index 0000000..ff20af1
--- /dev/null
+++ b/src/ext_depends/d2sqlite3/source/tests.d
@@ -0,0 +1,931 @@
+module tests.d;
+
+version (unittest):
+
+import d2sqlite3;
+import std.algorithm;
+import std.exception : assertThrown, assertNotThrown;
+import std.string : format;
+import std.typecons : Nullable;
+import std.conv : hexString;
+
+unittest // Test version of SQLite library
+{
+ import std.string : startsWith;
+ assert(versionString.startsWith("3."));
+ assert(versionNumber >= 3_008_007);
+}
+
+unittest // COV
+{
+ auto ts = threadSafe;
+}
+
+unittest // Configuration logging and db.close()
+{
+ static extern (C) void loggerCallback(void* arg, int code, const(char)* msg) nothrow
+ {
+ ++*(cast(int*) arg);
+ }
+
+ int marker = 42;
+
+ shutdown();
+ config(SQLITE_CONFIG_MULTITHREAD);
+ config(SQLITE_CONFIG_LOG, &loggerCallback, &marker);
+ initialize();
+
+ {
+ auto db = Database(":memory:");
+ try
+ {
+ db.run("DROP TABLE wtf");
+ }
+ catch (Exception e)
+ {
+ }
+ db.close();
+ }
+ assert(marker == 43);
+
+ shutdown();
+ config(SQLITE_CONFIG_LOG, null, null);
+ initialize();
+
+ {
+ auto db = Database(":memory:");
+ try
+ {
+ db.run("DROP TABLE wtf");
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ assert(marker == 43);
+}
+
+unittest // Database.tableColumnMetadata()
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT,
+ val FLOAT NOT NULL)");
+ assert(db.tableColumnMetadata("test", "id") ==
+ TableColumnMetadata("INTEGER", "BINARY", false, true, true));
+ assert(db.tableColumnMetadata("test", "val") ==
+ TableColumnMetadata("FLOAT", "BINARY", true, false, false));
+}
+
+unittest // Database.run()
+{
+ auto db = Database(":memory:");
+ int i;
+ db.run(`SELECT 1; SELECT 2;`, (ResultRange r) { i = r.oneValue!int; return false; });
+ assert(i == 1);
+}
+
+unittest // Database.errorCode()
+{
+ auto db = Database(":memory:");
+ db.run(`SELECT 1;`);
+ assert(db.errorCode == SQLITE_OK);
+ try
+ db.run(`DROP TABLE non_existent`);
+ catch (SqliteException e)
+ assert(db.errorCode == SQLITE_ERROR);
+}
+
+unittest // Database.config
+{
+ auto db = Database(":memory:");
+ db.run(`
+ CREATE TABLE test (val INTEGER);
+ CREATE TRIGGER test_trig BEFORE INSERT ON test
+ BEGIN
+ SELECT RAISE(FAIL, 'Test failed');
+ END;
+ `);
+ int res = 42;
+ db.config(SQLITE_DBCONFIG_ENABLE_TRIGGER, 0, &res);
+ assert(res == 0);
+ db.execute("INSERT INTO test (val) VALUES (1)");
+}
+
+unittest // Database.createFunction(ColumnData[]...)
+{
+ string myList(ColumnData[] args...)
+ {
+ import std.array : appender;
+ import std.string : format, join;
+
+ auto app = appender!(string[]);
+ foreach (arg; args)
+ {
+ if (arg.type == SqliteType.TEXT)
+ app.put(`"%s"`.format(arg));
+ else
+ app.put("%s".format(arg));
+ }
+ return app.data.join(", ");
+ }
+ auto db = Database(":memory:");
+ db.createFunction("my_list", &myList);
+ auto list = db.execute("SELECT my_list(42, 3.14, 'text', x'00FF', NULL)").oneValue!string;
+ assert(list == `42, 3.14, "text", [0, 255], null`, list);
+}
+
+unittest // Database.createFunction() exceptions
+{
+ import std.exception : assertThrown;
+
+ int myFun(int a, int b = 1)
+ {
+ return a * b;
+ }
+
+ auto db = Database(":memory:");
+ db.createFunction("myFun", &myFun);
+ assertThrown!SqliteException(db.execute("SELECT myFun()"));
+ assertThrown!SqliteException(db.execute("SELECT myFun(1, 2, 3)"));
+ assert(db.execute("SELECT myFun(5)").oneValue!int == 5);
+ assert(db.execute("SELECT myFun(5, 2)").oneValue!int == 10);
+
+ db.createFunction("myFun", null);
+ assertThrown!SqliteException(db.execute("SELECT myFun(5)"));
+ assertThrown!SqliteException(db.execute("SELECT myFun(5, 2)"));
+}
+
+unittest // Database.setUpdateHook()
+{
+ int i;
+ auto db = Database(":memory:");
+ db.setUpdateHook((int type, string dbName, string tableName, long rowid) {
+ assert(type == SQLITE_INSERT);
+ assert(dbName == "main");
+ assert(tableName == "test");
+ assert(rowid == 1);
+ i = 42;
+ });
+ db.run("CREATE TABLE test (val INTEGER);
+ INSERT INTO test VALUES (100)");
+ assert(i == 42);
+ db.setUpdateHook(null);
+}
+
+unittest // Database commit and rollback hooks
+{
+ int i;
+ auto db = Database(":memory:");
+ db.setCommitHook({ i = 42; return SQLITE_OK; });
+ db.setRollbackHook({ i = 666; });
+ db.begin();
+ db.execute("CREATE TABLE test (val INTEGER)");
+ db.rollback();
+ assert(i == 666);
+ db.begin();
+ db.execute("CREATE TABLE test (val INTEGER)");
+ db.commit();
+ assert(i == 42);
+ db.setCommitHook(null);
+ db.setRollbackHook(null);
+}
+
+unittest // Miscellaneous functions
+{
+ auto db = Database(":memory:");
+ assert(db.attachedFilePath("main") is null);
+ assert(!db.isReadOnly);
+ db.close();
+}
+
+unittest // Execute an SQL statement
+{
+ auto db = Database(":memory:");
+ db.run("");
+ db.run("-- This is a comment!");
+ db.run(";");
+ db.run("ANALYZE; VACUUM;");
+}
+
+unittest // Unexpected multiple statements
+{
+ auto db = Database(":memory:");
+ db.execute("BEGIN; CREATE TABLE test (val INTEGER); ROLLBACK;");
+ assertThrown(db.execute("DROP TABLE test"));
+
+ db.execute("CREATE TABLE test (val INTEGER); DROP TABLE test;");
+ assertNotThrown(db.execute("DROP TABLE test"));
+
+ db.execute("SELECT 1; CREATE TABLE test (val INTEGER); DROP TABLE test;");
+ assertThrown(db.execute("DROP TABLE test"));
+}
+
+unittest // Multiple statements with callback
+{
+ import std.array : appender;
+ auto db = Database(":memory:");
+ auto test = appender!string;
+ db.run("SELECT 1, 2, 3; SELECT 'A', 'B', 'C';", (ResultRange r) {
+ foreach (col; r.front)
+ test.put(col.as!string);
+ return true;
+ });
+ assert(test.data == "123ABC");
+}
+
+unittest // Different arguments and result types with createFunction
+{
+ auto db = Database(":memory:");
+
+ T display(T)(T value)
+ {
+ return value;
+ }
+
+ db.createFunction("display_integer", &display!int);
+ db.createFunction("display_float", &display!double);
+ db.createFunction("display_text", &display!string);
+ db.createFunction("display_blob", &display!Blob);
+
+ assert(db.execute("SELECT display_integer(42)").oneValue!int == 42);
+ assert(db.execute("SELECT display_float(3.14)").oneValue!double == 3.14);
+ assert(db.execute("SELECT display_text('ABC')").oneValue!string == "ABC");
+ assert(db.execute("SELECT display_blob(x'ABCD')").oneValue!Blob == cast(Blob) hexString!"ABCD");
+
+ assert(db.execute("SELECT display_integer(NULL)").oneValue!int == 0);
+ assert(db.execute("SELECT display_float(NULL)").oneValue!double == 0.0);
+ assert(db.execute("SELECT display_text(NULL)").oneValue!string is null);
+ assert(db.execute("SELECT display_blob(NULL)").oneValue!(Blob) is null);
+}
+
+unittest // Different Nullable argument types with createFunction
+{
+ auto db = Database(":memory:");
+
+ auto display(T : Nullable!U, U...)(T value)
+ {
+ if (value.isNull)
+ return T.init;
+ return value;
+ }
+
+ db.createFunction("display_integer", &display!(Nullable!int));
+ db.createFunction("display_float", &display!(Nullable!double));
+ db.createFunction("display_text", &display!(Nullable!string));
+ db.createFunction("display_blob", &display!(Nullable!Blob));
+
+ assert(db.execute("SELECT display_integer(42)").oneValue!(Nullable!int) == 42);
+ assert(db.execute("SELECT display_float(3.14)").oneValue!(Nullable!double) == 3.14);
+ assert(db.execute("SELECT display_text('ABC')").oneValue!(Nullable!string) == "ABC");
+ assert(db.execute("SELECT display_blob(x'ABCD')").oneValue!(Nullable!Blob) == cast(Blob) hexString!"ABCD");
+
+ assert(db.execute("SELECT display_integer(NULL)").oneValue!(Nullable!int).isNull);
+ assert(db.execute("SELECT display_float(NULL)").oneValue!(Nullable!double).isNull);
+ assert(db.execute("SELECT display_text(NULL)").oneValue!(Nullable!string).isNull);
+ assert(db.execute("SELECT display_blob(NULL)").oneValue!(Nullable!Blob).isNull);
+}
+
+unittest // Callable struct with createFunction
+{
+ import std.functional : toDelegate;
+
+ struct Fun
+ {
+ int factor;
+
+ this(int factor)
+ {
+ this.factor = factor;
+ }
+
+ int opCall(int value)
+ {
+ return value * factor;
+ }
+ }
+
+ auto f = Fun(2);
+ auto db = Database(":memory:");
+ db.createFunction("my_fun", toDelegate(f));
+ assert(db.execute("SELECT my_fun(4)").oneValue!int == 8);
+}
+
+unittest // Callbacks
+{
+ bool wasTraced = false;
+ bool wasProfiled = false;
+ bool hasProgressed = false;
+
+ auto db = Database(":memory:");
+ db.setTraceCallback((string s) { wasTraced = true; });
+ db.execute("SELECT * FROM sqlite_master;");
+ assert(wasTraced);
+ db.setProfileCallback((string s, ulong t) { wasProfiled = true; });
+ db.execute("SELECT * FROM sqlite_master;");
+ assert(wasProfiled);
+
+ db.setProgressHandler(1, { hasProgressed = true; return 0; });
+ db.execute("SELECT * FROM sqlite_master;");
+ assert(hasProgressed);
+}
+
+unittest // Statement.oneValue()
+{
+ Statement statement;
+ {
+ auto db = Database(":memory:");
+ statement = db.prepare(" SELECT 42 ");
+ }
+ assert(statement.execute.oneValue!int == 42);
+}
+
+unittest // Statement.finalize()
+{
+ auto db = Database(":memory:");
+ auto statement = db.prepare(" SELECT 42 ");
+ statement.finalize();
+}
+
+unittest // Simple parameters binding
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (val INTEGER)");
+
+ auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
+ statement.bind(1, 36);
+ statement.clearBindings();
+ statement.bind(1, 42);
+ statement.execute();
+ statement.reset();
+ statement.bind(1, 42);
+ statement.execute();
+
+ assert(db.lastInsertRowid == 2);
+ assert(db.changes == 1);
+ assert(db.totalChanges == 2);
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ assert(row.peek!int(0) == 42);
+}
+
+unittest // Multiple parameters binding
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
+ auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (:i, @f, $t)");
+
+ assert(statement.parameterCount == 3);
+ assert(statement.parameterName(2) == "@f");
+ assert(statement.parameterIndex("$t") == 3);
+ assert(statement.parameterIndex(":foo") == 0);
+
+ statement.bind("$t", "TEXT");
+ statement.bind(":i", 42);
+ statement.bind("@f", 3.14);
+ statement.execute();
+ statement.reset();
+ statement.bind(1, 42);
+ statement.bind(2, 3.14);
+ statement.bind(3, "TEXT");
+ statement.execute();
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ {
+ assert(row.length == 3);
+ assert(row.peek!int("i") == 42);
+ assert(row.peek!double("f") == 3.14);
+ assert(row.peek!string("t") == "TEXT");
+ }
+}
+
+// Binding/peeking structs with `toString` and `fromString`
+unittest
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (val TEXT)");
+
+ static struct ToStringSink {
+ string value;
+ void toString(scope void delegate(in char[]) sink) const
+ {
+ sink(this.value);
+ }
+ }
+
+ static struct ToStringMethod {
+ string value;
+ string toString() const
+ {
+ return this.value;
+ }
+ }
+
+ auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
+ statement.bind(1, ToStringMethod("oldmethod"));
+ statement.clearBindings();
+ statement.bind(1, ToStringMethod("method"));
+ statement.execute();
+ statement.reset();
+ statement.bind(1, ToStringSink("sink"));
+ statement.execute();
+
+ assert(db.lastInsertRowid == 2);
+ assert(db.changes == 1);
+ assert(db.totalChanges == 2);
+
+ auto results = db.execute("SELECT * FROM test");
+ results.equal!((a, b) => a.peek!string(0) == b)(["method", "sink"]);
+}
+
+unittest // Multiple parameters binding: tuples
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
+ auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)");
+ statement.bindAll(42, 3.14, "TEXT");
+ statement.execute();
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ {
+ assert(row.length == 3);
+ assert(row.peek!int(0) == 42);
+ assert(row.peek!double(1) == 3.14);
+ assert(row.peek!string(2) == "TEXT");
+ }
+}
+
+unittest // Binding/peeking integral values
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (val INTEGER)");
+
+ auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
+ statement.inject(cast(byte) 42);
+ statement.inject(42U);
+ statement.inject(42UL);
+ statement.inject('\x2A');
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ assert(row.peek!long(0) == 42);
+}
+
+void foobar() // Binding/peeking floating point values
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (val FLOAT)");
+
+ auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
+ statement.inject(42.0F);
+ statement.inject(42.0);
+ statement.inject(42.0L);
+ statement.inject("42");
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ assert(row.peek!double(0) == 42.0);
+}
+
+unittest // Binding/peeking text values
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (val TEXT);
+ INSERT INTO test (val) VALUES ('I am a text.')");
+
+ auto results = db.execute("SELECT * FROM test");
+ assert(results.front.peek!(string, PeekMode.slice)(0) == "I am a text.");
+ assert(results.front.peek!(string, PeekMode.copy)(0) == "I am a text.");
+
+ import std.exception : assertThrown;
+ import std.variant : VariantException;
+ assertThrown!VariantException(results.front[0].as!Blob);
+}
+
+unittest // Binding/peeking blob values
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (val BLOB)");
+
+ auto statement = db.prepare("INSERT INTO test (val) VALUES (?)");
+ auto array = cast(Blob) [1, 2, 3];
+ statement.inject(array);
+ ubyte[3] sarray = [1, 2, 3];
+ statement.inject(sarray);
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ {
+ assert(row.peek!(Blob, PeekMode.slice)(0) == [1, 2, 3]);
+ assert(row[0].as!Blob == [1, 2, 3]);
+ }
+}
+
+unittest // Struct injecting
+{
+ static struct Test
+ {
+ int i;
+ double f;
+ string t;
+ }
+
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
+ auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)");
+ auto test = Test(42, 3.14, "TEXT");
+ statement.inject(test);
+ statement.inject(Test(42, 3.14, "TEXT"));
+ auto itest = cast(immutable) Test(42, 3.14, "TEXT");
+ statement.inject(itest);
+
+ auto results = db.execute("SELECT * FROM test");
+ assert(!results.empty);
+ foreach (row; results)
+ {
+ assert(row.length == 3);
+ assert(row.peek!int(0) == 42);
+ assert(row.peek!double(1) == 3.14);
+ assert(row.peek!string(2) == "TEXT");
+ }
+}
+
+unittest // Iterable struct injecting
+{
+ import std.range : iota;
+
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (a INTEGER, b INTEGER, c INTEGER)");
+ auto statement = db.prepare("INSERT INTO test (a, b, c) VALUES (?, ?, ?)");
+ statement.inject(iota(0, 3));
+
+ auto results = db.execute("SELECT * FROM test");
+ assert(!results.empty);
+ foreach (row; results)
+ {
+ assert(row.length == 3);
+ assert(row.peek!int(0) == 0);
+ assert(row.peek!int(1) == 1);
+ assert(row.peek!int(2) == 2);
+ }
+}
+
+unittest // Injecting nullable
+{
+ import std.array : array;
+
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (i INTEGER, s TEXT)");
+ auto statement = db.prepare("INSERT INTO test (i, s) VALUES (?, ?)");
+ statement.inject(Nullable!int(1), "one");
+ statement = db.prepare("INSERT INTO test (i) VALUES (?)");
+ statement.inject(Nullable!int.init);
+
+ auto results = db.execute("SELECT i FROM test ORDER BY rowid");
+ assert(results.equal!((a, b) => a.peek!(Nullable!int)(0) == b)(
+ [ Nullable!int(1), Nullable!int.init ] ));
+}
+
+unittest // Injecting tuple
+{
+ import std.typecons : tuple;
+
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)");
+ auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)");
+ statement.inject(tuple(42, 3.14, "TEXT"));
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ {
+ assert(row.length == 3);
+ assert(row.peek!int(0) == 42);
+ assert(row.peek!double(1) == 3.14);
+ assert(row.peek!string(2) == "TEXT");
+ }
+}
+
+unittest // Injecting dict
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (a TEXT, b TEXT, c TEXT)");
+ auto statement = db.prepare("INSERT INTO test (c, b, a) VALUES (:c, :b, :a)");
+ statement.inject([":a":"a", ":b":"b", ":c":"c"]);
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ {
+ assert(row.length == 3);
+ assert(row.peek!string(0) == "a");
+ assert(row.peek!string(1) == "b");
+ assert(row.peek!string(2) == "c");
+ }
+}
+
+unittest // Binding Nullable
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (a, b, c, d, e);");
+
+ auto statement = db.prepare("INSERT INTO test (a,b,c,d,e) VALUES (?,?,?,?,?)");
+ statement.bind(1, Nullable!int(123));
+ statement.bind(2, Nullable!int());
+ statement.bind(3, Nullable!(uint, 0)(42));
+ statement.bind(4, Nullable!(uint, 0)());
+ statement.bind(5, Nullable!bool(false));
+ statement.execute();
+
+ auto results = db.execute("SELECT * FROM test");
+ foreach (row; results)
+ {
+ assert(row.length == 5);
+ assert(row.peek!int(0) == 123);
+ assert(row.columnType(1) == SqliteType.NULL);
+ assert(row.peek!int(2) == 42);
+ assert(row.columnType(3) == SqliteType.NULL);
+ assert(!row.peek!bool(4));
+ }
+}
+
+unittest // Peeking Nullable
+{
+ auto db = Database(":memory:");
+ auto results = db.execute("SELECT 1, NULL, 8.5, NULL");
+ foreach (row; results)
+ {
+ assert(row.length == 4);
+ assert(row.peek!(Nullable!double)(2).get == 8.5);
+ assert(row.peek!(Nullable!double)(3).isNull);
+ assert(row.peek!(Nullable!(int, 0))(0).get == 1);
+ assert(row.peek!(Nullable!(int, 0))(1).isNull);
+ }
+}
+
+unittest // GC anchoring test
+{
+ import core.memory : GC;
+
+ auto db = Database(":memory:");
+ auto stmt = db.prepare("SELECT ?");
+
+ auto str = ("I am test string").dup;
+ stmt.bind(1, str);
+ str = null;
+
+ foreach (_; 0..3)
+ {
+ GC.collect();
+ GC.minimize();
+ }
+
+ ResultRange results = stmt.execute();
+ foreach(row; results)
+ {
+ assert(row.length == 1);
+ assert(row.peek!string(0) == "I am test string");
+ }
+}
+
+version (unittest) // ResultRange is an input range of Row
+{
+ import std.range.primitives : isInputRange, ElementType;
+ static assert(isInputRange!ResultRange);
+ static assert(is(ElementType!ResultRange == Row));
+}
+
+unittest // Statement error
+{
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (val INTEGER NOT NULL)");
+ auto stmt = db.prepare("INSERT INTO test (val) VALUES (?)");
+ stmt.bind(1, null);
+ import std.exception : assertThrown;
+ assertThrown!SqliteException(stmt.execute());
+}
+
+version (unittest) // Row is a random access range of ColumnData
+{
+ import std.range.primitives : isRandomAccessRange, ElementType;
+ static assert(isRandomAccessRange!Row);
+ static assert(is(ElementType!Row == ColumnData));
+}
+
+unittest // Row.init
+{
+ import core.exception : AssertError;
+
+ Row row;
+ assert(row.empty);
+ assertThrown!AssertError(row.front);
+ assertThrown!AssertError(row.back);
+ assertThrown!AssertError(row.popFront);
+ assertThrown!AssertError(row.popBack);
+ assertThrown!AssertError(row[""]);
+ assertThrown!AssertError(row.peek!long(0));
+}
+
+unittest // Peek
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (value);
+ INSERT INTO test VALUES (NULL);
+ INSERT INTO test VALUES (42);
+ INSERT INTO test VALUES (3.14);
+ INSERT INTO test VALUES ('ABC');
+ INSERT INTO test VALUES (x'DEADBEEF');");
+
+ import std.math : isNaN;
+ auto results = db.execute("SELECT * FROM test");
+ auto row = results.front;
+ assert(row.peek!long(0) == 0);
+ assert(row.peek!double(0) == 0);
+ assert(row.peek!string(0) is null);
+ assert(row.peek!Blob(0) is null);
+ results.popFront();
+ row = results.front;
+ assert(row.peek!long(0) == 42);
+ assert(row.peek!double(0) == 42);
+ assert(row.peek!string(0) == "42");
+ assert(row.peek!Blob(0) == cast(Blob) "42");
+ results.popFront();
+ row = results.front;
+ assert(row.peek!long(0) == 3);
+ assert(row.peek!double(0) == 3.14);
+ assert(row.peek!string(0) == "3.14");
+ assert(row.peek!Blob(0) == cast(Blob) "3.14");
+ results.popFront();
+ row = results.front;
+ assert(row.peek!long(0) == 0);
+ assert(row.peek!double(0) == 0.0);
+ assert(row.peek!string(0) == "ABC");
+ assert(row.peek!Blob(0) == cast(Blob) "ABC");
+ results.popFront();
+ row = results.front;
+ assert(row.peek!long(0) == 0);
+ assert(row.peek!double(0) == 0.0);
+ assert(row.peek!string(0) == hexString!"DEADBEEF");
+ assert(row.peek!Blob(0) == cast(Blob) hexString!"DEADBEEF");
+}
+
+unittest // Peeking NULL values
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (val TEXT);
+ INSERT INTO test (val) VALUES (NULL)");
+
+ auto results = db.execute("SELECT * FROM test");
+ assert(results.front.peek!bool(0) == false);
+ assert(results.front.peek!long(0) == 0);
+ assert(results.front.peek!double(0) == 0);
+ assert(results.front.peek!string(0) is null);
+ assert(results.front.peek!Blob(0) is null);
+}
+
+unittest // Row life-time
+{
+ auto db = Database(":memory:");
+ auto row = db.execute("SELECT 1 AS one").front;
+ assert(row[0].as!long == 1);
+ assert(row["one"].as!long == 1);
+}
+
+unittest // PeekMode
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (value);
+ INSERT INTO test VALUES (x'01020304');
+ INSERT INTO test VALUES (x'0A0B0C0D');");
+
+ auto results = db.execute("SELECT * FROM test");
+ auto row = results.front;
+ auto b1 = row.peek!(Blob, PeekMode.copy)(0);
+ auto b2 = row.peek!(Blob, PeekMode.slice)(0);
+ results.popFront();
+ row = results.front;
+ auto b3 = row.peek!(Blob, PeekMode.slice)(0);
+ auto b4 = row.peek!(Nullable!Blob, PeekMode.copy)(0);
+ assert(b1 == cast(Blob) hexString!"01020304");
+ // assert(b2 != cast(Blob) x"01020304"); // PASS if SQLite reuses internal buffer
+ // assert(b2 == cast(Blob) x"0A0B0C0D"); // PASS (idem)
+ assert(b3 == cast(Blob) hexString!"0A0B0C0D");
+ assert(!b4.isNull && b4 == cast(Blob) hexString!"0A0B0C0D");
+}
+
+unittest // Row random-access range interface
+{
+ import std.array : front, popFront;
+
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (a INTEGER, b INTEGER, c INTEGER, d INTEGER);
+ INSERT INTO test VALUES (1, 2, 3, 4);
+ INSERT INTO test VALUES (5, 6, 7, 8);");
+
+ {
+ auto results = db.execute("SELECT * FROM test");
+ auto values = [1, 2, 3, 4, 5, 6, 7, 8];
+ foreach (row; results)
+ {
+ while (!row.empty)
+ {
+ assert(row.front.as!int == values.front);
+ row.popFront();
+ values.popFront();
+ }
+ }
+ }
+
+ {
+ auto results = db.execute("SELECT * FROM test");
+ auto values = [4, 3, 2, 1, 8, 7, 6, 5];
+ foreach (row; results)
+ {
+ while (!row.empty)
+ {
+ assert(row.back.as!int == values.front);
+ row.popBack();
+ values.popFront();
+ }
+ }
+ }
+
+ {
+ auto row = db.execute("SELECT * FROM test").front;
+ row.popFront();
+ auto copy = row.save();
+ row.popFront();
+ assert(row.front.as!int == 3);
+ assert(copy.front.as!int == 2);
+ }
+}
+
+unittest // ColumnData.init
+{
+ import core.exception : AssertError;
+ ColumnData data;
+ assertThrown!AssertError(data.type);
+ assertThrown!AssertError(data.as!string);
+}
+
+unittest // ColumnData-compatible types
+{
+ import std.meta : AliasSeq;
+
+ alias AllCases = AliasSeq!(bool, true, int, int.max, float, float.epsilon,
+ real, 42.0L, string, "おはよう!", const(ubyte)[], [0x00, 0xFF],
+ string, "", Nullable!byte, 42);
+
+ void test(Cases...)()
+ {
+ auto cd = ColumnData(Cases[1]);
+ assert(cd.as!(Cases[0]) == Cases[1]);
+ static if (Cases.length > 2)
+ test!(Cases[2..$])();
+ }
+
+ test!AllCases();
+}
+
+unittest // ColumnData.toString
+{
+ auto db = Database(":memory:");
+ auto rc = db.execute("SELECT 42, 3.14, 'foo_bar', x'00FF', NULL").cached;
+ assert("%(%s%)".format(rc) == "[42, 3.14, foo_bar, [0, 255], null]");
+}
+
+unittest // CachedResults copies
+{
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (msg TEXT);
+ INSERT INTO test (msg) VALUES ('ABC')");
+
+ static getdata(Database db)
+ {
+ return db.execute("SELECT * FROM test").cached;
+ }
+
+ auto data = getdata(db);
+ assert(data.length == 1);
+ assert(data[0][0].as!string == "ABC");
+}
+
+unittest // UTF-8
+{
+ auto db = Database(":memory:");
+ bool ran = false;
+ db.run("SELECT '\u2019\u2019';", (ResultRange r) {
+ assert(r.oneValue!string == "\u2019\u2019");
+ ran = true;
+ return true;
+ });
+ assert(ran);
+}
+
+unittest // loadExtension failure test
+{
+ import std.exception : collectExceptionMsg;
+ auto db = Database(":memory:");
+ auto msg = collectExceptionMsg(db.loadExtension("foobar"));
+ assert(msg.canFind("(not authorized)"));
+}