From 71fe0d81a881696b1cd769278bdcbcb181e9110f Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Mon, 16 May 2022 23:47:54 +0200 Subject: [PATCH 01/37] Start removal of command thread --- c_src/esqlite3_nif.c | 284 +++++++------------------------------------ src/esqlite3.erl | 9 +- src/esqlite3_nif.erl | 19 +-- 3 files changed, 50 insertions(+), 262 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 1e27af6..11ec062 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -35,13 +35,7 @@ static ErlNifResourceType *esqlite_backup_type = NULL; /* database connection context */ typedef struct { - ErlNifTid tid; - ErlNifThreadOpts* opts; - ErlNifPid notification_pid; - sqlite3 *db; - queue *commands; - } esqlite_connection; /* prepared statement */ @@ -79,27 +73,15 @@ typedef enum { cmd_get_autocommit, } command_type; -typedef struct { - command_type type; - - ErlNifEnv *env; - ERL_NIF_TERM ref; - ErlNifPid pid; - ERL_NIF_TERM arg; - ERL_NIF_TERM stmt; -} esqlite_command; - static ERL_NIF_TERM atom_esqlite3; -static ERL_NIF_TERM push_command(ErlNifEnv *env, esqlite_connection *conn, esqlite_command *cmd); - static ERL_NIF_TERM make_atom(ErlNifEnv *env, const char *atom_name) { ERL_NIF_TERM atom; if(enif_make_existing_atom(env, atom_name, &atom, ERL_NIF_LATIN1)) - return atom; + return atom; return enif_make_atom(env, atom_name); } @@ -186,38 +168,6 @@ make_sqlite3_error_tuple(ErlNifEnv *env, int error_code, sqlite3 *db) enif_make_string(env, msg, ERL_NIF_LATIN1))); } -static void -command_destroy(void *obj) -{ - esqlite_command *cmd = (esqlite_command *) obj; - - if(cmd->env != NULL) - enif_free_env(cmd->env); - - enif_free(cmd); -} - -static esqlite_command * -command_create() -{ - esqlite_command *cmd = (esqlite_command *) enif_alloc(sizeof(esqlite_command)); - if(cmd == NULL) - return NULL; - - cmd->env = enif_alloc_env(); - if(cmd->env == NULL) { - command_destroy(cmd); - return NULL; - } - - cmd->type = cmd_unknown; - cmd->ref = 0; - cmd->arg = 0; - cmd->stmt = 0; - - return cmd; -} - /* * */ @@ -225,27 +175,6 @@ static void destruct_esqlite_connection(ErlNifEnv *env, void *arg) { esqlite_connection *db = (esqlite_connection *) arg; - esqlite_command *cmd = command_create(); - - /* Send the stop command - */ - cmd->type = cmd_stop; - queue_push(db->commands, cmd); - - /* Wait for the thread to finish - */ - enif_thread_join(db->tid, NULL); - - enif_thread_opts_destroy(db->opts); - - /* The thread has finished... now remove the command queue, and close - * the database (if it was still open). - */ - while(queue_has_item(db->commands)) { - command_destroy(queue_pop(db->commands)); - } - queue_destroy(db->commands); - sqlite3_close_v2(db->db); db->db = NULL; } @@ -567,7 +496,6 @@ make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) ERL_NIF_TERM term; if(!enif_alloc_binary(size, &blob)) { - /* TODO: fix this */ return make_atom(env, "error"); } @@ -905,184 +833,54 @@ do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) return make_atom(env, "ok"); } -static ERL_NIF_TERM -evaluate_command(esqlite_command *cmd, esqlite_connection *conn) -{ - esqlite_statement *stmt = NULL; - - if(cmd->stmt) { - if(!enif_get_resource(cmd->env, cmd->stmt, esqlite_statement_type, (void **) &stmt)) { - return make_error_tuple(cmd->env, "invalid_statement"); - } - } - - switch(cmd->type) { - case cmd_open: - return do_open(cmd->env, conn, cmd->arg); - case cmd_update_hook_set: - return do_set_update_hook(cmd->env, conn, cmd->arg); - case cmd_exec: - return do_exec(cmd->env, conn, cmd->arg); - case cmd_changes: - return do_changes(cmd->env, conn, cmd->arg); - case cmd_prepare: - return do_prepare(cmd->env, conn, cmd->arg); - case cmd_multi_step: - return do_multi_step(cmd->env, conn->db, stmt->statement, cmd->arg); - case cmd_reset: - return do_reset(cmd->env, conn->db, stmt->statement); - case cmd_bind: - return do_bind(cmd->env, conn->db, stmt->statement, cmd->arg); - case cmd_column_names: - return do_column_names(cmd->env, stmt->statement); - case cmd_column_types: - return do_column_types(cmd->env, stmt->statement); - case cmd_backup_init: - return do_backup_init(cmd->env, conn->db, cmd->arg); - case cmd_backup_step: - return do_backup_step(cmd->env, conn->db, cmd->arg); - case cmd_backup_remaining: - return do_backup_remaining(cmd->env, cmd->arg); - case cmd_backup_pagecount: - return do_backup_pagecount(cmd->env, cmd->arg); - case cmd_backup_finish: - return do_backup_finish(cmd->env, cmd->arg); - case cmd_close: - return do_close(cmd->env, conn, cmd->arg); - case cmd_last_insert_rowid: - return do_last_insert_rowid(cmd->env, conn); - case cmd_insert: - return do_insert(cmd->env, conn, cmd->arg); - case cmd_get_autocommit: - return do_get_autocommit(cmd->env, conn); - case cmd_unknown: // not handled - case cmd_stop: // not handled here - case cmd_notification: // not handled here. - break; - } - - return make_error_tuple(cmd->env, "invalid_command"); -} - -static ERL_NIF_TERM -push_command(ErlNifEnv *env, esqlite_connection *conn, esqlite_command *cmd) { - if(!queue_push(conn->commands, cmd)) - return make_error_tuple(env, "command_push_failed"); - - return make_atom(env, "ok"); -} - -static ERL_NIF_TERM -make_answer(esqlite_command *cmd, ERL_NIF_TERM answer) -{ - return enif_make_tuple3(cmd->env, atom_esqlite3, cmd->ref, answer); -} - -static void * -esqlite_connection_run(void *arg) -{ - esqlite_connection *db = (esqlite_connection *) arg; - esqlite_command *cmd; - int continue_running = 1; - - while(continue_running) { - cmd = queue_pop(db->commands); - - if(cmd->type == cmd_stop) { - continue_running = 0; - } else if(cmd->type == cmd_notification) { - enif_send(NULL, &db->notification_pid, cmd->env, cmd->arg); - } else { - enif_send(NULL, &cmd->pid, cmd->env, make_answer(cmd, evaluate_command(cmd, db))); - } - - command_destroy(cmd); - } - - return NULL; -} - -/* - * Start the processing thread - */ -static ERL_NIF_TERM -esqlite_start(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite_connection *conn; - ERL_NIF_TERM db_conn; - - /* Initialize the resource */ - conn = enif_alloc_resource(esqlite_connection_type, sizeof(esqlite_connection)); - if(!conn) - return make_error_tuple(env, "no_memory"); - - conn->db = NULL; - - /* Create command queue */ - conn->commands = queue_create(); - if(!conn->commands) { - enif_release_resource(conn); - return make_error_tuple(env, "command_queue_create_failed"); - } - - /* Start command processing thread */ - conn->opts = enif_thread_opts_create("esqlite_thread_opts"); - if(conn->opts == NULL) { - return make_error_tuple(env, "thread_opts_failed"); - } - - /* Configure a fixed sized stack, windows uses a default of 1Mb, which - * can be too small for complex queries. Linux and MacOS uses a stack of about - * 8Mb, which is a bit too large, since the largest sqlite query is about 1Mb - * in size. The stack size depends on that. A value of 3Mb is about right. - */ - conn->opts->suggested_stack_size = 3072; - - if(enif_thread_create("esqlite_connection", &conn->tid, esqlite_connection_run, conn, conn->opts) != 0) { - enif_thread_opts_destroy(conn->opts); - enif_release_resource(conn); - return make_error_tuple(env, "thread_create_failed"); - } - - db_conn = enif_make_resource(env, conn); - enif_release_resource(conn); - - return make_ok_tuple(env, db_conn); -} - /* * Open the database */ static ERL_NIF_TERM esqlite_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; + esqlite_connection *conn; - if(argc != 4) + if(argc != 1) { return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); + } - if(!sqlite3_threadsafe()) - return make_error_tuple(env, "sqlite3 not thread safe."); + if(!sqlite3_threadsafe()) { + return enif_raise_exception(env, make_atom(env, "not_thread_safe")); + } - /* Note, no check is made for the type of the argument */ - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); + size = enif_get_string(env, argv[1], filename, MAX_PATHNAME, ERL_NIF_LATIN1); + if(size <= 0) + return make_error_tuple(env, "invalid_filename"); - cmd->type = cmd_open; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[3]); + /* Initialize the resource */ + conn = enif_alloc_resource(esqlite_connection_type, sizeof(esqlite_connection)); + if(!conn) { + return enif_raise_exception(env, make_atom(env, "no_memory")); + } - return push_command(env, db, cmd); + char filename[MAX_PATHNAME]; + unsigned int size; + int rc; + ERL_NIF_TERM error; + + /* Open the database. + */ + rc = sqlite3_open(filename, conn->db); + if(rc != SQLITE_OK) { + error = make_sqlite3_error_tuple(env, rc, db); + sqlite3_close_v2(conn->db); + enif_release_resource(conn); + return error; + } + + /* Set a standard busy timeout of 2 seconds */ + sqlite3_busy_timeout(conn->db, 2000); + + ERL_NIF_TERM = db_conn = enif_make_resource(env, conn); + enif_release_resource(conn); + + return make_ok_tuple(env, db_conn); } static ERL_NIF_TERM @@ -1746,17 +1544,17 @@ static int on_upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF } static ErlNifFunc nif_funcs[] = { - {"start", 0, esqlite_start}, - {"open", 4, esqlite_open}, + {"open", 4, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"set_update_hook", 4, set_update_hook}, - {"exec", 4, esqlite_exec}, + {"exec", 4, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"changes", 3, esqlite_changes}, {"prepare", 4, esqlite_prepare}, {"insert", 4, esqlite_insert}, {"last_insert_rowid", 3, esqlite_last_insert_rowid}, {"get_autocommit", 3, esqlite_get_autocommit}, - {"multi_step", 5, esqlite_multi_step}, + {"multi_step", 5, esqlite_multi_step, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"reset", 4, esqlite_reset}, + // TODO: {"esqlite_bind", 3, esqlite_bind_named}, {"bind", 5, esqlite_bind}, {"column_names", 4, esqlite_column_names}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index e4250bf..6f25857 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -105,13 +105,10 @@ open(Filename) -> %% @doc Like open/1, but with an additional timeout. %% -spec open(string(), timeout()) -> {ok, connection()} | {error, _}. -open(Filename, Timeout) -> - {ok, RawConnection} = esqlite3_nif:start(), - +open(Filename) -> Ref = make_ref(), - ok = esqlite3_nif:open(RawConnection, Ref, self(), Filename), - case receive_answer(RawConnection, Ref, Timeout) of - ok -> + case esqlite3_nif:open(Filename) of + {ok, RawConnection} -> {ok, #connection{raw_connection=RawConnection}}; {error, _Msg}=Error -> Error diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 2e7e3b6..dbd0b81 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -20,8 +20,7 @@ %% low-level exports -export([ - start/0, - open/4, + open/1, set_update_hook/4, exec/4, changes/3, @@ -61,22 +60,16 @@ init() -> end, ok = erlang:load_nif(NifFileName, 0). -%% @doc Start a low level thread which will can handle sqlite3 calls. -%% --spec start() -> {ok, raw_connection()} | {error, _}. -start() -> - erlang:nif_error(nif_library_not_loaded). %% @doc Open the specified sqlite3 database. %% -%% Sends an asynchronous open command over the connection and returns -%% ok immediately. When the database is opened -%% --spec open(raw_connection(), reference(), pid(), string()) -> ok | {error, _}. -open(_Db, _Ref, _Dest, _Filename) -> +-spec open(Filename) -> OpenResult + when Filename :: string() + OpenResult :: {ok, ref()} | {error, _}. +open(_Filename) -> erlang:nif_error(nif_library_not_loaded). --spec set_update_hook(raw_connection(), reference(), pid(), pid()) -> ok | {error, _}. +-spec set_update_hook(reference(), pid(), pid()) -> ok | {error, _}. set_update_hook(_Db, _Ref, _Dest, _Pid) -> erlang:nif_error(nif_library_not_loaded). From 6c5e13ced3336c13fb8f671f9115611d7722ad4f Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Tue, 17 May 2022 22:09:25 +0200 Subject: [PATCH 02/37] Changed open/close and prepare --- c_src/esqlite3_nif.c | 568 +++++++++++++------------ c_src/queue.c | 195 --------- c_src/queue.h | 24 -- rebar.config.script | 2 +- src/esqlite.app.src | 2 +- src/esqlite3.erl | 964 +++++++++++++++++++++---------------------- src/esqlite3_nif.erl | 187 +++++---- 7 files changed, 856 insertions(+), 1086 deletions(-) delete mode 100644 c_src/queue.c delete mode 100644 c_src/queue.h diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 11ec062..13a2057 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -23,31 +23,31 @@ #include #include -#include "queue.h" #define MAX_ATOM_LENGTH 255 /* from atom.h, not exposed in erlang include */ #define MAX_SQLITE_NAME_LENGTH 255 /* Maximum name length. Using longer will return misuse */ #define MAX_PATHNAME 512 /* unfortunately not in sqlite.h. */ -static ErlNifResourceType *esqlite_connection_type = NULL; -static ErlNifResourceType *esqlite_statement_type = NULL; -static ErlNifResourceType *esqlite_backup_type = NULL; +static ErlNifResourceType *esqlite3_type = NULL; +static ErlNifResourceType *esqlite3_stmt_type = NULL; +static ErlNifResourceType *esqlite3_backup_type = NULL; /* database connection context */ typedef struct { sqlite3 *db; -} esqlite_connection; +} esqlite3; /* prepared statement */ typedef struct { sqlite3_stmt *statement; -} esqlite_statement; +} esqlite3_stmt; /* data associated with ongoing backup */ typedef struct { sqlite3_backup *backup; -} esqlite_backup; +} esqlite3_backup; +/* typedef enum { cmd_unknown, cmd_open, @@ -72,6 +72,7 @@ typedef enum { cmd_last_insert_rowid, cmd_get_autocommit, } command_type; +*/ static ERL_NIF_TERM atom_esqlite3; @@ -172,25 +173,25 @@ make_sqlite3_error_tuple(ErlNifEnv *env, int error_code, sqlite3 *db) * */ static void -destruct_esqlite_connection(ErlNifEnv *env, void *arg) +destruct_esqlite3(ErlNifEnv *env, void *arg) { - esqlite_connection *db = (esqlite_connection *) arg; + esqlite3 *db = (esqlite3 *) arg; sqlite3_close_v2(db->db); db->db = NULL; } static void -destruct_esqlite_statement(ErlNifEnv *env, void *arg) +destruct_esqlite3_stmt(ErlNifEnv *env, void *arg) { - esqlite_statement *stmt = (esqlite_statement *) arg; + esqlite3_stmt *stmt = (esqlite3_stmt *) arg; sqlite3_finalize(stmt->statement); stmt->statement = NULL; } static void -destruct_esqlite_backup(ErlNifEnv *env, void *arg) +destruct_esqlite3_backup(ErlNifEnv *env, void *arg) { - esqlite_backup *backup = (esqlite_backup *) arg; + esqlite3_backup *backup = (esqlite3_backup *) arg; if(backup->backup) { sqlite3_backup_finish(backup->backup); @@ -199,38 +200,11 @@ destruct_esqlite_backup(ErlNifEnv *env, void *arg) backup->backup = NULL; } -static ERL_NIF_TERM -do_open(ErlNifEnv *env, esqlite_connection *db, const ERL_NIF_TERM arg) -{ - char filename[MAX_PATHNAME]; - unsigned int size; - int rc; - ERL_NIF_TERM error; - - size = enif_get_string(env, arg, filename, MAX_PATHNAME, ERL_NIF_LATIN1); - if(size <= 0) - return make_error_tuple(env, "invalid_filename"); - - /* Open the database. - */ - rc = sqlite3_open(filename, &db->db); - if(rc != SQLITE_OK) { - error = make_sqlite3_error_tuple(env, rc, db->db); - sqlite3_close_v2(db->db); - db->db = NULL; - - return error; - } - - sqlite3_busy_timeout(db->db, 2000); - - return make_atom(env, "ok"); -} - +/* void update_callback(void *arg, int sqlite_operation_type, char const *sqlite_database, char const *sqlite_table, sqlite3_int64 sqlite_rowid) { - esqlite_connection *db = (esqlite_connection *)arg; + esqlite3 *db = (esqlite3 *)arg; esqlite_command *cmd = NULL; ERL_NIF_TERM type, table, rowid; @@ -263,9 +237,11 @@ update_callback(void *arg, int sqlite_operation_type, char const *sqlite_databas push_command(cmd->env, db, cmd); } +*/ +/* static ERL_NIF_TERM -do_set_update_hook(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +do_set_update_hook(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) { if(!enif_get_local_pid(env, arg, &conn->notification_pid)) { return make_error_tuple(env, "invalid_pid"); @@ -279,11 +255,11 @@ do_set_update_hook(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM return make_atom(env, "ok"); } +*/ /* - */ static ERL_NIF_TERM -do_exec(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +do_exec(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) { ErlNifBinary bin; int rc; @@ -299,12 +275,12 @@ do_exec(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) return make_atom(env, "ok"); } + */ /* * Nr of changes - */ static ERL_NIF_TERM -do_changes(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +do_changes(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) { if(!conn->db) { return make_error_tuple(env, "closed"); @@ -315,12 +291,12 @@ do_changes(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) return make_ok_tuple(env, changes_term); } + */ /* * insert action -*/ static ERL_NIF_TERM -do_insert(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +do_insert(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) { ErlNifBinary bin; int rc; @@ -337,12 +313,12 @@ do_insert(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) ERL_NIF_TERM last_rowid_term = enif_make_int64(env, last_rowid); return make_ok_tuple(env, last_rowid_term); } +*/ /* * Return the last inserted rowid - */ static ERL_NIF_TERM -do_last_insert_rowid(ErlNifEnv *env, esqlite_connection *conn) +do_last_insert_rowid(ErlNifEnv *env, esqlite3 *conn) { if(!conn->db) { return make_error_tuple(env, "closed"); @@ -354,39 +330,9 @@ do_last_insert_rowid(ErlNifEnv *env, esqlite_connection *conn) return make_ok_tuple(env, last_rowid_term); } -/* - * Compile a sql statement */ -static ERL_NIF_TERM -do_prepare(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) -{ - ErlNifBinary bin; - esqlite_statement *stmt; - ERL_NIF_TERM esqlite_stmt; - const char *tail; - int rc; - ERL_NIF_TERM eos = enif_make_int(env, 0); - - if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, arg, eos), &bin)) { - return make_error_tuple(env, "no_iodata"); - } - - stmt = enif_alloc_resource(esqlite_statement_type, sizeof(esqlite_statement)); - if(!stmt) - return make_error_tuple(env, "no_memory"); - - rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail); - if(rc != SQLITE_OK) { - enif_release_resource(stmt); - return make_sqlite3_error_tuple(env, rc, conn->db); - } - - esqlite_stmt = enif_make_resource(env, stmt); - enif_release_resource(stmt); - - return make_ok_tuple(env, esqlite_stmt); -} +/* static int bind_cell(ErlNifEnv *env, const ERL_NIF_TERM cell, sqlite3_stmt *stmt, unsigned int i) { @@ -419,23 +365,23 @@ bind_cell(ErlNifEnv *env, const ERL_NIF_TERM cell, sqlite3_stmt *stmt, unsigned return sqlite3_bind_text(stmt, i, the_atom, strlen(the_atom), SQLITE_TRANSIENT); } - /* Bind as text assume it is utf-8 encoded text */ + // Bind as text assume it is utf-8 encoded text if(enif_inspect_iolist_as_binary(env, cell, &the_blob)) { return sqlite3_bind_text(stmt, i, (char *) the_blob.data, the_blob.size, SQLITE_TRANSIENT); } - /* Check for blob tuple */ + // Check for blob tuple if(enif_get_tuple(env, cell, &arity, &tuple)) { if(arity != 2) return -1; - /* length 2! */ + // length 2! if(enif_get_atom(env, tuple[0], the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) { - /* its a blob... */ + // its a blob... if(0 == strncmp("blob", the_atom, strlen("blob"))) { - /* with a iolist as argument */ + // with a iolist as argument if(enif_inspect_iolist_as_binary(env, tuple[1], &the_blob)) { - /* kaboom... get the blob */ + // kaboom... get the blob return sqlite3_bind_blob(stmt, i, the_blob.data, the_blob.size, SQLITE_TRANSIENT); } } @@ -444,7 +390,9 @@ bind_cell(ErlNifEnv *env, const ERL_NIF_TERM cell, sqlite3_stmt *stmt, unsigned return -1; } +*/ +/* static ERL_NIF_TERM do_bind(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg) { @@ -474,9 +422,11 @@ do_bind(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg) return make_atom(env, "ok"); } +*/ +/* static ERL_NIF_TERM -do_get_autocommit(ErlNifEnv *env, esqlite_connection *conn) +do_get_autocommit(ErlNifEnv *env, esqlite3 *conn) { if(!conn->db) { return make_error_tuple(env, "closed"); @@ -488,7 +438,9 @@ do_get_autocommit(ErlNifEnv *env, esqlite_connection *conn) return make_atom(env, "false"); } +*/ +/* static ERL_NIF_TERM make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) { @@ -505,7 +457,9 @@ make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) return term; } +*/ +/* static ERL_NIF_TERM make_cell(ErlNifEnv *env, sqlite3_stmt *statement, unsigned int i) { @@ -529,7 +483,9 @@ make_cell(ErlNifEnv *env, sqlite3_stmt *statement, unsigned int i) return make_atom(env, "should_not_happen"); } } +*/ +/* static ERL_NIF_TERM make_row(ErlNifEnv *env, sqlite3_stmt *statement, ERL_NIF_TERM *array, int size) { @@ -541,7 +497,9 @@ make_row(ErlNifEnv *env, sqlite3_stmt *statement, ERL_NIF_TERM *array, int size) return enif_make_tuple_from_array(env, array, size); } +*/ +/* static ERL_NIF_TERM do_multi_step(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg) { @@ -575,24 +533,26 @@ do_multi_step(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TER status = make_atom(env, "$busy"); break; case SQLITE_DONE: - /* - * Automatically reset the statement after a done so - * column_names will work after the statement is done. - * - * Not resetting the statement can lead to vm crashes. - */ + // + // Automatically reset the statement after a done so + // column_names will work after the statement is done. + // + // Not resetting the statement can lead to vm crashes. + // sqlite3_reset(stmt); status = make_atom(env, "$done"); break; default: - /* We use prepare_v2, so any error code can be returned. */ + // We use prepare_v2, so any error code can be returned. return make_sqlite3_error_tuple(env, rc, db); } enif_free(rowBuffer); return enif_make_tuple2(env, status, rows); } +*/ +/* static ERL_NIF_TERM do_reset(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt) { @@ -602,7 +562,9 @@ do_reset(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt) return make_atom(env, "ok"); } +*/ +/* static ERL_NIF_TERM do_column_names(ErlNifEnv *env, sqlite3_stmt *stmt) { @@ -635,7 +597,9 @@ do_column_names(ErlNifEnv *env, sqlite3_stmt *stmt) enif_free(array); return column_names; } +*/ +/* static ERL_NIF_TERM do_column_types(ErlNifEnv *env, sqlite3_stmt *stmt) { @@ -667,7 +631,9 @@ do_column_types(ErlNifEnv *env, sqlite3_stmt *stmt) enif_free(array); return column_types; } +*/ +/* static ERL_NIF_TERM do_backup_init(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) { @@ -677,8 +643,8 @@ do_backup_init(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) unsigned int size; char dst_name[MAX_SQLITE_NAME_LENGTH]; char src_name[MAX_SQLITE_NAME_LENGTH]; - esqlite_connection *src; - esqlite_backup *esqlite_backup; + esqlite3 *src; + esqlite3_backup *esqlite3_backup; ERL_NIF_TERM erl_backup_term; if(db == NULL) { @@ -696,7 +662,7 @@ do_backup_init(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) if(size <= 0) return make_error_tuple(env, "invalid_dst_name"); - if(!enif_get_resource(env, elements[1], esqlite_connection_type, (void **) &src)) { + if(!enif_get_resource(env, elements[1], esqlite3_type, (void **) &src)) { return make_error_tuple(env, "invalid_src_db"); } if(!src->db) { @@ -712,26 +678,28 @@ do_backup_init(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) return make_sqlite3_error_tuple(env, sqlite3_errcode(db), db); } - esqlite_backup = enif_alloc_resource(esqlite_backup_type, sizeof(esqlite_backup)); - if(!esqlite_backup) { + esqlite3_backup = enif_alloc_resource(esqlite3_backup_type, sizeof(esqlite3_backup)); + if(!esqlite3_backup) { // Release backup resouces (void) sqlite3_backup_finish(backup); return make_error_tuple(env, "no_memory"); } - esqlite_backup->backup = backup; - erl_backup_term = enif_make_resource(env, esqlite_backup); - enif_release_resource(esqlite_backup); + esqlite3_backup->backup = backup; + erl_backup_term = enif_make_resource(env, esqlite3_backup); + enif_release_resource(esqlite3_backup); return make_ok_tuple(env, erl_backup_term); } +*/ +/* static ERL_NIF_TERM do_backup_step(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) { int tuple_arity; const ERL_NIF_TERM *elements; - esqlite_backup *esqlite_backup; + esqlite3_backup *esqlite3_backup; int n_page = 0; int rc; @@ -746,10 +714,10 @@ do_backup_step(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) return make_error_tuple(env, "invalid_tuple"); } - if(!enif_get_resource(env, elements[0], esqlite_backup_type, (void **) &esqlite_backup)) { + if(!enif_get_resource(env, elements[0], esqlite3_backup_type, (void **) &esqlite3_backup)) { return make_error_tuple(env, "invalid"); } - if(!esqlite_backup->backup) { + if(!esqlite3_backup->backup) { return make_error_tuple(env, "backup"); } @@ -757,7 +725,7 @@ do_backup_step(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) return make_error_tuple(env, "n_page"); } - rc = sqlite3_backup_step(esqlite_backup->backup, n_page); + rc = sqlite3_backup_step(esqlite3_backup->backup, n_page); if(rc == SQLITE_DONE) { return make_atom(env, "done"); } @@ -768,63 +736,128 @@ do_backup_step(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) return make_atom(env, "ok"); } +*/ +/* static ERL_NIF_TERM do_backup_remaining(ErlNifEnv *env, const ERL_NIF_TERM arg) { - esqlite_backup *esqlite_backup; + esqlite3_backup *esqlite3_backup; int remaining; ERL_NIF_TERM remaining_term; - if(!enif_get_resource(env, arg, esqlite_backup_type, (void **) &esqlite_backup)) { + if(!enif_get_resource(env, arg, esqlite3_backup_type, (void **) &esqlite3_backup)) { return make_error_tuple(env, "invalid"); } - remaining = sqlite3_backup_remaining(esqlite_backup->backup); + remaining = sqlite3_backup_remaining(esqlite3_backup->backup); remaining_term = enif_make_int64(env, remaining); return make_ok_tuple(env, remaining_term); } +*/ +/* static ERL_NIF_TERM do_backup_pagecount(ErlNifEnv *env, const ERL_NIF_TERM arg) { - esqlite_backup *esqlite_backup; + esqlite3_backup *esqlite3_backup; int pagecount; ERL_NIF_TERM pagecount_term; - if(!enif_get_resource(env, arg, esqlite_backup_type, (void **) &esqlite_backup)) { + if(!enif_get_resource(env, arg, esqlite3_backup_type, (void **) &esqlite3_backup)) { return make_error_tuple(env, "invalid"); } - pagecount = sqlite3_backup_pagecount(esqlite_backup->backup); + pagecount = sqlite3_backup_pagecount(esqlite3_backup->backup); pagecount_term = enif_make_int64(env, pagecount); return make_ok_tuple(env, pagecount_term); } +*/ +/* static ERL_NIF_TERM do_backup_finish(ErlNifEnv *env, const ERL_NIF_TERM arg) { - esqlite_backup *esqlite_backup; + esqlite3_backup *esqlite3_backup; - if(!enif_get_resource(env, arg, esqlite_backup_type, (void **) &esqlite_backup)) { + if(!enif_get_resource(env, arg, esqlite3_backup_type, (void **) &esqlite3_backup)) { return make_error_tuple(env, "invalid"); } - if(esqlite_backup->backup) { - (void) sqlite3_backup_finish(esqlite_backup->backup); - esqlite_backup->backup = NULL; + if(esqlite3_backup->backup) { + (void) sqlite3_backup_finish(esqlite3_backup->backup); + esqlite3_backup->backup = NULL; } return make_atom(env, "ok"); } +*/ +/* + * Open the database + */ static ERL_NIF_TERM -do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) +esqlite_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + esqlite3 *conn; + char filename[MAX_PATHNAME]; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!sqlite3_threadsafe()) { + return enif_raise_exception(env, make_atom(env, "not_thread_safe")); + } + + int size = enif_get_string(env, argv[1], filename, MAX_PATHNAME, ERL_NIF_LATIN1); + if(size <= 0) { + return make_error_tuple(env, "invalid_filename"); + } + + /* Initialize the resource */ + conn = enif_alloc_resource(esqlite3_type, sizeof(esqlite3)); + if(!conn) { + return enif_raise_exception(env, make_atom(env, "no_memory")); + } + + /* Open the database. + */ + int rc = sqlite3_open(filename, &conn->db); + if(rc != SQLITE_OK) { + ERL_NIF_TERM error = make_sqlite3_error_tuple(env, rc, conn->db); + sqlite3_close_v2(conn->db); + enif_release_resource(conn); + return error; + } + + /* Set a standard busy timeout of 2 seconds */ + sqlite3_busy_timeout(conn->db, 2000); + + ERL_NIF_TERM db_conn = enif_make_resource(env, conn); + enif_release_resource(conn); + + return make_ok_tuple(env, db_conn); +} + +/* + * Close the database + */ +static ERL_NIF_TERM +esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; int rc; + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + return enif_make_badarg(env); + rc = sqlite3_close_v2(conn->db); if(rc != SQLITE_OK) return make_sqlite3_error_tuple(env, rc, conn->db); @@ -834,68 +867,65 @@ do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) } /* - * Open the database + * Prepare the sql statement */ static ERL_NIF_TERM -esqlite_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; + esqlite3 *conn; - if(argc != 1) { + if(argc != 2) + return enif_make_badarg(env); + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + return enif_make_badarg(env); + + ErlNifBinary bin; + esqlite3_stmt *stmt; + ERL_NIF_TERM esqlite_stmt; + const char *tail; + int rc; + ERL_NIF_TERM eos = enif_make_int(env, 0); + + if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[1], eos), &bin)) { return enif_make_badarg(env); } - if(!sqlite3_threadsafe()) { - return enif_raise_exception(env, make_atom(env, "not_thread_safe")); - } - - size = enif_get_string(env, argv[1], filename, MAX_PATHNAME, ERL_NIF_LATIN1); - if(size <= 0) - return make_error_tuple(env, "invalid_filename"); - - /* Initialize the resource */ - conn = enif_alloc_resource(esqlite_connection_type, sizeof(esqlite_connection)); - if(!conn) { + stmt = enif_alloc_resource(esqlite3_stmt_type, sizeof(esqlite3_stmt)); + if(!stmt) { return enif_raise_exception(env, make_atom(env, "no_memory")); } - char filename[MAX_PATHNAME]; - unsigned int size; - int rc; - ERL_NIF_TERM error; - - /* Open the database. - */ - rc = sqlite3_open(filename, conn->db); + rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail); if(rc != SQLITE_OK) { - error = make_sqlite3_error_tuple(env, rc, db); - sqlite3_close_v2(conn->db); - enif_release_resource(conn); - return error; + enif_release_resource(stmt); + return make_sqlite3_error_tuple(env, rc, conn->db); } - /* Set a standard busy timeout of 2 seconds */ - sqlite3_busy_timeout(conn->db, 2000); - - ERL_NIF_TERM = db_conn = enif_make_resource(env, conn); - enif_release_resource(conn); + esqlite_stmt = enif_make_resource(env, stmt); + enif_release_resource(stmt); - return make_ok_tuple(env, db_conn); + return make_ok_tuple(env, esqlite_stmt); } + +/* static ERL_NIF_TERM set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; + esqlite3 *db; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) return enif_make_badarg(env); + if(!enif_is_ref(env, argv[1])) return make_error_tuple(env, "invalid_ref"); + if(!enif_get_local_pid(env, argv[2], &pid)) return make_error_tuple(env, "invalid_pid"); @@ -903,7 +933,7 @@ set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!cmd) return make_error_tuple(env, "command_create_failed"); - /* command */ + // command cmd->type = cmd_update_hook_set; cmd->ref = enif_make_copy(cmd->env, argv[1]); cmd->pid = pid; @@ -911,20 +941,20 @@ set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, db, cmd); } +*/ /* * Execute the sql statement - */ static ERL_NIF_TERM esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; + esqlite3 *db; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) return make_error_tuple(env, "invalid_ref"); @@ -935,7 +965,7 @@ esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!cmd) return make_error_tuple(env, "command_create_failed"); - /* command */ + // command cmd->type = cmd_exec; cmd->ref = enif_make_copy(cmd->env, argv[1]); cmd->pid = pid; @@ -943,20 +973,20 @@ esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, db, cmd); } + */ /* * Count the nr of changes of last statement - */ static ERL_NIF_TERM esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; + esqlite3 *db; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 3) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) return make_error_tuple(env, "invalid_ref"); @@ -967,24 +997,26 @@ esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!cmd) return make_error_tuple(env, "command_create_failed"); - /* command */ + // command cmd->type = cmd_changes; cmd->ref = enif_make_copy(cmd->env, argv[1]); cmd->pid = pid; return push_command(env, db, cmd); } + */ +/* static ERL_NIF_TERM esqlite_insert(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; + esqlite3 *db; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) return make_error_tuple(env, "invalid_ref"); @@ -995,7 +1027,7 @@ esqlite_insert(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!cmd) return make_error_tuple(env, "command_create_failed"); - /* command */ + // command cmd->type = cmd_insert; cmd->ref = enif_make_copy(cmd->env, argv[1]); cmd->pid = pid; @@ -1003,17 +1035,19 @@ esqlite_insert(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, db, cmd); } +*/ +/* static ERL_NIF_TERM esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; + esqlite3 *db; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 3) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) return make_error_tuple(env, "invalid_ref"); @@ -1024,24 +1058,26 @@ esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!cmd) return make_error_tuple(env, "command_create_failed"); - /* command */ + // command cmd->type = cmd_last_insert_rowid; cmd->ref = enif_make_copy(cmd->env, argv[1]); cmd->pid = pid; return push_command(env, db, cmd); } +*/ +/* static ERL_NIF_TERM esqlite_get_autocommit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *db; + esqlite3 *db; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 3) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) return make_error_tuple(env, "invalid_ref"); @@ -1052,62 +1088,31 @@ esqlite_get_autocommit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!cmd) return make_error_tuple(env, "command_create_failed"); - /* command */ + // command cmd->type = cmd_get_autocommit; cmd->ref = enif_make_copy(cmd->env, argv[1]); cmd->pid = pid; return push_command(env, db, cmd); } - -/* - * Prepare the sql statement - */ -static ERL_NIF_TERM -esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite_connection *conn; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_prepare; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[3]); - - return push_command(env, conn, cmd); -} +*/ /* * Bind a variable to a prepared statement - */ static ERL_NIF_TERM esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_statement *stmt; + esqlite3 *conn; + esqlite3_stmt *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 5) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); @@ -1126,15 +1131,15 @@ esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Multi step to a prepared statement - */ static ERL_NIF_TERM esqlite_multi_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_statement *stmt; + esqlite3 *conn; + esqlite3_stmt *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; int chunk_size = 0; @@ -1142,10 +1147,10 @@ esqlite_multi_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(argc != 5) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) return enif_make_badarg(env); if(!enif_get_int(env, argv[2], &chunk_size)) @@ -1172,23 +1177,23 @@ esqlite_multi_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Reset a prepared statement to its initial state - */ static ERL_NIF_TERM esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_statement *stmt; + esqlite3 *conn; + esqlite3_stmt *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); @@ -1208,23 +1213,23 @@ esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Get the column names of the prepared statement. - */ static ERL_NIF_TERM esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_statement *stmt; + esqlite3 *conn; + esqlite3_stmt *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); @@ -1244,24 +1249,24 @@ esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Get the column types of the prepared statement. - */ static ERL_NIF_TERM esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_statement *stmt; + esqlite3 *conn; + esqlite3_stmt *stmt; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite_statement_type, (void **) &stmt)) + if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[2])) return make_error_tuple(env, "invalid_ref"); @@ -1282,24 +1287,25 @@ esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Backup functions * */ - +/* static ERL_NIF_TERM esqlite_backup_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *destination; + esqlite3 *destination; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 6) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &destination)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &destination)) return enif_make_badarg(env); // 1 destination name // 2 source connection with database @@ -1318,22 +1324,24 @@ esqlite_backup_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) cmd->pid = pid; cmd->arg = enif_make_tuple3(cmd->env, argv[1], argv[2], argv[3]); - /* Use the connection of the destination database */ + // Use the connection of the destination database return push_command(env, destination, cmd); } +*/ +/* static ERL_NIF_TERM esqlite_backup_finish(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_backup *backup; + esqlite3 *conn; + esqlite3_backup *backup; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); if(!enif_is_ref(env, argv[2])) @@ -1352,19 +1360,21 @@ esqlite_backup_finish(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } +*/ +/* static ERL_NIF_TERM esqlite_backup_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_backup *backup; + esqlite3 *conn; + esqlite3_backup *backup; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 5) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); // 1 backup if(!enif_is_number(env, argv[2])) @@ -1385,23 +1395,23 @@ esqlite_backup_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } +*/ /* * Get the remaining pagecount of the backup. - */ static ERL_NIF_TERM esqlite_backup_remaining(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_backup *backup; + esqlite3 *conn; + esqlite3_backup *backup; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); // backup 1 if(!enif_is_ref(env, argv[2])) @@ -1420,23 +1430,23 @@ esqlite_backup_remaining(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Get the total pagecount of the backup - */ static ERL_NIF_TERM esqlite_backup_pagecount(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; - esqlite_backup *backup; + esqlite3 *conn; + esqlite3_backup *backup; esqlite_command *cmd = NULL; ErlNifPid pid; if(argc != 4) return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); // backup 1 if(!enif_is_ref(env, argv[2])) @@ -1455,6 +1465,7 @@ esqlite_backup_pagecount(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, conn, cmd); } + */ /* * Interrupt currently active query. @@ -1463,45 +1474,17 @@ esqlite_backup_pagecount(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) static ERL_NIF_TERM esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite_connection *conn; + esqlite3 *conn; - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); - esqlite_connection *db = (esqlite_connection *) conn; + esqlite3 *db = (esqlite3 *) conn; sqlite3_interrupt(db->db); return enif_make_atom(env, "ok"); } -/* - * Close the database - */ -static ERL_NIF_TERM -esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite_connection *conn; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_close; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - - return push_command(env, conn, cmd); -} - /* * Load the nif. Initialize some stuff and such */ @@ -1510,25 +1493,26 @@ on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) { ErlNifResourceType *rt; - rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite_connection_type", destruct_esqlite_connection, + rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite3_type", destruct_esqlite3, ERL_NIF_RT_CREATE, NULL); if(!rt) return -1; - esqlite_connection_type = rt; + esqlite3_type = rt; - rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite_statement_type", destruct_esqlite_statement, + rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite3_stmt_type", destruct_esqlite3_stmt, ERL_NIF_RT_CREATE, NULL); if(!rt) return -1; - esqlite_statement_type = rt; + esqlite3_stmt_type = rt; - rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite_backup_type", destruct_esqlite_backup, + rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite3_backup_type", destruct_esqlite3_backup, ERL_NIF_RT_CREATE, NULL); if(!rt) return -1; - esqlite_backup_type = rt; + esqlite3_backup_type = rt; atom_esqlite3 = make_atom(env, "esqlite3"); - if(SQLITE_OK != sqlite3_initialize()) + if(SQLITE_OK != sqlite3_initialize()) { return -1; + } return 0; } @@ -1544,11 +1528,18 @@ static int on_upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF } static ErlNifFunc nif_funcs[] = { - {"open", 4, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"open", 1, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"close", 1, esqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND}, + + {"prepare", 2, esqlite_prepare}, + + {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND} + + /* {"set_update_hook", 4, set_update_hook}, + {"exec", 4, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"changes", 3, esqlite_changes}, - {"prepare", 4, esqlite_prepare}, {"insert", 4, esqlite_insert}, {"last_insert_rowid", 3, esqlite_last_insert_rowid}, {"get_autocommit", 3, esqlite_get_autocommit}, @@ -1565,9 +1556,8 @@ static ErlNifFunc nif_funcs[] = { {"backup_remaining", 4, esqlite_backup_remaining}, {"backup_pagecount", 4, esqlite_backup_pagecount}, {"backup_finish", 4, esqlite_backup_finish}, + */ - {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"close", 3, esqlite_close} }; ERL_NIF_INIT(esqlite3_nif, nif_funcs, on_load, on_reload, on_upgrade, NULL); diff --git a/c_src/queue.c b/c_src/queue.c deleted file mode 100644 index 685e9c8..0000000 --- a/c_src/queue.c +++ /dev/null @@ -1,195 +0,0 @@ -// This file is part of Emonk released under the MIT license. -// See the LICENSE file for more information. - -/* Adapted by: Maas-Maarten Zeeman -#include - -#include "queue.h" - -struct qitem_t -{ - struct qitem_t* next; - void* data; -}; - -typedef struct qitem_t qitem; - -struct queue_t -{ - ErlNifMutex *lock; - ErlNifCond *cond; - qitem *head; - qitem *tail; - void *message; - int length; -}; - -queue * -queue_create() -{ - queue *ret; - - ret = (queue *) enif_alloc(sizeof(struct queue_t)); - if(ret == NULL) goto error; - - ret->lock = NULL; - ret->cond = NULL; - ret->head = NULL; - ret->tail = NULL; - ret->message = NULL; - ret->length = 0; - - ret->lock = enif_mutex_create("queue_lock"); - if(ret->lock == NULL) goto error; - - ret->cond = enif_cond_create("queue_cond"); - if(ret->cond == NULL) goto error; - - return ret; - -error: - if(ret->lock != NULL) - enif_mutex_destroy(ret->lock); - if(ret->cond != NULL) - enif_cond_destroy(ret->cond); - if(ret != NULL) - enif_free(ret); - return NULL; -} - -void -queue_destroy(queue *queue) -{ - ErlNifMutex *lock; - ErlNifCond *cond; - int length; - - enif_mutex_lock(queue->lock); - lock = queue->lock; - cond = queue->cond; - length = queue->length; - - queue->lock = NULL; - queue->cond = NULL; - queue->head = NULL; - queue->tail = NULL; - queue->length = -1; - enif_mutex_unlock(lock); - - assert(length == 0 && "Attempting to destroy a non-empty queue."); - enif_cond_destroy(cond); - enif_mutex_destroy(lock); - enif_free(queue); -} - -int -queue_has_item(queue *queue) -{ - int ret; - - enif_mutex_lock(queue->lock); - ret = (queue->head != NULL); - enif_mutex_unlock(queue->lock); - - return ret; -} - -int -queue_push(queue *queue, void *item) -{ - qitem * entry = (qitem *) enif_alloc(sizeof(struct qitem_t)); - if(entry == NULL) - return 0; - - entry->data = item; - entry->next = NULL; - - enif_mutex_lock(queue->lock); - - assert(queue->length >= 0 && "Invalid queue size at push"); - - if(queue->tail != NULL) - queue->tail->next = entry; - - queue->tail = entry; - - if(queue->head == NULL) - queue->head = queue->tail; - - queue->length += 1; - - enif_cond_signal(queue->cond); - enif_mutex_unlock(queue->lock); - - return 1; -} - -void* -queue_pop(queue *queue) -{ - qitem *entry; - void* item; - - enif_mutex_lock(queue->lock); - - /* Wait for an item to become available. - */ - while(queue->head == NULL) - enif_cond_wait(queue->cond, queue->lock); - - assert(queue->length >= 0 && "Invalid queue size at pop."); - - /* Woke up because queue->head != NULL - * Remove the entry and return the payload. - */ - entry = queue->head; - queue->head = entry->next; - entry->next = NULL; - - if(queue->head == NULL) { - assert(queue->tail == entry && "Invalid queue state: Bad tail."); - queue->tail = NULL; - } - - queue->length -= 1; - - enif_mutex_unlock(queue->lock); - - item = entry->data; - enif_free(entry); - - return item; -} - -int -queue_send(queue *queue, void *item) -{ - enif_mutex_lock(queue->lock); - assert(queue->message == NULL && "Attempting to send multiple messages."); - queue->message = item; - enif_cond_signal(queue->cond); - enif_mutex_unlock(queue->lock); - return 1; -} - -void * -queue_receive(queue *queue) -{ - void *item; - - enif_mutex_lock(queue->lock); - - /* Wait for an item to become available. - */ - while(queue->message == NULL) - enif_cond_wait(queue->cond, queue->lock); - - item = queue->message; - queue->message = NULL; - - enif_mutex_unlock(queue->lock); - - return item; -} diff --git a/c_src/queue.h b/c_src/queue.h deleted file mode 100644 index 5ac597d..0000000 --- a/c_src/queue.h +++ /dev/null @@ -1,24 +0,0 @@ -// This file is part of Emonk released under the MIT license. -// See the LICENSE file for more information. - -/* adapted by: Maas-Maarten Zeeman sqlite type conversions @@ -81,7 +81,7 @@ -type row() :: tuple(). % tuple of cell_type -type cell_type() :: undefined | integer() | binary() | float(). --export_type([connection/0, statement/0, sql/0, row/0, rowid/0, cell_type/0]). +-export_type([esqlite3/0, esqlite3_stmt/0, sql/0, row/0, rowid/0, cell_type/0]). %% @doc Opens a sqlite3 database mentioned in Filename. %% @@ -98,388 +98,378 @@ %% ```open("file:memdb1?mode=memory&cache=shared")''' %% Opens a shared memory database named memdb1 with a shared cache. %% --spec open(string()) -> {ok, connection()} | {error, _}. +-spec open(Filename) -> OpenResult + when Filename :: string(), + OpenResult :: {ok, esqlite3()} | {error, _}. open(Filename) -> - open(Filename, ?DEFAULT_TIMEOUT). - -%% @doc Like open/1, but with an additional timeout. -%% --spec open(string(), timeout()) -> {ok, connection()} | {error, _}. -open(Filename) -> - Ref = make_ref(), case esqlite3_nif:open(Filename) of - {ok, RawConnection} -> - {ok, #connection{raw_connection=RawConnection}}; + {ok, Connection} -> + {ok, #esqlite3{db=Connection}}; {error, _Msg}=Error -> Error end. %% @doc Close the database --spec close(connection()) -> ok | {error, _}. -close(Connection) -> - close(Connection, ?DEFAULT_TIMEOUT). - -%% @doc Close the database --spec close(connection(), timeout()) -> ok | {error, _}. -close(#connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:close(RawConnection, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). +-spec close(Connection) -> CloseResult + when Connection :: esqlite3(), + CloseResult :: ok | {error, _}. +close(#esqlite3{db=Connection}) -> + esqlite3_nif:close(Connection). %% @doc Flush any stale answers left in the mailbox of the current process. %% This can happen if there has been a timeout. Normally the nif functions %% are called with the default 'infinite' timeout, so calling this is not %% needed. --spec flush() -> ok. -flush() -> - flush_answers(). - - -%% @doc Subscribe to database notifications. When rows are inserted deleted -%% or updates, the process will receive messages: -%% ```{insert, string(), rowid()}''' -%% When a new row has been inserted. -%% ```{delete, string(), rowid()}''' -%% When a new row has been deleted. -%% ```{update, string(), rowid()}''' -%% When a row has been updated. -%% --spec set_update_hook(pid(), connection()) -> ok | {error, term()}. -set_update_hook(Pid, Connection) -> - set_update_hook(Pid, Connection, ?DEFAULT_TIMEOUT). - -%% @doc Same as set_update_hook/2, but with an additional timeout parameter. -%% --spec set_update_hook(pid(), connection(), timeout()) -> ok | {error, term()}. -set_update_hook(Pid, #connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:set_update_hook(RawConnection, Ref, self(), Pid), - receive_answer(RawConnection, Ref, Timeout). - -%% -%% q -%% - -%% @doc Execute a sql statement, returns a list with tuples. --spec q(sql(), connection()) -> list(row()) | {error, _}. -q(Sql, Connection) -> - q(Sql, [], Connection, ?DEFAULT_TIMEOUT). - -%% @doc Execute statement, bind args and return a list with tuples as result. --spec q(sql(), list(), connection()) -> list(row()) | {error, _}. -q(Sql, Args, Connection) -> - q(Sql, Args, Connection, ?DEFAULT_TIMEOUT). - -%% @doc Execute statement, bind args and return a list with tuples as result restricted by timeout. --spec q(sql(), list(), connection(), timeout()) -> list(row()) | {error, _}. -q(Sql, [], Connection, Timeout) -> - case prepare(Sql, Connection, Timeout) of - {ok, Statement} -> - fetchall(Statement, ?DEFAULT_CHUNK_SIZE, Timeout); - {error, _Msg}=Error -> - Error - end; -q(Sql, Args, Connection, Timeout) -> - case prepare(Sql, Connection, Timeout) of - {ok, Statement} -> - case bind(Statement, Args, Timeout) of - ok -> - fetchall(Statement, ?DEFAULT_CHUNK_SIZE, Timeout); - {error, _}=Error -> - Error - end; - {error, _Msg}=Error -> - Error - end. - -%% +%-spec flush() -> ok. +%flush() -> +% flush_answers(). +% +% +%%% @doc Subscribe to database notifications. When rows are inserted deleted +%%% or updates, the process will receive messages: +%%% ```{insert, string(), rowid()}''' +%%% When a new row has been inserted. +%%% ```{delete, string(), rowid()}''' +%%% When a new row has been deleted. +%%% ```{update, string(), rowid()}''' +%%% When a row has been updated. +%%% +%-spec set_update_hook(pid(), connection()) -> ok | {error, term()}. +%set_update_hook(Pid, Connection) -> +% set_update_hook(Pid, Connection, ?DEFAULT_TIMEOUT). +% +%%% @doc Same as set_update_hook/2, but with an additional timeout parameter. +%%% +%-spec set_update_hook(pid(), connection(), timeout()) -> ok | {error, term()}. +%set_update_hook(Pid, #connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:set_update_hook(RawConnection, Ref, self(), Pid), +% receive_answer(RawConnection, Ref, Timeout). +% +%%% +%%% q +%%% +% +%%% @doc Execute a sql statement, returns a list with tuples. +%-spec q(sql(), connection()) -> list(row()) | {error, _}. +%q(Sql, Connection) -> +% q(Sql, [], Connection, ?DEFAULT_TIMEOUT). +% +%%% @doc Execute statement, bind args and return a list with tuples as result. +%-spec q(sql(), list(), connection()) -> list(row()) | {error, _}. +%%q(Sql, Args, Connection) -> +% q(Sql, Args, Connection, ?DEFAULT_TIMEOUT). +% +%%% @doc Execute statement, bind args and return a list with tuples as result restricted by timeout. +%-spec q(sql(), list(), connection(), timeout()) -> list(row()) | {error, _}. +%q(Sql, [], Connection, Timeout) -> +% case prepare(Sql, Connection, Timeout) of +% {ok, Statement} -> +% fetchall(Statement, ?DEFAULT_CHUNK_SIZE, Timeout); +% {error, _Msg}=Error -> +% Error +% end; +%q(Sql, Args, Connection, Timeout) -> +% case prepare(Sql, Connection, Timeout) of +% {ok, Statement} -> +% case bind(Statement, Args, Timeout) of +% ok -> +% fetchall(Statement, ?DEFAULT_CHUNK_SIZE, Timeout); +% {error, _}=Error -> +% Error +% end; +% {error, _Msg}=Error -> +% Error +% end. +% +%%% %% map -%% - -%% @doc Execute statement and return a list with the result of F for each row. --spec map(Fun, sql(), connection()) -> list(Type) when - Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), - Row :: row(), - ColumnNames :: tuple(), - Type :: any(). -map(Fun, Sql, Connection) -> - case prepare(Sql, Connection) of - {ok, Statement} -> - map_s(Fun, Statement); - {error, _Msg}=Error -> - Error - end. - -%% @doc Execute statement, bind args and return a list with the result of F for each row. --spec map(F, sql(), list(), connection()) -> list(Type) when - F :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), - Row :: tuple(), - ColumnNames :: tuple(), - Type :: any(). -map(Fun, Sql, [], Connection) -> - map(Fun, Sql, Connection); -map(Fun, Sql, Args, Connection) -> - case prepare(Sql, Connection) of - {ok, Statement} -> - case bind(Statement, Args) of - ok -> - map_s(Fun, Statement); - {error, _}=Error -> - Error - end; - {error, _Msg}=Error -> - Error - end. +%%% +% +%%% @doc Execute statement and return a list with the result of F for each row. +%-spec map(Fun, sql(), connection()) -> list(Type) when +%% Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), +% Row :: row(), +% ColumnNames :: tuple(), +% Type :: any(). +%map(Fun, Sql, Connection) -> +% case prepare(Sql, Connection) of +% {ok, Statement} -> +% map_s(Fun, Statement); +% {error, _Msg}=Error -> +% Error +% end. +% +%%% @doc Execute statement, bind args and return a list with the result of F for each row. +%-spec map(F, sql(), list(), connection()) -> list(Type) when +% F :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), +% Row :: tuple(), +% ColumnNames :: tuple(), +% Type :: any(). +%map(Fun, Sql, [], Connection) -> +% map(Fun, Sql, Connection); +%map(Fun, Sql, Args, Connection) -> +% case prepare(Sql, Connection) of +% {ok, Statement} -> +% case bind(Statement, Args) of +% ok -> +% map_s(Fun, Statement); +%% {error, _}=Error -> +% Error +% end; +% {error, _Msg}=Error -> +%% Error +% end. %% %% foreach %% %% @doc Execute statement and call F with each row. --spec foreach(Fun, sql(), connection()) -> ok when - Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), - Row :: tuple(), - ColumnNames :: tuple(). -foreach(Fun, Sql, Connection) -> - case prepare(Sql, Connection) of - {ok, Statement} -> - foreach_s(Fun, Statement); - {error, _Msg}=Error -> - Error - end. +%-spec foreach(Fun, sql(), connection()) -> ok when +% Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), +% Row :: tuple(), +% ColumnNames :: tuple(). +%foreach(Fun, Sql, Connection) -> +% case prepare(Sql, Connection) of +% {ok, Statement} -> +% foreach_s(Fun, Statement); +% {error, _Msg}=Error -> +% Error +% end. %% @doc Execute statement, bind args and call F with each row. --spec foreach(Fun, sql(), list(), connection()) -> ok when - Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), - Row :: row(), - ColumnNames :: tuple(). -foreach(F, Sql, [], Connection) -> - foreach(F, Sql, Connection); -foreach(F, Sql, Args, Connection) -> - case prepare(Sql, Connection) of - {ok, Statement} -> - case bind(Statement, Args) of - ok -> - foreach_s(F, Statement); - {error, _Msg}=Error -> - Error - end; - {error, _Msg}=Error -> - Error - end. +%-spec foreach(Fun, sql(), list(), connection()) -> ok when +% Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), +% Row :: row(), +% ColumnNames :: tuple(). +%foreach(F, Sql, [], Connection) -> +% foreach(F, Sql, Connection); +%foreach(F, Sql, Args, Connection) -> +% case prepare(Sql, Connection) of +% {ok, Statement} -> +% case bind(Statement, Args) of +%% ok -> +% foreach_s(F, Statement); +% {error, _Msg}=Error -> +% Error +% end; +% {error, _Msg}=Error -> +% Error +% end. %% %% fetchall %% %% --spec fetchone(statement()) -> tuple(). -fetchone(Statement) -> - case try_multi_step(Statement, 1, [], 0) of - {'$done', []} -> ok; - {error, _} = E -> E; - {rows, [Row | []]} -> Row - end. - -%% @doc Fetch all records -%% @param Statement is prepared sql statement --spec fetchall(statement()) -> list(row()) | {error, _}. -fetchall(Statement) -> - fetchall(Statement, ?DEFAULT_CHUNK_SIZE, ?DEFAULT_TIMEOUT). - +%-spec fetchone(statement()) -> tuple(). +%fetchone(Statement) -> +% case try_multi_step(Statement, 1, [], 0) of +% {'$done', []} -> ok; +% {error, _} = E -> E; +% {rows, [Row | []]} -> Row +% end. +% +%%% @doc Fetch all records +%%% @param Statement is prepared sql statement +%-spec fetchall(statement()) -> list(row()) | {error, _}. +%fetchall(Statement) -> +% fetchall(Statement, ?DEFAULT_CHUNK_SIZE, ?DEFAULT_TIMEOUT). +% %% @doc Fetch all records %% @param Statement is prepared sql statement %% @param ChunkSize is a count of rows to read from sqlite and send to erlang process in one bulk. %% Decrease this value if rows are heavy. Default value is 5000 (DEFAULT_CHUNK_SIZE). --spec fetchall(statement(), pos_integer()) -> list(row()) | {error, _}. -fetchall(Statement, ChunkSize) -> - fetchall(Statement, ChunkSize, ?DEFAULT_TIMEOUT). - -%% @doc Fetch all records -%% @param Statement is prepared sql statement +%-spec fetchall(statement(), pos_integer()) -> list(row()) | {error, _}. +%fetchall(Statement, ChunkSize) -> +% fetchall(Statement, ChunkSize, ?DEFAULT_TIMEOUT). +% +%%% @doc Fetch all records +%%% @param Statement is prepared sql statement %% @param ChunkSize is a count of rows to read from sqlite and send to erlang process in one bulk. %% Decrease this value if rows are heavy. Default value is 5000 (DEFAULT_CHUNK_SIZE). %% @param Timeout is timeout per each request of the one bulk --spec fetchall(statement(), pos_integer(), timeout()) -> list(row()) | {error, _}. -fetchall(Statement, ChunkSize, Timeout) -> - case fetchall_internal(Statement, ChunkSize, [], Timeout) of - {'$done', Rows} -> lists:reverse(Rows); - {error, _} = E -> E - end. +%-spec fetchall(statement(), pos_integer(), timeout()) -> list(row()) | {error, _}. +%fetchall(Statement, ChunkSize, Timeout) -> +% case fetchall_internal(Statement, ChunkSize, [], Timeout) of +% {'$done', Rows} -> lists:reverse(Rows); +% {error, _} = E -> E +% end. %% @doc Execute Sql statement. %% --spec exec(sql(), connection()) -> ok | {error, _}. -exec(Sql, Connection) -> - exec(Sql, [], Connection, ?DEFAULT_TIMEOUT). +%-spec exec(sql(), connection()) -> ok | {error, _}. +%exec(Sql, Connection) -> +% exec(Sql, [], Connection, ?DEFAULT_TIMEOUT). +% +%-spec exec(sql(), list(cell_type()) | connection(), connection() | timeout()) -> ok | {error, _}. +%exec(Sql, #connection{}=Connection, Timeout) -> +% exec(Sql, [], Connection, Timeout); +%exec(Sql, Params, #connection{}=Connection) -> +% exec(Sql, Params, Connection, ?DEFAULT_TIMEOUT). --spec exec(sql(), list(cell_type()) | connection(), connection() | timeout()) -> ok | {error, _}. -exec(Sql, #connection{}=Connection, Timeout) -> - exec(Sql, [], Connection, Timeout); -exec(Sql, Params, #connection{}=Connection) -> - exec(Sql, Params, Connection, ?DEFAULT_TIMEOUT). - --spec exec(sql(), list(cell_type()), connection(), timeout()) -> ok | {error, _}. -exec(Sql, [], #connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:exec(RawConnection, Ref, self(), Sql), - receive_answer(RawConnection, Ref, Timeout); -exec(Sql, Params, Connection, Timeout) -> - {ok, Statement} = prepare(Sql, Connection, Timeout), - case bind(Statement, Params) of - ok -> - step(Statement, Timeout); - {error, _}=Error -> - Error - end. +%-spec exec(sql(), list(cell_type()), connection(), timeout()) -> ok | {error, _}. +%exec(Sql, [], #connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:exec(RawConnection, Ref, self(), Sql), +% receive_answer(RawConnection, Ref, Timeout); +%exec(Sql, Params, Connection, Timeout) -> +% {ok, Statement} = prepare(Sql, Connection, Timeout), +% case bind(Statement, Params) of +% ok -> +% step(Statement, Timeout); +% {error, _}=Error -> +% Error +% end. %% @doc Return the number of affected rows of last statement. --spec changes(connection()) -> non_neg_integer(). -changes(Connection) -> - changes(Connection, ?DEFAULT_TIMEOUT). - --spec changes(connection(), timeout()) -> non_neg_integer(). -changes(#connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:changes(RawConnection, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). - -%% @doc Insert records, returns the last rowid. +%-spec changes(connection()) -> non_neg_integer(). +%changes(Connection) -> +% changes(Connection, ?DEFAULT_TIMEOUT). +% +%-spec changes(connection(), timeout()) -> non_neg_integer(). +%changes(#connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:changes(RawConnection, Ref, self()), +% receive_answer(RawConnection, Ref, Timeout). +% +%%% @doc Insert records, returns the last rowid. %% --spec insert(sql(), connection()) -> {ok, rowid()} | {error, _}. -insert(Sql, Connection) -> - insert(Sql, Connection, ?DEFAULT_TIMEOUT). +%-spec insert(sql(), connection()) -> {ok, rowid()} | {error, _}. +%insert(Sql, Connection) -> +% insert(Sql, Connection, ?DEFAULT_TIMEOUT). %% @doc Like insert/2, but with extra timeout parameter. --spec insert(sql(), connection(), timeout()) -> {ok, rowid()} | {error, _}. -insert(Sql, #connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:insert(RawConnection, Ref, self(), Sql), - receive_answer(RawConnection, Ref, Timeout). +%-spec insert(sql(), connection(), timeout()) -> {ok, rowid()} | {error, _}. +%insert(Sql, #connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:insert(RawConnection, Ref, self(), Sql), +% receive_answer(RawConnection, Ref, Timeout). %% @doc Get the last insert rowid, using the default timeout. %% --spec last_insert_rowid(connection()) -> {ok, rowid()} | {error, _}. -last_insert_rowid(Connection) -> - last_insert_rowid(Connection, ?DEFAULT_TIMEOUT). +%-spec last_insert_rowid(connection()) -> {ok, rowid()} | {error, _}. +%last_insert_rowid(Connection) -> +% last_insert_rowid(Connection, ?DEFAULT_TIMEOUT). %% @doc Get the last insert rowid. %% --spec last_insert_rowid(connection(), timeout()) -> {ok, rowid()} | {error, _}. -last_insert_rowid(#connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:last_insert_rowid(RawConnection, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). +%-spec last_insert_rowid(connection(), timeout()) -> {ok, rowid()} | {error, _}. +%last_insert_rowid(#connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:last_insert_rowid(RawConnection, Ref, self()), +% receive_answer(RawConnection, Ref, Timeout). %% @doc Check if the connection is in auto-commit mode. %% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details. %% --spec get_autocommit(connection()) -> true | false. -get_autocommit(Connection) -> - get_autocommit(Connection, ?DEFAULT_TIMEOUT). +%-spec get_autocommit(connection()) -> true | false. +%get_autocommit(Connection) -> +% get_autocommit(Connection, ?DEFAULT_TIMEOUT). %% @doc Like autocommit/1, but with an extra timeout attribute. --spec get_autocommit(connection(), timeout()) -> true | false. -get_autocommit(#connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:get_autocommit(RawConnection, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). - +%-spec get_autocommit(connection(), timeout()) -> true | false. +%get_autocommit(#connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:get_autocommit(RawConnection, Ref, self()), +% receive_answer(RawConnection, Ref, Timeout). +% %% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in %% queries. %% --spec prepare(sql(), connection()) -> {ok, statement()} | {error, _}. -prepare(Sql, Connection) -> - prepare(Sql, Connection, ?DEFAULT_TIMEOUT). +%-spec prepare(sql(), connection()) -> {ok, statement()} | {error, _}. +%prepare(Sql, Connection) -> +% prepare(Sql, Connection, ?DEFAULT_TIMEOUT). %% @doc Like prepare/2, but with an extra timeout value. --spec prepare(sql(), connection(), timeout()) -> {ok, statement()} | {error, _}. -prepare(Sql, #connection{raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:prepare(RawConnection, Ref, self(), Sql), - case receive_answer(RawConnection, Ref, Timeout) of - {ok, Stmt} when is_reference(Stmt) -> - {ok, #statement{raw_statement=Stmt, raw_connection=RawConnection}}; - {error, _}=Error -> - Error - end. +%-spec prepare(sql(), connection(), timeout()) -> {ok, statement()} | {error, _}. +%prepare(Sql, #connection{raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:prepare(RawConnection, Ref, self(), Sql), +% case receive_answer(RawConnection, Ref, Timeout) of +% {ok, Stmt} when is_reference(Stmt) -> +% {ok, #statement{raw_statement=Stmt, raw_connection=RawConnection}}; +% {error, _}=Error -> +%% Error +% end. %% @doc Step %% --spec step(statement()) -> tuple() | '$busy' | '$done'. -step(Stmt) -> - step(Stmt, ?DEFAULT_TIMEOUT). - +%-spec step(statement()) -> tuple() | '$busy' | '$done'. +%step(Stmt) -> +% step(Stmt, ?DEFAULT_TIMEOUT). +% %% @doc %% --spec step(statement(), timeout()) -> tuple() | '$busy' | '$done'. -step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:multi_step(RawConnection, RawStatement, 1, Ref, self()), - case receive_answer(RawConnection, Ref, Timeout) of - {rows, [Row | []]} -> {row, Row}; - {'$done', []} -> '$done'; - {'$busy', []} -> '$busy'; - Else -> Else - end. +%-spec step(statement(), timeout()) -> tuple() | '$busy' | '$done'. +%step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:multi_step(RawConnection, RawStatement, 1, Ref, self()), +% case receive_answer(RawConnection, Ref, Timeout) of +% {rows, [Row | []]} -> {row, Row}; +% {'$done', []} -> '$done'; +% {'$busy', []} -> '$busy'; +% Else -> Else +% end. %% @doc Reset the prepared statement back to its initial state. %% --spec reset(statement()) -> ok | {error, _}. -reset(#statement{raw_statement=RawStatement, raw_connection=RawConnection}) -> - Ref = make_ref(), - ok = esqlite3_nif:reset(RawConnection, RawStatement, Ref, self()), - receive_answer(RawConnection, Ref, ?DEFAULT_TIMEOUT). +%-spec reset(statement()) -> ok | {error, _}. +%reset(#statement{raw_statement=RawStatement, raw_connection=RawConnection}) -> +% Ref = make_ref(), +% ok = esqlite3_nif:reset(RawConnection, RawStatement, Ref, self()), +% receive_answer(RawConnection, Ref, ?DEFAULT_TIMEOUT). %% @doc Bind values to prepared statements %% --spec bind(statement(), list(cell_type())) -> ok | {error, _}. -bind(Stmt, Args) -> - bind(Stmt, Args, ?DEFAULT_TIMEOUT). +%-spec bind(statement(), list(cell_type())) -> ok | {error, _}. +%bind(Stmt, Args) -> +% bind(Stmt, Args, ?DEFAULT_TIMEOUT). %% @doc Bind values to prepared statements --spec bind(statement(), list(cell_type()), timeout()) -> ok | {error, _}. -bind(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Args, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:bind(RawConnection, RawStatement, Ref, self(), Args), - receive_answer(RawConnection, Ref, Timeout). - +%-spec bind(statement(), list(cell_type()), timeout()) -> ok | {error, _}. +%bind(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Args, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:bind(RawConnection, RawStatement, Ref, self(), Args), +% receive_answer(RawConnection, Ref, Timeout). +% %% @doc Return the column names of the prepared statement. %% --spec column_names(statement()) -> {atom()}. -column_names(Stmt) -> - column_names(Stmt, ?DEFAULT_TIMEOUT). +%-spec column_names(statement()) -> {atom()}. +%column_names(Stmt) -> +% column_names(Stmt, ?DEFAULT_TIMEOUT). --spec column_names(statement(), timeout()) -> {atom()}. -column_names(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:column_names(RawConnection, RawStatement, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). +%-spec column_names(statement(), timeout()) -> {atom()}. +%column_names(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:column_names(RawConnection, RawStatement, Ref, self()), +% receive_answer(RawConnection, Ref, Timeout). %% @doc Return the column types of the prepared statement. %% --spec column_types(statement()) -> {atom()}. -column_types(Statement) -> - column_types(Statement, ?DEFAULT_TIMEOUT). +%-spec column_types(statement()) -> {atom()}. +%column_types(Statement) -> +% column_types(Statement, ?DEFAULT_TIMEOUT). --spec column_types(statement(), timeout()) -> {atom()}. -column_types(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:column_types(RawConnection, RawStatement, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). +%-spec column_types(statement(), timeout()) -> {atom()}. +%column_types(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:column_types(RawConnection, RawStatement, Ref, self()), +% receive_answer(RawConnection, Ref, Timeout). %% @doc make multiple sqlite steps per call return rows in reverse order %% --spec multi_step(term(), pos_integer(), timeout()) -> - {rows, list(tuple())} | - {'$busy', list(tuple())} | - {'$done', list(tuple())} | - {error, _}. -multi_step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, ChunkSize, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:multi_step(RawConnection, RawStatement, ChunkSize, Ref, self()), - receive_answer(RawConnection, Ref, Timeout). +%-spec multi_step(term(), pos_integer(), timeout()) -> +% {rows, list(tuple())} | +% {'$busy', list(tuple())} | +% {'$done', list(tuple())} | +% {error, _}. +%multi_step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, ChunkSize, Timeout) -> +% Ref = make_ref(), +%% ok = esqlite3_nif:multi_step(RawConnection, RawStatement, ChunkSize, Ref, self()), +% receive_answer(RawConnection, Ref, Timeout). %% %% Backup API @@ -487,185 +477,185 @@ multi_step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, % @doc Initialize a backup procedure. %% --spec backup_init(connection(), string(), connection(), string()) -> {ok, backup()} | {error, _}. -backup_init(Dest, DestName, Src, SrcName) -> - backup_init(Dest, DestName, Src, SrcName, ?DEFAULT_TIMEOUT). - +%-spec backup_init(connection(), string(), connection(), string()) -> {ok, backup()} | {error, _}. +%backup_init(Dest, DestName, Src, SrcName) -> +% backup_init(Dest, DestName, Src, SrcName, ?DEFAULT_TIMEOUT). +% %% @doc Like backup_init/4, but with an extra timeout value. %% --spec backup_init(connection(), string(), connection(), string(), timeout()) -> {ok, backup()} | {error, _}. -backup_init(#connection{raw_connection=Dest}, DestName, #connection{raw_connection=Src}, SrcName, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:backup_init(Dest, DestName, Src, SrcName, Ref, self()), - case receive_answer(Dest, Ref, Timeout) of - {ok, RawBackup} when is_reference(RawBackup) -> - {ok, #backup{raw_connection=Dest, raw_backup=RawBackup}}; - {error, _} = Error -> - Error - end. +%-spec backup_init(connection(), string(), connection(), string(), timeout()) -> {ok, backup()} | {error, _}. +%backup_init(#connection{raw_connection=Dest}, DestName, #connection{raw_connection=Src}, SrcName, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:backup_init(Dest, DestName, Src, SrcName, Ref, self()), +% case receive_answer(Dest, Ref, Timeout) of +% {ok, RawBackup} when is_reference(RawBackup) -> +% {ok, #backup{raw_connection=Dest, raw_backup=RawBackup}}; +% {error, _} = Error -> +% Error +% end. %% @doc Release the resources held by the backup. --spec backup_finish(backup()) -> ok | {error, _}. -backup_finish(Backup) -> - backup_finish(Backup, ?DEFAULT_TIMEOUT). -%% @doc Like backup_finish/1, but with an extra timeout. --spec backup_finish(backup(), timeout()) -> ok | {error, _}. -backup_finish(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:backup_finish(Conn, Back, Ref, self()), - receive_answer(Conn, Ref, Timeout). +%-spec backup_finish(backup()) -> ok | {error, _}. +%backup_finish(Backup) -> +% backup_finish(Backup, ?DEFAULT_TIMEOUT). +%%% @doc Like backup_finish/1, but with an extra timeout. +%-spec backup_finish(backup(), timeout()) -> ok | {error, _}. +%backup_finish(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:backup_finish(Conn, Back, Ref, self()), +% receive_answer(Conn, Ref, Timeout). %% @doc Do a backup step. --spec backup_step(backup(), integer()) -> ok | {error, _}. -backup_step(Backup, NPage) -> - backup_step(Backup, NPage, ?DEFAULT_TIMEOUT). +%-spec backup_step(backup(), integer()) -> ok | {error, _}. +%backup_step(Backup, NPage) -> +% backup_step(Backup, NPage, ?DEFAULT_TIMEOUT). %% @doc Do a backup step. --spec backup_step(backup(), integer(), timeout()) -> ok | {error, _}. -backup_step(#backup{raw_connection=Conn, raw_backup=Back}, NPage, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:backup_step(Conn, Back, NPage, Ref, self()), - receive_answer(Conn, Ref, Timeout). +%-spec backup_step(backup(), integer(), timeout()) -> ok | {error, _}. +%backup_step(#backup{raw_connection=Conn, raw_backup=Back}, NPage, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:backup_step(Conn, Back, NPage, Ref, self()), +% receive_answer(Conn, Ref, Timeout). %% @doc Get the remaining number of pages which need to be backed up. --spec backup_remaining(backup()) -> {ok, pos_integer()} | {error, _}. -backup_remaining(Backup) -> - backup_remaining(Backup, ?DEFAULT_TIMEOUT). +%-spec backup_remaining(backup()) -> {ok, pos_integer()} | {error, _}. +%backup_remaining(Backup) -> +% backup_remaining(Backup, ?DEFAULT_TIMEOUT). %% @doc Get the remaining number of pages which need to be backed up. --spec backup_remaining(backup(), timeout()) -> {ok, pos_integer()} | {error, _}. -backup_remaining(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:backup_remaining(Conn, Back, Ref, self()), - case receive_answer(Conn, Ref, Timeout) of - {ok, R} when is_integer(R) -> - {ok, R}; - {error, _}=E -> - E - end. - +%-spec backup_remaining(backup(), timeout()) -> {ok, pos_integer()} | {error, _}. +%backup_remaining(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:backup_remaining(Conn, Back, Ref, self()), +% case receive_answer(Conn, Ref, Timeout) of +% {ok, R} when is_integer(R) -> +% {ok, R}; +% {error, _}=E -> +% E +% end. +%% %% @doc Get the remaining number of pages which need to be backed up. --spec backup_pagecount(backup()) -> {ok, pos_integer()} | {error, _}. -backup_pagecount(Backup) -> - backup_pagecount(Backup, ?DEFAULT_TIMEOUT). - +%-spec backup_pagecount(backup()) -> {ok, pos_integer()} | {error, _}. +%backup_pagecount(Backup) -> +% backup_pagecount(Backup, ?DEFAULT_TIMEOUT). +% %% @doc Get the remaining number of pages which need to be backed up. --spec backup_pagecount(backup(), timeout()) -> {ok, pos_integer()} | {error, _}. -backup_pagecount(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> - Ref = make_ref(), - ok = esqlite3_nif:backup_pagecount(Conn, Back, Ref, self()), - case receive_answer(Conn, Ref, Timeout) of - {ok, R} when is_integer(R) -> - {ok, R}; - {error, _}=E -> - E - end. +%-spec backup_pagecount(backup(), timeout()) -> {ok, pos_integer()} | {error, _}. +%backup_pagecount(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> +% Ref = make_ref(), +% ok = esqlite3_nif:backup_pagecount(Conn, Back, Ref, self()), +% case receive_answer(Conn, Ref, Timeout) of +% {ok, R} when is_integer(R) -> +% {ok, R}; +% {error, _}=E -> +% E +% end. %% %% Helpers %% --spec foreach_s(Fun, statement()) -> ok when - Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), - Row :: row(), - ColumnNames :: tuple(). -foreach_s(Fun, Statement) when is_function(Fun, 1) -> - case try_multi_step(Statement, 1, [], 0) of - {'$done', []} -> - ok; - {error, _} = Error -> - Error; - {rows, [Row | []]} -> - Fun(Row), - foreach_s(Fun, Statement) - end; -foreach_s(Fun, Statement) when is_function(Fun, 2) -> - ColumnNames = column_names(Statement), - case try_multi_step(Statement, 1, [], 0) of - {'$done', []} -> - ok; - {error, _} = Error -> - Error; - {rows, [Row | []]} -> - Fun(ColumnNames, Row), - foreach_s(Fun, Statement) - end. +%-spec foreach_s(Fun, statement()) -> ok when +% Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), +% Row :: row(), +% ColumnNames :: tuple(). +%foreach_s(Fun, Statement) when is_function(Fun, 1) -> +% case try_multi_step(Statement, 1, [], 0) of +% {'$done', []} -> +%% ok; +% {error, _} = Error -> +% Error; +% {rows, [Row | []]} -> +% Fun(Row), +% foreach_s(Fun, Statement) +% end; +%foreach_s(Fun, Statement) when is_function(Fun, 2) -> +% ColumnNames = column_names(Statement), +% case try_multi_step(Statement, 1, [], 0) of +% {'$done', []} -> +% ok; +% {error, _} = Error -> +% Error; +% {rows, [Row | []]} -> +% Fun(ColumnNames, Row), +% foreach_s(Fun, Statement) +% end. --spec map_s(Fun, statement()) -> list(Type) when - Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), - Row :: row(), - ColumnNames :: tuple(), - Type :: term(). -map_s(Fun, Statement) when is_function(Fun, 1) -> - case try_multi_step(Statement, 1, [], 0) of - {'$done', []} -> - []; - {error, _} = Error -> - Error; - {rows, [Row | []]} -> - [Fun(Row) | map_s(Fun, Statement)] - end; -map_s(Fun, Statement) when is_function(Fun, 2) -> - ColumnNames = column_names(Statement), - case try_multi_step(Statement, 1, [], 0) of - {'$done', []} -> - []; - {error, _} = Error -> - Error; - {rows, [Row | []]} -> - [Fun(ColumnNames, Row) | map_s(Fun, Statement)] - end. +%-spec map_s(Fun, statement()) -> list(Type) when +%% Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), +% Row :: row(), +% ColumnNames :: tuple(), +% Type :: term(). +%map_s(Fun, Statement) when is_function(Fun, 1) -> +% case try_multi_step(Statement, 1, [], 0) of +% {'$done', []} -> +% []; +% {error, _} = Error -> +% Error; +% {rows, [Row | []]} -> +% [Fun(Row) | map_s(Fun, Statement)] +% end; +%map_s(Fun, Statement) when is_function(Fun, 2) -> +% ColumnNames = column_names(Statement), +% case try_multi_step(Statement, 1, [], 0) of +% {'$done', []} -> +% []; +% {error, _} = Error -> +% Error; +% {rows, [Row | []]} -> +% [Fun(ColumnNames, Row) | map_s(Fun, Statement)] +% end. %% return rows in reverse order --spec fetchall_internal(statement(), pos_integer(), list(row()), timeout()) -> - {'$done', list(row())} | - {error, _}. -fetchall_internal(Statement, ChunkSize, Rest, Timeout) -> - case try_multi_step(Statement, ChunkSize, Rest, 0, Timeout) of - {rows, Rows} -> fetchall_internal(Statement, ChunkSize, Rows, Timeout); - Else -> Else - end. +%-spec fetchall_internal(statement(), pos_integer(), list(row()), timeout()) -> +% {'$done', list(row())} | +% {error, _}. +%fetchall_internal(Statement, ChunkSize, Rest, Timeout) -> +% case try_multi_step(Statement, ChunkSize, Rest, 0, Timeout) of +% {rows, Rows} -> fetchall_internal(Statement, ChunkSize, Rows, Timeout); +% Else -> Else +% end. + +%% Try a number of steps, when the database is busy, +%%% return rows in revers order +%try_multi_step(Statement, ChunkSize, Rest, Tries) -> +% try_multi_step(Statement, ChunkSize, Rest, Tries, ?DEFAULT_TIMEOUT). %% Try a number of steps, when the database is busy, %% return rows in revers order -try_multi_step(Statement, ChunkSize, Rest, Tries) -> - try_multi_step(Statement, ChunkSize, Rest, Tries, ?DEFAULT_TIMEOUT). +%-spec try_multi_step(statement(), pos_integer(), list(tuple()), non_neg_integer(), timeout()) -> +% {rows, list(tuple())} | +% {'$done', list(tuple())} | +% {error, term()}. +%try_multi_step(_Statement, _ChunkSize, _Rest, Tries, _Timeout) when Tries > 5 -> +% throw(too_many_tries); +%try_multi_step(Statement, ChunkSize, Rest, Tries, Timeout) -> +% case multi_step(Statement, ChunkSize, Timeout) of +% {'$busy', Rows} -> %% core can fetch a number of rows (rows < ChunkSize) per 'multi_step' call and then get busy... +% erlang:display({"busy", Tries}), +% timer:sleep(100 * Tries), +% try_multi_step(Statement, ChunkSize, Rows ++ Rest, Tries + 1, Timeout); +% {rows, Rows} -> +% {rows, Rows ++ Rest}; +% {'$done', Rows} -> +% {'$done', Rows ++ Rest}; +% Else -> Else +% end. -%% Try a number of steps, when the database is busy, -%% return rows in revers order --spec try_multi_step(statement(), pos_integer(), list(tuple()), non_neg_integer(), timeout()) -> - {rows, list(tuple())} | - {'$done', list(tuple())} | - {error, term()}. -try_multi_step(_Statement, _ChunkSize, _Rest, Tries, _Timeout) when Tries > 5 -> - throw(too_many_tries); -try_multi_step(Statement, ChunkSize, Rest, Tries, Timeout) -> - case multi_step(Statement, ChunkSize, Timeout) of - {'$busy', Rows} -> %% core can fetch a number of rows (rows < ChunkSize) per 'multi_step' call and then get busy... - erlang:display({"busy", Tries}), - timer:sleep(100 * Tries), - try_multi_step(Statement, ChunkSize, Rows ++ Rest, Tries + 1, Timeout); - {rows, Rows} -> - {rows, Rows ++ Rest}; - {'$done', Rows} -> - {'$done', Rows ++ Rest}; - Else -> Else - end. +%receive_answer(RawConnection, Ref, Timeout) -> +% receive +% {esqlite3, Ref, Resp} -> Resp +% after +% Timeout -> +% ok = esqlite3_nif:interrupt(RawConnection), +% throw({error, timeout, Ref}) +% end. -receive_answer(RawConnection, Ref, Timeout) -> - receive - {esqlite3, Ref, Resp} -> Resp - after - Timeout -> - ok = esqlite3_nif:interrupt(RawConnection), - throw({error, timeout, Ref}) - end. - -flush_answers() -> - receive - {esqlite3, _, _} -> flush_answers() - after - 0 -> ok - end. +%flush_answers() -> +% receive +% {esqlite3, _, _} -> flush_answers() +% after +% 0 -> ok +% end. diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index dbd0b81..74d2b37 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -21,34 +21,38 @@ %% low-level exports -export([ open/1, - set_update_hook/4, - exec/4, - changes/3, - insert/4, - last_insert_rowid/3, - get_autocommit/3, - prepare/4, - multi_step/5, - reset/4, - finalize/4, - bind/5, - column_names/4, - column_types/4, - backup_init/6, - backup_step/5, - backup_remaining/4, - backup_pagecount/4, - backup_finish/4, - interrupt/1, - close/3 + close/1, + prepare/2, + + interrupt/1 +% set_update_hook/4, +% exec/4, +% changes/3, +% insert/4, +% last_insert_rowid/3, +% get_autocommit/3, +% multi_step/5, +% reset/4, +% finalize/4, +% bind/5, + +% column_names/4, +% column_types/4, + +% backup_init/6, +% backup_step/5, +% backup_remaining/4, +% backup_pagecount/4, +% backup_finish/4, ]). --type raw_connection() :: reference(). --type raw_statement() :: reference(). --type raw_backup() :: reference(). +-type esqlite3() :: reference(). +-type esqlite3_stmt() :: reference(). +%-type esqlite3_backup() :: reference(). -type sql() :: iodata(). --export_type([raw_connection/0, raw_statement/0, raw_backup/0, sql/0]). +-export_type([esqlite3/0, esqlite3_stmt/0, sql/0]). +%-export_type([esqlite3_backup/0]). -on_load(init/0). @@ -64,15 +68,32 @@ init() -> %% @doc Open the specified sqlite3 database. %% -spec open(Filename) -> OpenResult - when Filename :: string() - OpenResult :: {ok, ref()} | {error, _}. + when Filename :: string(), + OpenResult :: {ok, esqlite3()} | {error, _}. open(_Filename) -> erlang:nif_error(nif_library_not_loaded). --spec set_update_hook(reference(), pid(), pid()) -> ok | {error, _}. -set_update_hook(_Db, _Ref, _Dest, _Pid) -> +%% @doc Close the connection. +%% +-spec close(Connection) -> CloseResult + when Connection :: esqlite3(), + CloseResult :: ok | {error, _}. +close(_Db) -> erlang:nif_error(nif_library_not_loaded). +%% @doc Compile a sql statement. +%% +-spec prepare(Connection, Sql) -> PrepareResult + when Connection :: esqlite3(), + Sql :: sql(), + PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. +prepare(_Connection, _Sql) -> + erlang:nif_error(nif_library_not_loaded). + +% -spec set_update_hook((), pid(), pid()) -> ok | {error, _}. +%set_update_hook(_Db, _Ref, _Dest, _Pid) -> +% erlang:nif_error(nif_library_not_loaded). + %% @doc Exec the query. %% %% Sends an asynchronous exec command over the connection and returns @@ -81,111 +102,99 @@ set_update_hook(_Db, _Ref, _Dest, _Pid) -> %% When the statement is executed Dest will receive message {Ref, answer()} %% with answer() integer | {error, reason()} %% --spec exec(raw_connection(), reference(), pid(), sql()) -> ok | {error, _}. -exec(_Db, _Ref, _Dest, _Sql) -> - erlang:nif_error(nif_library_not_loaded). +%-spec exec(esqlite3(), reference(), pid(), sql()) -> ok | {error, _}. +%%exec(_Db, _Ref, _Dest, _Sql) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Get the number of affected rows of last statement %% %% When the statement is executed Dest will receive message {Ref, answer()} %% with answer() integer | {error, reason()} --spec changes(raw_connection(), reference(), pid()) -> ok | {error, _}. -changes(_Db, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec changes(esqlite3(), reference(), pid()) -> ok | {error, _}. +%changes(_Db, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc %% --spec prepare(raw_connection(), reference(), pid(), sql()) -> ok | {error, _}. -prepare(_Db, _Ref, _Dest, _Sql) -> - erlang:nif_error(nif_library_not_loaded). +%-spec multi_step(esqlite3(), esqlite3_stmt(), pos_integer(), reference(), pid()) -> ok | {error, _}. +%multi_step(_Db, _Stmt, _Chunk_Size, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc %% --spec multi_step(raw_connection(), raw_statement(), pos_integer(), reference(), pid()) -> ok | {error, _}. -multi_step(_Db, _Stmt, _Chunk_Size, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec reset(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. +%reset(_Db, _Stmt, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc %% --spec reset(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}. -reset(_Db, _Stmt, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). - -%% @doc -%% --spec finalize(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}. -finalize(_Db, _Stmt, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec finalize(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. +%finalize(_Db, _Stmt, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Bind parameters to a prepared statement. %% --spec bind(raw_connection(), raw_statement(), reference(), pid(), list(any())) -> ok | {error, _}. -bind(_Db, _Stmt, _Ref, _Dest, _Args) -> - erlang:nif_error(nif_library_not_loaded). +%-spec bind(esqlite3(), esqlite3_stmt(), reference(), pid(), list(any())) -> ok | {error, _}. +%bind(_Db, _Stmt, _Ref, _Dest, _Args) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the column names of the prepared statement %% --spec column_names(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}. -column_names(_Db, _Stmt, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec column_names(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. +%column_names(_Db, _Stmt, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the column types of the prepared statement %% --spec column_types(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}. -column_types(_Db, _Stmt, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec column_types(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. +%column_types(_Db, _Stmt, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Initialize a backup procedure of a database. --spec backup_init(raw_connection(), string(), raw_connection(), string(), reference(), pid()) -> ok | {error, _}. -backup_init(_DestDb, _DestName, _SourceDb, _SourceName, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec backup_init(esqlite3(), string(), esqlite3_stmt(), string(), reference(), pid()) -> ok | {error, _}. +%backup_init(_DestDb, _DestName, _SourceDb, _SourceName, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Do a backup step. --spec backup_step(raw_connection(), raw_backup(), integer(), reference(), pid()) -> ok | {error, _}. -backup_step(_Db, _Backup, _NPages, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec backup_step(esqlite3(), esqlite3_backup(), integer(), reference(), pid()) -> ok | {error, _}. +%backup_step(_Db, _Backup, _NPages, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Get the amount of remaining pages which need to be backed up. --spec backup_remaining(raw_connection(), raw_backup(), reference(), pid()) -> ok | {error, _}. -backup_remaining(_Db, _Backup, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec backup_remaining(esqlite3(), esqlite3_backup(), reference(), pid()) -> ok | {error, _}. +%backup_remaining(_Db, _Backup, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Get the total number of pages which need to be backed up. --spec backup_pagecount(raw_connection(), raw_backup(), reference(), pid()) -> ok | {error, _}. -backup_pagecount(_Db, _Backup, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec backup_pagecount(esqlite3(), esqlite3_backup(), reference(), pid()) -> ok | {error, _}. +%backup_pagecount(_Db, _Backup, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Finish the backup. --spec backup_finish(raw_connection(), raw_backup(), reference(), pid()) -> ok | {error, _}. -backup_finish(_Db, _Backup, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec backup_finish(esqlite3(), esqlite3_backup(), reference(), pid()) -> ok | {error, _}. +%backup_finish(_Db, _Backup, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Interrupt all active queries. --spec interrupt(raw_connection()) -> ok. +-spec interrupt(esqlite3()) -> ok. interrupt(_Db) -> erlang:nif_error(nif_library_not_loaded). -%% @doc Close the connection. -%% --spec close(raw_connection(), reference(), pid()) -> ok | {error, _}. -close(_Db, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). - %% @doc Insert record %% --spec insert(raw_connection(), reference(), pid(), sql()) -> ok | {error, _}. -insert(_Db, _Ref, _Dest, _Sql) -> - erlang:nif_error(nif_library_not_loaded). +%-spec insert(esqlite3(), reference(), pid(), sql()) -> ok | {error, _}. +%insert(_Db, _Ref, _Dest, _Sql) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Get the last insert rowid. %% --spec last_insert_rowid(raw_connection(), reference(), pid()) -> ok | {error, _}. -last_insert_rowid(_Db, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec last_insert_rowid(esqlite3(), reference(), pid()) -> ok | {error, _}. +%last_insert_rowid(_Db, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). %% @doc Get automcommit %% --spec get_autocommit(raw_connection(), reference(), pid()) -> ok | {error, _}. -get_autocommit(_Db, _Ref, _Dest) -> - erlang:nif_error(nif_library_not_loaded). +%-spec get_autocommit(esqlite3(), reference(), pid()) -> ok | {error, _}. +%get_autocommit(_Db, _Ref, _Dest) -> +% erlang:nif_error(nif_library_not_loaded). From 953a12a20776b42c530e338291c109da0d50dce6 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Tue, 17 May 2022 23:06:25 +0200 Subject: [PATCH 03/37] Changed order of parameters --- c_src/esqlite3_nif.c | 2 +- src/esqlite3.erl | 24 +- test/esqlite_test.erl | 939 +++++++++++++++++++++--------------------- 3 files changed, 497 insertions(+), 468 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 13a2057..5dd7378 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -812,7 +812,7 @@ esqlite_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_raise_exception(env, make_atom(env, "not_thread_safe")); } - int size = enif_get_string(env, argv[1], filename, MAX_PATHNAME, ERL_NIF_LATIN1); + int size = enif_get_string(env, argv[0], filename, MAX_PATHNAME, ERL_NIF_LATIN1); if(size <= 0) { return make_error_tuple(env, "invalid_filename"); } diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 0102230..ab93360 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -19,7 +19,8 @@ %% higher-level export -export([ - open/1, close/1 + open/1, close/1, + prepare/2 % set_update_hook/2, set_update_hook/3, % exec/2, exec/3, exec/4, @@ -27,7 +28,6 @@ % insert/2, insert/3, % last_insert_rowid/1, % get_autocommit/1, get_autocommit/2, -% prepare/2, prepare/3, % step/1, step/2, % reset/1, % bind/2, bind/3, @@ -160,7 +160,8 @@ close(#esqlite3{db=Connection}) -> %%q(Sql, Args, Connection) -> % q(Sql, Args, Connection, ?DEFAULT_TIMEOUT). % -%%% @doc Execute statement, bind args and return a list with tuples as result restricted by timeout. + +%% @doc Execute statement, bind args and return a list with tuples as result restricted by timeout. %-spec q(sql(), list(), connection(), timeout()) -> list(row()) | {error, _}. %q(Sql, [], Connection, Timeout) -> % case prepare(Sql, Connection, Timeout) of @@ -376,18 +377,27 @@ close(#esqlite3{db=Connection}) -> % ok = esqlite3_nif:get_autocommit(RawConnection, Ref, self()), % receive_answer(RawConnection, Ref, Timeout). % + %% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in %% queries. %% -%-spec prepare(sql(), connection()) -> {ok, statement()} | {error, _}. -%prepare(Sql, Connection) -> -% prepare(Sql, Connection, ?DEFAULT_TIMEOUT). +-spec prepare(Connection, Sql) -> PrepareResult + when Connection :: esqlite3(), + Sql :: sql(), + PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. +prepare(#esqlite3{db=Connection}, Sql) -> + case esqlite3_nif:prepare(Connection, Sql) of + {ok, Stmt} -> + {ok, #esqlite3_stmt{db=Connection, stmt=Stmt}}; + {error, _}=Error -> + Error + end. + %% @doc Like prepare/2, but with an extra timeout value. %-spec prepare(sql(), connection(), timeout()) -> {ok, statement()} | {error, _}. %prepare(Sql, #connection{raw_connection=RawConnection}, Timeout) -> % Ref = make_ref(), -% ok = esqlite3_nif:prepare(RawConnection, Ref, self(), Sql), % case receive_answer(RawConnection, Ref, Timeout) of % {ok, Stmt} when is_reference(Stmt) -> % {ok, #statement{raw_statement=Stmt, raw_connection=RawConnection}}; diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 8a0aab4..e2b2050 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -19,23 +19,42 @@ close_test() -> {ok, C} = esqlite3:open(":memory:"), ok = esqlite3:close(C), + %% Double close should also work. + ok = esqlite3:close(C), + + ok. + + %% Check if functions still return sensible values. - {error, closed} = esqlite3:set_update_hook(self(), C), - {error, closed} = esqlite3:changes(C), - {error, closed} = esqlite3:get_autocommit(C), - {error, closed} = esqlite3:last_insert_rowid(C), +% {error, closed} = esqlite3:set_update_hook(self(), C), +% {error, closed} = esqlite3:changes(C), +% {error, closed} = esqlite3:get_autocommit(C), +% {error, closed} = esqlite3:last_insert_rowid(C), - {error, _} = esqlite3:exec("create table test(one, two, three)", C), +% {error, _} = esqlite3:exec("create table test(one, two, three)", C), - ok. +% ok. -iodata_test() -> + +prepare_test() -> {ok, C} = esqlite3:open(":memory:"), - {error, no_iodata} = esqlite3:exec(1000, C), - {error, no_iodata} = esqlite3:insert(1000, C), - + ?assertMatch({ok, {esqlite3_stmt, _, _}}, esqlite3:prepare(C, "select 1")), + ok = esqlite3:close(C), ok. +prepare_after_close_test() -> + {ok, C} = esqlite3:open(":memory:"), + ok = esqlite3:close(C), + ?assertMatch({error, {misuse, _}}, esqlite3:prepare(C, "select 1")), + ok. + + +%iodata_test() -> +% {ok, C} = esqlite3:open(":memory:"), +% {error, no_iodata} = esqlite3:exec(1000, C), +% {error, no_iodata} = esqlite3:insert(1000, C), + +% ok. open_multiple_same_databases_test() -> cleanup(), @@ -53,529 +72,529 @@ open_multiple_different_databases_test() -> cleanup(), ok. -get_autocommit_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), - true = esqlite3:get_autocommit(Db), - ok = esqlite3:exec("BEGIN;", Db), - false = esqlite3:get_autocommit(Db), - ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), - ok = esqlite3:exec("COMMIT;", Db), - true = esqlite3:get_autocommit(Db), - ok. +%get_autocommit_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), +% true = esqlite3:get_autocommit(Db), +% ok = esqlite3:exec("BEGIN;", Db), +% false = esqlite3:get_autocommit(Db), +% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), +% ok = esqlite3:exec("COMMIT;", Db), +% true = esqlite3:get_autocommit(Db), +% ok. -last_insert_rowid_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), - ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), - {ok, 1} = esqlite3:last_insert_rowid(Db), - ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is another test');", Db), - {ok, 2} = esqlite3:last_insert_rowid(Db), - ok. +%last_insert_rowid_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), +% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), +% {ok, 1} = esqlite3:last_insert_rowid(Db), +% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is another test');", Db), +% {ok, 2} = esqlite3:last_insert_rowid(Db), +% ok. -update_hook_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:set_update_hook(self(), Db), - ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), - ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), - ok = receive {insert, "test", 1} -> ok after 150 -> no_message end, - ok = esqlite3:exec("UPDATE test SET val = 'a new test' WHERE id = 1;", Db), - ok = receive {update, "test", 1} -> ok after 150 -> no_message end, - ok = esqlite3:exec("DELETE FROM test WHERE id = 1;", Db), - ok = receive {delete, "test", 1} -> ok after 150 -> no_message end, - ok. +%update_hook_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:set_update_hook(self(), Db), +% ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), +% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), +% ok = receive {insert, "test", 1} -> ok after 150 -> no_message end, +% ok = esqlite3:exec("UPDATE test SET val = 'a new test' WHERE id = 1;", Db), +% ok = receive {update, "test", 1} -> ok after 150 -> no_message end, +% ok = esqlite3:exec("DELETE FROM test WHERE id = 1;", Db), +% ok = receive {delete, "test", 1} -> ok after 150 -> no_message end, +% ok. -simple_query_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - {ok, 1} = esqlite3:changes(Db), +%simple_query_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% {ok, 1} = esqlite3:changes(Db), +% +% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), +% {ok, 1} = esqlite3:changes(Db), +% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), +% {ok, 1} = esqlite3:changes(Db), +% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% {ok, 1} = esqlite3:changes(Db), +% ok = esqlite3:exec("commit;", Db), +% ok = esqlite3:exec("select * from test_table;", Db), +% +% ok = esqlite3:exec("delete from test_table;", Db), +% {ok, 4} = esqlite3:changes(Db), - ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), - {ok, 1} = esqlite3:changes(Db), - ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), - {ok, 1} = esqlite3:changes(Db), - ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), - {ok, 1} = esqlite3:changes(Db), - ok = esqlite3:exec("commit;", Db), - ok = esqlite3:exec("select * from test_table;", Db), - - ok = esqlite3:exec("delete from test_table;", Db), - {ok, 4} = esqlite3:changes(Db), - - ok. - -prepare_test() -> - {ok, Db} = esqlite3:open(":memory:"), - esqlite3:exec("begin;", Db), - esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - {ok, Statement} = esqlite3:prepare("insert into test_table values('one', 2)", Db), - - '$done' = esqlite3:step(Statement), - {ok, 1} = esqlite3:changes(Db), - - ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% ok. +%prepare_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% esqlite3:exec("begin;", Db), +% esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% {ok, Statement} = esqlite3:prepare("insert into test_table values('one', 2)", Db), +% +% '$done' = esqlite3:step(Statement), +% {ok, 1} = esqlite3:changes(Db), +% +% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% %% Check if the values are there. - [{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q("select * from test_table order by two", Db), - esqlite3:exec("commit;", Db), - esqlite3:close(Db), +% [{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q("select * from test_table order by two", Db), +% esqlite3:exec("commit;", Db), +% esqlite3:close(Db), - ok. +% ok. -bind_test() -> - {ok, Db} = esqlite3:open(":memory:"), - - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("commit;", Db), - - %% Create a prepared statement - {ok, Statement} = esqlite3:prepare("insert into test_table values(?1, ?2)", Db), - esqlite3:bind(Statement, [one, 2]), - esqlite3:step(Statement), - esqlite3:bind(Statement, ["three", 4]), - esqlite3:step(Statement), - esqlite3:bind(Statement, ["five", 6]), - esqlite3:step(Statement), - esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text - esqlite3:step(Statement), - esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text - esqlite3:step(Statement), - esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos. - esqlite3:step(Statement), - esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null - esqlite3:step(Statement), +%bind_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("commit;", Db), +% +% %% Create a prepared statement +% {ok, Statement} = esqlite3:prepare("insert into test_table values(?1, ?2)", Db), +%% esqlite3:bind(Statement, [one, 2]), +% esqlite3:step(Statement), +% esqlite3:bind(Statement, ["three", 4]), +% esqlite3:step(Statement), +% esqlite3:bind(Statement, ["five", 6]), +% esqlite3:step(Statement), +% esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text +% esqlite3:step(Statement), +% esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text +% esqlite3:step(Statement), +% esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos. +% esqlite3:step(Statement), +% esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null +% esqlite3:step(Statement), %% int64 - esqlite3:bind(Statement, [int64, 308553449069486081]), - esqlite3:step(Statement), - +% esqlite3:bind(Statement, [int64, 308553449069486081]), +% esqlite3:step(Statement), +% %% negative int64 - esqlite3:bind(Statement, [negative_int64, -308553449069486081]), - esqlite3:step(Statement), +% esqlite3:bind(Statement, [negative_int64, -308553449069486081]), +% esqlite3:step(Statement), %% utf-8 - esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]), - esqlite3:step(Statement), +% esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]), +% esqlite3:step(Statement), - ?assertEqual([{<<"one">>, 2}], - esqlite3:q("select one, two from test_table where two = '2'", Db)), - ?assertEqual([{<<"three">>, 4}], - esqlite3:q("select one, two from test_table where two = 4", Db)), - ?assertEqual([{<<"five">>, 6}], - esqlite3:q("select one, two from test_table where two = 6", Db)), - ?assertEqual([{<<"seven">>, 8}], - esqlite3:q("select one, two from test_table where two = 8", Db)), - ?assertEqual([{<<"nine">>, 10}], - esqlite3:q("select one, two from test_table where two = 10", Db)), - ?assertEqual([{{blob, <<$e,$l,$e,$v,$e,$n,0>>}, 12}], - esqlite3:q("select one, two from test_table where two = 12", Db)), - ?assertEqual([{<<"empty">>, undefined}], - esqlite3:q("select one, two from test_table where two is null", Db)), +% ?assertEqual([{<<"one">>, 2}], +% esqlite3:q("select one, two from test_table where two = '2'", Db)), +% ?assertEqual([{<<"three">>, 4}], +% esqlite3:q("select one, two from test_table where two = 4", Db)), +% ?assertEqual([{<<"five">>, 6}], +% esqlite3:q("select one, two from test_table where two = 6", Db)), +% ?assertEqual([{<<"seven">>, 8}], +% esqlite3:q("select one, two from test_table where two = 8", Db)), +% ?assertEqual([{<<"nine">>, 10}], +% esqlite3:q("select one, two from test_table where two = 10", Db)), +% ?assertEqual([{{blob, <<$e,$l,$e,$v,$e,$n,0>>}, 12}], +% esqlite3:q("select one, two from test_table where two = 12", Db)), +% ?assertEqual([{<<"empty">>, undefined}], +% esqlite3:q("select one, two from test_table where two is null", Db)), - ?assertEqual([{<<"int64">>, 308553449069486081}], - esqlite3:q("select one, two from test_table where one = 'int64';", Db)), - ?assertEqual([{<<"negative_int64">>, -308553449069486081}], - esqlite3:q("select one, two from test_table where one = 'negative_int64';", Db)), +% ?assertEqual([{<<"int64">>, 308553449069486081}], +% esqlite3:q("select one, two from test_table where one = 'int64';", Db)), +% ?assertEqual([{<<"negative_int64">>, -308553449069486081}], +% esqlite3:q("select one, two from test_table where one = 'negative_int64';", Db)), %% utf-8 - ?assertEqual([{<<228,184,138,230,181,183>>, 100}], - esqlite3:q("select one, two from test_table where two = 100", Db)), +% ?assertEqual([{<<228,184,138,230,181,183>>, 100}], +% esqlite3:q("select one, two from test_table where two = 100", Db)), +% +% ok. - ok. +%bind_for_queries_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("commit;", Db), -bind_for_queries_test() -> - {ok, Db} = esqlite3:open(":memory:"), +% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, +% [test_table], Db)), +% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, +% ["test_table"], Db)), +% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, +% [<<"test_table">>], Db)), +% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, +% [[<<"test_table">>]], Db)), +% +% ok. - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("commit;", Db), - - ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, - [test_table], Db)), - ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, - ["test_table"], Db)), - ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, - [<<"test_table">>], Db)), - ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, - [[<<"test_table">>]], Db)), - - ok. - -column_names_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db), - ok = esqlite3:exec("commit;", Db), +%column_names_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db), +% ok = esqlite3:exec("commit;", Db), %% All columns - {ok, Stmt} = esqlite3:prepare("select * from test_table", Db), - {one, two} = esqlite3:column_names(Stmt), - {row, {<<"hello1">>, 10}} = esqlite3:step(Stmt), - {one, two} = esqlite3:column_names(Stmt), - {row, {<<"hello2">>, 20}} = esqlite3:step(Stmt), - {one, two} = esqlite3:column_names(Stmt), - '$done' = esqlite3:step(Stmt), - {one, two} = esqlite3:column_names(Stmt), +% {ok, Stmt} = esqlite3:prepare("select * from test_table", Db), +% {one, two} = esqlite3:column_names(Stmt), +% {row, {<<"hello1">>, 10}} = esqlite3:step(Stmt), +% {one, two} = esqlite3:column_names(Stmt), +% {row, {<<"hello2">>, 20}} = esqlite3:step(Stmt), +% {one, two} = esqlite3:column_names(Stmt), +% '$done' = esqlite3:step(Stmt), +% {one, two} = esqlite3:column_names(Stmt), %% One column - {ok, Stmt2} = esqlite3:prepare("select two from test_table", Db), - {two} = esqlite3:column_names(Stmt2), - {row, {10}} = esqlite3:step(Stmt2), - {two} = esqlite3:column_names(Stmt2), - {row, {20}} = esqlite3:step(Stmt2), - {two} = esqlite3:column_names(Stmt2), - '$done' = esqlite3:step(Stmt2), - {two} = esqlite3:column_names(Stmt2), +% {ok, Stmt2} = esqlite3:prepare("select two from test_table", Db), +% {two} = esqlite3:column_names(Stmt2), +% {row, {10}} = esqlite3:step(Stmt2), +% {two} = esqlite3:column_names(Stmt2), +% {row, {20}} = esqlite3:step(Stmt2), +% {two} = esqlite3:column_names(Stmt2), +% '$done' = esqlite3:step(Stmt2), +% {two} = esqlite3:column_names(Stmt2), %% No columns - {ok, Stmt3} = esqlite3:prepare("values(1);", Db), - {column1} = esqlite3:column_names(Stmt3), - {row, {1}} = esqlite3:step(Stmt3), - {column1} = esqlite3:column_names(Stmt3), +% {ok, Stmt3} = esqlite3:prepare("values(1);", Db), +% {column1} = esqlite3:column_names(Stmt3), +% {row, {1}} = esqlite3:step(Stmt3), +% {column1} = esqlite3:column_names(Stmt3), %% Things get a bit weird when you retrieve the column name %% when calling an aggragage function. - {ok, Stmt4} = esqlite3:prepare("select date('now');", Db), - {'date(\'now\')'} = esqlite3:column_names(Stmt4), - {row, {Date}} = esqlite3:step(Stmt4), - true = is_binary(Date), +%% {ok, Stmt4} = esqlite3:prepare("select date('now');", Db), +% {'date(\'now\')'} = esqlite3:column_names(Stmt4), +% {row, {Date}} = esqlite3:step(Stmt4), +% true = is_binary(Date), %% Some statements have no column names - {ok, Stmt5} = esqlite3:prepare("create table dummy(a, b, c);", Db), - {} = esqlite3:column_names(Stmt5), - - ok. - -column_types_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db), - ok = esqlite3:exec("commit;", Db), +% {ok, Stmt5} = esqlite3:prepare("create table dummy(a, b, c);", Db), +% {} = esqlite3:column_names(Stmt5), +% +% ok. +%column_types_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db), +% ok = esqlite3:exec("commit;", Db), +% %% All columns - {ok, Stmt} = esqlite3:prepare("select * from test_table", Db), - ?assertEqual({'varchar(10)', 'INT'}, esqlite3:column_types(Stmt)), - {row, {<<"hello1">>, 10}} = esqlite3:step(Stmt), - {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), - {row, {<<"hello2">>, 20}} = esqlite3:step(Stmt), - {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), - '$done' = esqlite3:step(Stmt), - {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), +% {ok, Stmt} = esqlite3:prepare("select * from test_table", Db), +% ?assertEqual({'varchar(10)', 'INT'}, esqlite3:column_types(Stmt)), +% {row, {<<"hello1">>, 10}} = esqlite3:step(Stmt), +% {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), +% {row, {<<"hello2">>, 20}} = esqlite3:step(Stmt), +% {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), +% '$done' = esqlite3:step(Stmt), +% {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), +% +% %% Some statements have no column types +% {ok, Stmt2} = esqlite3:prepare("create table dummy(a, b, c);", Db), +% {} = esqlite3:column_types(Stmt2), +% +% ok. +% +%nil_column_types_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table t1(c1 variant);", Db), +% ok = esqlite3:exec("commit;", Db), - %% Some statements have no column types - {ok, Stmt2} = esqlite3:prepare("create table dummy(a, b, c);", Db), - {} = esqlite3:column_types(Stmt2), +% {ok, Stmt} = esqlite3:prepare("select c1 + 1, c1 from t1", Db), +% {nil, variant} = esqlite3:column_types(Stmt), +% ok. - ok. - -nil_column_types_test() -> - {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table t1(c1 variant);", Db), - ok = esqlite3:exec("commit;", Db), - - {ok, Stmt} = esqlite3:prepare("select c1 + 1, c1 from t1", Db), - {nil, variant} = esqlite3:column_types(Stmt), - ok. - -reset_test() -> - {ok, Db} = esqlite3:open(":memory:"), - - {ok, Stmt} = esqlite3:prepare("select * from (values (1), (2));", Db), - {row, {1}} = esqlite3:step(Stmt), - - ok = esqlite3:reset(Stmt), - {row, {1}} = esqlite3:step(Stmt), - {row, {2}} = esqlite3:step(Stmt), - '$done' = esqlite3:step(Stmt), - - % After a done the statement is automatically reset. - {row, {1}} = esqlite3:step(Stmt), - - % Calling reset multiple times... - ok = esqlite3:reset(Stmt), - ok = esqlite3:reset(Stmt), - ok = esqlite3:reset(Stmt), - ok = esqlite3:reset(Stmt), +%reset_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% {ok, Stmt} = esqlite3:prepare("select * from (values (1), (2));", Db), +% {row, {1}} = esqlite3:step(Stmt), +% +% ok = esqlite3:reset(Stmt), +% {row, {1}} = esqlite3:step(Stmt), +% {row, {2}} = esqlite3:step(Stmt), +% '$done' = esqlite3:step(Stmt), +% +% % After a done the statement is automatically reset. +% {row, {1}} = esqlite3:step(Stmt), +% +% % Calling reset multiple times... +% ok = esqlite3:reset(Stmt), +% ok = esqlite3:reset(Stmt), +% ok = esqlite3:reset(Stmt), +% ok = esqlite3:reset(Stmt), +% % The statement should still be reset. - {row, {1}} = esqlite3:step(Stmt), +% {row, {1}} = esqlite3:step(Stmt), +% +% ok. +% +%foreach_test() -> +% {ok, Db} = esqlite3:open(":memory:"), - ok. +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% +% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), +% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), +% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% +% ok = esqlite3:exec("commit;", Db), -foreach_test() -> - {ok, Db} = esqlite3:open(":memory:"), +% F = fun(Row) -> +% case Row of +% {Key, Value} -> +% put(Key, Value); +% _ -> +% ok +% end +% end, +% +% esqlite3:foreach(F, "select * from test_table;", Db), +% +% 10 = get(<<"hello1">>), +% 11 = get(<<"hello2">>), +% 12 = get(<<"hello3">>), +% 13 = get(<<"hello4">>), +% +% ok. +% +%bind_for_foreach_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), +% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), +% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% ok = esqlite3:exec("commit;", Db), - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% F = fun(Row) -> +% case Row of +% {Key, Value} -> +% put(Key, Value); +%% _ -> +% ok +% end +% end, - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), - ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), - ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% esqlite3:foreach(F, "select * from test_table where one = ?;", ["hello1"], Db), +% +% 10 = get(<<"hello1">>), +% +% ok. - ok = esqlite3:exec("commit;", Db), +%map_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +%% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), +% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), +% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% ok = esqlite3:exec("commit;", Db), - F = fun(Row) -> - case Row of - {Key, Value} -> - put(Key, Value); - _ -> - ok - end - end, - - esqlite3:foreach(F, "select * from test_table;", Db), - - 10 = get(<<"hello1">>), - 11 = get(<<"hello2">>), - 12 = get(<<"hello3">>), - 13 = get(<<"hello4">>), - - ok. - -bind_for_foreach_test() -> - {ok, Db} = esqlite3:open(":memory:"), - - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), - ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), - ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), - ok = esqlite3:exec("commit;", Db), - - F = fun(Row) -> - case Row of - {Key, Value} -> - put(Key, Value); - _ -> - ok - end - end, - - esqlite3:foreach(F, "select * from test_table where one = ?;", ["hello1"], Db), - - 10 = get(<<"hello1">>), - - ok. - -map_test() -> - {ok, Db} = esqlite3:open(":memory:"), - - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), - ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), - ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), - ok = esqlite3:exec("commit;", Db), - - F = fun(Row) -> Row end, - - [{<<"hello1">>,10}, - {<<"hello2">>,11}, - {<<"hello3">>,12}, - {<<"hello4">>,13}] = esqlite3:map(F, "select * from test_table", Db), +% F = fun(Row) -> Row end, +% +% [{<<"hello1">>,10}, +% {<<"hello2">>,11}, +% {<<"hello3">>,12}, +% {<<"hello4">>,13}] = esqlite3:map(F, "select * from test_table", Db), %% Test that when the row-names are added.. - Assoc = fun(Names, Row) -> - lists:zip(tuple_to_list(Names), tuple_to_list(Row)) - end, +% Assoc = fun(Names, Row) -> +% lists:zip(tuple_to_list(Names), tuple_to_list(Row)) +% end, +% +% [[{one,<<"hello1">>},{two,10}], +% [{one,<<"hello2">>},{two,11}], +% [{one,<<"hello3">>},{two,12}], +% [{one,<<"hello4">>},{two,13}]] = esqlite3:map(Assoc, "select * from test_table", Db), +% +% ok. - [[{one,<<"hello1">>},{two,10}], - [{one,<<"hello2">>},{two,11}], - [{one,<<"hello3">>},{two,12}], - [{one,<<"hello4">>},{two,13}]] = esqlite3:map(Assoc, "select * from test_table", Db), +%bind_for_map_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% +% ok = esqlite3:exec("begin;", Db), +% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), +% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), +% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), +% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), +% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), +% ok = esqlite3:exec("commit;", Db), - ok. - -bind_for_map_test() -> - {ok, Db} = esqlite3:open(":memory:"), - - ok = esqlite3:exec("begin;", Db), - ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), - ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), - ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), - ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), - ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), - ok = esqlite3:exec("commit;", Db), - - F = fun(Row) -> Row end, - - [{<<"hello1">>,10}] - = esqlite3:map(F, "select * from test_table where one = ?", ["hello1"], Db), - - %% Test that when the row-names are added.. - Assoc = fun(Names, Row) -> - lists:zip(tuple_to_list(Names), tuple_to_list(Row)) - end, - - [[{one,<<"hello1">>},{two,10}]] = esqlite3:map(Assoc, "select * from test_table where one = ?", ["hello1"], Db), - - ok. +% F = fun(Row) -> Row end, +% +% [{<<"hello1">>,10}] +% = esqlite3:map(F, "select * from test_table where one = ?", ["hello1"], Db), +% +% %% Test that when the row-names are added.. +% Assoc = fun(Names, Row) -> +% lists:zip(tuple_to_list(Names), tuple_to_list(Row)) +% end, +% +% [[{one,<<"hello1">>},{two,10}]] = esqlite3:map(Assoc, "select * from test_table where one = ?", ["hello1"], Db), +% +% ok. -error1_msg_test() -> - {ok, Db} = esqlite3:open(":memory:"), - +%error1_msg_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% %% Not sql. - {error, {sqlite_error, _Msg1}} = esqlite3:exec("dit is geen sql", Db), - - %% Database test does not exist. - {error, {sqlite_error, _Msg2}} = esqlite3:exec("select * from test;", Db), - - %% Opening non-existant database. - {error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"), - - ok. - -prepare_and_close_connection_test() -> - {ok, Db} = esqlite3:open(":memory:"), - - [] = esqlite3:q("create table test(one, two, three)", Db), - ok = esqlite3:exec(["insert into test values(1,2,3);"], Db), - {ok, Stmt} = esqlite3:prepare("select * from test", Db), - - %% The prepated statment works. - {row, {1,2,3}} = esqlite3:step(Stmt), - '$done' = esqlite3:step(Stmt), - - ok = esqlite3:close(Db), - - ok = esqlite3:reset(Stmt), +% {error, {sqlite_error, _Msg1}} = esqlite3:exec("dit is geen sql", Db), +% +% %% Database test does not exist. +% {error, {sqlite_error, _Msg2}} = esqlite3:exec("select * from test;", Db), +% +% %% Opening non-existant database. +% {error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"), +% +% ok. +%prepare_and_close_connection_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% +% [] = esqlite3:q("create table test(one, two, three)", Db), +% ok = esqlite3:exec(["insert into test values(1,2,3);"], Db), +% {ok, Stmt} = esqlite3:prepare("select * from test", Db), +% +% %% The prepated statment works. +% {row, {1,2,3}} = esqlite3:step(Stmt), +% '$done' = esqlite3:step(Stmt), +% +% ok = esqlite3:close(Db), +% +% ok = esqlite3:reset(Stmt), +% %% Internally sqlite3_close_v2 is used by the nif. This will destruct the %% connection when the last perpared statement is finalized - {row, {1,2,3}} = esqlite3:step(Stmt), - '$done' = esqlite3:step(Stmt), +% {row, {1,2,3}} = esqlite3:step(Stmt), +% '$done' = esqlite3:step(Stmt), - ok. +% ok. -backup_test() -> - cleanup(), +%backup_test() -> +% cleanup(), +% +% {ok, Dest} = esqlite3:open(?DB1), +% {ok, Source} = esqlite3:open(?DB2), +% +% {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), +% {ok, 0} = esqlite3:backup_remaining(Backup), +% {ok, 0} = esqlite3:backup_pagecount(Backup), +% done = esqlite3:backup_step(Backup, 1), +% +% cleanup(), +% +% ok. - {ok, Dest} = esqlite3:open(?DB1), - {ok, Source} = esqlite3:open(?DB2), +%backup1_test() -> +% cleanup(), +% +% {ok, Dest} = esqlite3:open(?DB1), +% {ok, Source} = esqlite3:open(?DB2), - {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), - {ok, 0} = esqlite3:backup_remaining(Backup), - {ok, 0} = esqlite3:backup_pagecount(Backup), - done = esqlite3:backup_step(Backup, 1), +% [] = esqlite3:q("create table test(one, two)", Source), +% [] = esqlite3:q("begin;", Source), +% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), +% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), +% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), +% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), +% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), +% [] = esqlite3:q("commit;", Source), +% +% [{5}] = esqlite3:q("select count(*) from test", Source), +% {error, {sqlite_error, "no such table: test"}} = esqlite3:q("select count(*) from test", Dest), - cleanup(), +% {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), - ok. - -backup1_test() -> - cleanup(), - - {ok, Dest} = esqlite3:open(?DB1), - {ok, Source} = esqlite3:open(?DB2), - - [] = esqlite3:q("create table test(one, two)", Source), - [] = esqlite3:q("begin;", Source), - [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), - [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), - [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), - [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), - [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), - [] = esqlite3:q("commit;", Source), - - [{5}] = esqlite3:q("select count(*) from test", Source), - {error, {sqlite_error, "no such table: test"}} = esqlite3:q("select count(*) from test", Dest), - - {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), - - {ok, 0} = esqlite3:backup_remaining(Backup), - {ok, 0} = esqlite3:backup_pagecount(Backup), +% {ok, 0} = esqlite3:backup_remaining(Backup), +% {ok, 0} = esqlite3:backup_pagecount(Backup), %% Backup 1 page. - ok = esqlite3:backup_step(Backup, 1), - - {ok, 26} = esqlite3:backup_remaining(Backup), - {ok, 27} = esqlite3:backup_pagecount(Backup), +% ok = esqlite3:backup_step(Backup, 1), +% +% {ok, 26} = esqlite3:backup_remaining(Backup), +% {ok, 27} = esqlite3:backup_pagecount(Backup), %% Do all the remaining pages. - done = esqlite3:backup_step(Backup, -1), +%% done = esqlite3:backup_step(Backup, -1), - {ok, 0} = esqlite3:backup_remaining(Backup), - {ok, 27} = esqlite3:backup_pagecount(Backup), +% {ok, 0} = esqlite3:backup_remaining(Backup), +% {ok, 27} = esqlite3:backup_pagecount(Backup), - ok = esqlite3:backup_finish(Backup), - - [{5}] = esqlite3:q("select count(*) from test", Dest), - - cleanup(), - - ok. +% ok = esqlite3:backup_finish(Backup), +% +% [{5}] = esqlite3:q("select count(*) from test", Dest), +% +% cleanup(), +% +% ok. -sqlite_version_test() -> - {ok, Db} = esqlite3:open(":memory:"), - {ok, Stmt} = esqlite3:prepare("select sqlite_version() as sqlite_version;", Db), - {sqlite_version} = esqlite3:column_names(Stmt), - ?assertEqual({row, {<<"3.38.0">>}}, esqlite3:step(Stmt)), - ok. +%sqlite_version_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% {ok, Stmt} = esqlite3:prepare("select sqlite_version() as sqlite_version;", Db), +% {sqlite_version} = esqlite3:column_names(Stmt), +%% ?assertEqual({row, {<<"3.38.0">>}}, esqlite3:step(Stmt)), +% ok. -sqlite_source_id_test() -> - {ok, Db} = esqlite3:open(":memory:"), - {ok, Stmt} = esqlite3:prepare("select sqlite_source_id() as sqlite_source_id;", Db), - {sqlite_source_id} = esqlite3:column_names(Stmt), - ?assertEqual({row, {<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>}}, - esqlite3:step(Stmt)), - ok. +%sqlite_source_id_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% {ok, Stmt} = esqlite3:prepare("select sqlite_source_id() as sqlite_source_id;", Db), +% {sqlite_source_id} = esqlite3:column_names(Stmt), +% ?assertEqual({row, {<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>}}, +% esqlite3:step(Stmt)), +% ok. -interrupt_on_timeout_test() -> - {ok, Db} = esqlite3:open(":memory:"), - CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);", - ok = esqlite3:exec(CreateTableQuery, Db), - VeryLongQuery = " - WITH RECURSIVE - for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000) - INSERT INTO all_numbers_in_the_world SELECT i FROM for; - ", - try - ok = esqlite3:exec(VeryLongQuery, [], Db, 10) - catch - {error, timeout, _} -> - ?assertMatch([{0}], esqlite3:q("SELECT COUNT(*) FROM all_numbers_in_the_world", Db)), - %% There is now a stale answer, because the recursive query was interrupted. - receive - {esqlite3, _, {error, {interrupt, "interrupted"}}} -> - ok - end - end. +%interrupt_on_timeout_test() -> +% {ok, Db} = esqlite3:open(":memory:"), +% CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);", +% ok = esqlite3:exec(CreateTableQuery, Db), +% VeryLongQuery = " +% WITH RECURSIVE +% for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000) +% INSERT INTO all_numbers_in_the_world SELECT i FROM for; +% ", +% try +% ok = esqlite3:exec(VeryLongQuery, [], Db, 10) +% catch +% {error, timeout, _} -> +% ?assertMatch([{0}], esqlite3:q("SELECT COUNT(*) FROM all_numbers_in_the_world", Db)), +% %% There is now a stale answer, because the recursive query was interrupted. +% receive +% {esqlite3, _, {error, {interrupt, "interrupted"}}} -> +% ok +% end +% end. -garbage_collect_test() -> - F = fun() -> - {ok, Db} = esqlite3:open(":memory:"), - [] = esqlite3:q("create table test(one, two, three)", Db), - [] = esqlite3:q("insert into test values(1, '2', 3.0)", Db), - {ok, Stmt} = esqlite3:prepare("select * from test", Db), - {row, {1, <<"2">>, 3.0}} = esqlite3:step(Stmt), - '$done' = esqlite3:step(Stmt), - ok = esqlite3:close(Db) - end, - - [spawn(F) || _X <- lists:seq(0,30)], - receive after 500 -> ok end, - erlang:garbage_collect(), - - [spawn(F) || _X <- lists:seq(0,30)], - receive after 500 -> ok end, - erlang:garbage_collect(), +%garbage_collect_test() -> +% F = fun() -> +% {ok, Db} = esqlite3:open(":memory:"), +% [] = esqlite3:q("create table test(one, two, three)", Db), +% [] = esqlite3:q("insert into test values(1, '2', 3.0)", Db), +% {ok, Stmt} = esqlite3:prepare("select * from test", Db), +% {row, {1, <<"2">>, 3.0}} = esqlite3:step(Stmt), +% '$done' = esqlite3:step(Stmt), +% ok = esqlite3:close(Db) +% end, +% +% [spawn(F) || _X <- lists:seq(0,30)], +% receive after 500 -> ok end, +% erlang:garbage_collect(), +% +% [spawn(F) || _X <- lists:seq(0,30)], +% receive after 500 -> ok end, +% erlang:garbage_collect(), - ok. +% ok. %% %% Helpers From 616127c748155967494a885c45548854d14f1306 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Wed, 18 May 2022 00:15:29 +0200 Subject: [PATCH 04/37] Added column names --- c_src/esqlite3_nif.c | 55 +++++++++++++++++++++++-------------------- src/esqlite3.erl | 17 +++++-------- src/esqlite3_nif.erl | 9 +++---- test/esqlite_test.erl | 18 ++++++++++++++ 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 5dd7378..d1972bb 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -440,7 +440,6 @@ do_get_autocommit(ErlNifEnv *env, esqlite3 *conn) } */ -/* static ERL_NIF_TERM make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) { @@ -457,7 +456,6 @@ make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) return term; } -*/ /* static ERL_NIF_TERM @@ -1217,39 +1215,43 @@ esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) /* * Get the column names of the prepared statement. + */ static ERL_NIF_TERM esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite3 *conn; esqlite3_stmt *stmt; - esqlite_command *cmd = NULL; - ErlNifPid pid; - if(argc != 4) + if(argc != 1) { return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); - if(!stmt->statement) - return make_error_tuple(env, "no_prepared_statement"); + } - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } - cmd->type = cmd_column_names; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->stmt = enif_make_copy(cmd->env, argv[1]); + ERL_NIF_TERM column_names = enif_make_list(env, 0); - return push_command(env, conn, cmd); + int size = sqlite3_column_count(stmt->statement); + if(size < 0) { + return enif_raise_exception(env, make_atom(env, "invalid_column_count")); + } + + for(int i=size; i-- > 0; ) { + const char *name = sqlite3_column_name(stmt->statement, i); + if(name == NULL) { + return enif_raise_exception(env, make_atom(env, "sqlite3_malloc_failure")); + } + + ERL_NIF_TERM ename = make_binary(env, name, strlen(name)); + column_names = enif_make_list_cell(env, ename, column_names); + } + + return column_names; } - */ /* * Get the column types of the prepared statement. @@ -1533,6 +1535,9 @@ static ErlNifFunc nif_funcs[] = { {"prepare", 2, esqlite_prepare}, + {"column_names", 1, esqlite_column_names}, + // {"column_types", 1, esqlite_column_types}, + {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND} /* @@ -1548,8 +1553,6 @@ static ErlNifFunc nif_funcs[] = { // TODO: {"esqlite_bind", 3, esqlite_bind_named}, {"bind", 5, esqlite_bind}, - {"column_names", 4, esqlite_column_names}, - {"column_types", 4, esqlite_column_types}, {"backup_init", 6, esqlite_backup_init}, {"backup_step", 5, esqlite_backup_step}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index ab93360..629750c 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -20,7 +20,9 @@ %% higher-level export -export([ open/1, close/1, - prepare/2 + prepare/2, + + column_names/1 % set_update_hook/2, set_update_hook/3, % exec/2, exec/3, exec/4, @@ -33,7 +35,6 @@ % bind/2, bind/3, % fetchone/1, % fetchall/1, fetchall/2, fetchall/3, -% column_names/1, column_names/2, % column_types/1, column_types/2, % backup_init/4, backup_init/5, % backup_finish/1, backup_finish/2, @@ -447,15 +448,9 @@ prepare(#esqlite3{db=Connection}, Sql) -> % %% @doc Return the column names of the prepared statement. %% -%-spec column_names(statement()) -> {atom()}. -%column_names(Stmt) -> -% column_names(Stmt, ?DEFAULT_TIMEOUT). - -%-spec column_names(statement(), timeout()) -> {atom()}. -%column_names(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:column_names(RawConnection, RawStatement, Ref, self()), -% receive_answer(RawConnection, Ref, Timeout). +-spec column_names(esqlite3_stmt()) -> [binary()]. +column_names(#esqlite3_stmt{stmt=Stmt}) -> + esqlite3_nif:column_names(Stmt). %% @doc Return the column types of the prepared statement. %% diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 74d2b37..2498a24 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -24,6 +24,8 @@ close/1, prepare/2, + column_names/1, + interrupt/1 % set_update_hook/4, % exec/4, @@ -36,7 +38,6 @@ % finalize/4, % bind/5, -% column_names/4, % column_types/4, % backup_init/6, @@ -140,9 +141,9 @@ prepare(_Connection, _Sql) -> %% @doc Retrieve the column names of the prepared statement %% -%-spec column_names(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. -%column_names(_Db, _Stmt, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +-spec column_names(esqlite3_stmt()) -> list() | {error, _}. +column_names(_Stmt) -> + erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the column types of the prepared statement %% diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index e2b2050..523d5cc 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -48,6 +48,24 @@ prepare_after_close_test() -> ?assertMatch({error, {misuse, _}}, esqlite3:prepare(C, "select 1")), ok. +column_names_test() -> + {ok, C} = esqlite3:open(":memory:"), + + {ok, Stmt} = esqlite3:prepare(C, "select 1 as one"), + ?assertEqual([<<"one">>], esqlite3:column_names(Stmt)), + + {ok, Stmt1} = esqlite3:prepare(C, <<"select 1 as 😀"/utf8>>), + ?assertEqual([<<"😀"/utf8>>], esqlite3:column_names(Stmt1)), + + {ok, Stmt2} = esqlite3:prepare(C, <<"select 1">>), + ?assertEqual([<<"1">>], esqlite3:column_names(Stmt2)), + + {ok, Stmt3} = esqlite3:prepare(C, <<"select 1, 2, 3">>), + ?assertEqual([<<"1">>, <<"2">>, <<"3">>], esqlite3:column_names(Stmt3)), + + ok. + + %iodata_test() -> % {ok, C} = esqlite3:open(":memory:"), From 77cea7e9022353de04dbdfd2c333f1fb4904b3d7 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 19 May 2022 08:21:38 +0200 Subject: [PATCH 05/37] Turn on sqlite's extended error codes --- c_src/esqlite3_nif.c | 221 +++++++++++++++++------------------------- rebar.config.script | 2 +- src/esqlite3.erl | 20 ++-- src/esqlite3_nif.erl | 12 ++- test/esqlite_test.erl | 14 ++- 5 files changed, 121 insertions(+), 148 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index d1972bb..081e1c0 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -47,33 +47,6 @@ typedef struct { sqlite3_backup *backup; } esqlite3_backup; -/* -typedef enum { - cmd_unknown, - cmd_open, - cmd_update_hook_set, - cmd_notification, - cmd_exec, - cmd_changes, - cmd_prepare, - cmd_bind, - cmd_multi_step, - cmd_reset, - cmd_column_names, - cmd_column_types, - cmd_backup_init, - cmd_backup_step, - cmd_backup_remaining, - cmd_backup_pagecount, - cmd_backup_finish, - cmd_close, - cmd_stop, - cmd_insert, - cmd_last_insert_rowid, - cmd_get_autocommit, -} command_type; -*/ - static ERL_NIF_TERM atom_esqlite3; static ERL_NIF_TERM @@ -562,74 +535,6 @@ do_reset(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt) } */ -/* -static ERL_NIF_TERM -do_column_names(ErlNifEnv *env, sqlite3_stmt *stmt) -{ - int i, size; - const char *name; - ERL_NIF_TERM *array; - ERL_NIF_TERM column_names; - - size = sqlite3_column_count(stmt); - if(size == 0) - return enif_make_tuple(env, 0); - else if(size < 0) - return make_error_tuple(env, "invalid_column_count"); - - array = (ERL_NIF_TERM *) enif_alloc(sizeof(ERL_NIF_TERM) * size); - if(!array) - return make_error_tuple(env, "no_memory"); - - for(i = 0; i < size; i++) { - name = sqlite3_column_name(stmt, i); - if(name == NULL) { - enif_free(array); - return make_error_tuple(env, "sqlite3_malloc_failure"); - } - - array[i] = make_atom(env, name); - } - - column_names = enif_make_tuple_from_array(env, array, size); - enif_free(array); - return column_names; -} -*/ - -/* -static ERL_NIF_TERM -do_column_types(ErlNifEnv *env, sqlite3_stmt *stmt) -{ - int i, size; - const char *type; - ERL_NIF_TERM *array; - ERL_NIF_TERM column_types; - - size = sqlite3_column_count(stmt); - if(size == 0) - return enif_make_tuple(env, 0); - else if(size < 0) - return make_error_tuple(env, "invalid_column_count"); - - array = (ERL_NIF_TERM *) enif_alloc(sizeof(ERL_NIF_TERM) * size); - if(!array) - return make_error_tuple(env, "no_memory"); - - for(i = 0; i < size; i++) { - type = sqlite3_column_decltype(stmt, i); - if(type == NULL) { - type = "nil"; - } - - array[i] = make_atom(env, type); - } - - column_types = enif_make_tuple_from_array(env, array, size); - enif_free(array); - return column_types; -} -*/ /* static ERL_NIF_TERM @@ -833,6 +738,9 @@ esqlite_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) /* Set a standard busy timeout of 2 seconds */ sqlite3_busy_timeout(conn->db, 2000); + + /* Turn on extended error codes for better error reporting */ + sqlite3_extended_result_codes(conn->db, 1); ERL_NIF_TERM db_conn = enif_make_resource(env, conn); enif_release_resource(conn); @@ -1254,42 +1162,84 @@ esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } /* - * Get the column types of the prepared statement. -static ERL_NIF_TERM -esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *conn; - esqlite3_stmt *stmt; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); - - if(!stmt->statement) - return make_error_tuple(env, "no_prepared_statement"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_column_types; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->stmt = enif_make_copy(cmd->env, argv[1]); - - return push_command(env, conn, cmd); -} + * Get the column's declared datatypes of a statement. */ +static ERL_NIF_TERM +esqlite_column_decltypes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + ERL_NIF_TERM column_types = enif_make_list(env, 0); + + int size = sqlite3_column_count(stmt->statement); + if(size < 0) { + return enif_raise_exception(env, make_atom(env, "invalid_column_count")); + } + + for(int i=size; i-- > 0; ) { + ERL_NIF_TERM type_name; + + const char *type = sqlite3_column_decltype(stmt->statement, i); + + if(type == NULL) { + type_name = make_atom(env, "undefined"); + } else { + type_name = make_binary(env, type, strlen(type)); + } + + column_types = enif_make_list_cell(env, type_name, column_types); + } + + return column_types; +} + +static ERL_NIF_TERM +esqlite_bind_int(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + int index; + int value; + + if(argc != 3) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + if(!enif_get_int(env, argv[1], &index)) { + return enif_make_badarg(env); + } + + if(!enif_get_int(env, argv[2], &value)) { + return enif_make_badarg(env); + } + + int rc = sqlite3_bind_int(stmt->statement, index, value); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc, conn->db); + } + + return make_atom(env, ok); +} + /* * Backup functions @@ -1536,7 +1486,16 @@ static ErlNifFunc nif_funcs[] = { {"prepare", 2, esqlite_prepare}, {"column_names", 1, esqlite_column_names}, - // {"column_types", 1, esqlite_column_types}, + {"column_decltypes", 1, esqlite_column_decltypes}, + + {"bind_int", 3, esqlite_bind_int}, + /* + {"bind_int64", 3, esqlite_bind_blob}, + {"bind_double", 3, esqlite_bind_blob}, + {"bind_text", 3, esqlite_bind_blob}, + {"bind_blob", 3, esqlite_bind_blob}, + {"bind_null", 2, esqlite_bind_blob}, + */ {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND} diff --git a/rebar.config.script b/rebar.config.script index acbf198..d997ecb 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -23,7 +23,7 @@ CFlags = end. [ - {minimum_otp_vsn, "21.0"}, + {minimum_otp_vsn, "22.0"}, {erl_opts, [debug_info, warnings_as_errors]}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 629750c..20388ca 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -22,7 +22,8 @@ open/1, close/1, prepare/2, - column_names/1 + column_names/1, + column_decltypes/1 % set_update_hook/2, set_update_hook/3, % exec/2, exec/3, exec/4, @@ -448,21 +449,20 @@ prepare(#esqlite3{db=Connection}, Sql) -> % %% @doc Return the column names of the prepared statement. %% --spec column_names(esqlite3_stmt()) -> [binary()]. +-spec column_names(Statement) -> Names + when Statement :: esqlite3_stmt(), + Names :: list(binary()). column_names(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:column_names(Stmt). %% @doc Return the column types of the prepared statement. %% -%-spec column_types(statement()) -> {atom()}. -%column_types(Statement) -> -% column_types(Statement, ?DEFAULT_TIMEOUT). +-spec column_decltypes(Statement) -> Types + when Statement :: esqlite3_stmt(), + Types :: list(binary() | undefined). +column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> + esqlite3_nif:column_decltypes(Stmt). -%-spec column_types(statement(), timeout()) -> {atom()}. -%column_types(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:column_types(RawConnection, RawStatement, Ref, self()), -% receive_answer(RawConnection, Ref, Timeout). %% @doc make multiple sqlite steps per call return rows in reverse order %% diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 2498a24..b4ec869 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -25,6 +25,7 @@ prepare/2, column_names/1, + column_decltypes/1, interrupt/1 % set_update_hook/4, @@ -141,15 +142,16 @@ prepare(_Connection, _Sql) -> %% @doc Retrieve the column names of the prepared statement %% --spec column_names(esqlite3_stmt()) -> list() | {error, _}. +-spec column_names(esqlite3_stmt()) -> list(binary()) | {error, _}. column_names(_Stmt) -> erlang:nif_error(nif_library_not_loaded). -%% @doc Retrieve the column types of the prepared statement +%% @doc Retrieve the declared datatypes of all columns. %% -%-spec column_types(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. -%column_types(_Db, _Stmt, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +-spec column_decltypes(esqlite3_stmt()) -> list(undefined | binary()) | {error, _}. +column_decltypes(_Stmt) -> + erlang:nif_error(nif_library_not_loaded). + %% @doc Initialize a backup procedure of a database. %-spec backup_init(esqlite3(), string(), esqlite3_stmt(), string(), reference(), pid()) -> ok | {error, _}. diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 523d5cc..2adb3dc 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -44,7 +44,7 @@ prepare_test() -> prepare_after_close_test() -> {ok, C} = esqlite3:open(":memory:"), - ok = esqlite3:close(C), + ?assertEqual(ok, esqlite3:close(C)), ?assertMatch({error, {misuse, _}}, esqlite3:prepare(C, "select 1")), ok. @@ -65,6 +65,16 @@ column_names_test() -> ok. +column_decltypes_test() -> + {ok, C} = esqlite3:open(":memory:"), + + {ok, Stmt} = esqlite3:prepare(C, "select 1, 2, 3"), + + ?assertEqual([undefined, undefined, undefined], esqlite3:column_decltypes(Stmt)), + + %% Need to be able to define tables. + + ok. %iodata_test() -> @@ -77,6 +87,8 @@ column_names_test() -> open_multiple_same_databases_test() -> cleanup(), + %% Sqlite allows opening the same file multiple + %% times {ok, _C1} = esqlite3:open(?DB1), {ok, _C2} = esqlite3:open(?DB1), From 2062ff5d1bbc329fff1dcda45f0dc4fbdc226b58 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 19 May 2022 21:26:40 +0200 Subject: [PATCH 06/37] Used extended error codes --- c_src/esqlite3_nif.c | 189 ++++++++++++++++++++++++++++++++++++++---- src/esqlite3.erl | 11 ++- src/esqlite3_nif.erl | 7 ++ test/esqlite_test.erl | 2 + 4 files changed, 194 insertions(+), 15 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 081e1c0..941675c 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -116,6 +116,172 @@ get_sqlite3_return_code_msg(int r) return "unknown"; } +ERL_NIF_TERM +make_two_atom_tuple(ErlNifEnv *env, const char *one, const char *two) { + return enif_make_tuple2(env, make_atom(env, one), make_atom(env, two)); +} + +ERL_NIF_TERM +make_extended_error_tuple(ErlNifEnv *env, int code) { + switch(code) { + case SQLITE_MISUSE: + return make_two_atom_tuple(env, "misuse", "invoked_incorrectly"); + case SQLITE_ERROR_MISSING_COLLSEQ: + return make_two_atom_tuple(env, "error", "missing_collesq"); + case SQLITE_ERROR_RETRY: + return make_two_atom_tuple(env, "error", "retry"); + case SQLITE_ERROR_SNAPSHOT: + return make_two_atom_tuple(env, "error", "snapshot"); + case SQLITE_IOERR_READ: + return make_two_atom_tuple(env, "ioerr", "read"); + case SQLITE_IOERR_SHORT_READ: + return make_two_atom_tuple(env, "ioerr", "short_read"); + case SQLITE_IOERR_WRITE: + return make_two_atom_tuple(env, "ioerr", "write"); + case SQLITE_IOERR_FSYNC: + return make_two_atom_tuple(env, "ioerr", "fsync"); + case SQLITE_IOERR_DIR_FSYNC: + return make_two_atom_tuple(env, "ioerr", "dir_fsync"); + case SQLITE_IOERR_TRUNCATE: + return make_two_atom_tuple(env, "ioerr", "truncate"); + case SQLITE_IOERR_FSTAT: + return make_two_atom_tuple(env, "ioerr", "fstat"); + case SQLITE_IOERR_UNLOCK: + return make_two_atom_tuple(env, "ioerr", "unlock"); + case SQLITE_IOERR_RDLOCK: + return make_two_atom_tuple(env, "ioerr", "rdlock"); + case SQLITE_IOERR_DELETE: + return make_two_atom_tuple(env, "ioerr", "delete"); + case SQLITE_IOERR_BLOCKED: + return make_two_atom_tuple(env, "ioerr", "blocked"); + case SQLITE_IOERR_NOMEM: + return make_two_atom_tuple(env, "ioerr", "nomem"); + case SQLITE_IOERR_ACCESS: + return make_two_atom_tuple(env, "ioerr", "access"); + case SQLITE_IOERR_CHECKRESERVEDLOCK: + return make_two_atom_tuple(env, "ioerr", "checkreservedlock"); + case SQLITE_IOERR_LOCK: + return make_two_atom_tuple(env, "ioerr", "lock"); + case SQLITE_IOERR_CLOSE: + return make_two_atom_tuple(env, "ioerr", "close"); + case SQLITE_IOERR_DIR_CLOSE: + return make_two_atom_tuple(env, "ioerr", "dir_close"); + case SQLITE_IOERR_SHMOPEN: + return make_two_atom_tuple(env, "ioerr", "shmopen"); + case SQLITE_IOERR_SHMSIZE: + return make_two_atom_tuple(env, "ioerr", "shmsize"); + case SQLITE_IOERR_SHMLOCK: + return make_two_atom_tuple(env, "ioerr", "shmlock"); + case SQLITE_IOERR_SHMMAP: + return make_two_atom_tuple(env, "ioerr", "shmmap"); + case SQLITE_IOERR_SEEK: + return make_two_atom_tuple(env, "ioerr", "seek"); + case SQLITE_IOERR_DELETE_NOENT: + return make_two_atom_tuple(env, "ioerr", "delete_noent"); + case SQLITE_IOERR_MMAP: + return make_two_atom_tuple(env, "ioerr", "mmap"); + case SQLITE_IOERR_GETTEMPPATH: + return make_two_atom_tuple(env, "ioerr", "gettemppath"); + case SQLITE_IOERR_CONVPATH: + return make_two_atom_tuple(env, "ioerr", "convpath"); + case SQLITE_IOERR_VNODE: + return make_two_atom_tuple(env, "ioerr", "vnode"); + case SQLITE_IOERR_AUTH: + return make_two_atom_tuple(env, "ioerr", "auth"); + case SQLITE_IOERR_BEGIN_ATOMIC: + return make_two_atom_tuple(env, "ioerr", "begin_atomic"); + case SQLITE_IOERR_COMMIT_ATOMIC: + return make_two_atom_tuple(env, "ioerr", "commit_atomic"); + case SQLITE_IOERR_ROLLBACK_ATOMIC: + return make_two_atom_tuple(env, "ioerr", "rollback_atomic"); + case SQLITE_IOERR_DATA: + return make_two_atom_tuple(env, "ioerr", "data"); + case SQLITE_IOERR_CORRUPTFS: + return make_two_atom_tuple(env, "ioerr", "corruptfs"); + case SQLITE_LOCKED_SHAREDCACHE: + return make_two_atom_tuple(env, "locked", "sharedcache"); + case SQLITE_LOCKED_VTAB: + return make_two_atom_tuple(env, "locked", "vtab"); + case SQLITE_BUSY_RECOVERY: + return make_two_atom_tuple(env, "busy", "recovery"); + case SQLITE_BUSY_SNAPSHOT: + return make_two_atom_tuple(env, "busy", "snapshot"); + case SQLITE_BUSY_TIMEOUT: + return make_two_atom_tuple(env,"busy", "timeout"); + case SQLITE_CANTOPEN_NOTEMPDIR: + return make_two_atom_tuple(env, "cantopen", "notempdir"); + case SQLITE_CANTOPEN_ISDIR: + return make_two_atom_tuple(env, "cantopen", "isdir"); + case SQLITE_CANTOPEN_FULLPATH: + return make_two_atom_tuple(env, "cantopen", "fullpath"); + case SQLITE_CANTOPEN_CONVPATH: + return make_two_atom_tuple(env, "cantopen", "convpath"); + case SQLITE_CANTOPEN_DIRTYWAL: + return make_two_atom_tuple(env, "cantopen", "dirtywal"); + case SQLITE_CANTOPEN_SYMLINK: + return make_two_atom_tuple(env, "cantopen", "symlink"); + case SQLITE_CORRUPT_VTAB: + return make_two_atom_tuple(env, "corrupt", "vtab"); + case SQLITE_CORRUPT_SEQUENCE: + return make_two_atom_tuple(env, "corrupt", "sequence"); + case SQLITE_CORRUPT_INDEX: + return make_two_atom_tuple(env, "corrupt", "index"); + case SQLITE_READONLY_RECOVERY: + return make_two_atom_tuple(env, "readonly", "recovery"); + case SQLITE_READONLY_CANTLOCK: + return make_two_atom_tuple(env, "readonly", "cantlock"); + case SQLITE_READONLY_ROLLBACK: + return make_two_atom_tuple(env, "readonly", "rollback"); + case SQLITE_READONLY_DBMOVED: + return make_two_atom_tuple(env, "readonly", "dbmoved"); + case SQLITE_READONLY_CANTINIT: + return make_two_atom_tuple(env, "readonly", "cantinit"); + case SQLITE_READONLY_DIRECTORY: + return make_two_atom_tuple(env, "readonly", "directory"); + case SQLITE_ABORT_ROLLBACK: + return make_two_atom_tuple(env, "abort", "rollback"); + case SQLITE_CONSTRAINT_CHECK: + return make_two_atom_tuple(env, "constraint", "check"); + case SQLITE_CONSTRAINT_COMMITHOOK: + return make_two_atom_tuple(env, "constraint", "commithook"); + case SQLITE_CONSTRAINT_FOREIGNKEY: + return make_two_atom_tuple(env, "constraint", "foreignkey"); + case SQLITE_CONSTRAINT_FUNCTION: + return make_two_atom_tuple(env, "constraint", "function"); + case SQLITE_CONSTRAINT_NOTNULL: + return make_two_atom_tuple(env, "constraint", "notnull"); + case SQLITE_CONSTRAINT_PRIMARYKEY: + return make_two_atom_tuple(env, "constraint", "primarykey"); + case SQLITE_CONSTRAINT_TRIGGER: + return make_two_atom_tuple(env, "constraint", "trigger"); + case SQLITE_CONSTRAINT_UNIQUE: + return make_two_atom_tuple(env, "constraint", "unique"); + case SQLITE_CONSTRAINT_VTAB: + return make_two_atom_tuple(env, "constraint", "vtab"); + case SQLITE_CONSTRAINT_ROWID: + return make_two_atom_tuple(env, "constraint", "rowid"); + case SQLITE_CONSTRAINT_PINNED: + return make_two_atom_tuple(env, "constraint", "pinned"); + case SQLITE_CONSTRAINT_DATATYPE: + return make_two_atom_tuple(env, "constraint", "datatype"); + case SQLITE_NOTICE_RECOVER_WAL: + return make_two_atom_tuple(env, "notice", "recover_wal"); + case SQLITE_NOTICE_RECOVER_ROLLBACK: + return make_two_atom_tuple(env, "notice", "recover_rollback"); + case SQLITE_WARNING_AUTOINDEX: + return make_two_atom_tuple(env, "warning", "autoindex"); + case SQLITE_AUTH_USER: + return make_two_atom_tuple(env, "auth", "user"); + case SQLITE_OK_LOAD_PERMANENTLY: + return make_two_atom_tuple(env, "ok", "load_permanently"); + case SQLITE_OK_SYMLINK: + /* internal use only */ + return make_two_atom_tuple(env, "ok", "symlink"); + default: + return make_two_atom_tuple(env, "error", "unknown"); + } +} + static const char * get_sqlite3_error_msg(int error_code, sqlite3 *db) { @@ -132,14 +298,8 @@ get_sqlite3_error_msg(int error_code, sqlite3 *db) } static ERL_NIF_TERM -make_sqlite3_error_tuple(ErlNifEnv *env, int error_code, sqlite3 *db) -{ - const char *error_code_msg = get_sqlite3_return_code_msg(error_code); - const char *msg = get_sqlite3_error_msg(error_code, db); - - return enif_make_tuple2(env, make_atom(env, "error"), - enif_make_tuple2(env, make_atom(env, error_code_msg), - enif_make_string(env, msg, ERL_NIF_LATIN1))); +make_sqlite3_error_tuple(ErlNifEnv *env, int error_code) { + return enif_make_tuple2(env, make_atom(env, "error"), make_extended_error_tuple(env, error_code)); } /* @@ -730,7 +890,7 @@ esqlite_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) */ int rc = sqlite3_open(filename, &conn->db); if(rc != SQLITE_OK) { - ERL_NIF_TERM error = make_sqlite3_error_tuple(env, rc, conn->db); + ERL_NIF_TERM error = make_sqlite3_error_tuple(env, rc); sqlite3_close_v2(conn->db); enif_release_resource(conn); return error; @@ -766,7 +926,7 @@ esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) rc = sqlite3_close_v2(conn->db); if(rc != SQLITE_OK) - return make_sqlite3_error_tuple(env, rc, conn->db); + return make_sqlite3_error_tuple(env, rc); conn->db = NULL; return make_atom(env, "ok"); @@ -805,7 +965,7 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail); if(rc != SQLITE_OK) { enif_release_resource(stmt); - return make_sqlite3_error_tuple(env, rc, conn->db); + return make_sqlite3_error_tuple(env, rc); } esqlite_stmt = enif_make_resource(env, stmt); @@ -1234,10 +1394,10 @@ esqlite_bind_int(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) int rc = sqlite3_bind_int(stmt->statement, index, value); if(rc != SQLITE_OK) { - return make_sqlite3_error_tuple(env, rc, conn->db); + return make_sqlite3_error_tuple(env, rc); } - return make_atom(env, ok); + return make_atom(env, "ok"); } @@ -1487,8 +1647,9 @@ static ErlNifFunc nif_funcs[] = { {"column_names", 1, esqlite_column_names}, {"column_decltypes", 1, esqlite_column_decltypes}, - + {"bind_int", 3, esqlite_bind_int}, + /* {"bind_int64", 3, esqlite_bind_blob}, {"bind_double", 3, esqlite_bind_blob}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 20388ca..0f929f3 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -23,7 +23,9 @@ prepare/2, column_names/1, - column_decltypes/1 + column_decltypes/1, + + bind_int/3 % set_update_hook/2, set_update_hook/3, % exec/2, exec/3, exec/4, @@ -395,6 +397,13 @@ prepare(#esqlite3{db=Connection}, Sql) -> Error end. +-spec bind_int(Statement, Index, Value) -> BindResult + when Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: integer(), + BindResult :: ok | {error, _}. +bind_int(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> + esqlite3_nif:bind_int(Stmt, Index, Value). %% @doc Like prepare/2, but with an extra timeout value. %-spec prepare(sql(), connection(), timeout()) -> {ok, statement()} | {error, _}. diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index b4ec869..f5b9f1c 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -27,6 +27,8 @@ column_names/1, column_decltypes/1, + bind_int/3, + interrupt/1 % set_update_hook/4, % exec/4, @@ -92,6 +94,11 @@ close(_Db) -> prepare(_Connection, _Sql) -> erlang:nif_error(nif_library_not_loaded). + + +bind_int(_Statement, _Index, _Value) -> + erlang:nif_error(nif_library_not_loaded). + % -spec set_update_hook((), pid(), pid()) -> ok | {error, _}. %set_update_hook(_Db, _Ref, _Dest, _Pid) -> % erlang:nif_error(nif_library_not_loaded). diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 2adb3dc..3d37fba 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -45,7 +45,9 @@ prepare_test() -> prepare_after_close_test() -> {ok, C} = esqlite3:open(":memory:"), ?assertEqual(ok, esqlite3:close(C)), + ?assertMatch({error, {misuse, _}}, esqlite3:prepare(C, "select 1")), + ok. column_names_test() -> From 843643987330f5bc86cae4c45b601cff44e5fff2 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 19 May 2022 21:28:43 +0200 Subject: [PATCH 07/37] Build on OTP 21 is deprecated --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3dc9f86..1a7079c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - otp_version: [21,22,23,24] + otp_version: [22,23,24] os: [ubuntu-latest] container: From d1dffa1e8d1c4276cde44c6553b7a60ddfaf2cef Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 21 May 2022 09:30:56 +0200 Subject: [PATCH 08/37] Added step function in favour of multi_step --- c_src/esqlite3_nif.c | 207 ++++++++++++++++++++++++++---------------- src/esqlite3.erl | 83 ++++++++++++++++- src/esqlite3_nif.erl | 22 ++++- test/esqlite_test.erl | 13 +++ 4 files changed, 238 insertions(+), 87 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 941675c..9249312 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -40,6 +40,8 @@ typedef struct { /* prepared statement */ typedef struct { sqlite3_stmt *statement; + + int column_count; } esqlite3_stmt; /* data associated with ongoing backup */ @@ -590,7 +592,6 @@ make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) return term; } -/* static ERL_NIF_TERM make_cell(ErlNifEnv *env, sqlite3_stmt *statement, unsigned int i) { @@ -610,78 +611,10 @@ make_cell(ErlNifEnv *env, sqlite3_stmt *statement, unsigned int i) case SQLITE_TEXT: return make_binary(env, sqlite3_column_text(statement, i), sqlite3_column_bytes(statement, i)); - default: - return make_atom(env, "should_not_happen"); } + return enif_raise_exception(env, make_atom(env, "internal_error")); } -*/ -/* -static ERL_NIF_TERM -make_row(ErlNifEnv *env, sqlite3_stmt *statement, ERL_NIF_TERM *array, int size) -{ - if(!array) - return make_error_tuple(env, "no_memory"); - - for(int i = 0; i < size; i++) - array[i] = make_cell(env, statement, i); - - return enif_make_tuple_from_array(env, array, size); -} -*/ - -/* -static ERL_NIF_TERM -do_multi_step(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg) -{ - ERL_NIF_TERM status; - ERL_NIF_TERM rows = enif_make_list_from_array(env, NULL, 0); - ERL_NIF_TERM *rowBuffer = NULL; - int rowBufferSize = 0; - - int chunk_size = 0; - enif_get_int(env, arg, &chunk_size); - - int rc = sqlite3_step(stmt); - while (rc == SQLITE_ROW && chunk_size-- > 0) - { - if (!rowBufferSize) - rowBufferSize = sqlite3_column_count(stmt); - if (rowBuffer == NULL) - rowBuffer = (ERL_NIF_TERM *) enif_alloc(sizeof(ERL_NIF_TERM)*rowBufferSize); - - rows = enif_make_list_cell(env, make_row(env, stmt, rowBuffer, rowBufferSize), rows); - - if (chunk_size > 0) - rc = sqlite3_step(stmt); - } - - switch(rc) { - case SQLITE_ROW: - status = make_atom(env, "rows"); - break; - case SQLITE_BUSY: - status = make_atom(env, "$busy"); - break; - case SQLITE_DONE: - // - // Automatically reset the statement after a done so - // column_names will work after the statement is done. - // - // Not resetting the statement can lead to vm crashes. - // - sqlite3_reset(stmt); - status = make_atom(env, "$done"); - break; - default: - // We use prepare_v2, so any error code can be returned. - return make_sqlite3_error_tuple(env, rc, db); - } - - enif_free(rowBuffer); - return enif_make_tuple2(env, status, rows); -} -*/ /* static ERL_NIF_TERM @@ -940,11 +873,13 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; - if(argc != 2) + if(argc != 3) { return enif_make_badarg(env); + } - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) { return enif_make_badarg(env); + } ErlNifBinary bin; esqlite3_stmt *stmt; @@ -957,17 +892,24 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); } + unsigned int prep_flags; + if(!enif_get_uint(env, argv[2], &prep_flags)) { + return make_error_tuple(env, "invalid_chunk_size"); + } + stmt = enif_alloc_resource(esqlite3_stmt_type, sizeof(esqlite3_stmt)); if(!stmt) { return enif_raise_exception(env, make_atom(env, "no_memory")); } - rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail); + rc = sqlite3_prepare_v3(conn->db, (char *) bin.data, bin.size, prep_flags, &(stmt->statement), &tail); if(rc != SQLITE_OK) { enif_release_resource(stmt); return make_sqlite3_error_tuple(env, rc); } + stmt->column_count = sqlite3_column_count(stmt->statement); + esqlite_stmt = enif_make_resource(env, stmt); enif_release_resource(stmt); @@ -1400,6 +1342,115 @@ esqlite_bind_int(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +static ERL_NIF_TERM +esqlite_bind_int64(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + int index; + ErlNifSInt64 value; + + if(argc != 3) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + if(!enif_get_int(env, argv[1], &index)) { + return enif_make_badarg(env); + } + + if(!enif_get_int64(env, argv[2], &value)) { + return enif_make_badarg(env); + } + + int rc = sqlite3_bind_int64(stmt->statement, index, value); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + +static ERL_NIF_TERM +esqlite_bind_double(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + int index; + double value; + + if(argc != 3) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + if(!enif_get_int(env, argv[1], &index)) { + return enif_make_badarg(env); + } + + if(!enif_get_double(env, argv[2], &value)) { + return enif_make_badarg(env); + } + + int rc = sqlite3_bind_double(stmt->statement, index, value); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + +static ERL_NIF_TERM +esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + int rc = sqlite3_step(stmt->statement); + switch(rc) { + case SQLITE_ROW: + { + ERL_NIF_TERM row = enif_make_list(env, 0); + for(int i=stmt->column_count; i-- > 0; ) { + row = enif_make_list_cell(env, make_cell(env, stmt->statement, i), row); + } + return row; + } + case SQLITE_DONE: + /* since 3.6.23.1 it is no longer required to do an explict reset. + */ + return make_atom(env, "done"); + case SQLITE_BUSY: + return make_atom(env, "busy"); + } + + return make_sqlite3_error_tuple(env, rc); +} + + /* * Backup functions @@ -1643,16 +1694,19 @@ static ErlNifFunc nif_funcs[] = { {"open", 1, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"close", 1, esqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"prepare", 2, esqlite_prepare}, + {"prepare", 3, esqlite_prepare}, {"column_names", 1, esqlite_column_names}, {"column_decltypes", 1, esqlite_column_decltypes}, {"bind_int", 3, esqlite_bind_int}, + {"bind_int64", 3, esqlite_bind_int64}, + {"bind_double", 3, esqlite_bind_double}, + + {"step", 1, esqlite_step}, + /* - {"bind_int64", 3, esqlite_bind_blob}, - {"bind_double", 3, esqlite_bind_blob}, {"bind_text", 3, esqlite_bind_blob}, {"bind_blob", 3, esqlite_bind_blob}, {"bind_null", 2, esqlite_bind_blob}, @@ -1668,7 +1722,6 @@ static ErlNifFunc nif_funcs[] = { {"insert", 4, esqlite_insert}, {"last_insert_rowid", 3, esqlite_last_insert_rowid}, {"get_autocommit", 3, esqlite_get_autocommit}, - {"multi_step", 5, esqlite_multi_step, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"reset", 4, esqlite_reset}, // TODO: {"esqlite_bind", 3, esqlite_bind_named}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 0f929f3..b826300 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -20,30 +20,41 @@ %% higher-level export -export([ open/1, close/1, + prepare/2, + prepare/3, column_names/1, column_decltypes/1, - bind_int/3 + bind_int/3, + bind_int64/3, + bind_double/3, + + step/1 % set_update_hook/2, set_update_hook/3, % exec/2, exec/3, exec/4, % changes/1, changes/2, % insert/2, insert/3, % last_insert_rowid/1, +% % get_autocommit/1, get_autocommit/2, +% % step/1, step/2, +% % reset/1, % bind/2, bind/3, % fetchone/1, % fetchall/1, fetchall/2, fetchall/3, -% column_types/1, column_types/2, +% +% % backup_init/4, backup_init/5, % backup_finish/1, backup_finish/2, % backup_remaining/1, backup_remaining/2, % backup_pagecount/1, backup_pagecount/2, % backup_step/2, backup_step/3, +% % flush/0 ]). @@ -52,6 +63,10 @@ -define(DEFAULT_TIMEOUT, infinity). -define(DEFAULT_CHUNK_SIZE, 5000). + +-define(SQLITE_PREPARE_PERSISTENT, 16#01). +-define(SQLITE_PREPARE_NO_VTAB, 16#04). + -record(esqlite3, { db :: esqlite3_nif:esqlite3() }). @@ -71,6 +86,8 @@ %-type esqlite3_backup() :: #esqlite3_backup{}. -type sql() :: esqlite3_nif:sql(). +-type prepare_flags() :: persistent | no_vtab. + %% erlang -> sqlite type conversions %% %% 'undefined' -> null @@ -85,7 +102,7 @@ -type row() :: tuple(). % tuple of cell_type -type cell_type() :: undefined | integer() | binary() | float(). --export_type([esqlite3/0, esqlite3_stmt/0, sql/0, row/0, rowid/0, cell_type/0]). +-export_type([esqlite3/0, esqlite3_stmt/0, prepare_flags/0, sql/0, row/0, rowid/0, cell_type/0]). %% @doc Opens a sqlite3 database mentioned in Filename. %% @@ -389,8 +406,19 @@ close(#esqlite3{db=Connection}) -> when Connection :: esqlite3(), Sql :: sql(), PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. -prepare(#esqlite3{db=Connection}, Sql) -> - case esqlite3_nif:prepare(Connection, Sql) of +prepare(Connection, Sql) -> + prepare(Connection, Sql, []). + +%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in +%% queries. +%% +-spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult + when Connection :: esqlite3(), + Sql :: sql(), + PrepareFlags :: list(prepare_flags()), + PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. +prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> + case esqlite3_nif:prepare(Connection, Sql, props_to_prepare_flag(PrepareFlags)) of {ok, Stmt} -> {ok, #esqlite3_stmt{db=Connection, stmt=Stmt}}; {error, _}=Error -> @@ -405,6 +433,29 @@ prepare(#esqlite3{db=Connection}, Sql) -> bind_int(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int(Stmt, Index, Value). +-spec bind_int64(Statement, Index, Value) -> BindResult + when Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: integer(), + BindResult :: ok | {error, _}. +bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> + esqlite3_nif:bind_int64(Stmt, Index, Value). + +-spec bind_double(Statement, Index, Value) -> BindResult + when Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: float(), + BindResult :: ok | {error, _}. +bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> + esqlite3_nif:bind_double(Stmt, Index, Value). + +-spec step(Statement) -> StepResult + when Statement :: esqlite3_stmt(), + StepResult:: ok | {error, _}. +step(#esqlite3_stmt{stmt=Stmt}) -> + esqlite3_nif:step(Stmt). + + %% @doc Like prepare/2, but with an extra timeout value. %-spec prepare(sql(), connection(), timeout()) -> {ok, statement()} | {error, _}. %prepare(Sql, #connection{raw_connection=RawConnection}, Timeout) -> @@ -673,3 +724,25 @@ column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> % after % 0 -> ok % end. + +%% +%% Helpers +%% + + +props_to_prepare_flag(Props) -> + Flag = case proplists:get_value(no_vtab, Props, false) of + true -> ?SQLITE_PREPARE_NO_VTAB; + false -> 0 + end, + case proplists:get_value(persistent, Props, false) of + true -> Flag bor ?SQLITE_PREPARE_PERSISTENT; + false -> Flag + end. + + + + + + + diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index f5b9f1c..89601d0 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -22,12 +22,16 @@ -export([ open/1, close/1, - prepare/2, + prepare/3, column_names/1, column_decltypes/1, bind_int/3, + bind_int64/3, + bind_double/3, + + step/1, interrupt/1 % set_update_hook/4, @@ -87,18 +91,26 @@ close(_Db) -> %% @doc Compile a sql statement. %% --spec prepare(Connection, Sql) -> PrepareResult +-spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult when Connection :: esqlite3(), Sql :: sql(), + PrepareFlags :: non_neg_integer(), PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. -prepare(_Connection, _Sql) -> +prepare(_Connection, _Sql, _PrepareFlags) -> erlang:nif_error(nif_library_not_loaded). - - bind_int(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +bind_int64(_Statement, _Index, _Value) -> + erlang:nif_error(nif_library_not_loaded). + +bind_double(_Statement, _Index, _Value) -> + erlang:nif_error(nif_library_not_loaded). + +step(_Statement) -> + erlang:nif_error(nif_library_not_loaded). + % -spec set_update_hook((), pid(), pid()) -> ok | {error, _}. %set_update_hook(_Db, _Ref, _Dest, _Pid) -> % erlang:nif_error(nif_library_not_loaded). diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 3d37fba..2ecef5c 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -78,6 +78,19 @@ column_decltypes_test() -> ok. +step_test() -> + {ok, C} = esqlite3:open(":memory:"), + {ok, Stmt} = esqlite3:prepare(C, "select 1, 2, 3;" ), + + ?assertEqual([1,2,3], esqlite3:step(Stmt)), + ?assertEqual(done, esqlite3:step(Stmt)), + + %% After the done, the statement is reset and + ?assertEqual([1,2,3], esqlite3:step(Stmt)), + ?assertEqual(done, esqlite3:step(Stmt)), + + ok. + %iodata_test() -> % {ok, C} = esqlite3:open(":memory:"), From 71dbc8faafde68fac1267ee1c3bd0a1e4be0494d Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 21 May 2022 13:54:35 +0200 Subject: [PATCH 09/37] Removed outdated comments --- c_src/esqlite3_nif.c | 119 ++++++++++++++++++++++++++++++++++---- src/esqlite3.erl | 133 +++++++++++-------------------------------- src/esqlite3_nif.erl | 32 ++++++++--- 3 files changed, 166 insertions(+), 118 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 9249312..f0ba610 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -1412,6 +1412,38 @@ esqlite_bind_double(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +static ERL_NIF_TERM +esqlite_bind_null(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + int index; + double value; + + if(argc != 2) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + if(!enif_get_int(env, argv[1], &index)) { + return enif_make_badarg(env); + } + + int rc = sqlite3_bind_null(stmt->statement, index); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + + static ERL_NIF_TERM esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { @@ -1450,6 +1482,26 @@ esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_sqlite3_error_tuple(env, rc); } +static ERL_NIF_TERM +esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + int rc = sqlite3_reset(stmt->statement); + return make_sqlite3_error_tuple(env, rc); +} /* @@ -1643,11 +1695,54 @@ esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); esqlite3 *db = (esqlite3 *) conn; - sqlite3_interrupt(db->db); + if(db->db == NULL) { + return make_error_tuple(env, "closed"); + } + sqlite3_interrupt(db->db); return enif_make_atom(env, "ok"); } +static ERL_NIF_TERM +esqlite_get_autocommit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + return enif_make_badarg(env); + + esqlite3 *db = (esqlite3 *) conn; + + if(db->db == NULL) { + return make_error_tuple(env, "closed"); + } + + if(sqlite3_get_autocommit(db->db)) { + return make_atom(env, "true"); + } + + return make_atom(env, "false"); +} + +static ERL_NIF_TERM +esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + return enif_make_badarg(env); + + esqlite3 *db = (esqlite3 *) conn; + + if(db->db == NULL) { + return make_error_tuple(env, "closed"); + } + + sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(db->db); + return enif_make_int64(env, last_rowid); +} + + /* * Load the nif. Initialize some stuff and such */ @@ -1703,8 +1798,7 @@ static ErlNifFunc nif_funcs[] = { {"bind_int64", 3, esqlite_bind_int64}, {"bind_double", 3, esqlite_bind_double}, - {"step", 1, esqlite_step}, - + {"bind_null", 2, esqlite_bind_null}, /* {"bind_text", 3, esqlite_bind_blob}, @@ -1712,7 +1806,18 @@ static ErlNifFunc nif_funcs[] = { {"bind_null", 2, esqlite_bind_blob}, */ - {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND} + {"step", 1, esqlite_step}, + {"reset", 1, esqlite_reset}, + + /* + Other interesting additions... trace. + wal_hook triggered after every commit. + also interesting commit and rollback_hooks + */ + + {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"last_insert_rowid", 1, esqlite_last_insert_rowid}, + {"get_autocommit", 1, esqlite_get_autocommit}, /* {"set_update_hook", 4, set_update_hook}, @@ -1720,12 +1825,6 @@ static ErlNifFunc nif_funcs[] = { {"exec", 4, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"changes", 3, esqlite_changes}, {"insert", 4, esqlite_insert}, - {"last_insert_rowid", 3, esqlite_last_insert_rowid}, - {"get_autocommit", 3, esqlite_get_autocommit}, - {"reset", 4, esqlite_reset}, - - // TODO: {"esqlite_bind", 3, esqlite_bind_named}, - {"bind", 5, esqlite_bind}, {"backup_init", 6, esqlite_backup_init}, {"backup_step", 5, esqlite_backup_step}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index b826300..6f953f3 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -21,41 +21,40 @@ -export([ open/1, close/1, + %% db connection functions + get_autocommit/1, + last_insert_rowid/1, +% set_update_hook/2, set_update_hook/3, + prepare/2, prepare/3, + %% prepared statement functions column_names/1, column_decltypes/1, bind_int/3, bind_int64/3, bind_double/3, + % bind_text/3, + % bind_blob/3, + bind_null/2, - step/1 + step/1, + reset/1 -% set_update_hook/2, set_update_hook/3, % exec/2, exec/3, exec/4, % changes/1, changes/2, % insert/2, insert/3, -% last_insert_rowid/1, % -% get_autocommit/1, get_autocommit/2, -% -% step/1, step/2, -% -% reset/1, -% bind/2, bind/3, % fetchone/1, % fetchall/1, fetchall/2, fetchall/3, % -% % backup_init/4, backup_init/5, % backup_finish/1, backup_finish/2, % backup_remaining/1, backup_remaining/2, % backup_pagecount/1, backup_pagecount/2, % backup_step/2, backup_step/3, -% -% flush/0 ]). % -export([q/2, q/3, q/4, map/3, map/4, foreach/3, foreach/4]). @@ -137,15 +136,7 @@ open(Filename) -> close(#esqlite3{db=Connection}) -> esqlite3_nif:close(Connection). -%% @doc Flush any stale answers left in the mailbox of the current process. -%% This can happen if there has been a timeout. Normally the nif functions -%% are called with the default 'infinite' timeout, so calling this is not -%% needed. -%-spec flush() -> ok. -%flush() -> -% flush_answers(). -% -% + %%% @doc Subscribe to database notifications. When rows are inserted deleted %%% or updates, the process will receive messages: %%% ```{insert, string(), rowid()}''' @@ -378,26 +369,20 @@ close(#esqlite3{db=Connection}) -> %% @doc Get the last insert rowid. %% -%-spec last_insert_rowid(connection(), timeout()) -> {ok, rowid()} | {error, _}. -%last_insert_rowid(#connection{raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:last_insert_rowid(RawConnection, Ref, self()), -% receive_answer(RawConnection, Ref, Timeout). +-spec last_insert_rowid(Connection) -> RowidResult + when Connection :: esqlite3(), + RowidResult :: integer() | {error, closed}. +last_insert_rowid(#esqlite3{db=Connection}) -> + esqlite3_nif:last_insert_rowid(Connection). %% @doc Check if the connection is in auto-commit mode. %% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details. %% -%-spec get_autocommit(connection()) -> true | false. -%get_autocommit(Connection) -> -% get_autocommit(Connection, ?DEFAULT_TIMEOUT). - -%% @doc Like autocommit/1, but with an extra timeout attribute. -%-spec get_autocommit(connection(), timeout()) -> true | false. -%get_autocommit(#connection{raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:get_autocommit(RawConnection, Ref, self()), -% receive_answer(RawConnection, Ref, Timeout). -% +-spec get_autocommit(Connection) -> AutocommitResult + when Connection :: esqlite3(), + AutocommitResult :: true | false | {error, closed}. +get_autocommit(#esqlite3{db=Connection}) -> + esqlite3_nif:get_autocommit(Connection). %% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in %% queries. @@ -449,64 +434,25 @@ bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_double(Stmt, Index, Value). +-spec bind_null(Statement, Index) -> BindResult + when Statement :: esqlite3_stmt(), + Index :: integer(), + BindResult :: ok | {error, _}. +bind_null(#esqlite3_stmt{stmt=Stmt}, Index) -> + esqlite3_nif:bind_null(Stmt, Index). + -spec step(Statement) -> StepResult when Statement :: esqlite3_stmt(), StepResult:: ok | {error, _}. step(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:step(Stmt). +-spec reset(Statement) -> ResetResult + when Statement :: esqlite3_stmt(), + ResetResult:: ok | {error, _}. +reset(#esqlite3_stmt{stmt=Stmt}) -> + esqlite3_nif:reset(Stmt). -%% @doc Like prepare/2, but with an extra timeout value. -%-spec prepare(sql(), connection(), timeout()) -> {ok, statement()} | {error, _}. -%prepare(Sql, #connection{raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% case receive_answer(RawConnection, Ref, Timeout) of -% {ok, Stmt} when is_reference(Stmt) -> -% {ok, #statement{raw_statement=Stmt, raw_connection=RawConnection}}; -% {error, _}=Error -> -%% Error -% end. - -%% @doc Step -%% -%-spec step(statement()) -> tuple() | '$busy' | '$done'. -%step(Stmt) -> -% step(Stmt, ?DEFAULT_TIMEOUT). -% -%% @doc -%% -%-spec step(statement(), timeout()) -> tuple() | '$busy' | '$done'. -%step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:multi_step(RawConnection, RawStatement, 1, Ref, self()), -% case receive_answer(RawConnection, Ref, Timeout) of -% {rows, [Row | []]} -> {row, Row}; -% {'$done', []} -> '$done'; -% {'$busy', []} -> '$busy'; -% Else -> Else -% end. - -%% @doc Reset the prepared statement back to its initial state. -%% -%-spec reset(statement()) -> ok | {error, _}. -%reset(#statement{raw_statement=RawStatement, raw_connection=RawConnection}) -> -% Ref = make_ref(), -% ok = esqlite3_nif:reset(RawConnection, RawStatement, Ref, self()), -% receive_answer(RawConnection, Ref, ?DEFAULT_TIMEOUT). - -%% @doc Bind values to prepared statements -%% -%-spec bind(statement(), list(cell_type())) -> ok | {error, _}. -%bind(Stmt, Args) -> -% bind(Stmt, Args, ?DEFAULT_TIMEOUT). - -%% @doc Bind values to prepared statements -%-spec bind(statement(), list(cell_type()), timeout()) -> ok | {error, _}. -%bind(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, Args, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:bind(RawConnection, RawStatement, Ref, self(), Args), -% receive_answer(RawConnection, Ref, Timeout). -% %% @doc Return the column names of the prepared statement. %% -spec column_names(Statement) -> Names @@ -524,17 +470,6 @@ column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:column_decltypes(Stmt). -%% @doc make multiple sqlite steps per call return rows in reverse order -%% -%-spec multi_step(term(), pos_integer(), timeout()) -> -% {rows, list(tuple())} | -% {'$busy', list(tuple())} | -% {'$done', list(tuple())} | -% {error, _}. -%multi_step(#statement{raw_statement=RawStatement, raw_connection=RawConnection}, ChunkSize, Timeout) -> -% Ref = make_ref(), -%% ok = esqlite3_nif:multi_step(RawConnection, RawStatement, ChunkSize, Ref, self()), -% receive_answer(RawConnection, Ref, Timeout). %% %% Backup API diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 89601d0..64147e9 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -22,6 +22,10 @@ -export([ open/1, close/1, + + get_autocommit/1, + last_insert_rowid/1, + prepare/3, column_names/1, @@ -30,16 +34,20 @@ bind_int/3, bind_int64/3, bind_double/3, + %bind_text/3, + %bind_blob/3, + bind_null/2, step/1, + reset/1, + interrupt/1 % set_update_hook/4, % exec/4, % changes/3, % insert/4, -% last_insert_rowid/3, -% get_autocommit/3, +% % multi_step/5, % reset/4, % finalize/4, @@ -108,9 +116,15 @@ bind_int64(_Statement, _Index, _Value) -> bind_double(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +bind_null(_Statement, _Index) -> + erlang:nif_error(nif_library_not_loaded). + step(_Statement) -> erlang:nif_error(nif_library_not_loaded). +reset(_Statement) -> + erlang:nif_error(nif_library_not_loaded). + % -spec set_update_hook((), pid(), pid()) -> ok | {error, _}. %set_update_hook(_Db, _Ref, _Dest, _Pid) -> % erlang:nif_error(nif_library_not_loaded). @@ -210,13 +224,13 @@ interrupt(_Db) -> %% @doc Get the last insert rowid. %% -%-spec last_insert_rowid(esqlite3(), reference(), pid()) -> ok | {error, _}. -%last_insert_rowid(_Db, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +-spec last_insert_rowid(esqlite3()) -> integer() | {error, _}. +last_insert_rowid(_Connection) -> + erlang:nif_error(nif_library_not_loaded). -%% @doc Get automcommit +%% @doc Get autocommit %% -%-spec get_autocommit(esqlite3(), reference(), pid()) -> ok | {error, _}. -%get_autocommit(_Db, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +-spec get_autocommit(esqlite3()) -> true | false | {error, _}. +get_autocommit(_Connection) -> + erlang:nif_error(nif_library_not_loaded). From 7913ec08c9330a305cd07149f12a3a726c4019b7 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 21 May 2022 14:45:18 +0200 Subject: [PATCH 10/37] Added changes --- c_src/esqlite3_nif.c | 141 ++++++------------------------------------- src/esqlite3.erl | 21 ++++--- src/esqlite3_nif.erl | 16 ++--- 3 files changed, 41 insertions(+), 137 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index f0ba610..30128d9 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -951,37 +951,6 @@ set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } */ -/* - * Execute the sql statement -static ERL_NIF_TERM -esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - // command - cmd->type = cmd_exec; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[3]); - - return push_command(env, db, cmd); -} - */ /* * Count the nr of changes of last statement @@ -1014,96 +983,6 @@ esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } */ -/* -static ERL_NIF_TERM -esqlite_insert(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - // command - cmd->type = cmd_insert; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[3]); - - return push_command(env, db, cmd); -} -*/ - -/* -static ERL_NIF_TERM -esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 3) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - // command - cmd->type = cmd_last_insert_rowid; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - - return push_command(env, db, cmd); -} -*/ - -/* -static ERL_NIF_TERM -esqlite_get_autocommit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 3) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - // command - cmd->type = cmd_get_autocommit; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - - return push_command(env, db, cmd); -} -*/ /* * Bind a variable to a prepared statement @@ -1742,6 +1621,23 @@ esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_int64(env, last_rowid); } +static ERL_NIF_TERM +esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + return enif_make_badarg(env); + + esqlite3 *db = (esqlite3 *) conn; + + if(db->db == NULL) { + return make_error_tuple(env, "closed"); + } + + sqlite3_int64 changes = sqlite3_changes64(db->db); + return enif_make_int64(env, changes); +} /* * Load the nif. Initialize some stuff and such @@ -1818,13 +1714,12 @@ static ErlNifFunc nif_funcs[] = { {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"last_insert_rowid", 1, esqlite_last_insert_rowid}, {"get_autocommit", 1, esqlite_get_autocommit}, + {"changes", 1, esqlite_changes}, /* {"set_update_hook", 4, set_update_hook}, {"exec", 4, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"changes", 3, esqlite_changes}, - {"insert", 4, esqlite_insert}, {"backup_init", 6, esqlite_backup_init}, {"backup_step", 5, esqlite_backup_step}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 6f953f3..7d0be54 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -24,6 +24,8 @@ %% db connection functions get_autocommit/1, last_insert_rowid/1, + changes/1, + % set_update_hook/2, set_update_hook/3, prepare/2, @@ -44,7 +46,6 @@ reset/1 % exec/2, exec/3, exec/4, -% changes/1, changes/2, % insert/2, insert/3, % % fetchone/1, @@ -347,7 +348,8 @@ close(#esqlite3{db=Connection}) -> % Ref = make_ref(), % ok = esqlite3_nif:changes(RawConnection, Ref, self()), % receive_answer(RawConnection, Ref, Timeout). -% + + %%% @doc Insert records, returns the last rowid. %% %-spec insert(sql(), connection()) -> {ok, rowid()} | {error, _}. @@ -357,15 +359,11 @@ close(#esqlite3{db=Connection}) -> %% @doc Like insert/2, but with extra timeout parameter. %-spec insert(sql(), connection(), timeout()) -> {ok, rowid()} | {error, _}. %insert(Sql, #connection{raw_connection=RawConnection}, Timeout) -> +% [NOTE] it is an exec followed by a last_insert_rowid. % Ref = make_ref(), % ok = esqlite3_nif:insert(RawConnection, Ref, self(), Sql), % receive_answer(RawConnection, Ref, Timeout). -%% @doc Get the last insert rowid, using the default timeout. -%% -%-spec last_insert_rowid(connection()) -> {ok, rowid()} | {error, _}. -%last_insert_rowid(Connection) -> -% last_insert_rowid(Connection, ?DEFAULT_TIMEOUT). %% @doc Get the last insert rowid. %% @@ -375,6 +373,15 @@ close(#esqlite3{db=Connection}) -> last_insert_rowid(#esqlite3{db=Connection}) -> esqlite3_nif:last_insert_rowid(Connection). +%% @doc Get the number of changes in the most recent INSERT, UPDATE or DELETE. +%% +-spec changes(Connection) -> ChangesResult + when Connection :: esqlite3(), + ChangesResult :: integer() | {error, closed}. +changes(#esqlite3{db=Connection}) -> + esqlite3_nif:changes(Connection). + + %% @doc Check if the connection is in auto-commit mode. %% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details. %% diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 64147e9..2f9b5a2 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -25,6 +25,7 @@ get_autocommit/1, last_insert_rowid/1, + changes/1, prepare/3, @@ -46,7 +47,6 @@ % set_update_hook/4, % exec/4, % changes/3, -% insert/4, % % multi_step/5, % reset/4, @@ -216,18 +216,20 @@ column_decltypes(_Stmt) -> interrupt(_Db) -> erlang:nif_error(nif_library_not_loaded). -%% @doc Insert record -%% -%-spec insert(esqlite3(), reference(), pid(), sql()) -> ok | {error, _}. -%insert(_Db, _Ref, _Dest, _Sql) -> -% erlang:nif_error(nif_library_not_loaded). - %% @doc Get the last insert rowid. %% -spec last_insert_rowid(esqlite3()) -> integer() | {error, _}. last_insert_rowid(_Connection) -> erlang:nif_error(nif_library_not_loaded). +%% @doc Get number of changes insert, delete of the most recent completed +%% INSERT, DELETE or UPDATE statement. +%% +-spec changes(esqlite3()) -> integer() | {error, _}. +changes(_Connection) -> + erlang:nif_error(nif_library_not_loaded). + + %% @doc Get autocommit %% -spec get_autocommit(esqlite3()) -> true | false | {error, _}. From 0e45e9bea47e71766ca83065edc14bb812af3ee1 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 21 May 2022 22:00:54 +0200 Subject: [PATCH 11/37] Re-enabled a lot of tests --- c_src/esqlite3_nif.c | 41 ++++++++- src/esqlite3.erl | 15 ++- src/esqlite3_nif.erl | 11 +++ test/esqlite_test.erl | 208 +++++++++++++++++++++--------------------- 4 files changed, 165 insertions(+), 110 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 30128d9..e964d25 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -89,7 +89,7 @@ get_sqlite3_return_code_msg(int r) case SQLITE_INTERNAL: return "internal"; case SQLITE_PERM: return "perm"; case SQLITE_ABORT: return "abort"; - case SQLITE_BUSY: return "busy"; + case SQLITE_BUSY: return "$busy"; case SQLITE_LOCKED: return "locked"; case SQLITE_NOMEM: return "nomem"; case SQLITE_READONLY: return "readonly"; @@ -112,7 +112,7 @@ get_sqlite3_return_code_msg(int r) case SQLITE_RANGE: return "range"; case SQLITE_NOTADB: return "notadb"; case SQLITE_ROW: return "row"; - case SQLITE_DONE: return "done"; + case SQLITE_DONE: return "$done"; } return "unknown"; @@ -865,6 +865,38 @@ esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +/* + * Exec a sql statement + */ +static ERL_NIF_TERM +esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; + ErlNifBinary bin; + int rc; + ERL_NIF_TERM eos = enif_make_int(env, 0); + + if(argc != 2) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) { + return enif_make_badarg(env); + } + + if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[1], eos), &bin)) { + return enif_make_badarg(env); + } + + rc = sqlite3_exec(conn->db, (char *) bin.data, NULL, NULL, NULL); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + + /* * Prepare the sql statement */ @@ -1353,9 +1385,9 @@ esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) case SQLITE_DONE: /* since 3.6.23.1 it is no longer required to do an explict reset. */ - return make_atom(env, "done"); + return make_atom(env, "$done"); case SQLITE_BUSY: - return make_atom(env, "busy"); + return make_atom(env, "$busy"); } return make_sqlite3_error_tuple(env, rc); @@ -1685,6 +1717,7 @@ static ErlNifFunc nif_funcs[] = { {"open", 1, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"close", 1, esqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"exec", 2, esqlite_exec}, {"prepare", 3, esqlite_prepare}, {"column_names", 1, esqlite_column_names}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 7d0be54..7e74d29 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -28,6 +28,7 @@ % set_update_hook/2, set_update_hook/3, + exec/2, prepare/2, prepare/3, @@ -45,7 +46,6 @@ step/1, reset/1 -% exec/2, exec/3, exec/4, % insert/2, insert/3, % % fetchone/1, @@ -391,6 +391,17 @@ changes(#esqlite3{db=Connection}) -> get_autocommit(#esqlite3{db=Connection}) -> esqlite3_nif:get_autocommit(Connection). +%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in +%% queries. +%% +-spec exec(Connection, Sql) -> ExecResult + when Connection :: esqlite3(), + Sql :: sql(), + ExecResult :: ok | {error, _}. +exec(#esqlite3{db=Connection}, Sql) -> + esqlite3_nif:exec(Connection, Sql). + + %% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in %% queries. %% @@ -476,8 +487,6 @@ column_names(#esqlite3_stmt{stmt=Stmt}) -> column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:column_decltypes(Stmt). - - %% %% Backup API %% diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 2f9b5a2..65b8fc9 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -27,6 +27,7 @@ last_insert_rowid/1, changes/1, + exec/2, prepare/3, column_names/1, @@ -97,6 +98,16 @@ open(_Filename) -> close(_Db) -> erlang:nif_error(nif_library_not_loaded). +%% @doc Execute a sql statement +%% +-spec exec(Connection, Sql) -> ExecResult + when Connection :: esqlite3(), + Sql :: sql(), + ExecResult :: ok | {error, _}. +exec(_Connection, _Sql) -> + erlang:nif_error(nif_library_not_loaded). + + %% @doc Compile a sql statement. %% -spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 2ecef5c..9073427 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -83,21 +83,21 @@ step_test() -> {ok, Stmt} = esqlite3:prepare(C, "select 1, 2, 3;" ), ?assertEqual([1,2,3], esqlite3:step(Stmt)), - ?assertEqual(done, esqlite3:step(Stmt)), + ?assertEqual('$done', esqlite3:step(Stmt)), %% After the done, the statement is reset and ?assertEqual([1,2,3], esqlite3:step(Stmt)), - ?assertEqual(done, esqlite3:step(Stmt)), + ?assertEqual('$done', esqlite3:step(Stmt)), ok. +iodata_test() -> + {ok, C} = esqlite3:open(":memory:"), -%iodata_test() -> -% {ok, C} = esqlite3:open(":memory:"), -% {error, no_iodata} = esqlite3:exec(1000, C), -% {error, no_iodata} = esqlite3:insert(1000, C), + ?assertError(badarg, esqlite3:exec(C, 1000)), + ?assertError(badarg, esqlite3:exec(C, 1000)), -% ok. + ok. open_multiple_same_databases_test() -> cleanup(), @@ -117,25 +117,25 @@ open_multiple_different_databases_test() -> cleanup(), ok. -%get_autocommit_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), -% true = esqlite3:get_autocommit(Db), -% ok = esqlite3:exec("BEGIN;", Db), -% false = esqlite3:get_autocommit(Db), -% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), -% ok = esqlite3:exec("COMMIT;", Db), -% true = esqlite3:get_autocommit(Db), -% ok. +get_autocommit_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"), + true = esqlite3:get_autocommit(Db), + ok = esqlite3:exec(Db, "BEGIN;"), + false = esqlite3:get_autocommit(Db), + ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"), + ok = esqlite3:exec(Db, "COMMIT;"), + true = esqlite3:get_autocommit(Db), + ok. -%last_insert_rowid_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), -% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), -% {ok, 1} = esqlite3:last_insert_rowid(Db), -% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is another test');", Db), -% {ok, 2} = esqlite3:last_insert_rowid(Db), -% ok. +last_insert_rowid_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"), + ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"), + 1 = esqlite3:last_insert_rowid(Db), + ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is another test');"), + 2 = esqlite3:last_insert_rowid(Db), + ok. %update_hook_test() -> % {ok, Db} = esqlite3:open(":memory:"), @@ -149,44 +149,44 @@ open_multiple_different_databases_test() -> % ok = receive {delete, "test", 1} -> ok after 150 -> no_message end, % ok. -%simple_query_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% {ok, 1} = esqlite3:changes(Db), -% -% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), -% {ok, 1} = esqlite3:changes(Db), -% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), -% {ok, 1} = esqlite3:changes(Db), -% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), -% {ok, 1} = esqlite3:changes(Db), -% ok = esqlite3:exec("commit;", Db), -% ok = esqlite3:exec("select * from test_table;", Db), -% -% ok = esqlite3:exec("delete from test_table;", Db), -% {ok, 4} = esqlite3:changes(Db), +simple_query_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:exec(Db, "begin;"), + ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), + ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"), + ?assertEqual(1, esqlite3:changes(Db)), -% ok. + ok = esqlite3:exec(Db, "insert into test_table values('hello2', 11);"), + ?assertEqual(1, esqlite3:changes(Db)), + ok = esqlite3:exec(Db, "insert into test_table values('hello3', 12);"), + ?assertEqual(1, esqlite3:changes(Db)), + ok = esqlite3:exec(Db, "insert into test_table values('hello4', 13);"), + ?assertEqual(1, esqlite3:changes(Db)), + ok = esqlite3:exec(Db, "commit;"), + ok = esqlite3:exec(Db, "select * from test_table;"), + + ok = esqlite3:exec(Db, "delete from test_table;"), + ?assertEqual(4, esqlite3:changes(Db)), + + ok. + +prepare2_test() -> + {ok, Db} = esqlite3:open(":memory:"), + esqlite3:exec(Db, "begin;"), + esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), + {ok, Statement} = esqlite3:prepare(Db, "insert into test_table values('one', 2)"), + + '$done' = esqlite3:step(Statement), + 1 = esqlite3:changes(Db), + + ok = esqlite3:exec(Db, "insert into test_table values('hello4', 13);"), -%prepare_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% esqlite3:exec("begin;", Db), -% esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% {ok, Statement} = esqlite3:prepare("insert into test_table values('one', 2)", Db), -% -% '$done' = esqlite3:step(Statement), -% {ok, 1} = esqlite3:changes(Db), -% -% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), -% %% Check if the values are there. -% [{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q("select * from test_table order by two", Db), -% esqlite3:exec("commit;", Db), -% esqlite3:close(Db), + %% [{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q(Db, "select * from test_table order by two"), + esqlite3:exec(Db, "commit;"), + esqlite3:close(Db), -% ok. + ok. %bind_test() -> % {ok, Db} = esqlite3:open(":memory:"), @@ -339,41 +339,43 @@ open_multiple_different_databases_test() -> % {} = esqlite3:column_types(Stmt2), % % ok. -% -%nil_column_types_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table t1(c1 variant);", Db), -% ok = esqlite3:exec("commit;", Db), -% {ok, Stmt} = esqlite3:prepare("select c1 + 1, c1 from t1", Db), -% {nil, variant} = esqlite3:column_types(Stmt), -% ok. +nil_column_decltypes_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:exec(Db, "begin;"), + ok = esqlite3:exec(Db, "create table t1(c1 variant);"), + ok = esqlite3:exec(Db, "commit;"), -%reset_test() -> -% {ok, Db} = esqlite3:open(":memory:"), + {ok, Stmt} = esqlite3:prepare(Db, "select c1 + 1, c1 from t1"), + ?assertEqual([undefined, <<"variant">>], esqlite3:column_decltypes(Stmt)), + + ok. + +reset_test() -> + {ok, Db} = esqlite3:open(":memory:"), + + {ok, Stmt} = esqlite3:prepare(Db, "select * from (values (1), (2));"), + [1] = esqlite3:step(Stmt), + + ok = esqlite3:reset(Stmt), + + [1] = esqlite3:step(Stmt), + [2] = esqlite3:step(Stmt), + '$done' = esqlite3:step(Stmt), + + % After a done the statement is automatically reset. + [1] = esqlite3:step(Stmt), + + % Calling reset multiple times... + ok = esqlite3:reset(Stmt), + ok = esqlite3:reset(Stmt), + ok = esqlite3:reset(Stmt), + ok = esqlite3:reset(Stmt), -% {ok, Stmt} = esqlite3:prepare("select * from (values (1), (2));", Db), -% {row, {1}} = esqlite3:step(Stmt), -% -% ok = esqlite3:reset(Stmt), -% {row, {1}} = esqlite3:step(Stmt), -% {row, {2}} = esqlite3:step(Stmt), -% '$done' = esqlite3:step(Stmt), -% -% % After a done the statement is automatically reset. -% {row, {1}} = esqlite3:step(Stmt), -% -% % Calling reset multiple times... -% ok = esqlite3:reset(Stmt), -% ok = esqlite3:reset(Stmt), -% ok = esqlite3:reset(Stmt), -% ok = esqlite3:reset(Stmt), -% % The statement should still be reset. -% {row, {1}} = esqlite3:step(Stmt), -% -% ok. + [1] = esqlite3:step(Stmt), + + ok. % %foreach_test() -> % {ok, Db} = esqlite3:open(":memory:"), @@ -583,20 +585,20 @@ open_multiple_different_databases_test() -> % ok. -%sqlite_version_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% {ok, Stmt} = esqlite3:prepare("select sqlite_version() as sqlite_version;", Db), -% {sqlite_version} = esqlite3:column_names(Stmt), -%% ?assertEqual({row, {<<"3.38.0">>}}, esqlite3:step(Stmt)), -% ok. +sqlite_version_test() -> + {ok, Db} = esqlite3:open(":memory:"), + {ok, Stmt} = esqlite3:prepare(Db, "select sqlite_version() as sqlite_version;"), + [<<"sqlite_version">>] = esqlite3:column_names(Stmt), + ?assertEqual([<<"3.38.0">>], esqlite3:step(Stmt)), + ok. -%sqlite_source_id_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% {ok, Stmt} = esqlite3:prepare("select sqlite_source_id() as sqlite_source_id;", Db), -% {sqlite_source_id} = esqlite3:column_names(Stmt), -% ?assertEqual({row, {<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>}}, -% esqlite3:step(Stmt)), -% ok. +sqlite_source_id_test() -> + {ok, Db} = esqlite3:open(":memory:"), + {ok, Stmt} = esqlite3:prepare(Db, "select sqlite_source_id() as sqlite_source_id;"), + [<<"sqlite_source_id">>] = esqlite3:column_names(Stmt), + ?assertEqual([<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>], + esqlite3:step(Stmt)), + ok. %interrupt_on_timeout_test() -> % {ok, Db} = esqlite3:open(":memory:"), From cc76b4319f55f3485770958cb78f641d8538bf33 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 21 May 2022 22:58:22 +0200 Subject: [PATCH 12/37] Fixed returning ok and errors for prepare and exec --- c_src/esqlite3_nif.c | 122 +++--------------------------------------- test/esqlite_test.erl | 26 ++++----- 2 files changed, 20 insertions(+), 128 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index e964d25..b76c9d2 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -280,7 +280,7 @@ make_extended_error_tuple(ErlNifEnv *env, int code) { /* internal use only */ return make_two_atom_tuple(env, "ok", "symlink"); default: - return make_two_atom_tuple(env, "error", "unknown"); + return make_two_atom_tuple(env, "error", enif_make_int(env, code)); } } @@ -926,7 +926,7 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) unsigned int prep_flags; if(!enif_get_uint(env, argv[2], &prep_flags)) { - return make_error_tuple(env, "invalid_chunk_size"); + return enif_make_badarg(env); } stmt = enif_alloc_resource(esqlite3_stmt_type, sizeof(esqlite3_stmt)); @@ -984,38 +984,6 @@ set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) */ -/* - * Count the nr of changes of last statement -static ERL_NIF_TERM -esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 3) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - // command - cmd->type = cmd_changes; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - - return push_command(env, db, cmd); -} - */ - - /* * Bind a variable to a prepared statement static ERL_NIF_TERM @@ -1052,87 +1020,6 @@ esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } */ -/* - * Multi step to a prepared statement -static ERL_NIF_TERM -esqlite_multi_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *conn; - esqlite3_stmt *stmt; - esqlite_command *cmd = NULL; - ErlNifPid pid; - int chunk_size = 0; - - if(argc != 5) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) - return enif_make_badarg(env); - - if(!enif_get_int(env, argv[2], &chunk_size)) - return make_error_tuple(env, "invalid_chunk_size"); - - if(!enif_is_ref(env, argv[3])) - return make_error_tuple(env, "invalid_ref"); - - if(!enif_get_local_pid(env, argv[4], &pid)) - return make_error_tuple(env, "invalid_pid"); - - if(!stmt->statement) - return make_error_tuple(env, "no_prepared_statement"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_multi_step; - cmd->ref = enif_make_copy(cmd->env, argv[3]); - cmd->pid = pid; - cmd->stmt = enif_make_copy(cmd->env, argv[1]); - cmd->arg = enif_make_copy(cmd->env, argv[2]); - - return push_command(env, conn, cmd); -} - */ - -/* - * Reset a prepared statement to its initial state -static ERL_NIF_TERM -esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *conn; - esqlite3_stmt *stmt; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); - if(!stmt->statement) - return make_error_tuple(env, "no_prepared_statement"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_reset; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->stmt = enif_make_copy(cmd->env, argv[1]); - - return push_command(env, conn, cmd); -} - */ /* * Get the column names of the prepared statement. @@ -1411,7 +1298,10 @@ esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } int rc = sqlite3_reset(stmt->statement); - return make_sqlite3_error_tuple(env, rc); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + return make_atom(env, "ok"); } diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 9073427..1dfc544 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -22,19 +22,15 @@ close_test() -> %% Double close should also work. ok = esqlite3:close(C), - ok. - - %% Check if functions still return sensible values. % {error, closed} = esqlite3:set_update_hook(self(), C), -% {error, closed} = esqlite3:changes(C), -% {error, closed} = esqlite3:get_autocommit(C), -% {error, closed} = esqlite3:last_insert_rowid(C), + {error, closed} = esqlite3:changes(C), + {error, closed} = esqlite3:get_autocommit(C), + {error, closed} = esqlite3:last_insert_rowid(C), -% {error, _} = esqlite3:exec("create table test(one, two, three)", C), - -% ok. + ?assertEqual({error, {misuse, invoked_incorrectly}}, esqlite3:exec(C, "create table test(one, two, three)")), + ok. prepare_test() -> {ok, C} = esqlite3:open(":memory:"), @@ -45,9 +41,7 @@ prepare_test() -> prepare_after_close_test() -> {ok, C} = esqlite3:open(":memory:"), ?assertEqual(ok, esqlite3:close(C)), - - ?assertMatch({error, {misuse, _}}, esqlite3:prepare(C, "select 1")), - + ?assertMatch({error, {misuse, invoked_incorrectly}}, esqlite3:prepare(C, "select 1")), ok. column_names_test() -> @@ -119,13 +113,21 @@ open_multiple_different_databases_test() -> get_autocommit_test() -> {ok, Db} = esqlite3:open(":memory:"), + + %% By default, the database is in autocommit mode + true = esqlite3:get_autocommit(Db), ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"), true = esqlite3:get_autocommit(Db), + + %% After a begin statement, the connection will not be in autocommit mode anymore ok = esqlite3:exec(Db, "BEGIN;"), false = esqlite3:get_autocommit(Db), ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"), ok = esqlite3:exec(Db, "COMMIT;"), + + %% After a commit statement, the connection will be in autocommit mode true = esqlite3:get_autocommit(Db), + ok. last_insert_rowid_test() -> From 8aae31abdaa46249c5aff1ad712ab9f0fe6d7570 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 22 May 2022 13:07:57 +0200 Subject: [PATCH 13/37] Added update hook --- c_src/esqlite3_nif.c | 240 +++++++++++++++--------------------------- src/esqlite3.erl | 44 ++++---- src/esqlite3_nif.erl | 19 ++-- test/esqlite_test.erl | 34 ++++-- 4 files changed, 137 insertions(+), 200 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index b76c9d2..dd289e9 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -1,5 +1,5 @@ /* - * Copyright 2011 - 2017 Maas-Maarten Zeeman + * Copyright 2011 - 2022 Maas-Maarten Zeeman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ static ErlNifResourceType *esqlite3_backup_type = NULL; /* database connection context */ typedef struct { sqlite3 *db; + + ErlNifPid *update_hook_pid; } esqlite3; /* prepared statement */ @@ -279,9 +281,8 @@ make_extended_error_tuple(ErlNifEnv *env, int code) { case SQLITE_OK_SYMLINK: /* internal use only */ return make_two_atom_tuple(env, "ok", "symlink"); - default: - return make_two_atom_tuple(env, "error", enif_make_int(env, code)); } + return enif_make_tuple2(env, make_atom(env, "error"), enif_make_int(env, code)); } static const char * @@ -335,45 +336,6 @@ destruct_esqlite3_backup(ErlNifEnv *env, void *arg) backup->backup = NULL; } -/* -void -update_callback(void *arg, int sqlite_operation_type, char const *sqlite_database, char const *sqlite_table, sqlite3_int64 sqlite_rowid) -{ - esqlite3 *db = (esqlite3 *)arg; - esqlite_command *cmd = NULL; - ERL_NIF_TERM type, table, rowid; - - if(db == NULL) - return; - - cmd = command_create(); - if(!cmd) - return; - - rowid = enif_make_int64(cmd->env, sqlite_rowid); - table = enif_make_string(cmd->env, sqlite_table, ERL_NIF_LATIN1); - - switch(sqlite_operation_type) { - case SQLITE_INSERT: - type = make_atom(cmd->env, "insert"); - break; - case SQLITE_DELETE: - type = make_atom(cmd->env, "delete"); - break; - case SQLITE_UPDATE: - type = make_atom(cmd->env, "update"); - break; - default: - return; - } - - cmd->type = cmd_notification; - cmd->arg = enif_make_tuple3(cmd->env, type, table, rowid); - - push_command(cmd->env, db, cmd); -} -*/ - /* static ERL_NIF_TERM do_set_update_hook(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) @@ -392,80 +354,6 @@ do_set_update_hook(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) } */ -/* -static ERL_NIF_TERM -do_exec(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) -{ - ErlNifBinary bin; - int rc; - ERL_NIF_TERM eos = enif_make_int(env, 0); - - if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, arg, eos), &bin)) { - return make_error_tuple(env, "no_iodata"); - } - - rc = sqlite3_exec(conn->db, (char *) bin.data, NULL, NULL, NULL); - if(rc != SQLITE_OK) - return make_sqlite3_error_tuple(env, rc, conn->db); - - return make_atom(env, "ok"); -} - */ - -/* - * Nr of changes -static ERL_NIF_TERM -do_changes(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) -{ - if(!conn->db) { - return make_error_tuple(env, "closed"); - } - - sqlite3_int64 changes = sqlite3_changes64(conn->db); - ERL_NIF_TERM changes_term = enif_make_int64(env, changes); - - return make_ok_tuple(env, changes_term); -} - */ - -/* -* insert action -static ERL_NIF_TERM -do_insert(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) -{ - ErlNifBinary bin; - int rc; - ERL_NIF_TERM eos = enif_make_int(env, 0); - - if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, arg, eos), &bin)) { - return make_error_tuple(env, "no_iodata"); - } - - rc = sqlite3_exec(conn->db, (char *) bin.data, NULL, NULL, NULL); - if(rc != SQLITE_OK) - return make_sqlite3_error_tuple(env, rc, conn->db); - sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(conn->db); - ERL_NIF_TERM last_rowid_term = enif_make_int64(env, last_rowid); - return make_ok_tuple(env, last_rowid_term); -} -*/ - -/* - * Return the last inserted rowid -static ERL_NIF_TERM -do_last_insert_rowid(ErlNifEnv *env, esqlite3 *conn) -{ - if(!conn->db) { - return make_error_tuple(env, "closed"); - } - - sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(conn->db); - ERL_NIF_TERM last_rowid_term = enif_make_int64(env, last_rowid); - - return make_ok_tuple(env, last_rowid_term); -} - - */ /* static int @@ -854,17 +742,92 @@ esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); } - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) { return enif_make_badarg(env); + } rc = sqlite3_close_v2(conn->db); - if(rc != SQLITE_OK) + if(rc != SQLITE_OK) { return make_sqlite3_error_tuple(env, rc); + } conn->db = NULL; return make_atom(env, "ok"); } +void +update_callback(void *arg, int sqlite_operation_type, char const *sqlite_database, char const *sqlite_table, sqlite3_int64 sqlite_rowid) +{ + esqlite3 *conn = (esqlite3 *)arg; + + if(conn == NULL) { + return; + } + + /* Create a message environment */ + ErlNifEnv *msg_env = enif_alloc_env(); + + ERL_NIF_TERM type; + + switch(sqlite_operation_type) { + case SQLITE_INSERT: + type = make_atom(msg_env, "insert"); + break; + case SQLITE_DELETE: + type = make_atom(msg_env, "delete"); + break; + case SQLITE_UPDATE: + type = make_atom(msg_env, "update"); + break; + default: + return; + } + ERL_NIF_TERM rowid = enif_make_int64(msg_env, sqlite_rowid); + ERL_NIF_TERM database = make_binary(msg_env, sqlite_database, strlen(sqlite_database)); + ERL_NIF_TERM table = make_binary(msg_env, sqlite_table, strlen(sqlite_table)); + + ERL_NIF_TERM msg = enif_make_tuple4(msg_env, type, database, table, rowid); + + if(!enif_send(NULL, &conn->update_hook_pid, msg_env, msg)) { + sqlite3_update_hook(conn->db, NULL, NULL); + } +} + + +static ERL_NIF_TERM +esqlite_set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; + int rc; + + if(argc != 2) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) { + return enif_make_badarg(env); + } + + if(!conn->db) { + return make_error_tuple(env, "closed"); + } + + if(enif_is_atom(env, argv[1])) { + /* Assume this is undefined, reset the connection */ + sqlite3_update_hook(conn->db, NULL, NULL); + } else { + /* [todo] passing undefined resets the hook? */ + if(!enif_get_local_pid(env, argv[1], &conn->update_hook_pid)) { + return enif_make_badarg(env); + } + + sqlite3_update_hook(conn->db, update_callback, conn); + } + + return make_atom(env, "ok"); +} + + /* * Exec a sql statement */ @@ -984,43 +947,6 @@ set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) */ -/* - * Bind a variable to a prepared statement -static ERL_NIF_TERM -esqlite_bind(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *conn; - esqlite3_stmt *stmt; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 5) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) - return enif_make_badarg(env); - if(!enif_get_resource(env, argv[1], esqlite3_stmt_type, (void **) &stmt)) - return enif_make_badarg(env); - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_bind; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->stmt = enif_make_copy(cmd->env, argv[1]); - cmd->arg = enif_make_copy(cmd->env, argv[4]); - - return push_command(env, conn, cmd); -} - */ - - /* * Get the column names of the prepared statement. */ @@ -1241,7 +1167,6 @@ esqlite_bind_null(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } - static ERL_NIF_TERM esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { @@ -1607,6 +1532,8 @@ static ErlNifFunc nif_funcs[] = { {"open", 1, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"close", 1, esqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"set_update_hook", 2, esqlite_set_update_hook}, + {"exec", 2, esqlite_exec}, {"prepare", 3, esqlite_prepare}, @@ -1640,7 +1567,6 @@ static ErlNifFunc nif_funcs[] = { {"changes", 1, esqlite_changes}, /* - {"set_update_hook", 4, set_update_hook}, {"exec", 4, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 7e74d29..3fd624f 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -22,12 +22,13 @@ open/1, close/1, %% db connection functions + + set_update_hook/2, + get_autocommit/1, last_insert_rowid/1, changes/1, -% set_update_hook/2, set_update_hook/3, - exec/2, prepare/2, prepare/3, @@ -46,7 +47,6 @@ step/1, reset/1 -% insert/2, insert/3, % % fetchone/1, % fetchall/1, fetchall/2, fetchall/3, @@ -138,27 +138,20 @@ close(#esqlite3{db=Connection}) -> esqlite3_nif:close(Connection). -%%% @doc Subscribe to database notifications. When rows are inserted deleted -%%% or updates, the process will receive messages: -%%% ```{insert, string(), rowid()}''' -%%% When a new row has been inserted. -%%% ```{delete, string(), rowid()}''' -%%% When a new row has been deleted. -%%% ```{update, string(), rowid()}''' -%%% When a row has been updated. -%%% -%-spec set_update_hook(pid(), connection()) -> ok | {error, term()}. -%set_update_hook(Pid, Connection) -> -% set_update_hook(Pid, Connection, ?DEFAULT_TIMEOUT). -% -%%% @doc Same as set_update_hook/2, but with an additional timeout parameter. -%%% -%-spec set_update_hook(pid(), connection(), timeout()) -> ok | {error, term()}. -%set_update_hook(Pid, #connection{raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:set_update_hook(RawConnection, Ref, self(), Pid), -% receive_answer(RawConnection, Ref, Timeout). -% +%% @doc Subscribe to database notifications. When rows are inserted deleted +%% or updates, the process will receive messages: +%% ```{insert, binary(), binary(), rowid()}''' +%% When a new row has been inserted. +%% ```{delete, binary(), binary(), rowid()}''' +%% When a new row has been deleted. +%% ```{update, binary(), binary(), rowid()}''' +%% When a row has been updated. +%% +-spec set_update_hook(esqlite3(), pid() | undefined) -> ok | {error, term()}. +set_update_hook(#esqlite3{db=Connection}, MaybePid) when is_pid(MaybePid) orelse MaybePid =:= undefined -> + esqlite3_nif:set_update_hook(Connection, MaybePid). + + %%% %%% q %%% @@ -202,7 +195,7 @@ close(#esqlite3{db=Connection}) -> % %%% @doc Execute statement and return a list with the result of F for each row. %-spec map(Fun, sql(), connection()) -> list(Type) when -%% Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), +% Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), % Row :: row(), % ColumnNames :: tuple(), % Type :: any(). @@ -401,7 +394,6 @@ get_autocommit(#esqlite3{db=Connection}) -> exec(#esqlite3{db=Connection}, Sql) -> esqlite3_nif:exec(Connection, Sql). - %% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in %% queries. %% diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 65b8fc9..0e5c21f 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -23,10 +23,11 @@ open/1, close/1, + set_update_hook/2, + get_autocommit/1, last_insert_rowid/1, changes/1, - exec/2, prepare/3, @@ -45,7 +46,6 @@ reset/1, interrupt/1 -% set_update_hook/4, % exec/4, % changes/3, % @@ -94,10 +94,20 @@ open(_Filename) -> %% -spec close(Connection) -> CloseResult when Connection :: esqlite3(), - CloseResult :: ok | {error, _}. + CloseResult :: ok | {error, _}. close(_Db) -> erlang:nif_error(nif_library_not_loaded). +%% @doc Set an update hook +%% +-spec set_update_hook(Connection, Pid) -> Result + when Connection :: esqlite3(), + Pid :: pid(), + Result :: ok | {error, _}. +set_update_hook(_Db, _Pid) -> + erlang:nif_error(nif_library_not_loaded). + + %% @doc Execute a sql statement %% -spec exec(Connection, Sql) -> ExecResult @@ -136,9 +146,6 @@ step(_Statement) -> reset(_Statement) -> erlang:nif_error(nif_library_not_loaded). -% -spec set_update_hook((), pid(), pid()) -> ok | {error, _}. -%set_update_hook(_Db, _Ref, _Dest, _Pid) -> -% erlang:nif_error(nif_library_not_loaded). %% @doc Exec the query. %% diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 1dfc544..1611cda 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -139,17 +139,29 @@ last_insert_rowid_test() -> 2 = esqlite3:last_insert_rowid(Db), ok. -%update_hook_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:set_update_hook(self(), Db), -% ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db), -% ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db), -% ok = receive {insert, "test", 1} -> ok after 150 -> no_message end, -% ok = esqlite3:exec("UPDATE test SET val = 'a new test' WHERE id = 1;", Db), -% ok = receive {update, "test", 1} -> ok after 150 -> no_message end, -% ok = esqlite3:exec("DELETE FROM test WHERE id = 1;", Db), -% ok = receive {delete, "test", 1} -> ok after 150 -> no_message end, -% ok. +update_hook_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:set_update_hook(Db, self()), + + ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"), + ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"), + + ok = receive {insert, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end, + + ok = esqlite3:exec(Db, "UPDATE test SET val = 'a new test' WHERE id = 1;"), + + ok = receive {update, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end, + + ok = esqlite3:exec(Db, "DELETE FROM test WHERE id = 1;"), + + ok = receive {delete, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end, + + ok = esqlite3:set_update_hook(Db, undefined), + + ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"), + no_message = receive {insert, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end, + + ok. simple_query_test() -> {ok, Db} = esqlite3:open(":memory:"), From 20af1ea0bda604826f8584b984457faeebfb2ddd Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 22 May 2022 13:20:12 +0200 Subject: [PATCH 14/37] Removed commented out code --- src/esqlite3.erl | 70 ------------------------------------------------ 1 file changed, 70 deletions(-) diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 3fd624f..0b475f2 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -304,59 +304,6 @@ set_update_hook(#esqlite3{db=Connection}, MaybePid) when is_pid(MaybePid) orelse % {error, _} = E -> E % end. -%% @doc Execute Sql statement. -%% -%-spec exec(sql(), connection()) -> ok | {error, _}. -%exec(Sql, Connection) -> -% exec(Sql, [], Connection, ?DEFAULT_TIMEOUT). -% -%-spec exec(sql(), list(cell_type()) | connection(), connection() | timeout()) -> ok | {error, _}. -%exec(Sql, #connection{}=Connection, Timeout) -> -% exec(Sql, [], Connection, Timeout); -%exec(Sql, Params, #connection{}=Connection) -> -% exec(Sql, Params, Connection, ?DEFAULT_TIMEOUT). - -%-spec exec(sql(), list(cell_type()), connection(), timeout()) -> ok | {error, _}. -%exec(Sql, [], #connection{raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:exec(RawConnection, Ref, self(), Sql), -% receive_answer(RawConnection, Ref, Timeout); -%exec(Sql, Params, Connection, Timeout) -> -% {ok, Statement} = prepare(Sql, Connection, Timeout), -% case bind(Statement, Params) of -% ok -> -% step(Statement, Timeout); -% {error, _}=Error -> -% Error -% end. - - -%% @doc Return the number of affected rows of last statement. -%-spec changes(connection()) -> non_neg_integer(). -%changes(Connection) -> -% changes(Connection, ?DEFAULT_TIMEOUT). -% -%-spec changes(connection(), timeout()) -> non_neg_integer(). -%changes(#connection{raw_connection=RawConnection}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:changes(RawConnection, Ref, self()), -% receive_answer(RawConnection, Ref, Timeout). - - -%%% @doc Insert records, returns the last rowid. -%% -%-spec insert(sql(), connection()) -> {ok, rowid()} | {error, _}. -%insert(Sql, Connection) -> -% insert(Sql, Connection, ?DEFAULT_TIMEOUT). - -%% @doc Like insert/2, but with extra timeout parameter. -%-spec insert(sql(), connection(), timeout()) -> {ok, rowid()} | {error, _}. -%insert(Sql, #connection{raw_connection=RawConnection}, Timeout) -> -% [NOTE] it is an exec followed by a last_insert_rowid. -% Ref = make_ref(), -% ok = esqlite3_nif:insert(RawConnection, Ref, self(), Sql), -% receive_answer(RawConnection, Ref, Timeout). - %% @doc Get the last insert rowid. %% @@ -652,27 +599,10 @@ column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> % Else -> Else % end. -%receive_answer(RawConnection, Ref, Timeout) -> -% receive -% {esqlite3, Ref, Resp} -> Resp -% after -% Timeout -> -% ok = esqlite3_nif:interrupt(RawConnection), -% throw({error, timeout, Ref}) -% end. - -%flush_answers() -> -% receive -% {esqlite3, _, _} -> flush_answers() -% after -% 0 -> ok -% end. - %% %% Helpers %% - props_to_prepare_flag(Props) -> Flag = case proplists:get_value(no_vtab, Props, false) of true -> ?SQLITE_PREPARE_NO_VTAB; From c5f33d31bdf774e4ad7fa2a7678553ac50a2ff57 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 22 May 2022 16:54:21 +0200 Subject: [PATCH 15/37] Added bind blob & text --- c_src/esqlite3_nif.c | 116 +++++++++++++++++++++++++++++++----------- src/esqlite3.erl | 15 ++++-- src/esqlite3_nif.erl | 21 ++++---- test/esqlite_test.erl | 5 +- 4 files changed, 110 insertions(+), 47 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index dd289e9..ef285f7 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -486,37 +486,23 @@ make_cell(ErlNifEnv *env, sqlite3_stmt *statement, unsigned int i) int type = sqlite3_column_type(statement, i); switch(type) { + case SQLITE_NULL: + return make_atom(env, "undefined"); case SQLITE_INTEGER: return enif_make_int64(env, sqlite3_column_int64(statement, i)); case SQLITE_FLOAT: return enif_make_double(env, sqlite3_column_double(statement, i)); case SQLITE_BLOB: - return enif_make_tuple2(env, make_atom(env, "blob"), - make_binary(env, sqlite3_column_blob(statement, i), - sqlite3_column_bytes(statement, i))); - case SQLITE_NULL: - return make_atom(env, "undefined"); + return make_binary(env, sqlite3_column_blob(statement, i), + sqlite3_column_bytes(statement, i)); case SQLITE_TEXT: return make_binary(env, sqlite3_column_text(statement, i), sqlite3_column_bytes(statement, i)); } + return enif_raise_exception(env, make_atom(env, "internal_error")); } - -/* -static ERL_NIF_TERM -do_reset(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt) -{ - int rc = sqlite3_reset(stmt); - if(rc != SQLITE_OK) - return make_sqlite3_error_tuple(env, rc, db); - - return make_atom(env, "ok"); -} -*/ - - /* static ERL_NIF_TERM do_backup_init(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) @@ -1136,6 +1122,82 @@ esqlite_bind_double(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +static ERL_NIF_TERM +esqlite_bind_text(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + int index; + ErlNifBinary text; + + if(argc != 3) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + if(!enif_get_int(env, argv[1], &index)) { + return enif_make_badarg(env); + } + + if(!enif_inspect_iolist_as_binary(env, argv[1], &text)) { + return enif_make_badarg(env); + } + + /* + * Don't do any checks on the input data, sqlite handes all kinds of input. It is + * garbage-in, garbage-out. + * + */ + + int rc = sqlite3_bind_text64(stmt->statement, index, text.data, text.size, SQLITE_TRANSIENT, SQLITE_UTF8); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + +static ERL_NIF_TERM +esqlite_bind_blob(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_stmt *stmt; + int index; + ErlNifBinary blob; + + if(argc != 3) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_stmt_type, (void **) &stmt)) { + return enif_make_badarg(env); + } + + if(!stmt->statement) { + return enif_raise_exception(env, make_atom(env, "no_prepared_statement")); + } + + if(!enif_get_int(env, argv[1], &index)) { + return enif_make_badarg(env); + } + + if(!enif_inspect_iolist_as_binary(env, argv[1], &blob)) { + return enif_make_badarg(env); + } + + int rc = sqlite3_bind_blob64(stmt->statement, index, blob.data, blob.size, SQLITE_TRANSIENT); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + static ERL_NIF_TERM esqlite_bind_null(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { @@ -1543,23 +1605,22 @@ static ErlNifFunc nif_funcs[] = { {"bind_int", 3, esqlite_bind_int}, {"bind_int64", 3, esqlite_bind_int64}, {"bind_double", 3, esqlite_bind_double}, - + {"bind_text", 3, esqlite_bind_text}, + {"bind_blob", 3, esqlite_bind_blob}, {"bind_null", 2, esqlite_bind_null}, /* {"bind_text", 3, esqlite_bind_blob}, - {"bind_blob", 3, esqlite_bind_blob}, - {"bind_null", 2, esqlite_bind_blob}, */ {"step", 1, esqlite_step}, {"reset", 1, esqlite_reset}, /* - Other interesting additions... trace. - wal_hook triggered after every commit. - also interesting commit and rollback_hooks - */ + * Other interesting additions... trace. + * wal_hook triggered after every commit. + * also interesting commit and rollback_hooks + */ {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"last_insert_rowid", 1, esqlite_last_insert_rowid}, @@ -1567,9 +1628,6 @@ static ErlNifFunc nif_funcs[] = { {"changes", 1, esqlite_changes}, /* - - {"exec", 4, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"backup_init", 6, esqlite_backup_init}, {"backup_step", 5, esqlite_backup_step}, {"backup_remaining", 4, esqlite_backup_remaining}, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 0b475f2..fc650dc 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -22,13 +22,13 @@ open/1, close/1, %% db connection functions - set_update_hook/2, get_autocommit/1, last_insert_rowid/1, changes/1, + %% queries exec/2, prepare/2, prepare/3, @@ -41,13 +41,13 @@ bind_int64/3, bind_double/3, % bind_text/3, - % bind_blob/3, + bind_blob/3, bind_null/2, step/1, reset/1 -% + % fetchone/1, % fetchall/1, fetchall/2, fetchall/3, % @@ -63,7 +63,6 @@ -define(DEFAULT_TIMEOUT, infinity). -define(DEFAULT_CHUNK_SIZE, 5000). - -define(SQLITE_PREPARE_PERSISTENT, 16#01). -define(SQLITE_PREPARE_NO_VTAB, 16#04). @@ -391,6 +390,14 @@ bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_double(Stmt, Index, Value). +-spec bind_blob(Statement, Index, Value) -> BindResult + when Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: iodata(), + BindResult :: ok | {error, _}. +bind_blob(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> + esqlite3_nif:bind_blob(Stmt, Index, Value). + -spec bind_null(Statement, Index) -> BindResult when Statement :: esqlite3_stmt(), Index :: integer(), diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 0e5c21f..e9232ec 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -37,8 +37,8 @@ bind_int/3, bind_int64/3, bind_double/3, - %bind_text/3, - %bind_blob/3, + bind_text/3, + bind_blob/3, bind_null/2, step/1, @@ -46,16 +46,7 @@ reset/1, interrupt/1 -% exec/4, -% changes/3, -% -% multi_step/5, -% reset/4, -% finalize/4, -% bind/5, - -% column_types/4, - + % backup_init/6, % backup_step/5, % backup_remaining/4, @@ -137,6 +128,12 @@ bind_int64(_Statement, _Index, _Value) -> bind_double(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +bind_blob(_Statement, _Index, _Value) -> + erlang:nif_error(nif_library_not_loaded). + +bind_text(_Statement, _Index, _Value) -> + erlang:nif_error(nif_library_not_loaded). + bind_null(_Statement, _Index) -> erlang:nif_error(nif_library_not_loaded). diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 1611cda..85adbf6 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -23,12 +23,13 @@ close_test() -> ok = esqlite3:close(C), %% Check if functions still return sensible values. -% {error, closed} = esqlite3:set_update_hook(self(), C), + {error, closed} = esqlite3:set_update_hook(C, self()), {error, closed} = esqlite3:changes(C), {error, closed} = esqlite3:get_autocommit(C), {error, closed} = esqlite3:last_insert_rowid(C), - ?assertEqual({error, {misuse, invoked_incorrectly}}, esqlite3:exec(C, "create table test(one, two, three)")), + ?assertEqual({error, {misuse, invoked_incorrectly}}, + esqlite3:exec(C, "create table test(one, two, three)")), ok. From 71ab29c72433ece9905482f8311338b433e3f903 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 22 May 2022 16:57:01 +0200 Subject: [PATCH 16/37] Removed commented out code --- c_src/esqlite3_nif.c | 178 ++----------------------------------------- 1 file changed, 5 insertions(+), 173 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index ef285f7..fcf44a7 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -336,133 +336,6 @@ destruct_esqlite3_backup(ErlNifEnv *env, void *arg) backup->backup = NULL; } -/* -static ERL_NIF_TERM -do_set_update_hook(ErlNifEnv *env, esqlite3 *conn, const ERL_NIF_TERM arg) -{ - if(!enif_get_local_pid(env, arg, &conn->notification_pid)) { - return make_error_tuple(env, "invalid_pid"); - } - - if(!conn->db) { - return make_error_tuple(env, "closed"); - } - - sqlite3_update_hook(conn->db, update_callback, conn); - - return make_atom(env, "ok"); -} -*/ - - -/* -static int -bind_cell(ErlNifEnv *env, const ERL_NIF_TERM cell, sqlite3_stmt *stmt, unsigned int i) -{ - int the_int; - ErlNifSInt64 the_long_int; - double the_double; - char the_atom[MAX_ATOM_LENGTH+1]; - ErlNifBinary the_blob; - int arity; - const ERL_NIF_TERM* tuple; - - if(enif_get_int(env, cell, &the_int)) - return sqlite3_bind_int(stmt, i, the_int); - - if(enif_get_int64(env, cell, &the_long_int)) - return sqlite3_bind_int64(stmt, i, the_long_int); - - if(enif_get_double(env, cell, &the_double)) - return sqlite3_bind_double(stmt, i, the_double); - - if(enif_get_atom(env, cell, the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) { - if(strncmp("undefined", the_atom, strlen("undefined")) == 0) { - return sqlite3_bind_null(stmt, i); - } - - if(strncmp("null", the_atom, strlen("null")) == 0) { - return sqlite3_bind_null(stmt, i); - } - - return sqlite3_bind_text(stmt, i, the_atom, strlen(the_atom), SQLITE_TRANSIENT); - } - - // Bind as text assume it is utf-8 encoded text - if(enif_inspect_iolist_as_binary(env, cell, &the_blob)) { - return sqlite3_bind_text(stmt, i, (char *) the_blob.data, the_blob.size, SQLITE_TRANSIENT); - } - - // Check for blob tuple - if(enif_get_tuple(env, cell, &arity, &tuple)) { - if(arity != 2) - return -1; - - // length 2! - if(enif_get_atom(env, tuple[0], the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) { - // its a blob... - if(0 == strncmp("blob", the_atom, strlen("blob"))) { - // with a iolist as argument - if(enif_inspect_iolist_as_binary(env, tuple[1], &the_blob)) { - // kaboom... get the blob - return sqlite3_bind_blob(stmt, i, the_blob.data, the_blob.size, SQLITE_TRANSIENT); - } - } - } - } - - return -1; -} -*/ - -/* -static ERL_NIF_TERM -do_bind(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg) -{ - int parameter_count = sqlite3_bind_parameter_count(stmt); - int i, is_list, r; - ERL_NIF_TERM list, head, tail; - unsigned int list_length; - - is_list = enif_get_list_length(env, arg, &list_length); - if(!is_list) - return make_error_tuple(env, "bad_arg_list"); - if(parameter_count != list_length) - return make_error_tuple(env, "args_wrong_length"); - - sqlite3_reset(stmt); - - list = arg; - for(i=0; i < list_length; i++) { - enif_get_list_cell(env, list, &head, &tail); - r = bind_cell(env, head, stmt, i+1); - if(r == -1) - return make_error_tuple(env, "wrong_type"); - if(r != SQLITE_OK) - return make_sqlite3_error_tuple(env, r, db); - list = tail; - } - - return make_atom(env, "ok"); -} -*/ - -/* -static ERL_NIF_TERM -do_get_autocommit(ErlNifEnv *env, esqlite3 *conn) -{ - if(!conn->db) { - return make_error_tuple(env, "closed"); - } - - if(sqlite3_get_autocommit(conn->db) != 0) { - return make_atom(env, "true"); - } - - return make_atom(env, "false"); -} -*/ - static ERL_NIF_TERM make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) { @@ -897,42 +770,6 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_ok_tuple(env, esqlite_stmt); } - -/* -static ERL_NIF_TERM -set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *db; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &db)) - return enif_make_badarg(env); - - if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); - - if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - // command - cmd->type = cmd_update_hook_set; - cmd->ref = enif_make_copy(cmd->env, argv[1]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[3]); - - return push_command(env, db, cmd); -} -*/ - - /* * Get the column names of the prepared statement. */ @@ -1594,6 +1431,11 @@ static ErlNifFunc nif_funcs[] = { {"open", 1, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"close", 1, esqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND}, + /* + * Other interesting additions... trace. + * wal_hook triggered after every commit. + * also interesting commit and rollback_hooks + */ {"set_update_hook", 2, esqlite_set_update_hook}, {"exec", 2, esqlite_exec}, @@ -1609,19 +1451,9 @@ static ErlNifFunc nif_funcs[] = { {"bind_blob", 3, esqlite_bind_blob}, {"bind_null", 2, esqlite_bind_null}, - /* - {"bind_text", 3, esqlite_bind_blob}, - */ - {"step", 1, esqlite_step}, {"reset", 1, esqlite_reset}, - /* - * Other interesting additions... trace. - * wal_hook triggered after every commit. - * also interesting commit and rollback_hooks - */ - {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"last_insert_rowid", 1, esqlite_last_insert_rowid}, {"get_autocommit", 1, esqlite_get_autocommit}, From 135715bf0ae4bc8901c2d352e55e0d71a4bc21f1 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 22 May 2022 22:19:30 +0200 Subject: [PATCH 17/37] Added extra function to return a map with error information --- c_src/esqlite3_nif.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/esqlite3.erl | 12 +++++++++++- src/esqlite3_nif.erl | 10 ++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index fcf44a7..e082db2 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -282,7 +282,8 @@ make_extended_error_tuple(ErlNifEnv *env, int code) { /* internal use only */ return make_two_atom_tuple(env, "ok", "symlink"); } - return enif_make_tuple2(env, make_atom(env, "error"), enif_make_int(env, code)); + + return enif_make_tuple2(env, make_atom(env, "code"), enif_make_int(env, code)); } static const char * @@ -614,6 +615,42 @@ esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return make_atom(env, "ok"); } +/* + * Return a description of the last occurred error. + */ +static ERL_NIF_TERM +esqlite_error_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3 *conn; + int rc; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) { + return enif_make_badarg(env); + } + + if(conn->db == NULL) { + enif_make_badarg(env); + } + + int code = sqlite3_extended_errcode(conn->db); + int extended_code = sqlite3_extended_errcode(conn->db); + const char *errstr = sqlite3_errstr(extended_code); + const char *errmsg = sqlite3_errmsg(conn->db); + + ERL_NIF_TERM info = enif_make_new_map(env); + enif_make_map_put(env, info, make_atom(env, "errcode"), enif_make_int(env, code), &info); + enif_make_map_put(env, info, make_atom(env, "extended_errcode"), enif_make_int(env, extended_code), &info); + enif_make_map_put(env, info, make_atom(env, "errstr"), make_binary(env, errstr, strlen(errstr)), &info); + enif_make_map_put(env, info, make_atom(env, "errmsg"), make_binary(env, errmsg, strlen(errmsg)), &info); + enif_make_map_put(env, info, make_atom(env, "error_offset"), enif_make_int(env, sqlite3_error_offset(conn->db)), &info); + + return info; +} + void update_callback(void *arg, int sqlite_operation_type, char const *sqlite_database, char const *sqlite_table, sqlite3_int64 sqlite_rowid) { @@ -1431,6 +1468,8 @@ static ErlNifFunc nif_funcs[] = { {"open", 1, esqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"close", 1, esqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"error_info", 1, esqlite_error_info}, + /* * Other interesting additions... trace. * wal_hook triggered after every commit. diff --git a/src/esqlite3.erl b/src/esqlite3.erl index fc650dc..2c5aafb 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -19,7 +19,10 @@ %% higher-level export -export([ - open/1, close/1, + open/1, + close/1, + + error_info/1, %% db connection functions set_update_hook/2, @@ -136,6 +139,13 @@ open(Filename) -> close(#esqlite3{db=Connection}) -> esqlite3_nif:close(Connection). +%% @doc Return a description of the last occurred error. +-spec error_info(Connection) -> ErrorMsg + when Connection :: esqlite3(), + ErrorMsg :: undefined | binary(). +error_info(#esqlite3{db=Connection}) -> + esqlite3_nif:error_info(Connection). + %% @doc Subscribe to database notifications. When rows are inserted deleted %% or updates, the process will receive messages: diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index e9232ec..58e7496 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -22,6 +22,7 @@ -export([ open/1, close/1, + error_info/1, set_update_hook/2, @@ -89,6 +90,15 @@ open(_Filename) -> close(_Db) -> erlang:nif_error(nif_library_not_loaded). +%% @doc Get an error messages for the last occurred error. +%% +-spec error_info(Connection) -> ErrorMsg + when Connection :: esqlite3(), + ErrorMsg :: map(). +error_info(_Db) -> + erlang:nif_error(nif_library_not_loaded). + + %% @doc Set an update hook %% -spec set_update_hook(Connection, Pid) -> Result From f660d05cc253e98c9a0f07255f6d4491f49157d0 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 22 May 2022 22:28:28 +0200 Subject: [PATCH 18/37] Removed error code retrieval from the c code. Will be moved to erlang --- c_src/esqlite3_nif.c | 221 +----------------------------------------- test/esqlite_test.erl | 4 +- 2 files changed, 3 insertions(+), 222 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index e082db2..35a9a63 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -82,228 +82,9 @@ make_row_tuple(ErlNifEnv *env, ERL_NIF_TERM value) return enif_make_tuple2(env, make_atom(env, "row"), value); } -static const char * -get_sqlite3_return_code_msg(int r) -{ - switch(r) { - case SQLITE_OK: return "ok"; - case SQLITE_ERROR : return "sqlite_error"; - case SQLITE_INTERNAL: return "internal"; - case SQLITE_PERM: return "perm"; - case SQLITE_ABORT: return "abort"; - case SQLITE_BUSY: return "$busy"; - case SQLITE_LOCKED: return "locked"; - case SQLITE_NOMEM: return "nomem"; - case SQLITE_READONLY: return "readonly"; - case SQLITE_INTERRUPT: return "interrupt"; - case SQLITE_IOERR: return "ioerror"; - case SQLITE_CORRUPT: return "corrupt"; - case SQLITE_NOTFOUND: return "notfound"; - case SQLITE_FULL: return "full"; - case SQLITE_CANTOPEN: return "cantopen"; - case SQLITE_PROTOCOL: return "protocol"; - case SQLITE_EMPTY: return "empty"; - case SQLITE_SCHEMA: return "schema"; - case SQLITE_TOOBIG: return "toobig"; - case SQLITE_CONSTRAINT: return "constraint"; - case SQLITE_MISMATCH: return "mismatch"; - case SQLITE_MISUSE: return "misuse"; - case SQLITE_NOLFS: return "nolfs"; - case SQLITE_AUTH: return "auth"; - case SQLITE_FORMAT: return "format"; - case SQLITE_RANGE: return "range"; - case SQLITE_NOTADB: return "notadb"; - case SQLITE_ROW: return "row"; - case SQLITE_DONE: return "$done"; - } - - return "unknown"; -} - -ERL_NIF_TERM -make_two_atom_tuple(ErlNifEnv *env, const char *one, const char *two) { - return enif_make_tuple2(env, make_atom(env, one), make_atom(env, two)); -} - -ERL_NIF_TERM -make_extended_error_tuple(ErlNifEnv *env, int code) { - switch(code) { - case SQLITE_MISUSE: - return make_two_atom_tuple(env, "misuse", "invoked_incorrectly"); - case SQLITE_ERROR_MISSING_COLLSEQ: - return make_two_atom_tuple(env, "error", "missing_collesq"); - case SQLITE_ERROR_RETRY: - return make_two_atom_tuple(env, "error", "retry"); - case SQLITE_ERROR_SNAPSHOT: - return make_two_atom_tuple(env, "error", "snapshot"); - case SQLITE_IOERR_READ: - return make_two_atom_tuple(env, "ioerr", "read"); - case SQLITE_IOERR_SHORT_READ: - return make_two_atom_tuple(env, "ioerr", "short_read"); - case SQLITE_IOERR_WRITE: - return make_two_atom_tuple(env, "ioerr", "write"); - case SQLITE_IOERR_FSYNC: - return make_two_atom_tuple(env, "ioerr", "fsync"); - case SQLITE_IOERR_DIR_FSYNC: - return make_two_atom_tuple(env, "ioerr", "dir_fsync"); - case SQLITE_IOERR_TRUNCATE: - return make_two_atom_tuple(env, "ioerr", "truncate"); - case SQLITE_IOERR_FSTAT: - return make_two_atom_tuple(env, "ioerr", "fstat"); - case SQLITE_IOERR_UNLOCK: - return make_two_atom_tuple(env, "ioerr", "unlock"); - case SQLITE_IOERR_RDLOCK: - return make_two_atom_tuple(env, "ioerr", "rdlock"); - case SQLITE_IOERR_DELETE: - return make_two_atom_tuple(env, "ioerr", "delete"); - case SQLITE_IOERR_BLOCKED: - return make_two_atom_tuple(env, "ioerr", "blocked"); - case SQLITE_IOERR_NOMEM: - return make_two_atom_tuple(env, "ioerr", "nomem"); - case SQLITE_IOERR_ACCESS: - return make_two_atom_tuple(env, "ioerr", "access"); - case SQLITE_IOERR_CHECKRESERVEDLOCK: - return make_two_atom_tuple(env, "ioerr", "checkreservedlock"); - case SQLITE_IOERR_LOCK: - return make_two_atom_tuple(env, "ioerr", "lock"); - case SQLITE_IOERR_CLOSE: - return make_two_atom_tuple(env, "ioerr", "close"); - case SQLITE_IOERR_DIR_CLOSE: - return make_two_atom_tuple(env, "ioerr", "dir_close"); - case SQLITE_IOERR_SHMOPEN: - return make_two_atom_tuple(env, "ioerr", "shmopen"); - case SQLITE_IOERR_SHMSIZE: - return make_two_atom_tuple(env, "ioerr", "shmsize"); - case SQLITE_IOERR_SHMLOCK: - return make_two_atom_tuple(env, "ioerr", "shmlock"); - case SQLITE_IOERR_SHMMAP: - return make_two_atom_tuple(env, "ioerr", "shmmap"); - case SQLITE_IOERR_SEEK: - return make_two_atom_tuple(env, "ioerr", "seek"); - case SQLITE_IOERR_DELETE_NOENT: - return make_two_atom_tuple(env, "ioerr", "delete_noent"); - case SQLITE_IOERR_MMAP: - return make_two_atom_tuple(env, "ioerr", "mmap"); - case SQLITE_IOERR_GETTEMPPATH: - return make_two_atom_tuple(env, "ioerr", "gettemppath"); - case SQLITE_IOERR_CONVPATH: - return make_two_atom_tuple(env, "ioerr", "convpath"); - case SQLITE_IOERR_VNODE: - return make_two_atom_tuple(env, "ioerr", "vnode"); - case SQLITE_IOERR_AUTH: - return make_two_atom_tuple(env, "ioerr", "auth"); - case SQLITE_IOERR_BEGIN_ATOMIC: - return make_two_atom_tuple(env, "ioerr", "begin_atomic"); - case SQLITE_IOERR_COMMIT_ATOMIC: - return make_two_atom_tuple(env, "ioerr", "commit_atomic"); - case SQLITE_IOERR_ROLLBACK_ATOMIC: - return make_two_atom_tuple(env, "ioerr", "rollback_atomic"); - case SQLITE_IOERR_DATA: - return make_two_atom_tuple(env, "ioerr", "data"); - case SQLITE_IOERR_CORRUPTFS: - return make_two_atom_tuple(env, "ioerr", "corruptfs"); - case SQLITE_LOCKED_SHAREDCACHE: - return make_two_atom_tuple(env, "locked", "sharedcache"); - case SQLITE_LOCKED_VTAB: - return make_two_atom_tuple(env, "locked", "vtab"); - case SQLITE_BUSY_RECOVERY: - return make_two_atom_tuple(env, "busy", "recovery"); - case SQLITE_BUSY_SNAPSHOT: - return make_two_atom_tuple(env, "busy", "snapshot"); - case SQLITE_BUSY_TIMEOUT: - return make_two_atom_tuple(env,"busy", "timeout"); - case SQLITE_CANTOPEN_NOTEMPDIR: - return make_two_atom_tuple(env, "cantopen", "notempdir"); - case SQLITE_CANTOPEN_ISDIR: - return make_two_atom_tuple(env, "cantopen", "isdir"); - case SQLITE_CANTOPEN_FULLPATH: - return make_two_atom_tuple(env, "cantopen", "fullpath"); - case SQLITE_CANTOPEN_CONVPATH: - return make_two_atom_tuple(env, "cantopen", "convpath"); - case SQLITE_CANTOPEN_DIRTYWAL: - return make_two_atom_tuple(env, "cantopen", "dirtywal"); - case SQLITE_CANTOPEN_SYMLINK: - return make_two_atom_tuple(env, "cantopen", "symlink"); - case SQLITE_CORRUPT_VTAB: - return make_two_atom_tuple(env, "corrupt", "vtab"); - case SQLITE_CORRUPT_SEQUENCE: - return make_two_atom_tuple(env, "corrupt", "sequence"); - case SQLITE_CORRUPT_INDEX: - return make_two_atom_tuple(env, "corrupt", "index"); - case SQLITE_READONLY_RECOVERY: - return make_two_atom_tuple(env, "readonly", "recovery"); - case SQLITE_READONLY_CANTLOCK: - return make_two_atom_tuple(env, "readonly", "cantlock"); - case SQLITE_READONLY_ROLLBACK: - return make_two_atom_tuple(env, "readonly", "rollback"); - case SQLITE_READONLY_DBMOVED: - return make_two_atom_tuple(env, "readonly", "dbmoved"); - case SQLITE_READONLY_CANTINIT: - return make_two_atom_tuple(env, "readonly", "cantinit"); - case SQLITE_READONLY_DIRECTORY: - return make_two_atom_tuple(env, "readonly", "directory"); - case SQLITE_ABORT_ROLLBACK: - return make_two_atom_tuple(env, "abort", "rollback"); - case SQLITE_CONSTRAINT_CHECK: - return make_two_atom_tuple(env, "constraint", "check"); - case SQLITE_CONSTRAINT_COMMITHOOK: - return make_two_atom_tuple(env, "constraint", "commithook"); - case SQLITE_CONSTRAINT_FOREIGNKEY: - return make_two_atom_tuple(env, "constraint", "foreignkey"); - case SQLITE_CONSTRAINT_FUNCTION: - return make_two_atom_tuple(env, "constraint", "function"); - case SQLITE_CONSTRAINT_NOTNULL: - return make_two_atom_tuple(env, "constraint", "notnull"); - case SQLITE_CONSTRAINT_PRIMARYKEY: - return make_two_atom_tuple(env, "constraint", "primarykey"); - case SQLITE_CONSTRAINT_TRIGGER: - return make_two_atom_tuple(env, "constraint", "trigger"); - case SQLITE_CONSTRAINT_UNIQUE: - return make_two_atom_tuple(env, "constraint", "unique"); - case SQLITE_CONSTRAINT_VTAB: - return make_two_atom_tuple(env, "constraint", "vtab"); - case SQLITE_CONSTRAINT_ROWID: - return make_two_atom_tuple(env, "constraint", "rowid"); - case SQLITE_CONSTRAINT_PINNED: - return make_two_atom_tuple(env, "constraint", "pinned"); - case SQLITE_CONSTRAINT_DATATYPE: - return make_two_atom_tuple(env, "constraint", "datatype"); - case SQLITE_NOTICE_RECOVER_WAL: - return make_two_atom_tuple(env, "notice", "recover_wal"); - case SQLITE_NOTICE_RECOVER_ROLLBACK: - return make_two_atom_tuple(env, "notice", "recover_rollback"); - case SQLITE_WARNING_AUTOINDEX: - return make_two_atom_tuple(env, "warning", "autoindex"); - case SQLITE_AUTH_USER: - return make_two_atom_tuple(env, "auth", "user"); - case SQLITE_OK_LOAD_PERMANENTLY: - return make_two_atom_tuple(env, "ok", "load_permanently"); - case SQLITE_OK_SYMLINK: - /* internal use only */ - return make_two_atom_tuple(env, "ok", "symlink"); - } - - return enif_make_tuple2(env, make_atom(env, "code"), enif_make_int(env, code)); -} - -static const char * -get_sqlite3_error_msg(int error_code, sqlite3 *db) -{ - static const char *msg; - - if(error_code == SQLITE_MISUSE) - return "Sqlite3 was invoked incorrectly."; - - msg = sqlite3_errmsg(db); - if(!msg) - return "No sqlite3 error message found."; - - return msg; -} - static ERL_NIF_TERM make_sqlite3_error_tuple(ErlNifEnv *env, int error_code) { - return enif_make_tuple2(env, make_atom(env, "error"), make_extended_error_tuple(env, error_code)); + return enif_make_tuple2(env, make_atom(env, "error"), enif_make_int(env, error_code)); } /* diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 85adbf6..c36b017 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -28,7 +28,7 @@ close_test() -> {error, closed} = esqlite3:get_autocommit(C), {error, closed} = esqlite3:last_insert_rowid(C), - ?assertEqual({error, {misuse, invoked_incorrectly}}, + ?assertEqual({error, 21}, esqlite3:exec(C, "create table test(one, two, three)")), ok. @@ -42,7 +42,7 @@ prepare_test() -> prepare_after_close_test() -> {ok, C} = esqlite3:open(":memory:"), ?assertEqual(ok, esqlite3:close(C)), - ?assertMatch({error, {misuse, invoked_incorrectly}}, esqlite3:prepare(C, "select 1")), + ?assertMatch({error, 21}, esqlite3:prepare(C, "select 1")), ok. column_names_test() -> From 2f3eecdfe5fa7a87d95ae0031f22cdb63e74437c Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Mon, 23 May 2022 10:43:45 +0200 Subject: [PATCH 19/37] Added calls to get status information from sqlite --- c_src/esqlite3_nif.c | 93 +++++++++++++++++++++++++++++++++++++++++--- src/esqlite3_nif.erl | 31 ++++++++++++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 35a9a63..10755fa 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -36,7 +36,7 @@ static ErlNifResourceType *esqlite3_backup_type = NULL; typedef struct { sqlite3 *db; - ErlNifPid *update_hook_pid; + ErlNifPid update_hook_pid; } esqlite3; /* prepared statement */ @@ -104,6 +104,7 @@ destruct_esqlite3_stmt(ErlNifEnv *env, void *arg) esqlite3_stmt *stmt = (esqlite3_stmt *) arg; sqlite3_finalize(stmt->statement); stmt->statement = NULL; + stmt->column_count = 0; } static void @@ -387,6 +388,22 @@ esqlite_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); } + if(conn->db == NULL) { + return make_atom(env, "ok"); + } + + /* + * close_v2 is not guaranteed to close the connection. There could be + * an open transaction. Rollback this transaction first, before trying + * to close the connection. + */ + if(!sqlite3_get_autocommit(conn->db)) { + rc = sqlite3_exec(conn->db, "ROLLBACK;", NULL, NULL, NULL); + if (rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + } + rc = sqlite3_close_v2(conn->db); if(rc != SQLITE_OK) { return make_sqlite3_error_tuple(env, rc); @@ -403,7 +420,6 @@ static ERL_NIF_TERM esqlite_error_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; - int rc; if(argc != 1) { return enif_make_badarg(env); @@ -475,7 +491,6 @@ static ERL_NIF_TERM esqlite_set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; - int rc; if(argc != 2) { return enif_make_badarg(env); @@ -810,7 +825,7 @@ esqlite_bind_text(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) * */ - int rc = sqlite3_bind_text64(stmt->statement, index, text.data, text.size, SQLITE_TRANSIENT, SQLITE_UTF8); + int rc = sqlite3_bind_text64(stmt->statement, index, (char *) text.data, text.size, SQLITE_TRANSIENT, SQLITE_UTF8); if(rc != SQLITE_OK) { return make_sqlite3_error_tuple(env, rc); } @@ -858,7 +873,6 @@ esqlite_bind_null(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3_stmt *stmt; int index; - double value; if(argc != 2) { return enif_make_badarg(env); @@ -1134,6 +1148,9 @@ esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; + if(argc != 1) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); @@ -1151,6 +1168,9 @@ esqlite_get_autocommit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; + if(argc != 1) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); @@ -1172,6 +1192,9 @@ esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; + if(argc != 1) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); @@ -1190,6 +1213,9 @@ esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *conn; + if(argc != 1) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) return enif_make_badarg(env); @@ -1203,6 +1229,60 @@ esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_int64(env, changes); } +static ERL_NIF_TERM +esqlite_memory_stats(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + int highwater_reset_flag = 0; + + if(argc != 1) + return enif_make_badarg(env); + + if(!enif_get_int(env, argv[0], &highwater_reset_flag)) { + return enif_make_badarg(env); + } + + int used = sqlite3_memory_used(); + int highwater = sqlite3_memory_highwater(highwater_reset_flag); + + ERL_NIF_TERM stats = enif_make_new_map(env); + enif_make_map_put(env, stats, make_atom(env, "used"), enif_make_int64(env, used), &stats); + enif_make_map_put(env, stats, make_atom(env, "highwater"), enif_make_int64(env, highwater), &stats); + + return stats; +} + +static ERL_NIF_TERM +esqlite_status(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + int op = 0; + int highwater_reset_flag = 0; + + if(argc != 2) + return enif_make_badarg(env); + + if(!enif_get_int(env, argv[0], &op)) { + return enif_make_badarg(env); + } + + if(!enif_get_int(env, argv[0], &highwater_reset_flag)) { + return enif_make_badarg(env); + } + + sqlite3_int64 used; + sqlite3_int64 highwater; + + int rc = sqlite3_status64(op, &used, &highwater, highwater_reset_flag); + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + ERL_NIF_TERM stats = enif_make_new_map(env); + enif_make_map_put(env, stats, make_atom(env, "used"), enif_make_int64(env, used), &stats); + enif_make_map_put(env, stats, make_atom(env, "highwater"), enif_make_int64(env, highwater), &stats); + + return stats; +} + /* * Load the nif. Initialize some stuff and such */ @@ -1287,6 +1367,9 @@ static ErlNifFunc nif_funcs[] = { {"backup_finish", 4, esqlite_backup_finish}, */ + {"memory_stats", 1, esqlite_memory_stats}, + {"status", 2, esqlite_status} + }; ERL_NIF_INIT(esqlite3_nif, nif_funcs, on_load, on_reload, on_upgrade, NULL); diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 58e7496..3dcbff4 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -46,13 +46,16 @@ reset/1, - interrupt/1 + interrupt/1, % backup_init/6, % backup_step/5, % backup_remaining/4, % backup_pagecount/4, % backup_finish/4, + + memory_stats/1, + status/2 ]). -type esqlite3() :: reference(). @@ -254,10 +257,34 @@ last_insert_rowid(_Connection) -> changes(_Connection) -> erlang:nif_error(nif_library_not_loaded). - %% @doc Get autocommit %% -spec get_autocommit(esqlite3()) -> true | false | {error, _}. get_autocommit(_Connection) -> erlang:nif_error(nif_library_not_loaded). +%% @doc Get memory statistics +%% +-spec memory_stats(HighwaterResetFlag) -> Stats when + HighwaterResetFlag :: integer(), + Stats :: #{ used := integer(), highwater := integer() }. +memory_stats(_Flag) -> + erlang:nif_error(nif_library_not_loaded). + +%% @doc Get sqlite status information. +%% +%% MEMORY_USED 0 +%% PAGECACHE_USED 1 +%% PAGECACHE_OVERFLOW 2 +%% MALLOC_SIZE 5 +%% PARSER_STACK 6 +%% PAGECACHE_SIZE 7 +%% MALLOC_COUNT 8 +%% +-spec status(Op, HighwaterResetFlag) -> Stats when + Op :: integer(), + HighwaterResetFlag :: integer(), + Stats :: #{ used := integer(), highwater := integer() }. +status(_Op, _Flag) -> + erlang:nif_error(nif_library_not_loaded). + From 20ad5138a15316b126d1e122e7a874b79cd57ef5 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Mon, 23 May 2022 22:39:19 +0200 Subject: [PATCH 20/37] Re-added higher level api --- c_src/esqlite3_nif.c | 2 +- src/esqlite3.erl | 154 ++++++++++++++++++---------------- src/esqlite3_nif.erl | 31 +------ test/esqlite_test.erl | 186 ++++++++++++++++++++---------------------- 4 files changed, 177 insertions(+), 196 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 10755fa..409c251 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -815,7 +815,7 @@ esqlite_bind_text(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); } - if(!enif_inspect_iolist_as_binary(env, argv[1], &text)) { + if(!enif_inspect_iolist_as_binary(env, argv[2], &text)) { return enif_make_badarg(env); } diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 2c5aafb..d6f5f0d 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -48,11 +48,12 @@ bind_null/2, step/1, - reset/1 + reset/1, + q/2, q/3, -% fetchone/1, -% fetchall/1, fetchall/2, fetchall/3, + fetchone/1, + fetchall/1 % % backup_init/4, backup_init/5, % backup_finish/1, backup_finish/2, @@ -160,44 +161,38 @@ error_info(#esqlite3{db=Connection}) -> set_update_hook(#esqlite3{db=Connection}, MaybePid) when is_pid(MaybePid) orelse MaybePid =:= undefined -> esqlite3_nif:set_update_hook(Connection, MaybePid). - %%% %%% q %%% -% + %%% @doc Execute a sql statement, returns a list with tuples. %-spec q(sql(), connection()) -> list(row()) | {error, _}. -%q(Sql, Connection) -> -% q(Sql, [], Connection, ?DEFAULT_TIMEOUT). -% -%%% @doc Execute statement, bind args and return a list with tuples as result. -%-spec q(sql(), list(), connection()) -> list(row()) | {error, _}. -%%q(Sql, Args, Connection) -> -% q(Sql, Args, Connection, ?DEFAULT_TIMEOUT). -% +q(Connection, Sql) -> + q(Connection, Sql, []). %% @doc Execute statement, bind args and return a list with tuples as result restricted by timeout. %-spec q(sql(), list(), connection(), timeout()) -> list(row()) | {error, _}. -%q(Sql, [], Connection, Timeout) -> -% case prepare(Sql, Connection, Timeout) of -% {ok, Statement} -> -% fetchall(Statement, ?DEFAULT_CHUNK_SIZE, Timeout); -% {error, _Msg}=Error -> -% Error -% end; -%q(Sql, Args, Connection, Timeout) -> -% case prepare(Sql, Connection, Timeout) of -% {ok, Statement} -> -% case bind(Statement, Args, Timeout) of -% ok -> -% fetchall(Statement, ?DEFAULT_CHUNK_SIZE, Timeout); -% {error, _}=Error -> -% Error -% end; -% {error, _Msg}=Error -> -% Error -% end. -% +q(Connection, Sql, []) -> + case prepare(Connection, Sql) of + {ok, Statement} -> + fetchall(Statement); + {error, _Msg}=Error -> + Error + end; + +q(Connection, Sql, Args) -> + case prepare(Connection, Sql) of + {ok, Statement} -> + case bind(Statement, Args) of + ok -> + fetchall(Statement); + {error, _}=Error -> + Error + end; + {error, _Msg}=Error -> + Error + end. + %%% %% map %%% @@ -280,45 +275,63 @@ set_update_hook(#esqlite3{db=Connection}, MaybePid) when is_pid(MaybePid) orelse %% %-spec fetchone(statement()) -> tuple(). -%fetchone(Statement) -> -% case try_multi_step(Statement, 1, [], 0) of -% {'$done', []} -> ok; -% {error, _} = E -> E; -% {rows, [Row | []]} -> Row -% end. +fetchone(Statement) -> + case step(Statement) of + Row when is_list(Row) -> + Row; + '$done' -> + ok; + {error, _} = E -> + E + end. % %%% @doc Fetch all records %%% @param Statement is prepared sql statement %-spec fetchall(statement()) -> list(row()) | {error, _}. -%fetchall(Statement) -> -% fetchall(Statement, ?DEFAULT_CHUNK_SIZE, ?DEFAULT_TIMEOUT). -% -%% @doc Fetch all records -%% @param Statement is prepared sql statement -%% @param ChunkSize is a count of rows to read from sqlite and send to erlang process in one bulk. -%% Decrease this value if rows are heavy. Default value is 5000 (DEFAULT_CHUNK_SIZE). -%-spec fetchall(statement(), pos_integer()) -> list(row()) | {error, _}. -%fetchall(Statement, ChunkSize) -> -% fetchall(Statement, ChunkSize, ?DEFAULT_TIMEOUT). -% -%%% @doc Fetch all records -%%% @param Statement is prepared sql statement -%% @param ChunkSize is a count of rows to read from sqlite and send to erlang process in one bulk. -%% Decrease this value if rows are heavy. Default value is 5000 (DEFAULT_CHUNK_SIZE). -%% @param Timeout is timeout per each request of the one bulk -%-spec fetchall(statement(), pos_integer(), timeout()) -> list(row()) | {error, _}. -%fetchall(Statement, ChunkSize, Timeout) -> -% case fetchall_internal(Statement, ChunkSize, [], Timeout) of -% {'$done', Rows} -> lists:reverse(Rows); -% {error, _} = E -> E -% end. +fetchall(Statement) -> + fetchall1(Statement, []). + +fetchall1(Statement, Acc) -> + case step(Statement) of + Row when is_list(Row) -> + fetchall1(Statement, [Row|Acc]); + '$done' -> + lists:reverse(Acc); + {error, _} = E -> + E + end. + +bind(Statement, Args) -> + bind1(Statement, 1, Args). + +bind1(_Statement, _Column, []) -> + ok; +bind1(Statement, Column, [Arg | Args]) -> + bind_arg(Statement, Column, Arg), + bind1(Statement, Column + 1, Args). + +% Do automatic conversion +bind_arg(Statement, Column, undefined) -> + bind_null(Statement, Column); +bind_arg(Statement, Column, null) -> + bind_null(Statement, Column); +bind_arg(Statement, Column, Atom) when is_atom(Atom) -> + bind_text(Statement, Column, atom_to_binary(Atom, utf8)); +bind_arg(Statement, Column, Int) when is_integer(Int) -> + bind_int64(Statement, Column, Int); +bind_arg(Statement, Column, Float) when is_float(Float) -> + bind_double(Statement, Column, Float); +bind_arg(Statement, Column, Bin) when is_binary(Bin) -> + bind_text(Statement, Column, Bin); +bind_arg(Statement, Column, String) when is_list(String) -> + bind_text(Statement, Column, String). %% @doc Get the last insert rowid. %% --spec last_insert_rowid(Connection) -> RowidResult - when Connection :: esqlite3(), - RowidResult :: integer() | {error, closed}. +-spec last_insert_rowid(Connection) -> RowidResult when + Connection :: esqlite3(), + RowidResult :: integer() | {error, closed}. last_insert_rowid(#esqlite3{db=Connection}) -> esqlite3_nif:last_insert_rowid(Connection). @@ -400,6 +413,14 @@ bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_double(Stmt, Index, Value). +-spec bind_text(Statement, Index, Value) -> BindResult + when Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: iodata(), + BindResult :: ok | {error, _}. +bind_text(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> + esqlite3_nif:bind_text(Stmt, Index, Value). + -spec bind_blob(Statement, Index, Value) -> BindResult when Statement :: esqlite3_stmt(), Index :: integer(), @@ -631,8 +652,3 @@ props_to_prepare_flag(Props) -> end. - - - - - diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 3dcbff4..c49dabf 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -141,10 +141,10 @@ bind_int64(_Statement, _Index, _Value) -> bind_double(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). -bind_blob(_Statement, _Index, _Value) -> +bind_text(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). -bind_text(_Statement, _Index, _Value) -> +bind_blob(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). bind_null(_Statement, _Index) -> @@ -156,27 +156,6 @@ step(_Statement) -> reset(_Statement) -> erlang:nif_error(nif_library_not_loaded). - -%% @doc Exec the query. -%% -%% Sends an asynchronous exec command over the connection and returns -%% ok immediately. -%% -%% When the statement is executed Dest will receive message {Ref, answer()} -%% with answer() integer | {error, reason()} -%% -%-spec exec(esqlite3(), reference(), pid(), sql()) -> ok | {error, _}. -%%exec(_Db, _Ref, _Dest, _Sql) -> -% erlang:nif_error(nif_library_not_loaded). - -%% @doc Get the number of affected rows of last statement -%% -%% When the statement is executed Dest will receive message {Ref, answer()} -%% with answer() integer | {error, reason()} -%-spec changes(esqlite3(), reference(), pid()) -> ok | {error, _}. -%changes(_Db, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). - %% @doc %% %-spec multi_step(esqlite3(), esqlite3_stmt(), pos_integer(), reference(), pid()) -> ok | {error, _}. @@ -195,12 +174,6 @@ reset(_Statement) -> %finalize(_Db, _Stmt, _Ref, _Dest) -> % erlang:nif_error(nif_library_not_loaded). -%% @doc Bind parameters to a prepared statement. -%% -%-spec bind(esqlite3(), esqlite3_stmt(), reference(), pid(), list(any())) -> ok | {error, _}. -%bind(_Db, _Stmt, _Ref, _Dest, _Args) -> -% erlang:nif_error(nif_library_not_loaded). - %% @doc Retrieve the column names of the prepared statement %% -spec column_names(esqlite3_stmt()) -> list(binary()) | {error, _}. diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index c36b017..847d73e 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -64,13 +64,9 @@ column_names_test() -> column_decltypes_test() -> {ok, C} = esqlite3:open(":memory:"), - {ok, Stmt} = esqlite3:prepare(C, "select 1, 2, 3"), - ?assertEqual([undefined, undefined, undefined], esqlite3:column_decltypes(Stmt)), - %% Need to be able to define tables. - ok. step_test() -> @@ -197,7 +193,7 @@ prepare2_test() -> ok = esqlite3:exec(Db, "insert into test_table values('hello4', 13);"), %% Check if the values are there. - %% [{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q(Db, "select * from test_table order by two"), + [[<<"one">>, 2], [<<"hello4">>, 13]] = esqlite3:q(Db, "select * from test_table order by two"), esqlite3:exec(Db, "commit;"), esqlite3:close(Db), @@ -266,94 +262,91 @@ prepare2_test() -> % % ok. -%bind_for_queries_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("commit;", Db), +bind_for_queries_test() -> + {ok, Db} = esqlite3:open(":memory:"), -% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, -% [test_table], Db)), -% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, -% ["test_table"], Db)), -% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, -% [<<"test_table">>], Db)), -% ?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, -% [[<<"test_table">>]], Db)), -% -% ok. + ok = esqlite3:exec(Db, "begin;"), + ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), + ok = esqlite3:exec(Db, "commit;"), -%column_names_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db), -% ok = esqlite3:exec("commit;", Db), + ?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, + [test_table])), + ?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, + ["test_table"])), + ?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, + [<<"test_table">>])), + ?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>, + [[<<"test_table">>]])), + + ok. + +column_names2_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:exec(Db, "begin;"), + ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), + ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"), + ok = esqlite3:exec(Db, "insert into test_table values('hello2', 20);"), + ok = esqlite3:exec(Db, "commit;"), %% All columns -% {ok, Stmt} = esqlite3:prepare("select * from test_table", Db), -% {one, two} = esqlite3:column_names(Stmt), -% {row, {<<"hello1">>, 10}} = esqlite3:step(Stmt), -% {one, two} = esqlite3:column_names(Stmt), -% {row, {<<"hello2">>, 20}} = esqlite3:step(Stmt), -% {one, two} = esqlite3:column_names(Stmt), -% '$done' = esqlite3:step(Stmt), -% {one, two} = esqlite3:column_names(Stmt), + {ok, Stmt} = esqlite3:prepare(Db, "select * from test_table"), + [<<"one">>, <<"two">>] = esqlite3:column_names(Stmt), + [<<"hello1">>, 10] = esqlite3:step(Stmt), + [<<"one">>, <<"two">>] = esqlite3:column_names(Stmt), + [<<"hello2">>, 20] = esqlite3:step(Stmt), + [<<"one">>, <<"two">>] = esqlite3:column_names(Stmt), + '$done' = esqlite3:step(Stmt), + [<<"one">>, <<"two">>] = esqlite3:column_names(Stmt), %% One column -% {ok, Stmt2} = esqlite3:prepare("select two from test_table", Db), -% {two} = esqlite3:column_names(Stmt2), -% {row, {10}} = esqlite3:step(Stmt2), -% {two} = esqlite3:column_names(Stmt2), -% {row, {20}} = esqlite3:step(Stmt2), -% {two} = esqlite3:column_names(Stmt2), -% '$done' = esqlite3:step(Stmt2), -% {two} = esqlite3:column_names(Stmt2), + {ok, Stmt2} = esqlite3:prepare(Db, "select two from test_table"), + [<<"two">>] = esqlite3:column_names(Stmt2), + [10] = esqlite3:step(Stmt2), + [<<"two">>] = esqlite3:column_names(Stmt2), + [20] = esqlite3:step(Stmt2), + [<<"two">>] = esqlite3:column_names(Stmt2), + '$done' = esqlite3:step(Stmt2), + [<<"two">>] = esqlite3:column_names(Stmt2), %% No columns -% {ok, Stmt3} = esqlite3:prepare("values(1);", Db), -% {column1} = esqlite3:column_names(Stmt3), -% {row, {1}} = esqlite3:step(Stmt3), -% {column1} = esqlite3:column_names(Stmt3), + {ok, Stmt3} = esqlite3:prepare(Db, "values(1);"), + [<<"column1">>] = esqlite3:column_names(Stmt3), + [1] = esqlite3:step(Stmt3), + [<<"column1">>] = esqlite3:column_names(Stmt3), %% Things get a bit weird when you retrieve the column name %% when calling an aggragage function. -%% {ok, Stmt4} = esqlite3:prepare("select date('now');", Db), -% {'date(\'now\')'} = esqlite3:column_names(Stmt4), -% {row, {Date}} = esqlite3:step(Stmt4), -% true = is_binary(Date), + {ok, Stmt4} = esqlite3:prepare(Db, "select date('now');"), + [<<"date(\'now\')">>] = esqlite3:column_names(Stmt4), + [Date] = esqlite3:step(Stmt4), + true = is_binary(Date), %% Some statements have no column names -% {ok, Stmt5} = esqlite3:prepare("create table dummy(a, b, c);", Db), -% {} = esqlite3:column_names(Stmt5), -% -% ok. + {ok, Stmt5} = esqlite3:prepare(Db, "create table dummy(a, b, c);"), + [] = esqlite3:column_names(Stmt5), + + ok. + +column_types_test() -> + {ok, Db} = esqlite3:open(":memory:"), + ok = esqlite3:exec(Db, "begin;"), + ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), + ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"), + ok = esqlite3:exec(Db, "insert into test_table values('hello2', 20);"), + ok = esqlite3:exec(Db, "commit;"), -%column_types_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db), -% ok = esqlite3:exec("commit;", Db), -% %% All columns -% {ok, Stmt} = esqlite3:prepare("select * from test_table", Db), -% ?assertEqual({'varchar(10)', 'INT'}, esqlite3:column_types(Stmt)), -% {row, {<<"hello1">>, 10}} = esqlite3:step(Stmt), -% {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), -% {row, {<<"hello2">>, 20}} = esqlite3:step(Stmt), -% {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), -% '$done' = esqlite3:step(Stmt), -% {'varchar(10)', 'INT'} = esqlite3:column_types(Stmt), -% -% %% Some statements have no column types -% {ok, Stmt2} = esqlite3:prepare("create table dummy(a, b, c);", Db), -% {} = esqlite3:column_types(Stmt2), -% -% ok. + {ok, Stmt} = esqlite3:prepare(Db, "select * from test_table"), + ?assertEqual([<<"varchar(10)">>, <<"INT">>], esqlite3:column_decltypes(Stmt)), + + %% Some statements have no column types + {ok, Stmt2} = esqlite3:prepare(Db, "create table dummy(a, b, c);"), + [] = esqlite3:column_decltypes(Stmt2), + + {ok, Stmt3} = esqlite3:prepare(Db, "select 1, 2, 3;"), + [undefined, undefined, undefined] = esqlite3:column_decltypes(Stmt3), + + ok. nil_column_decltypes_test() -> {ok, Db} = esqlite3:open(":memory:"), @@ -636,27 +629,26 @@ sqlite_source_id_test() -> % end % end. -%garbage_collect_test() -> -% F = fun() -> -% {ok, Db} = esqlite3:open(":memory:"), -% [] = esqlite3:q("create table test(one, two, three)", Db), -% [] = esqlite3:q("insert into test values(1, '2', 3.0)", Db), -% {ok, Stmt} = esqlite3:prepare("select * from test", Db), -% {row, {1, <<"2">>, 3.0}} = esqlite3:step(Stmt), -% '$done' = esqlite3:step(Stmt), -% ok = esqlite3:close(Db) -% end, -% -% [spawn(F) || _X <- lists:seq(0,30)], -% receive after 500 -> ok end, -% erlang:garbage_collect(), -% -% [spawn(F) || _X <- lists:seq(0,30)], -% receive after 500 -> ok end, -% erlang:garbage_collect(), +garbage_collect_test() -> + F = fun() -> + {ok, Db} = esqlite3:open(":memory:"), + [] = esqlite3:q(Db, "create table test(one, two, three)"), + [] = esqlite3:q(Db, "insert into test values(1, '2', 3.0)"), + {ok, Stmt} = esqlite3:prepare(Db, "select * from test"), + [1, <<"2">>, 3.0] = esqlite3:step(Stmt), + '$done' = esqlite3:step(Stmt), + ok = esqlite3:close(Db) + end, + [spawn(F) || _X <- lists:seq(0,30)], + receive after 500 -> ok end, + erlang:garbage_collect(), -% ok. + [spawn(F) || _X <- lists:seq(0,30)], + receive after 500 -> ok end, + erlang:garbage_collect(), + + ok. %% %% Helpers From 8754c1c40b38ae85893f59c06a68386fbefe327c Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Tue, 24 May 2022 08:39:08 +0200 Subject: [PATCH 21/37] Uncomment the bind test --- test/esqlite_test.erl | 103 +++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 847d73e..c5fe605 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -199,68 +199,67 @@ prepare2_test() -> ok. -%bind_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("commit;", Db), -% -% %% Create a prepared statement -% {ok, Statement} = esqlite3:prepare("insert into test_table values(?1, ?2)", Db), -%% esqlite3:bind(Statement, [one, 2]), -% esqlite3:step(Statement), -% esqlite3:bind(Statement, ["three", 4]), -% esqlite3:step(Statement), -% esqlite3:bind(Statement, ["five", 6]), -% esqlite3:step(Statement), -% esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text -% esqlite3:step(Statement), -% esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text -% esqlite3:step(Statement), -% esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos. -% esqlite3:step(Statement), -% esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null -% esqlite3:step(Statement), +bind_test() -> + {ok, Db} = esqlite3:open(":memory:"), + + ok = esqlite3:exec(Db, "begin;"), + ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), + ok = esqlite3:exec(Db, "commit;"), + + %% Create a prepared statement + {ok, Statement} = esqlite3:prepare(Db, "insert into test_table values(?1, ?2)"), + esqlite3:bind(Statement, [one, 2]), + esqlite3:step(Statement), + esqlite3:bind(Statement, ["three", 4]), + esqlite3:step(Statement), + esqlite3:bind(Statement, ["five", 6]), + esqlite3:step(Statement), + esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text + esqlite3:step(Statement), + esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text + esqlite3:step(Statement), + esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos. + esqlite3:step(Statement), + esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null + esqlite3:step(Statement), %% int64 -% esqlite3:bind(Statement, [int64, 308553449069486081]), -% esqlite3:step(Statement), + esqlite3:bind(Statement, [int64, 308553449069486081]), + esqlite3:step(Statement), % %% negative int64 -% esqlite3:bind(Statement, [negative_int64, -308553449069486081]), -% esqlite3:step(Statement), - + esqlite3:bind(Statement, [negative_int64, -308553449069486081]), + esqlite3:step(Statement), %% utf-8 -% esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]), -% esqlite3:step(Statement), + esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]), + esqlite3:step(Statement), -% ?assertEqual([{<<"one">>, 2}], -% esqlite3:q("select one, two from test_table where two = '2'", Db)), -% ?assertEqual([{<<"three">>, 4}], -% esqlite3:q("select one, two from test_table where two = 4", Db)), -% ?assertEqual([{<<"five">>, 6}], -% esqlite3:q("select one, two from test_table where two = 6", Db)), -% ?assertEqual([{<<"seven">>, 8}], -% esqlite3:q("select one, two from test_table where two = 8", Db)), -% ?assertEqual([{<<"nine">>, 10}], -% esqlite3:q("select one, two from test_table where two = 10", Db)), -% ?assertEqual([{{blob, <<$e,$l,$e,$v,$e,$n,0>>}, 12}], -% esqlite3:q("select one, two from test_table where two = 12", Db)), -% ?assertEqual([{<<"empty">>, undefined}], -% esqlite3:q("select one, two from test_table where two is null", Db)), + ?assertEqual([[<<"one">>, 2]], + esqlite3:q(Db, "select one, two from test_table where two = '2'")), + ?assertEqual([[<<"three">>, 4]], + esqlite3:q(Db, "select one, two from test_table where two = 4")), + ?assertEqual([[<<"five">>, 6]], + esqlite3:q(Db, "select one, two from test_table where two = 6")), + ?assertEqual([[<<"seven">>, 8]], + esqlite3:q(Db, "select one, two from test_table where two = 8")), + ?assertEqual([[<<"nine">>, 10]], + esqlite3:q(Db, "select one, two from test_table where two = 10")), + ?assertEqual([[<<$e,$l,$e,$v,$e,$n,0>>, 12]], + esqlite3:q(Db, "select one, two from test_table where two = 12")), + ?assertEqual([[<<"empty">>, undefined]], + esqlite3:q(Db, "select one, two from test_table where two is null")), -% ?assertEqual([{<<"int64">>, 308553449069486081}], -% esqlite3:q("select one, two from test_table where one = 'int64';", Db)), -% ?assertEqual([{<<"negative_int64">>, -308553449069486081}], -% esqlite3:q("select one, two from test_table where one = 'negative_int64';", Db)), + ?assertEqual([[<<"int64">>, 308553449069486081]], + esqlite3:q(Db, "select one, two from test_table where one = 'int64';")), + ?assertEqual([[<<"negative_int64">>, -308553449069486081]], + esqlite3:q(Db, "select one, two from test_table where one = 'negative_int64';")), %% utf-8 -% ?assertEqual([{<<228,184,138,230,181,183>>, 100}], -% esqlite3:q("select one, two from test_table where two = 100", Db)), -% -% ok. + ?assertEqual([[<<228,184,138,230,181,183>>, 100]], + esqlite3:q(Db, "select one, two from test_table where two = 100")), + + ok. bind_for_queries_test() -> {ok, Db} = esqlite3:open(":memory:"), From 507e5407d893f5072d0b619e59b068758798c702 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Tue, 24 May 2022 23:46:31 +0200 Subject: [PATCH 22/37] The bind test now works again --- c_src/esqlite3_nif.c | 9 +++++++-- src/esqlite3.erl | 24 ++++++++++++++++++++---- test/esqlite_test.erl | 22 +++++++++++----------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 409c251..efa0b48 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -856,7 +856,7 @@ esqlite_bind_blob(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); } - if(!enif_inspect_iolist_as_binary(env, argv[1], &blob)) { + if(!enif_inspect_iolist_as_binary(env, argv[2], &blob)) { return enif_make_badarg(env); } @@ -926,8 +926,13 @@ esqlite_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return row; } case SQLITE_DONE: - /* since 3.6.23.1 it is no longer required to do an explict reset. + /* + * Automatically reset the statement after a done so + * column_names will work after the statement is done. + * + * Not resetting the statement can lead to vm crashes. */ + sqlite3_reset(stmt->statement); return make_atom(env, "$done"); case SQLITE_BUSY: return make_atom(env, "$busy"); diff --git a/src/esqlite3.erl b/src/esqlite3.erl index d6f5f0d..e89abd2 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -43,10 +43,12 @@ bind_int/3, bind_int64/3, bind_double/3, - % bind_text/3, + bind_text/3, bind_blob/3, bind_null/2, + bind/2, + step/1, reset/1, @@ -301,7 +303,9 @@ fetchall1(Statement, Acc) -> E end. -bind(Statement, Args) -> +%% @doc Bind an array of values to a prepared statement +%% +bind(#esqlite3_stmt{}=Statement, Args) when is_list(Args) -> bind1(Statement, 1, Args). bind1(_Statement, _Column, []) -> @@ -310,7 +314,7 @@ bind1(Statement, Column, [Arg | Args]) -> bind_arg(Statement, Column, Arg), bind1(Statement, Column + 1, Args). -% Do automatic conversion +% Bind with automatic tyoe conversion bind_arg(Statement, Column, undefined) -> bind_null(Statement, Column); bind_arg(Statement, Column, null) -> @@ -324,7 +328,19 @@ bind_arg(Statement, Column, Float) when is_float(Float) -> bind_arg(Statement, Column, Bin) when is_binary(Bin) -> bind_text(Statement, Column, Bin); bind_arg(Statement, Column, String) when is_list(String) -> - bind_text(Statement, Column, String). + bind_text(Statement, Column, String); +%% Explicit type binds. +bind_arg(Statement, Column, {int, Value}) -> + bind_int(Statement, Column, Value); +bind_arg(Statement, Column, {int64, Value}) -> + bind_int64(Statement, Column, Value); +bind_arg(Statement, Column, {float, Value}) -> + bind_double(Statement, Column, Value); +bind_arg(Statement, Column, {text, Value}) -> + bind_text(Statement, Column, Value); +bind_arg(Statement, Column, {blob, Value}) -> + bind_blob(Statement, Column, Value). + %% @doc Get the last insert rowid. diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index c5fe605..bc22e5d 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -208,31 +208,31 @@ bind_test() -> %% Create a prepared statement {ok, Statement} = esqlite3:prepare(Db, "insert into test_table values(?1, ?2)"), - esqlite3:bind(Statement, [one, 2]), + ok = esqlite3:bind(Statement, [one, 2]), + '$done' = esqlite3:step(Statement), + ok = esqlite3:bind(Statement, ["three", 4]), esqlite3:step(Statement), - esqlite3:bind(Statement, ["three", 4]), + ok = esqlite3:bind(Statement, ["five", 6]), esqlite3:step(Statement), - esqlite3:bind(Statement, ["five", 6]), + ok = esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text esqlite3:step(Statement), - esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text + ok = esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text esqlite3:step(Statement), - esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text + ok = esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos. esqlite3:step(Statement), - esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos. - esqlite3:step(Statement), - esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null + ok = esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null esqlite3:step(Statement), %% int64 - esqlite3:bind(Statement, [int64, 308553449069486081]), + ok = esqlite3:bind(Statement, [int64, 308553449069486081]), esqlite3:step(Statement), % %% negative int64 - esqlite3:bind(Statement, [negative_int64, -308553449069486081]), + ok = esqlite3:bind(Statement, [negative_int64, -308553449069486081]), esqlite3:step(Statement), %% utf-8 - esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]), + ok = esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]), esqlite3:step(Statement), ?assertEqual([[<<"one">>, 2]], From 55303c13f407518cfb24e085639d4f3c5e24a207 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 26 May 2022 16:59:10 +0200 Subject: [PATCH 23/37] Added dirty versions of the backup api --- c_src/esqlite3_nif.c | 476 +++++++++++++++--------------------------- rebar.config.script | 2 +- src/esqlite3.erl | 281 +++---------------------- src/esqlite3_nif.erl | 68 +++--- test/esqlite_test.erl | 210 +++++-------------- 5 files changed, 270 insertions(+), 767 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index efa0b48..a91de62 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -32,26 +32,29 @@ static ErlNifResourceType *esqlite3_type = NULL; static ErlNifResourceType *esqlite3_stmt_type = NULL; static ErlNifResourceType *esqlite3_backup_type = NULL; -/* database connection context */ +/* Database connection context */ typedef struct { sqlite3 *db; ErlNifPid update_hook_pid; } esqlite3; -/* prepared statement */ +/* Prepared statement */ typedef struct { - sqlite3_stmt *statement; + esqlite3 *connection; + sqlite3_stmt *statement; int column_count; } esqlite3_stmt; -/* data associated with ongoing backup */ +/* Data associated with an ongoing backup */ typedef struct { + esqlite3 *source; + esqlite3 *destination; + sqlite3_backup *backup; } esqlite3_backup; -static ERL_NIF_TERM atom_esqlite3; static ERL_NIF_TERM make_atom(ErlNifEnv *env, const char *atom_name) @@ -104,19 +107,34 @@ destruct_esqlite3_stmt(ErlNifEnv *env, void *arg) esqlite3_stmt *stmt = (esqlite3_stmt *) arg; sqlite3_finalize(stmt->statement); stmt->statement = NULL; + stmt->column_count = 0; + + if(stmt->connection) { + enif_release_resource(stmt->connection); + stmt->connection = NULL; + } } static void destruct_esqlite3_backup(ErlNifEnv *env, void *arg) { esqlite3_backup *backup = (esqlite3_backup *) arg; - + if(backup->backup) { sqlite3_backup_finish(backup->backup); } - backup->backup = NULL; + + if(backup->destination) { + enif_release_resource(backup->destination); + backup->destination = NULL; + } + + if(backup->source) { + enif_release_resource(backup->source); + backup->source = NULL; + } } static ERL_NIF_TERM @@ -159,168 +177,6 @@ make_cell(ErlNifEnv *env, sqlite3_stmt *statement, unsigned int i) return enif_raise_exception(env, make_atom(env, "internal_error")); } -/* -static ERL_NIF_TERM -do_backup_init(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) -{ - int tuple_arity; - const ERL_NIF_TERM *elements; - sqlite3_backup *backup; - unsigned int size; - char dst_name[MAX_SQLITE_NAME_LENGTH]; - char src_name[MAX_SQLITE_NAME_LENGTH]; - esqlite3 *src; - esqlite3_backup *esqlite3_backup; - ERL_NIF_TERM erl_backup_term; - - if(db == NULL) { - return make_error_tuple(env, "dst_closed"); - } - - if(!enif_get_tuple(env, arg, &tuple_arity, &elements)) { - return make_error_tuple(env, "no_tuple"); - } - if(tuple_arity != 3) { - return make_error_tuple(env, "invalid_tuple"); - } - - size = enif_get_string(env, elements[0], dst_name, MAX_PATHNAME, ERL_NIF_LATIN1); - if(size <= 0) - return make_error_tuple(env, "invalid_dst_name"); - - if(!enif_get_resource(env, elements[1], esqlite3_type, (void **) &src)) { - return make_error_tuple(env, "invalid_src_db"); - } - if(!src->db) { - return make_error_tuple(env, "src_closed"); - } - - size = enif_get_string(env, elements[2], src_name, MAX_PATHNAME, ERL_NIF_LATIN1); - if(size <= 0) - return make_error_tuple(env, "invalid_src_name"); - - backup = sqlite3_backup_init(db, dst_name, src->db, src_name); - if(backup == NULL) { - return make_sqlite3_error_tuple(env, sqlite3_errcode(db), db); - } - - esqlite3_backup = enif_alloc_resource(esqlite3_backup_type, sizeof(esqlite3_backup)); - if(!esqlite3_backup) { - // Release backup resouces - (void) sqlite3_backup_finish(backup); - return make_error_tuple(env, "no_memory"); - } - - esqlite3_backup->backup = backup; - erl_backup_term = enif_make_resource(env, esqlite3_backup); - enif_release_resource(esqlite3_backup); - - return make_ok_tuple(env, erl_backup_term); -} -*/ - -/* -static ERL_NIF_TERM -do_backup_step(ErlNifEnv *env, sqlite3 *db, const ERL_NIF_TERM arg) -{ - int tuple_arity; - const ERL_NIF_TERM *elements; - esqlite3_backup *esqlite3_backup; - int n_page = 0; - int rc; - - if(db == NULL) { - return make_error_tuple(env, "closed"); - } - - if(!enif_get_tuple(env, arg, &tuple_arity, &elements)) { - return make_error_tuple(env, "no_tuple"); - } - if(tuple_arity != 2) { - return make_error_tuple(env, "invalid_tuple"); - } - - if(!enif_get_resource(env, elements[0], esqlite3_backup_type, (void **) &esqlite3_backup)) { - return make_error_tuple(env, "invalid"); - } - if(!esqlite3_backup->backup) { - return make_error_tuple(env, "backup"); - } - - if(!enif_get_int(env, elements[1], &n_page)) { - return make_error_tuple(env, "n_page"); - } - - rc = sqlite3_backup_step(esqlite3_backup->backup, n_page); - if(rc == SQLITE_DONE) { - return make_atom(env, "done"); - } - - if(rc != SQLITE_OK) { - return make_sqlite3_error_tuple(env, rc, db); - } - - return make_atom(env, "ok"); -} -*/ - -/* -static ERL_NIF_TERM -do_backup_remaining(ErlNifEnv *env, const ERL_NIF_TERM arg) -{ - esqlite3_backup *esqlite3_backup; - int remaining; - ERL_NIF_TERM remaining_term; - - if(!enif_get_resource(env, arg, esqlite3_backup_type, (void **) &esqlite3_backup)) { - return make_error_tuple(env, "invalid"); - } - - remaining = sqlite3_backup_remaining(esqlite3_backup->backup); - remaining_term = enif_make_int64(env, remaining); - - return make_ok_tuple(env, remaining_term); -} -*/ - -/* -static ERL_NIF_TERM -do_backup_pagecount(ErlNifEnv *env, const ERL_NIF_TERM arg) -{ - esqlite3_backup *esqlite3_backup; - int pagecount; - ERL_NIF_TERM pagecount_term; - - if(!enif_get_resource(env, arg, esqlite3_backup_type, (void **) &esqlite3_backup)) { - return make_error_tuple(env, "invalid"); - } - - pagecount = sqlite3_backup_pagecount(esqlite3_backup->backup); - pagecount_term = enif_make_int64(env, pagecount); - - return make_ok_tuple(env, pagecount_term); -} -*/ - -/* -static ERL_NIF_TERM -do_backup_finish(ErlNifEnv *env, const ERL_NIF_TERM arg) -{ - esqlite3_backup *esqlite3_backup; - - if(!enif_get_resource(env, arg, esqlite3_backup_type, (void **) &esqlite3_backup)) { - return make_error_tuple(env, "invalid"); - } - - if(esqlite3_backup->backup) { - (void) sqlite3_backup_finish(esqlite3_backup->backup); - esqlite3_backup->backup = NULL; - } - - return make_atom(env, "ok"); -} -*/ - /* * Open the database */ @@ -588,6 +444,11 @@ esqlite_prepare(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) if(!stmt) { return enif_raise_exception(env, make_atom(env, "no_memory")); } + /* Keep a reference to the connection to prevent it from being garbage collected + * before the statement. + */ + enif_keep_resource((void *) conn); + stmt->connection = conn; rc = sqlite3_prepare_v3(conn->db, (char *) bin.data, bin.size, prep_flags, &(stmt->statement), &tail); if(rc != SQLITE_OK) { @@ -971,183 +832,177 @@ esqlite_reset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) * */ -/* static ERL_NIF_TERM esqlite_backup_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { esqlite3 *destination; - esqlite_command *cmd = NULL; - ErlNifPid pid; + ErlNifBinary destination_name; + esqlite3 *source; + ErlNifBinary source_name; + ERL_NIF_TERM eos = enif_make_int(env, 0); - if(argc != 6) + if(argc != 4) { return enif_make_badarg(env); + } - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &destination)) + if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &destination)) { return enif_make_badarg(env); - // 1 destination name - // 2 source connection with database - // 3 source name - if(!enif_is_ref(env, argv[4])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[5], &pid)) - return make_error_tuple(env, "invalid_pid"); + } - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); + if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[1], eos), &destination_name)) { + return enif_make_badarg(env); + } - cmd->type = cmd_backup_init; - cmd->ref = enif_make_copy(cmd->env, argv[4]); - cmd->pid = pid; - cmd->arg = enif_make_tuple3(cmd->env, argv[1], argv[2], argv[3]); + if(!enif_get_resource(env, argv[2], esqlite3_type, (void **) &source)) { + return enif_make_badarg(env); + } - // Use the connection of the destination database - return push_command(env, destination, cmd); + if(!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[3], eos), &source_name)) { + return enif_make_badarg(env); + } + + sqlite3_backup *backup = sqlite3_backup_init(destination->db, (const char *) destination_name.data, source->db, (const char *) source_name.data); + if(backup == NULL) { + return make_sqlite3_error_tuple(env, sqlite3_errcode(destination->db)); + } + + esqlite3_backup *ebackup = enif_alloc_resource(esqlite3_backup_type, sizeof(esqlite3_backup)); + if(!ebackup) { + (void) sqlite3_backup_finish(backup); + return enif_raise_exception(env, make_atom(env, "no_memory")); + } + + ebackup->backup = backup; + + /** + * Keep references to both database connections to prevent + * them from being garbage collected during the operation. + */ + enif_keep_resource((void *)destination); + ebackup->destination = destination; + enif_keep_resource((void *)source); + ebackup->source = source; + + ERL_NIF_TERM erl_backup_term = enif_make_resource(env, ebackup); + enif_release_resource(ebackup); + + return make_ok_tuple(env, erl_backup_term); } -*/ - -/* -static ERL_NIF_TERM -esqlite_backup_finish(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *conn; - esqlite3_backup *backup; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 4) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) - return enif_make_badarg(env); - - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_backup_finish; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[1]); - - return push_command(env, conn, cmd); -} -*/ - -/* -static ERL_NIF_TERM -esqlite_backup_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ - esqlite3 *conn; - esqlite3_backup *backup; - esqlite_command *cmd = NULL; - ErlNifPid pid; - - if(argc != 5) - return enif_make_badarg(env); - - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) - return enif_make_badarg(env); - // 1 backup - if(!enif_is_number(env, argv[2])) - return make_error_tuple(env, "invalid_count"); - if(!enif_is_ref(env, argv[3])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[4], &pid)) - return make_error_tuple(env, "invalid_pid"); - - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); - - cmd->type = cmd_backup_step; - cmd->ref = enif_make_copy(cmd->env, argv[3]); - cmd->pid = pid; - cmd->arg = enif_make_tuple2(cmd->env, argv[1], argv[2]); - - return push_command(env, conn, cmd); -} -*/ /* * Get the remaining pagecount of the backup. - + */ static ERL_NIF_TERM esqlite_backup_remaining(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite3 *conn; esqlite3_backup *backup; - esqlite_command *cmd = NULL; - ErlNifPid pid; - if(argc != 4) + if(argc != 1) { return enif_make_badarg(env); + } - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_backup_type, (void **) &backup)) { return enif_make_badarg(env); - // backup 1 - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); + } - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); + sqlite3_int64 remaining = sqlite3_backup_remaining(backup->backup); - cmd->type = cmd_backup_remaining; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[1]); - - return push_command(env, conn, cmd); + return enif_make_int64(env, remaining); } - */ /* * Get the total pagecount of the backup - + */ static ERL_NIF_TERM esqlite_backup_pagecount(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - esqlite3 *conn; esqlite3_backup *backup; - esqlite_command *cmd = NULL; - ErlNifPid pid; - if(argc != 4) + if(argc != 1) { return enif_make_badarg(env); + } - if(!enif_get_resource(env, argv[0], esqlite3_type, (void **) &conn)) + if(!enif_get_resource(env, argv[0], esqlite3_backup_type, (void **) &backup)) { return enif_make_badarg(env); - // backup 1 - if(!enif_is_ref(env, argv[2])) - return make_error_tuple(env, "invalid_ref"); - if(!enif_get_local_pid(env, argv[3], &pid)) - return make_error_tuple(env, "invalid_pid"); + } - cmd = command_create(); - if(!cmd) - return make_error_tuple(env, "command_create_failed"); + sqlite3_int64 pagecount = sqlite3_backup_pagecount(backup->backup); - cmd->type = cmd_backup_pagecount; - cmd->ref = enif_make_copy(cmd->env, argv[2]); - cmd->pid = pid; - cmd->arg = enif_make_copy(cmd->env, argv[1]); - - return push_command(env, conn, cmd); + return enif_make_int64(env, pagecount); +} + +static ERL_NIF_TERM +esqlite_backup_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_backup *backup; + int n_page; + + if(argc != 2) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_backup_type, (void **) &backup)) { + return enif_make_badarg(env); + } + + if(!enif_get_int(env, argv[1], &n_page)) { + return enif_make_badarg(env); + } + + int rc = sqlite3_backup_step(backup->backup, n_page); + if(rc == SQLITE_DONE) { + return make_atom(env, "done"); + } + + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); } - */ /* - * Interrupt currently active query. + * Explicitly finish the backup. */ +static ERL_NIF_TERM +esqlite_backup_finish(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + esqlite3_backup *backup; + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[0], esqlite3_backup_type, (void **) &backup)) { + return enif_make_badarg(env); + } + + int rc = SQLITE_OK; + if(backup->backup) { + rc = sqlite3_backup_finish(backup->backup); + backup->backup = NULL; + } + + if(backup->source) { + enif_release_resource(backup->source); + backup->source = NULL; + } + + if(backup->destination) { + enif_release_resource(backup->destination); + backup->destination = NULL; + } + + if(rc != SQLITE_OK) { + return make_sqlite3_error_tuple(env, rc); + } + + return make_atom(env, "ok"); +} + +/* + * Interrupt any currently active query. + */ static ERL_NIF_TERM esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { @@ -1311,8 +1166,6 @@ on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) if(!rt) return -1; esqlite3_backup_type = rt; - atom_esqlite3 = make_atom(env, "esqlite3"); - if(SQLITE_OK != sqlite3_initialize()) { return -1; } @@ -1356,7 +1209,7 @@ static ErlNifFunc nif_funcs[] = { {"bind_blob", 3, esqlite_bind_blob}, {"bind_null", 2, esqlite_bind_null}, - {"step", 1, esqlite_step}, + {"step", 1, esqlite_step, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"reset", 1, esqlite_reset}, {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, @@ -1364,17 +1217,14 @@ static ErlNifFunc nif_funcs[] = { {"get_autocommit", 1, esqlite_get_autocommit}, {"changes", 1, esqlite_changes}, - /* - {"backup_init", 6, esqlite_backup_init}, - {"backup_step", 5, esqlite_backup_step}, - {"backup_remaining", 4, esqlite_backup_remaining}, - {"backup_pagecount", 4, esqlite_backup_pagecount}, - {"backup_finish", 4, esqlite_backup_finish}, - */ + {"backup_init", 4, esqlite_backup_init, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"backup_remaining", 1, esqlite_backup_remaining}, + {"backup_pagecount", 1, esqlite_backup_pagecount}, + {"backup_step", 2, esqlite_backup_step}, + {"backup_finish", 1, esqlite_backup_finish}, {"memory_stats", 1, esqlite_memory_stats}, {"status", 2, esqlite_status} - }; ERL_NIF_INIT(esqlite3_nif, nif_funcs, on_load, on_reload, on_upgrade, NULL); diff --git a/rebar.config.script b/rebar.config.script index d997ecb..8615bf0 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -25,7 +25,7 @@ CFlags = [ {minimum_otp_vsn, "22.0"}, - {erl_opts, [debug_info, warnings_as_errors]}, + {erl_opts, [debug_info]}, {xref_checks, [undefined_function_calls, undefined_functions, diff --git a/src/esqlite3.erl b/src/esqlite3.erl index e89abd2..fafa4e7 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -55,16 +55,14 @@ q/2, q/3, fetchone/1, - fetchall/1 -% -% backup_init/4, backup_init/5, -% backup_finish/1, backup_finish/2, -% backup_remaining/1, backup_remaining/2, -% backup_pagecount/1, backup_pagecount/2, -% backup_step/2, backup_step/3, -]). + fetchall/1, -% -export([q/2, q/3, q/4, map/3, map/4, foreach/3, foreach/4]). + backup_init/4, + backup_remaining/1, + backup_pagecount/1, + backup_step/2, + backup_finish/1 +]). -define(DEFAULT_TIMEOUT, infinity). -define(DEFAULT_CHUNK_SIZE, 5000). @@ -81,14 +79,13 @@ stmt :: esqlite3_nif:esqlite3_stmt() }). -%-%record(esqlite3_backup, { -% db :: esqlite3_nif:esqlite3(), -% backup :: esqlite3_nif:esqlite3_backup() -%}). +-record(esqlite3_backup, { + backup :: esqlite3_nif:esqlite3_backup() +}). -type esqlite3() :: #esqlite3{}. -type esqlite3_stmt() :: #esqlite3_stmt{}. -%-type esqlite3_backup() :: #esqlite3_backup{}. +-type esqlite3_backup() :: #esqlite3_backup{}. -type sql() :: esqlite3_nif:sql(). -type prepare_flags() :: persistent | no_vtab. @@ -107,7 +104,7 @@ -type row() :: tuple(). % tuple of cell_type -type cell_type() :: undefined | integer() | binary() | float(). --export_type([esqlite3/0, esqlite3_stmt/0, prepare_flags/0, sql/0, row/0, rowid/0, cell_type/0]). +-export_type([esqlite3/0, esqlite3_stmt/0, esqlite3_backup/0, prepare_flags/0, sql/0, row/0, rowid/0, cell_type/0]). %% @doc Opens a sqlite3 database mentioned in Filename. %% @@ -195,81 +192,6 @@ q(Connection, Sql, Args) -> Error end. -%%% -%% map -%%% -% -%%% @doc Execute statement and return a list with the result of F for each row. -%-spec map(Fun, sql(), connection()) -> list(Type) when -% Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), -% Row :: row(), -% ColumnNames :: tuple(), -% Type :: any(). -%map(Fun, Sql, Connection) -> -% case prepare(Sql, Connection) of -% {ok, Statement} -> -% map_s(Fun, Statement); -% {error, _Msg}=Error -> -% Error -% end. -% -%%% @doc Execute statement, bind args and return a list with the result of F for each row. -%-spec map(F, sql(), list(), connection()) -> list(Type) when -% F :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), -% Row :: tuple(), -% ColumnNames :: tuple(), -% Type :: any(). -%map(Fun, Sql, [], Connection) -> -% map(Fun, Sql, Connection); -%map(Fun, Sql, Args, Connection) -> -% case prepare(Sql, Connection) of -% {ok, Statement} -> -% case bind(Statement, Args) of -% ok -> -% map_s(Fun, Statement); -%% {error, _}=Error -> -% Error -% end; -% {error, _Msg}=Error -> -%% Error -% end. - -%% -%% foreach -%% - -%% @doc Execute statement and call F with each row. -%-spec foreach(Fun, sql(), connection()) -> ok when -% Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), -% Row :: tuple(), -% ColumnNames :: tuple(). -%foreach(Fun, Sql, Connection) -> -% case prepare(Sql, Connection) of -% {ok, Statement} -> -% foreach_s(Fun, Statement); -% {error, _Msg}=Error -> -% Error -% end. - -%% @doc Execute statement, bind args and call F with each row. -%-spec foreach(Fun, sql(), list(), connection()) -> ok when -% Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), -% Row :: row(), -% ColumnNames :: tuple(). -%foreach(F, Sql, [], Connection) -> -% foreach(F, Sql, Connection); -%foreach(F, Sql, Args, Connection) -> -% case prepare(Sql, Connection) of -% {ok, Statement} -> -% case bind(Statement, Args) of -%% ok -> -% foreach_s(F, Statement); -% {error, _Msg}=Error -> -% Error -% end; -% {error, _Msg}=Error -> -% Error -% end. %% %% fetchall @@ -342,7 +264,6 @@ bind_arg(Statement, Column, {blob, Value}) -> bind_blob(Statement, Column, Value). - %% @doc Get the last insert rowid. %% -spec last_insert_rowid(Connection) -> RowidResult when @@ -486,172 +407,34 @@ column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> % @doc Initialize a backup procedure. %% -%-spec backup_init(connection(), string(), connection(), string()) -> {ok, backup()} | {error, _}. -%backup_init(Dest, DestName, Src, SrcName) -> -% backup_init(Dest, DestName, Src, SrcName, ?DEFAULT_TIMEOUT). -% -%% @doc Like backup_init/4, but with an extra timeout value. -%% -%-spec backup_init(connection(), string(), connection(), string(), timeout()) -> {ok, backup()} | {error, _}. -%backup_init(#connection{raw_connection=Dest}, DestName, #connection{raw_connection=Src}, SrcName, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:backup_init(Dest, DestName, Src, SrcName, Ref, self()), -% case receive_answer(Dest, Ref, Timeout) of -% {ok, RawBackup} when is_reference(RawBackup) -> -% {ok, #backup{raw_connection=Dest, raw_backup=RawBackup}}; -% {error, _} = Error -> -% Error -% end. - +-spec backup_init(esqlite3(), iodata(), esqlite3(), iodata()) -> {ok, esqlite3_backup()} | {error, _}. +backup_init(#esqlite3{db=Dest}, DestName, #esqlite3{db=Src}, SrcName) -> + case esqlite3_nif:backup_init(Dest, DestName, Src, SrcName) of + {ok, BackupRef} -> + {ok, #esqlite3_backup{backup=BackupRef}}; + {error, _}=Error -> + Error + end. %% @doc Release the resources held by the backup. -%-spec backup_finish(backup()) -> ok | {error, _}. -%backup_finish(Backup) -> -% backup_finish(Backup, ?DEFAULT_TIMEOUT). -%%% @doc Like backup_finish/1, but with an extra timeout. -%-spec backup_finish(backup(), timeout()) -> ok | {error, _}. -%backup_finish(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:backup_finish(Conn, Back, Ref, self()), -% receive_answer(Conn, Ref, Timeout). +-spec backup_finish(esqlite3_backup()) -> ok | {error, _}. +backup_finish(#esqlite3_backup{backup=Backup}) -> + esqlite3_nif:backup_finish(Backup). %% @doc Do a backup step. -%-spec backup_step(backup(), integer()) -> ok | {error, _}. -%backup_step(Backup, NPage) -> -% backup_step(Backup, NPage, ?DEFAULT_TIMEOUT). - -%% @doc Do a backup step. -%-spec backup_step(backup(), integer(), timeout()) -> ok | {error, _}. -%backup_step(#backup{raw_connection=Conn, raw_backup=Back}, NPage, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:backup_step(Conn, Back, NPage, Ref, self()), -% receive_answer(Conn, Ref, Timeout). - +-spec backup_step(esqlite3_backup(), integer()) -> ok | {error, _}. +backup_step(#esqlite3_backup{backup=Backup}, NPage) -> + esqlite3_nif:backup_step(Backup, NPage). %% @doc Get the remaining number of pages which need to be backed up. -%-spec backup_remaining(backup()) -> {ok, pos_integer()} | {error, _}. -%backup_remaining(Backup) -> -% backup_remaining(Backup, ?DEFAULT_TIMEOUT). +-spec backup_remaining(esqlite3_backup()) -> pos_integer(). +backup_remaining(#esqlite3_backup{backup=Backup}) -> + esqlite3_nif:backup_remaining(Backup). %% @doc Get the remaining number of pages which need to be backed up. -%-spec backup_remaining(backup(), timeout()) -> {ok, pos_integer()} | {error, _}. -%backup_remaining(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:backup_remaining(Conn, Back, Ref, self()), -% case receive_answer(Conn, Ref, Timeout) of -% {ok, R} when is_integer(R) -> -% {ok, R}; -% {error, _}=E -> -% E -% end. -%% -%% @doc Get the remaining number of pages which need to be backed up. -%-spec backup_pagecount(backup()) -> {ok, pos_integer()} | {error, _}. -%backup_pagecount(Backup) -> -% backup_pagecount(Backup, ?DEFAULT_TIMEOUT). -% -%% @doc Get the remaining number of pages which need to be backed up. -%-spec backup_pagecount(backup(), timeout()) -> {ok, pos_integer()} | {error, _}. -%backup_pagecount(#backup{raw_connection=Conn, raw_backup=Back}, Timeout) -> -% Ref = make_ref(), -% ok = esqlite3_nif:backup_pagecount(Conn, Back, Ref, self()), -% case receive_answer(Conn, Ref, Timeout) of -% {ok, R} when is_integer(R) -> -% {ok, R}; -% {error, _}=E -> -% E -% end. - -%% -%% Helpers -%% - -%-spec foreach_s(Fun, statement()) -> ok when -% Fun :: fun((Row) -> any()) | fun((ColumnNames, Row) -> any()), -% Row :: row(), -% ColumnNames :: tuple(). -%foreach_s(Fun, Statement) when is_function(Fun, 1) -> -% case try_multi_step(Statement, 1, [], 0) of -% {'$done', []} -> -%% ok; -% {error, _} = Error -> -% Error; -% {rows, [Row | []]} -> -% Fun(Row), -% foreach_s(Fun, Statement) -% end; -%foreach_s(Fun, Statement) when is_function(Fun, 2) -> -% ColumnNames = column_names(Statement), -% case try_multi_step(Statement, 1, [], 0) of -% {'$done', []} -> -% ok; -% {error, _} = Error -> -% Error; -% {rows, [Row | []]} -> -% Fun(ColumnNames, Row), -% foreach_s(Fun, Statement) -% end. - -%-spec map_s(Fun, statement()) -> list(Type) when -%% Fun :: fun((Row) -> Type) | fun((ColumnNames, Row) -> Type), -% Row :: row(), -% ColumnNames :: tuple(), -% Type :: term(). -%map_s(Fun, Statement) when is_function(Fun, 1) -> -% case try_multi_step(Statement, 1, [], 0) of -% {'$done', []} -> -% []; -% {error, _} = Error -> -% Error; -% {rows, [Row | []]} -> -% [Fun(Row) | map_s(Fun, Statement)] -% end; -%map_s(Fun, Statement) when is_function(Fun, 2) -> -% ColumnNames = column_names(Statement), -% case try_multi_step(Statement, 1, [], 0) of -% {'$done', []} -> -% []; -% {error, _} = Error -> -% Error; -% {rows, [Row | []]} -> -% [Fun(ColumnNames, Row) | map_s(Fun, Statement)] -% end. - -%% return rows in reverse order -%-spec fetchall_internal(statement(), pos_integer(), list(row()), timeout()) -> -% {'$done', list(row())} | -% {error, _}. -%fetchall_internal(Statement, ChunkSize, Rest, Timeout) -> -% case try_multi_step(Statement, ChunkSize, Rest, 0, Timeout) of -% {rows, Rows} -> fetchall_internal(Statement, ChunkSize, Rows, Timeout); -% Else -> Else -% end. - -%% Try a number of steps, when the database is busy, -%%% return rows in revers order -%try_multi_step(Statement, ChunkSize, Rest, Tries) -> -% try_multi_step(Statement, ChunkSize, Rest, Tries, ?DEFAULT_TIMEOUT). - -%% Try a number of steps, when the database is busy, -%% return rows in revers order -%-spec try_multi_step(statement(), pos_integer(), list(tuple()), non_neg_integer(), timeout()) -> -% {rows, list(tuple())} | -% {'$done', list(tuple())} | -% {error, term()}. -%try_multi_step(_Statement, _ChunkSize, _Rest, Tries, _Timeout) when Tries > 5 -> -% throw(too_many_tries); -%try_multi_step(Statement, ChunkSize, Rest, Tries, Timeout) -> -% case multi_step(Statement, ChunkSize, Timeout) of -% {'$busy', Rows} -> %% core can fetch a number of rows (rows < ChunkSize) per 'multi_step' call and then get busy... -% erlang:display({"busy", Tries}), -% timer:sleep(100 * Tries), -% try_multi_step(Statement, ChunkSize, Rows ++ Rest, Tries + 1, Timeout); -% {rows, Rows} -> -% {rows, Rows ++ Rest}; -% {'$done', Rows} -> -% {'$done', Rows ++ Rest}; -% Else -> Else -% end. +-spec backup_pagecount(esqlite3_backup()) -> pos_integer(). +backup_pagecount(#esqlite3_backup{backup=Backup}) -> + esqlite3_nif:backup_pagecount(Backup). %% %% Helpers diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index c49dabf..ff012d9 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -48,11 +48,11 @@ interrupt/1, -% backup_init/6, -% backup_step/5, -% backup_remaining/4, -% backup_pagecount/4, -% backup_finish/4, + backup_init/4, + backup_remaining/1, + backup_pagecount/1, + backup_step/2, + backup_finish/1, memory_stats/1, status/2 @@ -60,11 +60,10 @@ -type esqlite3() :: reference(). -type esqlite3_stmt() :: reference(). -%-type esqlite3_backup() :: reference(). +-type esqlite3_backup() :: reference(). -type sql() :: iodata(). --export_type([esqlite3/0, esqlite3_stmt/0, sql/0]). -%-export_type([esqlite3_backup/0]). +-export_type([esqlite3/0, esqlite3_stmt/0, esqlite3_backup/0, sql/0]). -on_load(init/0). @@ -156,24 +155,6 @@ step(_Statement) -> reset(_Statement) -> erlang:nif_error(nif_library_not_loaded). -%% @doc -%% -%-spec multi_step(esqlite3(), esqlite3_stmt(), pos_integer(), reference(), pid()) -> ok | {error, _}. -%multi_step(_Db, _Stmt, _Chunk_Size, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). - -%% @doc -%% -%-spec reset(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. -%reset(_Db, _Stmt, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). - -%% @doc -%% -%-spec finalize(esqlite3(), esqlite3_stmt(), reference(), pid()) -> ok | {error, _}. -%finalize(_Db, _Stmt, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). - %% @doc Retrieve the column names of the prepared statement %% -spec column_names(esqlite3_stmt()) -> list(binary()) | {error, _}. @@ -188,29 +169,28 @@ column_decltypes(_Stmt) -> %% @doc Initialize a backup procedure of a database. -%-spec backup_init(esqlite3(), string(), esqlite3_stmt(), string(), reference(), pid()) -> ok | {error, _}. -%backup_init(_DestDb, _DestName, _SourceDb, _SourceName, _Ref, _Dest) -> % erlang:nif_error(nif_library_not_loaded). +-spec backup_init(Destination, DestinationName, Source, SourceName) -> InitResult when + Destination :: esqlite3(), + DestinationName :: iodata(), + Source :: esqlite3(), + SourceName :: iodata(), + InitResult :: {ok, esqlite3_backup()} | {error, _}. +backup_init(_Dest, _DestName, _Src, _SrcName) -> + erlang:nif_error(nif_library_not_loaded). -%% @doc Do a backup step. -%-spec backup_step(esqlite3(), esqlite3_backup(), integer(), reference(), pid()) -> ok | {error, _}. -%backup_step(_Db, _Backup, _NPages, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). -%% @doc Get the amount of remaining pages which need to be backed up. -%-spec backup_remaining(esqlite3(), esqlite3_backup(), reference(), pid()) -> ok | {error, _}. -%backup_remaining(_Db, _Backup, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +backup_remaining(_Backup) -> + erlang:nif_error(nif_library_not_loaded). -%% @doc Get the total number of pages which need to be backed up. -%-spec backup_pagecount(esqlite3(), esqlite3_backup(), reference(), pid()) -> ok | {error, _}. -%backup_pagecount(_Db, _Backup, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +backup_pagecount(_Backup) -> + erlang:nif_error(nif_library_not_loaded). -%% @doc Finish the backup. -%-spec backup_finish(esqlite3(), esqlite3_backup(), reference(), pid()) -> ok | {error, _}. -%backup_finish(_Db, _Backup, _Ref, _Dest) -> -% erlang:nif_error(nif_library_not_loaded). +backup_step(_Backup, _PageCount) -> + erlang:nif_error(nif_library_not_loaded). + +backup_finish(_Backup) -> + erlang:nif_error(nif_library_not_loaded). %% @doc Interrupt all active queries. -spec interrupt(esqlite3()) -> ok. diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index bc22e5d..3c65466 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -383,119 +383,6 @@ reset_test() -> [1] = esqlite3:step(Stmt), ok. -% -%foreach_test() -> -% {ok, Db} = esqlite3:open(":memory:"), - -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% -% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), -% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), -% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), -% -% ok = esqlite3:exec("commit;", Db), - -% F = fun(Row) -> -% case Row of -% {Key, Value} -> -% put(Key, Value); -% _ -> -% ok -% end -% end, -% -% esqlite3:foreach(F, "select * from test_table;", Db), -% -% 10 = get(<<"hello1">>), -% 11 = get(<<"hello2">>), -% 12 = get(<<"hello3">>), -% 13 = get(<<"hello4">>), -% -% ok. -% -%bind_for_foreach_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), -% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), -% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), -% ok = esqlite3:exec("commit;", Db), - -% F = fun(Row) -> -% case Row of -% {Key, Value} -> -% put(Key, Value); -%% _ -> -% ok -% end -% end, - -% esqlite3:foreach(F, "select * from test_table where one = ?;", ["hello1"], Db), -% -% 10 = get(<<"hello1">>), -% -% ok. - -%map_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -%% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), -% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), -% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), -% ok = esqlite3:exec("commit;", Db), - -% F = fun(Row) -> Row end, -% -% [{<<"hello1">>,10}, -% {<<"hello2">>,11}, -% {<<"hello3">>,12}, -% {<<"hello4">>,13}] = esqlite3:map(F, "select * from test_table", Db), - - %% Test that when the row-names are added.. -% Assoc = fun(Names, Row) -> -% lists:zip(tuple_to_list(Names), tuple_to_list(Row)) -% end, -% -% [[{one,<<"hello1">>},{two,10}], -% [{one,<<"hello2">>},{two,11}], -% [{one,<<"hello3">>},{two,12}], -% [{one,<<"hello4">>},{two,13}]] = esqlite3:map(Assoc, "select * from test_table", Db), -% -% ok. - -%bind_for_map_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% -% ok = esqlite3:exec("begin;", Db), -% ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db), -% ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db), -% ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db), -% ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db), -% ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db), -% ok = esqlite3:exec("commit;", Db), - -% F = fun(Row) -> Row end, -% -% [{<<"hello1">>,10}] -% = esqlite3:map(F, "select * from test_table where one = ?", ["hello1"], Db), -% -% %% Test that when the row-names are added.. -% Assoc = fun(Names, Row) -> -% lists:zip(tuple_to_list(Names), tuple_to_list(Row)) -% end, -% -% [[{one,<<"hello1">>},{two,10}]] = esqlite3:map(Assoc, "select * from test_table where one = ?", ["hello1"], Db), -% -% ok. - %error1_msg_test() -> % {ok, Db} = esqlite3:open(":memory:"), @@ -533,63 +420,66 @@ reset_test() -> % ok. -%backup_test() -> -% cleanup(), -% -% {ok, Dest} = esqlite3:open(?DB1), -% {ok, Source} = esqlite3:open(?DB2), -% -% {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), -% {ok, 0} = esqlite3:backup_remaining(Backup), -% {ok, 0} = esqlite3:backup_pagecount(Backup), -% done = esqlite3:backup_step(Backup, 1), -% -% cleanup(), -% -% ok. +backup_test() -> + cleanup(), -%backup1_test() -> -% cleanup(), -% -% {ok, Dest} = esqlite3:open(?DB1), -% {ok, Source} = esqlite3:open(?DB2), + {ok, Dest} = esqlite3:open(?DB1), + {ok, Source} = esqlite3:open(?DB2), -% [] = esqlite3:q("create table test(one, two)", Source), -% [] = esqlite3:q("begin;", Source), -% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), -% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), -% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), -% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), -% [] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source), -% [] = esqlite3:q("commit;", Source), -% -% [{5}] = esqlite3:q("select count(*) from test", Source), -% {error, {sqlite_error, "no such table: test"}} = esqlite3:q("select count(*) from test", Dest), + {ok, Backup} = esqlite3:backup_init(Dest, <<"main">>, Source, <<"main">>), -% {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), + 0 = esqlite3:backup_remaining(Backup), + 0 = esqlite3:backup_pagecount(Backup), -% {ok, 0} = esqlite3:backup_remaining(Backup), -% {ok, 0} = esqlite3:backup_pagecount(Backup), + done = esqlite3:backup_step(Backup, 1), + + cleanup(), + + ok. + +backup1_test() -> + cleanup(), + + {ok, Dest} = esqlite3:open(?DB1), + {ok, Source} = esqlite3:open(?DB2), + + [] = esqlite3:q(Source, "create table test(one, two)"), + [] = esqlite3:q(Source, "begin;"), + [] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"), + [] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"), + [] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"), + [] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"), + [] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"), + [] = esqlite3:q(Source, "commit;"), + + [[5]] = esqlite3:q(Source, "select count(*) from test"), + {error, 1} = esqlite3:q(Dest, "select count(*) from test"), + #{ errmsg := <<"no such table: test">> } = esqlite3:error_info(Dest), + + {ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"), + + 0 = esqlite3:backup_remaining(Backup), + 0 = esqlite3:backup_pagecount(Backup), %% Backup 1 page. -% ok = esqlite3:backup_step(Backup, 1), -% -% {ok, 26} = esqlite3:backup_remaining(Backup), -% {ok, 27} = esqlite3:backup_pagecount(Backup), + ok = esqlite3:backup_step(Backup, 1), + + 26 = esqlite3:backup_remaining(Backup), + 27 = esqlite3:backup_pagecount(Backup), %% Do all the remaining pages. -%% done = esqlite3:backup_step(Backup, -1), + done = esqlite3:backup_step(Backup, -1), -% {ok, 0} = esqlite3:backup_remaining(Backup), -% {ok, 27} = esqlite3:backup_pagecount(Backup), + 0 = esqlite3:backup_remaining(Backup), + 27 = esqlite3:backup_pagecount(Backup), -% ok = esqlite3:backup_finish(Backup), -% -% [{5}] = esqlite3:q("select count(*) from test", Dest), -% -% cleanup(), -% -% ok. + ok = esqlite3:backup_finish(Backup), + + [[5]] = esqlite3:q(Dest, "select count(*) from test"), + + cleanup(), + + ok. sqlite_version_test() -> From e03deba3ca5f1298530baf4bdef6760ba1ab8aca Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 26 May 2022 21:00:17 +0200 Subject: [PATCH 24/37] Added a test for interrupt --- c_src/esqlite3_nif.c | 6 ++--- src/esqlite3.erl | 7 ++++++ test/esqlite_test.erl | 51 ++++++++++++++++++++++++++----------------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index a91de62..69cec18 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -1196,7 +1196,7 @@ static ErlNifFunc nif_funcs[] = { */ {"set_update_hook", 2, esqlite_set_update_hook}, - {"exec", 2, esqlite_exec}, + {"exec", 2, esqlite_exec, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"prepare", 3, esqlite_prepare}, {"column_names", 1, esqlite_column_names}, @@ -1220,8 +1220,8 @@ static ErlNifFunc nif_funcs[] = { {"backup_init", 4, esqlite_backup_init, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"backup_remaining", 1, esqlite_backup_remaining}, {"backup_pagecount", 1, esqlite_backup_pagecount}, - {"backup_step", 2, esqlite_backup_step}, - {"backup_finish", 1, esqlite_backup_finish}, + {"backup_step", 2, esqlite_backup_step, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"backup_finish", 1, esqlite_backup_finish, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"memory_stats", 1, esqlite_memory_stats}, {"status", 2, esqlite_status} diff --git a/src/esqlite3.erl b/src/esqlite3.erl index fafa4e7..f226c26 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -51,6 +51,7 @@ step/1, reset/1, + interrupt/1, q/2, q/3, @@ -146,6 +147,11 @@ close(#esqlite3{db=Connection}) -> error_info(#esqlite3{db=Connection}) -> esqlite3_nif:error_info(Connection). +-spec interrupt(Connection) -> Result + when Connection :: esqlite3(), + Result:: ok | {error, _}. +interrupt(#esqlite3{db=Db}) -> + esqlite3_nif:interrupt(Db). %% @doc Subscribe to database notifications. When rows are inserted deleted %% or updates, the process will receive messages: @@ -385,6 +391,7 @@ step(#esqlite3_stmt{stmt=Stmt}) -> reset(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:reset(Stmt). + %% @doc Return the column names of the prepared statement. %% -spec column_names(Statement) -> Names diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 3c65466..85d2510 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -497,26 +497,37 @@ sqlite_source_id_test() -> esqlite3:step(Stmt)), ok. -%interrupt_on_timeout_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);", -% ok = esqlite3:exec(CreateTableQuery, Db), -% VeryLongQuery = " -% WITH RECURSIVE -% for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000) -% INSERT INTO all_numbers_in_the_world SELECT i FROM for; -% ", -% try -% ok = esqlite3:exec(VeryLongQuery, [], Db, 10) -% catch -% {error, timeout, _} -> -% ?assertMatch([{0}], esqlite3:q("SELECT COUNT(*) FROM all_numbers_in_the_world", Db)), -% %% There is now a stale answer, because the recursive query was interrupted. -% receive -% {esqlite3, _, {error, {interrupt, "interrupted"}}} -> -% ok -% end -% end. +interrupt_on_timeout_test() -> + {ok, Db1} = esqlite3:open("file:memdb1?mode=memory&cache=shared"), + % {ok, Db2} = esqlite3:open("file:memdb1?mode=memory&cache=shared"), + + Self = self(), + + F = fun() -> + CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);", + ok = esqlite3:exec(Db1, CreateTableQuery), + VeryLongQuery = " + WITH RECURSIVE + for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000) + INSERT INTO all_numbers_in_the_world SELECT i FROM for; + ", + + %% The query was interrupted + Self ! {msg, esqlite3:exec(Db1, VeryLongQuery)} + end, + + spawn(F), + timer:sleep(10), + ok = esqlite3:interrupt(Db1), + + %% The query was interrupted, so no result + ?assertEqual([[0]], esqlite3:q(Db1, "SELECT COUNT(*) FROM all_numbers_in_the_world")), + + %% We should have gotten an interrupt error. + Msg = receive {msg, M} -> M end, + ?assertEqual({error, 9}, Msg), + + ok. garbage_collect_test() -> F = fun() -> From f383499868be4535d7c0765fde33d750b3ed8957 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 26 May 2022 23:49:04 +0200 Subject: [PATCH 25/37] Updated two test cases --- test/esqlite_test.erl | 61 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 85d2510..0f34f60 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -384,41 +384,40 @@ reset_test() -> ok. -%error1_msg_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% - %% Not sql. -% {error, {sqlite_error, _Msg1}} = esqlite3:exec("dit is geen sql", Db), -% -% %% Database test does not exist. -% {error, {sqlite_error, _Msg2}} = esqlite3:exec("select * from test;", Db), -% -% %% Opening non-existant database. -% {error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"), -% -% ok. +error1_msg_test() -> + {ok, Db} = esqlite3:open(":memory:"), + + %% Not sql. + {error, 1} = esqlite3:exec(Db, "dit is geen sql"), + + %% Database test does not exist. + {error, 1} = esqlite3:exec(Db, "select * from test;"), + + %% Opening non-existant database. + {error, 14} = esqlite3:open("/dit/bestaat/niet"), + + ok. + +prepare_and_close_connection_test() -> + {ok, Db} = esqlite3:open(":memory:"), + + [] = esqlite3:q(Db, "create table test(one, two, three)"), + ok = esqlite3:exec(Db, ["insert into test values(1,2,3);"]), + {ok, Stmt} = esqlite3:prepare(Db, "select * from test"), + + %% The prepated statment works. + [1,2,3] = esqlite3:step(Stmt), + '$done' = esqlite3:step(Stmt), + + ok = esqlite3:close(Db), + ok = esqlite3:reset(Stmt), -%prepare_and_close_connection_test() -> -% {ok, Db} = esqlite3:open(":memory:"), -% -% [] = esqlite3:q("create table test(one, two, three)", Db), -% ok = esqlite3:exec(["insert into test values(1,2,3);"], Db), -% {ok, Stmt} = esqlite3:prepare("select * from test", Db), -% -% %% The prepated statment works. -% {row, {1,2,3}} = esqlite3:step(Stmt), -% '$done' = esqlite3:step(Stmt), -% -% ok = esqlite3:close(Db), -% -% ok = esqlite3:reset(Stmt), -% %% Internally sqlite3_close_v2 is used by the nif. This will destruct the %% connection when the last perpared statement is finalized -% {row, {1,2,3}} = esqlite3:step(Stmt), -% '$done' = esqlite3:step(Stmt), + [1,2,3] = esqlite3:step(Stmt), + '$done' = esqlite3:step(Stmt), -% ok. + ok. backup_test() -> cleanup(), From e831ba0a33533a7b615456eefebb8dda4b8ce0a2 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Fri, 27 May 2022 09:38:24 +0200 Subject: [PATCH 26/37] Documentation tweaks --- doc/SQLite370.svg | 67 +++++++++++++++++++++++++++++++++++++++ doc/overview.edoc | 4 +-- doc/style.css | 76 +++++++++++++++++++++++++++++++++++++++++++++ rebar.config.script | 1 + src/esqlite3.erl | 4 +-- 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 doc/SQLite370.svg create mode 100644 doc/style.css diff --git a/doc/SQLite370.svg b/doc/SQLite370.svg new file mode 100644 index 0000000..424e0ab --- /dev/null +++ b/doc/SQLite370.svg @@ -0,0 +1,67 @@ + + + +SQLiteimage/svg+xmlSQLite \ No newline at end of file diff --git a/doc/overview.edoc b/doc/overview.edoc index 5428e27..8ca56be 100644 --- a/doc/overview.edoc +++ b/doc/overview.edoc @@ -1,10 +1,10 @@ @author Maas-Maarten Zeeman -@title eSqlite Documentation +@title ESQLite Documentation @doc -eSqlite is a library which makes it possible to use sqlite databases in erlang. It is implemented +ESQLite is a library which makes it possible to use sqlite databases in erlang. It is implemented as a NIF, which means that the sqlite database engine is linked to the erlang virtual machine.
diff --git a/doc/style.css b/doc/style.css new file mode 100644 index 0000000..ad59351 --- /dev/null +++ b/doc/style.css @@ -0,0 +1,76 @@ +/* standard EDoc style sheet */ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + margin-left: .25in; + margin-right: .2in; + margin-top: 0.2in; + margin-bottom: 0.2in; + color: #000000; + background-color: #ffffff; +} +h1,h2 { + margin-left: -0.2in; +} +div.navbar { + background-color: #add8e6; + padding: 0.2em; +} +h2.indextitle { + padding: 0.4em; + background-color: #add8e6; +} +h3.function,h3.typedecl { + background-color: #add8e6; + padding-left: 1em; +} +div.spec { + margin-left: 2em; + + background-color: #eeeeee; +} +a.module { + text-decoration:none +} +a.module:hover { + background-color: #eeeeee; +} +ul.definitions { + list-style-type: none; +} +ul.index { + list-style-type: none; + background-color: #eeeeee; +} + +/* + * Minor style tweaks + */ +ul { + list-style-type: square; +} +table { + border-collapse: collapse; +} +td { + padding: 3px; + vertical-align: middle; +} + +/* +Tune styles +*/ + +table[summary="navigation bar"] { + background-image: url('SQLite370.svg'); + background-size: 110px; + background-repeat: no-repeat; + background-position: center; +} + +code, p>tt, a>tt { + font-size: 1.2em; +} + +p { + line-height: 1.5; +} diff --git a/rebar.config.script b/rebar.config.script index 8615bf0..435ce86 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -68,6 +68,7 @@ CFlags = ]}, {edoc_opts, [{preprocess, true}, + {stylesheet, "style.css"}, {sort_functions, false}]}, {hex, [{doc, edoc}]} diff --git a/src/esqlite3.erl b/src/esqlite3.erl index f226c26..ea86db8 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -102,8 +102,8 @@ %% binary() -> text -type rowid() :: integer(). --type row() :: tuple(). % tuple of cell_type --type cell_type() :: undefined | integer() | binary() | float(). +-type cell_type() :: undefined | integer() | binary() | float(). +-type row() :: list(cell_type()). % tuple of cell_type -export_type([esqlite3/0, esqlite3_stmt/0, esqlite3_backup/0, prepare_flags/0, sql/0, row/0, rowid/0, cell_type/0]). From 51ea6d29be386c4db52291d990bc2c0d16fc611b Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 28 May 2022 14:28:58 +0200 Subject: [PATCH 27/37] Removed unused stuff --- c_src/esqlite3_nif.c | 7 ------- src/esqlite3.erl | 31 ++++++------------------------- test/esqlite_test.erl | 2 +- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 69cec18..9e3575e 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -25,7 +25,6 @@ #include #define MAX_ATOM_LENGTH 255 /* from atom.h, not exposed in erlang include */ -#define MAX_SQLITE_NAME_LENGTH 255 /* Maximum name length. Using longer will return misuse */ #define MAX_PATHNAME 512 /* unfortunately not in sqlite.h. */ static ErlNifResourceType *esqlite3_type = NULL; @@ -79,12 +78,6 @@ make_error_tuple(ErlNifEnv *env, const char *reason) return enif_make_tuple2(env, make_atom(env, "error"), make_atom(env, reason)); } -static ERL_NIF_TERM -make_row_tuple(ErlNifEnv *env, ERL_NIF_TERM value) -{ - return enif_make_tuple2(env, make_atom(env, "row"), value); -} - static ERL_NIF_TERM make_sqlite3_error_tuple(ErlNifEnv *env, int error_code) { return enif_make_tuple2(env, make_atom(env, "error"), enif_make_int(env, error_code)); diff --git a/src/esqlite3.erl b/src/esqlite3.erl index ea86db8..2834e8e 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -47,22 +47,19 @@ bind_blob/3, bind_null/2, - bind/2, - step/1, reset/1, interrupt/1, - q/2, q/3, - - fetchone/1, - fetchall/1, - backup_init/4, backup_remaining/1, backup_pagecount/1, backup_step/2, - backup_finish/1 + backup_finish/1, + + bind/2, + q/2, q/3, + fetchall/1 ]). -define(DEFAULT_TIMEOUT, infinity). @@ -76,7 +73,6 @@ }). -record(esqlite3_stmt, { - db :: esqlite3_nif:esqlite3(), stmt :: esqlite3_nif:esqlite3_stmt() }). @@ -203,21 +199,6 @@ q(Connection, Sql, Args) -> %% fetchall %% -%% -%-spec fetchone(statement()) -> tuple(). -fetchone(Statement) -> - case step(Statement) of - Row when is_list(Row) -> - Row; - '$done' -> - ok; - {error, _} = E -> - E - end. -% -%%% @doc Fetch all records -%%% @param Statement is prepared sql statement -%-spec fetchall(statement()) -> list(row()) | {error, _}. fetchall(Statement) -> fetchall1(Statement, []). @@ -327,7 +308,7 @@ prepare(Connection, Sql) -> prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> case esqlite3_nif:prepare(Connection, Sql, props_to_prepare_flag(PrepareFlags)) of {ok, Stmt} -> - {ok, #esqlite3_stmt{db=Connection, stmt=Stmt}}; + {ok, #esqlite3_stmt{stmt=Stmt}}; {error, _}=Error -> Error end. diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 0f34f60..e7ef48d 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -35,7 +35,7 @@ close_test() -> prepare_test() -> {ok, C} = esqlite3:open(":memory:"), - ?assertMatch({ok, {esqlite3_stmt, _, _}}, esqlite3:prepare(C, "select 1")), + ?assertMatch({ok, {esqlite3_stmt, _}}, esqlite3:prepare(C, "select 1")), ok = esqlite3:close(C), ok. From f3de7fb8a3f491e6fdddc319a4209beefbbb60f6 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 28 May 2022 17:43:05 +0200 Subject: [PATCH 28/37] Added specs for new status information function --- src/esqlite3.erl | 86 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 2834e8e..77de6f3 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -59,7 +59,9 @@ bind/2, q/2, q/3, - fetchall/1 + fetchall/1, + + status/0, status/1, status/2 ]). -define(DEFAULT_TIMEOUT, infinity). @@ -87,6 +89,15 @@ -type prepare_flags() :: persistent | no_vtab. +-type status_info() :: #{ memory_used => stats(), + pagecache_used => stats(), + pagecache_overflow => stats(), + malloc_size := stats(), + parser_stack := stats(), + pagecache_size := stats(), + malloc_count := stats() }. +-type stats() :: #{ used := non_neg_integer(), highwater := non_neg_integer() }. + %% erlang -> sqlite type conversions %% %% 'undefined' -> null @@ -299,12 +310,11 @@ prepare(Connection, Sql) -> %% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in %% queries. -%% --spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult - when Connection :: esqlite3(), - Sql :: sql(), - PrepareFlags :: list(prepare_flags()), - PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. +-spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult when + Connection :: esqlite3(), + Sql :: sql(), + PrepareFlags :: list(prepare_flags()), + PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> case esqlite3_nif:prepare(Connection, Sql, props_to_prepare_flag(PrepareFlags)) of {ok, Stmt} -> @@ -313,19 +323,19 @@ prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> Error end. --spec bind_int(Statement, Index, Value) -> BindResult - when Statement :: esqlite3_stmt(), - Index :: integer(), - Value :: integer(), - BindResult :: ok | {error, _}. +-spec bind_int(Statement, Index, Value) -> BindResult when + Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: integer(), + BindResult :: ok | {error, _}. bind_int(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int(Stmt, Index, Value). --spec bind_int64(Statement, Index, Value) -> BindResult - when Statement :: esqlite3_stmt(), - Index :: integer(), - Value :: integer(), - BindResult :: ok | {error, _}. +-spec bind_int64(Statement, Index, Value) -> BindResult when + Statement :: esqlite3_stmt(), + Index :: integer(), + Value :: integer(), + BindResult :: ok | {error, _}. bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int64(Stmt, Index, Value). @@ -424,10 +434,52 @@ backup_remaining(#esqlite3_backup{backup=Backup}) -> backup_pagecount(#esqlite3_backup{backup=Backup}) -> esqlite3_nif:backup_pagecount(Backup). + +%% @doc Get all internal status information from sqlite. +%% +-spec status() -> StatusInfo when + StatusInfo :: status_info(). +status() -> + status(false). + +%% @doc Specify which internal status information you need, when true +%% is passed, the highwater information from the status information +%% will be reset. +-spec status(ArgsOrResetHighWater) -> Status when + ArgsOrResetHighWater :: list(atom()) | boolean(), + Status :: status_info(). +status(Args) when is_list(Args) -> + status(Args, false); +status(ResetHighWater) when ResetHighWater =:= true orelse ResetHighWater =:= false -> + status([memory_used,pagecache_used, pagecache_overflow, malloc_size, + parser_stack, pagecache_size, malloc_count], ResetHighWater); +status(Op) when is_atom(Op) -> + status(Op, false). + +status(Args, ResetHighWater) when is_list(Args) -> + status1(Args, #{}, ResetHighWater); +status(Op, ResetHighWater) -> + esqlite3_nif:status(op_arg(Op), reset_arg(ResetHighWater)). + %% %% Helpers %% +status1([], Acc, _ResetArg) -> Acc; +status1([S|Rest], Acc, ResetArg) -> + status1(Rest, Acc#{ S => status(S, ResetArg)}, ResetArg). + +reset_arg(true) -> 1; +reset_arg(false) -> 0. + +op_arg(memory_used) -> 0; +op_arg(pagecache_used) -> 1; +op_arg(pagecache_overflow) -> 2; +op_arg(malloc_size) -> 5; +op_arg(parser_stack) -> 6; +op_arg(pagecache_size) -> 7; +op_arg(malloc_count) -> 8. + props_to_prepare_flag(Props) -> Flag = case proplists:get_value(no_vtab, Props, false) of true -> ?SQLITE_PREPARE_NO_VTAB; From 887e91b1de6564e40299bdeebc2a2952d233baad Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 28 May 2022 22:01:12 +0200 Subject: [PATCH 29/37] Added specs and documentation --- src/esqlite3.erl | 155 +++++++++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 71 deletions(-) diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 77de6f3..8047b58 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -148,12 +148,13 @@ close(#esqlite3{db=Connection}) -> esqlite3_nif:close(Connection). %% @doc Return a description of the last occurred error. --spec error_info(Connection) -> ErrorMsg +-spec error_info(Connection) -> ErrorInfo when Connection :: esqlite3(), - ErrorMsg :: undefined | binary(). + ErrorInfo :: map(). error_info(#esqlite3{db=Connection}) -> esqlite3_nif:error_info(Connection). +%% @doc Interrupt a long running query. See [https://sqlite.org/c3ref/interrupt.html] for more details. -spec interrupt(Connection) -> Result when Connection :: esqlite3(), Result:: ok | {error, _}. @@ -161,7 +162,7 @@ interrupt(#esqlite3{db=Db}) -> esqlite3_nif:interrupt(Db). %% @doc Subscribe to database notifications. When rows are inserted deleted -%% or updates, the process will receive messages: +%% or updates, the registered process will receive messages: %% ```{insert, binary(), binary(), rowid()}''' %% When a new row has been inserted. %% ```{delete, binary(), binary(), rowid()}''' @@ -177,13 +178,20 @@ set_update_hook(#esqlite3{db=Connection}, MaybePid) when is_pid(MaybePid) orelse %%% q %%% -%%% @doc Execute a sql statement, returns a list with tuples. -%-spec q(sql(), connection()) -> list(row()) | {error, _}. +%% @doc Execute a sql statement, returns the result as list rows. +-spec q(Connection, Sql) -> Result when + Connection :: esqlite3(), + Sql :: sql(), + Result :: list(row()) | {error, _}. q(Connection, Sql) -> q(Connection, Sql, []). -%% @doc Execute statement, bind args and return a list with tuples as result restricted by timeout. -%-spec q(sql(), list(), connection(), timeout()) -> list(row()) | {error, _}. +%% @doc Execute statement, bind args and return a list rows. +-spec q(Connection, Sql, Args) -> Result when + Connection :: esqlite3(), + Sql :: sql(), + Args :: list(), + Result :: list(row()) | {error, _}. q(Connection, Sql, []) -> case prepare(Connection, Sql) of {ok, Statement} -> @@ -191,7 +199,6 @@ q(Connection, Sql, []) -> {error, _Msg}=Error -> Error end; - q(Connection, Sql, Args) -> case prepare(Connection, Sql) of {ok, Statement} -> @@ -223,6 +230,71 @@ fetchall1(Statement, Acc) -> E end. +%% @doc Get the last inserted rowid. +%% See [https://sqlite.org/c3ref/set_last_insert_rowid.html] for more details. +-spec last_insert_rowid(Connection) -> RowidResult when + Connection :: esqlite3(), + RowidResult :: integer() | {error, closed}. +last_insert_rowid(#esqlite3{db=Connection}) -> + esqlite3_nif:last_insert_rowid(Connection). + +%% @doc Get the number of changes in the most recent INSERT, UPDATE or DELETE. +%% See [https://sqlite.org/c3ref/changes.html] for more details. +-spec changes(Connection) -> ChangesResult + when Connection :: esqlite3(), + ChangesResult :: integer() | {error, closed}. +changes(#esqlite3{db=Connection}) -> + esqlite3_nif:changes(Connection). + + +%% @doc Check if the connection is in auto-commit mode. +%% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details. +%% +-spec get_autocommit(Connection) -> AutocommitResult + when Connection :: esqlite3(), + AutocommitResult :: true | false | {error, closed}. +get_autocommit(#esqlite3{db=Connection}) -> + esqlite3_nif:get_autocommit(Connection). + +%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in +%% queries. +%% +-spec exec(Connection, Sql) -> ExecResult + when Connection :: esqlite3(), + Sql :: sql(), + ExecResult :: ok | {error, _}. +exec(#esqlite3{db=Connection}, Sql) -> + esqlite3_nif:exec(Connection, Sql). + +%% +%% Prepared Statements +%% + +%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in +%% queries. +%% +-spec prepare(Connection, Sql) -> PrepareResult + when Connection :: esqlite3(), + Sql :: sql(), + PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. +prepare(Connection, Sql) -> + prepare(Connection, Sql, []). + +%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in +%% queries. +-spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult when + Connection :: esqlite3(), + Sql :: sql(), + PrepareFlags :: list(prepare_flags()), + PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. +prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> + case esqlite3_nif:prepare(Connection, Sql, props_to_prepare_flag(PrepareFlags)) of + {ok, Stmt} -> + {ok, #esqlite3_stmt{stmt=Stmt}}; + {error, _}=Error -> + Error + end. + %% @doc Bind an array of values to a prepared statement %% bind(#esqlite3_stmt{}=Statement, Args) when is_list(Args) -> @@ -261,68 +333,6 @@ bind_arg(Statement, Column, {text, Value}) -> bind_arg(Statement, Column, {blob, Value}) -> bind_blob(Statement, Column, Value). - -%% @doc Get the last insert rowid. -%% --spec last_insert_rowid(Connection) -> RowidResult when - Connection :: esqlite3(), - RowidResult :: integer() | {error, closed}. -last_insert_rowid(#esqlite3{db=Connection}) -> - esqlite3_nif:last_insert_rowid(Connection). - -%% @doc Get the number of changes in the most recent INSERT, UPDATE or DELETE. -%% --spec changes(Connection) -> ChangesResult - when Connection :: esqlite3(), - ChangesResult :: integer() | {error, closed}. -changes(#esqlite3{db=Connection}) -> - esqlite3_nif:changes(Connection). - - -%% @doc Check if the connection is in auto-commit mode. -%% See: [https://sqlite.org/c3ref/get_autocommit.html] for more details. -%% --spec get_autocommit(Connection) -> AutocommitResult - when Connection :: esqlite3(), - AutocommitResult :: true | false | {error, closed}. -get_autocommit(#esqlite3{db=Connection}) -> - esqlite3_nif:get_autocommit(Connection). - -%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in -%% queries. -%% --spec exec(Connection, Sql) -> ExecResult - when Connection :: esqlite3(), - Sql :: sql(), - ExecResult :: ok | {error, _}. -exec(#esqlite3{db=Connection}, Sql) -> - esqlite3_nif:exec(Connection, Sql). - -%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in -%% queries. -%% --spec prepare(Connection, Sql) -> PrepareResult - when Connection :: esqlite3(), - Sql :: sql(), - PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. -prepare(Connection, Sql) -> - prepare(Connection, Sql, []). - -%% @doc Compile a SQL statement. Returns a cached compiled statement which can be used in -%% queries. --spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult when - Connection :: esqlite3(), - Sql :: sql(), - PrepareFlags :: list(prepare_flags()), - PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. -prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> - case esqlite3_nif:prepare(Connection, Sql, props_to_prepare_flag(PrepareFlags)) of - {ok, Stmt} -> - {ok, #esqlite3_stmt{stmt=Stmt}}; - {error, _}=Error -> - Error - end. - -spec bind_int(Statement, Index, Value) -> BindResult when Statement :: esqlite3_stmt(), Index :: integer(), @@ -404,7 +414,7 @@ column_decltypes(#esqlite3_stmt{stmt=Stmt}) -> %% % @doc Initialize a backup procedure. -%% +% See [https://sqlite.org/backup.html] for more details on the backup api. -spec backup_init(esqlite3(), iodata(), esqlite3(), iodata()) -> {ok, esqlite3_backup()} | {error, _}. backup_init(#esqlite3{db=Dest}, DestName, #esqlite3{db=Src}, SrcName) -> case esqlite3_nif:backup_init(Dest, DestName, Src, SrcName) of @@ -434,6 +444,9 @@ backup_remaining(#esqlite3_backup{backup=Backup}) -> backup_pagecount(#esqlite3_backup{backup=Backup}) -> esqlite3_nif:backup_pagecount(Backup). +%% +%% Status +%% %% @doc Get all internal status information from sqlite. %% From 1d9e9ee69648013ac2217218a08a75c6e93dfb21 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 28 May 2022 22:42:03 +0200 Subject: [PATCH 30/37] Fixed dialyzer warnings --- c_src/esqlite3_nif.c | 2 +- src/esqlite3.erl | 32 +++++++++++++++++++++++--------- src/esqlite3_nif.erl | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 9e3575e..0dee0ee 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -1009,7 +1009,7 @@ esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) esqlite3 *db = (esqlite3 *) conn; if(db->db == NULL) { - return make_error_tuple(env, "closed"); + return make_atom(env, "ok"); } sqlite3_interrupt(db->db); diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 8047b58..837673a 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -157,7 +157,7 @@ error_info(#esqlite3{db=Connection}) -> %% @doc Interrupt a long running query. See [https://sqlite.org/c3ref/interrupt.html] for more details. -spec interrupt(Connection) -> Result when Connection :: esqlite3(), - Result:: ok | {error, _}. + Result:: ok. interrupt(#esqlite3{db=Db}) -> esqlite3_nif:interrupt(Db). @@ -170,9 +170,12 @@ interrupt(#esqlite3{db=Db}) -> %% ```{update, binary(), binary(), rowid()}''' %% When a row has been updated. %% --spec set_update_hook(esqlite3(), pid() | undefined) -> ok | {error, term()}. -set_update_hook(#esqlite3{db=Connection}, MaybePid) when is_pid(MaybePid) orelse MaybePid =:= undefined -> - esqlite3_nif:set_update_hook(Connection, MaybePid). +-spec set_update_hook(Connection, Pid) -> Result when + Connection :: esqlite3(), + Pid :: pid(), + Result :: ok | {error, closed}. +set_update_hook(#esqlite3{db=Connection}, Pid) -> + esqlite3_nif:set_update_hook(Connection, Pid). %%% %%% q @@ -217,6 +220,10 @@ q(Connection, Sql, Args) -> %% fetchall %% +% @doc Fetch all rows from the prepared statement. +-spec fetchall(Statement) -> Result when + Statement :: esqlite3_stmt(), + Result :: list(row()) | {error, _}. fetchall(Statement) -> fetchall1(Statement, []). @@ -295,16 +302,23 @@ prepare(#esqlite3{db=Connection}, Sql, PrepareFlags) -> Error end. -%% @doc Bind an array of values to a prepared statement -%% +%% @doc Bind an array of values as parameters of a prepared statement +-spec bind(Statement, Args) -> Result when + Statement :: esqlite3_stmt(), + Args :: list(), + Result :: ok | {error, _}. bind(#esqlite3_stmt{}=Statement, Args) when is_list(Args) -> bind1(Statement, 1, Args). bind1(_Statement, _Column, []) -> ok; bind1(Statement, Column, [Arg | Args]) -> - bind_arg(Statement, Column, Arg), - bind1(Statement, Column + 1, Args). + case bind_arg(Statement, Column, Arg) of + ok -> + bind1(Statement, Column + 1, Args); + {error, _}=Error -> + Error + end. % Bind with automatic tyoe conversion bind_arg(Statement, Column, undefined) -> @@ -382,7 +396,7 @@ bind_null(#esqlite3_stmt{stmt=Stmt}, Index) -> -spec step(Statement) -> StepResult when Statement :: esqlite3_stmt(), - StepResult:: ok | {error, _}. + StepResult:: row() | '$done' | {error, _}. step(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:step(Stmt). diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index ff012d9..6c0976d 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -106,7 +106,7 @@ error_info(_Db) -> -spec set_update_hook(Connection, Pid) -> Result when Connection :: esqlite3(), Pid :: pid(), - Result :: ok | {error, _}. + Result :: ok | {error, closed}. set_update_hook(_Db, _Pid) -> erlang:nif_error(nif_library_not_loaded). From 66e82155a44ffecf55cbcf058c36476f7cfd23ba Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sat, 28 May 2022 22:44:28 +0200 Subject: [PATCH 31/37] Fixed gitignore for docs directory --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 15348a3..91441e3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ _build c_src/**/*.d erl_crash.dump tags +doc/* +!doc/style.css +!doc/overview.edoc +!doc/SQLite370.svg From e098dc1ca430127bf755a2a3279f0ff546bce997 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 29 May 2022 12:39:51 +0200 Subject: [PATCH 32/37] Changed the direct reference typenames to something more describing --- c_src/esqlite3_nif.c | 15 ++++----- src/esqlite3.erl | 20 +++++------ src/esqlite3_nif.erl | 78 +++++++++++++++++++++++++------------------ test/esqlite_test.erl | 8 ++--- 4 files changed, 66 insertions(+), 55 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 0dee0ee..50cc3f7 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -282,7 +282,7 @@ esqlite_error_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) enif_make_badarg(env); } - int code = sqlite3_extended_errcode(conn->db); + int code = sqlite3_errcode(conn->db); int extended_code = sqlite3_extended_errcode(conn->db); const char *errstr = sqlite3_errstr(extended_code); const char *errmsg = sqlite3_errmsg(conn->db); @@ -350,14 +350,13 @@ esqlite_set_update_hook(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } if(!conn->db) { - return make_error_tuple(env, "closed"); + return enif_raise_exception(env, make_atom(env, "closed")); } if(enif_is_atom(env, argv[1])) { - /* Assume this is undefined, reset the connection */ + /* Reset the hook when an atom is passed */ sqlite3_update_hook(conn->db, NULL, NULL); } else { - /* [todo] passing undefined resets the hook? */ if(!enif_get_local_pid(env, argv[1], &conn->update_hook_pid)) { return enif_make_badarg(env); } @@ -1009,7 +1008,7 @@ esqlite_interrupt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) esqlite3 *db = (esqlite3 *) conn; if(db->db == NULL) { - return make_atom(env, "ok"); + return enif_raise_exception(env, make_atom(env, "closed")); } sqlite3_interrupt(db->db); @@ -1030,7 +1029,7 @@ esqlite_get_autocommit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) esqlite3 *db = (esqlite3 *) conn; if(db->db == NULL) { - return make_error_tuple(env, "closed"); + return enif_raise_exception(env, make_atom(env, "closed")); } if(sqlite3_get_autocommit(db->db)) { @@ -1054,7 +1053,7 @@ esqlite_last_insert_rowid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) esqlite3 *db = (esqlite3 *) conn; if(db->db == NULL) { - return make_error_tuple(env, "closed"); + return enif_raise_exception(env, make_atom(env, "closed")); } sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(db->db); @@ -1075,7 +1074,7 @@ esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) esqlite3 *db = (esqlite3 *) conn; if(db->db == NULL) { - return make_error_tuple(env, "closed"); + return enif_raise_exception(env, make_atom(env, "closed")); } sqlite3_int64 changes = sqlite3_changes64(db->db); diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 837673a..90a340f 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -71,15 +71,15 @@ -define(SQLITE_PREPARE_NO_VTAB, 16#04). -record(esqlite3, { - db :: esqlite3_nif:esqlite3() + db :: esqlite3_nif:esqlite3_ref() }). -record(esqlite3_stmt, { - stmt :: esqlite3_nif:esqlite3_stmt() + stmt :: esqlite3_nif:esqlite3_stmt_ref() }). -record(esqlite3_backup, { - backup :: esqlite3_nif:esqlite3_backup() + backup :: esqlite3_nif:esqlite3_backup_ref() }). -type esqlite3() :: #esqlite3{}. @@ -131,12 +131,12 @@ %% -spec open(Filename) -> OpenResult when Filename :: string(), - OpenResult :: {ok, esqlite3()} | {error, _}. + OpenResult :: {ok, esqlite3()} | esqlite3_nif:error(). open(Filename) -> case esqlite3_nif:open(Filename) of {ok, Connection} -> {ok, #esqlite3{db=Connection}}; - {error, _Msg}=Error -> + {error, _}=Error -> Error end. @@ -150,7 +150,7 @@ close(#esqlite3{db=Connection}) -> %% @doc Return a description of the last occurred error. -spec error_info(Connection) -> ErrorInfo when Connection :: esqlite3(), - ErrorInfo :: map(). + ErrorInfo :: esqlite3_nif:error_info(). error_info(#esqlite3{db=Connection}) -> esqlite3_nif:error_info(Connection). @@ -173,7 +173,7 @@ interrupt(#esqlite3{db=Db}) -> -spec set_update_hook(Connection, Pid) -> Result when Connection :: esqlite3(), Pid :: pid(), - Result :: ok | {error, closed}. + Result :: ok. set_update_hook(#esqlite3{db=Connection}, Pid) -> esqlite3_nif:set_update_hook(Connection, Pid). @@ -241,7 +241,7 @@ fetchall1(Statement, Acc) -> %% See [https://sqlite.org/c3ref/set_last_insert_rowid.html] for more details. -spec last_insert_rowid(Connection) -> RowidResult when Connection :: esqlite3(), - RowidResult :: integer() | {error, closed}. + RowidResult :: integer(). last_insert_rowid(#esqlite3{db=Connection}) -> esqlite3_nif:last_insert_rowid(Connection). @@ -249,7 +249,7 @@ last_insert_rowid(#esqlite3{db=Connection}) -> %% See [https://sqlite.org/c3ref/changes.html] for more details. -spec changes(Connection) -> ChangesResult when Connection :: esqlite3(), - ChangesResult :: integer() | {error, closed}. + ChangesResult :: integer(). changes(#esqlite3{db=Connection}) -> esqlite3_nif:changes(Connection). @@ -259,7 +259,7 @@ changes(#esqlite3{db=Connection}) -> %% -spec get_autocommit(Connection) -> AutocommitResult when Connection :: esqlite3(), - AutocommitResult :: true | false | {error, closed}. + AutocommitResult :: true | false. get_autocommit(#esqlite3{db=Connection}) -> esqlite3_nif:get_autocommit(Connection). diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 6c0976d..a958254 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -1,3 +1,9 @@ +%% @author Maas-Maarten Zeeman +%% @copyright 2011 - 2022 Maas-Maarten Zeeman +%% +%% @doc Low level Erlang API for sqlite3 databases. +%% @end + %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at @@ -9,11 +15,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% @author Maas-Maarten Zeeman -%% @copyright 2011 - 2022 Maas-Maarten Zeeman -%% -%% @doc Low level erlang API for sqlite3 databases. -module(esqlite3_nif). -author("Maas-Maarten Zeeman "). @@ -58,12 +59,20 @@ status/2 ]). --type esqlite3() :: reference(). --type esqlite3_stmt() :: reference(). --type esqlite3_backup() :: reference(). --type sql() :: iodata(). +-type esqlite3_ref() :: reference(). % Reference to a database connection handle. See [https://sqlite.org/c3ref/sqlite3.html] for more details. +-type esqlite3_stmt_ref() :: reference(). % Reference to a prepared statement object. See [https://sqlite.org/c3ref/stmt.html] for more details. +-type esqlite3_backup_ref() :: reference(). % Reference to a online backup object. See [https://sqlite.org/c3ref/backup.html] for more details. +-type sql() :: iodata(). % Make sure the iodata contains utf-8 encoded data. +-type extended_errcode() :: integer(). % Extended sqlite3 error code. See [https://sqlite.org/rescode.html] for more details. +-type error() :: {error, extended_errcode()}. +-type error_info() :: #{ errcode := integer(), + extended_errcode := extended_errcode(), + errstr := unicode:unicode_binary(), % English-language text that describes the result code, as UTF-8 + errmsg := unicode:unicode_binary(), % English-language text that describes the error, as UTF-8 + error_offset := integer() % The byte offset to the token in the input sql. + }. % See: [https://sqlite.org/c3ref/errcode.html] for more information. --export_type([esqlite3/0, esqlite3_stmt/0, esqlite3_backup/0, sql/0]). +-export_type([esqlite3_ref/0, esqlite3_stmt_ref/0, esqlite3_backup_ref/0, sql/0, error/0, error_info/0]). -on_load(init/0). @@ -76,27 +85,28 @@ init() -> ok = erlang:load_nif(NifFileName, 0). -%% @doc Open the specified sqlite3 database. -%% --spec open(Filename) -> OpenResult - when Filename :: string(), - OpenResult :: {ok, esqlite3()} | {error, _}. +%% @doc Open the specified sqlite3 database. +%% It is possible to use sqlite's uri filenames to open files. +%% See: [https://sqlite.org/uri.html] for more information. +-spec open(Filename) -> OpenResult when + Filename :: string(), + OpenResult :: {ok, esqlite3_ref()} | error(). open(_Filename) -> erlang:nif_error(nif_library_not_loaded). %% @doc Close the connection. %% -spec close(Connection) -> CloseResult - when Connection :: esqlite3(), + when Connection :: esqlite3_ref(), CloseResult :: ok | {error, _}. close(_Db) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get an error messages for the last occurred error. %% --spec error_info(Connection) -> ErrorMsg - when Connection :: esqlite3(), - ErrorMsg :: map(). +-spec error_info(Connection) -> ErrorInfo + when Connection :: esqlite3_ref(), + ErrorInfo :: error_info(). error_info(_Db) -> erlang:nif_error(nif_library_not_loaded). @@ -104,9 +114,9 @@ error_info(_Db) -> %% @doc Set an update hook %% -spec set_update_hook(Connection, Pid) -> Result - when Connection :: esqlite3(), + when Connection :: esqlite3_ref(), Pid :: pid(), - Result :: ok | {error, closed}. + Result :: ok. set_update_hook(_Db, _Pid) -> erlang:nif_error(nif_library_not_loaded). @@ -114,7 +124,7 @@ set_update_hook(_Db, _Pid) -> %% @doc Execute a sql statement %% -spec exec(Connection, Sql) -> ExecResult - when Connection :: esqlite3(), + when Connection :: esqlite3_ref(), Sql :: sql(), ExecResult :: ok | {error, _}. exec(_Connection, _Sql) -> @@ -124,10 +134,10 @@ exec(_Connection, _Sql) -> %% @doc Compile a sql statement. %% -spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult - when Connection :: esqlite3(), + when Connection :: esqlite3_ref(), Sql :: sql(), PrepareFlags :: non_neg_integer(), - PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. + PrepareResult :: {ok, esqlite3_stmt_ref()} | {error, _}. prepare(_Connection, _Sql, _PrepareFlags) -> erlang:nif_error(nif_library_not_loaded). @@ -157,13 +167,13 @@ reset(_Statement) -> %% @doc Retrieve the column names of the prepared statement %% --spec column_names(esqlite3_stmt()) -> list(binary()) | {error, _}. +-spec column_names(esqlite3_stmt_ref()) -> list(binary()) | {error, _}. column_names(_Stmt) -> erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the declared datatypes of all columns. %% --spec column_decltypes(esqlite3_stmt()) -> list(undefined | binary()) | {error, _}. +-spec column_decltypes(esqlite3_stmt_ref()) -> list(undefined | binary()) | {error, _}. column_decltypes(_Stmt) -> erlang:nif_error(nif_library_not_loaded). @@ -171,11 +181,11 @@ column_decltypes(_Stmt) -> %% @doc Initialize a backup procedure of a database. % erlang:nif_error(nif_library_not_loaded). -spec backup_init(Destination, DestinationName, Source, SourceName) -> InitResult when - Destination :: esqlite3(), + Destination :: esqlite3_ref(), DestinationName :: iodata(), - Source :: esqlite3(), + Source :: esqlite3_ref(), SourceName :: iodata(), - InitResult :: {ok, esqlite3_backup()} | {error, _}. + InitResult :: {ok, esqlite3_backup_ref()} | {error, _}. backup_init(_Dest, _DestName, _Src, _SrcName) -> erlang:nif_error(nif_library_not_loaded). @@ -193,26 +203,26 @@ backup_finish(_Backup) -> erlang:nif_error(nif_library_not_loaded). %% @doc Interrupt all active queries. --spec interrupt(esqlite3()) -> ok. +-spec interrupt(esqlite3_ref()) -> ok. interrupt(_Db) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get the last insert rowid. %% --spec last_insert_rowid(esqlite3()) -> integer() | {error, _}. +-spec last_insert_rowid(esqlite3_ref()) -> integer(). last_insert_rowid(_Connection) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get number of changes insert, delete of the most recent completed %% INSERT, DELETE or UPDATE statement. %% --spec changes(esqlite3()) -> integer() | {error, _}. +-spec changes(esqlite3_ref()) -> integer(). changes(_Connection) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get autocommit %% --spec get_autocommit(esqlite3()) -> true | false | {error, _}. +-spec get_autocommit(esqlite3_ref()) -> true | false. get_autocommit(_Connection) -> erlang:nif_error(nif_library_not_loaded). @@ -226,6 +236,7 @@ memory_stats(_Flag) -> %% @doc Get sqlite status information. %% +%% %% MEMORY_USED 0 %% PAGECACHE_USED 1 %% PAGECACHE_OVERFLOW 2 @@ -233,6 +244,7 @@ memory_stats(_Flag) -> %% PARSER_STACK 6 %% PAGECACHE_SIZE 7 %% MALLOC_COUNT 8 +%% %% -spec status(Op, HighwaterResetFlag) -> Stats when Op :: integer(), diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index e7ef48d..5238204 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -23,10 +23,10 @@ close_test() -> ok = esqlite3:close(C), %% Check if functions still return sensible values. - {error, closed} = esqlite3:set_update_hook(C, self()), - {error, closed} = esqlite3:changes(C), - {error, closed} = esqlite3:get_autocommit(C), - {error, closed} = esqlite3:last_insert_rowid(C), + ?assertError(closed, esqlite3:set_update_hook(C, self())), + ?assertError(closed, esqlite3:changes(C)), + ?assertError(closed, esqlite3:get_autocommit(C)), + ?assertError(closed, esqlite3:last_insert_rowid(C)), ?assertEqual({error, 21}, esqlite3:exec(C, "create table test(one, two, three)")), From bd98ad8030adcb2037c1dada4ab46499c1ac65c7 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 29 May 2022 13:36:50 +0200 Subject: [PATCH 33/37] Improved accuracy of the returned error tuples. Made closed error tuple an exception --- src/esqlite3.erl | 18 +++++++++--------- src/esqlite3_nif.erl | 45 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 90a340f..83ed700 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -269,7 +269,7 @@ get_autocommit(#esqlite3{db=Connection}) -> -spec exec(Connection, Sql) -> ExecResult when Connection :: esqlite3(), Sql :: sql(), - ExecResult :: ok | {error, _}. + ExecResult :: ok | esqlite3_nif:error(). exec(#esqlite3{db=Connection}, Sql) -> esqlite3_nif:exec(Connection, Sql). @@ -283,7 +283,7 @@ exec(#esqlite3{db=Connection}, Sql) -> -spec prepare(Connection, Sql) -> PrepareResult when Connection :: esqlite3(), Sql :: sql(), - PrepareResult :: {ok, esqlite3_stmt()} | {error, _}. + PrepareResult :: {ok, esqlite3_stmt()} | esqlite3_nif:error(). prepare(Connection, Sql) -> prepare(Connection, Sql, []). @@ -351,15 +351,15 @@ bind_arg(Statement, Column, {blob, Value}) -> Statement :: esqlite3_stmt(), Index :: integer(), Value :: integer(), - BindResult :: ok | {error, _}. + BindResult :: ok | esqlite3_nif:error(). bind_int(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int(Stmt, Index, Value). -spec bind_int64(Statement, Index, Value) -> BindResult when Statement :: esqlite3_stmt(), - Index :: integer(), + Index :: non_neg_integer(), Value :: integer(), - BindResult :: ok | {error, _}. + BindResult :: ok | esqlite3_nif:error(). bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int64(Stmt, Index, Value). @@ -367,7 +367,7 @@ bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> when Statement :: esqlite3_stmt(), Index :: integer(), Value :: float(), - BindResult :: ok | {error, _}. + BindResult :: ok | esqlite3_nif:error(). bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_double(Stmt, Index, Value). @@ -375,7 +375,7 @@ bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> when Statement :: esqlite3_stmt(), Index :: integer(), Value :: iodata(), - BindResult :: ok | {error, _}. + BindResult :: ok | esqlite3_nif:error(). bind_text(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_text(Stmt, Index, Value). @@ -383,14 +383,14 @@ bind_text(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> when Statement :: esqlite3_stmt(), Index :: integer(), Value :: iodata(), - BindResult :: ok | {error, _}. + BindResult :: ok | esqlite3_nif:error(). bind_blob(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_blob(Stmt, Index, Value). -spec bind_null(Statement, Index) -> BindResult when Statement :: esqlite3_stmt(), Index :: integer(), - BindResult :: ok | {error, _}. + BindResult :: ok | esqlite3_nif:error(). bind_null(#esqlite3_stmt{stmt=Stmt}, Index) -> esqlite3_nif:bind_null(Stmt, Index). diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index a958254..37a314e 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -112,7 +112,6 @@ error_info(_Db) -> %% @doc Set an update hook -%% -spec set_update_hook(Connection, Pid) -> Result when Connection :: esqlite3_ref(), Pid :: pid(), @@ -122,40 +121,73 @@ set_update_hook(_Db, _Pid) -> %% @doc Execute a sql statement -%% -spec exec(Connection, Sql) -> ExecResult when Connection :: esqlite3_ref(), Sql :: sql(), - ExecResult :: ok | {error, _}. + ExecResult :: ok | error(). exec(_Connection, _Sql) -> erlang:nif_error(nif_library_not_loaded). %% @doc Compile a sql statement. -%% -spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult when Connection :: esqlite3_ref(), Sql :: sql(), PrepareFlags :: non_neg_integer(), - PrepareResult :: {ok, esqlite3_stmt_ref()} | {error, _}. + PrepareResult :: {ok, esqlite3_stmt_ref()} | error(). prepare(_Connection, _Sql, _PrepareFlags) -> erlang:nif_error(nif_library_not_loaded). +% @doc Bind an integer to a position in a prepared statement. +-spec bind_int(Statement, Index, Value) -> Result when + Statement :: esqlite3_stmt_ref(), + Index :: integer(), + Value :: integer(), %% [todo] Should be a 32 bit integer range. + Result :: ok | error(). bind_int(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +% @doc Bind a 64 bit integer to a position in a prepared statement. +-spec bind_int64(Statement, Index, Value) -> Result when + Statement :: esqlite3_stmt_ref(), + Index :: integer(), + Value :: integer(), %% [todo] Should be a 64 bit integer range. + Result :: ok | error(). bind_int64(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +% @doc Bind an double/float to a position in a prepared statement. +-spec bind_double(Statement, Index, Value) -> Result when + Statement :: esqlite3_stmt_ref(), + Index :: integer(), + Value :: float(), %% [todo] Should be a 64 bit integer range. + Result :: ok | error(). bind_double(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +% @doc Bind a utf-8 string to a position in a prepared statement. +-spec bind_text(Statement, Index, Value) -> Result when + Statement :: esqlite3_stmt_ref(), + Index :: integer(), + Value :: iodata(), %% [todo] Should be a utf-8 iodata. + Result :: ok | error(). bind_text(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +% @doc Bind a blob to a position in a prepared statement. +-spec bind_blob(Statement, Index, Value) -> Result when + Statement :: esqlite3_stmt_ref(), + Index :: integer(), + Value :: iodata(), + Result :: ok | error(). bind_blob(_Statement, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). +% @doc Bind a null to a position in a prepared statement. +-spec bind_null(Statement, Index) -> Result when + Statement :: esqlite3_stmt_ref(), + Index :: integer(), + Result :: ok | error(). bind_null(_Statement, _Index) -> erlang:nif_error(nif_library_not_loaded). @@ -189,7 +221,6 @@ column_decltypes(_Stmt) -> backup_init(_Dest, _DestName, _Src, _SrcName) -> erlang:nif_error(nif_library_not_loaded). - backup_remaining(_Backup) -> erlang:nif_error(nif_library_not_loaded). @@ -249,7 +280,7 @@ memory_stats(_Flag) -> -spec status(Op, HighwaterResetFlag) -> Stats when Op :: integer(), HighwaterResetFlag :: integer(), - Stats :: #{ used := integer(), highwater := integer() }. + Stats :: #{ used := non_neg_integer(), highwater := non_neg_integer() }. status(_Op, _Flag) -> erlang:nif_error(nif_library_not_loaded). From 331579e8dca0d4327d29672291cb32d3abdb7f22 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 29 May 2022 15:13:28 +0200 Subject: [PATCH 34/37] Improved accuracy of names and decltypes --- src/esqlite3.erl | 51 ++++++++++++++++++-------------------------- src/esqlite3_nif.erl | 21 ++++++++++++++---- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 83ed700..ea462fa 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -97,22 +97,12 @@ pagecache_size := stats(), malloc_count := stats() }. -type stats() :: #{ used := non_neg_integer(), highwater := non_neg_integer() }. +-type rowid() :: esqlite3_nif:rowid(). +-type cell() :: esqlite3_nif:cell(). +-type row() :: esqlite3_nif:row(). +-type error() :: esqlite3_nif:error(). -%% erlang -> sqlite type conversions -%% -%% 'undefined' -> null -%% 'null' -> null -%% atom() -> text -%% int() -> int or int64 -%% float() -> double -%% string() -> text -%% binary() -> text - --type rowid() :: integer(). --type cell_type() :: undefined | integer() | binary() | float(). --type row() :: list(cell_type()). % tuple of cell_type - --export_type([esqlite3/0, esqlite3_stmt/0, esqlite3_backup/0, prepare_flags/0, sql/0, row/0, rowid/0, cell_type/0]). +-export_type([esqlite3/0, esqlite3_stmt/0, esqlite3_backup/0, prepare_flags/0, sql/0, row/0, rowid/0, cell/0]). %% @doc Opens a sqlite3 database mentioned in Filename. %% @@ -121,7 +111,7 @@ %% information about this can be found here: [https://sqlite.org/uri.html] %% %% Example: -%% +% %% ```open("file:data.db")''' %% Opens "data.db" in the current working directory %% ```open("file:data.db?mode=ro&cache=private")''' @@ -131,7 +121,7 @@ %% -spec open(Filename) -> OpenResult when Filename :: string(), - OpenResult :: {ok, esqlite3()} | esqlite3_nif:error(). + OpenResult :: {ok, esqlite3()} | error(). open(Filename) -> case esqlite3_nif:open(Filename) of {ok, Connection} -> @@ -185,7 +175,7 @@ set_update_hook(#esqlite3{db=Connection}, Pid) -> -spec q(Connection, Sql) -> Result when Connection :: esqlite3(), Sql :: sql(), - Result :: list(row()) | {error, _}. + Result :: list(row()) | error(). q(Connection, Sql) -> q(Connection, Sql, []). @@ -194,7 +184,7 @@ q(Connection, Sql) -> Connection :: esqlite3(), Sql :: sql(), Args :: list(), - Result :: list(row()) | {error, _}. + Result :: list(row()) | error(). q(Connection, Sql, []) -> case prepare(Connection, Sql) of {ok, Statement} -> @@ -223,7 +213,7 @@ q(Connection, Sql, Args) -> % @doc Fetch all rows from the prepared statement. -spec fetchall(Statement) -> Result when Statement :: esqlite3_stmt(), - Result :: list(row()) | {error, _}. + Result :: list(row()) | error(). fetchall(Statement) -> fetchall1(Statement, []). @@ -269,7 +259,7 @@ get_autocommit(#esqlite3{db=Connection}) -> -spec exec(Connection, Sql) -> ExecResult when Connection :: esqlite3(), Sql :: sql(), - ExecResult :: ok | esqlite3_nif:error(). + ExecResult :: ok | error(). exec(#esqlite3{db=Connection}, Sql) -> esqlite3_nif:exec(Connection, Sql). @@ -283,7 +273,7 @@ exec(#esqlite3{db=Connection}, Sql) -> -spec prepare(Connection, Sql) -> PrepareResult when Connection :: esqlite3(), Sql :: sql(), - PrepareResult :: {ok, esqlite3_stmt()} | esqlite3_nif:error(). + PrepareResult :: {ok, esqlite3_stmt()} | error(). prepare(Connection, Sql) -> prepare(Connection, Sql, []). @@ -351,7 +341,7 @@ bind_arg(Statement, Column, {blob, Value}) -> Statement :: esqlite3_stmt(), Index :: integer(), Value :: integer(), - BindResult :: ok | esqlite3_nif:error(). + BindResult :: ok | error(). bind_int(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int(Stmt, Index, Value). @@ -359,7 +349,7 @@ bind_int(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> Statement :: esqlite3_stmt(), Index :: non_neg_integer(), Value :: integer(), - BindResult :: ok | esqlite3_nif:error(). + BindResult :: ok | error(). bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_int64(Stmt, Index, Value). @@ -367,7 +357,7 @@ bind_int64(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> when Statement :: esqlite3_stmt(), Index :: integer(), Value :: float(), - BindResult :: ok | esqlite3_nif:error(). + BindResult :: ok | error(). bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_double(Stmt, Index, Value). @@ -375,7 +365,7 @@ bind_double(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> when Statement :: esqlite3_stmt(), Index :: integer(), Value :: iodata(), - BindResult :: ok | esqlite3_nif:error(). + BindResult :: ok | error(). bind_text(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_text(Stmt, Index, Value). @@ -383,26 +373,27 @@ bind_text(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> when Statement :: esqlite3_stmt(), Index :: integer(), Value :: iodata(), - BindResult :: ok | esqlite3_nif:error(). + BindResult :: ok | error(). bind_blob(#esqlite3_stmt{stmt=Stmt}, Index, Value) -> esqlite3_nif:bind_blob(Stmt, Index, Value). -spec bind_null(Statement, Index) -> BindResult when Statement :: esqlite3_stmt(), Index :: integer(), - BindResult :: ok | esqlite3_nif:error(). + BindResult :: ok | error(). bind_null(#esqlite3_stmt{stmt=Stmt}, Index) -> esqlite3_nif:bind_null(Stmt, Index). -spec step(Statement) -> StepResult when Statement :: esqlite3_stmt(), - StepResult:: row() | '$done' | {error, _}. + StepResult:: row() | '$done' | error(). step(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:step(Stmt). +% @doc Reset the prepared statement. -spec reset(Statement) -> ResetResult when Statement :: esqlite3_stmt(), - ResetResult:: ok | {error, _}. + ResetResult:: ok | error(). reset(#esqlite3_stmt{stmt=Stmt}) -> esqlite3_nif:reset(Stmt). diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 37a314e..e6a1511 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -63,6 +63,9 @@ -type esqlite3_stmt_ref() :: reference(). % Reference to a prepared statement object. See [https://sqlite.org/c3ref/stmt.html] for more details. -type esqlite3_backup_ref() :: reference(). % Reference to a online backup object. See [https://sqlite.org/c3ref/backup.html] for more details. -type sql() :: iodata(). % Make sure the iodata contains utf-8 encoded data. +-type rowid() :: integer(). +-type cell() :: undefined | integer() | float() | binary(). +-type row() :: list(cell()). -type extended_errcode() :: integer(). % Extended sqlite3 error code. See [https://sqlite.org/rescode.html] for more details. -type error() :: {error, extended_errcode()}. -type error_info() :: #{ errcode := integer(), @@ -72,7 +75,7 @@ error_offset := integer() % The byte offset to the token in the input sql. }. % See: [https://sqlite.org/c3ref/errcode.html] for more information. --export_type([esqlite3_ref/0, esqlite3_stmt_ref/0, esqlite3_backup_ref/0, sql/0, error/0, error_info/0]). +-export_type([esqlite3_ref/0, esqlite3_stmt_ref/0, esqlite3_backup_ref/0, sql/0, rowid/0, cell/0, row/0, error/0, error_info/0]). -on_load(init/0). @@ -191,21 +194,31 @@ bind_blob(_Statement, _Index, _Value) -> bind_null(_Statement, _Index) -> erlang:nif_error(nif_library_not_loaded). +-spec step(Statement) -> StepResult when + Statement :: esqlite3_stmt_ref(), + StepResult :: row() | '$done' | error(). step(_Statement) -> erlang:nif_error(nif_library_not_loaded). +-spec reset(Statement) -> ResetResult when + Statement :: esqlite3_stmt_ref(), + ResetResult :: ok | error(). reset(_Statement) -> erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the column names of the prepared statement %% --spec column_names(esqlite3_stmt_ref()) -> list(binary()) | {error, _}. +-spec column_names(Statement) -> Names when + Statement :: esqlite3_stmt_ref(), + Names :: list(unicode:unicode_binary()). column_names(_Stmt) -> erlang:nif_error(nif_library_not_loaded). %% @doc Retrieve the declared datatypes of all columns. %% --spec column_decltypes(esqlite3_stmt_ref()) -> list(undefined | binary()) | {error, _}. +-spec column_decltypes(Statement) -> Types when + Statement :: esqlite3_stmt_ref(), + Types :: list(undefined | unicode:unicode_binary()). column_decltypes(_Stmt) -> erlang:nif_error(nif_library_not_loaded). @@ -240,7 +253,7 @@ interrupt(_Db) -> %% @doc Get the last insert rowid. %% --spec last_insert_rowid(esqlite3_ref()) -> integer(). +-spec last_insert_rowid(esqlite3_ref()) -> rowid(). last_insert_rowid(_Connection) -> erlang:nif_error(nif_library_not_loaded). From 540bd642631a82e1ccbace202c0fcdd7b5bb6b4f Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 29 May 2022 17:19:04 +0200 Subject: [PATCH 35/37] Specs for the backup api --- c_src/esqlite3_nif.c | 2 +- src/esqlite3.erl | 14 ++++++++++---- src/esqlite3_nif.erl | 16 ++++++++++++++-- test/esqlite_test.erl | 4 ++-- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 50cc3f7..361c425 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -943,7 +943,7 @@ esqlite_backup_step(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) int rc = sqlite3_backup_step(backup->backup, n_page); if(rc == SQLITE_DONE) { - return make_atom(env, "done"); + return make_atom(env, "$done"); } if(rc != SQLITE_OK) { diff --git a/src/esqlite3.erl b/src/esqlite3.erl index ea462fa..8a4cdf0 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -430,22 +430,28 @@ backup_init(#esqlite3{db=Dest}, DestName, #esqlite3{db=Src}, SrcName) -> end. %% @doc Release the resources held by the backup. --spec backup_finish(esqlite3_backup()) -> ok | {error, _}. +-spec backup_finish(Backup) -> Result when + Backup :: esqlite3_backup(), + Result :: ok | error(). backup_finish(#esqlite3_backup{backup=Backup}) -> esqlite3_nif:backup_finish(Backup). %% @doc Do a backup step. --spec backup_step(esqlite3_backup(), integer()) -> ok | {error, _}. +-spec backup_step(esqlite3_backup(), integer()) -> ok | '$done' | error(). backup_step(#esqlite3_backup{backup=Backup}, NPage) -> esqlite3_nif:backup_step(Backup, NPage). %% @doc Get the remaining number of pages which need to be backed up. --spec backup_remaining(esqlite3_backup()) -> pos_integer(). +-spec backup_remaining(Backup) -> Remaining when + Backup :: esqlite3_backup(), + Remaining :: integer(). backup_remaining(#esqlite3_backup{backup=Backup}) -> esqlite3_nif:backup_remaining(Backup). %% @doc Get the remaining number of pages which need to be backed up. --spec backup_pagecount(esqlite3_backup()) -> pos_integer(). +-spec backup_pagecount(Backup) -> Pagecount when + Backup :: esqlite3_backup(), + Pagecount :: integer(). backup_pagecount(#esqlite3_backup{backup=Backup}) -> esqlite3_nif:backup_pagecount(Backup). diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index e6a1511..e341f82 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -224,25 +224,37 @@ column_decltypes(_Stmt) -> %% @doc Initialize a backup procedure of a database. -% erlang:nif_error(nif_library_not_loaded). -spec backup_init(Destination, DestinationName, Source, SourceName) -> InitResult when Destination :: esqlite3_ref(), DestinationName :: iodata(), Source :: esqlite3_ref(), SourceName :: iodata(), - InitResult :: {ok, esqlite3_backup_ref()} | {error, _}. + InitResult :: {ok, esqlite3_backup_ref()} | error(). backup_init(_Dest, _DestName, _Src, _SrcName) -> erlang:nif_error(nif_library_not_loaded). +-spec backup_remaining(Backup) -> Remaining when + Backup :: esqlite3_backup_ref(), + Remaining :: integer(). backup_remaining(_Backup) -> erlang:nif_error(nif_library_not_loaded). +-spec backup_pagecount(Backup) -> Pagecount when + Backup :: esqlite3_backup_ref(), + Pagecount :: integer(). backup_pagecount(_Backup) -> erlang:nif_error(nif_library_not_loaded). +-spec backup_step(Backup, NPage) -> Result when + Backup :: esqlite3_backup_ref(), + NPage :: integer(), + Result :: ok | '$done' | error(). backup_step(_Backup, _PageCount) -> erlang:nif_error(nif_library_not_loaded). +-spec backup_finish(Backup) -> Result when + Backup :: esqlite3_backup_ref(), + Result :: ok | error(). backup_finish(_Backup) -> erlang:nif_error(nif_library_not_loaded). diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 5238204..6cf8de7 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -430,7 +430,7 @@ backup_test() -> 0 = esqlite3:backup_remaining(Backup), 0 = esqlite3:backup_pagecount(Backup), - done = esqlite3:backup_step(Backup, 1), + '$done' = esqlite3:backup_step(Backup, 1), cleanup(), @@ -467,7 +467,7 @@ backup1_test() -> 27 = esqlite3:backup_pagecount(Backup), %% Do all the remaining pages. - done = esqlite3:backup_step(Backup, -1), + '$done' = esqlite3:backup_step(Backup, -1), 0 = esqlite3:backup_remaining(Backup), 27 = esqlite3:backup_pagecount(Backup), From ae0ec25419960e8d1c9963c46b34eaf1a7d7a68c Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 29 May 2022 23:19:42 +0200 Subject: [PATCH 36/37] Added small blurb to the readme --- README.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5cd8efc..004a00b 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,9 @@ in the nif library or the sqlite database can crash the entire Erlang VM. If you do not want to take this risk, it is always possible to access the sqlite nif from a separate erlang node. -Special care has been taken not to block the scheduler of the calling -process. This is done by handling all commands from erlang within a -lightweight thread. The erlang scheduler will get control back when -the command has been added to the command-queue of the thread. +Special care has been taken not to block the normal erlang scheduler +of the calling process. This is done by handling neccesary commands +from erlang by using a dirty scheduler. SQLite Compile Options ---------------------- @@ -66,3 +65,19 @@ INSERT INTO table VALUES("abcd", 1234); Will not work, because it sees the value 'abcd' as a SQL object value, and not a string literal. +Version 0.8.0 +------------- + +This version is a major derivation from previous versions. When +I started with this library it was implemented by using a separate +os level thread per connection. At the time this was the only way +to use functions in C which take longer to process than 1ms. +A lot has changed since then. The VM now has dirty schedulers +which make it possible to remove the thread per connection. +This makes it possible to open a lot more connections. On the +SQLite side some things have also changed. Extended error codes, +introspection into the internals. This release modernizes the +integration. In some places the API is no longer compatible and +will require small changes. + + From 99214043418f05e3372db62f8a6d367fe37f7612 Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Sun, 29 May 2022 23:20:48 +0200 Subject: [PATCH 37/37] Added small blurb to the readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 004a00b..6540c04 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ This makes it possible to open a lot more connections. On the SQLite side some things have also changed. Extended error codes, introspection into the internals. This release modernizes the integration. In some places the API is no longer compatible and -will require small changes. +will require small changes. In order to ease this process the +library now has typespecs, and the documentation was extended.