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)")); }