1 module microrm.util;
2 
3 import std.traits;
4 import std.format : format, formattedWrite;
5 
6 enum IDNAME = "id";
7 enum SEPARATOR = ".";
8 
9 string tableName(T)()
10 {
11     return T.stringof;
12 }
13 
14 string[] fieldToCol(string name, T)(string prefix="")
15 {
16     static if (name == IDNAME)
17         return ["'" ~ IDNAME ~ "' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"];
18     else static if (is(T == struct))
19     {
20         T t;
21         string[] ret;
22         foreach (i, f; t.tupleof)
23         {
24             enum fname = __traits(identifier, t.tupleof[i]);
25             alias F = typeof(f);
26             auto np = prefix ~ (name.length ? name~SEPARATOR : "");
27             ret ~= fieldToCol!(fname, F)(np);
28         }
29         return ret;
30     }
31     else
32     {
33         enum NOTNULL = " NOT NULL";
34         string type, param;
35         static if (isFloatingPoint!T) type = "REAL";
36         else static if (isNumeric!T || is(T == bool))
37         {
38             type = "INTEGER";
39             param = NOTNULL;
40         }
41         else static if (isSomeString!T) type = "TEXT";
42         else static if (isArray!T) type = "BLOB";
43         else static assert(0, "unsupported type: " ~ T.stringof);
44 
45         return [format("'%s%s' %s%s", prefix, name, type, param)];
46     }
47 }
48 
49 unittest
50 {
51     struct Baz { string a, b; }
52 
53     struct Foo
54     {
55         float xx;
56         string yy;
57         Baz baz;
58         int zz;
59     }
60 
61     struct Bar
62     {
63         ulong id;
64         float abc;
65         Foo foo;
66         string baz;
67         ubyte[] data;
68     }
69 
70     assert (fieldToCol!("", Bar)() ==
71     ["'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
72      "'abc' REAL",
73      "'foo.xx' REAL",
74      "'foo.yy' TEXT",
75      "'foo.baz.a' TEXT",
76      "'foo.baz.b' TEXT",
77      "'foo.zz' INTEGER NOT NULL",
78      "'baz' TEXT",
79      "'data' BLOB"
80      ]);
81 }
82 
83 void valueToCol(T, Writer)(ref Writer w, T x)
84 {
85     void fmtwrt(string fmt, T val)
86     {
87         // LDC, WTF? https://github.com/ldc-developers/ldc/issues/2355
88         version (LDC) w.put(format(fmt, val));
89         else w.formattedWrite(fmt, val);
90     }
91 
92     static if(is(T == struct))
93     {
94         foreach (i, v; x.tupleof)
95         {
96             valueToCol(w, v);
97             static if (i+1 != x.tupleof.length)
98                 w.put(",");
99         }
100     }
101     else static if (is(T == bool)) fmtwrt("%d", cast(int)x);
102     else static if (isFloatingPoint!T)
103     {
104         if (x == x) fmtwrt("%e", x);
105         else w.put("null");
106     }
107     else static if (isNumeric!T) fmtwrt("%d", x);
108     else static if (isSomeString!T)
109     {
110         w.put('\'');
111         w.put(x);
112         w.put('\'');
113     }
114     else static if (isDynamicArray!T)
115     {
116         if (x.length == 0) w.put("null");
117         else
118         {
119             static if (is(T == ubyte[])) auto dd = x;
120             else auto dd = cast(ubyte[])(cast(void[])x);
121             fmtwrt("x'%-(%02x%)'", dd);
122         }
123     }
124     else static assert(0, "unsupported type: " ~ T.stringof);
125 }
126 
127 unittest
128 {
129     import std.array : Appender;
130     Appender!(char[]) buf;
131     valueToCol(buf, 3);
132     assert(buf.data == "3");
133     buf.clear;
134     valueToCol(buf, "hello");
135     assert(buf.data == "'hello'");
136     buf.clear;
137 }
138 
139 unittest
140 {
141     struct Foo
142     {
143         int xx;
144         string yy;
145     }
146 
147     struct Bar
148     {
149         ulong id;
150         int abc;
151         string baz;
152         Foo foo;
153     }
154 
155     Bar val = {id: 12, abc: 32, baz: "hello",
156                 foo: {xx: 45, yy: "ok"}};
157 
158     import std.array : Appender;
159     Appender!(char[]) buf;
160     valueToCol(buf, val);
161     assert(buf.data == "12,32,'hello',45,'ok'");
162 }
163 
164 mixin template whereCondition()
165 {
166     import std.format : formattedWrite;
167     import std.conv : text;
168     import std.range : isOutputRange;
169     static assert(isOutputRange!(typeof(this.query), char));
170 
171     ref where(V)(string field, V val)
172     {
173         query.put(" WHERE ");
174         query.put(field);
175         query.put(" '");
176         version (LDC) query.put(text(val));
177         else query.formattedWrite("%s", val);
178         query.put("'");
179         return this;
180     }
181 
182     ref whereQ(string field, string cmd)
183     {
184         query.put(" WHERE ");
185         query.put(field);
186         query.put(" ");
187         query.put(cmd);
188         return this;
189     }
190 
191     ref and(V)(string field, V val)
192     {
193         query.put(" AND ");
194         query.put(field);
195         query.put(" '");
196         version (LDC) query.put(text(val));
197         else query.formattedWrite("%s", val);
198         query.put("'");
199         return this;
200     }
201 
202     ref andQ(string field, string cmd)
203     {
204         query.put(" AND ");
205         query.put(field);
206         query.put(" ");
207         query.put(cmd);
208         return this;
209     }
210 
211     ref limit(int limit)
212     {
213         query.put(" LIMIT '");
214         version (LDC) query.put(text(limit));
215         else query.formattedWrite("%s", limit);
216         query.put("'");
217         return this;
218     }
219 }
220 
221 mixin template baseQueryData(string SQLTempl)
222 {
223     import std.array : Appender, appender;
224     import std.format : formattedWrite, format;
225 
226     enum initialSQL = format(SQLTempl, tableName!T);
227 
228     alias Buffer = BUF;
229 
230     Database* db;
231     Buffer* buf;
232 
233     @disable this();
234 
235     private ref Buffer query() @property { return (*buf); }
236 
237     this(Database* db, Buffer* buf)
238     {
239         this.db = db;
240         this.buf = buf;
241         query.put(initialSQL);
242     }
243 
244     void reset()
245     {
246         query.clear();
247         query.put(initialSQL);
248     }
249 }
250 
251 string[] fieldNames(string name, T)(string prefix="")
252 {
253     static if (is(T == struct))
254     {
255         T t;
256         string[] ret;
257         foreach (i, f; t.tupleof)
258         {
259             enum fname = __traits(identifier, t.tupleof[i]);
260             alias F = typeof(f);
261             auto np = prefix ~ (name.length ? name~SEPARATOR : "");
262             ret ~= fieldNames!(fname, F)(np);
263         }
264         return ret;
265     }
266     else return ["'" ~ prefix ~ name ~ "'"];
267 }
268 
269 unittest
270 {
271     struct Foo
272     {
273         ulong id;
274         float xx;
275         string yy;
276     }
277 
278     struct Bar
279     {
280         ulong id;
281         float abc;
282         Foo foo;
283         string baz;
284     }
285 
286     assert (fieldNames!("", Bar) ==
287             ["'id'", "'abc'", "'foo.id'",
288              "'foo.xx'", "'foo.yy'", "'baz'"]);
289 }