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 }