1 module utile.db.sqlite; 2 import std, core.sync.mutex, core.sync.rwmutex, etc.c.sqlite3, utile.except, utile.db, utile.misc; 3 4 final class SQLite 5 { 6 this(string name) 7 { 8 const(char)* p; 9 auto flags = SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; 10 11 if (name.empty) 12 { 13 flags |= SQLITE_OPEN_MEMORY; 14 } 15 else 16 p = name.toStringz; 17 18 auto code = sqlite3_open_v2(p, &_db, flags, null); 19 code == SQLITE_OK || error(code); 20 21 exec(`pragma temp_store = MEMORY;`); 22 exec(`pragma synchronous = NORMAL;`); 23 24 _mutex = new ReadWriteMutex(ReadWriteMutex.Policy.PREFER_READERS); 25 } 26 27 ~this() 28 { 29 _cache.byValue.each!(a => remove(a)); 30 sqlite3_close(_db); 31 } 32 33 void backup(SQLite dest) 34 { 35 auto bk = sqlite3_backup_init(dest._db, MainDb, _db, MainDb); 36 bk || throwError(`cannot init backup`); 37 38 scope (exit) 39 { 40 sqlite3_backup_finish(bk); 41 } 42 43 auto code = sqlite3_backup_step(bk, -1); 44 code == SQLITE_DONE || error(code); 45 } 46 47 static Blob blobNull() => ( & _null)[0 .. 0]; 48 static string textNull() => cast(string)blobNull; 49 50 void begin() => exec(`begin;`); 51 void commit() => exec(`commit;`); 52 void rollback() => exec(`rollback;`); 53 54 mixin DbBase; 55 private: 56 enum immutable(char)[4] MainDb = `main`; 57 58 void exec(const(char)* sql) 59 { 60 char* msg; 61 sqlite3_exec(_db, sql, null, null, &msg); 62 63 if (msg) 64 { 65 auto s = msg.fromStringz.idup; 66 sqlite3_free(msg); 67 68 throwError!`error executing query: %s`(s); 69 } 70 } 71 72 void process(S* s) 73 { 74 execute(s.stmt); 75 reset(s); 76 } 77 78 auto process(A...)(S* s) 79 { 80 auto self = this; // TODO: DMD BUG 81 auto stmt = s.stmt; 82 83 struct S 84 { 85 this(this) @disable; 86 87 ~this() => self.reset(s); 88 89 const empty() => !_hasRow; 90 91 void popFront() 92 in 93 { 94 assert(_hasRow); 95 } 96 do 97 { 98 _hasRow = self.execute(stmt); 99 } 100 101 auto array() 102 { 103 ReturnType!front[] res; 104 105 for (; _hasRow; popFront) 106 { 107 res ~= front; 108 } 109 110 return res; 111 } 112 113 auto front() 114 in 115 { 116 assert(_hasRow); 117 } 118 do 119 { 120 Tuple!A r; 121 122 debug 123 { 124 auto N = r.Types.length; 125 auto cnt = sqlite3_column_count(stmt); 126 127 cnt == N || throwError!`expected %u columns, but query returned %u`(N, cnt); 128 } 129 130 foreach (i, ref v; r) 131 { 132 alias T = r.Types[i]; 133 134 static if (isFloatingPoint!T) 135 { 136 v = cast(T)sqlite3_column_double(stmt, i); 137 } 138 else static if (isIntegral!T) 139 { 140 v = cast(T)sqlite3_column_int64(stmt, i); 141 } 142 else static if (is(T == string)) 143 { 144 v = sqlite3_column_text(stmt, i)[0 .. dataLen(i)].idup; 145 } 146 else static if (is(T == Blob)) 147 { 148 v = cast(Blob)sqlite3_column_blob(stmt, i)[0 .. dataLen(i)].dup; 149 } 150 else 151 static assert(false); 152 } 153 154 static if (A.length > 1) 155 return r; 156 else 157 return r[0]; 158 } 159 160 private: 161 auto dataLen(uint col) => sqlite3_column_bytes(stmt, col); 162 163 bool _hasRow; 164 } 165 166 return S(execute(stmt)); 167 } 168 169 auto prepare(string sql) 170 { 171 S* s; 172 173 synchronized (_mutex.reader) 174 { 175 s = _cache.get(sql, null); 176 } 177 178 if (s) 179 { 180 s.mutex.lock; 181 return s; 182 } 183 184 synchronized (_mutex.writer) 185 { 186 s = _cache.get(sql, null); 187 188 if (s is null) 189 { 190 sqlite3_stmt* stmt; 191 192 auto code = sqlite3_prepare_v2(_db, sql.toStringz, cast(uint)sql.length, &stmt, null); 193 code == SQLITE_OK || error(code); 194 195 s = _cache[sql] = new S(new Mutex, stmt); 196 } 197 198 s.mutex.lock; 199 return s; 200 } 201 } 202 203 void bind(A...)(S* s, A args) 204 { 205 auto stmt = s.stmt; 206 207 debug 208 { 209 auto cnt = sqlite3_bind_parameter_count(stmt); 210 A.length == cnt || throwError!`expected %u parameters to bind, but %u provided`(cnt, A.length); 211 } 212 213 foreach (uint i, v; args) 214 { 215 alias T = Unqual!(typeof(v)); 216 217 uint code; 218 uint idx = i + 1; 219 220 static if (is(T == typeof(null))) 221 { 222 code = sqlite3_bind_null(stmt, idx); 223 } 224 else static if (isFloatingPoint!T) 225 { 226 code = sqlite3_bind_double(stmt, idx, v); 227 } 228 else static if (isIntegral!T) 229 { 230 code = sqlite3_bind_int64(stmt, idx, v); 231 } 232 else static if (is(T == string)) 233 { 234 const(char)* p; 235 236 if (cast(void*)v.ptr is &_null) 237 { 238 p = null; 239 } 240 else 241 p = v.length ? v.ptr : cast(const(char)*)&_null; 242 243 code = sqlite3_bind_text64(stmt, idx, p, v.length, SQLITE_TRANSIENT, SQLITE_UTF8); 244 } 245 else static if (is(T == Blob)) 246 { 247 const(ubyte)* p; 248 249 if (v.ptr is &_null) 250 { 251 p = null; 252 } 253 else 254 p = v.length ? v.ptr : &_null; 255 256 code = sqlite3_bind_blob64(stmt, idx, p, v.length, SQLITE_TRANSIENT); 257 } 258 else 259 static assert(false); 260 261 code == SQLITE_OK || error(code); 262 } 263 } 264 265 auto lastId(S * ) => sqlite3_last_insert_rowid(_db); 266 auto affected(S * ) => sqlite3_changes(_db); 267 private: 268 void reset(S* s) 269 { 270 sqlite3_reset(s.stmt); 271 s.mutex.unlock; 272 } 273 274 void remove(S* s) 275 { 276 s.mutex.destroy; 277 sqlite3_finalize(s.stmt); 278 } 279 280 bool execute(sqlite3_stmt* stmt) 281 { 282 auto res = sqlite3_step(stmt); 283 res == SQLITE_ROW || res == SQLITE_DONE || error(res); 284 return res == SQLITE_ROW; 285 } 286 287 bool error(uint code) 288 { 289 return throwError(sqlite3_errstr(code).fromStringz.assumeUnique); 290 } 291 292 struct S 293 { 294 Mutex mutex; 295 sqlite3_stmt* stmt; 296 } 297 298 sqlite3* _db; 299 300 S*[string] _cache; 301 ReadWriteMutex _mutex; 302 303 immutable __gshared ubyte _null; 304 }