diff --git a/c_src/sqlite3_drv.c b/c_src/sqlite3_drv.c index 1b2e42a..a246b22 100644 --- a/c_src/sqlite3_drv.c +++ b/c_src/sqlite3_drv.c @@ -162,6 +162,9 @@ static int control( case CMD_PREPARED_COLUMNS: prepared_columns(driver_data, buf, len); break; + case CMD_SQL_EXEC_SCRIPT: + sql_exec_script(driver_data, buf, len); + break; default: unknown(driver_data, buf, len); } @@ -193,7 +196,7 @@ static inline int output_error( int term_count; return_error(drv, error_code, error, &dataset, &term_count); driver_output_term(drv->port, dataset, term_count); - return 0; + return 1; } static inline int output_db_error(sqlite3_drv_t *drv) { @@ -259,6 +262,32 @@ static int sql_exec(sqlite3_drv_t *drv, char *command, int command_size) { return sql_exec_statement(drv, statement); } +static int sql_exec_script(sqlite3_drv_t *drv, char *command, int command_size) { + int result; + const char *rest = command; + const char *end = command + command_size; + sqlite3_stmt *statement; + + // printf("SQL: %.*s\n", command_size, command); + + while (rest < end) { + result = sqlite3_prepare_v2(drv->db, command, end - command, &statement, &rest); + command = (char *) rest; // won't actually mutate! + if (result != SQLITE_OK) { + return output_db_error(drv); + } else if (statement == NULL) { + return output_error(drv, SQLITE_MISUSE, "empty statement"); + } + + result = sql_exec_statement(drv, statement); + if (result) { + // there was an error, bail out + return result; + } + } + return result; +} + static inline int decode_and_bind_param( sqlite3_drv_t *drv, char *buffer, int *p_index, sqlite3_stmt *statement, int param_index, int *p_type, int *p_size) { diff --git a/c_src/sqlite3_drv.h b/c_src/sqlite3_drv.h index b9c7a44..3609d70 100644 --- a/c_src/sqlite3_drv.h +++ b/c_src/sqlite3_drv.h @@ -34,6 +34,7 @@ #define CMD_PREPARED_CLEAR_BINDINGS 9 #define CMD_PREPARED_FINALIZE 10 #define CMD_PREPARED_COLUMNS 11 +#define CMD_SQL_EXEC_SCRIPT 12 // Number of bytes for each key // (160 bits for SHA1 hash) @@ -84,6 +85,7 @@ static int control(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen); static int sql_exec(sqlite3_drv_t *drv, char *buf, int len); static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buf, int len); +static int sql_exec_script(sqlite3_drv_t *drv, char *buf, int len); static int prepare(sqlite3_drv_t *drv, char *buf, int len); static int prepared_bind(sqlite3_drv_t *drv, char *buf, int len); static int prepared_step(sqlite3_drv_t *drv, char *buf, int len); diff --git a/src/sqlite3.erl b/src/sqlite3.erl index 8a402c5..6065654 100644 --- a/src/sqlite3.erl +++ b/src/sqlite3.erl @@ -15,7 +15,8 @@ -export([open/1, open/2]). -export([start_link/1, start_link/2]). -export([stop/0, close/1]). --export([sql_exec/1, sql_exec/2, sql_exec/3]). +-export([sql_exec/1, sql_exec/2, sql_exec/3, + sql_exec_script/2]). -export([prepare/2, bind/3, next/2, reset/2, clear_bindings/2, finalize/2, columns/2]). -export([create_table/2, create_table/3, create_table/4]). @@ -170,6 +171,17 @@ sql_exec(Db, SQL) -> sql_exec(Db, SQL, Params) -> gen_server:call(Db, {sql_bind_and_exec, SQL, Params}). +%%-------------------------------------------------------------------- +%% @spec sql_exec_script(Db :: atom(), Sql :: iodata()) -> sql_non_query_result() +%% @doc +%% Executes the Sql script (consisting of semicolon-separated statements) +%% directly on the Db database. Returns 'ok' if there were no errors. +%% @end +%%-------------------------------------------------------------------- +-spec sql_exec_script(atom(), iodata()) -> sql_non_query_result(). +sql_exec_script(Db, SQL) -> + gen_server:call(Db, {sql_exec_script, SQL}). + -spec prepare(atom(), iodata()) -> {ok, reference()} | sqlite_error(). prepare(Db, SQL) -> gen_server:call(Db, {prepare, SQL}). @@ -643,6 +655,9 @@ handle_call({sql_exec, SQL}, _From, 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({sql_exec_script, SQL}, _From, State) -> + Reply = do_sql_exec_script(SQL, 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); @@ -813,6 +828,7 @@ get_priv_dir() -> -define(PREPARED_CLEAR_BINDINGS, 9). -define(PREPARED_FINALIZE, 10). -define(PREPARED_COLUMNS, 11). +-define(SQL_EXEC_SCRIPT, 12). create_port_cmd(DbFile) -> atom_to_list(?DRIVER_NAME) ++ " " ++ DbFile. @@ -829,6 +845,10 @@ 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}). +do_sql_exec_script(SQL, #state{port = Port}) -> + ?dbg("SQL: ~s~n", [SQL]), + exec(Port, {sql_exec_script, SQL}). + 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)), @@ -840,6 +860,9 @@ 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), wait_result(Port); +exec(Port, {sql_exec_script, SQL}) -> + port_control(Port, ?SQL_EXEC_SCRIPT, SQL), + wait_result(Port); exec(Port, {prepare, SQL}) -> port_control(Port, ?PREPARE, SQL), wait_result(Port); diff --git a/test/sqlite3_test.erl b/test/sqlite3_test.erl index e7bf266..1f0b56d 100644 --- a/test/sqlite3_test.erl +++ b/test/sqlite3_test.erl @@ -233,6 +233,25 @@ prepared_test() -> ?assertEqual(ok, sqlite3:finalize(prepared, Ref2)), sqlite3:close(prepared). +script_test() -> + sqlite3:open(script, [in_memory]), + Script = string:join( + ["CREATE TABLE person(", + "id INTEGER", + ");", + " ", + "-- Comment", + "", + "INSERT INTO person (id) VALUES (1);", + "INSERT INTO person (id) VALUES (2);", + " " + ], "\n"), + ?assertEqual(ok, sqlite3:sql_exec_script(script, Script)), + ?assertEqual( + [{columns,["id"]},{rows,[{1},{2}]}], + sqlite3:read_all(script, person)), + sqlite3:close(script). + % create, read, update, delete %%==================================================================== %% Internal functions