1 /// 2 module microrm.api; 3 4 import std.array : Appender; 5 import std.range; 6 7 import microrm.queries; 8 import microrm.util; 9 import microrm.exception; 10 11 import d2sqlite3; 12 13 debug (microrm) import std.stdio : stderr; 14 15 //version = microrm_cache_stmt; 16 17 /// 18 class MDatabase 19 { 20 version (microrm_cache_stmt) 21 private Statement[string] cachedStmt; 22 23 private Appender!(char[]) buf; 24 25 /// 26 Database db; 27 28 /// 29 this(string path, 30 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 31 size_t queryBufferInitReserve=512) 32 { 33 db = Database(path, flags); 34 buf.reserve(queryBufferInitReserve); 35 } 36 37 void run(string sql, scope bool delegate(ResultRange) dg = null) 38 { db.run(sql, dg); } 39 40 void close() { db.close(); } 41 42 /// 43 auto select(T)() @property 44 { 45 buf.clear(); 46 return Select!(T, typeof(buf))(&db, &buf); 47 } 48 49 /// 50 auto count(T)() @property 51 { 52 buf.clear(); 53 return Count!(T, typeof(buf))(&db, &buf); 54 } 55 56 /// 57 void insert(bool all=false, T)(T[] arr...) if (!isInputRange!T) 58 { procInsert!all(false, arr); } 59 /// 60 void insertOrReplace(bool all=false, T)(T[] arr...) if (!isInputRange!T) 61 { procInsert!all(true, arr); } 62 63 /// 64 void insert(bool all=false, R)(R rng) 65 if (isInputRange!R && ((all && hasLength!R) || !all)) 66 { procInsert!all(false, rng); } 67 /// 68 void insertOrReplace(bool all=false, R)(R rng) 69 if (isInputRange!R && ((all && hasLength!R) || !all)) 70 { procInsert!all(true, rng); } 71 72 private auto procInsert(bool all=false, R)(bool replace, R rng) 73 if ((all && hasLength!R) || !all) 74 { 75 buf.clear; 76 alias T = ElementType!R; 77 static if (all) 78 buf.buildInsertOrReplace!T(replace, rng.length); 79 else 80 buf.buildInsertOrReplace!T(replace); 81 82 auto sql = buf.data.idup; 83 84 debug (microrm) stderr.writeln(sql); 85 86 version (microrm_cache_stmt) 87 { 88 if (sql !in cachedStmt) 89 cachedStmt[sql] = db.prepare(sql); 90 auto stmt = cachedStmt[sql]; 91 } 92 else auto stmt = db.prepare(sql); 93 94 int n; 95 static if (all) 96 { 97 foreach (v; rng) 98 bindStruct(stmt, v, replace, n); 99 stmt.execute(); 100 stmt.reset(); 101 } 102 else foreach (v; rng) 103 { 104 n = 0; 105 bindStruct(stmt, v, replace, n); 106 stmt.execute(); 107 stmt.reset(); 108 } 109 110 version (microrm_cache_stmt) {} 111 else stmt.finalize(); 112 113 return true; 114 } 115 116 /// 117 auto del(T)() 118 { 119 buf.clear(); 120 return Delete!(T, typeof(buf))(&db, &buf); 121 } 122 123 /// 124 auto lastInsertId() @property 125 { 126 return db. 127 executeCheck("SELECT last_insert_rowid()"). 128 front.front.as!ulong; 129 } 130 131 private static int bindStruct(T)(ref Statement stmt, T v, bool replace, ref int n) 132 { 133 foreach (i, f; v.tupleof) 134 { 135 enum name = __traits(identifier, v.tupleof[i]); 136 alias F = typeof(f); 137 static if (is(F==struct)) 138 bindStruct(stmt, f, replace, n); 139 else 140 { 141 if (name == IDNAME && !replace) continue; 142 stmt.bind(n+1, f); 143 n++; 144 } 145 } 146 return n; 147 } 148 } 149 150 version (unittest) 151 { 152 import microrm.schema; 153 154 import std.conv : text, to; 155 import std.range; 156 import std.algorithm; 157 import std.datetime; 158 import std.array; 159 import std.stdio; 160 } 161 162 unittest 163 { 164 struct One 165 { 166 ulong id; 167 string text; 168 } 169 170 import std.experimental.allocator; 171 import std.experimental.allocator.mallocator; 172 import std.experimental.allocator.building_blocks.scoped_allocator; 173 174 MDatabase db; 175 ScopedAllocator!Mallocator scalloc; 176 db = scalloc.make!MDatabase(":memory:"); 177 scope (exit) db.close(); 178 db.run(buildSchema!One); 179 180 assert(db.count!One.run == 0); 181 db.insert(iota(0,10).map!(i=>One(i*100,"hello" ~ text(i)))); 182 assert(db.count!One.run == 10); 183 184 auto ones = db.select!One.run.array; 185 assert(ones.length == 10); 186 assert(ones.all!(a=>a.id < 100)); 187 assert(db.lastInsertId == ones[$-1].id); 188 db.del!One.run; 189 assert(db.count!One.run == 0); 190 db.insertOrReplace(iota(0,499).map!(i=>One((i+1)*100,"hello" ~ text(i)))); 191 assert(ones.length == 10); 192 ones = db.select!One.run.array; 193 assert(ones.length == 499); 194 assert(ones.all!(a=>a.id >= 100)); 195 assert(db.lastInsertId == ones[$-1].id); 196 } 197 198 unittest 199 { 200 struct One 201 { 202 ulong id; 203 string text; 204 } 205 206 auto db = new MDatabase(":memory:"); 207 scope (exit) db.close(); 208 db.run(buildSchema!One); 209 210 assert(db.count!One.run == 0); 211 db.insert!true(iota(0,10).map!(i=>One(i*100,"hello" ~ text(i)))); 212 assert(db.count!One.run == 10); 213 214 auto ones = db.select!One.run.array; 215 assert(ones.length == 10); 216 assert(ones.all!(a=>a.id < 100)); 217 assert(db.lastInsertId == ones[$-1].id); 218 db.del!One.run; 219 assert(db.count!One.run == 0); 220 import std.datetime; 221 import std.conv : to; 222 db.insertOrReplace!true(iota(0,499).map!(i=>One((i+1)*100,"hello" ~ text(i)))); 223 assert(ones.length == 10); 224 ones = db.select!One.run.array; 225 assert(ones.length == 499); 226 assert(ones.all!(a=>a.id >= 100)); 227 assert(db.lastInsertId == ones[$-1].id); 228 } 229 230 unittest 231 { 232 struct Limit { int min, max; } 233 struct Limits { Limit volt, curr; } 234 struct Settings 235 { 236 ulong id; 237 Limits limits; 238 } 239 240 auto db = new MDatabase(":memory:"); 241 scope (exit) db.close(); 242 db.run(buildSchema!Settings); 243 assert(db.count!Settings.run == 0); 244 db.insertOrReplace(Settings(10, Limits(Limit(0,12), Limit(-10, 10)))); 245 assert(db.count!Settings.run == 1); 246 247 db.insertOrReplace(Settings(10, Limits(Limit(0,2), Limit(-3, 3)))); 248 db.insertOrReplace(Settings(11, Limits(Limit(0,11), Limit(-11, 11)))); 249 db.insertOrReplace(Settings(12, Limits(Limit(0,12), Limit(-12, 12)))); 250 251 assert(db.count!Settings.run == 3); 252 assert(db.count!Settings.where(`"limits.volt.max" = `, 2).run == 1); 253 assert(db.count!Settings.where(`"limits.volt.max" > `, 10).run == 2); 254 db.del!Settings.where(`"limits.volt.max" < `, 10).run; 255 assert(db.count!Settings.run == 2); 256 } 257 258 unittest 259 { 260 struct Settings 261 { 262 ulong id; 263 int[5] data; 264 } 265 266 auto db = new MDatabase(":memory:"); 267 scope (exit) db.close(); 268 db.run(buildSchema!Settings); 269 270 db.insert(Settings(0, [1,2,3,4,5])); 271 272 assert(db.count!Settings.run == 1); 273 auto s = db.select!Settings.run.front; 274 assert(s.data == [1,2,3,4,5]); 275 }