Merge branch 'master' of github.com:alexeyr/erlang-sqlite3

This commit is contained in:
Alexey Romanov
2011-02-16 17:56:45 +03:00
6 changed files with 404 additions and 59 deletions

2
.gitignore vendored
View File

@@ -17,3 +17,5 @@ TEST*.xml
*.dll *.dll
rebar.cross_compile.config rebar.cross_compile.config
config.tmp config.tmp
*.beam
*.so

View File

@@ -17,7 +17,7 @@ test:
$(REBAR_COMPILE) eunit $(REBAR_COMPILE) eunit
clean: clean:
-rm -rf deps ebin priv doc/* .eunit c_src/*.o config.tmp -rm -rf deps ebin priv/*.so doc/* .eunit/* c_src/*.o config.tmp
docs: docs:
$(REBAR_COMPILE) doc $(REBAR_COMPILE) doc

View File

@@ -162,6 +162,9 @@ static int control(
case CMD_PREPARED_COLUMNS: case CMD_PREPARED_COLUMNS:
prepared_columns(driver_data, buf, len); prepared_columns(driver_data, buf, len);
break; break;
case CMD_SQL_EXEC_SCRIPT:
sql_exec_script(driver_data, buf, len);
break;
default: default:
unknown(driver_data, buf, len); unknown(driver_data, buf, len);
} }
@@ -193,7 +196,7 @@ static inline int output_error(
int term_count; int term_count;
return_error(drv, error_code, error, &dataset, &term_count); return_error(drv, error_code, error, &dataset, &term_count);
driver_output_term(drv->port, 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) { static inline int output_db_error(sqlite3_drv_t *drv) {
@@ -210,6 +213,16 @@ static inline int output_ok(sqlite3_drv_t *drv) {
return driver_output_term(drv->port, spec, sizeof(spec) / sizeof(spec[0])); 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( static inline async_sqlite3_command *make_async_command(
sqlite3_drv_t *drv, sqlite3_stmt *statement) { sqlite3_drv_t *drv, sqlite3_stmt *statement) {
async_sqlite3_command *result = async_sqlite3_command *result =
@@ -242,22 +255,50 @@ static inline int sql_exec_statement(
static int sql_exec(sqlite3_drv_t *drv, char *command, int command_size) { static int sql_exec(sqlite3_drv_t *drv, char *command, int command_size) {
int result; int result;
char *rest = NULL; const char *rest;
sqlite3_stmt *statement; sqlite3_stmt *statement;
#ifdef DEBUG #ifdef DEBUG
fprintf(drv->log, "Preexec: %.*s\n", command_size, command); fprintf(drv->log, "Preexec: %.*s\n", command_size, command);
fflush(drv->log); fflush(drv->log);
#endif #endif
result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, &rest);
(const char **) &rest);
if (result != SQLITE_OK) { if (result != SQLITE_OK) {
return output_db_error(drv); return output_db_error(drv);
} else if (statement == NULL) {
return output_error(drv, SQLITE_MISUSE, "empty statement");
} }
return sql_exec_statement(drv, statement); 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;
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) {
output_db_error(drv);
break;
} else if (statement == NULL) {
output_error(drv, SQLITE_MISUSE, "empty statement");
break;
}
result = sql_exec_statement(drv, statement);
if (result) {
// there was an error, bail out
break;
}
}
output_done(drv);
return result;
}
static inline int decode_and_bind_param( static inline int decode_and_bind_param(
sqlite3_drv_t *drv, char *buffer, int *p_index, sqlite3_drv_t *drv, char *buffer, int *p_index,
sqlite3_stmt *statement, int param_index, int *p_type, int *p_size) { sqlite3_stmt *statement, int param_index, int *p_type, int *p_size) {
@@ -441,7 +482,7 @@ static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buffer, int buffer_size)
int result; int result;
int index = 0; int index = 0;
int type, size; int type, size;
char *rest = NULL; const char *rest;
sqlite3_stmt *statement; sqlite3_stmt *statement;
long bin_size; long bin_size;
char *command; char *command;
@@ -469,12 +510,13 @@ static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buffer, int buffer_size)
command = driver_alloc(size * sizeof(char)); command = driver_alloc(size * sizeof(char));
ei_decode_binary(buffer, &index, command, &bin_size); ei_decode_binary(buffer, &index, command, &bin_size);
// assert(bin_size == size) // assert(bin_size == size)
result = sqlite3_prepare_v2(drv->db, command, size, &statement, result = sqlite3_prepare_v2(drv->db, command, size, &statement, &rest);
(const char **) &rest);
driver_free(command); driver_free(command);
if (result != SQLITE_OK) { if (result != SQLITE_OK) {
return output_db_error(drv); return output_db_error(drv);
} else if (statement == NULL) {
return output_error(drv, SQLITE_MISUSE, "empty statement");
} }
result = bind_parameters(drv, buffer, buffer_size, &index, statement, &type, &size); result = bind_parameters(drv, buffer, buffer_size, &index, statement, &type, &size);
@@ -917,7 +959,7 @@ static void ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data) {
static int prepare(sqlite3_drv_t *drv, char *command, int command_size) { static int prepare(sqlite3_drv_t *drv, char *command, int command_size) {
int result; int result;
char *rest = NULL; const char *rest;
sqlite3_stmt *statement; sqlite3_stmt *statement;
ErlDrvTermData spec[6]; ErlDrvTermData spec[6];
@@ -925,10 +967,11 @@ static int prepare(sqlite3_drv_t *drv, char *command, int command_size) {
fprintf(drv->log, "Preparing statement: %.*s\n", command_size, command); fprintf(drv->log, "Preparing statement: %.*s\n", command_size, command);
fflush(drv->log); fflush(drv->log);
#endif #endif
result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, &rest);
(const char **) &rest);
if (result != SQLITE_OK) { if (result != SQLITE_OK) {
return output_db_error(drv); return output_db_error(drv);
} else if (statement == NULL) {
return output_error(drv, SQLITE_MISUSE, "empty statement");
} }
if (drv->prepared_count >= drv->prepared_alloc) { if (drv->prepared_count >= drv->prepared_alloc) {

View File

@@ -34,6 +34,7 @@
#define CMD_PREPARED_CLEAR_BINDINGS 9 #define CMD_PREPARED_CLEAR_BINDINGS 9
#define CMD_PREPARED_FINALIZE 10 #define CMD_PREPARED_FINALIZE 10
#define CMD_PREPARED_COLUMNS 11 #define CMD_PREPARED_COLUMNS 11
#define CMD_SQL_EXEC_SCRIPT 12
// Number of bytes for each key // Number of bytes for each key
// (160 bits for SHA1 hash) // (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); int len, char **rbuf, int rlen);
static int sql_exec(sqlite3_drv_t *drv, char *buf, int len); 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_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 prepare(sqlite3_drv_t *drv, char *buf, int len);
static int prepared_bind(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); static int prepared_step(sqlite3_drv_t *drv, char *buf, int len);

View File

@@ -14,19 +14,26 @@
%% API %% API
-export([open/1, open/2]). -export([open/1, open/2]).
-export([start_link/1, start_link/2]). -export([start_link/1, start_link/2]).
-export([stop/0, close/1]). -export([stop/0, close/1, close_timeout/2]).
-export([sql_exec/1, sql_exec/2, sql_exec/3]). -export([sql_exec/1, sql_exec/2, sql_exec_timeout/3,
sql_exec_script/2, sql_exec_script_timeout/3,
sql_exec/3, sql_exec_timeout/4]).
-export([prepare/2, bind/3, next/2, reset/2, clear_bindings/2, finalize/2, -export([prepare/2, bind/3, next/2, reset/2, clear_bindings/2, finalize/2,
columns/2]). columns/2, prepare_timeout/3, bind_timeout/4, next_timeout/3,
-export([create_table/2, create_table/3, create_table/4]). reset_timeout/3, clear_bindings_timeout/3, finalize_timeout/3,
-export([list_tables/0, list_tables/1, table_info/1, table_info/2]). columns_timeout/3]).
-export([write/2, write/3, write_many/2, write_many/3]). -export([create_table/2, create_table/3, create_table/4, create_table_timeout/4,
-export([update/3, update/4]). create_table_timeout/5]).
-export([read_all/2, read_all/3, read/2, read/3, read/4]). -export([list_tables/0, list_tables/1, list_tables_timeout/2,
-export([delete/2, delete/3]). table_info/1, table_info/2, table_info_timeout/3]).
-export([drop_table/1, drop_table/2]). -export([write/2, write/3, write_timeout/4, write_many/2, write_many/3,
-export([begin_transaction/1, commit_transaction/1, rollback_transaction/1]). write_many_timeout/4]).
-export([vacuum/0, vacuum/1]). -export([update/3, update/4, update_timeout/5]).
-export([read_all/2, read_all/3, read_all_timeout/3, read_all_timeout/4,
read/2, read/3, read/4, read_timeout/4, read_timeout/5]).
-export([delete/2, delete/3, delete_timeout/4]).
-export([drop_table/1, drop_table/2, drop_table_timeout/3]).
-export([vacuum/0, vacuum/1, vacuum_timeout/2]).
%% -export([create_function/3]). %% -export([create_function/3]).
@@ -127,6 +134,16 @@ open(Db, Options) ->
close(Db) -> close(Db) ->
gen_server:call(Db, close). gen_server:call(Db, close).
%%--------------------------------------------------------------------
%% @spec close_timeout(Db :: atom(), Timeout :: timeout()) -> ok
%% @doc
%% Closes the Db sqlite3 database.
%% @end
%%--------------------------------------------------------------------
-spec close_timeout(atom(), timeout()) -> 'ok'.
close_timeout(Db, Timeout) ->
gen_server:call(Db, close, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec stop() -> ok %% @spec stop() -> ok
%% @doc %% @doc
@@ -166,10 +183,63 @@ sql_exec(Db, SQL) ->
%% database. Returns the result of the Sql call. %% database. Returns the result of the Sql call.
%% @end %% @end
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec sql_exec(atom(), iodata(), [sql_value() | {atom() | string() | integer(), sql_value()}]) -> sql_result(). -spec sql_exec(atom(), iodata(), [sql_value() | {atom() | string() | integer(), sql_value()}]) ->
sql_result().
sql_exec(Db, SQL, Params) -> sql_exec(Db, SQL, Params) ->
gen_server:call(Db, {sql_bind_and_exec, SQL, Params}). gen_server:call(Db, {sql_bind_and_exec, SQL, Params}).
%%--------------------------------------------------------------------
%% @spec sql_exec_timeout(Db :: atom(), Sql :: iodata()) -> sql_result()
%% @doc
%% Executes the Sql statement directly on the Db database. Returns the
%% result of the Sql call.
%% @end
%%--------------------------------------------------------------------
-spec sql_exec_timeout(atom(), iodata(), timeout()) -> sql_result().
sql_exec_timeout(Db, SQL, Timeout) ->
gen_server:call(Db, {sql_exec, SQL}, Timeout).
%%--------------------------------------------------------------------
%% @spec sql_exec_timeout(Db :: atom(), Sql :: iodata(), Params) -> sql_result()
%% 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_timeout(atom(), iodata(), [sql_value() | {atom() | string() | integer(), sql_value()}], timeout()) ->
sql_result().
sql_exec_timeout(Db, SQL, Params, Timeout) ->
gen_server:call(Db, {sql_bind_and_exec, SQL, Params}, Timeout).
%%--------------------------------------------------------------------
%% @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 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_result()].
sql_exec_script(Db, SQL) ->
gen_server:call(Db, {sql_exec_script, SQL}).
%%--------------------------------------------------------------------
%% @spec sql_exec_script_timeout(Db :: atom(), Sql :: iodata(), Timeout :: timeout()) -> [sql_result()]
%% @doc
%% Executes the Sql script (consisting of semicolon-separated statements)
%% directly on the Db database. Returns the list of their results (same as
%% if sql_exec/3 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_timeout(atom(), iodata(), timeout()) -> [sql_result()].
sql_exec_script_timeout(Db, SQL, Timeout) ->
gen_server:call(Db, {sql_exec_script, SQL}, Timeout).
-spec prepare(atom(), iodata()) -> {ok, reference()} | sqlite_error(). -spec prepare(atom(), iodata()) -> {ok, reference()} | sqlite_error().
prepare(Db, SQL) -> prepare(Db, SQL) ->
gen_server:call(Db, {prepare, SQL}). gen_server:call(Db, {prepare, SQL}).
@@ -198,6 +268,34 @@ finalize(Db, Ref) ->
columns(Db, Ref) -> columns(Db, Ref) ->
gen_server:call(Db, {columns, Ref}). gen_server:call(Db, {columns, Ref}).
-spec prepare_timeout(atom(), iodata(), timeout()) -> {ok, reference()} | sqlite_error().
prepare_timeout(Db, SQL, Timeout) ->
gen_server:call(Db, {prepare, SQL}, Timeout).
-spec bind_timeout(atom(), reference(), sql_params(), timeout()) -> sql_non_query_result().
bind_timeout(Db, Ref, Params, Timeout) ->
gen_server:call(Db, {bind, Ref, Params}, Timeout).
-spec next_timeout(atom(), reference(), timeout()) -> tuple() | done | sqlite_error().
next_timeout(Db, Ref, Timeout) ->
gen_server:call(Db, {next, Ref}, Timeout).
-spec reset_timeout(atom(), reference(), timeout()) -> sql_non_query_result().
reset_timeout(Db, Ref, Timeout) ->
gen_server:call(Db, {reset, Ref}, Timeout).
-spec clear_bindings_timeout(atom(), reference(), timeout()) -> sql_non_query_result().
clear_bindings_timeout(Db, Ref, Timeout) ->
gen_server:call(Db, {clear_bindings, Ref}, Timeout).
-spec finalize_timeout(atom(), reference(), timeout()) -> sql_non_query_result().
finalize_timeout(Db, Ref, Timeout) ->
gen_server:call(Db, {finalize, Ref}, Timeout).
-spec columns_timeout(atom(), reference(), timeout()) -> sql_non_query_result().
columns_timeout(Db, Ref, Timeout) ->
gen_server:call(Db, {columns, Ref}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec create_table(Tbl :: atom(), TblInfo :: [{atom(), atom()}]) -> sql_non_query_result() %% @spec create_table(Tbl :: atom(), TblInfo :: [{atom(), atom()}]) -> sql_non_query_result()
%% @doc %% @doc
@@ -227,6 +325,21 @@ create_table(Tbl, Columns) ->
create_table(Db, Tbl, Columns) -> create_table(Db, Tbl, Columns) ->
gen_server:call(Db, {create_table, Tbl, Columns}). gen_server:call(Db, {create_table, Tbl, Columns}).
%%--------------------------------------------------------------------
%% @spec create_table_timeout(Db :: atom(), Tbl :: atom(), Columns, Timeout :: timeout()) -> sql_non_query_result()
%% Columns = [{atom(), atom()}]
%% @doc
%% Creates the Tbl table in Db using Columns as the table structure.
%% The table structure is a list of {column name, column type} pairs.
%% e.g. [{name, text}, {age, integer}]
%%
%% Returns the result of the create table call.
%% @end
%%--------------------------------------------------------------------
-spec create_table_timeout(atom(), atom(), [{atom(), atom()}], timeout()) -> sql_non_query_result().
create_table_timeout(Db, Tbl, Columns, Timeout) ->
gen_server:call(Db, {create_table, Tbl, Columns}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec create_table(Db :: atom(), Tbl :: atom(), TblInfo, Constraints) -> sql_non_query_result() %% @spec create_table(Db :: atom(), Tbl :: atom(), TblInfo, Constraints) -> sql_non_query_result()
%% Columns = [{atom(), atom()}] %% Columns = [{atom(), atom()}]
@@ -244,6 +357,23 @@ create_table(Db, Tbl, Columns) ->
create_table(Db, Tbl, Columns, Constraints) -> create_table(Db, Tbl, Columns, Constraints) ->
gen_server:call(Db, {create_table, Tbl, Columns, Constraints}). gen_server:call(Db, {create_table, Tbl, Columns, Constraints}).
%%--------------------------------------------------------------------
%% @spec create_table_timeout(Db :: atom(), Tbl :: atom(), TblInfo, Constraints, Timeout) -> sql_non_query_result()
%% Columns = [{atom(), atom()}]
%% Constraints = [term()]
%% @doc
%% Creates the Tbl table in Db using Columns as the table structure and
%% Constraints as table constraints.
%% The table structure is a list of {column name, column type} pairs.
%% e.g. [{name, text}, {age, integer}]
%%
%% Returns the result of the create table call.
%% @end
%%--------------------------------------------------------------------
-spec create_table_timeout(atom(), atom(), [{atom(), atom()}], [any()], timeout()) -> sql_non_query_result().
create_table_timeout(Db, Tbl, Columns, Constraints, Timeout) ->
gen_server:call(Db, {create_table, Tbl, Columns, Constraints}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec list_tables() -> [atom()] %% @spec list_tables() -> [atom()]
%% @doc %% @doc
@@ -264,6 +394,16 @@ list_tables() ->
list_tables(Db) -> list_tables(Db) ->
gen_server:call(Db, list_tables). gen_server:call(Db, list_tables).
%%--------------------------------------------------------------------
%% @spec list_tables_timeout(Db :: atom(), Timeout :: timeout()) -> [atom()]
%% @doc
%% Returns a list of tables for Db.
%% @end
%%--------------------------------------------------------------------
-spec list_tables_timeout(atom(), timeout()) -> [atom()].
list_tables_timeout(Db, Timeout) ->
gen_server:call(Db, list_tables, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec table_info(Tbl :: atom()) -> [any()] %% @spec table_info(Tbl :: atom()) -> [any()]
%% @doc %% @doc
@@ -284,6 +424,16 @@ table_info(Tbl) ->
table_info(Db, Tbl) -> table_info(Db, Tbl) ->
gen_server:call(Db, {table_info, Tbl}). gen_server:call(Db, {table_info, Tbl}).
%%--------------------------------------------------------------------
%% @spec table_info_timeout(Db :: atom(), Tbl :: atom()) -> [any()]
%% @doc
%% Returns table schema for Tbl in Db.
%% @end
%%--------------------------------------------------------------------
-spec table_info_timeout(atom(), atom(), timeout()) -> [any()].
table_info_timeout(Db, Tbl, Timeout) ->
gen_server:call(Db, {table_info, Tbl}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec write(Tbl :: atom(), Data) -> sql_non_query_result() %% @spec write(Tbl :: atom(), Data) -> sql_non_query_result()
%% Data = [{Column :: atom(), Value :: sql_value()}] %% Data = [{Column :: atom(), Value :: sql_value()}]
@@ -308,6 +458,18 @@ write(Tbl, Data) ->
write(Db, Tbl, Data) -> write(Db, Tbl, Data) ->
gen_server:call(Db, {write, Tbl, Data}). gen_server:call(Db, {write, Tbl, Data}).
%%--------------------------------------------------------------------
%% @spec write_timeout(Db :: atom(), Tbl :: atom(), Data) -> sql_non_query_result()
%% Data = [{Column :: atom(), Value :: sql_value()}]
%% @doc
%% Write Data into Tbl table in Db database. Value must be of the
%% same type as determined from table_info/3.
%% @end
%%--------------------------------------------------------------------
-spec write_timeout(atom(), atom(), [{atom(), sql_value()}], timeout()) -> sql_non_query_result().
write_timeout(Db, Tbl, Data, Timeout) ->
gen_server:call(Db, {write, Tbl, Data}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec write_many(Tbl :: atom(), Data) -> sql_non_query_result() %% @spec write_many(Tbl :: atom(), Data) -> sql_non_query_result()
%% Data = [[{Column :: atom(), Value :: sql_value()}]] %% Data = [[{Column :: atom(), Value :: sql_value()}]]
@@ -332,6 +494,18 @@ write_many(Tbl, Data) ->
write_many(Db, Tbl, Data) -> write_many(Db, Tbl, Data) ->
gen_server:call(Db, {write_many, Tbl, Data}). gen_server:call(Db, {write_many, Tbl, Data}).
%%--------------------------------------------------------------------
%% @spec write_many_timeout(Db :: atom(), Tbl :: atom(), Data) -> sql_non_query_result()
%% Data = [[{Column :: atom(), Value :: sql_value()}]]
%% @doc
%% Write all records in Data into table Tbl in database Db. Value
%% must be of the same type as determined from table_info/3.
%% @end
%%--------------------------------------------------------------------
-spec write_many_timeout(atom(), atom(), [[{atom(), sql_value()}]], timeout()) -> sql_non_query_result().
write_many_timeout(Db, Tbl, Data, Timeout) ->
gen_server:call(Db, {write_many, Tbl, Data}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec update(Tbl :: atom(), {Key :: atom(), Value}, Data) -> sql_non_query_result() %% @spec update(Tbl :: atom(), {Key :: atom(), Value}, Data) -> sql_non_query_result()
%% Value = any() %% Value = any()
@@ -358,6 +532,19 @@ update(Tbl, {Key, Value}, Data) ->
update(Db, Tbl, {Key, Value}, Data) -> update(Db, Tbl, {Key, Value}, Data) ->
gen_server:call(Db, {update, Tbl, Key, Value, Data}). gen_server:call(Db, {update, Tbl, Key, Value, Data}).
%%--------------------------------------------------------------------
%% @spec update_timeout(Db :: atom(), Tbl :: atom(), {Key :: atom(), Value}, Data) -> sql_non_query_result()
%% Value = sql_value()
%% Data = [{Column :: atom(), Value :: sql_value()}]
%% @doc
%% Updates rows into Tbl table in Db database such that the Value
%% matches the value in Key with Data.
%% @end
%%--------------------------------------------------------------------
-spec update_timeout(atom(), atom(), {atom(), sql_value()}, [{atom(), sql_value()}], timeout()) -> sql_non_query_result().
update_timeout(Db, Tbl, {Key, Value}, Data, Timeout) ->
gen_server:call(Db, {update, Tbl, Key, Value, Data}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec read_all(Db :: atom(), Table :: atom()) -> sql_result() %% @spec read_all(Db :: atom(), Table :: atom()) -> sql_result()
%% @doc %% @doc
@@ -368,6 +555,16 @@ update(Db, Tbl, {Key, Value}, Data) ->
read_all(Db, Tbl) -> read_all(Db, Tbl) ->
gen_server:call(Db, {read, Tbl}). gen_server:call(Db, {read, Tbl}).
%%--------------------------------------------------------------------
%% @spec read_all_timeout(Db :: atom(), Table :: atom()) -> sql_result()
%% @doc
%% Reads all rows from Table in Db.
%% @end
%%--------------------------------------------------------------------
-spec read_all_timeout(atom(), atom(), timeout()) -> sql_result().
read_all_timeout(Db, Tbl, Timeout) ->
gen_server:call(Db, {read, Tbl}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec read_all(Db :: atom(), Table :: atom(), Columns :: [atom()]) -> sql_result() %% @spec read_all(Db :: atom(), Table :: atom(), Columns :: [atom()]) -> sql_result()
%% @doc %% @doc
@@ -378,6 +575,16 @@ read_all(Db, Tbl) ->
read_all(Db, Tbl, Columns) -> read_all(Db, Tbl, Columns) ->
gen_server:call(Db, {read, Tbl, Columns}). gen_server:call(Db, {read, Tbl, Columns}).
%%--------------------------------------------------------------------
%% @spec read_all_timeout(Db :: atom(), Table :: atom(), Columns :: [atom()]) -> sql_result()
%% @doc
%% Reads Columns in all rows from Table in Db.
%% @end
%%--------------------------------------------------------------------
-spec read_all_timeout(atom(), atom(), [atom()], timeout()) -> sql_result().
read_all_timeout(Db, Tbl, Columns, Timeout) ->
gen_server:call(Db, {read, Tbl, Columns}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec read(Tbl :: atom(), Key) -> sql_result() %% @spec read(Tbl :: atom(), Key) -> sql_result()
%% Key = {Column :: atom(), Value :: sql_value()} %% Key = {Column :: atom(), Value :: sql_value()}
@@ -420,6 +627,35 @@ read(Db, Tbl, {Column, Value}) ->
read(Db, Tbl, {Key, Value}, Columns) -> read(Db, Tbl, {Key, Value}, Columns) ->
gen_server:call(Db, {read, Tbl, Key, Value, Columns}). gen_server:call(Db, {read, Tbl, Key, Value, Columns}).
%%--------------------------------------------------------------------
%% @spec read_timeout(Db :: atom(), Tbl :: atom(), Key) -> sql_result()
%% Key = {Column :: atom(), Value :: sql_value()}
%% @doc
%% Reads a row from Tbl table in Db database such that the Value
%% matches the value in Column. ColValue must have the same type
%% as determined from table_info/3.
%% @end
%%--------------------------------------------------------------------
-spec read_timeout(atom(), atom(), {atom(), sql_value()}, timeout()) -> sql_result().
read_timeout(Db, Tbl, {Column, Value}, Timeout) ->
gen_server:call(Db, {read, Tbl, Column, Value}, Timeout).
%%--------------------------------------------------------------------
%% @spec read_timeout(Db, Tbl, Key, Columns) -> [any()]
%% Db = atom()
%% Tbl = atom()
%% Key = {Column :: atom(), Value :: sql_value()}
%% Columns = [atom()]
%% @doc
%% Reads a row from Tbl table in Db database such that the Value
%% matches the value in Column. Value must have the same type as
%% determined from table_info/3.
%% @end
%%--------------------------------------------------------------------
-spec read_timeout(atom(), atom(), {atom(), sql_value()}, [atom()], timeout()) -> sql_result().
read_timeout(Db, Tbl, {Key, Value}, Columns, Timeout) ->
gen_server:call(Db, {read, Tbl, Key, Value, Columns}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec delete(Tbl :: atom(), Key) -> any() %% @spec delete(Tbl :: atom(), Key) -> any()
%% Key = {Column :: atom(), Value :: sql_value()} %% Key = {Column :: atom(), Value :: sql_value()}
@@ -433,6 +669,19 @@ read(Db, Tbl, {Key, Value}, Columns) ->
delete(Tbl, Key) -> delete(Tbl, Key) ->
delete(?MODULE, Tbl, Key). delete(?MODULE, Tbl, Key).
%%--------------------------------------------------------------------
%% @spec delete_timeout(Db :: atom(), Tbl :: atom(), Key) -> sql_non_query_result()
%% Key = {Column :: atom(), Value :: sql_value()}
%% @doc
%% Delete a row from Tbl table in Db database such that the Value
%% matches the value in Column.
%% Value must have the same type as determined from table_info/3.
%% @end
%%--------------------------------------------------------------------
-spec delete_timeout(atom(), atom(), {atom(), any()}, timeout()) -> sql_non_query_result().
delete_timeout(Db, Tbl, Key, Timeout) ->
gen_server:call(Db, {delete, Tbl, Key}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec delete(Db :: atom(), Tbl :: atom(), Key) -> sql_non_query_result() %% @spec delete(Db :: atom(), Tbl :: atom(), Key) -> sql_non_query_result()
%% Key = {Column :: atom(), Value :: sql_value()} %% Key = {Column :: atom(), Value :: sql_value()}
@@ -466,6 +715,16 @@ drop_table(Tbl) ->
drop_table(Db, Tbl) -> drop_table(Db, Tbl) ->
gen_server:call(Db, {drop_table, Tbl}). gen_server:call(Db, {drop_table, Tbl}).
%%--------------------------------------------------------------------
%% @spec drop_table_timeout(Db :: atom(), Tbl :: atom()) -> sql_non_query_result()
%% @doc
%% Drop the table Tbl from Db database.
%% @end
%%--------------------------------------------------------------------
-spec drop_table_timeout(atom(), atom(), timeout()) -> sql_non_query_result().
drop_table_timeout(Db, Tbl, Timeout) ->
gen_server:call(Db, {drop_table, Tbl}, Timeout).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec vacuum() -> sql_non_query_result() %% @spec vacuum() -> sql_non_query_result()
%% @doc %% @doc
@@ -486,6 +745,16 @@ vacuum() ->
vacuum(Db) -> vacuum(Db) ->
gen_server:call(Db, vacuum). gen_server:call(Db, vacuum).
%%--------------------------------------------------------------------
%% @spec vacuum_timeout(Db :: atom()) -> sql_non_query_result()
%% @doc
%% Vacuum the Db database.
%% @end
%%--------------------------------------------------------------------
-spec vacuum_timeout(atom(), timeout()) -> sql_non_query_result().
vacuum_timeout(Db, Timeout) ->
gen_server:call(Db, vacuum, Timeout).
%% %%-------------------------------------------------------------------- %% %%--------------------------------------------------------------------
%% %% @spec create_function(Db :: atom(), FunctionName :: atom(), Function :: function()) -> term() %% %% @spec create_function(Db :: atom(), FunctionName :: atom(), Function :: function()) -> term()
%% %% @doc %% %% @doc
@@ -497,39 +766,6 @@ vacuum(Db) ->
%% create_function(Db, FunctionName, Function) -> %% create_function(Db, FunctionName, Function) ->
%% gen_server:call(Db, {create_function, FunctionName, Function}). %% gen_server:call(Db, {create_function, FunctionName, Function}).
%%--------------------------------------------------------------------
%% @spec begin_transaction(Db :: atom()) -> sql_non_query_result()
%% @doc
%% Begins a transaction in Db.
%% @end
%%--------------------------------------------------------------------
-spec begin_transaction(atom()) -> sql_non_query_result().
begin_transaction(Db) ->
SQL = "BEGIN;",
sql_exec(Db, SQL).
%%--------------------------------------------------------------------
%% @spec commit_transaction(Db :: atom()) -> sql_non_query_result()
%% @doc
%% Commits the current transaction in Db.
%% @end
%%--------------------------------------------------------------------
-spec commit_transaction(atom()) -> sql_non_query_result().
commit_transaction(Db) ->
SQL = "COMMIT;",
sql_exec(Db, SQL).
%%--------------------------------------------------------------------
%% @spec rollback_transaction(Db :: atom()) -> sql_non_query_result()
%% @doc
%% Rolls back the current transaction in Db.
%% @end
%%--------------------------------------------------------------------
-spec rollback_transaction(atom()) -> sql_non_query_result().
rollback_transaction(Db) ->
SQL = "ROLLBACK;",
sql_exec(Db, SQL).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @spec value_to_sql_unsafe(Value :: sql_value()) -> iolist() %% @spec value_to_sql_unsafe(Value :: sql_value()) -> iolist()
%% @doc %% @doc
@@ -643,6 +879,9 @@ handle_call({sql_exec, SQL}, _From, State) ->
handle_call({sql_bind_and_exec, SQL, Params}, _From, State) -> handle_call({sql_bind_and_exec, SQL, Params}, _From, State) ->
Reply = do_sql_bind_and_exec(SQL, Params, State), Reply = do_sql_bind_and_exec(SQL, Params, State),
{reply, Reply, 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) -> handle_call({create_table, Tbl, Columns}, _From, State) ->
SQL = sqlite3_lib:create_table_sql(Tbl, Columns), SQL = sqlite3_lib:create_table_sql(Tbl, Columns),
do_handle_call_sql_exec(SQL, State); do_handle_call_sql_exec(SQL, State);
@@ -813,6 +1052,7 @@ get_priv_dir() ->
-define(PREPARED_CLEAR_BINDINGS, 9). -define(PREPARED_CLEAR_BINDINGS, 9).
-define(PREPARED_FINALIZE, 10). -define(PREPARED_FINALIZE, 10).
-define(PREPARED_COLUMNS, 11). -define(PREPARED_COLUMNS, 11).
-define(SQL_EXEC_SCRIPT, 12).
create_port_cmd(DbFile) -> create_port_cmd(DbFile) ->
atom_to_list(?DRIVER_NAME) ++ " " ++ DbFile. atom_to_list(?DRIVER_NAME) ++ " " ++ DbFile.
@@ -829,6 +1069,10 @@ do_sql_bind_and_exec(SQL, Params, #state{port = Port}) ->
?dbg("SQL: ~s; Parameters: ~p~n", [SQL, Params]), ?dbg("SQL: ~s; Parameters: ~p~n", [SQL, Params]),
exec(Port, {sql_bind_and_exec, 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}) -> exec(_Port, {create_function, _FunctionName, _Function}) ->
error_logger:error_report([{application, sqlite3}, "NOT IMPL YET"]); error_logger:error_report([{application, sqlite3}, "NOT IMPL YET"]);
%port_control(Port, ?SQL_CREATE_FUNCTION, list_to_binary(Cmd)), %port_control(Port, ?SQL_CREATE_FUNCTION, list_to_binary(Cmd)),
@@ -840,6 +1084,9 @@ exec(Port, {sql_bind_and_exec, SQL, Params}) ->
Bin = term_to_binary({iolist_to_binary(SQL), Params}), Bin = term_to_binary({iolist_to_binary(SQL), Params}),
port_control(Port, ?SQL_BIND_AND_EXEC_COMMAND, Bin), port_control(Port, ?SQL_BIND_AND_EXEC_COMMAND, Bin),
wait_result(Port); wait_result(Port);
exec(Port, {sql_exec_script, SQL}) ->
port_control(Port, ?SQL_EXEC_SCRIPT, SQL),
many_results_loop(Port);
exec(Port, {prepare, SQL}) -> exec(Port, {prepare, SQL}) ->
port_control(Port, ?PREPARE, SQL), port_control(Port, ?PREPARE, SQL),
wait_result(Port); wait_result(Port);
@@ -876,6 +1123,17 @@ wait_result(Port) ->
{error, -1, Reason} {error, -1, Reason}
end. 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) -> parse_table_info(Info) ->
[_, Tail] = string:tokens(Info, "()"), [_, Tail] = string:tokens(Info, "()"),
Cols = string:tokens(Tail, ","), Cols = string:tokens(Tail, ","),

View File

@@ -17,6 +17,7 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-define(FuncTest(Name), {??Name, fun Name/0}). -define(FuncTest(Name), {??Name, fun Name/0}).
-define(WARN_ERROR_MESSAGE, io:format(user, "Error message should be shown...~n", [])).
drop_all_tables(Db) -> drop_all_tables(Db) ->
Tables = sqlite3:list_tables(Db), Tables = sqlite3:list_tables(Db),
@@ -61,6 +62,10 @@ basic_functionality() ->
AbbyOnly = [{1, <<"abby">>, 20, 2000}], AbbyOnly = [{1, <<"abby">>, 20, 2000}],
TableInfo = [{id, integer, [primary_key]}, {name, text, [not_null, unique]}, {age, integer}, {wage, integer}], TableInfo = [{id, integer, [primary_key]}, {name, text, [not_null, unique]}, {age, integer}, {wage, integer}],
drop_all_tables(ct), drop_all_tables(ct),
?WARN_ERROR_MESSAGE,
?assertEqual(
{error, 21, "empty statement"},
sqlite3:sql_exec(ct, "-- Comment")),
?assertEqual( ?assertEqual(
[], [],
sqlite3:list_tables(ct)), sqlite3:list_tables(ct)),
@@ -77,7 +82,7 @@ basic_functionality() ->
?assertEqual( ?assertEqual(
{rowid, 2}, {rowid, 2},
sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), 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( ?assertEqual(
{error, 19, "constraint failed"}, {error, 19, "constraint failed"},
sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])),
@@ -229,6 +234,41 @@ prepared_test() ->
?assertEqual(ok, sqlite3:finalize(prepared, Ref2)), ?assertEqual(ok, sqlite3:finalize(prepared, Ref2)),
sqlite3:close(prepared). 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"),
?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 % create, read, update, delete
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions