From 6bdaf1e894c88b86c187750f2095cd3a45818c8b Mon Sep 17 00:00:00 2001 From: Maas-Maarten Zeeman Date: Thu, 6 Jan 2022 23:55:40 +0100 Subject: [PATCH] Added basics to expose backup api --- c_src/esqlite3_nif.c | 338 ++++++++++++++++++++++++++++++++++++------- src/esqlite3_nif.erl | 68 ++++++--- 2 files changed, 339 insertions(+), 67 deletions(-) diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index e96e6b5..4d23f53 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -47,6 +47,10 @@ typedef struct { sqlite3_stmt *statement; } esqlite_statement; +/* data associated with ongoing backup */ +typedef struct { + sqlite3_backup *backup; +} esqlite_backup; typedef enum { cmd_unknown, @@ -61,6 +65,11 @@ typedef enum { 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, @@ -115,36 +124,37 @@ 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"; + 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"; } @@ -180,7 +190,7 @@ command_destroy(void *obj) esqlite_command *cmd = (esqlite_command *) obj; if(cmd->env != NULL) - enif_free_env(cmd->env); + enif_free_env(cmd->env); enif_free(cmd); } @@ -194,7 +204,7 @@ command_create() cmd->env = enif_alloc_env(); if(cmd->env == NULL) { - command_destroy(cmd); + command_destroy(cmd); return NULL; } @@ -246,6 +256,14 @@ destruct_esqlite_statement(ErlNifEnv *env, void *arg) stmt->statement = NULL; } +static void +destruct_esqlite_backup(ErlNifEnv *env, void *arg) +{ + esqlite_backup *backup = (esqlite_backup *) arg; + + backup->backup = NULL; +} + static ERL_NIF_TERM do_open(ErlNifEnv *env, esqlite_connection *db, const ERL_NIF_TERM arg) { @@ -262,11 +280,11 @@ do_open(ErlNifEnv *env, esqlite_connection *db, const ERL_NIF_TERM arg) */ 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; + error = make_sqlite3_error_tuple(env, rc, db->db); + sqlite3_close_v2(db->db); + db->db = NULL; - return error; + return error; } sqlite3_busy_timeout(db->db, 2000); @@ -286,7 +304,7 @@ update_callback(void *arg, int sqlite_operation_type, char const *sqlite_databas return; if(!cmd) - return; + return; rowid = enif_make_int64(cmd->env, sqlite_rowid); table = enif_make_string(cmd->env, sqlite_table, ERL_NIF_LATIN1); @@ -304,6 +322,7 @@ update_callback(void *arg, int sqlite_operation_type, char const *sqlite_databas default: return; } + cmd->type = cmd_notification; cmd->arg = enif_make_tuple3(cmd->env, type, table, rowid); push_command(cmd->env, db, cmd); @@ -710,6 +729,36 @@ do_column_types(ErlNifEnv *env, sqlite3_stmt *stmt) return column_types; } +static ERL_NIF_TERM +do_backup_init(ErlNifEnv *env, const ERL_NIF_TERM arg) +{ + return make_atom(env, "todo"); +} + +static ERL_NIF_TERM +do_backup_step(ErlNifEnv *env, const ERL_NIF_TERM arg) +{ + return make_atom(env, "todo"); +} + +static ERL_NIF_TERM +do_backup_remaining(ErlNifEnv *env, const ERL_NIF_TERM arg) +{ + return make_atom(env, "todo"); +} + +static ERL_NIF_TERM +do_backup_pagecount(ErlNifEnv *env, const ERL_NIF_TERM arg) +{ + return make_atom(env, "todo"); +} + +static ERL_NIF_TERM +do_backup_finish(ErlNifEnv *env, const ERL_NIF_TERM arg) +{ + return make_atom(env, "todo"); +} + static ERL_NIF_TERM do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) { @@ -755,6 +804,16 @@ evaluate_command(esqlite_command *cmd, esqlite_connection *conn) 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, cmd->arg); + case cmd_backup_step: + return do_backup_step(cmd->env, 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: @@ -763,9 +822,13 @@ evaluate_command(esqlite_command *cmd, esqlite_connection *conn) return do_insert(cmd->env, conn, cmd->arg); case cmd_get_autocommit: return do_get_autocommit(cmd->env, conn); - default: - return make_error_tuple(cmd->env, "invalid_command"); + 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 @@ -917,17 +980,17 @@ esqlite_exec(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) ErlNifPid pid; if(argc != 4) - return enif_make_badarg(env); + return enif_make_badarg(env); if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) - return enif_make_badarg(env); + return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); + return make_error_tuple(env, "invalid_ref"); if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); + return make_error_tuple(env, "invalid_pid"); cmd = command_create(); if(!cmd) - return make_error_tuple(env, "command_create_failed"); + return make_error_tuple(env, "command_create_failed"); /* command */ cmd->type = cmd_exec; @@ -949,17 +1012,17 @@ esqlite_changes(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) ErlNifPid pid; if(argc != 3) - return enif_make_badarg(env); + return enif_make_badarg(env); if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &db)) - return enif_make_badarg(env); + return enif_make_badarg(env); if(!enif_is_ref(env, argv[1])) - return make_error_tuple(env, "invalid_ref"); + return make_error_tuple(env, "invalid_ref"); if(!enif_get_local_pid(env, argv[2], &pid)) - return make_error_tuple(env, "invalid_pid"); + return make_error_tuple(env, "invalid_pid"); cmd = command_create(); if(!cmd) - return make_error_tuple(env, "command_create_failed"); + return make_error_tuple(env, "command_create_failed"); /* command */ cmd->type = cmd_changes; @@ -1251,7 +1314,7 @@ esqlite_column_types(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) ErlNifPid pid; if(argc != 4) - return enif_make_badarg(env); + return enif_make_badarg(env); if(!enif_get_resource(env, argv[0], esqlite_connection_type, (void **) &conn)) return enif_make_badarg(env); @@ -1277,6 +1340,168 @@ 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; + 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)) + 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"); + + 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]); + + /* 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; + 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[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]); + cmd->stmt = NULL; + + 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; + 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)) + 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->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[]) +{ + esqlite_connection *conn; + esqlite_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)) + return enif_make_badarg(env); + // backup + 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->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); +} + +/* + * 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; + 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); + // backup + 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->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); +} + /* * Interrupt currently active query. */ @@ -1343,6 +1568,14 @@ on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) return -1; esqlite_statement_type = rt; + rt = enif_open_resource_type(env, "esqlite3_nif", "esqlite_backup_type", + destruct_esqlite_backup, ERL_NIF_RT_CREATE, NULL); + if(!rt) + return -1; + esqlite_statement_type = rt; + + + atom_esqlite3 = make_atom(env, "esqlite3"); return 0; @@ -1374,6 +1607,13 @@ static ErlNifFunc nif_funcs[] = { {"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}, + {"backup_remaining", 4, esqlite_backup_remaining}, + {"backup_pagecount", 4, esqlite_backup_remaining}, + {"backup_finish", 4, esqlite_backup_finish}, + {"interrupt", 1, esqlite_interrupt, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"close", 3, esqlite_close} }; diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index 02a1678..96301fb 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -21,27 +21,34 @@ -author("Maas-Maarten Zeeman "). %% low-level exports --export([start/0, - open/4, - 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, - interrupt/1, - close/3 - ]). +-export([ + start/0, + open/4, + 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 +]). -type raw_connection() :: reference(). -type raw_statement() :: reference(). +-type raw_backup() :: reference(). -type sql() :: iodata(). -export_type([raw_connection/0, raw_statement/0, sql/0]). @@ -137,6 +144,31 @@ column_names(_Db, _Stmt, _Ref, _Dest) -> 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(), iodata(), raw_connection(), iodata(), 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). + +%% @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). + +%% @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). + +%% @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). + %% @doc Interrupt all active queries. -spec interrupt(raw_connection()) -> ok. interrupt(_Db) ->