diff --git a/c_src/sqlite3_drv.c b/c_src/sqlite3_drv.c index a246b22..15aedee 100644 --- a/c_src/sqlite3_drv.c +++ b/c_src/sqlite3_drv.c @@ -213,6 +213,16 @@ static inline int output_ok(sqlite3_drv_t *drv) { return driver_output_term(drv->port, spec, sizeof(spec) / sizeof(spec[0])); } +static inline int output_done(sqlite3_drv_t *drv) { + // Return {Port, ok} + ErlDrvTermData spec[] = { + ERL_DRV_PORT, driver_mk_port(drv->port), + ERL_DRV_ATOM, drv->atom_done, + ERL_DRV_TUPLE, 2 + }; + return driver_output_term(drv->port, spec, sizeof(spec) / sizeof(spec[0])); +} + static inline async_sqlite3_command *make_async_command( sqlite3_drv_t *drv, sqlite3_stmt *statement) { async_sqlite3_command *result = @@ -268,23 +278,24 @@ static int sql_exec_script(sqlite3_drv_t *drv, char *command, int command_size) 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); + output_db_error(drv); + break; } else if (statement == NULL) { - return output_error(drv, SQLITE_MISUSE, "empty statement"); + output_error(drv, SQLITE_MISUSE, "empty statement"); + break; } result = sql_exec_statement(drv, statement); if (result) { // there was an error, bail out - return result; + break; } } + output_done(drv); return result; } diff --git a/src/sqlite3.erl b/src/sqlite3.erl index 6065654..ad6f451 100644 --- a/src/sqlite3.erl +++ b/src/sqlite3.erl @@ -172,13 +172,16 @@ 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() +%% @spec sql_exec_script(Db :: atom(), Sql :: iodata()) -> [sql_result()] %% @doc %% Executes the Sql script (consisting of semicolon-separated statements) -%% directly on the Db database. Returns 'ok' if there were no errors. +%% directly on the Db database. Returns the list of their results (same as +%% if sql_exec/2 was called for all of them in order, but more efficient). +%% Note that any whitespace or comments after the last semicolon will be +%% considered an empty statement and produce the corresponding error. %% @end %%-------------------------------------------------------------------- --spec sql_exec_script(atom(), iodata()) -> sql_non_query_result(). +-spec sql_exec_script(atom(), iodata()) -> [sql_result()]. sql_exec_script(Db, SQL) -> gen_server:call(Db, {sql_exec_script, SQL}). @@ -862,7 +865,7 @@ exec(Port, {sql_bind_and_exec, SQL, Params}) -> wait_result(Port); exec(Port, {sql_exec_script, SQL}) -> port_control(Port, ?SQL_EXEC_SCRIPT, SQL), - wait_result(Port); + many_results_loop(Port); exec(Port, {prepare, SQL}) -> port_control(Port, ?PREPARE, SQL), wait_result(Port); @@ -899,6 +902,17 @@ wait_result(Port) -> {error, -1, Reason} end. +many_results_loop(Port) -> + do_many_results_loop(Port, []). + +do_many_results_loop(Port, Acc) -> + case wait_result(Port) of + done -> + lists:reverse(Acc); + Reply -> + do_many_results_loop(Port, [Reply | Acc]) + end. + parse_table_info(Info) -> [_, Tail] = string:tokens(Info, "()"), Cols = string:tokens(Tail, ","), diff --git a/test/sqlite3_test.erl b/test/sqlite3_test.erl index 1f0b56d..c746d53 100644 --- a/test/sqlite3_test.erl +++ b/test/sqlite3_test.erl @@ -17,6 +17,7 @@ -include_lib("eunit/include/eunit.hrl"). -define(FuncTest(Name), {??Name, fun Name/0}). +-define(WARN_ERROR_MESSAGE, io:format(user, "Error message should be shown...~n", [])). drop_all_tables(Db) -> Tables = sqlite3:list_tables(Db), @@ -61,7 +62,7 @@ basic_functionality() -> AbbyOnly = [{1, <<"abby">>, 20, 2000}], TableInfo = [{id, integer, [primary_key]}, {name, text, [not_null, unique]}, {age, integer}, {wage, integer}], drop_all_tables(ct), - ?debugMsg("Error message \"sqlite3 driver error: empty statement\" should be shown..."), + ?WARN_ERROR_MESSAGE, ?assertEqual( {error, 21, "empty statement"}, sqlite3:sql_exec(ct, "-- Comment")), @@ -81,7 +82,7 @@ basic_functionality() -> ?assertEqual( {rowid, 2}, sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), - ?debugMsg("Error message \"sqlite3 driver error: constraint failed\" should be shown..."), + ?WARN_ERROR_MESSAGE, ?assertEqual( {error, 19, "constraint failed"}, sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), @@ -241,15 +242,31 @@ script_test() -> ");", " ", "-- Comment", - "", "INSERT INTO person (id) VALUES (1);", "INSERT INTO person (id) VALUES (2);", " " ], "\n"), - ?assertEqual(ok, sqlite3:sql_exec_script(script, Script)), + ?WARN_ERROR_MESSAGE, + ?assertEqual( + [ok, ok, ok, {error, 21, "empty statement"}], + sqlite3:sql_exec_script(script, Script)), ?assertEqual( [{columns,["id"]},{rows,[{1},{2}]}], sqlite3:read_all(script, person)), + BadScript = string:join( + ["CREATE TABLE person2(", + "id INTEGER", + ");", + " ", + "-- Comment", + "SYNTAX ERROR;", + "INSERT INTO person (id) VALUES (2);", + " " + ], "\n"), + ?WARN_ERROR_MESSAGE, + ?assertEqual( + [ok, {error, 1, "near \"SYNTAX\": syntax error"}], + sqlite3:sql_exec_script(script, BadScript)), sqlite3:close(script). % create, read, update, delete