summaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d')
-rw-r--r--src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d1353
1 files changed, 1353 insertions, 0 deletions
diff --git a/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d b/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d
new file mode 100644
index 0000000..93a6509
--- /dev/null
+++ b/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d
@@ -0,0 +1,1353 @@
+/++
+Managing SQLite3 database connections.
+
+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.database;
+
+import d2sqlite3.statement;
+import d2sqlite3.results;
+import d2sqlite3.sqlite3;
+import d2sqlite3.internal.memory;
+import d2sqlite3.internal.util;
+
+import std.conv : text, to;
+import std.exception : enforce;
+import std.string : format, fromStringz, toStringz;
+import std.typecons : Nullable;
+
+import core.stdc.stdlib : free;
+
+/// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify
+version (SqliteEnableUnlockNotify) version = _UnlockNotify;
+else version (SqliteFakeUnlockNotify) version = _UnlockNotify;
+
+/// Type for the internal representation of blobs
+alias Blob = immutable(ubyte)[];
+
+/// SQLite type codes
+enum SqliteType
+{
+ INTEGER = SQLITE_INTEGER, ///
+ FLOAT = SQLITE_FLOAT, ///
+ TEXT = SQLITE3_TEXT, ///
+ BLOB = SQLITE_BLOB, ///
+ NULL = SQLITE_NULL ///
+}
+
+/++
+A caracteristic of user-defined functions or aggregates.
++/
+enum Deterministic
+{
+ /++
+ The returned value is the same if the function is called with the same parameters.
+ +/
+ yes = 0x800,
+
+ /++
+ The returned value can vary even if the function is called with the same parameters.
+ +/
+ no = 0
+}
+
+/++
+An database connection.
+
+This struct is a reference-counted wrapper around a `sqlite3*` pointer.
++/
+struct Database
+{
+ import std.traits : isFunctionPointer, isDelegate;
+ import std.typecons : RefCounted, RefCountedAutoInitialize;
+
+private:
+ struct Payload
+ {
+ sqlite3* handle;
+ void* updateHook;
+ void* commitHook;
+ void* rollbackHook;
+ void* progressHandler;
+ void* traceCallback;
+ void* profileCallback;
+ version (_UnlockNotify) IUnlockNotifyHandler unlockNotifyHandler;
+ debug string filename;
+
+ this(sqlite3* handle) nothrow
+ {
+ this.handle = handle;
+ }
+
+ ~this() nothrow
+ {
+ debug ensureNotInGC!Database(filename);
+ free(updateHook);
+ free(commitHook);
+ free(rollbackHook);
+ free(progressHandler);
+ free(traceCallback);
+ free(profileCallback);
+
+ if (!handle)
+ return;
+ sqlite3_progress_handler(handle, 0, null, null);
+ sqlite3_close(handle);
+ }
+ }
+
+ RefCounted!(Payload, RefCountedAutoInitialize.no) p;
+
+ void check(int result)
+ {
+ enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result));
+ }
+
+public:
+ /++
+ Opens a database connection.
+
+ Params:
+ path = The path to the database file. In recent versions of SQLite, the path can be
+ an URI with options.
+
+ flags = Options flags.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/open.html) to know how to use the flags
+ parameter or to use path as a file URI if the current configuration allows it.
+ +/
+ this(string path, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
+ {
+ sqlite3* hdl;
+ auto result = sqlite3_open_v2(path.toStringz, &hdl, flags, null);
+ enforce(result == SQLITE_OK, new SqliteException(hdl ? errmsg(hdl) : "Error opening the database", result));
+ p = Payload(hdl);
+ debug p.filename = path;
+ }
+
+ /++
+ Explicitly closes the database connection.
+
+ After a successful call to `close()`, using the database connection or one of its prepared
+ statement is an error. The `Database` object is destroyed and cannot be used any more.
+ +/
+ void close()
+ {
+ auto result = sqlite3_close(p.handle);
+ enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result));
+ p.handle = null;
+ destroy(p);
+ }
+
+ /++
+ Gets the SQLite internal _handle of the database connection.
+ +/
+ sqlite3* handle() @property nothrow
+ {
+ return p.handle;
+ }
+
+ /++
+ Gets the path associated with an attached database.
+
+ Params:
+ database = The name of an attached database.
+
+ Returns: The absolute path of the attached database.
+ If there is no attached database, or if database is a temporary or
+ in-memory database, then null is returned.
+ +/
+ string attachedFilePath(string database = "main")
+ {
+ assert(p.handle);
+ return sqlite3_db_filename(p.handle, database.toStringz).to!string;
+ }
+
+ /++
+ Gets the read-only status of an attached database.
+
+ Params:
+ database = The name of an attached database.
+ +/
+ bool isReadOnly(string database = "main")
+ {
+ assert(p.handle);
+ immutable ret = sqlite3_db_readonly(p.handle, database.toStringz);
+ enforce(ret >= 0, new SqliteException("Database not found: %s".format(database), ret));
+ return ret == 1;
+ }
+
+ /++
+ Gets metadata for a specific table column of an attached database.
+
+ Params:
+ table = The name of the table.
+
+ column = The name of the column.
+
+ database = The name of a database attached. If null, then all attached databases
+ are searched for the table using the same algorithm used by the database engine
+ to resolve unqualified table references.
+ +/
+ TableColumnMetadata tableColumnMetadata(string table, string column, string database = "main")
+ {
+ TableColumnMetadata data;
+ char* pzDataType, pzCollSeq;
+ int notNull, primaryKey, autoIncrement;
+ assert(p.handle);
+ check(sqlite3_table_column_metadata(p.handle, database.toStringz, table.toStringz,
+ column.toStringz, &pzDataType, &pzCollSeq, &notNull, &primaryKey, &autoIncrement));
+ data.declaredTypeName = pzDataType.to!string;
+ data.collationSequenceName = pzCollSeq.to!string;
+ data.isNotNull = cast(bool) notNull;
+ data.isPrimaryKey = cast(bool) primaryKey;
+ data.isAutoIncrement = cast(bool) autoIncrement;
+ return data;
+ }
+
+ /++
+ Executes a single SQL statement and returns the results directly.
+
+ It's the equivalent of `prepare(sql).execute()`.
+ Or when used with args the equivalent of:
+ ---
+ auto stm = prepare(sql);
+ stm.bindAll(args);
+ stm.execute();
+ ---
+
+ The results become undefined when the Database goes out of scope and is destroyed.
+
+ Params:
+ sql = The code of the SQL statement.
+ args = Optional arguments to bind to the SQL statement.
+ +/
+ ResultRange execute(Args...)(string sql, Args args)
+ {
+ auto stm = prepare(sql);
+ static if (Args.length) stm.bindAll(args);
+ return stm.execute();
+ }
+ ///
+ unittest
+ {
+ auto db = Database(":memory:");
+ db.execute("CREATE TABLE test (val INTEGER)");
+ db.execute("INSERT INTO test (val) VALUES (:v)", 1);
+ assert(db.execute("SELECT val FROM test WHERE val=:v", 1).oneValue!int == 1);
+ }
+
+ /++
+ Runs an SQL script that can contain multiple statements.
+
+ Params:
+ script = The code of the SQL script.
+
+ dg = A delegate to call for each statement to handle the results. The passed
+ ResultRange will be empty if a statement doesn't return rows. If the delegate
+ return false, the execution is aborted.
+ +/
+ void run(string script, bool delegate(ResultRange) dg = null)
+ {
+ foreach (sql; script.byStatement)
+ {
+ auto stmt = prepare(sql);
+ auto results = stmt.execute();
+ if (dg && !dg(results))
+ return;
+ }
+ }
+ ///
+ unittest
+ {
+ auto db = Database(":memory:");
+ db.run(`CREATE TABLE test1 (val INTEGER);
+ CREATE TABLE test2 (val FLOAT);
+ DROP TABLE test1;
+ DROP TABLE test2;`);
+ }
+
+ /++
+ Prepares (compiles) a single SQL statement and returns it, so that it can be bound to
+ values before execution.
+
+ The statement becomes invalid if the Database goes out of scope and is destroyed.
+ +/
+ Statement prepare(string sql)
+ {
+ return Statement(this, sql);
+ }
+
+ /// Convenience functions equivalent to an SQL statement.
+ void begin() { execute("BEGIN"); }
+ /// Ditto
+ void commit() { execute("COMMIT"); }
+ /// Ditto
+ void rollback() { execute("ROLLBACK"); }
+
+ /++
+ Returns the rowid of the last INSERT statement.
+ +/
+ long lastInsertRowid()
+ {
+ assert(p.handle);
+ return sqlite3_last_insert_rowid(p.handle);
+ }
+
+ /++
+ Gets the number of database rows that were changed, inserted or deleted by the most
+ recently executed SQL statement.
+ +/
+ int changes() @property nothrow
+ {
+ assert(p.handle);
+ return sqlite3_changes(p.handle);
+ }
+
+ /++
+ Gets the number of database rows that were changed, inserted or deleted since the
+ database was opened.
+ +/
+ int totalChanges() @property nothrow
+ {
+ assert(p.handle);
+ return sqlite3_total_changes(p.handle);
+ }
+
+ /++
+ Gets the SQLite error code of the last operation.
+ +/
+ int errorCode() @property nothrow
+ {
+ return p.handle ? sqlite3_errcode(p.handle) : 0;
+ }
+
+ /++
+ Interrupts any pending database operations.
+
+ It's safe to call this function from anouther thread.
+
+ See_also: $(LINK http://www.sqlite.org/c3ref/interrupt.html).
+ +/
+ void interrupt()
+ {
+ assert(p.handle);
+ sqlite3_interrupt(p.handle);
+ }
+
+ /++
+ Sets a connection configuration option.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/db_config.html).
+ +/
+ void config(Args...)(int code, Args args)
+ {
+ assert(p.handle);
+ auto result = sqlite3_db_config(p.handle, code, args);
+ enforce(result == SQLITE_OK, new SqliteException("Database configuration: error %s".format(result), result));
+ }
+
+ /++
+ Enables or disables loading extensions.
+ +/
+ void enableLoadExtensions(bool enable = true)
+ {
+ assert(p.handle);
+ immutable ret = sqlite3_enable_load_extension(p.handle, enable);
+ enforce(ret == SQLITE_OK,
+ new SqliteException("Could not enable loading extensions.", ret));
+ }
+
+ /++
+ Loads an extension.
+
+ Params:
+ path = The path of the extension file.
+
+ entryPoint = The name of the entry point function. If null is passed, SQLite
+ uses the name of the extension file as the entry point.
+ +/
+ void loadExtension(string path, string entryPoint = null)
+ {
+ assert(p.handle);
+ char* p_err;
+ scope (failure)
+ sqlite3_free(p_err);
+
+ immutable ret = sqlite3_load_extension(p.handle, path.toStringz, entryPoint.toStringz, &p_err);
+ enforce(ret == SQLITE_OK, new SqliteException(
+ "Could not load extension: %s:%s (%s)".format(entryPoint, path,
+ p_err !is null ? fromStringz(p_err) : "No additional info"), ret));
+ }
+
+ /++
+ Creates and registers a new function in the database.
+
+ If a function with the same name and the same arguments already exists, it is replaced
+ by the new one.
+
+ The memory associated with the function will be released when the database connection
+ is closed.
+
+ Params:
+ name = The name that the function will have in the database.
+
+ fun = a delegate or function that implements the function. $(D_PARAM fun)
+ must satisfy the following criteria:
+ $(UL
+ $(LI It must not be variadic.)
+ $(LI Its arguments must all have a type that is compatible with 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.)
+ $(LI Its return value must also be of a compatible type.)
+ )
+ or
+ $(UL
+ $(LI It must be a normal or type-safe variadic function where the arguments
+ are of type `ColumnData`. In other terms, the signature of the function must be:
+ `function(ColumnData[] args)` or `function(ColumnData[] args...)`)
+ $(LI Its return value must be a boolean or numeric type, a string, an array, `null`,
+ or a `Nullable!T` where T is any of the previous types.)
+ )
+ Pass a `null` function pointer to delete the function from the database connection.
+
+ det = Tells SQLite whether the result of the function is deterministic, i.e. if the
+ result is the same when called with the same parameters. Recent versions of SQLite
+ perform optimizations based on this. Set to `Deterministic.no` otherwise.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/create_function.html).
+ +/
+ void createFunction(T)(string name, T fun, Deterministic det = Deterministic.yes)
+ if (isFunctionPointer!T || isDelegate!T)
+ {
+ import std.meta : AliasSeq, staticMap, EraseAll;
+ import std.traits : variadicFunctionStyle, Variadic, ParameterTypeTuple,
+ ParameterDefaultValueTuple, ReturnType, Unqual;
+
+ static assert(variadicFunctionStyle!(fun) == Variadic.no
+ || is(ParameterTypeTuple!fun == AliasSeq!(ColumnData[])),
+ "only type-safe variadic functions with ColumnData arguments are supported");
+
+ static if (is(ParameterTypeTuple!fun == AliasSeq!(ColumnData[])))
+ {
+ extern(C) static nothrow
+ void x_func(sqlite3_context* context, int argc, sqlite3_value** argv)
+ {
+ string name;
+ try
+ {
+ import std.array : appender;
+ auto args = appender!(ColumnData[]);
+
+ foreach (i; 0 .. argc)
+ {
+ auto value = argv[i];
+ immutable type = sqlite3_value_type(value);
+
+ final switch (type)
+ {
+ case SqliteType.INTEGER:
+ args.put(ColumnData(getValue!long(value)));
+ break;
+
+ case SqliteType.FLOAT:
+ args.put(ColumnData(getValue!double(value)));
+ break;
+
+ case SqliteType.TEXT:
+ args.put(ColumnData(getValue!string(value)));
+ break;
+
+ case SqliteType.BLOB:
+ args.put(ColumnData(getValue!Blob(value)));
+ break;
+
+ case SqliteType.NULL:
+ args.put(ColumnData(null));
+ break;
+ }
+ }
+
+ auto ptr = sqlite3_user_data(context);
+
+ auto wrappedDelegate = delegateUnwrap!T(ptr);
+ auto dlg = wrappedDelegate.dlg;
+ name = wrappedDelegate.name;
+ setResult(context, dlg(args.data));
+ }
+ catch (Exception e)
+ {
+ sqlite3_result_error(context, "error in function %s(): %s"
+ .nothrowFormat(name, e.msg).toStringz, -1);
+ }
+ }
+ }
+ else
+ {
+ static assert(!is(ReturnType!fun == void), "function must not return void");
+
+ alias PT = staticMap!(Unqual, ParameterTypeTuple!fun);
+ alias PD = ParameterDefaultValueTuple!fun;
+
+ extern (C) static nothrow
+ void x_func(sqlite3_context* context, int argc, sqlite3_value** argv)
+ {
+ string name;
+ try
+ {
+ // Get the deledate and its name
+ auto ptr = sqlite3_user_data(context);
+ auto wrappedDelegate = delegateUnwrap!T(ptr);
+ auto dlg = wrappedDelegate.dlg;
+ name = wrappedDelegate.name;
+
+ enum maxArgc = PT.length;
+ enum minArgc = PT.length - EraseAll!(void, PD).length;
+
+ if (argc > maxArgc)
+ {
+ auto txt = ("too many arguments in function %s(), expecting at most %s"
+ ).format(name, maxArgc);
+ sqlite3_result_error(context, txt.toStringz, -1);
+ }
+ else if (argc < minArgc)
+ {
+ auto txt = ("too few arguments in function %s(), expecting at least %s"
+ ).format(name, minArgc);
+ sqlite3_result_error(context, txt.toStringz, -1);
+ }
+ else
+ {
+ PT args;
+ foreach (i, type; PT)
+ {
+ if (i < argc)
+ args[i] = getValue!type(argv[i]);
+ else
+ static if (is(typeof(PD[i])))
+ args[i] = PD[i];
+ }
+ setResult(context, dlg(args));
+ }
+ }
+ catch (Exception e)
+ {
+ sqlite3_result_error(context, "error in function %s(): %s"
+ .nothrowFormat(name, e.msg).toStringz, -1);
+ }
+ }
+ }
+
+ assert(name.length, "function has an empty name");
+
+ if (!fun)
+ createFunction(name, null);
+
+ assert(p.handle);
+ check(sqlite3_create_function_v2(p.handle, name.toStringz, -1,
+ SQLITE_UTF8 | det, delegateWrap(fun, name), &x_func, null, null, &free));
+ }
+ ///
+ unittest
+ {
+ string star(int count, string starSymbol = "*")
+ {
+ import std.range : repeat;
+ import std.array : join;
+
+ return starSymbol.repeat(count).join;
+ }
+
+ auto db = Database(":memory:");
+ db.createFunction("star", &star);
+ assert(db.execute("SELECT star(5)").oneValue!string == "*****");
+ assert(db.execute("SELECT star(3, '♥')").oneValue!string == "♥♥♥");
+ }
+ ///
+ unittest
+ {
+ // The implementation of the new function
+ string myList(ColumnData[] args)
+ {
+ import std.array : appender;
+ import std.string : format, join;
+
+ auto app = appender!(string[]);
+ foreach (arg; args)
+ {
+ if (arg.type == SqliteType.TEXT)
+ app.put(`"%s"`.format(arg));
+ else
+ app.put("%s".format(arg));
+ }
+ return app.data.join(", ");
+ }
+
+ auto db = Database(":memory:");
+ db.createFunction("my_list", &myList);
+ auto list = db.execute("SELECT my_list(42, 3.14, 'text', NULL)").oneValue!string;
+ assert(list == `42, 3.14, "text", null`);
+ }
+
+ /// Ditto
+ void createFunction(T)(string name, T fun = null)
+ if (is(T == typeof(null)))
+ {
+ assert(name.length, "function has an empty name");
+ assert(p.handle);
+ check(sqlite3_create_function_v2(p.handle, name.toStringz, -1, SQLITE_UTF8,
+ null, fun, null, null, null));
+ }
+
+ /++
+ Creates and registers a new aggregate function in the database.
+
+ Params:
+ name = The name that the aggregate function will have in the database.
+
+ agg = The struct of type T implementing the aggregate. T must implement
+ at least these two methods: `accumulate()` and `result()`.
+ Each parameter and the returned type of `accumulate()` and `result()` must be
+ a boolean or numeric type, a string, an array, `null`, or a `Nullable!T`
+ where T is any of the previous types. These methods cannot be variadic.
+
+ det = Tells SQLite whether the result of the function is deterministic, i.e. if the
+ result is the same when called with the same parameters. Recent versions of SQLite
+ perform optimizations based on this. Set to `Deterministic.no` otherwise.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/create_function.html).
+ +/
+ void createAggregate(T)(string name, T agg, Deterministic det = Deterministic.yes)
+ {
+ import std.meta : staticMap;
+ import std.traits : isAggregateType, ReturnType, variadicFunctionStyle, Variadic,
+ Unqual, ParameterTypeTuple;
+ import core.stdc.stdlib : malloc;
+
+ static assert(isAggregateType!T,
+ T.stringof ~ " should be an aggregate type");
+ static assert(is(typeof(T.accumulate) == function),
+ T.stringof ~ " should have a method named accumulate");
+ static assert(is(typeof(T.result) == function),
+ T.stringof ~ " should have a method named result");
+ static assert(is(typeof({
+ alias RT = ReturnType!(T.result);
+ setResult!RT(null, RT.init);
+ })), T.stringof ~ ".result should return an SQLite-compatible type");
+ static assert(variadicFunctionStyle!(T.accumulate) == Variadic.no,
+ "variadic functions are not supported");
+ static assert(variadicFunctionStyle!(T.result) == Variadic.no,
+ "variadic functions are not supported");
+
+ alias PT = staticMap!(Unqual, ParameterTypeTuple!(T.accumulate));
+ alias RT = ReturnType!(T.result);
+
+ static struct Context
+ {
+ T aggregate;
+ string functionName;
+ }
+
+ extern(C) static nothrow
+ void x_step(sqlite3_context* context, int /* argc */, sqlite3_value** argv)
+ {
+ auto ctx = cast(Context*) sqlite3_user_data(context);
+ if (!ctx)
+ {
+ sqlite3_result_error_nomem(context);
+ return;
+ }
+
+ PT args;
+ try
+ {
+ foreach (i, type; PT)
+ args[i] = getValue!type(argv[i]);
+
+ ctx.aggregate.accumulate(args);
+ }
+ catch (Exception e)
+ {
+ sqlite3_result_error(context, "error in aggregate function %s(): %s"
+ .nothrowFormat(ctx.functionName, e.msg).toStringz, -1);
+ }
+ }
+
+ extern(C) static nothrow
+ void x_final(sqlite3_context* context)
+ {
+ auto ctx = cast(Context*) sqlite3_user_data(context);
+ if (!ctx)
+ {
+ sqlite3_result_error_nomem(context);
+ return;
+ }
+
+ try
+ {
+ setResult(context, ctx.aggregate.result());
+ }
+ catch (Exception e)
+ {
+ sqlite3_result_error(context, "error in aggregate function %s(): %s"
+ .nothrowFormat(ctx.functionName, e.msg).toStringz, -1);
+ }
+ }
+
+ static if (is(T == class) || is(T == Interface))
+ assert(agg, "Attempt to create an aggregate function from a null reference");
+
+ auto ctx = cast(Context*) malloc(Context.sizeof);
+ ctx.aggregate = agg;
+ ctx.functionName = name;
+
+ assert(p.handle);
+ check(sqlite3_create_function_v2(p.handle, name.toStringz, PT.length, SQLITE_UTF8 | det,
+ cast(void*) ctx, null, &x_step, &x_final, &free));
+ }
+ ///
+ unittest // Aggregate creation
+ {
+ import std.array : Appender, join;
+
+ // The implementation of the aggregate function
+ struct Joiner
+ {
+ private
+ {
+ Appender!(string[]) stringList;
+ string separator;
+ }
+
+ this(string separator)
+ {
+ this.separator = separator;
+ }
+
+ void accumulate(string word)
+ {
+ stringList.put(word);
+ }
+
+ string result()
+ {
+ return stringList.data.join(separator);
+ }
+ }
+
+ auto db = Database(":memory:");
+ db.run("CREATE TABLE test (word TEXT);
+ INSERT INTO test VALUES ('My');
+ INSERT INTO test VALUES ('cat');
+ INSERT INTO test VALUES ('is');
+ INSERT INTO test VALUES ('black');");
+
+ db.createAggregate("dash_join", Joiner("-"));
+ auto text = db.execute("SELECT dash_join(word) FROM test").oneValue!string;
+ assert(text == "My-cat-is-black");
+ }
+
+ /++
+ Creates and registers a collation function in the database.
+
+ Params:
+ name = The name that the function will have in the database.
+
+ fun = a delegate or function that implements the collation. The function $(D_PARAM fun)
+ must be `nothrow`` and satisfy these criteria:
+ $(UL
+ $(LI Takes two string arguments (s1 and s2). These two strings are slices of C-style strings
+ that SQLite manages internally, so there is no guarantee that they are still valid
+ when the function returns.)
+ $(LI Returns an integer (ret).)
+ $(LI If s1 is less than s2, ret < 0.)
+ $(LI If s1 is equal to s2, ret == 0.)
+ $(LI If s1 is greater than s2, ret > 0.)
+ $(LI If s1 is equal to s2, then s2 is equal to s1.)
+ $(LI If s1 is equal to s2 and s2 is equal to s3, then s1 is equal to s3.)
+ $(LI If s1 is less than s2, then s2 is greater than s1.)
+ $(LI If s1 is less than s2 and s2 is less than s3, then s1 is less than s3.)
+ )
+
+ See_Also: $(LINK http://www.sqlite.org/lang_aggfunc.html)
+ +/
+ void createCollation(T)(string name, T fun)
+ if (isFunctionPointer!T || isDelegate!T)
+ {
+ import std.traits : isImplicitlyConvertible, functionAttributes, FunctionAttribute,
+ ParameterTypeTuple, isSomeString, ReturnType;
+
+ static assert(isImplicitlyConvertible!(typeof(fun("a", "b")), int),
+ "the collation function has a wrong signature");
+
+ static assert(functionAttributes!(T) & FunctionAttribute.nothrow_,
+ "only nothrow functions are allowed as collations");
+
+ alias PT = ParameterTypeTuple!fun;
+ static assert(isSomeString!(PT[0]),
+ "the first argument of function " ~ name ~ " should be a string");
+ static assert(isSomeString!(PT[1]),
+ "the second argument of function " ~ name ~ " should be a string");
+ static assert(isImplicitlyConvertible!(ReturnType!fun, int),
+ "function " ~ name ~ " should return a value convertible to an int");
+
+ extern (C) static nothrow
+ int x_compare(void* ptr, int n1, const(void)* str1, int n2, const(void)* str2)
+ {
+ static string slice(const(void)* str, int n) nothrow
+ {
+ // The string data is owned by SQLite, so it should be safe
+ // to take a slice of it.
+ return str ? (cast(immutable) (cast(const(char)*) str)[0 .. n]) : null;
+ }
+
+ return delegateUnwrap!T(ptr).dlg(slice(str1, n1), slice(str2, n2));
+ }
+
+ assert(p.handle);
+ auto dgw = delegateWrap(fun, name);
+ auto result = sqlite3_create_collation_v2(p.handle, name.toStringz, SQLITE_UTF8,
+ delegateWrap(fun, name), &x_compare, &free);
+ if (result != SQLITE_OK)
+ {
+ free(dgw);
+ throw new SqliteException(errmsg(p.handle), result);
+ }
+ }
+ ///
+ unittest // Collation creation
+ {
+ // The implementation of the collation
+ int my_collation(string s1, string s2) nothrow
+ {
+ import std.uni : icmp;
+ import std.exception : assumeWontThrow;
+
+ return assumeWontThrow(icmp(s1, s2));
+ }
+
+ auto db = Database(":memory:");
+ db.createCollation("my_coll", &my_collation);
+ db.run("CREATE TABLE test (word TEXT);
+ INSERT INTO test (word) VALUES ('straße');
+ INSERT INTO test (word) VALUES ('strasses');");
+
+ auto word = db.execute("SELECT word FROM test ORDER BY word COLLATE my_coll")
+ .oneValue!string;
+ assert(word == "straße");
+ }
+
+ /++
+ Registers a delegate of type `UpdateHookDelegate` as the database's update hook.
+
+ Any previously set hook is released. Pass `null` to disable the callback.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/commit_hook.html).
+ +/
+ void setUpdateHook(UpdateHookDelegate updateHook)
+ {
+ extern(C) static nothrow
+ void callback(void* ptr, int type, const(char)* dbName, const(char)* tableName, long rowid)
+ {
+ WrappedDelegate!UpdateHookDelegate* dg;
+ dg = delegateUnwrap!UpdateHookDelegate(ptr);
+ dg.dlg(type, dbName.to!string, tableName.to!string, rowid);
+ }
+
+ free(p.updateHook);
+ p.updateHook = delegateWrap(updateHook);
+ assert(p.handle);
+ sqlite3_update_hook(p.handle, &callback, p.updateHook);
+ }
+
+ /++
+ Registers a delegate of type `CommitHookDelegate` as the database's commit hook.
+ Any previously set hook is released.
+
+ Params:
+ commitHook = A delegate that should return a non-zero value
+ if the operation must be rolled back, or 0 if it can commit.
+ Pass `null` to disable the callback.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/commit_hook.html).
+ +/
+ void setCommitHook(CommitHookDelegate commitHook)
+ {
+ extern(C) static nothrow
+ int callback(void* ptr)
+ {
+ auto dlg = delegateUnwrap!CommitHookDelegate(ptr).dlg;
+ return dlg();
+ }
+
+ free(p.commitHook);
+ p.commitHook = delegateWrap(commitHook);
+ assert(p.handle);
+ sqlite3_commit_hook(p.handle, &callback, p.commitHook);
+ }
+
+ /++
+ Registers a delegate of type `RoolbackHookDelegate` as the database's rollback hook.
+
+ Any previously set hook is released.
+ Pass `null` to disable the callback.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/commit_hook.html).
+ +/
+ void setRollbackHook(RoolbackHookDelegate rollbackHook)
+ {
+ extern(C) static nothrow
+ void callback(void* ptr)
+ {
+ auto dlg = delegateUnwrap!RoolbackHookDelegate(ptr).dlg;
+ dlg();
+ }
+
+ free(p.rollbackHook);
+ p.rollbackHook = delegateWrap(rollbackHook);
+ assert(p.handle);
+ sqlite3_rollback_hook(p.handle, &callback, p.rollbackHook);
+ }
+
+ /++
+ Registers a delegate of type `ProgressHandlerDelegate` as the progress handler.
+
+ Any previously set handler is released.
+ Pass `null` to disable the callback.
+
+ Params:
+ pace = The approximate number of virtual machine instructions that are
+ evaluated between successive invocations of the handler.
+
+ progressHandler = A delegate that should return 0 if the operation can continue
+ or another value if it must be aborted.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/progress_handler.html).
+ +/
+ void setProgressHandler(int pace, ProgressHandlerDelegate progressHandler)
+ {
+ extern(C) static nothrow
+ int callback(void* ptr)
+ {
+ auto dlg = delegateUnwrap!ProgressHandlerDelegate(ptr).dlg;
+ return dlg();
+ }
+
+ free(p.progressHandler);
+ p.progressHandler = delegateWrap(progressHandler);
+ assert(p.handle);
+ sqlite3_progress_handler(p.handle, pace, &callback, p.progressHandler);
+ }
+
+ /++
+ Registers a delegate of type `TraceCallbackDelegate` as the trace callback.
+
+ Any previously set profile or trace callback is released.
+ Pass `null` to disable the callback.
+
+ The string parameter that is passed to the callback is the SQL text of the statement being
+ executed.
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/profile.html).
+ +/
+ void setTraceCallback(TraceCallbackDelegate traceCallback)
+ {
+ extern(C) static nothrow
+ void callback(void* ptr, const(char)* str)
+ {
+ auto dlg = delegateUnwrap!TraceCallbackDelegate(ptr).dlg;
+ dlg(str.to!string);
+ }
+
+ free(p.traceCallback);
+ p.traceCallback = delegateWrap(traceCallback);
+ assert(p.handle);
+ sqlite3_trace(p.handle, &callback, p.traceCallback);
+ }
+
+ /++
+ Registers a delegate of type `ProfileCallbackDelegate` as the profile callback.
+
+ Any previously set profile or trace callback is released.
+ Pass `null` to disable the callback.
+
+ The string parameter that is passed to the callback is the SQL text of the statement being
+ executed. The time unit is defined in SQLite's documentation as nanoseconds (subject to change,
+ as the functionality is experimental).
+
+ See_Also: $(LINK http://www.sqlite.org/c3ref/profile.html).
+ +/
+ void setProfileCallback(ProfileCallbackDelegate profileCallback)
+ {
+ extern(C) static nothrow
+ void callback(void* ptr, const(char)* str, sqlite3_uint64 time)
+ {
+ auto dlg = delegateUnwrap!ProfileCallbackDelegate(ptr).dlg;
+ dlg(str.to!string, time);
+ }
+
+ free(p.profileCallback);
+ p.profileCallback = delegateWrap(profileCallback);
+ assert(p.handle);
+ sqlite3_profile(p.handle, &callback, p.profileCallback);
+ }
+
+ version (_UnlockNotify)
+ {
+ /++
+ Registers a `IUnlockNotifyHandler` used to handle database locks.
+
+ When running in shared-cache mode, a database operation may fail with an SQLITE_LOCKED error if
+ the required locks on the shared-cache or individual tables within the shared-cache cannot be obtained.
+ See SQLite Shared-Cache Mode for a description of shared-cache locking.
+ This API may be used to register a callback that SQLite will invoke when the connection currently
+ holding the required lock relinquishes it.
+ This API can be used only if the SQLite library was compiled with the `SQLITE_ENABLE_UNLOCK_NOTIFY`
+ C-preprocessor symbol defined.
+
+ See_Also: $(LINK http://sqlite.org/c3ref/unlock_notify.html).
+
+ Parameters:
+ unlockNotifyHandler - custom handler used to control the unlocking mechanism
+ +/
+ void setUnlockNotifyHandler(IUnlockNotifyHandler unlockNotifyHandler)
+ {
+ p.unlockNotifyHandler = unlockNotifyHandler;
+ }
+
+ /// Setup and waits for unlock notify using the provided `IUnlockNotifyHandler`
+ package (d2sqlite3) auto waitForUnlockNotify()
+ {
+ if (p.unlockNotifyHandler is null) return SQLITE_LOCKED;
+
+ version (SqliteEnableUnlockNotify)
+ {
+ extern(C) static nothrow
+ void callback(void** ntfPtr, int nPtr)
+ {
+ for (int i=0; i<nPtr; i++)
+ {
+ auto handler = cast(IUnlockNotifyHandler*)ntfPtr[i];
+ handler.emit(SQLITE_OK);
+ }
+ }
+
+ int rc = sqlite3_unlock_notify(p.handle, &callback, &p.unlockNotifyHandler);
+ assert(rc==SQLITE_LOCKED || rc==SQLITE_OK);
+
+ /+ The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED or SQLITE_OK.
+
+ If SQLITE_LOCKED was returned, then the system is deadlocked. In this case this function
+ needs to return SQLITE_LOCKED to the caller so that the current transaction can be rolled
+ back. Otherwise, block until the unlock-notify callback is invoked, then return SQLITE_OK.
+ +/
+ if(rc == SQLITE_OK)
+ {
+ p.unlockNotifyHandler.wait();
+ scope (exit) p.unlockNotifyHandler.reset();
+ return p.unlockNotifyHandler.result;
+ }
+ return rc;
+ }
+ else
+ {
+ p.unlockNotifyHandler.waitOne();
+ auto res = p.unlockNotifyHandler.result;
+ if (res != SQLITE_OK) p.unlockNotifyHandler.reset();
+ return res;
+ }
+ }
+ }
+}
+
+/// Delegate types
+alias UpdateHookDelegate = void delegate(int type, string dbName, string tableName, long rowid) nothrow;
+/// ditto
+alias CommitHookDelegate = int delegate() nothrow;
+/// ditto
+alias RoolbackHookDelegate = void delegate() nothrow;
+/// ditto
+alias ProgressHandlerDelegate = int delegate() nothrow;
+/// ditto
+alias TraceCallbackDelegate = void delegate(string sql) nothrow;
+/// ditto
+alias ProfileCallbackDelegate = void delegate(string sql, ulong time) nothrow;
+
+/// Information about a table column.
+struct TableColumnMetadata
+{
+ string declaredTypeName; ///
+ string collationSequenceName; ///
+ bool isNotNull; ///
+ bool isPrimaryKey; ///
+ bool isAutoIncrement; ///
+}
+
+version (_UnlockNotify)
+{
+ /++
+ UnlockNotifyHandler interface to be used for custom implementations of UnlockNotify pattern with SQLite.
+
+ Note:
+ For the C API sqlite3_unlock_notify to be used, this library must be compiled with
+ `-version=SqliteEnableUnlockNotify`.
+ Otherwise only emulated solution is provided, that is based on retries for the defined amount of time.
+
+ Implementation must be able to handle situation when emit is called sooner than the wait itself.
+
+ See_Also: $(LINK http://sqlite.org/c3ref/unlock_notify.html).
+ See_Also: $(LINK http://www.sqlite.org/unlock_notify.html).
+ +/
+ interface IUnlockNotifyHandler
+ {
+ version (SqliteEnableUnlockNotify)
+ {
+ /// Blocks until emit is called
+ void wait();
+
+ /++
+ Unlocks the handler.
+ This is called from registered callback from SQLite.
+
+ Params:
+ state = Value to set as a handler result. It can be SQLITE_LOCKED or SQLITE_OK.
+ +/
+ void emit(int state) nothrow;
+ }
+ else
+ {
+ /++
+ This is used as an alternative when SQLite is not compiled with SQLITE_ENABLE_UNLOCK_NOTIFY, and
+ when the library is built with `-version=SqliteFakeUnlockNotify`.
+ Using this, the handler tries to wait out the SQLITE_LOCKED state for some time.
+ Implementation have to block for some amount of time and check if total amount is not greater than some constant afterwards.
+ If there is still some time to try again, the handler must set the result to SQLITE_OK or to SQLITE_LOCKED otherwise.
+ +/
+ void waitOne();
+ }
+
+ /// Resets the handler for the next use
+ void reset();
+
+ /// Result after wait is finished
+ @property int result() const;
+ }
+
+ version (SqliteEnableUnlockNotify)
+ {
+ /++
+ UnlockNotifyHandler used when SQLite is compiled with SQLITE_ENABLE_UNLOCK_NOTIFY, and
+ when the library is built with `-version=SqliteEnableUnlockNotify`.
+ It is implemented using the standard `core.sync` package.
+
+ Use setUnlockNotifyHandler method to handle the database lock.
+
+ See_Also: $(LINK http://sqlite.org/c3ref/unlock_notify.html).
+ See_Also: $(LINK http://www.sqlite.org/unlock_notify.html).
+ +/
+ final class UnlockNotifyHandler : IUnlockNotifyHandler
+ {
+ import core.sync.condition : Condition;
+ import core.sync.mutex : Mutex;
+
+ private
+ {
+ __gshared Mutex mtx;
+ __gshared Condition cond;
+ __gshared int res;
+ __gshared bool fired;
+ }
+
+ /// Constructor
+ this()
+ {
+ mtx = new Mutex();
+ cond = new Condition(mtx);
+ }
+
+ /// Blocks until emit is called
+ void wait()
+ {
+ synchronized (mtx)
+ {
+ if (!fired) cond.wait();
+ }
+ }
+
+ /// Unlocks the handler, state is one of SQLITE_LOCKED or SQLITE_OK
+ void emit(int res) nothrow
+ in { assert(res == SQLITE_LOCKED || res == SQLITE_OK); }
+ do
+ {
+ try
+ {
+ synchronized (mtx)
+ {
+ this.res = res;
+ fired = true;
+ cond.notify();
+ }
+ }
+ catch (Exception) {}
+ }
+
+ /// Resets the handler for the next use
+ void reset()
+ {
+ res = SQLITE_LOCKED;
+ fired = false;
+ }
+
+ /// Result after wait is finished
+ @property int result() const
+ out (result) { assert(result == SQLITE_OK || result == SQLITE_LOCKED); }
+ do { return res; }
+ }
+ }
+ else
+ {
+ /++
+ UnlockNotifyHandler that can be used when SQLite is not compiled with SQLITE_ENABLE_UNLOCK_NOTIFY,
+ and when the library is built with `-version=SqliteFakeUnlockNotify`..
+ It retries the statement execution for the provided amount of time before the SQLITE_LOCKED is returned.
+
+ Use setUnlockNotifyHandler method to handle the database lock.
+
+ See_Also: $(LINK http://sqlite.org/c3ref/unlock_notify.html).
+ See_Also: $(LINK http://www.sqlite.org/unlock_notify.html).
+ +/
+ final class UnlockNotifyHandler : IUnlockNotifyHandler
+ {
+ import core.time : Duration, msecs;
+ import std.datetime.stopwatch : StopWatch;
+
+ private
+ {
+ int res;
+ Duration maxDuration;
+ StopWatch sw;
+ }
+
+ /// Constructor
+ this(Duration max = 1000.msecs)
+ in { assert(max > Duration.zero); }
+ do
+ {
+ maxDuration = max;
+ }
+
+ /// Blocks for some time to retry the statement
+ void waitOne()
+ {
+ import core.thread : Thread;
+ import std.random : uniform;
+
+ if (!sw.running) sw.start;
+
+ Thread.sleep(uniform(50, 100).msecs);
+
+ if (sw.peek > maxDuration)
+ {
+ sw.stop;
+ res = SQLITE_LOCKED;
+ }
+ else res = SQLITE_OK;
+ }
+
+ /// Resets the handler for the next use
+ void reset()
+ {
+ res = SQLITE_LOCKED;
+ sw.reset();
+ }
+
+ /// Result after wait is finished
+ @property int result() const
+ out (result) { assert(result == SQLITE_OK || result == SQLITE_LOCKED); }
+ do
+ {
+ return res;
+ }
+ }
+ }
+
+ unittest
+ {
+ import core.time : Duration, msecs;
+
+ /++
+ Tests the unlock notify facility.
+ Params:
+ delay - time to wait in the transaction to block the other one
+ expected - expected result (can be used to test timeout when fake unlock notify is used)
+ +/
+ void testUnlockNotify(Duration delay = 500.msecs, int expected = 3)
+ {
+ import core.thread : Thread;
+ import core.time : msecs, seconds;
+ import std.concurrency : spawn;
+
+ static void test(int n, Duration delay)
+ {
+ auto db = Database("file::memory:?cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_MEMORY);
+ db.setUnlockNotifyHandler = new UnlockNotifyHandler();
+ db.execute("BEGIN IMMEDIATE");
+ Thread.sleep(delay);
+ db.execute("INSERT INTO foo (bar) VALUES (?)", n);
+ db.commit();
+ }
+
+ auto db = Database("file::memory:?cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_MEMORY);
+ db.execute(`CREATE TABLE foo (bar INTEGER);`);
+
+ spawn(&test, 1, delay);
+ Thread.sleep(100.msecs);
+ spawn(&test, 2, delay);
+ Thread.sleep(2*delay + 100.msecs);
+ assert(db.execute("SELECT sum(bar) FROM foo").oneValue!int == expected, format!"%s != %s"(db.execute("SELECT sum(bar) FROM foo").oneValue!int, expected));
+ }
+
+ testUnlockNotify();
+ version (SqliteFakeUnlockNotify) testUnlockNotify(1500.msecs, 1); //timeout test
+ }
+}
+
+/++
+Exception thrown when SQLite functions return an error.
++/
+class SqliteException : Exception
+{
+ /++
+ The _code of the error that raised the exception
+ +/
+ int code;
+
+ /++
+ The SQL code that raised the exception, if applicable.
+ +/
+ string sql;
+
+ private this(string msg, string sql, int code,
+ string file = __FILE__, size_t line = __LINE__, Throwable next = null)
+ @safe pure nothrow @nogc
+ {
+ this.sql = sql;
+ this.code = code;
+ super(msg, file, line, next);
+ }
+
+package(d2sqlite3):
+ this(string msg, int code, string sql = null,
+ string file = __FILE__, size_t line = __LINE__, Throwable next = null)
+ @safe pure nothrow
+ {
+ this(text("error ", code, ": ", msg), sql, code, file, line, next);
+ }
+}