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