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 }