Files
esqlite/test/esqlite_test.erl
2022-01-08 17:04:47 +01:00

511 lines
18 KiB
Erlang

%%
%% Test suite for esqlite.
%%
-module(esqlite_test).
-include_lib("eunit/include/eunit.hrl").
open_single_database_test() ->
{ok, _C1} = esqlite3:open("test.db"),
ok.
close_test() ->
%% Open and close the database immediately
{ok, C} = esqlite3:open(":memory:"),
ok = esqlite3:close(C),
%% Check if functions still return sensible values.
{error, closed} = esqlite3:set_update_hook(self(), C),
{error, closed} = esqlite3:changes(C),
{error, closed} = esqlite3:get_autocommit(C),
{error, closed} = esqlite3:last_insert_rowid(C),
{error, _} = esqlite3:exec("create table test(one, two, three)", C),
ok.
open_multiple_same_databases_test() ->
{ok, _C1} = esqlite3:open("test.db"),
{ok, _C2} = esqlite3:open("test.db"),
ok.
open_multiple_different_databases_test() ->
{ok, _C1} = esqlite3:open("test1.db"),
{ok, _C2} = esqlite3:open("test2.db"),
ok.
get_autocommit_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
true = esqlite3:get_autocommit(Db),
ok = esqlite3:exec("BEGIN;", Db),
false = esqlite3:get_autocommit(Db),
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
ok = esqlite3:exec("COMMIT;", Db),
true = esqlite3:get_autocommit(Db),
ok.
last_insert_rowid_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
{ok, 1} = esqlite3:last_insert_rowid(Db),
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is another test');", Db),
{ok, 2} = esqlite3:last_insert_rowid(Db),
ok.
update_hook_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:set_update_hook(self(), Db),
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
ok = receive {insert, "test", 1} -> ok after 150 -> no_message end,
ok = esqlite3:exec("UPDATE test SET val = 'a new test' WHERE id = 1;", Db),
ok = receive {update, "test", 1} -> ok after 150 -> no_message end,
ok = esqlite3:exec("DELETE FROM test WHERE id = 1;", Db),
ok = receive {delete, "test", 1} -> ok after 150 -> no_message end,
ok.
simple_query_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
{ok, 1} = esqlite3:changes(Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "11" ");"], Db),
{ok, 1} = esqlite3:changes(Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello3\"", ",", "12" ");"], Db),
{ok, 1} = esqlite3:changes(Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db),
{ok, 1} = esqlite3:changes(Db),
ok = esqlite3:exec("commit;", Db),
ok = esqlite3:exec("select * from test_table;", Db),
ok = esqlite3:exec("delete from test_table;", Db),
{ok, 4} = esqlite3:changes(Db),
ok.
prepare_test() ->
{ok, Db} = esqlite3:open(":memory:"),
esqlite3:exec("begin;", Db),
esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
{ok, Statement} = esqlite3:prepare("insert into test_table values(\"one\", 2)", Db),
'$done' = esqlite3:step(Statement),
{ok, 1} = esqlite3:changes(Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db),
%% Check if the values are there.
[{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q("select * from test_table order by two", Db),
esqlite3:exec("commit;", Db),
esqlite3:close(Db),
ok.
bind_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec("commit;", Db),
%% Create a prepared statement
{ok, Statement} = esqlite3:prepare("insert into test_table values(?1, ?2)", Db),
esqlite3:bind(Statement, [one, 2]),
esqlite3:step(Statement),
esqlite3:bind(Statement, ["three", 4]),
esqlite3:step(Statement),
esqlite3:bind(Statement, ["five", 6]),
esqlite3:step(Statement),
esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text
esqlite3:step(Statement),
esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text
esqlite3:step(Statement),
esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos.
esqlite3:step(Statement),
esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null
esqlite3:step(Statement),
%% int64
esqlite3:bind(Statement, [int64, 308553449069486081]),
esqlite3:step(Statement),
%% negative int64
esqlite3:bind(Statement, [negative_int64, -308553449069486081]),
esqlite3:step(Statement),
%% utf-8
esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]),
esqlite3:step(Statement),
?assertEqual([{<<"one">>, 2}],
esqlite3:q("select one, two from test_table where two = '2'", Db)),
?assertEqual([{<<"three">>, 4}],
esqlite3:q("select one, two from test_table where two = 4", Db)),
?assertEqual([{<<"five">>, 6}],
esqlite3:q("select one, two from test_table where two = 6", Db)),
?assertEqual([{<<"seven">>, 8}],
esqlite3:q("select one, two from test_table where two = 8", Db)),
?assertEqual([{<<"nine">>, 10}],
esqlite3:q("select one, two from test_table where two = 10", Db)),
?assertEqual([{{blob, <<$e,$l,$e,$v,$e,$n,0>>}, 12}],
esqlite3:q("select one, two from test_table where two = 12", Db)),
?assertEqual([{<<"empty">>, undefined}],
esqlite3:q("select one, two from test_table where two is null", Db)),
?assertEqual([{<<"int64">>, 308553449069486081}],
esqlite3:q("select one, two from test_table where one = 'int64';", Db)),
?assertEqual([{<<"negative_int64">>, -308553449069486081}],
esqlite3:q("select one, two from test_table where one = 'negative_int64';", Db)),
%% utf-8
?assertEqual([{<<228,184,138,230,181,183>>, 100}],
esqlite3:q("select one, two from test_table where two = 100", Db)),
ok.
bind_for_queries_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec("commit;", Db),
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
[test_table], Db)),
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
["test_table"], Db)),
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
[<<"test_table">>], Db)),
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
[[<<"test_table">>]], Db)),
ok.
column_names_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "20" ");"], Db),
ok = esqlite3:exec("commit;", Db),
%% All columns
{ok, Stmt} = esqlite3:prepare("select * from test_table", Db),
{one, two} = esqlite3:column_names(Stmt),
{row, {<<"hello1">>, 10}} = esqlite3:step(Stmt),
{one, two} = esqlite3:column_names(Stmt),
{row, {<<"hello2">>, 20}} = esqlite3:step(Stmt),
{one, two} = esqlite3:column_names(Stmt),
'$done' = esqlite3:step(Stmt),
{one, two} = esqlite3:column_names(Stmt),
%% One column
{ok, Stmt2} = esqlite3:prepare("select two from test_table", Db),
{two} = esqlite3:column_names(Stmt2),
{row, {10}} = esqlite3:step(Stmt2),
{two} = esqlite3:column_names(Stmt2),
{row, {20}} = esqlite3:step(Stmt2),
{two} = esqlite3:column_names(Stmt2),
'$done' = esqlite3:step(Stmt2),
{two} = esqlite3:column_names(Stmt2),
%% No columns
{ok, Stmt3} = esqlite3:prepare("values(1);", Db),
{column1} = esqlite3:column_names(Stmt3),
{row, {1}} = esqlite3:step(Stmt3),
{column1} = esqlite3:column_names(Stmt3),
%% Things get a bit weird when you retrieve the column name
%% when calling an aggragage function.
{ok, Stmt4} = esqlite3:prepare("select date('now');", Db),
{'date(\'now\')'} = esqlite3:column_names(Stmt4),
{row, {Date}} = esqlite3:step(Stmt4),
true = is_binary(Date),
%% Some statements have no column names
{ok, Stmt5} = esqlite3:prepare("create table dummy(a, b, c);", Db),
{} = esqlite3:column_names(Stmt5),
ok.
column_types_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "20" ");"], Db),
ok = esqlite3:exec("commit;", Db),
%% All columns
{ok, Stmt} = esqlite3:prepare("select * from test_table", Db),
?assertEqual({'varchar(10)', 'INT'}, esqlite3:column_types(Stmt)),
{row, {<<"hello1">>, 10}} = esqlite3:step(Stmt),
{'varchar(10)', 'INT'} = esqlite3:column_types(Stmt),
{row, {<<"hello2">>, 20}} = esqlite3:step(Stmt),
{'varchar(10)', 'INT'} = esqlite3:column_types(Stmt),
'$done' = esqlite3:step(Stmt),
{'varchar(10)', 'INT'} = esqlite3:column_types(Stmt),
%% Some statements have no column types
{ok, Stmt2} = esqlite3:prepare("create table dummy(a, b, c);", Db),
{} = esqlite3:column_types(Stmt2),
ok.
nil_column_types_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table t1(c1 variant);", Db),
ok = esqlite3:exec("commit;", Db),
{ok, Stmt} = esqlite3:prepare("select c1 + 1, c1 from t1", Db),
{nil, variant} = esqlite3:column_types(Stmt),
ok.
reset_test() ->
{ok, Db} = esqlite3:open(":memory:"),
{ok, Stmt} = esqlite3:prepare("select * from (values (1), (2));", Db),
{row, {1}} = esqlite3:step(Stmt),
ok = esqlite3:reset(Stmt),
{row, {1}} = esqlite3:step(Stmt),
{row, {2}} = esqlite3:step(Stmt),
'$done' = esqlite3:step(Stmt),
% After a done the statement is automatically reset.
{row, {1}} = esqlite3:step(Stmt),
% Calling reset multiple times...
ok = esqlite3:reset(Stmt),
ok = esqlite3:reset(Stmt),
ok = esqlite3:reset(Stmt),
ok = esqlite3:reset(Stmt),
% The statement should still be reset.
{row, {1}} = esqlite3:step(Stmt),
ok.
foreach_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "11" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello3\"", ",", "12" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db),
ok = esqlite3:exec("commit;", Db),
F = fun(Row) ->
case Row of
{Key, Value} ->
put(Key, Value);
_ ->
ok
end
end,
esqlite3:foreach(F, "select * from test_table;", Db),
10 = get(<<"hello1">>),
11 = get(<<"hello2">>),
12 = get(<<"hello3">>),
13 = get(<<"hello4">>),
ok.
bind_for_foreach_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "11" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello3\"", ",", "12" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db),
ok = esqlite3:exec("commit;", Db),
F = fun(Row) ->
case Row of
{Key, Value} ->
put(Key, Value);
_ ->
ok
end
end,
esqlite3:foreach(F, "select * from test_table where one = ?;", ["hello1"], Db),
10 = get(<<"hello1">>),
ok.
map_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "11" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello3\"", ",", "12" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db),
ok = esqlite3:exec("commit;", Db),
F = fun(Row) -> Row end,
[{<<"hello1">>,10},
{<<"hello2">>,11},
{<<"hello3">>,12},
{<<"hello4">>,13}] = esqlite3:map(F, "select * from test_table", Db),
%% Test that when the row-names are added..
Assoc = fun(Names, Row) ->
lists:zip(tuple_to_list(Names), tuple_to_list(Row))
end,
[[{one,<<"hello1">>},{two,10}],
[{one,<<"hello2">>},{two,11}],
[{one,<<"hello3">>},{two,12}],
[{one,<<"hello4">>},{two,13}]] = esqlite3:map(Assoc, "select * from test_table", Db),
ok.
bind_for_map_test() ->
{ok, Db} = esqlite3:open(":memory:"),
ok = esqlite3:exec("begin;", Db),
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello1\"", ",", "10" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello2\"", ",", "11" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello3\"", ",", "12" ");"], Db),
ok = esqlite3:exec(["insert into test_table values(", "\"hello4\"", ",", "13" ");"], Db),
ok = esqlite3:exec("commit;", Db),
F = fun(Row) -> Row end,
[{<<"hello1">>,10}]
= esqlite3:map(F, "select * from test_table where one = ?", ["hello1"], Db),
%% Test that when the row-names are added..
Assoc = fun(Names, Row) ->
lists:zip(tuple_to_list(Names), tuple_to_list(Row))
end,
[[{one,<<"hello1">>},{two,10}]] = esqlite3:map(Assoc, "select * from test_table where one = ?", ["hello1"], Db),
ok.
error1_msg_test() ->
{ok, Db} = esqlite3:open(":memory:"),
%% Not sql.
{error, {sqlite_error, _Msg1}} = esqlite3:exec("dit is geen sql", Db),
%% Database test does not exist.
{error, {sqlite_error, _Msg2}} = esqlite3:exec("select * from test;", Db),
%% Opening non-existant database.
{error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"),
ok.
prepare_and_close_connection_test() ->
{ok, Db} = esqlite3:open(":memory:"),
[] = esqlite3:q("create table test(one, two, three)", Db),
ok = esqlite3:exec(["insert into test values(1,2,3);"], Db),
{ok, Stmt} = esqlite3:prepare("select * from test", Db),
%% The prepated statment works.
{row, {1,2,3}} = esqlite3:step(Stmt),
'$done' = esqlite3:step(Stmt),
ok = esqlite3:close(Db),
ok = esqlite3:reset(Stmt),
%% Internally sqlite3_close_v2 is used by the nif. This will destruct the
%% connection when the last perpared statement is finalized
{row, {1,2,3}} = esqlite3:step(Stmt),
'$done' = esqlite3:step(Stmt),
ok.
backup_test() ->
{ok, Dest} = esqlite3:open("test1.sql"),
{ok, Source} = esqlite3:open("test2.sql"),
{ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"),
{ok, 0} = esqlite3:backup_remaining(Backup),
{ok, 0} = esqlite3:backup_pagecount(Backup),
ok.
sqlite_version_test() ->
{ok, Db} = esqlite3:open(":memory:"),
{ok, Stmt} = esqlite3:prepare("select sqlite_version() as sqlite_version;", Db),
{sqlite_version} = esqlite3:column_names(Stmt),
?assertEqual({row, {<<"3.37.1">>}}, esqlite3:step(Stmt)),
ok.
sqlite_source_id_test() ->
{ok, Db} = esqlite3:open(":memory:"),
{ok, Stmt} = esqlite3:prepare("select sqlite_source_id() as sqlite_source_id;", Db),
{sqlite_source_id} = esqlite3:column_names(Stmt),
?assertEqual({row, {<<"2021-12-30 15:30:28 378629bf2ea546f73eee84063c5358439a12f7300e433f18c9e1bddd948dea62">>}},
esqlite3:step(Stmt)),
ok.
interrupt_on_timeout_test() ->
{ok, Db} = esqlite3:open(":memory:"),
CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);",
ok = esqlite3:exec(CreateTableQuery, Db),
VeryLongQuery = "
WITH RECURSIVE
for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000)
INSERT INTO all_numbers_in_the_world SELECT i FROM for;
",
try
ok = esqlite3:exec(VeryLongQuery, [], Db, 10)
catch
{error, timeout, _} ->
?assertMatch([{0}], esqlite3:q("SELECT COUNT(*) FROM all_numbers_in_the_world", Db)),
%% There is now a stale answer, because the recursive query was interrupted.
receive
{esqlite3, _, {error, {interrupt, "interrupted"}}} ->
ok
end
end.
garbage_collect_test() ->
F = fun() ->
{ok, Db} = esqlite3:open(":memory:"),
[] = esqlite3:q("create table test(one, two, three)", Db),
[] = esqlite3:q("insert into test values(1, '2', 3.0)", Db),
{ok, Stmt} = esqlite3:prepare("select * from test", Db),
{row, {1, <<"2">>, 3.0}} = esqlite3:step(Stmt),
'$done' = esqlite3:step(Stmt),
ok = esqlite3:close(Db)
end,
[spawn(F) || _X <- lists:seq(0,30)],
receive after 500 -> ok end,
erlang:garbage_collect(),
[spawn(F) || _X <- lists:seq(0,30)],
receive after 500 -> ok end,
erlang:garbage_collect(),
ok.