1 /**
2  * Authors: Jason White, Alexey Khmara
3  *
4  * Description: Wraps the SQLite3 C API in D goodness.
5  * This is an updated version of $(WEB https://github.com/bayun/SQLite3-D).
6  */
7 module util.sqlite3;
8 
9 import etc.c.sqlite3;
10 import std.string, std.exception;
11 
12 
13 /**
14  * This is thrown if something went wrong in SQLite3.
15  */
16 class SQLite3Exception : Exception
17 {
18     // SQLite3 error code.
19     // See https://www.sqlite.org/rescode.html
20     int code;
21 
22     this(int code, string msg, string file = __FILE__, size_t line = __LINE__)
23     {
24         super(msg, file, line);
25         this.code = code;
26     }
27 
28     this(sqlite3 *db, string msg = null, string file = __FILE__, size_t line = __LINE__)
29     {
30         super(fromStringz(sqlite3_errmsg(db)), file, line);
31         this.code = sqlite3_errcode(db);
32     }
33 }
34 
35 T sqliteEnforce(T, string file = __FILE__, size_t line = __LINE__)
36     (T value, sqlite3 *db, lazy string msg = null)
37 {
38     // TODO: Use extended result codes instead.
39     if (!value)
40         throw new SQLite3Exception(db, msg, file, line);
41     return value;
42 }
43 
44 
45 /**
46  * SQLite3 database wrapper.
47  */
48 class SQLite3
49 {
50     private sqlite3* db;
51 
52     /**
53      * Opens the given database file.
54      */
55     this(string file)
56     {
57         open(file);
58     }
59 
60     /**
61      * Takes control of an existing database handle. The database will still be
62      * closed upon destruction.
63      */
64     this(sqlite3* db)
65     {
66         this.db = db;
67     }
68 
69     /**
70      * Closes the database.
71      */
72     ~this()
73     {
74         close();
75     }
76 
77     /**
78      * Opens or creates a database.
79      */
80     void open(string file)
81     {
82         close();
83 
84         auto r = sqlite3_open(&(toStringz(file))[0], &db);
85         if (r != SQLITE_OK) {
86             throw new SQLite3Exception(r, "Cannot open database " ~ file);
87         }
88     }
89 
90     /**
91      * Takes control of an existing database handle. The database will still be
92      * closed upon destruction.
93      */
94     void open(sqlite3* db)
95     {
96         close();
97         this.db = db;
98     }
99 
100     /**
101      * Closes the database.
102      */
103     void close()
104     {
105         if (db)
106         {
107             sqlite3_close(db);
108             db = null;
109         }
110     }
111 
112     /**
113      * Convenience functions for beginning, committing, or rolling back a
114      * transaction.
115      */
116     void begin()
117     {
118         execute("BEGIN");
119     }
120 
121     /// Ditto
122     void commit()
123     {
124         execute("COMMIT");
125     }
126 
127     /// Ditto
128     void rollback()
129     {
130         execute("ROLLBACK");
131     }
132 
133     /**
134      * Returns the internal handle to the SQLite3 database. This should only be
135      * used if this class does not provide the necessary functionality.
136      */
137     @property sqlite3* handle() { return db; }
138 
139     /**
140      * Prepare SQL statement for multiple execution or for parameters binding.
141      *
142      * If $(D args) are given, they are bound before return, so client can
143      * immediately call step() to get rows.
144      */
145     Statement prepare(T...)(string sql, const auto ref T args)
146     {
147         debug (SQLite)
148         {
149             import std.stdio;
150             writeln("Prepared Statement: ", sql);
151             foreach(arg; args)
152                 writefln("    %s: %s", typeof(arg).stringof, arg);
153         }
154 
155         auto s = new Statement(sql);
156         if (args.length) s.bind(args);
157         return s;
158     }
159 
160     /**
161      * Like $(D prepare), but ignores results and returns the number of changed
162      * rows.
163      */
164     uint execute(T...)(string sql, const auto ref T args)
165     {
166         auto s = prepare(sql, args);
167 
168         s.step();
169 
170         if (s.columns > 0)
171             return this.changes;
172 
173         return 0;
174     }
175 
176     /**
177      * Returns the ID of the last row that was inserted.
178      */
179     @property ulong lastInsertId()
180     {
181         return sqlite3_last_insert_rowid(db);
182     }
183 
184     /**
185      * Returns the number of rows changed by the last statement.
186      */
187     @property uint changes()
188     {
189         return cast(uint)sqlite3_changes(db);
190     }
191 
192     /**
193      * The database is accessed using statements.
194      *
195      * First, a statement is prepared from a SQL query. Then, values are bound to
196      * the parameters in the statement using $(D bind). Finally, the statement is
197      * executed using $(D step).
198      */
199     class Statement
200     {
201         private sqlite3_stmt *_stmt;
202 
203         /**
204          * Compiles the SQL statement. Values can then be bound to the
205          * parameters of the statement using $(D bind).
206          */
207         this(string sql)
208         {
209             auto r = sqlite3_prepare_v2(
210                 db, toStringz(sql), cast(int)sql.length, &_stmt, null
211                 );
212 
213             sqliteEnforce(r == SQLITE_OK, db);
214         }
215 
216         ~this()
217         {
218             sqlite3_finalize(_stmt);
219         }
220 
221         /**
222          * Returns the internal SQLite3 statement handle. This should only be
223          * used if this class does not provide the necessary functionality.
224          */
225         @property sqlite3_stmt* handle() { return _stmt; }
226 
227         /**
228          * Returns the number of columns in the result set. This number will be
229          * 0 if there is no result set (e.g., INSERT, UPDATE, CREATE TABLE).
230          */
231         @property uint columns()
232         {
233             return cast(uint)sqlite3_column_count(_stmt);
234         }
235 
236         /**
237          * Returns the SQL statement string.
238          */
239         @property string sql() { return fromStringz(sqlite3_sql(_stmt)); }
240 
241         /**
242          * Binds a value to the statement at a particular index. Indices start
243          * at 0.
244          */
245         void opIndexAssign(T : int)(T v, uint i)
246         {
247             sqliteEnforce(sqlite3_bind_int(_stmt, i+1, v) == SQLITE_OK, db);
248         }
249 
250         /// Ditto
251         void opIndexAssign(T : uint)(T v, uint i)
252         {
253             sqliteEnforce(sqlite3_bind_int(_stmt, i+1, v) == SQLITE_OK, db);
254         }
255 
256         /// Ditto
257         void opIndexAssign(T : long)(T v, uint i)
258         {
259             sqliteEnforce(sqlite3_bind_int64(_stmt, i+1, v) == SQLITE_OK, db);
260         }
261 
262         /// Ditto
263         void opIndexAssign(T : ulong)(T v, uint i)
264         {
265             sqliteEnforce(sqlite3_bind_int64(_stmt, i+1, v) == SQLITE_OK, db);
266         }
267 
268         /// Ditto
269         void opIndexAssign(T : double)(T v, uint i)
270         {
271             sqliteEnforce(sqlite3_bind_double(_stmt, i+1, v) == SQLITE_OK, db);
272         }
273 
274         /// Ditto
275         void opIndexAssign(T : const(char)[])(T v, uint i)
276         {
277             import std.conv : to;
278             sqliteEnforce(sqlite3_bind_text(_stmt, i+1, toStringz(v),
279                 v.length.to!int, SQLITE_TRANSIENT) == SQLITE_OK, db);
280         }
281 
282         /// Ditto
283         void opIndexAssign(const(ubyte)[] v, uint i)
284         {
285             import std.conv : to;
286             sqliteEnforce(sqlite3_bind_blob(_stmt, i+1, v.ptr,
287                 v.length.to!int, SQLITE_TRANSIENT) == SQLITE_OK, db);
288         }
289 
290         /// Ditto
291         void opIndexAssign(T : typeof(null))(T v, uint i)
292         {
293             sqliteEnforce(sqlite3_bind_null(_stmt, i+1) == SQLITE_OK, db);
294         }
295 
296         version (none)
297         {
298             // FIXME: These require bind_*64() functions which, at this time,
299             // are not in DMD.
300             void opIndexAssign(T : const(string))(T v, uint i)
301             {
302                 sqliteEnforce(sqlite3_bind_text64(_stmt, i+1, toStringz(v),
303                     v.length, SQLITE_TRANSIENT, SQLITE_UTF8) == SQLITE_OK, db);
304             }
305 
306             void opIndexAssign(T : const(wstring))(T v, uint i)
307             {
308                 sqliteEnforce(sqlite3_bind_text64(_stmt, i+1, toStringz(v),
309                     v.length * wchar.sizeof, SQLITE_TRANSIENT, SQLITE_UTF16) == SQLITE_OK, db);
310             }
311 
312             void opIndexAssign(in ubyte[] v, uint i)
313             {
314                 // FIXME: void* has no length property
315                 sqliteEnforce(sqlite3_bind_blob64(_stmt, i+1, v,
316                     v.length, SQLITE_TRANSIENT) == SQLITE_OK, db);
317             }
318         }
319 
320         /**
321          * Gets the index of the bind parameter $(D name).
322          */
323         uint opIndex(string name)
324         {
325             int pos = sqlite3_bind_parameter_index(_stmt, toStringz(name));
326             sqliteEnforce(pos <= 0, db, "Invalid bind parameter '"~ name ~"'");
327             return cast(uint)(pos - 1);
328         }
329 
330         /**
331          * Binds a value by name.
332          */
333         void opIndexAssign(T)(const auto ref T v, string name)
334         {
335             this[this[name]] = v;
336         }
337 
338         /**
339          * Bind multiple values to the statement.
340          */
341         void bind(T...)(const auto ref T args)
342         {
343             foreach (i, arg; args)
344                 this[i] = arg;
345         }
346 
347         /**
348          * Steps through the results of the statement. Returns true while there
349          * are results or false if there are no more results.
350          *
351          * Throws: $(D SQLite3Exception) if an error occurs.
352          */
353         bool step()
354         {
355             int r = sqlite3_step(_stmt);
356             if (r == SQLITE_ROW)
357                 return true;
358             else if (r == SQLITE_DONE)
359                 return false;
360             else
361                 throw new SQLite3Exception(db);
362         }
363 
364         /**
365          * Gets the value of a column.
366          */
367         T get(T)(uint i) if (is(T == int) || is(T == uint))
368         {
369             return cast(T)sqlite3_column_int(_stmt, cast(int)i);
370         }
371 
372         /// Ditto
373         T get(T)(uint i) if (is(T == long) || is(T == ulong))
374         {
375             return cast(T)sqlite3_column_int64(_stmt, cast(int)i);
376         }
377 
378         /// Ditto
379         T get(T)(uint i) if (is(T == double))
380         {
381             return sqlite3_column_double(_stmt, cast(int)i);
382         }
383 
384         /// Ditto
385         T get(T)(uint i) if (is(T == char[]))
386         {
387             auto s = sqlite3_column_text(_stmt, cast(int)i);
388             int l = sqlite3_column_bytes(_stmt, cast(int)i);
389             return s[0 .. l].dup;
390         }
391 
392         /// Ditto
393         T get(T)(uint i) if (is(T : const(string)))
394         {
395             // We can safely cast here.
396             return cast(T)get!(char[])(i);
397         }
398 
399         /// Ditto
400         const(void)[] get(T)(uint i) if (is(T == void[]))
401         {
402             auto v = sqlite3_column_blob(_stmt, cast(int)i);
403             int l = sqlite3_column_bytes(_stmt, cast(int)i);
404             return v[0 .. l];
405         }
406 
407         /**
408          * Gets the values in the row..
409          */
410         void getRow(T...)(ref T args)
411         {
412             foreach (i, arg; args)
413                 args[i] = get!(typeof(arg))(i);
414         }
415 
416         /**
417          * Returns true if the column index has a NULL value.
418          */
419         bool isNull(uint i)
420         in { assert(i < columns); }
421         body
422         {
423             return sqlite3_column_type(_stmt, cast(int)i) == SQLITE_NULL;
424         }
425 
426         /**
427          * Resets the execution of this statement. This must be called after $(D
428          * step) returns false.
429          *
430          * Note: Bindings are not reset too.
431          */
432         void reset()
433         {
434             sqliteEnforce(sqlite3_reset(_stmt) == SQLITE_OK, db);
435         }
436 
437         /**
438          * Sets all bindings to NULL.
439          */
440         void clear()
441         {
442             sqliteEnforce(sqlite3_clear_bindings(_stmt) == SQLITE_OK, db);
443         }
444     }
445 }
446 
447 /**
448  * Range that deserializes and returns row data.
449  */
450 static struct Rows(alias read)
451 {
452     import std.traits : Unqual, ReturnType;
453 
454     alias T = ReturnType!read;
455 
456     private
457     {
458         SQLite3.Statement _statement;
459         bool _empty;
460         Unqual!T _data;
461     }
462 
463     this(SQLite3.Statement statement)
464     {
465         _statement = statement;
466 
467         // Prime the cannons
468         popFront();
469     }
470 
471     bool empty() const pure nothrow
472     {
473         return _empty;
474     }
475 
476     void popFront()
477     {
478         if (!_statement.step())
479         {
480             _empty = true;
481             return;
482         }
483 
484         _data = read(_statement);
485     }
486 
487     ref T front() pure nothrow
488     {
489         return _data;
490     }
491 }
492 
493 /**
494  * Convenience function to return a range to iterate over rows in the
495  * statement.
496  */
497 @property auto rows(alias read)(SQLite3.Statement s)
498 {
499     return Rows!read(s);
500 }
501 
502 // Converts a C-string string to a D-string.
503 private string fromStringz(const(char)* s)
504 {
505     size_t i = 0;
506     while (s[i] != '\0') ++i;
507     return s[0 .. i].idup;
508 }