1 module ddb.db;
2 
3 /**
4 Common relational database interfaces.
5 
6 Copyright: Copyright Piotr Szturmaj 2011-.
7 License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0).
8 Authors: Piotr Szturmaj
9 */
10 
11 //module db;
12 
13 import std.conv, std.traits, std.typecons, std.typetuple, std.variant;
14 import std.format;
15 
16 static import std.typecons;
17 
18 /**
19 Data row returned from database servers.
20 
21 DBRow may be instantiated with any number of arguments. It subtypes base type which
22 depends on that number:
23 
24 $(TABLE
25     $(TR $(TH Number of arguments) $(TH Base type))
26     $(TR $(TD 0) $(TD Variant[] $(BR)$(BR)
27     It is default dynamic row, which can handle arbitrary number of columns and any of their types.
28     ))
29     $(TR $(TD 1) $(TD Specs itself, more precisely Specs[0] $(BR)
30     ---
31     struct S { int i, float f }
32 
33     DBRow!int rowInt;
34     DBRow!S rowS;
35     DBRow!(Tuple!(string, bool)) rowTuple;
36     DBRow!(int[10]) rowSA;
37     DBRow!(bool[]) rowDA;
38     ---
39     ))
40     $(TR $(TD >= 2) $(TD Tuple!Specs $(BR)
41     ---
42     DBRow!(int, string) row1; // two arguments
43     DBRow!(int, "i") row2; // two arguments
44     ---
45     ))
46 )
47 
48 If there is only one argument, the semantics depend on its type:
49 
50 $(TABLE
51     $(TR $(TH Type) $(TH Semantics))
52     $(TR $(TD base type, such as int) $(TD Row contains only one column of that type))
53     $(TR $(TD struct) $(TD Row columns are mapped to fields of the struct in the same order))
54     $(TR $(TD Tuple) $(TD Row columns are mapped to tuple fields in the same order))
55     $(TR $(TD static array) $(TD Row columns are mapped to array items, they share the same type))
56     $(TR $(TD dynamic array) $(TD Same as static array, except that column count may change during runtime))
57 )
58 Note: String types are treated as base types.
59 
60 There is an exception for RDBMSes which are capable of returning arrays and/or composite types. If such a
61 database server returns array or composite in one column it may be mapped to DBRow as if it was many columns.
62 For example:
63 ---
64 struct S { string field1; int field2; }
65 DBRow!S row;
66 ---
67 In this case row may handle result that either:
68 $(UL
69     $(LI has two columns convertible to respectively, string and int)
70     $(LI has one column with composite type compatible with S)
71 )
72 
73 _DBRow's instantiated with dynamic array (and thus default Variant[]) provide additional bracket syntax
74 for accessing fields:
75 ---
76 auto value = row["columnName"];
77 ---
78 There are cases when result contains duplicate column names. Normally column name inside brackets refers
79 to the first column of that name. To access other columns with that name, use additional index parameter:
80 ---
81 auto value = row["columnName", 1]; // second column named "columnName"
82 
83 auto value = row["columnName", 0]; // first column named "columnName"
84 auto value = row["columnName"]; // same as above
85 ---
86 
87 Examples:
88 
89 Default untyped (dynamic) _DBRow:
90 ---
91 DBRow!() row1;
92 DBRow!(Variant[]) row2;
93 
94 assert(is(typeof(row1.base == row2.base)));
95 
96 auto cmd = new PGCommand(conn, "SElECT typname, typlen FROM pg_type");
97 auto result = cmd.executeQuery;
98 
99 foreach (i, row; result)
100 {
101     writeln(i, " - ", row["typname"], ", ", row["typlen"]);
102 }
103 
104 result.close;
105 ---
106 _DBRow with only one field:
107 ---
108 DBRow!int row;
109 row = 10;
110 row += 1;
111 assert(row == 11);
112 
113 DBRow!Variant untypedRow;
114 untypedRow = 10;
115 ---
116 _DBRow with more than one field:
117 ---
118 struct S { int i; string s; }
119 alias Tuple!(int, "i", string, "s") TS;
120 
121 // all three rows are compatible
122 DBRow!S row1;
123 DBRow!TS row2;
124 DBRow!(int, "i", string, "s") row3;
125 
126 row1.i = row2.i = row3.i = 10;
127 row1.s = row2.s = row3.s = "abc";
128 
129 // these two rows are also compatible
130 DBRow!(int, int) row4;
131 DBRow!(int[2]) row5;
132 
133 row4[0] = row5[0] = 10;
134 row4[1] = row5[1] = 20;
135 ---
136 Advanced example:
137 ---
138 enum Axis { x, y, z }
139 struct SubRow1 { string s; int[] nums; int num; }
140 alias Tuple!(int, "num", string, "s") SubRow2;
141 struct Row { SubRow1 left; SubRow2[] right; Axis axis; string text; }
142 
143 auto cmd = new PGCommand(conn, "SELECT ROW('text', ARRAY[1, 2, 3], 100),
144                                 ARRAY[ROW(1, 'str'), ROW(2, 'aab')], 'x', 'anotherText'");
145 
146 auto row = cmd.executeRow!Row;
147 
148 assert(row.left.s == "text");
149 assert(row.left.nums == [1, 2, 3]);
150 assert(row.left.num == 100);
151 assert(row.right[0].num == 1 && row.right[0].s == "str");
152 assert(row.right[1].num == 2 && row.right[1].s == "aab");
153 assert(row.axis == Axis.x);
154 assert(row.s == "anotherText");
155 ---
156 */
157 struct DBRow(Specs...)
158 {
159     static if (Specs.length == 0)
160         alias Variant[] T;
161     else static if (Specs.length == 1)
162         alias Specs[0] T;
163     else
164         alias Tuple!Specs T;
165 
166     T base;
167     alias base this;
168 
169     static if (isDynamicArray!T && !isSomeString!T)
170     {
171 		mixin template elmnt(U : U[]){
172 			alias U ElemType;
173 		}
174         mixin elmnt!T;
175         enum hasStaticLength = false;
176 
177         void setLength(size_t length)
178         {
179             base.length = length;
180         }
181 
182         void setNull(size_t index)
183         {
184             static if (isNullable!ElemType)
185                 base[index] = null;
186             else
187                 throw new Exception("Cannot set NULL to field " ~ to!string(index) ~ " of " ~ T.stringof ~ ", it is not nullable");
188         }
189 
190         ColumnToIndexDelegate columnToIndex;
191 
192         ElemType opIndex(string column, size_t index)
193         {
194             return base[columnToIndex(column, index)];
195         }
196 
197         ElemType opIndexAssign(ElemType value, string column, size_t index)
198         {
199             return base[columnToIndex(column, index)] = value;
200         }
201 
202         ElemType opIndex(string column)
203         {
204             return base[columnToIndex(column, 0)];
205         }
206 
207         ElemType opIndexAssign(ElemType value, string column)
208         {
209             return base[columnToIndex(column, 0)] = value;
210         }
211 
212         ElemType opIndex(size_t index)
213         {
214             return base[index];
215         }
216 
217         ElemType opIndexAssign(ElemType value, size_t index)
218         {
219             return base[index] = value;
220         }
221     }
222     else static if (isCompositeType!T)
223     {
224         static if (isStaticArray!T)
225         {
226             template ArrayTypeTuple(AT : U[N], U, size_t N)
227             {
228                 static if (N > 1)
229                     alias TypeTuple!(U, ArrayTypeTuple!(U[N - 1])) ArrayTypeTuple;
230                 else
231                     alias TypeTuple!U ArrayTypeTuple;
232             }
233 
234             alias ArrayTypeTuple!T fieldTypes;
235         }
236         else
237             alias FieldTypeTuple!T fieldTypes;
238 
239         enum hasStaticLength = true;
240 
241         void set(U, size_t index)(U value)
242         {
243             static if (isStaticArray!T)
244                 base[index] = value;
245             else
246                 base.tupleof[index] = value;
247         }
248 
249         void setNull(size_t index)()
250         {
251             static if (isNullable!(fieldTypes[index]))
252             {
253                 static if (isStaticArray!T)
254                     base[index] = null;
255                 else static if (is(typeof(base.tupleof[index]) == Option!U, U))
256                     base.tupleof[index].nullify;
257                 else
258                     base.tupleof[index] = null;
259             }
260             else
261                 throw new Exception("Cannot set NULL to field " ~ to!string(index) ~ " of " ~ T.stringof ~ ", it is not nullable");
262         }
263     }
264     else static if (Specs.length == 1)
265     {
266         alias TypeTuple!T fieldTypes;
267         enum hasStaticLength = true;
268 
269         void set(T, size_t index)(T value)
270         {
271             base = value;
272         }
273 
274         void setNull(size_t index)()
275         {
276             static if (isNullable!T)
277                 base = null;
278             else
279                 throw new Exception("Cannot set NULL to " ~ T.stringof ~ ", it is not nullable");
280         }
281     }
282 
283     static if (hasStaticLength)
284     {
285         /**
286         Checks if received field count matches field count of this row type.
287 
288         This is used internally by clients and it applies only to DBRow types, which have static number of fields.
289         */
290         static pure void checkReceivedFieldCount(int fieldCount)
291         {
292             if (fieldTypes.length != fieldCount)
293                 throw new Exception(format("Received field(%s) count is not equal to %s's field count(%s)", fieldCount, T.stringof, fieldTypes.length));
294         }
295     }
296 
297     string toString()
298     {
299         return to!string(base);
300     }
301 }
302 
303 alias size_t delegate(string column, size_t index) ColumnToIndexDelegate;
304 
305 /**
306 Check if type is a composite.
307 
308 Composite is a type with static number of fields. These types are:
309 $(UL
310     $(LI Tuples)
311     $(LI structs)
312     $(LI static arrays)
313 )
314 */
315 template isCompositeType(T)
316 {
317     import std.datetime : SysTime;
318     static if (isTuple!T || (is(T == struct) && !(is(T == SysTime))) || isStaticArray!T)
319         enum isCompositeType = true;
320     else
321         enum isCompositeType = false;
322 }
323 
324 deprecated("Please used std.typecons.Nullable instead") template Nullable(T)
325     if (!__traits(compiles, { T t = null; }))
326 {
327     /*
328     Currently with void*, because otherwise it wont accept nulls.
329     VariantN need to be changed to support nulls without using void*, which may
330     be a legitimate type to store, as pointed out by Andrei.
331     Preferable alias would be then Algebraic!(T, void) or even Algebraic!T, since
332     VariantN already may hold "uninitialized state".
333     */
334     alias Algebraic!(T, void*) Nullable;
335 }
336 
337 template isVariantN(T)
338 {
339     //static if (is(T X == VariantN!(N, Types), uint N, Types...)) // doesn't work due to BUG 5784
340     static if (T.stringof.length >= 8 && T.stringof[0..8] == "VariantN") // ugly temporary workaround
341         enum isVariantN = true;
342     else
343         enum isVariantN = false;
344 }
345 
346 static assert(isVariantN!Variant);
347 static assert(isVariantN!(Algebraic!(int, string)));
348 static assert(isVariantN!(Nullable!int));
349 
350 // an alias is used due to a bug in the compiler not allowing fully
351 // qualified names in an is expression
352 private alias Option = std.typecons.Nullable;
353 
354 template isNullable(T)
355 {
356     static if ((isVariantN!T && T.allowed!(void*)) || is(T X == Nullable!U, U) || is(T == Option!U, U))
357         enum isNullable = true;
358     else
359         enum isNullable = false;
360 }
361 
362 static assert(isNullable!Variant);
363 static assert(isNullable!(Nullable!int));
364 
365 template nullableTarget(T)
366     if (isVariantN!T && T.allowed!(void*))
367 {
368     alias T nullableTarget;
369 }
370 
371 template nullableTarget(T : Nullable!U, U)
372 {
373     alias U nullableTarget;
374 }