1 /** 2 Copyright: © 2015 Chris Barnes 3 License: The MIT License, see license file 4 Authors: Chris Barnes 5 6 See_also: 7 quill.mapper; 8 quill.variant_mapper; 9 */ 10 module quill.database; 11 12 import ddbc.all; 13 import std.array; 14 import std.regex; 15 import std.string; 16 import std.traits; 17 import std.variant; 18 import quill.mapper; 19 import quill.variant_mapper; 20 21 /** 22 Represents a database that can execute various kinds of SQL Queries 23 */ 24 class Database 25 { 26 /** 27 Creates a new Database given an existing DataSource 28 */ 29 this(DataSource datasource) 30 { 31 this.datasource = datasource; 32 } 33 34 version(USE_SQLITE) 35 { 36 /** 37 Creates a new SQLite Database 38 */ 39 this(string path) 40 { 41 string[string] params; 42 SQLITEDriver sqliteDriver = new SQLITEDriver(); 43 this.datasource = new ConnectionPoolDataSourceImpl(sqliteDriver, path, params); 44 } 45 } 46 47 version(USE_PGSQL) 48 { 49 /** 50 Creates a new PostgreSQL Database. 51 */ 52 this(string host, ushort port, string name, string username, string password, bool ssl) 53 { 54 string[string] params; 55 PGSQLDriver driver = new PGSQLDriver(); 56 string url = PGSQLDriver.generateUrl(host, port, name); 57 params["user"] = username; 58 params["password"] = password; 59 params["ssl"] = ssl ? "true" : "false"; 60 this.datasource = new ConnectionPoolDataSourceImpl(driver, url, params); 61 } 62 } 63 64 version(USE_MYSQL) 65 { 66 /** 67 Creates a new MySQL Database. 68 */ 69 this(string host, ushort port, string name, string username, string password) 70 { 71 string[string] params; 72 MySQLDriver driver = new MySQLDriver(); 73 string url = MySQLDriver.generateUrl(host, port, name); 74 params = MySQLDriver.setUserAndPassword(username, password); 75 this.datasource = new ConnectionPoolDataSourceImpl(driver, url, params); 76 } 77 } 78 79 /** 80 Import a SQL string from a file 81 Params: 82 path = path to a sql file that will be imported 83 Returns: 84 a string of the SQL in the file 85 */ 86 string sql(string path)() 87 { 88 return import(path); 89 } 90 91 /* No Parameters */ 92 93 /** 94 Execute a SQL statment with no parameters and no return 95 Params: 96 sql = SQL statement to execute 97 Examples: 98 --- 99 database.execute("insert into models values('value');") 100 --- 101 */ 102 void execute(string sql) 103 { 104 auto connection = this.datasource.getConnection(); 105 scope(exit) connection.close(); 106 auto statement = connection.createStatement(); 107 scope(exit) statement.close(); 108 statement.executeUpdate(sql); 109 } 110 111 /** 112 Execute a SQL statment from a file with no parameters and no return 113 Params: 114 path = path to SQL file 115 Examples: 116 --- 117 database.execute!("queries/statement.sql")(); 118 --- 119 */ 120 void execute(string path)() 121 { 122 this.execute(this.sql!(path)()); 123 } 124 125 /** 126 Execute a SQL statement that returns a single row 127 Params: 128 T = type to map columns to 129 sql = SQL statement to execute 130 Returns: 131 new instance of T with fields mapped to columns 132 Examples: 133 --- 134 audo model = database.single!(Model)("select * from models limit 1;"); 135 --- 136 */ 137 T single(T)(string sql) 138 { 139 auto connection = this.datasource.getConnection(); 140 scope(exit) connection.close(); 141 142 auto statement = connection.createStatement(); 143 scope(exit) statement.close(); 144 145 ResultSet resultSet = statement.executeQuery(sql); 146 147 auto mapper = new Mapper!(T)(resultSet); 148 return mapper.mapOne(); 149 } 150 151 /** 152 Execute a SQL statment from a file that returns a single row 153 Params: 154 T = type to map columns to 155 path = path to SQL file 156 Returns: 157 new instance of T with fields mapped to columns 158 Examples: 159 --- 160 audo model = database.single!(Model, "queries/statement.sql")(); 161 --- 162 */ 163 T single(T, string path)() 164 { 165 return this.single!(T)(this.sql!(path)); 166 } 167 168 /** 169 Execute a SQL statment that returns multiple rows 170 Params: 171 T = type to map columns to 172 sql = SQL statement to execute 173 Returns: 174 new array of T with the fields for each T mapped to columns 175 Examples: 176 --- 177 auto models = database.list!(Model)("select * from models;"); 178 --- 179 */ 180 T[] list(T)(string sql) 181 { 182 auto connection = this.datasource.getConnection(); 183 scope(exit) connection.close(); 184 185 auto statement = connection.createStatement(); 186 scope(exit) statement.close(); 187 188 ResultSet resultSet = statement.executeQuery(sql); 189 190 auto mapper = new Mapper!(T)(resultSet); 191 return mapper.mapArray(); 192 } 193 194 /** 195 Execute a SQL statement from a file that returns multiple rows 196 Params: 197 T = type to map columns to 198 path = path to SQL file 199 Returns 200 new array of T with the fields for each T mapped to columns 201 Examples: 202 --- 203 auto models = database.list!(Model, "queries/statement.sql")(); 204 --- 205 */ 206 T[] list(T, string path)() 207 { 208 return this.list!(T)(this.sql!(path)()); 209 } 210 211 /* Variant Parameters */ 212 213 /** 214 Execute a SQL statment that takes a single Variant parameter 215 Params: 216 sql = SQL statement to execute 217 parameter = a single Variant parameter 218 Examples: 219 --- 220 database.execute("insert into models(name) values(?);", Variant("name")); 221 --- 222 */ 223 void execute(string sql, Variant parameter) 224 { 225 this.execute(sql, [parameter]); 226 } 227 228 /** 229 Execute a SQL statement from a file that takes a single Variant parameter 230 Params: 231 path = path to SQL file 232 parameter = a single Variant parameter 233 Examples: 234 --- 235 database.execute!("queries/statement.sql")(Variant("value")); 236 --- 237 */ 238 void execute(string path)(Variant parameter) 239 { 240 this.execute(this.sql!(path)(), parameter); 241 } 242 243 /** 244 Execute a SQL statement that takes many Variant parameters 245 Params: 246 sql = SQL statement to execute 247 parameters = many ordered Variant parameters 248 Examples: 249 --- 250 database.execute("insert into models(name, description) values(?, ?)", variantArray("value one", "value two")); 251 --- 252 */ 253 void execute(string sql, Variant[] parameters) 254 { 255 auto connection = this.datasource.getConnection(); 256 scope(exit) connection.close(); 257 258 auto statement = connection.prepareStatement(sql); 259 scope(exit) statement.close(); 260 261 auto mapper = new VariantMapper(statement); 262 mapper.map(parameters); 263 statement.executeUpdate(); 264 } 265 266 /** 267 Execute a SQL statement from a file that takes many Variant parameters 268 Params: 269 path = path to SQL file 270 parameters = many ordered Variant parameters 271 Examples: 272 --- 273 database.execute!("queries/statement.sql")(variantArray("value one", "value two")); 274 --- 275 */ 276 void execute(string path)(Variant[] parameters) 277 { 278 this.execute(this.sql!(path)(), parameters); 279 } 280 281 /** 282 Execute a SQL statement that takes a Variant parameter and returns a single row 283 Params: 284 T = type to map columns to 285 sql = SQL statement to execute 286 parameter = single Variant parameter 287 Returns: 288 new instance of T with fields mapped to columns 289 Examples: 290 --- 291 auto model = database.single!(Model)("select * from models where id = ?;", Variant(4)); 292 --- 293 */ 294 T single(T)(string sql, Variant parameter) 295 { 296 return this.single!(T)(sql, [parameter]); 297 } 298 299 /** 300 Execute a SQL statement from a file that takes a Variant parameter and returns a single row 301 Params: 302 T = type to map columns to 303 path = path to SQL file 304 parameter = single Variant parameter 305 Returns: 306 new instance of T with fields mapped to columns 307 Examples: 308 --- 309 auto model = database.single!(Model, "queries/statement.sql")(Variant(4)); 310 --- 311 */ 312 T single(T, string path)(Variant parameter) 313 { 314 return this.single!(T)(this.sql!(path)(), parameter); 315 } 316 317 /** 318 Execute a SQL statement that takes many Variant parameters and returns a single row 319 Params: 320 T = type to map columns to 321 sql = SQL statement to execute 322 parameters = many ordered Variant parameters 323 Returns: 324 new instance of T with fields mapped to columns 325 Examples: 326 --- 327 auto model = database.single!(Model)("select * from models where name = ? and description = ?", variantArray("value one", "value two")); 328 --- 329 */ 330 T single(T)(string sql, Variant[] parameters) 331 { 332 auto connection = this.datasource.getConnection(); 333 scope(exit) connection.close(); 334 335 auto statement = connection.prepareStatement(sql); 336 scope(exit) statement.close(); 337 338 auto parameterMapper = new VariantMapper(statement); 339 parameterMapper.map(parameters); 340 341 ResultSet resultSet = statement.executeQuery(); 342 343 auto resultMapper = new Mapper!(T)(resultSet); 344 return resultMapper.mapOne(); 345 } 346 347 /** 348 Execute a SQL statement from a file that takes many Variant parameters and returns a single row 349 Params: 350 T = type to map columns to 351 path = path to SQL file 352 parameters = many ordered Variant parameters 353 Returns: 354 new instance of T with fields mapped to columns 355 Examples: 356 --- 357 auto model = database.single!(Model, "queries/statement.sql")(variantArray("value one", "value two")); 358 --- 359 */ 360 T single(T, string path)(Variant[] parameters) 361 { 362 return this.single!(T)(this.sql!(path)(), parameters); 363 } 364 365 /** 366 Execute a SQL statement that takes a single Variant parameter and returns many rows 367 Params: 368 T = type to map columns to 369 sql = SQL statement to execute 370 parameter = single Variant parameter 371 Returns: 372 new array of T with the fields for each T mapped to columns 373 Examples: 374 --- 375 auto models = database.list!(Model)("select * from models where name = ?;", Variant("value")) 376 --- 377 */ 378 T[] list(T)(string sql, Variant parameter) 379 { 380 return this.list!(T)(sql, [parameter]); 381 } 382 383 /** 384 Execute a SQL statement from a file that takes a single Variant parameter and returns many rows 385 Params: 386 T = type to map columns to 387 path = path to SQL file 388 parameter = single Variant parameter 389 Returns: 390 new array of T with the fields for each T mapped to columns 391 Examples: 392 --- 393 auto models = database.list!(Model, "queries/statement.sql")(Variant("value")); 394 --- 395 */ 396 T[] list(T, string path)(Variant parameter) 397 { 398 return this.list!(T)(this.sql!(path)(), parameter); 399 } 400 401 /** 402 Execute a SQL statement that takes many Variant parameters and returns many rows 403 Params: 404 T = type to map columns to 405 sql = SQL statement to execute 406 parameters = many ordered Variant parameters 407 Returns: 408 new array of T with the fields for each T mapped to columns 409 Examples: 410 --- 411 auto models = database.list!(Model)("select * from models where name = ? and description = ?;", variantArray("value one", "value two")); 412 --- 413 */ 414 T[] list(T)(string sql, Variant[] parameters) 415 { 416 auto connection = this.datasource.getConnection(); 417 scope(exit) connection.close(); 418 419 auto statement = connection.prepareStatement(sql); 420 scope(exit) statement.close(); 421 422 auto parameterMapper = new VariantMapper(statement); 423 parameterMapper.map(parameters); 424 425 ResultSet resultSet = statement.executeQuery(); 426 427 auto resultMapper = new Mapper!(T)(resultSet); 428 return resultMapper.mapArray(); 429 } 430 431 /** 432 Execute a SQL statement from a file that takes many Variant parameters and returns many rows 433 Params: 434 T = type to map columns to 435 path = path to SQL file 436 parameters = many ordered Variant parameters 437 Returns: 438 new array of T with the fields for each T mapped to columns 439 Examples: 440 --- 441 auto models = database.list!(Model, "queries/statement.sql")(variantArray("value one", "value two")); 442 --- 443 */ 444 T[] list(T, string path)(Variant[] parameters) 445 { 446 return this.list!(T)(this.sql!(path)(), parameters); 447 } 448 449 /* Model Parameters */ 450 451 /** 452 Execute a SQL statement that takes a model parameter 453 Params: 454 T = type to map parameters to 455 sql = SQL statement to execute 456 model = model to map parameters to 457 Examples: 458 --- 459 class Model 460 { 461 int id; 462 @(bind("name")) string name; 463 } 464 auto model = new Model(); 465 model.name = "value"; 466 database.execute!(Model)("insert into models(name) values(?(name));", model); 467 --- 468 */ 469 void execute(T)(string sql, T model) 470 { 471 string[] map = this.parseBindValues!(T)(sql, model); 472 auto connection = this.datasource.getConnection(); 473 scope(exit) connection.close(); 474 475 auto statement = connection.prepareStatement(sql); 476 scope(exit) statement.close(); 477 478 auto mapper = new Mapper!(T)(statement); 479 mapper.map(model, map); 480 statement.executeUpdate(); 481 } 482 483 /** 484 Execute a SQL statement from a file that takes a model parameter 485 Params: 486 T = type to map parameters to 487 path = path to SQL file 488 model = model to map parameters to 489 Examples: 490 --- 491 class Model 492 { 493 int id; 494 @(bind("name")) string name; 495 } 496 auto model = new Model(); 497 model.name = "value"; 498 database.execute!(Model, "queries/statement.sql", model); 499 --- 500 */ 501 void execute(T, string path)(T model) 502 { 503 this.execute!(T)(this.sql!(path)(), model); 504 } 505 506 /** 507 Execute a SQL statement that takes a model parameter and returns a single row 508 Params: 509 T = type to map parameters and columns to 510 sql = SQL statement to execute 511 model = model to map parameters and columns to 512 Returns: 513 new instance of T with fields mapped to columns 514 Examples: 515 --- 516 class Model 517 { 518 int id; 519 @(bind("name")) string name; 520 } 521 auto model = new Model(); 522 model.id = 4; 523 auto fetchedModel = database.single!("select * from models where id = ?;", model); 524 --- 525 */ 526 T single(T)(string sql, T model) 527 { 528 string[] map = this.parseBindValues!(T)(sql, model); 529 auto connection = this.datasource.getConnection(); 530 scope(exit) connection.close(); 531 532 auto statement = connection.prepareStatement(sql); 533 scope(exit) statement.close(); 534 535 auto parameterMapper = new Mapper!(T)(statement); 536 parameterMapper.map(model, map); 537 538 ResultSet resultSet = statement.executeQuery(); 539 540 auto resultMapper = new Mapper!(T)(resultSet); 541 return resultMapper.mapOne(); 542 } 543 544 /** 545 Execute a SQL statement from a file that takes a model parameter and returns a single row 546 Params: 547 T = type to map parameters and columns to 548 path = path to SQL file 549 model = model to map parameters and columns to 550 Returns: 551 new instance of T with fields mapped to columns 552 Examples: 553 --- 554 class Model 555 { 556 int id; 557 @(bind("name")) string name; 558 } 559 auto model = new Model(); 560 model.id = 4; 561 auto model = database.single!(Model, "queries/statement.sql", model); 562 --- 563 */ 564 T single(T, string path)(T model) 565 { 566 return this.single!(T)(this.sql!(path)(), model); 567 } 568 569 /** 570 Execute a SQL statement that takes a model parameter and returns many rows 571 Params: 572 T = type to map parameters and columns to 573 sql = SQL statement to execute 574 model = model to map parameters and columns to 575 Returns: 576 new array of T with the fields for each T mapped to columns 577 Examples: 578 --- 579 class Model 580 { 581 int id; 582 @(bind("name")) string name; 583 } 584 auto model = new Model(); 585 model.name = "value"; 586 auto models = database.list!(Model)("select * from models where name = ?(name);", model); 587 --- 588 */ 589 T[] list(T)(string sql, T model) 590 { 591 string[] map = this.parseBindValues!(T)(sql, model); 592 auto connection = this.datasource.getConnection(); 593 scope(exit) connection.close(); 594 595 auto statement = connection.prepareStatement(sql); 596 scope(exit) statement.close(); 597 598 auto parameterMapper = new Mapper!(T)(statement); 599 parameterMapper.map(model, map); 600 601 ResultSet resultSet = statement.executeQuery(); 602 603 auto resultMapper = new Mapper!(T)(resultSet); 604 return resultMapper.mapArray(); 605 } 606 607 /** 608 Execute a SQL statement from a file that takes a model parameter and returns many rows 609 Params: 610 T = type to map parameters and columns to 611 path = path to SQL file 612 model = model to map parameters and columns to 613 Returns: 614 new array of T with the fields for each T mapped to columns 615 Examples: 616 --- 617 class Model 618 { 619 int id; 620 @(bind("name")) string name; 621 } 622 auto model = new Model(); 623 model.name = "value"; 624 auto models = database.list!(Model, "queries/statement.sql")(model); 625 --- 626 */ 627 T[] list(T, string path)(T model) 628 { 629 return this.list!(T)(this.sql!(path)(), model); 630 } 631 632 private: 633 DataSource datasource; 634 635 /** 636 Replace ?(parameter_name) with ? and return an array indicating the order of the named parameters 637 Params: 638 T = type of model to map parameters to 639 sql = raw SQL string 640 model = model to be mapped into the PreparedStatement 641 Returns: 642 An array where the index matches the parameter index and the value matches the name of the parameter 643 */ 644 string[] parseBindValues(T)(ref string sql, T model) 645 { 646 auto appender = appender!(string[])(); 647 648 foreach(match; matchAll(sql, regex(r"\?\(([^)]+)\)"))) 649 { 650 appender.put(chompPrefix(chop(match.hit), "?(")); 651 sql = sql.replace(match.hit, "?"); 652 } 653 return appender.data; 654 } 655 } 656 657 version(unittest) 658 { 659 import quill.attributes; 660 import std.datetime; 661 import std.conv; 662 663 Database createSqliteConnection() 664 { 665 string[string] sqlLiteParams; 666 SQLITEDriver sqliteDriver = new SQLITEDriver(); 667 DataSource sqlite3DataSource = new ConnectionPoolDataSourceImpl(sqliteDriver, "test.sqlite3", sqlLiteParams); 668 DataSource datasource = new ConnectionPoolDataSourceImpl(sqliteDriver, "test.sqlite3", sqlLiteParams); 669 670 auto db = new Database(datasource); 671 string sql = " 672 create table if not exists models( 673 id integer primary key autoincrement not null, 674 name text not null, 675 title text 676 ); 677 "; 678 db.execute(sql); 679 return db; 680 } 681 682 Database createPostgresConnection() 683 { 684 auto db = new Database("127.0.0.1", to!(ushort)(54320), "testdb", "admin", "password", true); 685 string sql = " 686 create table if not exists models( 687 id serial primary key, 688 name varchar(100), 689 title varchar(100) 690 ); 691 "; 692 db.execute(sql); 693 return db; 694 } 695 696 Database createMysqlConnection() 697 { 698 auto db = new Database("127.0.0.1", to!(ushort)(33060), "testdb", "admin", "password"); 699 700 string sql = " 701 create table if not exists models( 702 id mediumint not null auto_increment primary key, 703 name varchar(100), 704 title varchar(100) 705 ); 706 "; 707 db.execute(sql); 708 return db; 709 } 710 711 void teardown(Database[] dbs) 712 { 713 foreach(db; dbs) 714 { 715 db.execute("drop table if exists models;"); 716 } 717 } 718 719 class Model 720 { 721 @(bind("id")) int id; 722 @(bind("name")) string name; 723 @(bind("title")) string title; 724 725 this(string name) 726 { 727 this.name = name; 728 } 729 730 this() { } 731 } 732 733 Database[] all() 734 { 735 return [createSqliteConnection(), createPostgresConnection(), createMysqlConnection()]; 736 } 737 } 738 739 /* No Parameters */ 740 741 // T single(T)(string); 742 unittest 743 { 744 auto dbs = all(); 745 scope(exit) teardown(dbs); 746 foreach(db; dbs) 747 { 748 db.execute!("none/insert.sql")(); 749 Model model = db.single!(Model, "none/single.sql")(); 750 assert(model.name == "Some Name"); 751 } 752 } 753 754 // T[] list(T)(string); 755 unittest 756 { 757 auto dbs = all(); 758 scope(exit) teardown(dbs); 759 foreach(db; dbs) 760 { 761 db.execute("insert into models(name) values('Some Name One');"); 762 db.execute("insert into models(name) values('Some Name Two');"); 763 Model[] models = db.list!(Model, "none/list.sql")(); 764 assert(models.length == 2); 765 assert(models[1].name == "Some Name One"); 766 assert(models[0].name == "Some Name Two"); 767 } 768 } 769 770 /* Variant Parameters */ 771 772 // void execute(string, Variant); 773 unittest 774 { 775 auto dbs = all(); 776 scope(exit) teardown(dbs); 777 foreach(db; dbs) 778 { 779 db.execute!("variant/insert.sql")(Variant("Some Variant Name")); 780 Model model = db.single!(Model, "variant/single.sql")(); 781 assert(model.name == "Some Variant Name"); 782 } 783 } 784 785 // void execute(string, Variant[]); 786 unittest 787 { 788 auto dbs = all(); 789 scope(exit) teardown(dbs); 790 foreach(db; dbs) 791 { 792 db.execute!("variant/insert-many.sql")(variantArray("Some Variant Name", "Some Variant Title")); 793 Model model = db.single!(Model)("select * from models order by id desc limit 1"); 794 assert(model.name == "Some Variant Name"); 795 assert(model.title == "Some Variant Title"); 796 } 797 } 798 799 // T single(T)(string, Variant); 800 unittest 801 { 802 auto dbs = all(); 803 scope(exit) teardown(dbs); 804 foreach(db; dbs) 805 { 806 db.execute("insert into models(name) values('some filter name');"); 807 Model model = db.single!(Model, "variant/single-by-name.sql")(Variant("some filter name")); 808 assert(model.name == "some filter name"); 809 } 810 } 811 812 // T single(T)(string, Variant[]); 813 unittest 814 { 815 auto dbs = all(); 816 scope(exit) teardown(dbs); 817 foreach(db; dbs) 818 { 819 db.execute("insert into models(name, title) values('some filter name', 'some filter title');"); 820 Model model = db.single!(Model, "variant/single-by-many.sql")(variantArray("some filter name", "some filter title")); 821 assert(model.name == "some filter name"); 822 assert(model.title == "some filter title"); 823 } 824 } 825 826 // T[] list(T)(string, Variant); 827 unittest 828 { 829 auto dbs = all(); 830 scope(exit) teardown(dbs); 831 foreach(db; dbs) 832 { 833 db.execute("insert into models(name, title) values('some name', 'some first title');"); 834 db.execute("insert into models(name, title) values('some name', 'some second title');"); 835 Model[] models = db.list!(Model, "variant/list.sql")(Variant("some name")); 836 assert(models[0].name == "some name"); 837 assert(models[0].title == "some second title"); 838 assert(models[1].name == "some name"); 839 assert(models[1].title == "some first title"); 840 } 841 } 842 843 // T[] list(T)(string, Variant[]); 844 unittest 845 { 846 auto dbs = all(); 847 scope(exit) teardown(dbs); 848 foreach(db; dbs) 849 { 850 db.execute("insert into models(name, title) values('some name', 'some title');"); 851 db.execute("insert into models(name, title) values('some name', 'some title');"); 852 Model[] models = db.list!(Model, "variant/list-by-many.sql")(variantArray("some name", "some title") 853 ); 854 assert(models[0].name == "some name"); 855 assert(models[0].title == "some title"); 856 assert(models[1].name == "some name"); 857 assert(models[1].title == "some title"); 858 } 859 } 860 861 /* Model Parameters */ 862 863 // void execute(T)(string, T); 864 unittest 865 { 866 auto dbs = all(); 867 scope(exit) teardown(dbs); 868 foreach(db; dbs) 869 { 870 Model model = new Model(); 871 model.name = "some name"; 872 model.title = "some title"; 873 874 db.execute!(Model, "model/insert.sql")(model); 875 876 Model insertedModel = db.single!(Model)("select * from models;"); 877 assert(insertedModel.name == "some name"); 878 assert(insertedModel.title == "some title"); 879 } 880 } 881 882 // T single(T)(string, T); 883 unittest 884 { 885 auto dbs = all(); 886 scope(exit) teardown(dbs); 887 foreach(db; dbs) 888 { 889 db.execute("insert into models(name) values('some name');"); 890 Model model = new Model(); 891 model.name = "some name"; 892 Model insertedModel = db.single!(Model, "model/single.sql")(model); 893 assert(model.name == "some name"); 894 } 895 } 896 897 // T[] list(T)(string, T); 898 unittest 899 { 900 auto dbs = all(); 901 scope(exit) teardown(dbs); 902 foreach(db; dbs) 903 { 904 db.execute("insert into models(name) values('some name');"); 905 db.execute("insert into models(name) values('some name');"); 906 Model model = new Model(); 907 model.name = "some name"; 908 Model[] insertedModels = db.list!(Model, "model/list.sql")(model); 909 assert(insertedModels[0].name == "some name"); 910 assert(insertedModels[1].name == "some name"); 911 } 912 } 913 914 unittest 915 { 916 auto dbs = all(); 917 scope(exit) teardown(dbs); 918 foreach(db; dbs) 919 { 920 db.execute("insert into models(name) values('some name');"); 921 string name = db.single!(string)("select name from models order by id desc limit 1"); 922 assert(name == "some name"); 923 } 924 } 925 926 version(unittest) 927 { 928 class BindModel 929 { 930 int id; 931 @bind("name") string notName; 932 } 933 } 934 935 unittest 936 { 937 auto dbs = all(); 938 scope(exit) teardown(dbs); 939 foreach(db; dbs) 940 { 941 db.execute("insert into models(name) values('some name');"); 942 BindModel model = db.single!(BindModel)("select * from models order by id desc limit 1;"); 943 assert(model.notName == "some name"); 944 } 945 } 946 947 version(unittest) 948 { 949 class OmitModel 950 { 951 protected int id; 952 @omit string name; 953 } 954 } 955 956 unittest 957 { 958 auto dbs = all(); 959 scope(exit) teardown(dbs); 960 foreach(db; dbs) 961 { 962 db.execute("insert into models(name) values('some name');"); 963 OmitModel model = db.single!(OmitModel)("select * from models order by id desc limit 1;"); 964 assert(model.name == ""); 965 assert(model.id == 0); 966 } 967 } 968 969 version(unittest) 970 { 971 class FullModel 972 { 973 /* These are all of the supported types */ 974 /* Note that PostgreSQL doesn't support unsigned numeric types */ 975 int id; 976 float float_column; 977 double double_column; 978 bool bool_column; 979 long long_column; 980 ulong ulong_column; 981 int int_column; 982 uint uint_column; 983 short short_column; 984 ushort ushort_column; 985 byte byte_column; 986 ubyte ubyte_column; 987 ubyte[] ubytes_column; 988 string string_column; 989 DateTime datetime_column; 990 991 this() { } 992 993 this(float fc, double d, bool b, long l, ulong ul, int i, uint ui, short s, ushort us, byte bt, ubyte ub, ubyte[] ubs, string st, DateTime dt) 994 { 995 this.float_column = fc; 996 this.double_column = d; 997 this.bool_column = b; 998 this.long_column = l; 999 this.ulong_column = ul; 1000 this.int_column = i; 1001 this.uint_column = ui; 1002 this.short_column = s; 1003 this.ushort_column = us; 1004 this.byte_column = bt; 1005 this.ubyte_column = ub; 1006 this.ubytes_column = ubs; 1007 this.string_column = st; 1008 this.datetime_column = dt; 1009 } 1010 } 1011 } 1012 1013 unittest 1014 { 1015 auto sqlite = createSqliteConnection(); 1016 scope(exit) sqlite.execute("drop table if exists full_models;"); 1017 sqlite.execute(" 1018 create table if not exists full_models( 1019 id integer primary key autoincrement not null, 1020 float_column float, 1021 double_column double, 1022 bool_column boolean, 1023 long_column long, 1024 ulong_column ulong, 1025 int_column integer, 1026 uint_column uint, 1027 short_column short, 1028 ushort_column ushort, 1029 byte_column byte, 1030 ubyte_column ubyte, 1031 ubytes_column blob, 1032 string_column string, 1033 datetime_column datetime 1034 ); 1035 "); 1036 1037 auto dateTime = DateTime(1999, 7, 6, 9); 1038 FullModel model = new FullModel(0.2f, 3.40483, true, 1_000_000L, 1_000_000UL, 2, to!(uint)(2), 1039 to!(short)(2), to!(ushort)(2), to!(byte)(2), to!(ubyte)(2), [1, 2, 3], "test string", dateTime 1040 ); 1041 1042 sqlite.execute!(FullModel)("insert into full_models values( 1043 null, 1044 ?(float_column), ?(double_column), ?(bool_column), ?(long_column), ?(ulong_column), 1045 ?(int_column), ?(uint_column), ?(short_column), ?(ushort_column), ?(byte_column), ?(ubyte_column), 1046 ?(ubytes_column), ?(string_column), ?(datetime_column)); 1047 ", model); 1048 1049 FullModel insertedModel = sqlite.single!(FullModel)("select * from full_models order by id desc limit 1"); 1050 assert(insertedModel.float_column == 0.2f); 1051 assert(insertedModel.double_column == 3.40483); 1052 assert(insertedModel.bool_column == true); 1053 assert(insertedModel.long_column == 1_000_000L); 1054 assert(insertedModel.ulong_column == 1_000_000UL); 1055 assert(insertedModel.int_column == 2); 1056 assert(insertedModel.uint_column == to!(uint)(2)); 1057 assert(insertedModel.short_column == to!(short)(2)); 1058 assert(insertedModel.ushort_column == to!(ushort)(2)); 1059 assert(insertedModel.byte_column == to!(byte)(2)); 1060 assert(insertedModel.ubyte_column == to!(ubyte)(2)); 1061 assert(insertedModel.ubytes_column == [1,2,3]); 1062 assert(insertedModel.string_column == "test string"); 1063 assert(insertedModel.datetime_column == DateTime(1999, 7, 6, 9)); 1064 } 1065 1066 unittest 1067 { 1068 auto postgres = createPostgresConnection(); 1069 scope(exit) postgres.execute("drop table if exists full_models;"); 1070 postgres.execute(" 1071 create table if not exists full_models( 1072 id serial primary key, 1073 float_column real, 1074 double_column double precision, 1075 bool_column boolean, 1076 long_column bigint, 1077 int_column integer, 1078 short_column smallint, 1079 byte_column smallint, 1080 string_column text, 1081 datetime_column timestamp 1082 ); 1083 "); 1084 1085 auto dateTime = DateTime(1999, 7, 6, 9); 1086 FullModel model = new FullModel(0.2f, 3.40483, true, 1_000_000L, 1_000_000UL, 2, to!(uint)(2), 1087 to!(short)(2), to!(ushort)(2), to!(byte)(2), to!(ubyte)(2), [1,2,3], "test string", dateTime 1088 ); 1089 1090 postgres.execute!(FullModel)("insert into full_models values( 1091 default, ?(float_column), ?(double_column), ?(bool_column), ?(long_column), 1092 ?(int_column), ?(short_column), ?(byte_column), ?(string_column), ?(datetime_column)); 1093 ", model); 1094 1095 FullModel insertedModel = postgres.single!(FullModel)("select * from full_models order by id desc limit 1"); 1096 assert(insertedModel.float_column == 0.2f); 1097 assert(insertedModel.double_column == 3.40483); 1098 assert(insertedModel.bool_column == true); 1099 assert(insertedModel.long_column == 1_000_000L); 1100 assert(insertedModel.int_column == 2); 1101 assert(insertedModel.short_column == to!(short)(2)); 1102 assert(insertedModel.byte_column == to!(byte)(2)); 1103 assert(insertedModel.string_column == "test string"); 1104 assert(insertedModel.datetime_column == DateTime(1999, 7, 6, 9)); 1105 postgres.execute("drop table if exists full_models;"); 1106 } 1107 1108 unittest 1109 { 1110 auto mysql = createMysqlConnection(); 1111 scope(exit) mysql.execute("drop table if exists full_models;"); 1112 mysql.execute(" 1113 create table if not exists full_models( 1114 id int not null auto_increment primary key, 1115 float_column float, 1116 double_column double, 1117 bool_column boolean, 1118 long_column bigint, 1119 ulong_column bigint unsigned, 1120 int_column integer, uint_column int unsigned, 1121 short_column mediumint, 1122 ushort_column mediumint unsigned, 1123 byte_column tinyint, 1124 ubyte_column tinyint unsigned, 1125 ubytes_column blob, 1126 string_column varchar(100), 1127 datetime_column datetime 1128 ); 1129 "); 1130 1131 auto dateTime = DateTime(1999, 7, 6, 9); 1132 FullModel model = new FullModel(0.2f, 3.40483, true, 1_000_000L, 1_000_000UL, 2, to!(uint)(2), 1133 to!(short)(2), to!(ushort)(2), to!(byte)(2), to!(ubyte)(2), [1,2,3], "test string", dateTime 1134 ); 1135 1136 mysql.execute!(FullModel)("insert into full_models values( 1137 null, ?(float_column), ?(double_column), ?(bool_column), ?(long_column), ?(ulong_column), 1138 ?(int_column), ?(uint_column), ?(short_column), ?(ushort_column), ?(byte_column), ?(ubyte_column), 1139 ?(ubytes_column), ?(string_column), ?(datetime_column)); 1140 ", model); 1141 1142 FullModel insertedModel = mysql.single!(FullModel)("select * from full_models order by id desc limit 1"); 1143 assert(insertedModel.float_column == 0.2f); 1144 assert(insertedModel.double_column == 3.40483); 1145 assert(insertedModel.bool_column == true); 1146 assert(insertedModel.long_column == 1_000_000L); 1147 assert(insertedModel.ulong_column == 1_000_000UL); 1148 assert(insertedModel.int_column == 2); 1149 assert(insertedModel.uint_column == to!(uint)(2)); 1150 assert(insertedModel.short_column == to!(short)(2)); 1151 assert(insertedModel.ushort_column == to!(ushort)(2)); 1152 assert(insertedModel.byte_column == to!(byte)(2)); 1153 assert(insertedModel.ubyte_column == to!(ubyte)(2)); 1154 assert(insertedModel.ubytes_column == [1,2,3]); 1155 assert(insertedModel.string_column == "test string"); 1156 assert(insertedModel.datetime_column == DateTime(1999, 7, 6, 9)); 1157 }