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 }