1 /**
2  * UDA items for creating model structures for your datasets.
3  */
4 module sqlbuilder.uda;
5 import sqlbuilder.types : Spec;
6 
7 /**
8  * Tag your structure to provide an alternative table name. Without this, the
9  * struct's name is used as the table name.
10  */
11 struct tableName
12 {
13     string name;
14 }
15 
16 ///
17 unittest
18 {
19     import sqlbuilder.uda;
20     import sqlbuilder.dialect.mysql;
21     import sqlbuilder.dataset;
22 
23     @tableName("trec") static struct TableRecord
24     {
25         int id;
26     }
27     DataSet!TableRecord ds;
28     assert(select(ds).where(ds.id, " = ", 5.param).sql ==
29            "SELECT `trec`.* FROM `trec` WHERE (`trec`.`id` = ?)");
30 }
31 
32 
33 ///
34 struct AllowNull(T)
35 {
36     T nullValue;
37 }
38 
39 /**
40  * Allow null for a specific field that is not typed as Nullable!T.
41  *
42  * One can either assign a value to use as the NULL value, or simply tag a
43  * member with the @allowNull function alias. In the latter case, the .init
44  * value of the column type is used as the NULL value.
45  *
46  * This attribute ONLY applies on reading data. Inserting data using a
47  * blueprint or model will not translate the NULL value sentinel to an insert
48  * of NULL. However, using standard `set` calls, you can still set the value to
49  * NULL.
50  */
51 AllowNull!T allowNull(T)(T val)
52 {
53     return AllowNull!T(val);
54 }
55 
56 /*enum RefType
57 {
58     One,
59     Many,
60 }*/
61 
62 enum Strong : bool
63 {
64     no = false,
65     yes = true
66 }
67 
68 /**
69  * This attribute identifies the table that this column or relation refers to.
70  * Use the `mustReferTo` form to more intuitively indicate a strong relation
71  * (rather than pass a `true` as a third parameter to `refersTo`).
72  *
73  * The alias `foreign_table` identifies the other table type that this
74  * identifier refers to.
75  *
76  * The name identifies the name that should be used for a column only. A
77  * relation tagged with this UDA will ignore the name field.
78  *
79  * The joinType field identifies the join that should be used. By default, all
80  * joins are `LEFT` joins so as to not affect the data already selected by the
81  * join. You can override the join type when using the relation from a dataset.
82  *
83  * The strong field identifies that the relatable row must exist in the foreign
84  * table.
85  */
86 struct refersTo(T)
87 {
88     alias foreign_table = T;
89     string name;
90     Spec joinType = Spec.none;
91     Strong strong = Strong.no;
92 }
93 
94 /// ditto
95 refersTo!T mustReferTo(T)(string name = null, Spec joinType = Spec.none)
96 {
97     return refersTo!T(name, joinType, Strong.yes);
98 }
99 
100 /**
101  * This defines a mapping for a relationship. Each item can either represent a
102  * field in the foreign table or a literal string to use as the mapping. The
103  * library distinguishes between the two by checking to see if the key could be
104  * a valid identifier. Typically, literals are not valid identifiers in any
105  * SQL.
106  *
107  * If one key is an identifier, and the other is a literal, then the identifier
108  * is always provided first, to allow any relationship to be expressed.
109  *
110  * The default key is "id", as this is the most common name to map tables with.
111  *
112  * Only when both fields are identifiers is the '=' operator inserted.
113  * Otherwise, the operator must be provided in the literal portion.
114  *
115  * If the key field is an identifier, and the mapping attribute is attached to
116  * an actual column, the field is ignored, and the column name is used instead
117  * for the local field. Relations that need multiple column mappings should be
118  * specified as Relation types.
119  *
120  * Examples (assuming ftable = foreign table and ltable = local table):
121  *
122  * mapping("foo", "bar") => ftable.foo = ltable.bar
123  * mapping("foo") => ftable.foo = ltable.id (or ftable.foo = ltable.field when this is attached to a field)
124  * mapping("foo", "IS NULL") => ftable.foo IS NULL
125  * mapping("foo", "= 7") => ftable.foo = 7
126  *
127  * note the following reverse the order of expression
128  * mapping("IS NULL", "bar") => ltable.bar IS NULL
129  * mapping(" = 1", "bar") => ltable.bar = 1
130  *
131  * mapping("1", " = 0") => 1 = 0 (i.e. never match)
132  */
133 struct mapping
134 {
135     string foreign_key = "id";
136     string key = "id";
137 }
138 
139 ///
140 unittest
141 {
142     import sqlbuilder.uda;
143     import sqlbuilder.types;
144     import sqlbuilder.dataset;
145     import sqlbuilder.dialect.mysql;
146 
147     // foreign table
148     static struct FT
149     {
150         int id;
151         int foo;
152     }
153 
154     // local table
155     static struct LT
156     {
157         int id;
158         @refersTo!FT("byColumnMap") @mapping("foo") int bar;
159         @refersTo!FT @mapping("foo", "bar") Relation byFieldMap;
160         @refersTo!FT @mapping("foo") Relation byFooWithId;
161         @refersTo!FT @mapping("foo", "IS NULL") Relation byFooIsNull;
162         @refersTo!FT @mapping("foo", "= 42") Relation byFoo42;
163         @refersTo!FT @mapping("IS NULL", "bar") Relation byBarIsNull;
164         @refersTo!FT @mapping("= 1", "bar") Relation byBar1;
165         @refersTo!FT @mapping("1", "= 0") Relation noMatch;
166     }
167 
168     DataSet!LT ds;
169     assert(select(ds.byColumnMap).sql ==
170            "SELECT `LT_L_byColumnMap`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byColumnMap` ON (`LT_L_byColumnMap`.`foo` = `LT`.`bar`)");
171     assert(select(ds.byFieldMap).sql ==
172            "SELECT `LT_L_byFieldMap`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFieldMap` ON (`LT_L_byFieldMap`.`foo` = `LT`.`bar`)");
173     assert(select(ds.byFooWithId).sql ==
174            "SELECT `LT_L_byFooWithId`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFooWithId` ON (`LT_L_byFooWithId`.`foo` = `LT`.`id`)");
175 
176     assert(select(ds.byFooIsNull).sql ==
177            "SELECT `LT_L_byFooIsNull`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFooIsNull` ON (`LT_L_byFooIsNull`.`foo` IS NULL)");
178     assert(select(ds.byFoo42).sql ==
179            "SELECT `LT_L_byFoo42`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFoo42` ON (`LT_L_byFoo42`.`foo` = 42)");
180     assert(select(ds.byBarIsNull).sql ==
181            "SELECT `LT_L_byBarIsNull`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byBarIsNull` ON (`LT`.`bar` IS NULL)");
182     assert(select(ds.byBar1).sql ==
183            "SELECT `LT_L_byBar1`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byBar1` ON (`LT`.`bar` = 1)");
184     assert(select(ds.noMatch).sql ==
185            "SELECT `LT_L_noMatch`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_noMatch` ON (1 = 0)");
186 }
187 
188 /**
189  * Specify a different column name for a field. By default, the field's name is
190  * used.
191  */
192 struct colName
193 {
194     string name;
195 }
196 
197 ///
198 unittest
199 {
200     import sqlbuilder.dataset;
201     import sqlbuilder.uda;
202     import sqlbuilder.dialect.mysql;
203     static struct table
204     {
205         @colName("password_hash") string pwHash;
206     }
207 
208     DataSet!table ds;
209     assert(select(ds.pwHash).sql ==
210            "SELECT `table`.`password_hash` FROM `table`");
211 }
212 
213 /**
214  * Specify an alternate column type. This is possibly specific to the database
215  * engine. By default the engine picks the type based on the field type. This
216  * only has any bearing when creating tables.
217  *
218  * This does NOT affect parameter types, and should still be something that
219  * properly serializes to/from the given D type. For example, a string field
220  * could be adjusted to be VARCHAR(10) instead of TEXT. Use a specialized type
221  * to convert between different D types.
222  */
223 struct colType
224 {
225     string type;
226 }
227 
228 ///
229 unittest
230 {
231     import sqlbuilder.uda;
232     import sqlbuilder.dialect.mysql;
233     static struct table
234     {
235         @colType("VARCHAR(10)") string name;
236     }
237 
238     assert(createTableSql!table ==
239            "CREATE TABLE `table` (`name` VARCHAR(10) NOT NULL)");
240 }
241 
242 /**
243  * Ignore a field, it is not considered part of the database data.
244  *
245  * This is useful for fields that only exist on the record in D, but not on the
246  * database. For instance a calculated field that is not in the database, or a
247  * network connection associated with a record.
248  */
249 enum ignore;
250 
251 /**
252  * Tag a column as part of the primary key.
253  *
254  * The primary key is used when generating keys for a type, and also for
255  * updating a record. sqlbuilder does not treat any fields by default as the
256  * primary key.
257  */
258 enum primaryKey;
259 
260 /**
261  * Mark a specific column as unique on the table. This only is used when
262  * generating the table and is not (currently) used in any queries or
263  * executions.
264  */
265 enum unique;
266 
267 /**
268  * Tag a column as receiving an automatic value incremented by the database
269  * (usually the id).
270  *
271  * When inserting whole items into the table, any autoincrement columns are
272  * given the value returned from the database.
273  *
274  * In many cases, database implementations only allow one autoIncrement value
275  * to be returned. In this case, the autoIncrement value will be assigned to
276  * ALL items in the model, even if they are different.
277  */
278 enum autoIncrement;
279 
280 // TODO: figure out how to do indexes besides primary key.