Increased robustness against bad arguments
This commit is contained in:
@@ -32,7 +32,7 @@
|
|||||||
-type table_constraints() :: table_constraint() | [table_constraint()].
|
-type table_constraints() :: table_constraint() | [table_constraint()].
|
||||||
-type table_info() :: [{atom(), sql_type()} | {atom(), sql_type(), column_constraints()}].
|
-type table_info() :: [{atom(), sql_type()} | {atom(), sql_type(), column_constraints()}].
|
||||||
|
|
||||||
-type sqlite_error() :: {error, integer(), string()}.
|
-type sqlite_error() :: {error, integer(), string()} | {error, term()}.
|
||||||
-type sql_params() :: [sql_value() | {atom() | string() | integer(), sql_value()}].
|
-type sql_params() :: [sql_value() | {atom() | string() | integer(), sql_value()}].
|
||||||
-type sql_non_query_result() :: ok | sqlite_error() | {rowid, integer()}.
|
-type sql_non_query_result() :: ok | sqlite_error() | {rowid, integer()}.
|
||||||
-type sql_result() :: sql_non_query_result() | [{columns, [string()]} | {rows, [tuple()]}].
|
-type sql_result() :: sql_non_query_result() | [{columns, [string()]} | {rows, [tuple()]}].
|
||||||
|
|||||||
129
src/sqlite3.erl
129
src/sqlite3.erl
@@ -866,9 +866,8 @@ handle_call(list_tables, _From, State) ->
|
|||||||
TableList = proplists:get_value(rows, Data),
|
TableList = proplists:get_value(rows, Data),
|
||||||
TableNames = [erlang:list_to_atom(erlang:binary_to_list(Name)) || {Name} <- TableList],
|
TableNames = [erlang:list_to_atom(erlang:binary_to_list(Name)) || {Name} <- TableList],
|
||||||
{reply, TableNames, State};
|
{reply, TableNames, State};
|
||||||
handle_call({table_info, Tbl}, _From, State) ->
|
handle_call({table_info, Tbl}, _From, State) when is_atom(Tbl) ->
|
||||||
% make sure we only get table info.
|
% make sure we only get table info.
|
||||||
% SQL Injection warning
|
|
||||||
SQL = io_lib:format("select sql from sqlite_master where tbl_name = '~p' and type='table';", [Tbl]),
|
SQL = io_lib:format("select sql from sqlite_master where tbl_name = '~p' and type='table';", [Tbl]),
|
||||||
Data = do_sql_exec(SQL, State),
|
Data = do_sql_exec(SQL, State),
|
||||||
TableSql = proplists:get_value(rows, Data),
|
TableSql = proplists:get_value(rows, Data),
|
||||||
@@ -879,6 +878,8 @@ handle_call({table_info, Tbl}, _From, State) ->
|
|||||||
[] ->
|
[] ->
|
||||||
{reply, table_does_not_exist, State}
|
{reply, table_does_not_exist, State}
|
||||||
end;
|
end;
|
||||||
|
handle_call({table_info, _NotAnAtom}, _From, State) ->
|
||||||
|
{reply, {error, badarg}, State};
|
||||||
handle_call({create_function, FunctionName, Function}, _From, #state{port = Port} = State) ->
|
handle_call({create_function, FunctionName, Function}, _From, #state{port = Port} = State) ->
|
||||||
Reply = exec(Port, {create_function, FunctionName, Function}),
|
Reply = exec(Port, {create_function, FunctionName, Function}),
|
||||||
{reply, Reply, State};
|
{reply, Reply, State};
|
||||||
@@ -891,43 +892,83 @@ handle_call({sql_exec_script, SQL}, _From, State) ->
|
|||||||
Reply = do_sql_exec_script(SQL, State),
|
Reply = do_sql_exec_script(SQL, State),
|
||||||
{reply, Reply, 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),
|
try sqlite3_lib:create_table_sql(Tbl, Columns) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({create_table, Tbl, Columns, Constraints}, _From, State) ->
|
handle_call({create_table, Tbl, Columns, Constraints}, _From, State) ->
|
||||||
SQL = sqlite3_lib:create_table_sql(Tbl, Columns, Constraints),
|
try sqlite3_lib:create_table_sql(Tbl, Columns, Constraints) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({update, Tbl, Key, Value, Data}, _From, State) ->
|
handle_call({update, Tbl, Key, Value, Data}, _From, State) ->
|
||||||
SQL = sqlite3_lib:update_sql(Tbl, Key, Value, Data),
|
try sqlite3_lib:update_sql(Tbl, Key, Value, Data) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({write, Tbl, Data}, _From, State) ->
|
handle_call({write, Tbl, Data}, _From, State) ->
|
||||||
% insert into t1 (data,num) values ('This is sample data',3);
|
% insert into t1 (data,num) values ('This is sample data',3);
|
||||||
SQL = sqlite3_lib:write_sql(Tbl, Data),
|
try sqlite3_lib:write_sql(Tbl, Data) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({write_many, Tbl, DataList}, _From, State) ->
|
handle_call({write_many, Tbl, DataList}, _From, State) ->
|
||||||
do_sql_exec("BEGIN;", State),
|
do_sql_exec("BEGIN;", State),
|
||||||
[do_sql_exec(sqlite3_lib:write_sql(Tbl, Data), State) || Data <- DataList],
|
[do_sql_exec(sqlite3_lib:write_sql(Tbl, Data), State) || Data <- DataList],
|
||||||
do_handle_call_sql_exec("COMMIT;", State);
|
do_handle_call_sql_exec("COMMIT;", State);
|
||||||
handle_call({read, Tbl}, _From, State) ->
|
handle_call({read, Tbl}, _From, State) ->
|
||||||
% select * from Tbl where Key = Value;
|
% select * from Tbl where Key = Value;
|
||||||
SQL = sqlite3_lib:read_sql(Tbl),
|
try sqlite3_lib:read_sql(Tbl) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({read, Tbl, Columns}, _From, State) ->
|
handle_call({read, Tbl, Columns}, _From, State) ->
|
||||||
SQL = sqlite3_lib:read_sql(Tbl, Columns),
|
try sqlite3_lib:read_sql(Tbl, Columns) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({read, Tbl, Key, Value}, _From, State) ->
|
handle_call({read, Tbl, Key, Value}, _From, State) ->
|
||||||
% select * from Tbl where Key = Value;
|
% select * from Tbl where Key = Value;
|
||||||
SQL = sqlite3_lib:read_sql(Tbl, Key, Value),
|
try sqlite3_lib:read_sql(Tbl, Key, Value) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({read, Tbl, Key, Value, Columns}, _From, State) ->
|
handle_call({read, Tbl, Key, Value, Columns}, _From, State) ->
|
||||||
SQL = sqlite3_lib:read_sql(Tbl, Key, Value, Columns),
|
try sqlite3_lib:read_sql(Tbl, Key, Value, Columns) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({delete, Tbl, {Key, Value}}, _From, State) ->
|
handle_call({delete, Tbl, {Key, Value}}, _From, State) ->
|
||||||
% delete from Tbl where Key = Value;
|
% delete from Tbl where Key = Value;
|
||||||
SQL = sqlite3_lib:delete_sql(Tbl, Key, Value),
|
try sqlite3_lib:delete_sql(Tbl, Key, Value) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({drop_table, Tbl}, _From, State) ->
|
handle_call({drop_table, Tbl}, _From, State) ->
|
||||||
SQL = sqlite3_lib:drop_table_sql(Tbl),
|
try sqlite3_lib:drop_table_sql(Tbl) of
|
||||||
do_handle_call_sql_exec(SQL, State);
|
SQL -> do_handle_call_sql_exec(SQL, State)
|
||||||
|
catch
|
||||||
|
_:Exception ->
|
||||||
|
{reply, {error, Exception}, State}
|
||||||
|
end;
|
||||||
handle_call({prepare, SQL}, _From, State = #state{port = Port, refs = Refs}) ->
|
handle_call({prepare, SQL}, _From, State = #state{port = Port, refs = Refs}) ->
|
||||||
case exec(Port, {prepare, SQL}) of
|
case exec(Port, {prepare, SQL}) of
|
||||||
Index when is_integer(Index) ->
|
Index when is_integer(Index) ->
|
||||||
@@ -940,17 +981,26 @@ handle_call({prepare, SQL}, _From, State = #state{port = Port, refs = Refs}) ->
|
|||||||
end,
|
end,
|
||||||
{reply, Reply, NewState};
|
{reply, Reply, NewState};
|
||||||
handle_call({bind, Ref, Params}, _From, State = #state{port = Port, refs = Refs}) ->
|
handle_call({bind, Ref, Params}, _From, State = #state{port = Port, refs = Refs}) ->
|
||||||
Index = dict:fetch(Ref, Refs),
|
Reply = case dict:find(Ref, Refs) of
|
||||||
Reply = exec(Port, {bind, Index, Params}),
|
{ok, Index} ->
|
||||||
|
exec(Port, {bind, Index, Params});
|
||||||
|
error ->
|
||||||
|
{error, badarg}
|
||||||
|
end,
|
||||||
{reply, Reply, State};
|
{reply, Reply, State};
|
||||||
handle_call({finalize, Ref}, _From, State = #state{port = Port, refs = Refs}) ->
|
handle_call({finalize, Ref}, _From, State = #state{port = Port, refs = Refs}) ->
|
||||||
Index = dict:fetch(Ref, Refs),
|
case dict:find(Ref, Refs) of
|
||||||
case exec(Port, {finalize, Index}) of
|
{ok, Index} ->
|
||||||
ok ->
|
case exec(Port, {finalize, Index}) of
|
||||||
Reply = ok,
|
ok ->
|
||||||
NewState = State#state{refs = dict:erase(Ref, Refs)};
|
Reply = ok,
|
||||||
Error ->
|
NewState = State#state{refs = dict:erase(Ref, Refs)};
|
||||||
Reply = Error,
|
Error ->
|
||||||
|
Reply = Error,
|
||||||
|
NewState = State
|
||||||
|
end;
|
||||||
|
error ->
|
||||||
|
Reply = {error, badarg},
|
||||||
NewState = State
|
NewState = State
|
||||||
end,
|
end,
|
||||||
{reply, Reply, NewState};
|
{reply, Reply, NewState};
|
||||||
@@ -959,8 +1009,7 @@ handle_call({Cmd, Ref}, _From, State = #state{port = Port, refs = Refs}) ->
|
|||||||
{ok, Index} ->
|
{ok, Index} ->
|
||||||
exec(Port, {Cmd, Index});
|
exec(Port, {Cmd, Index});
|
||||||
error ->
|
error ->
|
||||||
{error, -1,
|
{error, badarg}
|
||||||
"Bad reference to prepared statement; it is already finalized or doesn't exist"}
|
|
||||||
end,
|
end,
|
||||||
{reply, Reply, State};
|
{reply, Reply, State};
|
||||||
handle_call(vacuum, _From, State) ->
|
handle_call(vacuum, _From, State) ->
|
||||||
@@ -1144,7 +1193,7 @@ wait_result(Port) ->
|
|||||||
error_logger:error_msg("sqlite3 driver port closed with reason ~p~n",
|
error_logger:error_msg("sqlite3 driver port closed with reason ~p~n",
|
||||||
[Reason]),
|
[Reason]),
|
||||||
% ?dbg("Error: ~p~n", [Reason]),
|
% ?dbg("Error: ~p~n", [Reason]),
|
||||||
{error, -1, Reason};
|
{error, Reason};
|
||||||
Other when is_tuple(Other), element(1, Other) =/= '$gen_call', element(1, Other) =/= '$gen_cast' ->
|
Other when is_tuple(Other), element(1, Other) =/= '$gen_call', element(1, Other) =/= '$gen_cast' ->
|
||||||
error_logger:error_msg("sqlite3 unexpected reply ~p~n",
|
error_logger:error_msg("sqlite3 unexpected reply ~p~n",
|
||||||
[Other]),
|
[Other]),
|
||||||
@@ -1232,11 +1281,15 @@ build_primary_key_constraint(Tail, Acc) ->
|
|||||||
%%
|
%%
|
||||||
%% Currently supported constraints for {@link table_info()} and {@link sqlite3:create_table/4}.
|
%% Currently supported constraints for {@link table_info()} and {@link sqlite3:create_table/4}.
|
||||||
%% @end
|
%% @end
|
||||||
%% @type sqlite_error() = {'error', integer(), string()}.
|
%% @type sqlite_error() = {'error', integer(), string()} | {'error', any()}.
|
||||||
%%
|
%%
|
||||||
%% Errors are reported by their SQLite result code
|
%% Errors occuring on the C side are represented by 3-element tuples containing
|
||||||
%% ([http://www.sqlite.org/c3ref/c_busy_recovery.html]) and a string containing
|
%% atom 'error', SQLite result code ([http://www.sqlite.org/c3ref/c_abort.html],
|
||||||
%% English-language text that describes the error.
|
%% [http://www.sqlite.org/c3ref/c_busy_recovery.html]) and an English-language error
|
||||||
|
%% message.
|
||||||
|
%%
|
||||||
|
%% Errors occuring on the Erlang side are represented by 2-element tuples with
|
||||||
|
%% first element 'error'.
|
||||||
%% @end
|
%% @end
|
||||||
%% @type sql_non_query_result() = ok | sqlite_error() | {rowid, integer()}.
|
%% @type sql_non_query_result() = ok | sqlite_error() | {rowid, integer()}.
|
||||||
%% The result returned by functions which call the database but don't return
|
%% The result returned by functions which call the database but don't return
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ all_test_() ->
|
|||||||
fun close_db/1,
|
fun close_db/1,
|
||||||
[?FuncTest(basic_functionality),
|
[?FuncTest(basic_functionality),
|
||||||
?FuncTest(parametrized),
|
?FuncTest(parametrized),
|
||||||
|
?FuncTest(negative),
|
||||||
?FuncTest(blob),
|
?FuncTest(blob),
|
||||||
?FuncTest(escaping),
|
?FuncTest(escaping),
|
||||||
?FuncTest(select_many_records),
|
?FuncTest(select_many_records),
|
||||||
@@ -127,6 +128,10 @@ parametrized() ->
|
|||||||
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?3, ?5)", [{3, 2}, {5, "joe"}]),
|
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?3, ?5)", [{3, 2}, {5, "joe"}]),
|
||||||
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (:id, @name)", [{":id", 3}, {'@name', <<"jack">>}]),
|
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (:id, @name)", [{":id", 3}, {'@name', <<"jack">>}]),
|
||||||
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [4, "james"]),
|
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [4, "james"]),
|
||||||
|
?WARN_ERROR_MESSAGE,
|
||||||
|
?assertMatch(
|
||||||
|
{error, _, _},
|
||||||
|
sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [4, bad_sql_value])),
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
[{columns, ["id", "name"]},
|
[{columns, ["id", "name"]},
|
||||||
{rows, [{1, <<"john">>}, {2, <<"joe">>}, {3, <<"jack">>}, {4, <<"james">>}]}],
|
{rows, [{1, <<"john">>}, {2, <<"joe">>}, {3, <<"jack">>}, {4, <<"james">>}]}],
|
||||||
@@ -140,6 +145,12 @@ parametrized() ->
|
|||||||
{rows, [{null, 1.0, {blob, <<1,0,0>>}}]}],
|
{rows, [{null, 1.0, {blob, <<1,0,0>>}}]}],
|
||||||
sqlite3:read_all(ct, user1)).
|
sqlite3:read_all(ct, user1)).
|
||||||
|
|
||||||
|
negative() ->
|
||||||
|
drop_table_if_exists(ct, negative),
|
||||||
|
sqlite3:create_table(ct, negative, [{id, int}]),
|
||||||
|
?assertEqual({error, badarg},
|
||||||
|
sqlite3:write(ct, negative, [{id, bad_sql_value}])).
|
||||||
|
|
||||||
blob() ->
|
blob() ->
|
||||||
drop_table_if_exists(ct, blobs),
|
drop_table_if_exists(ct, blobs),
|
||||||
sqlite3:create_table(ct, blobs, [{blob_col, blob}]),
|
sqlite3:create_table(ct, blobs, [{blob_col, blob}]),
|
||||||
@@ -234,6 +245,7 @@ prepared_test() ->
|
|||||||
sqlite3:write(prepared, user, [{name, "marge"}, {age, 30}, {wage, 2000}]),
|
sqlite3:write(prepared, user, [{name, "marge"}, {age, 30}, {wage, 2000}]),
|
||||||
{ok, Ref1} = sqlite3:prepare(prepared, "SELECT * FROM user"),
|
{ok, Ref1} = sqlite3:prepare(prepared, "SELECT * FROM user"),
|
||||||
{ok, Ref2} = sqlite3:prepare(prepared, "SELECT * FROM user WHERE name = ?"),
|
{ok, Ref2} = sqlite3:prepare(prepared, "SELECT * FROM user WHERE name = ?"),
|
||||||
|
?assertMatch({error, _}, sqlite3:next(prepared, make_ref())),
|
||||||
?assertEqual(Columns, sqlite3:columns(prepared, Ref1)),
|
?assertEqual(Columns, sqlite3:columns(prepared, Ref1)),
|
||||||
?assertEqual(Abby, sqlite3:next(prepared, Ref1)),
|
?assertEqual(Abby, sqlite3:next(prepared, Ref1)),
|
||||||
?assertEqual(ok, sqlite3:reset(prepared, Ref1)),
|
?assertEqual(ok, sqlite3:reset(prepared, Ref1)),
|
||||||
@@ -241,7 +253,7 @@ prepared_test() ->
|
|||||||
?assertEqual(Marge, sqlite3:next(prepared, Ref1)),
|
?assertEqual(Marge, sqlite3:next(prepared, Ref1)),
|
||||||
?assertEqual(done, sqlite3:next(prepared, Ref1)),
|
?assertEqual(done, sqlite3:next(prepared, Ref1)),
|
||||||
?assertEqual(ok, sqlite3:finalize(prepared, Ref1)),
|
?assertEqual(ok, sqlite3:finalize(prepared, Ref1)),
|
||||||
?assertMatch({error, _, _}, sqlite3:next(prepared, Ref1)),
|
?assertMatch({error, _}, sqlite3:next(prepared, Ref1)),
|
||||||
?assertEqual(ok, sqlite3:reset(prepared, Ref2)),
|
?assertEqual(ok, sqlite3:reset(prepared, Ref2)),
|
||||||
?assertEqual(ok, sqlite3:bind(prepared, Ref2, ["marge"])),
|
?assertEqual(ok, sqlite3:bind(prepared, Ref2, ["marge"])),
|
||||||
?assertEqual(Marge, sqlite3:next(prepared, Ref2)),
|
?assertEqual(Marge, sqlite3:next(prepared, Ref2)),
|
||||||
|
|||||||
Reference in New Issue
Block a user