diff options
Diffstat (limited to 'src/ext_depends/d2sqlite3/source/d2sqlite3/statement.d')
-rw-r--r-- | src/ext_depends/d2sqlite3/source/d2sqlite3/statement.d | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/src/ext_depends/d2sqlite3/source/d2sqlite3/statement.d b/src/ext_depends/d2sqlite3/source/d2sqlite3/statement.d new file mode 100644 index 0000000..14fe855 --- /dev/null +++ b/src/ext_depends/d2sqlite3/source/d2sqlite3/statement.d @@ -0,0 +1,438 @@ +/++ +Managing prepared statements. + +Authors: + Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) + +Copyright: + Copyright 2011-18 Nicolas Sicard. + +License: + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). ++/ +module d2sqlite3.statement; + +import d2sqlite3.database; +import d2sqlite3.results; +import d2sqlite3.sqlite3; +import d2sqlite3.internal.memory; +import d2sqlite3.internal.util; + +import std.conv : to; +import std.exception : enforce; +import std.string : format, toStringz; +import std.typecons : Nullable; + +/// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify +version (SqliteEnableUnlockNotify) version = _UnlockNotify; +else version (SqliteFakeUnlockNotify) version = _UnlockNotify; + +/++ +A prepared statement. + +This struct is a reference-counted wrapper around a `sqlite3_stmt*` pointer. +Instances of this struct are typically returned by `Database.prepare()`. ++/ +struct Statement +{ + import std.meta : allSatisfy; + import std.traits : isIntegral, isSomeChar, isBoolean, isFloatingPoint, + isSomeString, isStaticArray, isDynamicArray, isIterable; + import std.typecons : RefCounted, RefCountedAutoInitialize; + +private: + + /// Returns $(D true) if the value can be directly bound to the statement + enum bool isBindable(T) = + is(T == typeof(null)) || is(T == void*) || isIntegral!T || isSomeChar!T + || isBoolean!T || isFloatingPoint!T || isSomeString!T || isStaticArray!T + || isDynamicArray!T || is(T == Nullable!U, U...); + + struct Payload + { + Database db; + sqlite3_stmt* handle; // null if error or empty statement + int paramCount; + debug string sql; + + ~this() nothrow + { + debug ensureNotInGC!Statement(sql); + sqlite3_finalize(handle); + } + } + + RefCounted!(Payload, RefCountedAutoInitialize.no) p; + + void checkResult(int result) + { + enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result)); + } + + version (_UnlockNotify) + { + auto sqlite3_blocking_prepare_v2(Database db, const char *zSql, int nByte, + sqlite3_stmt **ppStmt, const char **pzTail) + { + int rc; + while(SQLITE_LOCKED == (rc = sqlite3_prepare_v2(db.handle(), zSql, nByte, ppStmt, pzTail))) + { + rc = db.waitForUnlockNotify(); + if(rc != SQLITE_OK) break; + } + return rc; + } + } + +package(d2sqlite3): + this(Database db, string sql) + { + sqlite3_stmt* handle; + version (_UnlockNotify) + { + auto result = sqlite3_blocking_prepare_v2(db, sql.toStringz, sql.length.to!int, + &handle, null); + } + else + { + auto result = sqlite3_prepare_v2(db.handle(), sql.toStringz, sql.length.to!int, + &handle, null); + } + enforce(result == SQLITE_OK, new SqliteException(errmsg(db.handle()), result, sql)); + p = Payload(db, handle); + p.paramCount = sqlite3_bind_parameter_count(p.handle); + debug p.sql = sql; + } + + version (_UnlockNotify) + { + /// Setup and waits for unlock notify using the provided `IUnlockNotifyHandler` + auto waitForUnlockNotify() + { + return p.db.waitForUnlockNotify(); + } + } + +public: + /++ + Gets the SQLite internal _handle of the statement. + +/ + inout(sqlite3_stmt)* handle() inout @safe pure nothrow @nogc + { + return p.handle; + } + + /++ + Explicitly finalizes the prepared statement. + + After a call to `finalize()`, the `Statement` object is destroyed and cannot be used. + +/ + void finalize() + { + destroy(p); + } + + /++ + Tells whether the statement is empty (no SQL statement). + +/ + bool empty() const @safe pure nothrow @nogc + { + return p.handle is null; + } + /// + unittest + { + auto db = Database(":memory:"); + auto statement = db.prepare(" ; "); + assert(statement.empty); + } + + /++ + Binds values to parameters of this statement, using parameter index. + + Params: + index = The index of the parameter (starting from 1). + + value = The bound _value. The type of value must be compatible with the SQLite + types: it must be a boolean or numeric type, a string, an array, null, + or a Nullable!T where T is any of the previous types. + +/ + void bind(T)(int index, T value) + in + { + assert(index > 0 && index <= p.paramCount, "parameter index out of range"); + } + body + { + assert(p.handle); + + static if (is(T == typeof(null)) || is(T == void*)) + checkResult(sqlite3_bind_null(p.handle, index)); + + // Handle nullable before user-provided hook as we don't want to write + // `Nullable.null` when the value `isNull`. + else static if (is(T == Nullable!U, U...)) + { + if (value.isNull) + checkResult(sqlite3_bind_null(p.handle, index)); + else + this.bind(index, value.get); + } + + // Check for user-defined hook + else static if (is(typeof(value.toString((in char[]) {})))) + { + string str = format("%s", value); + auto ptr = anchorMem(cast(void*) str.ptr); + checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, + str.length, &releaseMem, SQLITE_UTF8)); + } + else static if (is(typeof(value.toString()) : string)) + { + string str = value.toString(); + auto ptr = anchorMem(cast(void*) str.ptr); + checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, + str.length, &releaseMem, SQLITE_UTF8)); + } + + else static if (isIntegral!T || isSomeChar!T || isBoolean!T) + checkResult(sqlite3_bind_int64(p.handle, index, value.to!long)); + else static if (isFloatingPoint!T) + checkResult(sqlite3_bind_double(p.handle, index, value.to!double)); + else static if (isSomeString!T) + { + string str = value.to!string; + auto ptr = anchorMem(cast(void*) str.ptr); + checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, + str.length, &releaseMem, SQLITE_UTF8)); + } + else static if (isStaticArray!T) + checkResult(sqlite3_bind_blob64(p.handle, index, cast(void*) value.ptr, + value.sizeof, SQLITE_TRANSIENT)); + else static if (isDynamicArray!T) + { + const void[] arr = value; + checkResult(sqlite3_bind_blob64(p.handle, index, anchorMem(arr.ptr), + arr.length, &releaseMem)); + } + else + static assert(0, "Don't know how to bind an instance of type: " ~ T.stringof); + } + + /++ + Binds values to parameters of this statement, using parameter names. + + Params: + name = The name of the parameter, including the ':', '@' or '$' that introduced it. + + value = The bound _value. The type of value must be compatible with the SQLite + types: it must be a boolean or numeric type, a string, an array, null, + or a Nullable!T where T is any of the previous types. + + Warning: + While convenient, this overload of `bind` is less performant, because it has to + retrieve the column index with a call to the SQLite function + `sqlite3_bind_parameter_index`. + +/ + void bind(T)(string name, T value) + in + { + assert(name.length); + } + body + { + assert(p.handle); + auto index = sqlite3_bind_parameter_index(p.handle, name.toStringz); + assert(index > 0, "no parameter named '%s'".format(name)); + bind(index, value); + } + + /++ + Binds all the arguments at once in order. + +/ + void bindAll(Args...)(Args args) + in + { + assert(Args.length == this.parameterCount, "parameter count mismatch"); + } + body + { + foreach (index, _; Args) + bind(index + 1, args[index]); + } + + /++ + Clears the bindings. + + This does not reset the statement. Use `Statement.reset()` for this. + +/ + void clearBindings() + { + assert(p.handle); + checkResult(sqlite3_clear_bindings(p.handle)); + } + + /++ + Executes the statement and return a (possibly empty) range of results. + +/ + ResultRange execute() + { + return ResultRange(this); + } + + /++ + Resets a this statement before a new execution. + + Calling this method invalidates any `ResultRange` struct returned by a previous call + to `Database.execute()` or `Statement.execute()`. + + This does not clear the bindings. Use `Statement.clearBindings()` for this. + +/ + void reset() + { + assert(p.handle); + checkResult(sqlite3_reset(p.handle)); + } + + /++ + Binds arguments, executes and resets the statement, in one call. + + This convenience function is equivalent to: + --- + bindAll(args); + execute(); + reset(); + --- + +/ + void inject(Args...)(Args args) + if (allSatisfy!(isBindable, Args)) + { + bindAll(args); + execute(); + reset(); + } + + /++ + Binds the fields of a struct in order, executes and resets the statement, in one call. + +/ + void inject(T)(auto ref const T obj) + if (is(T == struct)) + { + import std.meta : Filter; + import std.traits : FieldNameTuple; + + enum accesible(string F) = __traits(compiles, __traits(getMember, obj, F)); + enum bindable(string F) = isBindable!(typeof(__traits(getMember, obj, F))); + + alias FieldNames = Filter!(bindable, Filter!(accesible, FieldNameTuple!T)); + assert(FieldNames.length == this.parameterCount, "parameter count mismatch"); + foreach (i, field; FieldNames) + bind(i + 1, __traits(getMember, obj, field)); + execute(); + reset(); + } + + /++ + Binds iterable values in order, executes and resets the statement, in one call. + +/ + void inject(T)(auto ref T obj) + if (!isBindable!T && isIterable!T) + in + { + static if (__traits(compiles, obj.length)) + assert(obj.length == this.parameterCount, "parameter count mismatch"); + } + body + { + static if (__traits(compiles, { foreach (string k, ref v; obj) {} })) + { + foreach (string k, ref v; obj) bind(k, v); + } + else + { + int i = 1; + foreach (ref v; obj) bind(i++, v); + } + execute(); + reset(); + } + + /// Gets the count of bind parameters. + int parameterCount() nothrow + { + assert(p.handle); + return p.paramCount; + } + + /++ + Gets the name of the bind parameter at the given index. + + Params: + index = The index of the parameter (the first parameter has the index 1). + + Returns: The name of the parameter or null is not found or out of range. + +/ + string parameterName(int index) + in + { + assert(index > 0 && index <= p.paramCount, "parameter index out of range"); + } + body + { + assert(p.handle); + return sqlite3_bind_parameter_name(p.handle, index).to!string; + } + + /++ + Gets the index of a bind parameter. + + Returns: The index of the parameter (the first parameter has the index 1) + or 0 is not found or out of range. + +/ + int parameterIndex(string name) + in + { + assert(name.length); + } + body + { + assert(p.handle); + return sqlite3_bind_parameter_index(p.handle, name.toStringz); + } +} + +/++ +Turns $(D_PARAM value) into a _literal that can be used in an SQLite expression. ++/ +string literal(T)(T value) +{ + import std.string : replace; + import std.traits : isBoolean, isNumeric, isSomeString, isArray; + + static if (is(T == typeof(null))) + return "NULL"; + else static if (isBoolean!T) + return value ? "1" : "0"; + else static if (isNumeric!T) + return value.to!string(); + else static if (isSomeString!T) + return format("'%s'", value.replace("'", "''")); + else static if (isArray!T) + return "'X%(%X%)'".format(cast(Blob) value); + else + static assert(false, "cannot make a literal of a value of type " ~ T.stringof); +} +/// +unittest +{ + assert(null.literal == "NULL"); + assert(false.literal == "0"); + assert(true.literal == "1"); + assert(4.literal == "4"); + assert(4.1.literal == "4.1"); + assert("foo".literal == "'foo'"); + assert("a'b'".literal == "'a''b'''"); + import std.conv : hexString; + auto a = cast(Blob) hexString!"DEADBEEF"; + assert(a.literal == "'XDEADBEEF'"); +} |