%%%------------------------------------------------------------------- %%% File : sqlite3_test.erl %%% Author : Tee Teoh %%% Description : %%% %%% Created : 10 Jun 2008 by Tee Teoh %%%------------------------------------------------------------------- -module(sqlite3_test). %% ==================================================================== %% API %% ==================================================================== %% -------------------------------------------------------------------- %% Function: %% Description: %% -------------------------------------------------------------------- -include_lib("eunit/include/eunit.hrl"). -define(FuncTest(Name), {??Name, fun Name/0}). -define(WARN_ERROR_MESSAGE, io:format(user, "Error message should be shown...~n", [])). drop_all_tables(Db) -> Tables = sqlite3:list_tables(Db), [sqlite3:drop_table(Db, Table) || Table <- Tables], Tables. drop_table_if_exists(Db, Table) -> case lists:member(Table, sqlite3:list_tables(Db)) of true -> sqlite3:drop_table(Db, Table); false -> ok end. rows(SqlExecReply) -> case SqlExecReply of [{columns, _Columns}, {rows, Rows}] -> Rows; {error, Code, Reason} -> {error, Code, Reason} end. all_test_() -> {setup, fun open_db/0, fun close_db/1, [?FuncTest(basic_functionality), ?FuncTest(parametrized), ?FuncTest(negative), ?FuncTest(blob), ?FuncTest(escaping), ?FuncTest(select_many_records), ?FuncTest(nonexistent_table_info), ?FuncTest(large_number), ?FuncTest(unicode), ?FuncTest(acc_string_encoding), ?FuncTest(large_offset), ?FuncTest(issue23), ?FuncTest(issue13), ?FuncTest(enable_load_extension)]}. anonymous_test() -> {ok, Pid} = sqlite3:open(anonymous, []), try ?assertEqual(undefined, whereis(anonymous)), ?assertNot(filelib:is_file("./anonymous.db")) after sqlite3:close(Pid) end. open_db() -> sqlite3:open(ct, [in_memory]). close_db({ok, _Pid}) -> sqlite3:close(ct); close_db(_) -> ok. 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, [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, ?assertMatch( {error, 21, _}, sqlite3:sql_exec(ct, "-- Comment")), ?assertEqual( [], sqlite3:list_tables(ct)), ok = sqlite3:create_table(ct, user, TableInfo), ?assertEqual( [user, sqlite_sequence], sqlite3:list_tables(ct)), ?assertEqual( TableInfo1, sqlite3:table_info(ct, user)), ?assertEqual( {rowid, 1}, sqlite3:write(ct, user, [{name, "abby"}, {age, 20}, {<<"wage">>, 2000}])), ?assertEqual( {rowid, 2}, sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), ?WARN_ERROR_MESSAGE, ?assertMatch( {error, 19, _}, sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), ?assertEqual( [{columns, Columns}, {rows, AllRows}], sqlite3:sql_exec(ct, "select * from user;")), ?assertEqual( [{columns, Columns}, {rows, AllRows}], sqlite3:read_all(ct, user)), ?assertEqual( [{columns, ["name"]}, {rows, [{<<"abby">>}, {<<"marge">>}]}], sqlite3:read_all(ct, user, [name])), ?assertEqual( [{columns, Columns}, {rows, AbbyOnly}], sqlite3:read(ct, user, {name, "abby"})), ?assertEqual( [{columns, Columns}, {rows, AllRows}], sqlite3:read(ct, user, {wage, 2000})), ?assertEqual( ok, sqlite3:delete(ct, user, {name, "marge"})), ?assertEqual( ok, sqlite3:update(ct, user, {name, "abby"}, [{wage, 3000}])), ?assertEqual( [{columns, Columns}, {rows, [{1, <<"abby">>, 20, 3000}]}], sqlite3:sql_exec(ct, "select * from user;")), ?assertEqual( ok, sqlite3:drop_table(ct, user)). parametrized() -> drop_table_if_exists(ct, user1), sqlite3:create_table(ct, user1, [{id, integer}, {name, text}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [{1, 1}, {2, "john"}]), 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 (?, ?)", [4, "james"]), ?WARN_ERROR_MESSAGE, ?assertMatch( {error, _, _}, sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [4, bad_sql_value])), ?assertEqual( [{columns, ["id", "name"]}, {rows, [{1, <<"john">>}, {2, <<"joe">>}, {3, <<"jack">>}, {4, <<"james">>}]}], sqlite3:read_all(ct, user1)), sqlite3:drop_table(ct, user1), sqlite3:create_table(ct, user1, [{i, integer}, {d, double}, {b, blob}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (i, d, b) VALUES (?, ?, ?)", [null, 1.0, {blob, <<1,0,0>>}]), ?assertEqual( [{columns, ["i", "d", "b"]}, {rows, [{null, 1.0, {blob, <<1,0,0>>}}]}], 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() -> drop_table_if_exists(ct, blobs), sqlite3:create_table(ct, blobs, [{blob_col, blob}]), sqlite3:write(ct, blobs, [{blob_col, {blob, <<0,255,1,2>>}}]), ?assertEqual( [{columns, ["blob_col"]}, {rows, [{{blob, <<0,255,1,2>>}}]}], sqlite3:read_all(ct, blobs)). escaping() -> drop_table_if_exists(ct, escaping), sqlite3:create_table(ct, escaping, [{str, text}]), Strings = ["a'", "b\"c", "d''e", "f\"\""], Input = [[{str, String}] || String <- Strings], ExpectedRows = [{list_to_binary(String)} || String <- Strings], sqlite3:write_many(ct, escaping, Input), ?assertEqual( [{columns, ["str"]}, {rows, ExpectedRows}], sqlite3:read_all(ct, escaping)). select_many_records() -> N = 1024, drop_table_if_exists(ct, many_records), sqlite3:create_table(ct, many_records, [{id, integer}, {name, text}]), sqlite3:write_many(ct, many_records, [[{id, X}, {name, "bar"}] || X <- lists:seq(1, N)]), Columns = ["id", "name"], ?assertEqual( [{columns, Columns}, {rows, [{1, <<"bar">>}]}], sqlite3:read(ct, many_records, {id, 1})), [?assertEqual( M, length(rows(sqlite3:sql_exec( ct, io_lib:format("select * from many_records limit ~p;", [M]))))) || M <- [10, 100, 1000]], ?assertEqual( N, length(rows(sqlite3:sql_exec(ct, "select * from many_records;")))). %% note that inserts are actually serialized by gen_server concurrent_inserts_test() -> N = 1024, sqlite3:open(concurrent, [in_memory]), %% doing this test not in memory is much slower! drop_table_if_exists(concurrent, t), sqlite3:create_table(concurrent, t, [{id0, integer}]), Self = self(), [spawn(fun () -> sqlite3:write(concurrent, t, [{id0, X}]), Self ! {finished, N} end) || X <- lists:seq(1, N)], loop_concurrent_inserts(N), ?assertEqual( N, length(rows(sqlite3:read_all(concurrent, t)))), sqlite3:close(concurrent). loop_concurrent_inserts(0) -> ok; loop_concurrent_inserts(N) -> receive {finished, _} -> loop_concurrent_inserts(N - 1) end. nonexistent_table_info() -> ?assertEqual(table_does_not_exist, sqlite3:table_info(ct, nonexistent)). large_number() -> N1 = 9223372036854775807, N2 = -9223372036854775808, Query1 = io_lib:format("select ~p, ~p", [N1, N2]), ?assertEqual([{N1, N2}], rows(sqlite3:sql_exec(ct, Query1))), Query2 = "select ?, ?", ?assertEqual([{N1, N2}], rows(sqlite3:sql_exec(ct, Query2, [N1, N2]))), ?assertNot([{N1 + 1, N2 - 1}] == rows(sqlite3:sql_exec(ct, Query2, [N1 + 1, N2 - 1]))). unicode() -> UnicodeString = [1102,1085,1080,1082,1086,1076], %% "Unicode" in Russian, in UTF-8 drop_table_if_exists(ct, unicode), sqlite3:create_table(ct, unicode, [{str, text}]), 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}, Marge = {2, <<"marge">>, 30, 2000}, TableInfo = [{id, integer, [primary_key]}, {name, text, [unique]}, {age, integer}, {wage, integer}], sqlite3:open(prepared, [in_memory]), ok = sqlite3:create_table(prepared, user, TableInfo), sqlite3:write(prepared, user, [{name, "abby"}, {age, 20}, {wage, 2000}]), sqlite3:write(prepared, user, [{name, "marge"}, {age, 30}, {wage, 2000}]), {ok, Ref1} = sqlite3:prepare(prepared, "SELECT * FROM user"), {ok, Ref2} = sqlite3:prepare(prepared, "SELECT * FROM user WHERE name = ?"), ?assertMatch({error, _}, sqlite3:next(prepared, make_ref())), ?assertEqual(Columns, sqlite3:columns(prepared, Ref1)), ?assertEqual(Abby, sqlite3:next(prepared, Ref1)), ?assertEqual(ok, sqlite3:reset(prepared, Ref1)), ?assertEqual(Abby, sqlite3:next(prepared, Ref1)), ?assertEqual(Marge, sqlite3:next(prepared, Ref1)), ?assertEqual(done, sqlite3:next(prepared, Ref1)), ?assertEqual(ok, sqlite3:finalize(prepared, Ref1)), ?assertMatch({error, _}, sqlite3:next(prepared, Ref1)), ?assertEqual(ok, sqlite3:reset(prepared, Ref2)), ?assertEqual(ok, sqlite3:bind(prepared, Ref2, ["marge"])), ?assertEqual(Marge, sqlite3:next(prepared, Ref2)), ?assertEqual(done, sqlite3:next(prepared, Ref2)), ?assertEqual(ok, sqlite3:finalize(prepared, Ref2)), sqlite3:close(prepared). script_test() -> sqlite3:open(script, [in_memory]), Script = string:join( ["CREATE TABLE person(", "id INTEGER", ");", " ", "-- Comment", "INSERT INTO person (id) VALUES (1);", "INSERT INTO person (id) VALUES (2);", " " ], "\n"), ?assertEqual( [ok, ok, ok], sqlite3:sql_exec_script(script, Script)), ?assertEqual( [{columns,["id"]},{rows,[{1},{2}]}], sqlite3:read_all(script, person)), Script2 = "select * from person; update person set id=3 where id=2", ?assertEqual( [[{columns,["id"]},{rows,[{1},{2}]}], ok], sqlite3:sql_exec_script(script, Script2)), ?assertEqual( [{columns,["id"]},{rows,[{1},{3}]}], sqlite3:read_all(script, person)), BadScript = string:join( ["CREATE TABLE person2(", "id INTEGER", ");", " ", "-- Comment", "SYNTAX ERROR;", "INSERT INTO person (id) VALUES (2);", " " ], "\n"), ?WARN_ERROR_MESSAGE, ?assertEqual( [ok, {error, 1, "near \"SYNTAX\": syntax error"}], sqlite3:sql_exec_script(script, BadScript)), sqlite3:close(script). large_offset() -> drop_table_if_exists(ct, large_offset), ok = sqlite3:create_table(ct, large_offset, [{id, integer}]), ?assertMatch( [{columns, ["id"]}, {rows, []}, {error, 20, _}], sqlite3:sql_exec(ct, "select * from large_offset limit 1 offset 9223372036854775808")). issue13() -> drop_table_if_exists(ct, issue13), ok = sqlite3:create_table(ct, issue13, [{foo, integer}]), sqlite3:write_many(ct, issue13, [[{foo, X}] || X <- [-1, 0, 127, 128, 255, 256]]), ?assertEqual( [{columns, ["foo"]}, {rows, [{255}, {256}]}], sqlite3:sql_exec(ct, "select foo from issue13 where foo > 128;")), ?assertEqual( [{columns, ["foo"]}, {rows, [{255}, {256}]}], sqlite3:sql_exec(ct, "select foo from issue13 where foo > ?;", [128.0])), ?assertEqual( [{columns, ["foo"]}, {rows, [{255}, {256}]}], sqlite3:sql_exec(ct, "select foo from issue13 where foo > ?;", [128])). enable_load_extension() -> ?assertEqual(ok, sqlite3:enable_load_extension(ct, 1)). issue23() -> sqlite3:open(issue23, [in_memory]), ok = sqlite3:create_table(issue23, issue23, [{issue23, integer}]), SingleStmt = "SELECT * FROM issue23;", SingleStmtResult = [{columns, ["issue23"]}, {rows, []}], ScriptResult = sqlite3:sql_exec_script(issue23,[SingleStmt ++ SingleStmt]), ?assertEqual([SingleStmtResult, SingleStmtResult], ScriptResult), sqlite3:close(issue23). % create, read, update, delete %%==================================================================== %% Internal functions %%====================================================================