diff --git a/src/esqlite3.erl b/src/esqlite3.erl index 76fdd01..4f2d922 100644 --- a/src/esqlite3.erl +++ b/src/esqlite3.erl @@ -28,6 +28,8 @@ bind/2, bind/3, close/1, close/2]). +-export([q/2, map/3, foreach/3]). + -define(DEFAULT_TIMEOUT, infinity). %% @doc Opens a sqlite3 database mentioned in Filename. @@ -40,43 +42,95 @@ open(Filename) -> %% %% @spec open(string(), timeout()) -> {ok, connection()} | {error, error_message()} open(Filename, Timeout) -> - {ok, Db} = esqlite3_nif:start(), + {ok, Connection} = esqlite3_nif:start(), Ref = make_ref(), - ok = esqlite3_nif:open(Db, Ref, self(), Filename), + ok = esqlite3_nif:open(Connection, Ref, self(), Filename), case receive_answer(Ref, Timeout) of ok -> - {ok, Db}; + {ok, Connection}; Other -> {error, Other} end. -%% @doc Execute Sql statement +%% @doc Execute a sql statement, returns a list with tuples. +q(Sql, Connection) -> + {ok, Statement} = prepare(Sql, Connection), + do_steps(Statement). + +%% +map(F, Sql, Connection) -> + {ok, Statement} = prepare(Sql, Connection), + map_s(F, Statement). + +%% +foreach(F, Sql, Connection) -> + {ok, Statement} = prepare(Sql, Connection), + foreach_s(F, Statement). + %% -%% @spec exec(connection(), iolist()) -> integer() | {error, error_message()} -exec(Db, Sql) -> - exec(Db, Sql, ?DEFAULT_TIMEOUT). +foreach_s(F, Statement) -> + case try_step(Statement, 0) of + '$done' -> ok; + Row when is_tuple(Row) -> + F(Row), + foreach_s(F, Statement) + end. + +%% +map_s(F, Statement) -> + case try_step(Statement, 0) of + '$done' -> + []; + Row -> + [F(Row) | map_s(F, Statement)] + end. + +%% +do_steps(Statement) -> + case try_step(Statement, 0) of + '$done' -> + []; + Row when is_tuple(Row) -> + [Row | do_steps(Statement)] + end. + +try_step(_Statement, Tries) when Tries > 5 -> + throw(too_many_tries); +try_step(Statement, Tries) -> + case esqlite3:step(Statement) of + '$busy' -> + try_step(Statement, Tries + 1); + Something -> + Something + end. + +%% @doc Execute Sql statement, returns the number of affected rows. +%% +%% @spec exec(iolist(), connection()) -> integer() | {error, error_message()} +exec(Sql, Connection) -> + exec(Sql, Connection, ?DEFAULT_TIMEOUT). %% @doc Execute %% -%% @spec exec(connection(), iolist(), timeout()) -> integer() | {error, error_message()} -exec(Db, Sql, Timeout) -> +%% @spec exec(iolist(), connection(), timeout()) -> integer() | {error, error_message()} +exec(Sql, Connection, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:exec(Db, Ref, self(), add_eos(Sql)), + ok = esqlite3_nif:exec(Connection, Ref, self(), add_eos(Sql)), receive_answer(Ref, Timeout). %% @doc Prepare a statement %% -%% @spec prepare(connection(), iolist()) -> {ok, prepared_statement()} | {error, error_message()} -prepare(Db, Sql) -> - prepare(Db, Sql, ?DEFAULT_TIMEOUT). +%% @spec prepare(iolost(), connection()) -> {ok, prepared_statement()} | {error, error_message()} +prepare(Sql, Connection) -> + prepare(Sql, Connection, ?DEFAULT_TIMEOUT). %% @doc %% -%% @spec(connection(), iolist(), timeout()) -> {ok, prepared_statement()} | {error, error_message()} -prepare(Db, Sql, Timeout) -> +%% @spec(iolist(), connection(), timeout()) -> {ok, prepared_statement()} | {error, error_message()} +prepare(Sql, Connection, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:prepare(Db, Ref, self(), add_eos(Sql)), + ok = esqlite3_nif:prepare(Connection, Ref, self(), add_eos(Sql)), receive_answer(Ref, Timeout). %% @doc Step @@ -110,21 +164,20 @@ bind(Stmt, Args, Timeout) -> %% @doc Close the database %% %% @spec close(connection()) -> ok | {error, error_message()} -close(Db) -> - close(Db, ?DEFAULT_TIMEOUT). +close(Connection) -> + close(Connection, ?DEFAULT_TIMEOUT). %% @doc Close the database %% %% @spec close(connection(), integer()) -> ok | {error, error_message()} -close(Db, Timeout) -> +close(Connection, Timeout) -> Ref = make_ref(), - ok = esqlite3_nif:close(Db, Ref, self()), + ok = esqlite3_nif:close(Connection, Ref, self()), receive_answer(Ref, Timeout). %% Internal functions - -add_eos(String) when is_list(String) -> - [String, 0]. +add_eos(IoList) -> + [IoList, 0]. receive_answer(Ref, Timeout) -> receive diff --git a/test/esqlite_test.erl b/test/esqlite_test.erl index 07602f1..3a91925 100644 --- a/test/esqlite_test.erl +++ b/test/esqlite_test.erl @@ -22,44 +22,45 @@ open_multiple_different_databases_test() -> simple_query_test() -> {ok, Db} = esqlite3:open(":memory:"), - ok = esqlite3:exec(Db, "begin;"), - ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello1\"", ",", "10" ");"]), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello2\"", ",", "11" ");"]), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello3\"", ",", "12" ");"]), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello4\"", ",", "13" ");"]), - ok = esqlite3:exec(Db, "commit;"), - ok = esqlite3:exec(Db, "select * from test_table;"), + 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), + ok = esqlite3:exec("select * from test_table;", Db), ok. prepare_test() -> {ok, Db} = esqlite3:open(":memory:"), - esqlite3:exec(Db, "begin;"), - esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), - {ok, Statement} = esqlite3:prepare(Db, "insert into test_table values(\"one\", 2)"), + 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 = esqlite3:exec(Db, ["insert into test_table values(", "\"hello4\"", ",", "13" ");"]), - {ok, St2} = esqlite3:prepare(Db, "select * from test_table order by two"), - [{"one", 2}, {"hello4", 13}] = exec(St2), - esqlite3:exec(Db, "commit;"), + 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(Db, "begin;"), - ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello1\"", ",", "10" ");"]), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello2\"", ",", "11" ");"]), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello3\"", ",", "12" ");"]), - ok = esqlite3:exec(Db, ["insert into test_table values(", "\"hello4\"", ",", "13" ");"]), - ok = esqlite3:exec(Db, "commit;"), + 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), %% Create a prepared statement - {ok, Statement} = esqlite3:prepare(Db, "insert into test_table values(?1, ?2)"), + {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]), @@ -67,87 +68,20 @@ bind_test() -> esqlite3:bind(Statement, [<<"five">>, 6]), esqlite3:step(Statement), - [{"one", 2}] = q(Db, "select * from test_table where two = '2'"), - [{"three", 4}] = q(Db, "select * from test_table where two = 4"), - [{<<"five">>, 6}] = q(Db, "select * from test_table where two = 6"), + [{"one", 2}] = esqlite3:q("select * from test_table where two = '2'", Db), + [{"three", 4}] = esqlite3:q("select * from test_table where two = 4", Db), + [{<<"five">>, 6}] = esqlite3:q("select * from test_table where two = 6", Db), ok. -gen_db_test() -> - {ok, Conn} = gen_db:open(sqlite, ":memory:"), - [] = gen_db:execute("create table some_shit(hole_one varchar(10), hole_two int);", [], Conn), - [] = gen_db:execute("insert into some_shit values('dung', 100);", Conn), - [] = gen_db:execute("insert into some_shit values(?, ?);", ["manure", 1000], Conn), - ok. +%%gen_db_test() -> + %% {ok, Conn} = gen_db:open(sqlite, ":memory:"), + %% [] = gen_db:execute("create table some_shit(hole_one varchar(10), hole_two int);", [], Conn), + %% [] = gen_db:execute("insert into some_shit values('dung', 100);", Conn), + %% [] = gen_db:execute("insert into some_shit values(?, ?);", ["manure", 1000], Conn), + %% ok. -%% Handy functions... -%% -q(Connection, Sql) -> - {ok, Statement} = esqlite3:prepare(Connection, Sql), - exec(Statement). - -q(Connection, Sql, []) -> - q(Connection, Sql); -q(Connection, Sql, Args) -> - {ok, Statement} = esqlite3:prepare(Connection, Sql), - esqlite3:bind(Statement, Args), - exec(Statement). - - -exec(Statement) -> - exec(Statement, [], 0). - -exec(_Statement, _Acc, Tries) when Tries > 5 -> - throw(too_many_tries); -exec(Statement, Acc, Tries) -> - case esqlite3:step(Statement) of - '$done' -> - lists:reverse(Acc); - '$busy' -> - timer:sleep(100), - exec(Statement, Acc, Tries + 1); - V when is_tuple(V) -> - exec(Statement, [V | Acc], 0) - end. - -%% esqlite:bind(Statement, ":one", "hello"), -%% esqlite:bind(Statement, ":two", 11), - -%% %% of -%% esqlite:bind(Statement, ["hello", 11]), - -%% {ok, []} = esqlite:execute(Statement), - -%% esqlite:exec(Db, "commit;"), - -%% - -% api... - -%% q(Sql, Connection) -> -%% ok. - -%% q(Sql, Args, Connection) -> -%% ok. - - -%% -%% options -- -%% [{sync, bool()} -%% -- asynchronous or synchronous. Default true -%% {receiver, (pid()|function/1|{M, F, A}}, -%% -- for asynchronous requests, to send the result to. -%% {stream, pid()}, -%% -- instead of receiving all rows at once, send them one by one. -%% {..}, -%% ] -%% -%% return -%% {ok, Result} -- result kan dan headers, rows of request-id (voor async) -%% {error, ... -%% q(Sql, Args, Options, Connection) -> -%% ok.