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 }