1 module sqlbuilder.types;
2 import sqlbuilder.traits;
3 
4 @safe:
5 
6 enum Spec : char
7 {
8     none = '\0',
9     id = 'i',
10     tableid = 't',
11     param = 'p',
12     leftJoin = 'L',
13     innerJoin = 'I',
14     rightJoin = 'R',
15     outerJoin = 'O',
16     objend = 'e', // denotes the end of an object
17     // separator between clauses.
18     separator = ';',
19 
20     // clause starts and ends. Using these can allow for nested ands or ors
21     beginAnd = '&',
22     beginOr = '|',
23     endGroup = '$',
24 }
25 
26 enum joinSpec = "\\" ~ Spec.leftJoin;
27 
28 enum paramSpec = "\\" ~ Spec.param;
29 
30 enum objEndSpec = "\\" ~ Spec.objend;
31 
32 enum andSpec = "\\" ~ Spec.beginAnd;
33 
34 enum orSpec = "\\" ~ Spec.beginOr;
35 
36 enum sepSpec = "\\" ~ Spec.separator;
37 
38 enum endGroupSpec = "\\" ~ Spec.endGroup;
39 
40 Spec getSpec(const(char)[] s)
41 {
42     if(s.length >= 2 && s[0] == '\\')
43     {
44         return cast(Spec)(s[1]);
45     }
46     return Spec.none;
47 }
48 
49 bool isJoin(Spec s)
50 {
51     return s == Spec.leftJoin ||
52         s == Spec.rightJoin ||
53         s == Spec.innerJoin ||
54         s == Spec.outerJoin;
55 }
56 
57 bool isGroup(Spec s)
58 {
59     return s == Spec.beginOr || s == Spec.beginAnd;
60 }
61 
62 package bool isKeyLiteral(string val)
63 {
64     // returns true if any character inside val cannot be an identifier
65     if(val.length == 0)
66         return false;
67     import std.utf;
68     import std.uni;
69     auto checkme = val.byDchar;
70     if(!(checkme.front == '_' || checkme.front.isAlpha))
71         return true;
72     checkme.popFront;
73     foreach(c; checkme)
74     {
75         if(!(c == '_' || c.isAlphaNum))
76             return true;
77     }
78     return false;
79 }
80 
81 struct ExprString
82 {
83     string[] data;
84 
85     this(const(string)[] input...)
86     {
87         data = input.dup;
88     }
89 
90     this(string[] input)
91     {
92         data = input;
93     }
94 
95     ExprString opBinary(string op: "~")(auto ref const(ExprString) other) const
96     {
97         return ExprString(data ~ other.data);
98     }
99 
100     ExprString opBinary(string op: "~")(string other) const
101     {
102         if(other.length)
103             return ExprString(data ~ other);
104         return ExprString(data);
105     }
106 
107     ExprString opBinaryRight(string op: "~")(string other) const
108     {
109         if(other.length)
110             return ExprString(other ~ data);
111         return ExprString(data);
112     }
113 
114     ref ExprString opOpAssign(string op: "~")(auto ref const(ExprString) other)
115     {
116         if(other.data.length)
117             data ~= other.data;
118         return this;
119     }
120 
121     ref ExprString opOpAssign(string op: "~")(string other)
122     {
123         // only add non-empty strings
124         if(other.length)
125             data ~= other;
126         return this;
127     }
128     
129     private import std.range;
130     ref ExprString opOpAssign(string op: "~", R)(R other) if (isInputRange!R && is(ElementType!R == string))
131     {
132         // append only non-empty strings
133         foreach(s; other)
134             if(s.length > 0)
135                 data ~= s;
136         return this;
137     }
138 
139     bool opCast(T: bool)()
140     {
141         return data.length > 0;
142     }
143 
144     void toString(Out)(Out outputRange)
145     {
146         import std.format : formattedWrite;
147         put(outputRange, "sql{");
148         foreach(d; data)
149         {
150             auto spec = getSpec(d);
151             if(spec != Spec.none)
152             {
153                 put(outputRange, "@");
154                 formattedWrite(outputRange, "%s", spec);
155                 if(d.length > 2)
156                 {
157                     put(outputRange, "(");
158                     put(outputRange, d[2 .. $]);
159                     put(outputRange, ")");
160                 }
161             }
162             else
163                 put(outputRange, d);
164         }
165         put(outputRange, "}");
166     }
167 }
168 
169 void addSep(ref ExprString expr)
170 {
171     if(expr)
172     {
173         auto lastSpec = expr.data[$-1].getSpec;
174 
175         if(lastSpec != Spec.beginAnd && lastSpec != Spec.beginOr)
176             expr ~= sepSpec;
177     }
178 }
179 
180 string makeSpec(string value, Spec spec)
181 {
182     return "\\" ~ spec ~ value;
183 }
184 
185 string makeSpec(Spec spec)
186 {
187     return "\\" ~ spec;
188 }
189 
190 struct TableDef
191 {
192     @property Spec joinType() const
193     {
194         if(joinExpr.data.length)
195         {
196             auto s = getSpec(joinExpr.data[0]);
197             return s.isJoin ? s : Spec.none;
198         }
199         return Spec.none;
200     }
201 
202     string as; // table name used in the expression
203     ExprString joinExpr; // join expression defines the relationship and the table name
204     const(TableDef)[] dependencies; // tables that must be included first
205 }
206 
207 // used to designate a field as a relation (for when a relation doesn't have a
208 // dedicated field).
209 struct Relation
210 {
211 }
212 
213 
214 // An SQLFragment has an expression used to generate a portion of the SQL
215 // statement, along with a list of parameters that are used to pass custom data
216 // to the server.
217 struct SQLFragment(Item)
218 {
219     ExprString expr;
220     static if(!is(Item == void))
221         Item[] params;
222 }
223 
224 struct Joins(Item)
225 {
226     SQLFragment!Item joinFragment;
227     alias joinFragment this;
228 
229     bool hasJoin(const TableDef def)
230     {
231         // linear search. It's backwards because we only search for
232         // dependencies if the leaf is not present. And leaves go on the
233         // end.
234         import std.algorithm : canFind;
235         import std.range : retro;
236         return joinFragment.expr.data.retro.canFind(def.joinExpr.data.retro);
237     }
238 }
239 
240 // a query is a dynamic structure designed to contain all the things needed to
241 // generate an SQL query. The function sql will fetch the current query
242 // string based on the Dialect.
243 struct Query(Item, RowT...)
244 {
245     import std.meta : staticMap;
246     SQLFragment!(Item) fields;
247     SQLFragment!(Item) conditions;
248     SQLFragment!(Item) groups;
249     SQLFragment!(Item) orders;
250     Joins!Item joins;
251     size_t limitQty = 0;
252     size_t limitOffset = 0;
253 
254     // used by the serialization system to determine which rows this will
255     // fetch. This is only valid if fetch was used to generate the query.
256     alias RowTypes = staticMap!(getFetchType, RowT);
257     alias QueryTypes = RowT;
258 
259     // convenience to avoid having to use traits tricks.
260     package alias ItemType = Item;
261 
262     // allow forgetting all the row types.
263     static if(RowT.length == 0 || RowT.length > 1 || (RowT.length == 1 && !is(RowT[0] == void)))
264     {
265         ref .Query!(Item, void) basicQuery() return @trusted
266         {
267             return *cast(.Query!(Item, void)*)&this;
268         }
269 
270         alias basicQuery this;
271     }
272     else
273         ref Query basicQuery() return { return this; }
274 }
275 
276 // UFCS method to fetch all the parameters from the given item.
277 package template paramsImpl(FieldNames...)
278 {
279     string paramStr()
280     {
281         string result;
282         foreach(n; FieldNames)
283             result ~= "t." ~ n ~ ".params,";
284         return result;
285     }
286     auto paramsImpl(T)(T t)
287     {
288         static if(is(t.ItemType == void))
289         {
290             import std.range : only;
291             return only();
292         }
293         else
294         {
295             import std.range : chain;
296             mixin("return chain(" ~ paramStr() ~ ");");
297         }
298     }
299 }
300 
301 struct Insert(Item)
302 {
303     alias ItemType = Item;
304 
305     // table for insertion.
306     string tableid;
307 
308     // the items to set.
309     SQLFragment!Item colNames;
310     SQLFragment!Item colValues;
311 }
312 
313 struct Update(Item)
314 {
315     SQLFragment!Item settings;
316     SQLFragment!Item conditions;
317     Joins!Item joins;
318 
319     alias ItemType = Item;
320 }
321 
322 struct Delete(Item)
323 {
324     // relations, possibly used for "where" clause, but not deleted from
325     Joins!Item joins;
326     SQLFragment!Item conditions;
327 
328     alias ItemType = Item;
329 }
330 
331 struct AllowNullType(T, T defaultVal)
332 {
333     alias type = T;
334     enum nullVal = defaultVal;
335 }
336 
337 struct ColumnDef(T)
338 {
339     const TableDef table;
340     ExprString expr;
341     alias type = T;
342 }
343 
344 struct ConcatDef
345 {
346     const(TableDef)[] tables;
347     ExprString expr;
348     alias type = string;
349 }
350 
351 // a change struct
352 struct Changed(ColTypes...)
353 {
354     ColTypes val;
355     package bool _changed;
356 
357     bool opCast(T : bool)() { return _changed; }
358 }
359 
360 // this specialized struct is something that is used as a placeholder for a
361 // type that is actually a row. This happens when you select an entire row from
362 // the DB instead of a single column. This expects an _objEnd column to define
363 // where the row pieces stop.
364 // This never should be seen by user code.
365 package struct RowObj(T)
366 {
367 }
368 
369 // basic expression for strings. Used to provide literal SQL to ExprString.
370 struct Expr
371 {
372     string expr;
373 }
374 
375 // Deserialization game plan:
376 //
377 // 1. A RowObj of a Nullable!T:
378 //    a. T.deserializeRowNull exists? use it
379 //    b. T.deserializeRow exists? use it, catch any exception and set to null
380 //    c. Process all fields in the struct, if any of the fields are null, but are not Nullable themselves, the whole object is null.
381 // 2. A RowObj of a non-Nullable T:
382 //    a. T.deserializeRow exists? use it
383 //    b. Process all fields in the struct.
384 // 3. Changes struct
385 //    a. do the deserialization, compare with previous version. Set up struct
386 //
387 // The rest are for SINGLE COLUMN types (leaf types):
388 // 4. Nullable!T
389 //    a. Is the value null? return Nullable!T.init
390 //    b. Else, deserialize T
391 // 5. T that has dbValue/fromDbValue
392 //    a. return T.fromDbValue;
393 // 6. other dialect specific hooked types (e.g. DateTime, values that can marshal to/from strings)
394 //    a. Deserialize according to the dialect rules.
395 // 7. primitive types:
396 //    a. Deserialize according to the dialect rules.