diff --git a/Makefile b/Makefile index c32dc62..4deab9f 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ compile: $(REBAR_COMPILE) test: - $(REBAR) eunit + $(REBAR_COMPILE) eunit clean: -rm -rf deps ebin priv doc/* .eunit c_src/*.o diff --git a/c_src/sqlite3_drv.c b/c_src/sqlite3_drv.c index 68584f1..2aa133c 100644 --- a/c_src/sqlite3_drv.c +++ b/c_src/sqlite3_drv.c @@ -103,7 +103,6 @@ static void stop(ErlDrvData handle) { static int control(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen) { sqlite3_drv_t* driver_data = (sqlite3_drv_t*) drv_data; - switch (command) { case CMD_SQL_EXEC: sql_exec(driver_data, buf, len); @@ -422,11 +421,12 @@ static void ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data) { // Unknown Command static int unknown(sqlite3_drv_t *drv, char *command, int command_size) { - // Return {error, unknown_command} + // Return {Port, error, unknown_command} ErlDrvTermData spec[] = { + ERL_DRV_PORT, driver_mk_port(drv->port), ERL_DRV_ATOM, drv->atom_error, ERL_DRV_ATOM, drv->atom_unknown_cmd, - ERL_DRV_TUPLE, 2 + ERL_DRV_TUPLE, 3 }; return driver_output_term(drv->port, spec, sizeof(spec) / sizeof(spec[0])); } diff --git a/src/sqlite3.erl b/src/sqlite3.erl index 8e2c420..215b15f 100644 --- a/src/sqlite3.erl +++ b/src/sqlite3.erl @@ -15,7 +15,7 @@ -export([open/1, open/2]). -export([start_link/1, start_link/2]). -export([stop/0, close/1]). --export([sql_exec/1, sql_exec/2]). +-export([sql_exec/1, sql_exec/2, sql_exec/3]). -export([create_table/2, create_table/3, create_table/4]). -export([list_tables/0, list_tables/1, table_info/1, table_info/2]). @@ -157,6 +157,18 @@ sql_exec(SQL) -> sql_exec(Db, SQL) -> gen_server:call(Db, {sql_exec, SQL}). +%%-------------------------------------------------------------------- +%% @spec sql_exec(Db :: atom(), Sql :: iodata(), Params) -> any() +%% Params = [sql_value() | {atom() | string() | integer(), sql_value()}] +%% @doc +%% Executes the Sql statement with parameters Params directly on the Db +%% database. Returns the result of the Sql call. +%% @end +%%-------------------------------------------------------------------- +-spec sql_exec(atom(), iodata(), [sql_value() | {atom() | string() | integer(), sql_value()}]) -> any(). +sql_exec(Db, SQL, Params) -> + gen_server:call(Db, {sql_bind_and_exec, SQL, Params}). + %%-------------------------------------------------------------------- %% @spec create_table(Tbl :: atom(), TblInfo :: [{atom(), atom()}]) -> any() %% @doc @@ -575,12 +587,13 @@ handle_call({table_info, Tbl}, _From, State) -> {reply, table_does_not_exist, State} end; handle_call({create_function, FunctionName, Function}, _From, #state{port = Port} = State) -> - % make sure we only get table info. - % SQL Injection warning Reply = exec(Port, {create_function, FunctionName, Function}), {reply, Reply, State}; handle_call({sql_exec, SQL}, _From, State) -> do_handle_call_sql_exec(SQL, State); +handle_call({sql_bind_and_exec, SQL, Params}, _From, State) -> + Reply = do_sql_bind_and_exec(SQL, Params, State), + {reply, Reply, State}; handle_call({create_table, Tbl, Columns}, _From, State) -> SQL = sqlite3_lib:create_table_sql(Tbl, Columns), do_handle_call_sql_exec(SQL, State); @@ -693,6 +706,7 @@ get_priv_dir() -> -define(SQL_EXEC_COMMAND, 2). -define(SQL_CREATE_FUNCTION, 3). +-define(SQL_BIND_AND_EXEC_COMMAND, 4). create_port_cmd(DbFile) -> atom_to_list(?DRIVER_NAME) ++ " " ++ DbFile. @@ -705,17 +719,25 @@ do_sql_exec(SQL, #state{port = Port}) -> ?dbg("SQL: ~s~n", [SQL]), exec(Port, {sql_exec, SQL}). +do_sql_bind_and_exec(SQL, Params, #state{port = Port}) -> + ?dbg("SQL: ~s; Parameters: ~p~n", [SQL, Params]), + exec(Port, {sql_bind_and_exec, SQL, Params}). + exec(_Port, {create_function, _FunctionName, _Function}) -> error_logger:error_report([{application, sqlite3}, "NOT IMPL YET"]); %port_control(Port, ?SQL_CREATE_FUNCTION, list_to_binary(Cmd)), %wait_result(Port); exec(Port, {sql_exec, Cmd}) -> port_control(Port, ?SQL_EXEC_COMMAND, Cmd), + wait_result(Port); +exec(Port, {sql_bind_and_exec, SQL, Params}) -> + Bin = term_to_binary({iolist_to_binary(SQL), Params}), + port_control(Port, ?SQL_BIND_AND_EXEC_COMMAND, Bin), + io:format(user, "Sending sql_bind_and_exec to port", []), wait_result(Port). wait_result(Port) -> receive - %% Messages given at http://www.erlang.org/doc/reference_manual/ports.html {Port, error, Reason} -> error_logger:error_msg("sqlite3 driver error: ~s~n", [Reason]), % ?dbg("Error: ~p~n", [Reason]), diff --git a/test/sqlite3_test.erl b/test/sqlite3_test.erl index 7a07afd..2566477 100644 --- a/test/sqlite3_test.erl +++ b/test/sqlite3_test.erl @@ -38,6 +38,7 @@ all_test_() -> fun open_db/0, fun close_db/1, [?FuncTest(basic_functionality), + ?FuncTest(parametrized), ?FuncTest(blob), ?FuncTest(escaping), ?FuncTest(select_many_records), @@ -102,6 +103,14 @@ basic_functionality() -> ok, sqlite3:drop_table(ct, user)). +parametrized() -> + drop_table_if_exists(ct, user1), + sqlite3:create_table(ct, user1, [{id, integer}, {name, text}]), + sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [{1, 1}, {2, "john"}]), + ?assertEqual( + [{columns, ["id", "name"]}, {rows, [{1, <<"john">>}]}], + sqlite3:sql_exec(ct, "select * from user1;")). + blob() -> drop_table_if_exists(ct, blobs), sqlite3:create_table(ct, blobs, [{blob_col, blob}]), @@ -139,21 +148,21 @@ select_many_records() -> N, length(rows(sqlite3:sql_exec(ct, "select * from many_records;")))). -%% note that inserts are actually serialized by gen_server -concurrent_inserts_test() -> - N = 1024, - sqlite3:open(concurrent, [in_memory]), %% doing this test not in memory is much slower! - drop_table_if_exists(concurrent, t), - sqlite3:create_table(concurrent, t, [{id0, integer}]), - Self = self(), - [spawn(fun () -> - sqlite3:write(concurrent, t, [{id0, X}]), - Self ! {finished, N} - end) || X <- lists:seq(1, N)], - loop_concurrent_inserts(N), - ?assertEqual( - N, length(rows(sqlite3:read_all(concurrent, t)))), - sqlite3:close(concurrent). +%% %% note that inserts are actually serialized by gen_server +%% concurrent_inserts_test() -> +%% N = 1024, +%% sqlite3:open(concurrent, [in_memory]), %% doing this test not in memory is much slower! +%% drop_table_if_exists(concurrent, t), +%% sqlite3:create_table(concurrent, t, [{id0, integer}]), +%% Self = self(), +%% [spawn(fun () -> +%% sqlite3:write(concurrent, t, [{id0, X}]), +%% Self ! {finished, N} +%% end) || X <- lists:seq(1, N)], +%% loop_concurrent_inserts(N), +%% ?assertEqual( +%% N, length(rows(sqlite3:read_all(concurrent, t)))), +%% sqlite3:close(concurrent). loop_concurrent_inserts(0) -> ok;