1 module microrm.queries; 2 3 import std.exception : enforce; 4 import std.algorithm : joiner; 5 import std.string : join; 6 7 import d2sqlite3; 8 9 import microrm.util; 10 import microrm.exception; 11 12 debug (microrm) import std.stdio : stderr; 13 14 enum BASEQUERYLENGTH = 512; 15 16 struct Select(T, BUF) 17 { 18 import std.range : InputRange; 19 20 mixin baseQueryData!("SELECT * FROM %s"); 21 mixin whereCondition; 22 23 private ref orderBy(string[] fields, string orderType) 24 { 25 assert(orderType == "ASC" || orderType == "DESC"); 26 query.put(" ORDER BY "); 27 query.put(fields.joiner(", ")); 28 query.put(" "); 29 query.put(orderType); 30 return this; 31 } 32 33 ref ascOrderBy(string[] fields...) { return orderBy(fields, "ASC"); } 34 ref descOrderBy(string[] fields...) { return orderBy(fields, "DESC"); } 35 36 auto run() @property 37 { 38 import std.range : inputRangeObject; 39 import std.algorithm : map; 40 41 enforce(db, "database is null"); 42 43 query.put(';'); 44 auto q = query.data.idup; 45 debug (microrm) stderr.writeln(q); 46 auto result = (*db).executeCheck(q); 47 48 static T qconv(typeof(result.front) e) 49 { 50 enum names = fieldNames!("", T)(); 51 T ret; 52 static string rr() 53 { 54 string[] res; 55 res ~= "import std.traits;"; 56 foreach (i, a; fieldNames!("", T)()) 57 { 58 res ~= `{`; 59 res ~= q{alias ET = typeof(ret.%s);}.format(a[1..$-1]); 60 res ~= q{static if (!isStaticArray!ET)}; 61 res ~= format(q{ret.%1$s = e[%2$d].as!ET;}, a[1..$-1], i); 62 res ~= q{else}; 63 res ~= ` 64 { 65 import std.algorithm : min; 66 auto ubval = e[%2$d].as!(ubyte[]); 67 auto etval = cast(typeof(ET.init[]))ubval; 68 auto ln = min(ret.%1$s.length, etval.length); 69 ret.%1$s[0..ln] = etval[0..ln]; 70 } 71 `.format(a[1..$-1], i); 72 res ~= `}`; 73 } 74 return res.join("\n"); 75 } 76 mixin(rr()); 77 return ret; 78 } 79 80 return result.map!qconv; 81 } 82 } 83 84 unittest 85 { 86 static struct Foo 87 { 88 ulong id; 89 string text; 90 ulong ts; 91 } 92 93 import std.array : Appender; 94 Appender!(char[]) buf; 95 96 auto test = Select!(Foo, typeof(buf))(null, &buf); 97 test.where("text =", "privet").and("ts >", 123); 98 assert (test.query.data == "SELECT * FROM Foo WHERE text = 'privet' AND ts > '123'"); 99 } 100 101 void buildInsertOrReplace(T, W)(ref W buf, bool replace, size_t valCount=1) 102 { 103 if (!replace) buf.put("INSERT INTO "); 104 else buf.put("INSERT OR REPLACE INTO "); 105 buf.put(tableName!T); 106 buf.put(" ("); 107 108 bool isInsertId = true; 109 110 auto tt = T.init; 111 foreach (i, f; tt.tupleof) 112 { 113 alias F = typeof(f); 114 enum name = __traits(identifier, tt.tupleof[i]); 115 116 static if (is(F==struct)) 117 enum tmp = fieldNames!(name, F)().join(","); 118 else 119 { 120 if (name == IDNAME && !replace) continue; 121 enum tmp = "'" ~ name ~ "'"; 122 } 123 124 buf.put(tmp); 125 static if (i+1 != tt.tupleof.length) 126 buf.put(","); 127 } 128 buf.put(") VALUES ("); 129 130 foreach (n; 0 .. valCount) 131 { 132 foreach (i, f; tt.tupleof) 133 { 134 enum name = __traits(identifier, tt.tupleof[i]); 135 alias F = typeof(f); 136 static if (is(F==struct)) buf.fmtValues!F; 137 else 138 { 139 if (name == IDNAME && !replace) continue; 140 buf.put("?"); 141 } 142 static if (i+1 != tt.tupleof.length) buf.put(","); 143 } 144 if (n+1 != valCount) buf.put("),("); 145 } 146 buf.put(");"); 147 } 148 149 void fmtValues(T, W)(ref W buf) 150 { 151 auto tt = T.init; 152 foreach (i, f; tt.tupleof) 153 { 154 alias F = typeof(f); 155 static if (is(F==struct)) buf.fmtValues!F; 156 else buf.put("?"); 157 static if (i+1 != tt.tupleof.length) 158 buf.put(","); 159 } 160 } 161 162 unittest 163 { 164 static struct Foo 165 { 166 ulong id; 167 string text; 168 float val; 169 ulong ts; 170 } 171 172 import std.array : appender; 173 auto buf = appender!(char[]); 174 175 buf.buildInsertOrReplace!Foo(true); 176 auto q = buf.data; 177 assert(q == "INSERT OR REPLACE INTO Foo "~ 178 "('id','text','val','ts') VALUES "~ 179 "(?,?,?,?);"); 180 buf.clear(); 181 buf.buildInsertOrReplace!Foo(false); 182 q = buf.data; 183 assert(q == "INSERT INTO Foo "~ 184 "('text','val','ts') VALUES "~ 185 "(?,?,?);"); 186 } 187 188 unittest 189 { 190 static struct Foo 191 { 192 ulong id; 193 string text; 194 float val; 195 ulong ts; 196 } 197 198 static struct Bar 199 { 200 ulong id; 201 float value; 202 Foo foo; 203 } 204 205 import std.array : appender; 206 auto buf = appender!(char[]); 207 208 buf.buildInsertOrReplace!Bar(true); 209 auto q = buf.data; 210 assert(q == "INSERT OR REPLACE INTO Bar "~ 211 "('id','value','foo.id','foo.text','foo.val','foo.ts') VALUES "~ 212 "(?,?,?,?,?,?);"); 213 buf.clear(); 214 buf.buildInsertOrReplace!Bar(false); 215 q = buf.data; 216 assert(q == "INSERT INTO Bar "~ 217 "('value','foo.id','foo.text','foo.val','foo.ts') VALUES "~ 218 "(?,?,?,?,?);"); 219 buf.clear(); 220 buf.buildInsertOrReplace!Bar(false, 3); 221 q = buf.data; 222 assert(q == "INSERT INTO Bar "~ 223 "('value','foo.id','foo.text','foo.val','foo.ts') VALUES "~ 224 "(?,?,?,?,?),(?,?,?,?,?),(?,?,?,?,?);"); 225 } 226 227 unittest 228 { 229 struct Foo { string text; float val; ulong ts; } 230 struct Bar { float v; Foo foo; } 231 struct Baz { ulong id; float v; Bar xyz; float w; } 232 233 234 import std.array : appender; 235 auto buf = appender!(char[]); 236 237 buf.buildInsertOrReplace!Baz(true); 238 auto q = buf.data; 239 assert(q == "INSERT OR REPLACE INTO Baz "~ 240 "('id','v','xyz.v',"~ 241 "'xyz.foo.text','xyz.foo.val','xyz.foo.ts','w') VALUES "~ 242 "(?,?,?,?,"~"?,?,?);"); 243 } 244 245 struct Delete(T, BUF) 246 { 247 mixin baseQueryData!("DELETE FROM %s"); 248 mixin whereCondition; 249 250 auto run() @property 251 { 252 enforce(db, "database is null"); 253 254 query.put(';'); 255 auto q = query.data.idup; 256 debug (microrm) stderr.writeln(q); 257 return (*db).executeCheck(q); 258 } 259 } 260 261 unittest 262 { 263 static struct Foo 264 { 265 ulong id; 266 string text; 267 ulong ts; 268 } 269 270 import std.array : Appender; 271 Appender!(char[]) buf; 272 273 auto test = Delete!(Foo, typeof(buf))(null, &buf); 274 test.where("text =", "privet").and("ts >", 123); 275 assert (test.query.data == "DELETE FROM Foo WHERE text = 'privet' AND ts > '123'"); 276 } 277 278 struct Count(T, BUF) 279 { 280 mixin baseQueryData!("SELECT Count(*) FROM %s"); 281 mixin whereCondition; 282 283 size_t run() @property 284 { 285 enforce(db, "database is null"); 286 auto q = query.data.idup; 287 debug (microrm) stderr.writeln(q); 288 return (*db).executeCheck(q).front.front.as!size_t; 289 } 290 } 291 292 unittest 293 { 294 static struct Foo 295 { 296 ulong id; 297 string text; 298 ulong ts; 299 } 300 301 import std.array : Appender; 302 Appender!(char[]) buf; 303 304 auto test = Count!(Foo, typeof(buf))(null, &buf); 305 test.where("text =", "privet").and("ts >", 123); 306 assert (test.query.data == "SELECT Count(*) FROM Foo WHERE text = 'privet' AND ts > '123'"); 307 }