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 <ae@cy.md> 12 */ 13 14 module ae.sys.sqlite3; 15 16 pragma(lib, "sqlite3"); 17 18 import etc.c.sqlite3; 19 import std.exception; 20 import std.string : toStringz; 21 import std.conv : to; 22 import std.traits; 23 import std.typecons : Nullable; 24 25 /// `sqlite3*` wrapper. 26 final class SQLite 27 { 28 /// C `sqlite3*` object. 29 sqlite3* db; 30 31 this(string fn, bool readOnly = false) 32 { 33 sqenforce(sqlite3_open_v2(toStringz(fn), &db, readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null)); 34 } /// 35 36 ~this() /*@nogc*/ 37 { 38 sqlite3_close(db); 39 } 40 41 /// Run a simple query, provided as an SQL string. 42 auto query(string sql) 43 { 44 struct Iterator 45 { 46 alias int delegate(ref const(char)[][] args, ref const(char)[][] columns) F; 47 48 string sql; 49 sqlite3* db; 50 F dg; 51 int fres; 52 Throwable throwable = null; 53 54 int opApply(F dg) 55 { 56 this.dg = dg; 57 auto res = sqlite3_exec(db, toStringz(sql), &callback, &this, null); 58 if (res == SQLITE_ABORT) 59 { 60 if (throwable) 61 throw throwable; 62 else 63 return fres; 64 } 65 else 66 if (res != SQLITE_OK) 67 throw new SQLiteException(db, res); 68 return 0; 69 } 70 71 static /*nothrow*/ extern(C) int callback(void* ctx, int argc, char** argv, char** colv) 72 { 73 auto i = cast(Iterator*)ctx; 74 static const(char)[][] args, cols; 75 args.length = cols.length = argc; 76 foreach (n; 0..argc) 77 args[n] = to!(const(char)[])(argv[n]), 78 cols[n] = to!(const(char)[])(colv[n]); 79 try 80 return i.fres = i.dg(args, cols); 81 catch (Exception e) 82 { 83 i.throwable = e; 84 return 1; 85 } 86 } 87 } 88 89 return Iterator(sql, db); 90 } 91 92 /// Run a simple query, discarding the result. 93 void exec(string sql) 94 { 95 foreach (cells, columns; query(sql)) 96 break; 97 } 98 99 /// Return the ID of the last inserted row. 100 /// (`sqlite3_last_insert_rowid`) 101 @property long lastInsertRowID() 102 { 103 return sqlite3_last_insert_rowid(db); 104 } 105 106 /// Return the number of changed rows. 107 /// (`sqlite3_changes`) 108 @property int changes() 109 { 110 return sqlite3_changes(db); 111 } 112 113 /// `sqlite3_stmt*` wrapper. 114 final class PreparedStatement 115 { 116 private sqlite3_stmt* stmt; 117 118 /// `sqlite3_bind_XXX` wrapper. 119 void bind(int idx, int v) 120 { 121 sqlite3_bind_int(stmt, idx, v); 122 } 123 124 void bind(int idx, long v) 125 { 126 sqlite3_bind_int64(stmt, idx, v); 127 } /// ditto 128 129 void bind(int idx, double v) 130 { 131 sqlite3_bind_double(stmt, idx, v); 132 } /// ditto 133 134 void bind(int idx, in char[] v) 135 { 136 sqlite3_bind_text(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT); 137 } /// ditto 138 139 void bind(int idx, in wchar[] v) 140 { 141 sqlite3_bind_text16(stmt, idx, v.ptr, to!int(v.length*2), SQLITE_TRANSIENT); 142 } /// ditto 143 144 void bind(int idx, void* n) 145 { 146 assert(n is null); 147 sqlite3_bind_null(stmt, idx); 148 } /// ditto 149 150 void bind(int idx, const(void)[] v) 151 { 152 sqlite3_bind_blob(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT); 153 } /// ditto 154 155 void bind(T)(int idx, Nullable!T v) 156 if (is(typeof(bind(idx, v.get())))) 157 { 158 if (v.isNull) 159 sqlite3_bind_null(stmt, idx); 160 else 161 bind(v.get()); 162 } /// ditto 163 164 /// Bind all arguments according to their type, in order. 165 void bindAll(T...)(T args) 166 { 167 foreach (int n, arg; args) 168 bind(n+1, arg); 169 } 170 171 /// Return "true" if a row is available, "false" if done. 172 bool step() 173 { 174 auto res = sqlite3_step(stmt); 175 if (res == SQLITE_DONE) 176 { 177 reset(); 178 return false; 179 } 180 else 181 if (res == SQLITE_ROW) 182 return true; 183 else 184 { 185 scope(exit) sqlite3_reset(stmt); 186 sqenforce(res); 187 return false; // only on SQLITE_OK, which shouldn't happen 188 } 189 } 190 191 /// Calls `sqlite3_reset`. 192 void reset() 193 { 194 sqenforce(sqlite3_reset(stmt)); 195 } 196 197 /// Binds the given arguments and executes the prepared statement, discarding the result. 198 void exec(T...)(T args) 199 { 200 static if (T.length) 201 bindAll!T(args); 202 while (step()) {} 203 } 204 205 /// Binds the given arguments and executes the prepared statement, returning the results as an iterator. 206 static struct Iterator 207 { 208 PreparedStatement stmt; /// 209 210 @trusted int opApply(U...)(int delegate(ref U args) @system dg) 211 { 212 int res = 0; 213 while (stmt.step()) 214 { 215 scope(failure) stmt.reset(); 216 static if (U.length == 1 && is(U[0] V : V[]) && 217 !is(U[0] : string) && !is(Unqual!V == void) && !is(V == ubyte)) 218 { 219 U[0] result; 220 result.length = stmt.columnCount(); 221 foreach (int c, ref r; result) 222 r = stmt.column!V(c); 223 res = dg(result); 224 } 225 else 226 static if (U.length == 1 && is(U[0] V : V[string])) 227 { 228 U[0] result; 229 foreach (c; 0..stmt.columnCount()) 230 result[stmt.columnName(c)] = stmt.column!V(c); 231 res = dg(result); 232 } 233 else 234 { 235 U columns; 236 stmt.columns(columns); 237 res = dg(columns); 238 } 239 if (res) 240 { 241 stmt.reset(); 242 break; 243 } 244 } 245 return res; 246 } /// 247 } 248 249 /// ditto 250 Iterator iterate(T...)(T args) 251 { 252 static if (T.length) 253 bindAll!T(args); 254 return Iterator(this); 255 } 256 257 /// Returns the value of a column by its index, as the given D type. 258 T column(T)(int idx) 259 { 260 static if (is(T == Nullable!U, U)) 261 return sqlite3_column_type(stmt, idx) == SQLITE_NULL 262 ? T.init 263 : T(column!U(idx)); 264 else 265 static if (is(T == string)) 266 return (cast(char*)sqlite3_column_blob(stmt, idx))[0..sqlite3_column_bytes(stmt, idx)].idup; 267 else 268 static if (is(T V : V[]) && (is(Unqual!V == void) || is(V == ubyte))) 269 { 270 auto arr = (cast(V*)sqlite3_column_blob(stmt, idx))[0..sqlite3_column_bytes(stmt, idx)]; 271 static if (isStaticArray!T) 272 { 273 enforce(arr.length == T.length, "Wrong size for static array column"); 274 return arr[0..T.length]; 275 } 276 else 277 return arr.dup; 278 } 279 else 280 static if (is(T == int)) 281 return sqlite3_column_int(stmt, idx); 282 else 283 static if (is(T == long)) 284 return sqlite3_column_int64(stmt, idx); 285 else 286 static if (is(T == bool)) 287 return sqlite3_column_int(stmt, idx) != 0; 288 else 289 static if (is(T == double)) 290 return sqlite3_column_double(stmt, idx); 291 else 292 static assert(0, "Can't get column with type " ~ T.stringof); 293 } 294 295 unittest 296 { 297 PreparedStatement s; 298 if (false) 299 { 300 s.column!(void[])(0); 301 s.column!(ubyte[])(0); 302 s.column!(void[16])(0); 303 s.column!(ubyte[16])(0); 304 s.column!(Nullable!(ubyte[16]))(0); 305 } 306 } 307 308 /// Returns the value of all columns, as the given D types. 309 void columns(T...)(ref T args) 310 { 311 foreach (i, arg; args) 312 args[i] = column!(typeof(arg))(i); 313 } 314 315 /// Returns the value of all columns, as an array of the given D type (`string` by default). 316 T[] getArray(T=string)() 317 { 318 T[] result = new T[dataCount()]; 319 foreach (i, ref value; result) 320 value = column!T(i); 321 return result; 322 } 323 324 /// Returns the value of all columns as an associative array, 325 /// with the column names as the key, 326 /// and the values with given D type (`string` by default). 327 T[string] getAssoc(T=string)() 328 { 329 T[string] result; 330 foreach (i; 0..dataCount()) 331 result[columnName(i)] = column!T(i); 332 return result; 333 } 334 335 /// `sqlite3_column_count` wrapper. 336 int columnCount() 337 { 338 return sqlite3_column_count(stmt); 339 } 340 341 /// `sqlite3_data_count` wrapper. 342 int dataCount() 343 { 344 return sqlite3_data_count(stmt); 345 } 346 347 /// Returns the column name by its index, as a D string. 348 string columnName(int idx) 349 { 350 return to!string(sqlite3_column_name(stmt, idx)); 351 } 352 353 ~this() /*@nogc*/ 354 { 355 sqlite3_finalize(stmt); 356 } 357 } 358 359 /// Construct a prepared statement. 360 PreparedStatement prepare(string sql) 361 { 362 auto s = new PreparedStatement; 363 const(char)* tail; 364 auto sqlz = toStringz(sql); 365 sqenforce(sqlite3_prepare_v2(db, sqlz, -1, &s.stmt, &tail)); 366 assert(tail == sqlz + sql.length, "Trailing SQL not compiled: " ~ sql[tail - sqlz .. $]); 367 return s; 368 } 369 370 private void sqenforce(int res) 371 { 372 if (res != SQLITE_OK) 373 throw new SQLiteException(db, res); 374 } 375 } 376 377 /// Exception class thrown on SQLite errors. 378 class SQLiteException : Exception 379 { 380 int code; /// 381 382 this(sqlite3* db, int code) 383 { 384 this.code = code; 385 super(to!string(sqlite3_errmsg(db)) ~ " (" ~ to!string(code) ~ ")"); 386 } /// 387 }