1 /** 2 * Higher-level wrapper over etc.c.sqlite3 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.sys.sqlite3; 15 16 pragma(lib, "sqlite3"); 17 18 import etc.c.sqlite3; 19 import std.string : toStringz; 20 import std.conv : to; 21 22 final class SQLite 23 { 24 private sqlite3* db; 25 26 this(string fn) 27 { 28 sqenforce(sqlite3_open(toStringz(fn), &db)); 29 } 30 31 ~this() 32 { 33 sqlite3_close(db); 34 } 35 36 auto query(string sql) 37 { 38 struct Iterator 39 { 40 alias int delegate(ref const(char)[][] args, ref const(char)[][] columns) F; 41 42 string sql; 43 sqlite3* db; 44 F dg; 45 int fres; 46 Throwable throwable = null; 47 48 int opApply(F dg) 49 { 50 this.dg = dg; 51 auto res = sqlite3_exec(db, toStringz(sql), &callback, &this, null); 52 if (res == SQLITE_ABORT) 53 { 54 if (throwable) 55 throw throwable; 56 else 57 return fres; 58 } 59 else 60 if (res != SQLITE_OK) 61 throw new SQLiteException(db, res); 62 return 0; 63 } 64 65 static /*nothrow*/ extern(C) int callback(void* ctx, int argc, char** argv, char** colv) 66 { 67 auto i = cast(Iterator*)ctx; 68 static const(char)[][] args, cols; 69 args.length = cols.length = argc; 70 foreach (n; 0..argc) 71 args[n] = to!(const(char)[])(argv[n]), 72 cols[n] = to!(const(char)[])(colv[n]); 73 try 74 return i.fres = i.dg(args, cols); 75 catch (Exception e) 76 { 77 i.throwable = e; 78 return 1; 79 } 80 } 81 } 82 83 return Iterator(sql, db); 84 } 85 86 void exec(string sql) 87 { 88 foreach (cells, columns; query(sql)) 89 break; 90 } 91 92 @property long lastInsertRowID() 93 { 94 return sqlite3_last_insert_rowid(db); 95 } 96 97 final class PreparedStatement 98 { 99 sqlite3_stmt* stmt; 100 101 void bind(int idx, int v) 102 { 103 sqlite3_bind_int(stmt, idx, v); 104 } 105 106 void bind(int idx, long v) 107 { 108 sqlite3_bind_int64(stmt, idx, v); 109 } 110 111 void bind(int idx, double v) 112 { 113 sqlite3_bind_double(stmt, idx, v); 114 } 115 116 void bind(int idx, in char[] v) 117 { 118 sqlite3_bind_text(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT); 119 } 120 121 void bind(int idx, in wchar[] v) 122 { 123 sqlite3_bind_text16(stmt, idx, v.ptr, to!int(v.length*2), SQLITE_TRANSIENT); 124 } 125 126 void bind(int idx, void* n) 127 { 128 assert(n is null); 129 sqlite3_bind_null(stmt, idx); 130 } 131 132 void bind(int idx, in ubyte[] v) 133 { 134 sqlite3_bind_blob(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT); 135 } 136 137 void bindAll(T...)(T args) 138 { 139 foreach (int n, arg; args) 140 bind(n+1, arg); 141 } 142 143 /// Return "true" if a row is available, "false" if done. 144 bool step() 145 { 146 auto res = sqlite3_step(stmt); 147 if (res == SQLITE_DONE) 148 { 149 reset(); 150 return false; 151 } 152 else 153 if (res == SQLITE_ROW) 154 return true; 155 else 156 { 157 sqlite3_reset(stmt); 158 sqenforce(res); 159 return false; // only on SQLITE_OK, which shouldn't happen 160 } 161 } 162 163 void reset() 164 { 165 sqenforce(sqlite3_reset(stmt)); 166 } 167 168 void exec(T...)(T args) 169 { 170 static if (T.length) 171 bindAll!T(args); 172 while (step()) {} 173 } 174 175 static struct Iterator 176 { 177 PreparedStatement stmt; 178 179 @trusted int opApply(U...)(int delegate(ref U args) @system dg) 180 { 181 int res = 0; 182 while (stmt.step()) 183 { 184 scope(failure) stmt.reset(); 185 static if (U.length == 1 && is(U[0] V : V[string])) 186 { 187 U[0] result; 188 foreach (c; 0..stmt.columnCount()) 189 result[stmt.columnName(c)] = stmt.column!V(c); 190 res = dg(result); 191 } 192 else 193 { 194 U columns; 195 stmt.columns(columns); 196 res = dg(columns); 197 } 198 if (res) 199 { 200 stmt.reset(); 201 break; 202 } 203 } 204 return res; 205 } 206 } 207 208 Iterator iterate(T...)(T args) 209 { 210 static if (T.length) 211 bindAll!T(args); 212 return Iterator(this); 213 } 214 215 T column(T)(int idx) 216 { 217 static if (is(T == string)) 218 return (cast(char*)sqlite3_column_blob(stmt, idx))[0..sqlite3_column_bytes(stmt, idx)].idup; 219 else 220 static if (is(T == int)) 221 return sqlite3_column_int(stmt, idx); 222 else 223 static if (is(T == long)) 224 return sqlite3_column_int64(stmt, idx); 225 else 226 static if (is(T == bool)) 227 return sqlite3_column_int(stmt, idx) != 0; 228 else 229 static if (is(T == double)) 230 return sqlite3_column_double(stmt, idx); 231 else 232 static assert(0, "Can't get column with type " ~ T.stringof); 233 } 234 235 void columns(T...)(ref T args) 236 { 237 foreach (i, arg; args) 238 args[i] = column!(typeof(arg))(i); 239 } 240 241 T[] getArray(T=string)() 242 { 243 T[] result = new T[dataCount()]; 244 foreach (i, ref value; result) 245 value = column!T(i); 246 return result; 247 } 248 249 T[string] getAssoc(T=string)() 250 { 251 T[string] result; 252 foreach (i; 0..dataCount()) 253 result[columnName(i)] = column!T(i); 254 return result; 255 } 256 257 int columnCount() 258 { 259 return sqlite3_column_count(stmt); 260 } 261 262 int dataCount() 263 { 264 return sqlite3_data_count(stmt); 265 } 266 267 string columnName(int idx) 268 { 269 return to!string(sqlite3_column_name(stmt, idx)); 270 } 271 272 ~this() 273 { 274 sqlite3_finalize(stmt); 275 } 276 } 277 278 PreparedStatement prepare(string sql) 279 { 280 auto s = new PreparedStatement; 281 sqenforce(sqlite3_prepare_v2(db, toStringz(sql), -1, &s.stmt, null)); 282 return s; 283 } 284 285 private void sqenforce(int res) 286 { 287 if (res != SQLITE_OK) 288 throw new SQLiteException(db, res); 289 } 290 } 291 292 class SQLiteException : Exception 293 { 294 int code; 295 296 this(sqlite3* db, int code) 297 { 298 this.code = code; 299 super(to!string(sqlite3_errmsg(db))); 300 } 301 } 302