diff options
Diffstat (limited to 'src/ext_depends/d2sqlite3/source/tests.d')
-rw-r--r-- | src/ext_depends/d2sqlite3/source/tests.d | 931 |
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)")); +} |