1 module sqlbuilder.traits; 2 import sqlbuilder.uda; 3 import sqlbuilder.types; 4 5 private struct SingleTableDef 6 { 7 @safe pure @nogc nothrow: 8 const(TableDef) _front; 9 bool _empty = false; 10 auto front() { return _front; } 11 void popFront() { _empty = true; } 12 bool empty() { return _empty; } 13 } 14 15 // get any dependent tables for an item, even if the item doesn't have any 16 package auto ref getTables(T)(auto ref T item) 17 { 18 import std.range.primitives; 19 static if(is(typeof({ return item.tables;}()) R) && isInputRange!R && is(ElementType!R : const TableDef)) 20 { 21 return item.tables; 22 } 23 else static if(__traits(hasMember, T, "tables")) 24 { 25 static assert(0, "Type " ~ T.stringof ~ " has a member `tables`, but does not provide the correct type"); 26 } 27 else static if(is(typeof({ return item.table;}()) : const(TableDef))) 28 { 29 return SingleTableDef(item.table); 30 } 31 else static if(__traits(hasMember, T, "table")) 32 { 33 static assert(0, "Type " ~ T.stringof ~ " has a member `table`, but does not provide the correct type"); 34 } 35 else 36 return TableDef[].init; 37 } 38 39 // gets the table name of a given type. This looks for the TableName UDA or 40 // just uses the struct's name otherwise. 41 template getTableName(T) 42 { 43 static foreach(u; __traits(getAttributes, T)) 44 static if(is(typeof(u) == tableName)) 45 enum result = u.name; 46 static if(is(typeof(result))) 47 enum getTableName = result; 48 else 49 enum getTableName = T.stringof; 50 } 51 52 template isField(T, string item) 53 { 54 private import std.traits; 55 // is a member, is not a static member, is not a Relation type and not ignored. 56 // TODO: we ignore functions for now, but possibly we may want to include them. 57 static if(__traits(hasMember, T, item)) 58 { 59 enum isField = !is(__traits(getMember, T, item)) && // not a type 60 // is a field with an offset 61 __traits(compiles, __traits(getMember, T, item).offsetof) && 62 !hasUDA!(__traits(getMember, T, item), ignore) && // no ignore uda 63 !is(typeof(__traits(getMember, T, item)) == Relation); 64 } 65 else 66 enum isField = false; 67 } 68 69 // gets the field that contains the given relation. 70 template getRelationField(T, string item) 71 { 72 private import std.traits; 73 static if(__traits(hasMember, T, item) && is(typeof(__traits(getMember, T, item)) == Relation)) 74 { 75 // A relation field directly in the item 76 enum getRelationField = item; 77 } 78 else 79 { 80 // look for a field with a UDA 81 static foreach(f; __traits(allMembers, T)) 82 static foreach(u; __traits(getAttributes, __traits(getMember, T, f))) 83 { 84 static if(is(typeof(u)) && isInstanceOf!(refersTo, typeof(u)) && u.name == item) 85 enum result = f; 86 } 87 static if(is(typeof(result))) 88 enum getRelationField = result; 89 else 90 enum string getRelationField = null; 91 } 92 } 93 94 // get a relation in T that has a relation to a U 95 template getRelationField(T, U) 96 { 97 private import std.traits; 98 // look for a field with a UDA 99 static foreach(f; __traits(allMembers, T)) 100 static foreach(u; __traits(getAttributes, __traits(getMember, T, f))) 101 { 102 static if(is(typeof(u)) && isInstanceOf!(refersTo, typeof(u)) && is(u.foreign_table == U)) 103 { 104 static if(is(typeof(result))) 105 static assert(0, "Multiple relations exist for " ~ T.stringof ~ " to " ~ U.stringof); 106 else 107 enum result = f; 108 } 109 } 110 static if(is(typeof(result))) 111 enum getRelationField = result; 112 else 113 enum string getRelationField = null; 114 } 115 116 enum isRelation(T, string item) = getRelationField!(T, item) != null; 117 118 template getRelationFor(alias sym) 119 { 120 import std.traits : isInstanceOf; 121 static foreach(u; __traits(getAttributes, sym)) 122 { 123 static if(is(typeof(u) : refersTo!T, T)) 124 { 125 static if(u.name == null) 126 // make a copy with the name replaced 127 enum result = () { 128 auto x = u; 129 x.name = __traits(identifier, sym); 130 return x; 131 } (); 132 else 133 enum result = u; 134 } 135 static if(is(u : refersTo!T, T)) 136 enum result = u(__traits(identifier, sym)); 137 static if(isInstanceOf!(mustReferTo, u)) 138 enum result = u(__traits(identifier, sym)); 139 } 140 static if(is(typeof(result))) 141 enum getRelationFor = result; 142 else 143 alias getRelationFor = void; 144 } 145 146 enum isRelationField(alias sym) = is(typeof(getRelationFor!sym) == refersTo!U, U); 147 148 template getMappingsFor(alias sym) 149 { 150 import std.meta; 151 template m_list(Params...) 152 { 153 static if(Params.length == 0) 154 alias m_list = AliasSeq!(); 155 else static if(is(typeof(Params[0]) == mapping)) 156 { 157 static if(is(typeof(sym) == Relation)) 158 enum result = Params[0]; 159 else 160 { 161 // mapping attached to a field, use the name of the field 162 // instead of the key. 163 static if(isKeyLiteral(Params[0].key)) 164 enum result = Params[0]; 165 else 166 // ignore any name there, just use the actual field name. 167 enum result = mapping(Params[0].foreign_key, __traits(identifier, sym)); 168 } 169 alias m_list = AliasSeq!(result, m_list!(Params[1 .. $])); 170 } 171 else 172 alias m_list = m_list!(Params[1 .. $]); 173 } 174 alias result = m_list!(__traits(getAttributes, sym)); 175 static if(result.length > 0) 176 enum getMappingsFor = result; 177 else 178 { 179 // no mapping UDA 180 // for Relations, assume both keys are the default. For non-relations, 181 // use the field name as the local key. 182 static if(is(typeof(sym) == Relation)) 183 alias getMappingsFor = AliasSeq!(mapping.init); 184 else 185 enum getMappingsFor = AliasSeq!(mapping(mapping.init.foreign_key, __traits(identifier, sym))); 186 } 187 } 188 189 template getParamType(T...) 190 { 191 static if(T.length == 0) 192 alias getParamType = void; 193 else static if(T.length == 1) 194 { 195 alias X = T[0]; 196 static if(is(typeof({X x = X.init; return x.params;}()) P)) 197 { 198 import std.range : ElementType; 199 alias getParamType = ElementType!P; 200 } 201 else 202 { 203 alias getParamType = void; 204 } 205 } 206 else 207 { 208 alias P1 = getParamType!(T[0]); 209 alias P2 = getParamType!(T[1 .. $]); 210 static if(is(P1 == void)) 211 alias getParamType = P2; 212 else static if(is(P2 == void)) 213 alias getParamType = P1; 214 else 215 alias getParamType = typeof(true ? P1.init : P2.init); 216 } 217 } 218 219 template getColumnName(alias sym) 220 { 221 static foreach(u; __traits(getAttributes, sym)) 222 static if(is(typeof(u) == colName)) 223 enum result = u.name; 224 static if(is(typeof(result))) 225 enum getColumnName = result; 226 else 227 enum getColumnName = __traits(identifier, sym); 228 } 229 230 // TODO: use some means to detect custom datasets. 231 template isDataSet(T) 232 { 233 import std.traits : isInstanceOf; 234 import sqlbuilder.dataset; 235 enum isDataSet = isInstanceOf!(DataSet, T); 236 } 237 238 template isQuery(T) 239 { 240 import std.traits : isInstanceOf; 241 enum isQuery = isInstanceOf!(Query, T); 242 } 243 244 template getQueryTypeList(Q, Cols...) 245 { 246 import std.meta : AliasSeq; 247 static if(Q.QueryTypes.length == 1 && is(Q.QueryTypes[0] == void)) 248 alias getQueryTypeList = AliasSeq!(void); 249 else 250 { 251 template helper(size_t idx, X...) 252 { 253 static if(idx >= X.length) 254 alias helper = X; 255 else static if(isDataSet!(X[idx])) 256 alias helper = helper!(idx + 1, X[0 .. idx], typeof(X[idx].allColumns()).type, X[idx + 1 .. $]); 257 else static if(is(X[idx].type) && !is(X[idx].type == void)) 258 { 259 alias helper = helper!(idx + 1, X[0 .. idx], X[idx].type, X[idx + 1 .. $]); 260 } 261 else 262 alias helper = AliasSeq!(void); 263 } 264 alias newTypes = helper!(0, Cols); 265 static if(newTypes.length == 1 && is(newTypes[0] == void)) 266 alias getQueryTypeList = AliasSeq!(void); 267 else 268 alias getQueryTypeList = AliasSeq!(Q.QueryTypes, newTypes); 269 } 270 } 271 272 template primaryKeyFields(T) 273 { 274 import std.meta : AliasSeq; 275 import std.traits : hasUDA; 276 template PKHelper(Fields...) 277 { 278 static if(Fields.length == 0) 279 alias PKHelper = AliasSeq!(); 280 else 281 { 282 static if(hasUDA!(__traits(getMember, T, Fields[0]), primaryKey)) 283 alias PKHelper = AliasSeq!(Fields[0], PKHelper!(Fields[1 .. $])); 284 else 285 alias PKHelper = PKHelper!(Fields[1 .. $]); 286 } 287 } 288 289 alias primaryKeyFields = PKHelper!(__traits(allMembers, T)); 290 } 291 292 enum hasPrimaryKey(T) = primaryKeyFields!T.length > 0; 293 294 template PrimaryKeyTypes(T) 295 { 296 import std.meta : AliasSeq; 297 template PKHelper(Fields...) 298 { 299 static if(Fields.length == 0) 300 alias PKHelper = AliasSeq!(); 301 else 302 alias PKHelper = AliasSeq!(typeof(__traits(getMember, T, Fields[0])), PKHelper!(Fields[1 .. $])); 303 } 304 305 alias PrimaryKeyTypes = PKHelper!(primaryKeyFields!T); 306 } 307 308 template possibleNullColumn(alias sym) 309 { 310 import std.typecons : Nullable; 311 static if(is(typeof(sym) : Nullable!T, T)) 312 enum possibleNullColumn = true; 313 else static if(is(getAllowNullType!sym == AllowNullType!Args, Args...)) 314 enum possibleNullColumn = true; 315 else 316 enum possibleNullColumn = false; 317 } 318 319 template getAllowNullType(alias sym) 320 { 321 // get the null type, which includes the default value if it's null 322 static foreach(alias att; __traits(getAttributes, sym)) 323 { 324 static if(__traits(isSame, att, allowNull)) 325 alias result = AllowNullType!(typeof(sym), sym.init); 326 else static if(is(typeof(att) : AllowNull!T, T)) 327 alias result = AllowNullType!(typeof(sym), att.nullValue); 328 } 329 // if we didn't alias, then allowNull isn't present. 330 static assert(is(result), "No allowNull UDA detected on symbol " ~ __traits(identifier, sym)); 331 alias getAllowNullType = result; 332 } 333 334 template getFetchType(T) 335 { 336 static if(is(T == AllowNullType!Args, Args...)) 337 alias getFetchType = T.type; 338 else static if(is(T == RowObj!U, U)) 339 alias getFetchType = U; 340 else 341 alias getFetchType = T; 342 } 343 344 345 unittest 346 { 347 import std.typecons : Nullable; 348 static struct TestRow 349 { 350 Nullable!int n1; 351 @allowNull int n2; 352 @allowNull(5) int n3; 353 int v1; 354 } 355 356 static assert(possibleNullColumn!(TestRow.n1)); 357 static assert(possibleNullColumn!(TestRow.n2)); 358 static assert(possibleNullColumn!(TestRow.n3)); 359 static assert(!possibleNullColumn!(TestRow.v1)); 360 361 static assert(is(getAllowNullType!(TestRow.n2) == AllowNullType!(int, 0))); 362 static assert(is(getAllowNullType!(TestRow.n3) == AllowNullType!(int, 5))); 363 static assert(!__traits(compiles, getAllowNullType!(TestRow.n1))); 364 static assert(!__traits(compiles, getAllowNullType!(TestRow.v1))); 365 }