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 }