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