1 module microrm.queries;
2 
3 import std.format : formattedWrite;
4 import std.exception : enforce;
5 
6 import d2sqlite3;
7 
8 import microrm.util;
9 
10 debug (microrm) import std.stdio : stderr;
11 
12 enum BASEQUERYLENGTH = 512;
13 
14 struct Select(T)
15 {
16     import std.range : InputRange;
17 
18     mixin baseQueryData!("SELECT * FROM %s", BASEQUERYLENGTH);
19     mixin whereCondition;
20 
21     private ref orderBy(string[] fields, string orderType)
22     {
23         assert(orderType == "ASC" || orderType == "DESC");
24         query.formattedWrite(" ORDER BY %(-%s, %) %s", fields, orderType);
25         return this;
26     }
27 
28     ref ascOrderBy(string[] fields...) { return orderBy(fields, "ASC"); }
29     ref descOrderBy(string[] fields...) { return orderBy(fields, "DESC"); }
30 
31     InputRange!T run() @property
32     {
33         import std.range : inputRangeObject;
34         import std.algorithm : map;
35 
36         enforce(db, "database is null");
37 
38         query.put(';');
39         auto q = query.data.idup;
40         debug (microrm) stderr.writeln(q);
41         auto result = (*db).execute(q);
42 
43         static T qconv(typeof(result.front) e)
44         {
45             T ret;
46             foreach (i, ref f; ret.tupleof)
47                 f = e[__traits(identifier, ret.tupleof[i])].as!(typeof(f));
48             return ret;
49         }
50 
51         return inputRangeObject(result.map!qconv);
52     }
53 }
54 
55 unittest
56 {
57     static struct Foo
58     {
59         ulong id;
60         string text;
61         ulong ts;
62     }
63 
64     auto test = Select!Foo(null);
65     test.where("text =", "privet").and("ts >", 123);
66     assert (test.query.data == "SELECT * FROM Foo WHERE text = 'privet' AND ts > '123'");
67 }
68 
69 void buildInsert(W, T)(ref W buf, bool orInsert, T[] arr...)
70 {
71     assert(arr.length);
72 
73     static void vconv(Y, X)(ref Y wrt, X x)
74     {
75         import std.traits;
76         static if (isFloatingPoint!X)
77         {
78             if (x == x) wrt.formattedWrite("%e", x);
79             else wrt.formattedWrite("null");
80         }
81         else static if (isNumeric!X)
82             wrt.formattedWrite("%d", x);
83         else wrt.formattedWrite("'%s'", x);
84     }
85 
86     buf.formattedWrite("INSERT ");
87     if (orInsert) buf.formattedWrite("OR REPLACE ");
88     buf.formattedWrite("INTO %s (", tableName!T);
89     auto tt = arr[0];
90     foreach (i, f; tt.tupleof)
91     {
92         enum name = __traits(identifier, tt.tupleof[i]);
93         if (name != IDNAME || orInsert)
94         {
95             buf.formattedWrite(name);
96             static if (i+1 != tt.tupleof.length)
97                 buf.formattedWrite(", ");
98         }
99     }
100     buf.formattedWrite(") VALUES (");
101     foreach (n, v; arr)
102     {
103         foreach (i, f; v.tupleof)
104         {
105             enum name = __traits(identifier, v.tupleof[i]);
106             if (name != IDNAME || orInsert)
107             {
108                 vconv(buf, f);
109                 static if (i+1 != v.tupleof.length)
110                     buf.formattedWrite(", ");
111             }
112         }
113         if (n+1 != arr.length)
114             buf.formattedWrite("), (");
115     }
116     buf.formattedWrite(");");
117 }
118 
119 unittest
120 {
121     static struct Foo
122     {
123         ulong id;
124         string text;
125         float val;
126         ulong ts;
127     }
128 
129     import std.array : appender;
130     auto buf = appender!(char[]);
131 
132     buf.buildInsert(Foo(1, "hello", 3.14, 12), Foo(2, "world", 2.7, 42));
133 
134     auto q = buf.data;
135     assert(q == "INSERT INTO Foo (text, val, ts) VALUES "~
136                 "('hello', 3.140000e+00, 12), ('world', 2.700000e+00, 42);");
137 }
138 
139 struct Delete(T)
140 {
141     mixin baseQueryData!("DELETE FROM %s", BASEQUERYLENGTH);
142     mixin whereCondition;
143 
144     auto run() @property
145     {
146         enforce(db, "database is null");
147 
148         query.put(';');
149         auto q = query.data.idup;
150         debug (microrm) stderr.writeln(q);
151         return (*db).execute(q);
152     }
153 }
154 
155 unittest
156 {
157     static struct Foo
158     {
159         ulong id;
160         string text;
161         ulong ts;
162     }
163 
164     auto test = Delete!Foo(null);
165     test.where("text =", "privet").and("ts >", 123);
166     assert (test.query.data == "DELETE FROM Foo WHERE text = 'privet' AND ts > '123'");
167 }
168 
169 struct Count(T)
170 {
171     mixin baseQueryData!("SELECT Count(*) FROM %s", BASEQUERYLENGTH);
172     mixin whereCondition;
173 
174     size_t run() @property
175     {
176         enforce(db, "database is null");
177         auto q = query.data.idup;
178         debug (microrm) stderr.writeln(q);
179         return (*db).execute(q).front.front.as!size_t;
180     }
181 }
182 
183 unittest
184 {
185     static struct Foo
186     {
187         ulong id;
188         string text;
189         ulong ts;
190     }
191 
192     auto test = Count!Foo(null);
193     test.where("text =", "privet").and("ts >", 123);
194     assert (test.query.data == "SELECT Count(*) FROM Foo WHERE text = 'privet' AND ts > '123'");
195 }