Merge branch 'master' of github.com:alexeyr/erlang-sqlite3
This commit is contained in:
@@ -365,20 +365,24 @@ static inline int decode_and_bind_param(
|
||||
case ERL_BINARY_EXT:
|
||||
char_buf_val = driver_alloc(*p_size * sizeof(char));
|
||||
ei_decode_binary(buffer, p_index, char_buf_val, &bin_size);
|
||||
// assert(bin_size == *p_size)
|
||||
result = sqlite3_bind_text(statement, param_index, char_buf_val, *p_size, &driver_free_fun);
|
||||
break;
|
||||
case ERL_SMALL_TUPLE_EXT:
|
||||
// assume this is {blob, Blob}
|
||||
ei_get_type(buffer, p_index, p_type, p_size);
|
||||
ei_decode_tuple_header(buffer, p_index, p_size);
|
||||
assert (*p_size == 2);
|
||||
if (*p_size != 2) {
|
||||
output_error(drv, SQLITE_MISUSE, "bad parameter type");
|
||||
return 1;
|
||||
}
|
||||
ei_skip_term(buffer, p_index); // skipped the atom 'blob'
|
||||
ei_get_type(buffer, p_index, p_type, p_size);
|
||||
assert (*p_type == ERL_BINARY_EXT);
|
||||
if (*p_type != ERL_BINARY_EXT) {
|
||||
output_error(drv, SQLITE_MISUSE, "bad parameter type");
|
||||
return 1;
|
||||
}
|
||||
char_buf_val = driver_alloc(*p_size * sizeof(char));
|
||||
ei_decode_binary(buffer, p_index, char_buf_val, &bin_size);
|
||||
// assert(bin_size == *p_size)
|
||||
result = sqlite3_bind_blob(statement, param_index, char_buf_val, *p_size, &driver_free_fun);
|
||||
break;
|
||||
default:
|
||||
@@ -399,76 +403,95 @@ static int bind_parameters(
|
||||
int i, cur_list_size = -1, param_index = 1, param_indices_are_explicit = 0, result = 0;
|
||||
long param_index_long;
|
||||
char param_name[MAXATOMLEN + 1]; // parameter names shouldn't be longer than 256!
|
||||
while (*p_index < buffer_size) {
|
||||
ei_decode_list_header(buffer, p_index, &cur_list_size);
|
||||
for (i = 0; i < cur_list_size; i++) {
|
||||
char *acc_string;
|
||||
result = ei_decode_list_header(buffer, p_index, &cur_list_size);
|
||||
if (result) {
|
||||
// probably all parameters are integers between 0 and 255
|
||||
// and the list was encoded as string (see ei documentation)
|
||||
ei_get_type(buffer, p_index, p_type, p_size);
|
||||
if (*p_type != ERL_STRING_EXT) {
|
||||
return output_error(drv, SQLITE_ERROR,
|
||||
"error while binding parameters");
|
||||
}
|
||||
acc_string = driver_alloc(sizeof(char*) * (*p_size + 1));
|
||||
ei_decode_string(buffer, p_index, acc_string);
|
||||
for (param_index = 1; param_index <= *p_size; param_index++) {
|
||||
sqlite3_bind_int(statement, param_index, (int) acc_string[param_index - 1]);
|
||||
}
|
||||
driver_free(acc_string);
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < cur_list_size; i++) {
|
||||
if (*p_index >= buffer_size) {
|
||||
return output_error(drv, SQLITE_ERROR,
|
||||
"error while binding parameters");
|
||||
}
|
||||
ei_get_type(buffer, p_index, p_type, p_size);
|
||||
if (*p_type == ERL_SMALL_TUPLE_EXT) {
|
||||
int old_index = *p_index;
|
||||
// param with name or explicit index
|
||||
param_indices_are_explicit = 1;
|
||||
if (*p_size != 2) {
|
||||
return output_error(drv, SQLITE_MISUSE,
|
||||
"tuple should contain index or name, and value");
|
||||
}
|
||||
ei_decode_tuple_header(buffer, p_index, p_size);
|
||||
ei_get_type(buffer, p_index, p_type, p_size);
|
||||
if (*p_type == ERL_SMALL_TUPLE_EXT) {
|
||||
int old_index = *p_index;
|
||||
// param with name or explicit index
|
||||
param_indices_are_explicit = 1;
|
||||
if (*p_size != 2) {
|
||||
return output_error(drv, SQLITE_MISUSE,
|
||||
"tuple should contain index or name, and value");
|
||||
// first element of tuple is int (index), atom, or string (name)
|
||||
switch (*p_type) {
|
||||
case ERL_SMALL_INTEGER_EXT:
|
||||
case ERL_INTEGER_EXT:
|
||||
ei_decode_long(buffer, p_index, ¶m_index_long);
|
||||
param_index = param_index_long;
|
||||
break;
|
||||
case ERL_ATOM_EXT:
|
||||
ei_decode_atom(buffer, p_index, param_name);
|
||||
// insert zero terminator
|
||||
param_name[*p_size] = '\0';
|
||||
if (strncmp(param_name, "blob", 5) == 0) {
|
||||
// this isn't really a parameter name!
|
||||
*p_index = old_index;
|
||||
param_indices_are_explicit = 0;
|
||||
goto IMPLICIT_INDEX; // yuck
|
||||
}
|
||||
ei_decode_tuple_header(buffer, p_index, p_size);
|
||||
ei_get_type(buffer, p_index, p_type, p_size);
|
||||
// first element of tuple is int (index), atom, or string (name)
|
||||
switch (*p_type) {
|
||||
case ERL_SMALL_INTEGER_EXT:
|
||||
case ERL_INTEGER_EXT:
|
||||
ei_decode_long(buffer, p_index, ¶m_index_long);
|
||||
param_index = param_index_long;
|
||||
break;
|
||||
case ERL_ATOM_EXT:
|
||||
ei_decode_atom(buffer, p_index, param_name);
|
||||
// insert zero terminator
|
||||
param_name[*p_size] = '\0';
|
||||
if (strncmp(param_name, "blob", 5) == 0) {
|
||||
// this isn't really a parameter name!
|
||||
*p_index = old_index;
|
||||
param_indices_are_explicit = 0;
|
||||
goto IMPLICIT_INDEX; // yuck
|
||||
}
|
||||
else {
|
||||
param_index = sqlite3_bind_parameter_index(statement, param_name);
|
||||
}
|
||||
break;
|
||||
case ERL_STRING_EXT:
|
||||
if (*p_size >= MAXATOMLEN) {
|
||||
return output_error(drv, SQLITE_TOOBIG, "parameter name too long");
|
||||
}
|
||||
ei_decode_string(buffer, p_index, param_name);
|
||||
// insert zero terminator
|
||||
param_name[*p_size] = '\0';
|
||||
else {
|
||||
param_index = sqlite3_bind_parameter_index(statement, param_name);
|
||||
break;
|
||||
default:
|
||||
return output_error(
|
||||
drv, SQLITE_MISMATCH,
|
||||
"parameter index must be given as integer, atom, or string");
|
||||
}
|
||||
result = decode_and_bind_param(
|
||||
drv, buffer, p_index, statement, param_index, p_type, p_size);
|
||||
if (result != SQLITE_OK) {
|
||||
return result; // error has already been output
|
||||
break;
|
||||
case ERL_STRING_EXT:
|
||||
if (*p_size >= MAXATOMLEN) {
|
||||
return output_error(drv, SQLITE_TOOBIG, "parameter name too long");
|
||||
}
|
||||
ei_decode_string(buffer, p_index, param_name);
|
||||
// insert zero terminator
|
||||
param_name[*p_size] = '\0';
|
||||
param_index = sqlite3_bind_parameter_index(statement, param_name);
|
||||
break;
|
||||
default:
|
||||
return output_error(
|
||||
drv, SQLITE_MISMATCH,
|
||||
"parameter index must be given as integer, atom, or string");
|
||||
}
|
||||
result = decode_and_bind_param(
|
||||
drv, buffer, p_index, statement, param_index, p_type, p_size);
|
||||
if (result != SQLITE_OK) {
|
||||
return result; // error has already been output
|
||||
}
|
||||
}
|
||||
else {
|
||||
IMPLICIT_INDEX:
|
||||
if (param_indices_are_explicit) {
|
||||
return output_error(
|
||||
drv, SQLITE_MISUSE,
|
||||
"parameters without indices shouldn't follow indexed or named parameters");
|
||||
}
|
||||
else {
|
||||
IMPLICIT_INDEX:
|
||||
if (param_indices_are_explicit) {
|
||||
return output_error(
|
||||
drv, SQLITE_MISUSE,
|
||||
"parameters without indices shouldn't follow indexed or named parameters");
|
||||
}
|
||||
|
||||
result = decode_and_bind_param(
|
||||
drv, buffer, p_index, statement, param_index, p_type, p_size);
|
||||
if (result != SQLITE_OK) {
|
||||
return result; // error has already been output
|
||||
}
|
||||
++param_index;
|
||||
result = decode_and_bind_param(
|
||||
drv, buffer, p_index, statement, param_index, p_type, p_size);
|
||||
if (result != SQLITE_OK) {
|
||||
return result; // error has already been output
|
||||
}
|
||||
++param_index;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -516,7 +539,7 @@ static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buffer, int buffer_size)
|
||||
|
||||
ei_decode_version(buffer, &index, NULL);
|
||||
result = ei_decode_tuple_header(buffer, &index, &size);
|
||||
if (size != 2) {
|
||||
if (result || (size != 2)) {
|
||||
return output_error(drv, SQLITE_MISUSE,
|
||||
"Expected a tuple of SQL command and params");
|
||||
}
|
||||
@@ -1019,7 +1042,7 @@ POPULATE_COMMAND:
|
||||
async_command->binaries = binaries;
|
||||
async_command->row_count = 1;
|
||||
#ifdef DEBUG
|
||||
fprintf(drv->log, "Total term count: %p %d, columns count: %dx%d\n", statement, term_count, column_count);
|
||||
fprintf(drv->log, "Total term count: %p %d, columns count: %d\n", statement, term_count, column_count);
|
||||
fflush(drv->log);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -20,9 +20,19 @@
|
||||
-endif.
|
||||
|
||||
-define(NULL_ATOM, null).
|
||||
-type(sql_value() :: number() | ?NULL_ATOM | iodata() | {blob, binary()}).
|
||||
-type sql_value() :: number() | ?NULL_ATOM | iodata() | {blob, binary()}.
|
||||
-type sql_type() :: integer | text | double | real | blob | string().
|
||||
|
||||
-type(sqlite_error() :: {error, integer(), string()}).
|
||||
-type(sql_params() :: [sql_value() | {atom() | string() | integer(), sql_value()}]).
|
||||
-type(sql_non_query_result() :: ok | sqlite_error() | {rowid, integer()}).
|
||||
-type(sql_result() :: sql_non_query_result() | [{columns, [string()]} | {rows, [tuple()]}]).
|
||||
-type pk_constraint() :: autoincrement | desc | asc.
|
||||
-type pk_constraints() :: pk_constraint() | [pk_constraint()].
|
||||
-type column_constraint() :: non_null | primary_key | {primary_key, pk_constraints()}
|
||||
| unique | {default, sql_value()}.
|
||||
-type column_constraints() :: column_constraint() | [column_constraint()].
|
||||
-type table_constraint() :: {primary_key, [atom()]} | {unique, [atom()]}.
|
||||
-type table_constraints() :: table_constraint() | [table_constraint()].
|
||||
-type table_info() :: [{atom(), sql_type()} | {atom(), sql_type(), column_constraints()}].
|
||||
|
||||
-type sqlite_error() :: {error, integer(), string()}.
|
||||
-type sql_params() :: [sql_value() | {atom() | string() | integer(), sql_value()}].
|
||||
-type sql_non_query_result() :: ok | sqlite_error() | {rowid, integer()}.
|
||||
-type sql_result() :: sql_non_query_result() | [{columns, [string()]} | {rows, [tuple()]}].
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{application, sqlite3,
|
||||
[
|
||||
{description, "SQLite3 Interface"},
|
||||
{vsn, "1.0"},
|
||||
{vsn, "1.0.1"},
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [kernel, stdlib]},
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(sqlite3).
|
||||
-include("sqlite3.hrl").
|
||||
-export_types([sql_value/0, sql_type/0, table_info/0, sqlite_error/0,
|
||||
sql_params/0, sql_non_query_result/0, sql_result/0]).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
@@ -189,7 +191,7 @@ sql_exec(Db, SQL, Params) ->
|
||||
gen_server:call(Db, {sql_bind_and_exec, SQL, Params}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec sql_exec_timeout(Db :: atom(), Sql :: iodata()) -> sql_result()
|
||||
%% @spec sql_exec_timeout(Db :: atom(), Sql :: iodata(), Timeout :: timeout()) -> sql_result()
|
||||
%% @doc
|
||||
%% Executes the Sql statement directly on the Db database. Returns the
|
||||
%% result of the Sql call.
|
||||
@@ -200,7 +202,7 @@ 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()
|
||||
%% @spec sql_exec_timeout(Db :: atom(), Sql :: iodata(), Params, Timeout :: timeout()) -> sql_result()
|
||||
%% Params = [sql_value() | {atom() | string() | integer(), sql_value()}]
|
||||
%% @doc
|
||||
%% Executes the Sql statement with parameters Params directly on the Db
|
||||
@@ -297,7 +299,7 @@ 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 :: table_info()) -> sql_non_query_result()
|
||||
%% @doc
|
||||
%% Creates the Tbl table using TblInfo as the table structure. The
|
||||
%% table structure is a list of {column name, column type} pairs.
|
||||
@@ -306,13 +308,13 @@ columns_timeout(Db, Ref, Timeout) ->
|
||||
%% Returns the result of the create table call.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table(atom(), [{atom(), atom()}]) -> sql_non_query_result().
|
||||
-spec create_table(atom(), table_info()) -> sql_non_query_result().
|
||||
create_table(Tbl, Columns) ->
|
||||
create_table(?MODULE, Tbl, Columns).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec create_table(Db :: atom(), Tbl :: atom(), Columns) -> sql_non_query_result()
|
||||
%% Columns = [{atom(), atom()}]
|
||||
%% Columns = table_info()
|
||||
%% @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.
|
||||
@@ -321,13 +323,13 @@ create_table(Tbl, Columns) ->
|
||||
%% Returns the result of the create table call.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table(atom(), atom(), [{atom(), atom()}]) -> sql_non_query_result().
|
||||
-spec create_table(atom(), atom(), table_info()) -> sql_non_query_result().
|
||||
create_table(Db, 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()}]
|
||||
%% Columns = table_info()
|
||||
%% @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.
|
||||
@@ -336,13 +338,13 @@ create_table(Db, Tbl, Columns) ->
|
||||
%% Returns the result of the create table call.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table_timeout(atom(), atom(), [{atom(), atom()}], timeout()) -> sql_non_query_result().
|
||||
-spec create_table_timeout(atom(), atom(), table_info(), 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()
|
||||
%% Columns = [{atom(), atom()}]
|
||||
%% Columns = table_info()
|
||||
%% Constraints = [term()]
|
||||
%% @doc
|
||||
%% Creates the Tbl table in Db using Columns as the table structure and
|
||||
@@ -353,13 +355,14 @@ create_table_timeout(Db, Tbl, Columns, Timeout) ->
|
||||
%% Returns the result of the create table call.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table(atom(), atom(), [{atom(), atom()}], [any()]) -> sql_non_query_result().
|
||||
-spec create_table(atom(), atom(), table_info(), table_constraints()) ->
|
||||
sql_non_query_result().
|
||||
create_table(Db, 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()}]
|
||||
%% Columns = table_info()
|
||||
%% Constraints = [term()]
|
||||
%% @doc
|
||||
%% Creates the Tbl table in Db using Columns as the table structure and
|
||||
@@ -370,7 +373,8 @@ create_table(Db, Tbl, Columns, Constraints) ->
|
||||
%% Returns the result of the create table call.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table_timeout(atom(), atom(), [{atom(), atom()}], [any()], timeout()) -> sql_non_query_result().
|
||||
-spec create_table_timeout(atom(), atom(), table_info(), table_constraints(), timeout()) ->
|
||||
sql_non_query_result().
|
||||
create_table_timeout(Db, Tbl, Columns, Constraints, Timeout) ->
|
||||
gen_server:call(Db, {create_table, Tbl, Columns, Constraints}, Timeout).
|
||||
|
||||
@@ -405,32 +409,32 @@ list_tables_timeout(Db, Timeout) ->
|
||||
gen_server:call(Db, list_tables, Timeout).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec table_info(Tbl :: atom()) -> [any()]
|
||||
%% @spec table_info(Tbl :: atom()) -> table_info()
|
||||
%% @doc
|
||||
%% Returns table schema for Tbl.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec table_info(atom()) -> [any()].
|
||||
-spec table_info(atom()) -> table_info().
|
||||
table_info(Tbl) ->
|
||||
table_info(?MODULE, Tbl).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec table_info(Db :: atom(), Tbl :: atom()) -> [any()]
|
||||
%% @spec table_info(Db :: atom(), Tbl :: atom()) -> table_info()
|
||||
%% @doc
|
||||
%% Returns table schema for Tbl in Db.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec table_info(atom(), atom()) -> [any()].
|
||||
-spec table_info(atom(), atom()) -> table_info().
|
||||
table_info(Db, Tbl) ->
|
||||
gen_server:call(Db, {table_info, Tbl}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec table_info_timeout(Db :: atom(), Tbl :: atom()) -> [any()]
|
||||
%% @spec table_info_timeout(Db :: atom(), Tbl :: atom(), Timeout :: timeout()) -> table_info()
|
||||
%% @doc
|
||||
%% Returns table schema for Tbl in Db.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec table_info_timeout(atom(), atom(), timeout()) -> [any()].
|
||||
-spec table_info_timeout(atom(), atom(), timeout()) -> table_info().
|
||||
table_info_timeout(Db, Tbl, Timeout) ->
|
||||
gen_server:call(Db, {table_info, Tbl}, Timeout).
|
||||
|
||||
@@ -459,7 +463,7 @@ write(Db, Tbl, Data) ->
|
||||
gen_server:call(Db, {write, Tbl, Data}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec write_timeout(Db :: atom(), Tbl :: atom(), Data) -> sql_non_query_result()
|
||||
%% @spec write_timeout(Db :: atom(), Tbl :: atom(), Data, Timeout :: timeout()) -> sql_non_query_result()
|
||||
%% Data = [{Column :: atom(), Value :: sql_value()}]
|
||||
%% @doc
|
||||
%% Write Data into Tbl table in Db database. Value must be of the
|
||||
@@ -495,7 +499,7 @@ write_many(Db, Tbl, Data) ->
|
||||
gen_server:call(Db, {write_many, Tbl, Data}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec write_many_timeout(Db :: atom(), Tbl :: atom(), Data) -> sql_non_query_result()
|
||||
%% @spec write_many_timeout(Db :: atom(), Tbl :: atom(), Data, Timeout :: timeout()) -> sql_non_query_result()
|
||||
%% Data = [[{Column :: atom(), Value :: sql_value()}]]
|
||||
%% @doc
|
||||
%% Write all records in Data into table Tbl in database Db. Value
|
||||
@@ -528,12 +532,13 @@ update(Tbl, {Key, Value}, Data) ->
|
||||
%% matches the value in Key with Data.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec update(atom(), atom(), {atom(), sql_value()}, [{atom(), sql_value()}]) -> sql_non_query_result().
|
||||
-spec update(atom(), atom(), {atom(), sql_value()}, [{atom(), sql_value()}]) ->
|
||||
sql_non_query_result().
|
||||
update(Db, 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()
|
||||
%% @spec update_timeout(Db :: atom(), Tbl :: atom(), {Key :: atom(), Value}, Data, Timeout :: timeout()) -> sql_non_query_result()
|
||||
%% Value = sql_value()
|
||||
%% Data = [{Column :: atom(), Value :: sql_value()}]
|
||||
%% @doc
|
||||
@@ -541,7 +546,8 @@ update(Db, Tbl, {Key, Value}, Data) ->
|
||||
%% matches the value in Key with Data.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec update_timeout(atom(), atom(), {atom(), sql_value()}, [{atom(), sql_value()}], timeout()) -> sql_non_query_result().
|
||||
-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).
|
||||
|
||||
@@ -556,7 +562,7 @@ read_all(Db, Tbl) ->
|
||||
gen_server:call(Db, {read, Tbl}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec read_all_timeout(Db :: atom(), Table :: atom()) -> sql_result()
|
||||
%% @spec read_all_timeout(Db :: atom(), Table :: atom(), Timeout :: timeout()) -> sql_result()
|
||||
%% @doc
|
||||
%% Reads all rows from Table in Db.
|
||||
%% @end
|
||||
@@ -576,7 +582,7 @@ read_all(Db, Tbl, Columns) ->
|
||||
gen_server:call(Db, {read, Tbl, Columns}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec read_all_timeout(Db :: atom(), Table :: atom(), Columns :: [atom()]) -> sql_result()
|
||||
%% @spec read_all_timeout(Db :: atom(), Table :: atom(), Columns :: [atom()], Timeout :: timeout()) -> sql_result()
|
||||
%% @doc
|
||||
%% Reads Columns in all rows from Table in Db.
|
||||
%% @end
|
||||
@@ -628,7 +634,7 @@ read(Db, Tbl, {Key, Value}, Columns) ->
|
||||
gen_server:call(Db, {read, Tbl, Key, Value, Columns}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec read_timeout(Db :: atom(), Tbl :: atom(), Key) -> sql_result()
|
||||
%% @spec read_timeout(Db :: atom(), Tbl :: atom(), Key, Timeout :: timeout()) -> sql_result()
|
||||
%% Key = {Column :: atom(), Value :: sql_value()}
|
||||
%% @doc
|
||||
%% Reads a row from Tbl table in Db database such that the Value
|
||||
@@ -641,7 +647,7 @@ read_timeout(Db, Tbl, {Column, Value}, Timeout) ->
|
||||
gen_server:call(Db, {read, Tbl, Column, Value}, Timeout).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec read_timeout(Db, Tbl, Key, Columns) -> [any()]
|
||||
%% @spec read_timeout(Db, Tbl, Key, Columns, Timeout :: timeout()) -> [any()]
|
||||
%% Db = atom()
|
||||
%% Tbl = atom()
|
||||
%% Key = {Column :: atom(), Value :: sql_value()}
|
||||
@@ -670,7 +676,7 @@ delete(Tbl, Key) ->
|
||||
delete(?MODULE, Tbl, Key).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec delete_timeout(Db :: atom(), Tbl :: atom(), Key) -> sql_non_query_result()
|
||||
%% @spec delete_timeout(Db :: atom(), Tbl :: atom(), Key, Timeout :: timeout()) -> sql_non_query_result()
|
||||
%% Key = {Column :: atom(), Value :: sql_value()}
|
||||
%% @doc
|
||||
%% Delete a row from Tbl table in Db database such that the Value
|
||||
@@ -716,7 +722,7 @@ drop_table(Db, Tbl) ->
|
||||
gen_server:call(Db, {drop_table, Tbl}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec drop_table_timeout(Db :: atom(), Tbl :: atom()) -> sql_non_query_result()
|
||||
%% @spec drop_table_timeout(Db :: atom(), Tbl :: atom(), Timeout :: timeout()) -> sql_non_query_result()
|
||||
%% @doc
|
||||
%% Drop the table Tbl from Db database.
|
||||
%% @end
|
||||
@@ -746,7 +752,7 @@ vacuum(Db) ->
|
||||
gen_server:call(Db, vacuum).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec vacuum_timeout(Db :: atom()) -> sql_non_query_result()
|
||||
%% @spec vacuum_timeout(Db :: atom(), Timeout :: timeout()) -> sql_non_query_result()
|
||||
%% @doc
|
||||
%% Vacuum the Db database.
|
||||
%% @end
|
||||
@@ -1181,15 +1187,40 @@ build_primary_key_constraint(Tail, Acc) ->
|
||||
%% {no_on_conflict, NoOnConflictClause}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @type sql_value() = number() | 'null' | iodata().
|
||||
%% @type sql_value() = null | number() | iodata() | {blob, binary()}.
|
||||
%%
|
||||
%% Values accepted in SQL statements include numbers, atom 'null',
|
||||
%% and io:iolist().
|
||||
%% Values accepted in SQL statements are atom 'null', numbers,
|
||||
%% strings (represented as iodata()) and blobs.
|
||||
%% @end
|
||||
%% @type sql_type() = integer | text | double | blob | atom() | string().
|
||||
%%
|
||||
%% Types of SQLite columns are represented by atoms 'integer', 'text', 'double',
|
||||
%% 'blob'. Other atoms and strings may also be used (e.g. "VARCHAR(20)", 'smallint', etc.)
|
||||
%% See [http://www.sqlite.org/datatype3.html].
|
||||
%% @end
|
||||
%% @type pk_constraint() = autoincrement | desc | asc.
|
||||
%% See {@link pk_constraints()}.
|
||||
%% @type pk_constraints() = pk_constraint() | [pk_constraint()].
|
||||
%% See {@link column_constraint()}.
|
||||
%% @type column_constraint() = non_null | primary_key | {primary_key, pk_constraints()}
|
||||
%% | unique | {default, sql_value()}.
|
||||
%% See {@link column_constraints()}.
|
||||
%% @type column_constraints() = column_constraint() | [column_constraint()].
|
||||
%% See {@link table_info()}.
|
||||
%% @type table_info() = [{atom(), sql_type()} | {atom(), sql_type(), column_constraints()}].
|
||||
%%
|
||||
%% Describes the columns of an SQLite table: each tuple contains name, type and constraints (if any)
|
||||
%% of one column.
|
||||
%% @end
|
||||
%% @type table_constraint() = {primary_key, [atom()]} | {unique, [atom()]}.
|
||||
%% @type table_constraints() = table_constraint() | [table_constraint()].
|
||||
%%
|
||||
%% Currently supported constraints for {@link table_info()} and {@link sqlite3:create_table/4}.
|
||||
%% @end
|
||||
%% @type sqlite_error() = {'error', integer(), string()}.
|
||||
%%
|
||||
%% Errors are reported by their SQLite result code
|
||||
%% (http://www.sqlite.org/c3ref/c_busy_recovery.html) and a string containing
|
||||
%% ([http://www.sqlite.org/c3ref/c_busy_recovery.html]) and a string containing
|
||||
%% English-language text that describes the error.
|
||||
%% @end
|
||||
%% @type sql_non_query_result() = ok | sqlite_error() | {rowid, integer()}.
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
%% @doc Maps sqlite3 column type.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec col_type_to_string(atom() | string()) -> string().
|
||||
-spec col_type_to_string(sql_type()) -> string().
|
||||
col_type_to_string(integer) ->
|
||||
"INTEGER";
|
||||
col_type_to_string(text) ->
|
||||
@@ -36,6 +36,8 @@ col_type_to_string(real) ->
|
||||
"REAL";
|
||||
col_type_to_string(blob) ->
|
||||
"BLOB";
|
||||
col_type_to_string(Atom) when is_atom(Atom) ->
|
||||
string:to_upper(atom_to_list(Atom));
|
||||
col_type_to_string(String) when is_list(String) ->
|
||||
String.
|
||||
|
||||
@@ -52,7 +54,10 @@ col_type_to_atom("TEXT") ->
|
||||
col_type_to_atom("REAL") ->
|
||||
double;
|
||||
col_type_to_atom("BLOB") ->
|
||||
blob.
|
||||
blob;
|
||||
col_type_to_atom(String) ->
|
||||
list_to_atom(string:to_lower(String)).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec value_to_sql_unsafe(Value :: sql_value()) -> iolist()
|
||||
@@ -196,7 +201,7 @@ read_cols_sql(Columns) ->
|
||||
%% @doc Generates a table create stmt in SQL.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table_sql(atom(), [{atom(), atom()} | {atom(), atom(), [any()]}]) -> iolist().
|
||||
-spec create_table_sql(atom(), table_info()) -> iolist().
|
||||
create_table_sql(Tbl, Columns) ->
|
||||
["CREATE TABLE ", atom_to_list(Tbl), " (",
|
||||
map_intersperse(fun column_sql_for_create_table/1, Columns, ", "), ");"].
|
||||
@@ -212,11 +217,11 @@ create_table_sql(Tbl, Columns) ->
|
||||
%% @doc Generates a table create stmt in SQL.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create_table_sql(atom(), [{atom(), atom()} | {atom(), atom(), [any()]}], [any()]) -> iolist().
|
||||
-spec create_table_sql(atom(), table_info(), table_constraints()) -> iolist().
|
||||
create_table_sql(Tbl, Columns, TblConstraints) ->
|
||||
["CREATE TABLE ", atom_to_list(Tbl), " (",
|
||||
map_intersperse(fun column_sql_for_create_table/1, Columns, ", "), ", ",
|
||||
map_intersperse(fun table_constraint_sql/1, TblConstraints, ", "),
|
||||
table_constraint_sql(TblConstraints),
|
||||
");"].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -334,7 +339,7 @@ drop_table_sql(Tbl) ->
|
||||
|
||||
%% @doc Works like string:join for iolists
|
||||
|
||||
-spec map_intersperse(fun((X) -> iolist()), [X], [iolist() | integer()]) -> iolist().
|
||||
-spec map_intersperse(fun((X) -> iolist()), [X], iolist()) -> iolist().
|
||||
map_intersperse(_Fun, [], _Sep) -> [];
|
||||
map_intersperse(Fun, [Elem], _Sep) -> [Fun(Elem)];
|
||||
map_intersperse(Fun, [Head | Tail], Sep) -> [Fun(Head), Sep | map_intersperse(Fun, Tail, Sep)].
|
||||
@@ -373,20 +378,29 @@ hex_str_to_bin([X, Y | Tail], Acc) ->
|
||||
column_sql_for_create_table({Name, Type}) ->
|
||||
[atom_to_list(Name), " ", col_type_to_string(Type)];
|
||||
column_sql_for_create_table({Name, Type, Constraints}) ->
|
||||
[atom_to_list(Name), " ", col_type_to_string(Type),
|
||||
" " | map_intersperse(fun constraint_sql/1, Constraints, " ")].
|
||||
[atom_to_list(Name), " ", col_type_to_string(Type), " ", constraint_sql(Constraints)].
|
||||
|
||||
-spec constraint_sql(any()) -> iolist().
|
||||
-spec pk_constraint_sql(pk_constraints()) -> iolist().
|
||||
pk_constraint_sql(Constraint) ->
|
||||
case Constraint of
|
||||
desc -> "DESC";
|
||||
asc -> "ASC";
|
||||
autoincrement -> "AUTOINCREMENT";
|
||||
_ when is_list(Constraint) -> map_intersperse(fun pk_constraint_sql/1, Constraint, " ")
|
||||
end.
|
||||
|
||||
-spec constraint_sql(column_constraints()) -> iolist().
|
||||
constraint_sql(Constraint) ->
|
||||
case Constraint of
|
||||
primary_key -> "PRIMARY KEY";
|
||||
{primary_key, desc} -> "PRIMARY KEY DESC";
|
||||
{primary_key, C} -> ["PRIMARY KEY ", pk_constraint_sql(C)];
|
||||
unique -> "UNIQUE";
|
||||
not_null -> "NOT NULL";
|
||||
{default, DefaultValue} -> ["DEFAULT ", value_to_sql(DefaultValue)]
|
||||
{default, DefaultValue} -> ["DEFAULT ", value_to_sql(DefaultValue)];
|
||||
_ when is_list(Constraint) -> map_intersperse(fun constraint_sql/1, Constraint, " ")
|
||||
end.
|
||||
|
||||
-spec table_constraint_sql(any()) -> iolist().
|
||||
-spec table_constraint_sql(table_constraints()) -> iolist().
|
||||
table_constraint_sql(TableConstraint) ->
|
||||
case TableConstraint of
|
||||
{primary_key, Columns} ->
|
||||
@@ -394,8 +408,10 @@ table_constraint_sql(TableConstraint) ->
|
||||
map_intersperse(fun indexed_column_sql/1, Columns, ", "), ")"];
|
||||
{unique, Columns} ->
|
||||
["UNIQUE(",
|
||||
map_intersperse(fun indexed_column_sql/1, Columns, ", "), ")"]
|
||||
map_intersperse(fun indexed_column_sql/1, Columns, ", "), ")"];
|
||||
%% TODO: foreign key
|
||||
_ when is_list(TableConstraint) ->
|
||||
map_intersperse(fun table_constraint_sql/1, TableConstraint, ", ")
|
||||
end.
|
||||
|
||||
indexed_column_sql({ColumnName, asc}) -> [atom_to_list(ColumnName), " ASC"];
|
||||
@@ -427,6 +443,12 @@ create_table_sql_test() ->
|
||||
?assertFlat(
|
||||
"CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT);",
|
||||
create_table_sql(user, [{id, integer, [primary_key]}, {name, text}])),
|
||||
?assertFlat(
|
||||
"CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);",
|
||||
create_table_sql(user, [{id, integer, [{primary_key, autoincrement}]}, {name, text}])),
|
||||
?assertFlat(
|
||||
"CREATE TABLE user (id INTEGER PRIMARY KEY DESC, name TEXT);",
|
||||
create_table_sql(user, [{id, integer, [{primary_key, desc}]}, {name, text}])),
|
||||
?assertFlat(
|
||||
"CREATE TABLE user (id INTEGER, name TEXT, PRIMARY KEY(id));",
|
||||
create_table_sql(user,
|
||||
|
||||
@@ -47,7 +47,8 @@ all_test_() ->
|
||||
?FuncTest(select_many_records),
|
||||
?FuncTest(nonexistent_table_info),
|
||||
?FuncTest(large_number),
|
||||
?FuncTest(unicode)]}.
|
||||
?FuncTest(unicode),
|
||||
?FuncTest(acc_string_encoding)]}.
|
||||
|
||||
open_db() ->
|
||||
sqlite3:open(ct, [in_memory]).
|
||||
@@ -61,7 +62,11 @@ basic_functionality() ->
|
||||
Columns = ["id", "name", "age", "wage"],
|
||||
AllRows = [{1, <<"abby">>, 20, 2000}, {2, <<"marge">>, 30, 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, [asc, autoincrement]}]},
|
||||
{name, text, [not_null, unique]},
|
||||
{age, integer, not_null},
|
||||
{wage, integer}],
|
||||
TableInfo1 = lists:keyreplace(age, 1, TableInfo, {age, integer, [not_null]}),
|
||||
drop_all_tables(ct),
|
||||
?WARN_ERROR_MESSAGE,
|
||||
?assertEqual(
|
||||
@@ -72,10 +77,10 @@ basic_functionality() ->
|
||||
sqlite3:list_tables(ct)),
|
||||
ok = sqlite3:create_table(ct, user, TableInfo),
|
||||
?assertEqual(
|
||||
[user],
|
||||
[user, sqlite_sequence],
|
||||
sqlite3:list_tables(ct)),
|
||||
?assertEqual(
|
||||
TableInfo,
|
||||
TableInfo1,
|
||||
sqlite3:table_info(ct, user)),
|
||||
?assertEqual(
|
||||
{rowid, 1},
|
||||
@@ -216,6 +221,9 @@ unicode() ->
|
||||
sqlite3:write(ct, unicode, [{str, UnicodeString}]),
|
||||
?assertEqual([{unicode:characters_to_binary(UnicodeString)}], rows(sqlite3:read_all(ct, unicode))).
|
||||
|
||||
acc_string_encoding() ->
|
||||
?assertEqual([{62}], rows(sqlite3:sql_exec(ct, "SELECT ? + ?", [30,32]))).
|
||||
|
||||
prepared_test() ->
|
||||
Columns = ["id", "name", "age", "wage"],
|
||||
Abby = {1, <<"abby">>, 20, 2000},
|
||||
|
||||
Reference in New Issue
Block a user