From 19080b849c57e3271397e85fa8cda6d77b605497 Mon Sep 17 00:00:00 2001 From: Michael Ries Date: Fri, 30 Jan 2015 09:16:35 -0700 Subject: [PATCH] get column types --- .gitignore | 8 +---- c_src/esqlite3_nif.c | 73 +++++++++++++++++++++++++++++++++++++++++++ src/esqlite3.erl | 13 ++++++++ src/esqlite3_nif.erl | 7 +++++ test/esqlite_test.erl | 20 ++++++++++++ 5 files changed, 114 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index f5f0461..03d45f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,9 @@ .rebar - rebar - ebin - priv - .eunit/* - *.swp - *.o - *~ +_build diff --git a/c_src/esqlite3_nif.c b/c_src/esqlite3_nif.c index 1109f89..72daa31 100644 --- a/c_src/esqlite3_nif.c +++ b/c_src/esqlite3_nif.c @@ -59,6 +59,7 @@ typedef enum { cmd_step, cmd_reset, cmd_column_names, + cmd_column_types, cmd_close, cmd_stop, cmd_insert @@ -567,6 +568,37 @@ do_column_names(ErlNifEnv *env, sqlite3_stmt *stmt) 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 make_error_tuple(env, "no_columns"); + + array = (ERL_NIF_TERM *) malloc(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) { + free(array); + return make_error_tuple(env, "sqlite3_malloc_failure"); + } + + array[i] = make_atom(env, type); + } + + column_types = enif_make_tuple_from_array(env, array, size); + free(array); + return column_types; +} + static ERL_NIF_TERM do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg) { @@ -600,6 +632,8 @@ evaluate_command(esqlite_command *cmd, esqlite_connection *conn) return do_bind(cmd->env, conn->db, cmd->stmt, cmd->arg); case cmd_column_names: return do_column_names(cmd->env, cmd->stmt); + case cmd_column_types: + return do_column_types(cmd->env, cmd->stmt); case cmd_close: return do_close(cmd->env, conn, cmd->arg); case cmd_insert: @@ -990,6 +1024,44 @@ esqlite_column_names(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return push_command(env, stmt->connection, 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_statement *stmt; + esqlite_command *cmd = NULL; + ErlNifPid pid; + + if(argc != 3) + return enif_make_badarg(env); + if(!enif_get_resource(env, argv[0], esqlite_statement_type, (void **) &stmt)) + 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(!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[1]); + cmd->pid = pid; + cmd->stmt = stmt->statement; + + if(!stmt->connection) + return make_error_tuple(env, "no_connection"); + if(!stmt->connection->commands) + return make_error_tuple(env, "no_command_queue"); + + return push_command(env, stmt->connection, cmd); +} + /* * Close the database */ @@ -1065,6 +1137,7 @@ static ErlNifFunc nif_funcs[] = { // TODO: {"esqlite_bind", 3, esqlite_bind_named}, {"bind", 4, esqlite_bind}, {"column_names", 3, esqlite_column_names}, + {"column_types", 3, esqlite_column_types}, {"close", 3, esqlite_close} }; diff --git a/src/esqlite3.erl b/src/esqlite3.erl index a345ab2..db0ab5a 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -32,6 +32,7 @@ fetchone/1, fetchall/1, column_names/1, column_names/2, + column_types/1, column_types/2, close/1, close/2]). -export([q/2, q/3, map/3, foreach/3]). @@ -305,6 +306,18 @@ column_names(Stmt, Timeout) -> ok = esqlite3_nif:column_names(Stmt, Ref, self()), receive_answer(Ref, Timeout). +%% @doc Return the column types of the prepared statement. +%% +-spec column_types(statement()) -> tuple(atom()). +column_types(Stmt) -> + column_types(Stmt, ?DEFAULT_TIMEOUT). + +-spec column_types(statement(), timeout()) -> tuple(atom()). +column_types(Stmt, Timeout) -> + Ref = make_ref(), + ok = esqlite3_nif:column_types(Stmt, Ref, self()), + receive_answer(Ref, Timeout). + %% @doc Close the database %% %% @spec close(connection()) -> ok | {error, error_message()} diff --git a/src/esqlite3_nif.erl b/src/esqlite3_nif.erl index f21311d..21d2ec3 100644 --- a/src/esqlite3_nif.erl +++ b/src/esqlite3_nif.erl @@ -32,6 +32,7 @@ finalize/3, bind/4, column_names/3, + column_types/3, close/3 ]). @@ -116,6 +117,12 @@ bind(_Stmt, _Ref, _Dest, _Args) -> column_names(_Stmt, _Ref, _Dest) -> exit(nif_library_not_loaded). +%% @doc Retrieve the column types of the prepared statement +%% +%% @spec column_types(statement(), reference(), pid()) -> {ok, tuple()} | {error, message()} +column_types(_Stmt, _Ref, _Dest) -> + exit(nif_library_not_loaded). + %% @doc Close the connection. %% %% @spec close(connection(), reference(), pid()) -> ok | {error, message()} diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 09d2790..0887a2a 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -180,6 +180,26 @@ column_names_test() -> 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), + {'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. + reset_test() -> {ok, Db} = esqlite3:open(":memory:"),