1 /++
2 Managing prepared statements.
3 
4 Authors:
5     Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3)
6 
7 Copyright:
8     Copyright 2011-18 Nicolas Sicard.
9 
10 License:
11     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
12 +/
13 module d2sqlite3.statement;
14 
15 import d2sqlite3.database;
16 import d2sqlite3.results;
17 import d2sqlite3.sqlite3;
18 import d2sqlite3.internal.memory;
19 import d2sqlite3.internal.util;
20 
21 import std.conv : to;
22 import std.exception : enforce;
23 import std..string : format, toStringz;
24 import std.typecons : Nullable;
25 
26 /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify
27 version (SqliteEnableUnlockNotify) version = _UnlockNotify;
28 else version (SqliteFakeUnlockNotify) version = _UnlockNotify;
29 
30 /++
31 A prepared statement.
32 
33 This struct is a reference-counted wrapper around a `sqlite3_stmt*` pointer.
34 Instances of this struct are typically returned by `Database.prepare()`.
35 +/
36 struct Statement
37 {
38     import std.meta : allSatisfy;
39     import std.traits : isIntegral, isSomeChar, isBoolean, isFloatingPoint,
40         isSomeString, isStaticArray, isDynamicArray, isIterable;
41     import std.typecons : RefCounted, RefCountedAutoInitialize;
42 
43 private:
44 
45     /// Returns $(D true) if the value can be directly bound to the statement
46     enum bool isBindable(T) =
47         is(T == typeof(null)) || is(T == void*) || isIntegral!T || isSomeChar!T
48         || isBoolean!T || isFloatingPoint!T || isSomeString!T || isStaticArray!T
49         || isDynamicArray!T || is(T == Nullable!U, U...);
50 
51     struct Payload
52     {
53         Database db;
54         sqlite3_stmt* handle; // null if error or empty statement
55         int paramCount;
56         debug string sql;
57 
58         ~this() nothrow
59         {
60             debug ensureNotInGC!Statement(sql);
61             sqlite3_finalize(handle);
62         }
63     }
64 
65     RefCounted!(Payload, RefCountedAutoInitialize.no) p;
66 
67     void checkResult(int result)
68     {
69         enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result));
70     }
71 
72     version (_UnlockNotify)
73     {
74         auto sqlite3_blocking_prepare_v2(Database db, const char *zSql, int nByte,
75                                          sqlite3_stmt **ppStmt, const char **pzTail)
76         {
77             int rc;
78             while(SQLITE_LOCKED == (rc = sqlite3_prepare_v2(db.handle(), zSql, nByte, ppStmt, pzTail)))
79             {
80                 rc = db.waitForUnlockNotify();
81                 if(rc != SQLITE_OK) break;
82             }
83             return rc;
84         }
85     }
86 
87 package(d2sqlite3):
88     this(Database db, string sql)
89     {
90         sqlite3_stmt* handle;
91         version (_UnlockNotify)
92         {
93             auto result = sqlite3_blocking_prepare_v2(db, sql.toStringz, sql.length.to!int,
94                 &handle, null);
95         }
96         else
97         {
98             auto result = sqlite3_prepare_v2(db.handle(), sql.toStringz, sql.length.to!int,
99                 &handle, null);
100         }
101         enforce(result == SQLITE_OK, new SqliteException(errmsg(db.handle()), result, sql));
102         p = Payload(db, handle);
103         p.paramCount = sqlite3_bind_parameter_count(p.handle);
104         debug p.sql = sql;
105     }
106 
107     version (_UnlockNotify)
108     {
109         /// Setup and waits for unlock notify using the provided `IUnlockNotifyHandler`
110         auto waitForUnlockNotify()
111         {
112             return p.db.waitForUnlockNotify();
113         }
114     }
115 
116 public:
117     /++
118     Gets the SQLite internal _handle of the statement.
119     +/
120     inout(sqlite3_stmt)* handle() inout @safe pure nothrow @nogc
121     {
122         return p.handle;
123     }
124 
125     /++
126     Explicitly finalizes the prepared statement.
127 
128     After a call to `finalize()`, the `Statement` object is destroyed and cannot be used.
129     +/
130     void finalize()
131     {
132         destroy(p);
133     }
134 
135     /++
136     Tells whether the statement is empty (no SQL statement).
137     +/
138     bool empty() const @safe pure nothrow @nogc
139     {
140         return p.handle is null;
141     }
142     ///
143     unittest
144     {
145         auto db = Database(":memory:");
146         auto statement = db.prepare(" ; ");
147         assert(statement.empty);
148     }
149 
150     /++
151     Binds values to parameters of this statement, using parameter index.
152 
153     Params:
154         index = The index of the parameter (starting from 1).
155 
156         value = The bound _value. The type of value must be compatible with the SQLite
157         types: it must be a boolean or numeric type, a string, an array, null,
158         or a Nullable!T where T is any of the previous types.
159     +/
160     void bind(T)(int index, T value)
161     in
162     {
163         assert(index > 0 && index <= p.paramCount, "parameter index out of range");
164     }
165     body
166     {
167         assert(p.handle);
168 
169         static if (is(T == typeof(null)) || is(T == void*))
170             checkResult(sqlite3_bind_null(p.handle, index));
171 
172         // Handle nullable before user-provided hook as we don't want to write
173         // `Nullable.null` when the value `isNull`.
174         else static if (is(T == Nullable!U, U...))
175         {
176             if (value.isNull)
177                 checkResult(sqlite3_bind_null(p.handle, index));
178             else
179                 this.bind(index, value.get);
180         }
181 
182         // Check for user-defined hook
183         else static if (is(typeof(value.toString((in char[]) {}))))
184         {
185             string str = format("%s", value);
186             auto ptr = anchorMem(cast(void*) str.ptr);
187             checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr,
188                                             str.length, &releaseMem, SQLITE_UTF8));
189         }
190         else static if (is(typeof(value.toString()) : string))
191         {
192             string str = value.toString();
193             auto ptr = anchorMem(cast(void*) str.ptr);
194             checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr,
195                                             str.length, &releaseMem, SQLITE_UTF8));
196         }
197 
198         else static if (isIntegral!T || isSomeChar!T || isBoolean!T)
199             checkResult(sqlite3_bind_int64(p.handle, index, value.to!long));
200         else static if (isFloatingPoint!T)
201             checkResult(sqlite3_bind_double(p.handle, index, value.to!double));
202         else static if (isSomeString!T)
203         {
204             string str = value.to!string;
205             auto ptr = anchorMem(cast(void*) str.ptr);
206             checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr,
207                                             str.length, &releaseMem, SQLITE_UTF8));
208         }
209         else static if (isStaticArray!T)
210             checkResult(sqlite3_bind_blob64(p.handle, index, cast(void*) value.ptr,
211                                             value.sizeof, SQLITE_TRANSIENT));
212         else static if (isDynamicArray!T)
213         {
214             const void[] arr = value;
215             checkResult(sqlite3_bind_blob64(p.handle, index, anchorMem(arr.ptr),
216                                             arr.length, &releaseMem));
217         }
218         else
219             static assert(0, "Don't know how to bind an instance of type: " ~ T.stringof);
220     }
221 
222     /++
223     Binds values to parameters of this statement, using parameter names.
224 
225     Params:
226         name = The name of the parameter, including the ':', '@' or '$' that introduced it.
227 
228         value = The bound _value. The type of value must be compatible with the SQLite
229         types: it must be a boolean or numeric type, a string, an array, null,
230         or a Nullable!T where T is any of the previous types.
231 
232     Warning:
233         While convenient, this overload of `bind` is less performant, because it has to
234         retrieve the column index with a call to the SQLite function
235         `sqlite3_bind_parameter_index`.
236     +/
237     void bind(T)(string name, T value)
238     in
239     {
240         assert(name.length);
241     }
242     body
243     {
244         assert(p.handle);
245         auto index = sqlite3_bind_parameter_index(p.handle, name.toStringz);
246         assert(index > 0, "no parameter named '%s'".format(name));
247         bind(index, value);
248     }
249 
250     /++
251     Binds all the arguments at once in order.
252     +/
253     void bindAll(Args...)(Args args)
254     in
255     {
256         assert(Args.length == this.parameterCount, "parameter count mismatch");
257     }
258     body
259     {
260         foreach (index, _; Args)
261             bind(index + 1, args[index]);
262     }
263 
264     /++
265     Clears the bindings.
266 
267     This does not reset the statement. Use `Statement.reset()` for this.
268     +/
269     void clearBindings()
270     {
271         assert(p.handle);
272         checkResult(sqlite3_clear_bindings(p.handle));
273     }
274 
275     /++
276     Executes the statement and return a (possibly empty) range of results.
277     +/
278     ResultRange execute()
279     {
280         return ResultRange(this);
281     }
282 
283     /++
284     Resets a this statement before a new execution.
285 
286     Calling this method invalidates any `ResultRange` struct returned by a previous call
287     to `Database.execute()` or `Statement.execute()`.
288 
289     This does not clear the bindings. Use `Statement.clearBindings()` for this.
290     +/
291     void reset()
292     {
293         assert(p.handle);
294         checkResult(sqlite3_reset(p.handle));
295     }
296 
297     /++
298     Binds arguments, executes and resets the statement, in one call.
299 
300     This convenience function is equivalent to:
301     ---
302     bindAll(args);
303     execute();
304     reset();
305     ---
306     +/
307     void inject(Args...)(Args args)
308         if (allSatisfy!(isBindable, Args))
309     {
310         bindAll(args);
311         execute();
312         reset();
313     }
314 
315     /++
316     Binds the fields of a struct in order, executes and resets the statement, in one call.
317     +/
318     void inject(T)(auto ref const T obj)
319         if (is(T == struct))
320     {
321         import std.meta : Filter;
322         import std.traits : FieldNameTuple;
323 
324         enum accesible(string F) = __traits(compiles, __traits(getMember, obj, F));
325         enum bindable(string F) = isBindable!(typeof(__traits(getMember, obj, F)));
326 
327         alias FieldNames = Filter!(bindable, Filter!(accesible, FieldNameTuple!T));
328         assert(FieldNames.length == this.parameterCount, "parameter count mismatch");
329         foreach (i, field; FieldNames)
330             bind(i + 1, __traits(getMember, obj, field));
331         execute();
332         reset();
333     }
334 
335     /++
336     Binds iterable values in order, executes and resets the statement, in one call.
337     +/
338     void inject(T)(auto ref T obj)
339         if (!isBindable!T && isIterable!T)
340     in
341     {
342         static if (__traits(compiles, obj.length))
343             assert(obj.length == this.parameterCount, "parameter count mismatch");
344     }
345     body
346     {
347         static if (__traits(compiles, { foreach (string k, ref v; obj) {} }))
348         {
349             foreach (string k, ref v; obj) bind(k, v);
350         }
351         else
352         {
353             int i = 1;
354             foreach (ref v; obj) bind(i++, v);
355         }
356         execute();
357         reset();
358     }
359 
360     /// Gets the count of bind parameters.
361     int parameterCount() nothrow
362     {
363         assert(p.handle);
364         return p.paramCount;
365     }
366 
367     /++
368     Gets the name of the bind parameter at the given index.
369 
370     Params:
371         index = The index of the parameter (the first parameter has the index 1).
372 
373     Returns: The name of the parameter or null is not found or out of range.
374     +/
375     string parameterName(int index)
376     in
377     {
378         assert(index > 0 && index <= p.paramCount, "parameter index out of range");
379     }
380     body
381     {
382         assert(p.handle);
383         return sqlite3_bind_parameter_name(p.handle, index).to!string;
384     }
385 
386     /++
387     Gets the index of a bind parameter.
388 
389     Returns: The index of the parameter (the first parameter has the index 1)
390     or 0 is not found or out of range.
391     +/
392     int parameterIndex(string name)
393     in
394     {
395         assert(name.length);
396     }
397     body
398     {
399         assert(p.handle);
400         return sqlite3_bind_parameter_index(p.handle, name.toStringz);
401     }
402 }
403 
404 /++
405 Turns $(D_PARAM value) into a _literal that can be used in an SQLite expression.
406 +/
407 string literal(T)(T value)
408 {
409     import std..string : replace;
410     import std.traits : isBoolean, isNumeric, isSomeString, isArray;
411 
412     static if (is(T == typeof(null)))
413         return "NULL";
414     else static if (isBoolean!T)
415         return value ? "1" : "0";
416     else static if (isNumeric!T)
417         return value.to!string();
418     else static if (isSomeString!T)
419         return format("'%s'", value.replace("'", "''"));
420     else static if (isArray!T)
421         return "'X%(%X%)'".format(cast(Blob) value);
422     else
423         static assert(false, "cannot make a literal of a value of type " ~ T.stringof);
424 }
425 ///
426 unittest
427 {
428     assert(null.literal == "NULL");
429     assert(false.literal == "0");
430     assert(true.literal == "1");
431     assert(4.literal == "4");
432     assert(4.1.literal == "4.1");
433     assert("foo".literal == "'foo'");
434     assert("a'b'".literal == "'a''b'''");
435     import std.conv : hexString;
436     auto a = cast(Blob) hexString!"DEADBEEF";
437     assert(a.literal == "'XDEADBEEF'");
438 }