diff options
Diffstat (limited to 'src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d')
-rw-r--r-- | src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d | 1353 |
1 files changed, 0 insertions, 1353 deletions
diff --git a/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d b/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d deleted file mode 100644 index 93a6509..0000000 --- a/src/ext_depends_cgi/d2sqlite3/source/d2sqlite3/database.d +++ /dev/null @@ -1,1353 +0,0 @@ -/++ -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, ¬Null, &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); - } -} |