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