1 module tests.dbal_sqlite3.tests; 2 3 import oceandrift.db.dbal; 4 import oceandrift.db.sqlite3; 5 import oceandrift.db.orm; 6 7 @safe: 8 9 void print(SQLiteX ex) @trusted 10 { 11 import std.stdio; 12 13 stderr.writeln( 14 "SQLite3 Exception; status code: ", cast(int) ex.code, " = ", ex.code, 15 "; msg: ", ex.msg, 16 "\n\tEX Trace:", ex.info, "\n\t--- End of Trace" 17 ); 18 } 19 20 struct Person 21 { 22 ulong id = 0; 23 string name; 24 ubyte age; 25 } 26 27 unittest 28 { 29 auto driver = new SQLite3(":memory:", OpenMode.create); 30 driver.connect(); 31 scope (exit) 32 driver.close(); 33 34 driver.execute( 35 `CREATE TABLE "person" (id INTEGER PRIMARY KEY, name TEXT, age INTEGER UNSIGNED)` 36 ); 37 38 auto em = EntityManager!SQLite3(driver); 39 40 { 41 Person p; 42 immutable bool success = driver.get!(Person)(1, p); 43 assert(!success); 44 } 45 46 { 47 Person p; 48 immutable bool success = em.get!(Person)(1, p); 49 assert(!success); 50 } 51 52 driver.execute( 53 `INSERT INTO "person" (name, age) VALUES ("Walter", 65), ("Tom", 30), ("Carl", 60)` 54 ); 55 56 { 57 Person p; 58 immutable bool success = em.get!Person(1, p); 59 assert(success); 60 assert(p.name == "Walter"); 61 assert(p.age == 65); 62 } 63 64 { 65 EntityCollection!Person ec = 66 em.find!Person() 67 .where("age", ComparisonOperator.greaterThanOrEquals, 60) 68 .select() 69 .via(driver); 70 assert(!ec.empty); 71 72 Person p1 = ec.front; 73 assert(p1.name == "Walter"); 74 assert(p1.age >= 60); 75 76 ec.popFront(); 77 assert(!ec.empty); 78 79 Person p2 = ec.front; 80 assert(p2.name == "Carl", p2.name); 81 assert(p2.age >= 60); 82 83 ec.popFront(); 84 assert(ec.empty); 85 } 86 87 { 88 EntityCollection!Person ec = em 89 .find!Person() 90 .where("age", ComparisonOperator.greaterThanOrEquals, 60) 91 .select() 92 .via(driver); 93 assert(!ec.empty); 94 95 Person p1 = ec.front; 96 assert(p1.name == "Walter"); 97 assert(p1.age >= 60); 98 99 ec.popFront(); 100 assert(!ec.empty); 101 102 Person p2 = ec.front; 103 assert(p2.name == "Carl"); 104 assert(p2.age >= 60); 105 106 ec.popFront(); 107 assert(ec.empty); 108 } 109 110 { 111 EntityCollection!Person ec = em 112 .find!Person() 113 .whereParentheses(q => q 114 .where("age", ComparisonOperator.greaterThanOrEquals, 60) 115 ) 116 .select() 117 .via(driver); 118 assert(!ec.empty); 119 120 Person p1 = ec.front; 121 assert(p1.name == "Walter"); 122 assert(p1.age >= 60); 123 124 ec.popFront(); 125 assert(!ec.empty); 126 127 Person p2 = ec.front; 128 assert(p2.name == "Carl"); 129 assert(p2.age >= 60); 130 131 ec.popFront(); 132 assert(ec.empty); 133 } 134 135 { 136 EntityCollection!Person ec = em 137 .find!Person() 138 .whereParentheses(q => q 139 .where("age", ComparisonOperator.greaterThanOrEquals, 60) 140 .limit(1) 141 ) 142 .select() 143 .via(driver); 144 assert(!ec.empty); 145 146 Person p1 = ec.front; 147 assert(p1.name == "Walter"); 148 assert(p1.age >= 60); 149 150 ec.popFront(); 151 assert(ec.empty); 152 } 153 154 { 155 EntityCollection!Person ec = em 156 .find!Person() 157 .whereParentheses( 158 (Query q) => q 159 .where("age", ComparisonOperator.greaterThanOrEquals, 60) 160 .limit(1, 1) 161 ) 162 .select() 163 .via(driver); 164 assert(!ec.empty); 165 166 Person p1 = ec.front; 167 assert(p1.name == "Carl"); 168 assert(p1.age >= 60); 169 170 ec.popFront(); 171 assert(ec.empty); 172 } 173 174 { 175 Person p; 176 immutable bool success = em.get!Person(2, p); 177 assert(success); 178 assert(p.name == "Tom"); 179 assert(p.age == 30); 180 181 em.remove(p); 182 Person p2; 183 immutable bool notRemoved = em.get!Person(2, p2); 184 assert(!notRemoved); 185 186 Person p3; 187 immutable bool p3stillThere = em.get!Person(3, p3); 188 assert(p3stillThere); 189 assert(p3.name == "Carl"); 190 } 191 192 { 193 auto p = Person(0, "Peter", 45); 194 195 em.save(p); 196 assert(p.id == 4); // 1-4 is used by previous rows 197 198 Person tmp; 199 immutable personFound = em.get!Person(4, tmp); 200 assert(personFound); 201 202 p.age += 1; 203 em.save(p); 204 assert(p.id == 4); // ID remains unchanged 205 206 Person tmp2; 207 immutable personFoundAgain = em.get!Person(p.id, tmp2); 208 assert(personFoundAgain); 209 assert(tmp2.age == 46); // update propagated 210 211 immutable idxCopy = em.store(p); // store a copy 212 assert(idxCopy == 5); 213 214 em.update(p); 215 } 216 } 217 218 class Mountain 219 { 220 ulong id = 0; 221 string name; 222 string location; 223 uint height; 224 225 public this() // ORM 226 { 227 } 228 229 public this(string name, string location, uint height) 230 { 231 this.name = name; 232 this.location = location; 233 this.height = height; 234 } 235 } 236 237 static assert(isEntityType!Mountain); 238 239 unittest 240 { 241 auto db = new SQLite3(":memory:", OpenMode.create); 242 db.connect(); 243 scope (exit) 244 db.close(); 245 246 db.execute( 247 `CREATE TABLE "mountain" (id INTEGER PRIMARY KEY, name TEXT, location TEXT, height INTEGER UNSIGNED)` 248 ); 249 250 auto em = EntityManager!SQLite3(db); 251 252 { 253 auto mt1 = new Mountain("Hill 1", "Nowhere", 3000); 254 em.save(mt1); 255 auto mt2 = new Mountain("Snowmountain", "Elsewhere", 4111); 256 em.save(mt2); 257 auto mt3 = new Mountain("Mt. Skyscrape", "Somewhere", 4987); 258 em.save(mt3); 259 auto mt4 = new Mountain("Little Hill", "Nowhere", 1200); 260 em.save(mt4); 261 auto mt5 = new Mountain("K2010", "Nowhere", 2010); 262 em.save(mt5); 263 auto mt6 = new Mountain("Icy Heights", "Elsewhere", 3201); 264 em.save(mt6); 265 auto mt7 = new Mountain("Mt. Nowhere", "Nowhere", 6408); 266 em.save(mt7); 267 268 Statement stmt = db.prepare("SELECT COUNT(*) FROM mountain"); 269 stmt.execute(); 270 assert(stmt.front[0].getAs!int == 7); 271 } 272 273 { 274 enum PreCollection!(Mountain, SQLite3) pcMt4000 = 275 em.find!Mountain() 276 .where("height", ComparisonOperator.greaterThanOrEquals, 4000); 277 278 enum BuiltPreCollection!Mountain bpcMt4000 = pcMt4000.select(); 279 280 EntityCollection!Mountain mt4000 = db.map(bpcMt4000); 281 282 assert(!mt4000.empty); 283 284 int n = 0; 285 foreach (Mountain mt; mt4000) 286 { 287 assert(mt.height >= 4000); 288 ++n; 289 } 290 assert(n == 3); 291 } 292 293 { 294 static immutable int cap = 3000; 295 static immutable string loc = "Nowhere"; 296 enum PreCollection!(Mountain, SQLite3) pc = em.find!Mountain() 297 .where("height", '<', cap) 298 .where("location", '=', loc); 299 300 immutable ulong cnt = pc.countVia(db); 301 assert(cnt == 2); 302 303 enum BuiltQuery qCntCT = pc.count(); 304 Statement stmt = db.prepareBuiltQuery(qCntCT); 305 stmt.execute(); 306 immutable cnt2 = stmt.front[0].getAs!ulong; 307 assert(cnt2 == 2); 308 } 309 310 { 311 static immutable string loc = "Elsewhere"; 312 enum PreCollection!(Mountain, SQLite3) pc = em.find!Mountain().where("location", '=', loc); 313 314 immutable DBValue maxV = pc.aggregateVia(AggregateFunction.max, "height", db); 315 316 immutable int max = maxV.getAs!int; 317 assert(max == 4111); 318 } 319 320 { 321 enum BuiltQuery bq = em 322 .find!Mountain() 323 .aggregate(AggregateFunction.min, "height"); 324 325 Statement stmt = db.prepareBuiltQuery(bq); 326 stmt.execute(); 327 assert(!stmt.empty); 328 329 immutable int min = stmt.front[0].getAs!int; 330 assert(min == 1200); 331 } 332 333 { 334 enum BuiltPreCollection!Mountain bpc = em 335 .find!Mountain() 336 .limit(1) 337 .select(); 338 339 EntityCollection!Mountain ec = bpc.via(db); 340 assert(!ec.empty); 341 342 ec.popFront(); 343 assert(ec.empty); 344 } 345 346 { 347 enum BuiltPreCollection!Mountain bpc = em 348 .find!Mountain() 349 .limit(3) 350 .select(); 351 352 EntityCollection!Mountain ec = bpc.via(db); 353 assert(!ec.empty); 354 355 ec.popFront(); 356 assert(!ec.empty); 357 358 ec.popFront(); 359 assert(!ec.empty); 360 361 ec.popFront(); 362 assert(ec.empty); 363 } 364 365 { 366 enum BuiltPreCollection!Mountain bpc = em 367 .find!Mountain() 368 .orderBy("height") 369 .limit(2, 3) 370 .select(); 371 372 EntityCollection!Mountain ec = bpc.via(db); 373 assert(!ec.empty); 374 375 const Mountain m1 = ec.front; 376 assert(m1.height == 3201); 377 378 ec.popFront(); 379 assert(!ec.empty); 380 const Mountain m2 = ec.front; 381 assert(m2.name == "Snowmountain"); 382 383 ec.popFront(); 384 assert(ec.empty); 385 } 386 387 { 388 enum BuiltPreCollection!Mountain bpc = em 389 .find!Mountain() 390 .where("height", '<') 391 .orderBy("height") 392 .select(); 393 394 PreparedCollection!Mountain pc = bpc.prepareCollection(db); 395 pc.bind(0, 3000); 396 EntityCollection!Mountain ec = pc.execute(); 397 assert(!ec.empty); 398 399 const Mountain m1 = ec.front; 400 assert(m1.height == 1200); 401 402 ec.popFront(); 403 assert(!ec.empty); 404 const Mountain m2 = ec.front; 405 assert(m2.name == "K2010"); 406 407 ec.popFront(); 408 assert(ec.empty); 409 } 410 411 { 412 EntityCollection!Mountain ec = em 413 .find!Mountain() 414 .orderBy("height", desc) 415 .limit(1) 416 .selectVia(db); 417 418 assert(!ec.empty); 419 420 const Mountain highest = ec.front; 421 assert(highest.name == "Mt. Nowhere"); 422 423 ec.popFront(); 424 assert(ec.empty); 425 } 426 427 { 428 static immutable string loc = "Elsewhere"; 429 enum PreCollection!(Mountain, SQLite3) pc = em.find!Mountain().where("location", '=', loc); 430 enum BuiltQuery bq = pc.delete_(); 431 432 Statement stmt = db.prepareBuiltQuery(bq); 433 stmt.execute(); 434 assert(stmt.empty); 435 } 436 437 { 438 em.find!Mountain().where("height", '<', 3000).deleteVia(db); 439 440 ulong left = em.find!Mountain().countVia(db); 441 assert(left == 3); 442 443 EntityCollection!Mountain mtsLeft = em.find!Mountain().orderBy("height").selectVia(db); 444 445 assert(!mtsLeft.empty); 446 assert(mtsLeft.front.name == "Hill 1"); 447 448 mtsLeft.popFront(); 449 assert(!mtsLeft.empty); 450 assert(mtsLeft.front.name == "Mt. Skyscrape"); 451 452 mtsLeft.popFront(); 453 assert(!mtsLeft.empty); 454 assert(mtsLeft.front.name == "Mt. Nowhere"); 455 456 mtsLeft.popFront(); 457 assert(mtsLeft.empty); 458 } 459 } 460 461 unittest // many2one + one2many 462 { 463 auto db = new SQLite3(":memory:", OpenMode.create); 464 db.connect(); 465 scope (exit) 466 db.close(); 467 468 db.execute( 469 `CREATE TABLE "author" ( 470 "id" INTEGER PRIMARY KEY, 471 "name" TEXT, 472 "birthday" DATETIME 473 );` 474 ); 475 476 db.execute( 477 `CREATE TABLE "book" ( 478 "id" INTEGER PRIMARY KEY, 479 "name" TEXT, 480 "published" DATE, 481 "author_id" INTEGER UNSIGNED REFERENCES author(id) 482 );` 483 ); 484 485 auto em = EntityManager!SQLite3(db); 486 487 struct Author 488 { 489 string name; 490 mixin EntityID; 491 } 492 493 struct Book 494 { 495 string name; 496 Date published; 497 ulong authorID; 498 mixin EntityID; 499 } 500 501 { 502 auto adam = Author("Adam D. Ruppe"); 503 em.save(adam); 504 auto someone = Author("Some One"); 505 em.save(someone); 506 auto nobody = Author("No Body"); 507 em.save(nobody); 508 509 em.store(Book("D Cookbook", Date(2014, 5, 26), adam.id)); 510 em.store(Book("Funny Things", Date(2016, 2, 4), someone.id)); 511 em.store(Book("Very funny Things", Date(2017, 3, 3), someone.id)); 512 em.store(Book("Fancy Things", Date(2017, 9, 9), someone.id)); 513 em.store(Book("Other Things", Date(2018, 12, 20), someone.id)); 514 em.store(Book("Stories", Date(2012, 7, 1), nobody.id)); 515 em.store(Book("Stories, vol.2", Date(2018, 4, 8), nobody.id)); 516 em.store(Book("Stories, vol.3", Date(2019, 8, 10), nobody.id)); 517 em.store(Book("Much more Stories", Date(2020, 11, 11), nobody.id)); 518 } 519 520 { 521 Book b; 522 immutable bookFound = em.get(4, b); 523 assert(bookFound); 524 525 Author a; 526 immutable authorFound = em.manyToOne!Author(b, a); 527 assert(authorFound); 528 assert(a.name == "Some One"); 529 530 assert(a.id == b.authorID); 531 assert(a.name == "Some One"); 532 } 533 534 { 535 auto authorsFound = em.find!Author.where("name", '=', "No Body").selectVia(db); 536 assert(!authorsFound.empty); 537 538 Author a = authorsFound.front; 539 assert(a.name == "No Body"); 540 541 authorsFound.popFront(); 542 assert(authorsFound.empty); 543 544 auto pcBooks = em.oneToMany!Book(a); 545 546 immutable ulong cntBooks = pcBooks.countVia(db); 547 assert(cntBooks == 4); 548 549 EntityCollection!Book booksSince2019 = pcBooks 550 .where("published", '>', Date(2019, 1, 1)) 551 .orderBy("published") 552 .selectVia(db); 553 assert(!booksSince2019.empty); 554 555 assert(booksSince2019.front.authorID == a.id); 556 assert(booksSince2019.front.published.year >= 2019); 557 assert(booksSince2019.front.name == "Stories, vol.3"); 558 559 booksSince2019.popFront(); 560 assert(!booksSince2019.empty); 561 562 assert(booksSince2019.front.authorID == a.id); 563 assert(booksSince2019.front.published.year >= 2019); 564 assert(booksSince2019.front.name == "Much more Stories"); 565 566 booksSince2019.popFront(); 567 assert(booksSince2019.empty); 568 } 569 } 570 571 unittest // many2many 572 { 573 auto db = new SQLite3(":memory:", OpenMode.create); 574 db.connect(); 575 scope (exit) 576 db.close(); 577 578 struct Thing 579 { 580 string name; 581 mixin EntityID; 582 } 583 584 struct Tag 585 { 586 string name; 587 mixin EntityID; 588 } 589 590 assert(joinTableName!(Thing, Tag) == "tag_thing"); 591 assert(joinTableName!(Tag, Thing) == "tag_thing"); 592 593 db.execute( 594 `CREATE TABLE "thing" ( 595 "id" INTEGER PRIMARY KEY, 596 "name" TEXT 597 );` 598 ); 599 600 db.execute( 601 `CREATE TABLE "tag" ( 602 "id" INTEGER PRIMARY KEY, 603 "name" TEXT 604 );` 605 ); 606 607 db.execute( 608 `CREATE TABLE "tag_thing" ( 609 "tag_id" INTEGER REFERENCES tag(id), 610 "thing_id" INTEGER REFERENCES thing(id), 611 PRIMARY KEY ("tag_id", "thing_id") 612 );` 613 ); 614 615 auto em = EntityManager!SQLite3(db); 616 617 auto fruit = Tag("Fruit"); 618 em.save(fruit); 619 auto berry = Tag("Berry"); 620 em.save(berry); 621 auto red = Tag("red"); 622 em.save(red); 623 624 auto apple = Thing("Apple"); 625 em.save(apple); 626 em.manyToManyAssign(fruit, apple); 627 em.manyToManyAssign(red, apple); 628 auto pear = Thing("Pear"); 629 em.save(pear); 630 em.manyToManyAssign(fruit, pear); 631 auto plum = Thing("Plum"); 632 em.save(plum); 633 em.manyToManyAssign(fruit, plum); 634 auto banana = Thing("Banana"); 635 em.save(banana); 636 em.manyToManyAssign(fruit, banana); 637 auto raspberry = Thing("Raspberry"); 638 em.save(raspberry); 639 em.manyToManyAssign(raspberry, fruit); 640 em.manyToManyAssign(raspberry, berry); 641 auto blueberry = Thing("Blueberry"); 642 em.save(blueberry); 643 em.manyToManyAssign(blueberry, fruit); 644 em.manyToManyAssign(blueberry, berry); 645 auto strawberry = Thing("Strawberry"); 646 em.save(strawberry); 647 em.manyToManyAssign(strawberry, fruit); 648 em.manyToManyAssign(strawberry, berry); 649 em.manyToManyAssign(strawberry, red); 650 auto book = Thing("Book"); 651 em.save(book); 652 653 assert(em.manyToMany!Tag(apple).countVia(db) == 2); 654 assert(em.manyToMany!Tag(pear).countVia(db) == 1); 655 assert(em.manyToMany!Tag(strawberry).countVia(db) == 3); 656 assert(em.manyToMany!Tag(book).countVia(db) == 0); 657 658 assert(em.manyToMany!Thing(fruit).countVia(db) == 7); 659 assert(em.manyToMany!Thing(berry).countVia(db) == 3); 660 assert(em.manyToMany!Thing(red).countVia(db) == 2); 661 662 { 663 em.manyToManyAssign(book, fruit); // actually wrong… 664 assert(em.manyToMany!Tag(book).countVia(db) == 1); 665 666 em.manyToManyUnassign(book, fruit); // …so unassign 667 assert(em.manyToMany!Tag(book).countVia(db) == 0); 668 } 669 }