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)")),