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