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

This commit is contained in:
Alexey Romanov
2011-05-09 18:44:11 +04:00
7 changed files with 219 additions and 125 deletions

View File

@@ -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, &param_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, &param_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
}

View File

@@ -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()]}].

BIN
rebar vendored

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{application, sqlite3,
[
{description, "SQLite3 Interface"},
{vsn, "1.0"},
{vsn, "1.0.1"},
{modules, []},
{registered, []},
{applications, [kernel, stdlib]},

View File

@@ -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()}.

View File

@@ -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,

View File

@@ -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},