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 }