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