Busy with creating an edbc lib

This commit is contained in:
Maas-Maarten Zeeman
2011-12-28 21:31:14 +01:00
parent fc2d08f50f
commit 719197313a
6 changed files with 182 additions and 62 deletions

View File

@@ -95,6 +95,14 @@ make_error_tuple(ErlNifEnv *env, const char *reason)
return enif_make_tuple2(env, make_atom(env, "error"), make_atom(env, reason)); return enif_make_tuple2(env, make_atom(env, "error"), make_atom(env, reason));
} }
static ERL_NIF_TERM
make_sqlite3_error_tuple(ErlNifEnv *env, const char *msg)
{
return enif_make_tuple2(env, make_atom(env, "error"),
enif_make_tuple2(env, make_atom(env, "sqlite3_error"),
enif_make_string(env, msg, ERL_NIF_LATIN1)));
}
static void static void
command_destroy(void *obj) command_destroy(void *obj)
{ {
@@ -185,7 +193,7 @@ do_open(ErlNifEnv *env, esqlite_connection *db, const ERL_NIF_TERM arg)
*/ */
rc = sqlite3_open(filename, &db->db); rc = sqlite3_open(filename, &db->db);
if(rc != SQLITE_OK) { if(rc != SQLITE_OK) {
error = make_error_tuple(env, sqlite3_errmsg(db->db)); error = make_sqlite3_error_tuple(env, sqlite3_errmsg(db->db));
sqlite3_close(db->db); sqlite3_close(db->db);
db->db = NULL; db->db = NULL;
@@ -207,7 +215,7 @@ do_exec(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg)
rc = sqlite3_exec(conn->db, (char *) bin.data, NULL, NULL, NULL); rc = sqlite3_exec(conn->db, (char *) bin.data, NULL, NULL, NULL);
if(rc != SQLITE_OK) if(rc != SQLITE_OK)
return make_error_tuple(env, sqlite3_errmsg(conn->db)); return make_sqlite3_error_tuple(env, sqlite3_errmsg(conn->db));
return make_atom(env, "ok"); return make_atom(env, "ok");
} }
@@ -231,7 +239,7 @@ do_prepare(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg)
rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail); rc = sqlite3_prepare_v2(conn->db, (char *) bin.data, bin.size, &(stmt->statement), &tail);
if(rc != SQLITE_OK) if(rc != SQLITE_OK)
return make_error_tuple(env, sqlite3_errmsg(conn->db)); return make_sqlite3_error_tuple(env, sqlite3_errmsg(conn->db));
enif_keep_resource(conn); enif_keep_resource(conn);
stmt->connection = conn; stmt->connection = conn;
@@ -299,7 +307,7 @@ do_bind(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg)
if(r == -1) if(r == -1)
return make_error_tuple(env, "wrong_type"); return make_error_tuple(env, "wrong_type");
if(r != SQLITE_OK) if(r != SQLITE_OK)
return make_error_tuple(env, sqlite3_errmsg(db)); return make_sqlite3_error_tuple(env, sqlite3_errmsg(db));
list = tail; list = tail;
} }
@@ -413,7 +421,7 @@ do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg)
rc = sqlite3_close(conn->db); rc = sqlite3_close(conn->db);
if(rc != SQLITE_OK) if(rc != SQLITE_OK)
return make_error_tuple(env, sqlite3_errmsg(conn->db)); return make_sqlite3_error_tuple(env, sqlite3_errmsg(conn->db));
conn->db = NULL; conn->db = NULL;
return make_atom(env, "ok"); return make_atom(env, "ok");

4
src/edbc.hrl Normal file
View File

@@ -0,0 +1,4 @@
%%
%%
-record(edbc_column_info, {name, type, default, notnull, pk}).

View File

@@ -26,10 +26,12 @@
prepare/2, prepare/3, prepare/2, prepare/3,
step/1, step/2, step/1, step/2,
bind/2, bind/3, bind/2, bind/3,
fetchone/1,
fetchall/1,
column_names/1, column_names/2, column_names/1, column_names/2,
close/1, close/2]). close/1, close/2]).
-export([q/2, map/3, foreach/3]). -export([q/2, q/3, map/3, foreach/3]).
-define(DEFAULT_TIMEOUT, infinity). -define(DEFAULT_TIMEOUT, infinity).
@@ -56,8 +58,16 @@ open(Filename, Timeout) ->
%% @doc Execute a sql statement, returns a list with tuples. %% @doc Execute a sql statement, returns a list with tuples.
q(Sql, Connection) -> q(Sql, Connection) ->
q(Sql, [], Connection).
%% @doc Execute statement, bind args and return a list with tuples as result.
q(Sql, [], Connection) ->
{ok, Statement} = prepare(Sql, Connection), {ok, Statement} = prepare(Sql, Connection),
do_steps(Statement). fetchall(Statement);
q(Sql, Args, Connection) ->
{ok, Statement} = prepare(Sql, Connection),
ok = bind(Statement, Args),
fetchall(Statement).
%% %%
map(F, Sql, Connection) -> map(F, Sql, Connection) ->
@@ -102,12 +112,20 @@ map_s(F, Statement) when is_function(F, 2) ->
end. end.
%% %%
do_steps(Statement) -> fetchone(Statement) ->
case try_step(Statement, 0) of
'$done' -> ok;
Row when is_tuple(Row) ->
Row
end.
%%
fetchall(Statement) ->
case try_step(Statement, 0) of case try_step(Statement, 0) of
'$done' -> '$done' ->
[]; [];
Row when is_tuple(Row) -> Row when is_tuple(Row) ->
[Row | do_steps(Statement)] [Row | fetchall(Statement)]
end. end.
%% Try the step, when the database is busy, %% Try the step, when the database is busy,

View File

@@ -26,6 +26,7 @@
exec/4, exec/4,
prepare/4, prepare/4,
step/3, step/3,
finalize/3,
bind/4, bind/4,
column_names/3, column_names/3,
close/3 close/3
@@ -75,6 +76,12 @@ prepare(_Db, _Ref, _Dest, _Sql) ->
step(_Stmt, _Ref, _Dest) -> step(_Stmt, _Ref, _Dest) ->
exit(nif_library_not_loaded). exit(nif_library_not_loaded).
%% @doc
%%
%%
finalize(_Stmt, _Ref, _Dest) ->
exit(nif_library_not_loaded).
%% @doc Bind parameters to a prepared statement. %% @doc Bind parameters to a prepared statement.
%% %%
%% @spec bind(statement(), reference(), pid(), []) -> ok | {error, message()} %% @spec bind(statement(), reference(), pid(), []) -> ok | {error, message()}

View File

@@ -1,41 +1,78 @@
%% %%
%% Generic database interface. Sort of... %% Generic database interface. Sort of...
%% %%
%% Inspired by python's db api
%% %%
-module(gen_db). -module(gen_db).
-export([behaviour_info/1]). -export([behaviour_info/1]).
-export([open/2, execute/2, execute/3, close/1]). -export([open/2, close/1, run/2, run/3, execute/2, execute/3, commit/1, rollback/1, tables/1, describe_table/2, column_names/2]).
-include("edbc.hrl").
behaviour_info(callbacks) -> behaviour_info(callbacks) ->
[{handle_open, 1}, [{open, 1},
{handle_execute, 3}, {close, 1},
{handle_close, 1}]; {commit, 1},
{rollback, 1},
{run, 3},
{execute, 3},
{tables, 1},
{describe_table, 2}];
behaviour_info(_Other) -> behaviour_info(_Other) ->
undefined. undefined.
-record(gen_connection, {module, connection}). -record(connection, {driver, connection_data}).
%% @doc Open a connection to a new database %% @doc Open a connection to a new database
%% %%
open(Module, ModuleArgs) -> open(Driver, Args) ->
{ok, Conn} = Module:handle_open(ModuleArgs), {ok, ConnectionData} = Driver:open(Args),
{ok, #gen_connection{module=Module, connection=Conn}}. {ok, #connection{driver=Driver, connection_data=ConnectionData}}.
%% @doc Prepare and execute a database operation
%%
execute(Operation, Connection) ->
execute(Operation, [], Connection).
%% @doc Prepare and execute a database operation
%%
execute(Operation, Args, #gen_connection{module=Module, connection=Connection}) ->
Module:handle_execute(Operation, Args, Connection).
%% @doc Close a database connection. %% @doc Close a database connection.
%% %%
close(#gen_connection{module=Module, connection=Connection}) -> close(#connection{driver=Driver, connection_data=Data}) ->
Module:handle_close(Connection). Driver:close(Data).
%% @doc Commit all changes
commit(#connection{driver=Driver, connection_data=Data}) ->
Driver:commit(Data).
%% @doc Rollback all changes
rollback(#connection{driver=Driver, connection_data=Data}) ->
Driver:rollback(Data).
%% @doc Return a list with tablenames...
tables(#connection{driver=Driver, connection_data=Data}) ->
Driver:tables(Data).
%% @doc
describe_table(TableName, #connection{driver=Driver, connection_data=Data}) ->
Driver:describe_table(TableName, Data).
%% @doc
column_names(TableName, Connection) ->
[Info#edbc_column_info.name || Info <- describe_table(TableName, Connection) ].
%% @doc Execute the statement, without returning results.
run(Sql, Connection) ->
run(Sql, [], Connection).
run(Sql, Params, #connection{driver=Driver, connection_data=Data}) ->
Driver:run(Sql, Params, Data).
%% @doc Execute the statement, return the result
execute(Sql, Connection) ->
execute(Sql, [], Connection).
execute(Sql, Params, #connection{driver=Driver, connection_data=Data}) ->
Driver:execute(Sql, Params, Data).

View File

@@ -4,46 +4,92 @@
-module(sqlite). -module(sqlite).
-include("edbc.hrl").
-behaviour(gen_db). -behaviour(gen_db).
-export([handle_open/1, handle_execute/3, handle_close/1]). -export([open/1, run/3, execute/3, close/1, commit/1, rollback/1, tables/1, describe_table/2]).
%% @doc Open a database connection %% @doc Open a database connection
%% %%
handle_open(DatabaseName) -> open([DatabaseName]) ->
esqlite3:open(DatabaseName). {ok, C} = esqlite3:open(DatabaseName),
ok = esqlite3:exec(<<"BEGIN TRANSACTION;">>, C),
%% @doc Execute a query and return the results {ok, C}.
%%
handle_execute(Operation, Args, Connection) ->
{ok, Stmt} = esqlite3:prepare(Connection, Operation),
ok = esqlite3:bind(Stmt, Args),
Answer = execute(Stmt),
%% TODO Finalize the statement.
Answer.
%% @doc Close the connection %% @doc Close the connection
%% %%
handle_close(Connection) -> close(Connection) ->
esqlite:close(Connection). ok = esqlite3:close(Connection).
%% @doc %% @doc
%% %%
execute(Statement) -> run(Sql, [], Connection) ->
execute(Statement, [], 0). case esqlite3:exec(Sql, Connection) of
{error, Error} -> {error, ?MODULE, Error};
%% @doc ok -> ok
%% end;
execute(_Statement, _Acc, Tries) when Tries > 5 -> run(Sql, Args, Connection) ->
throw(too_many_tries); case esqlite3:prepare(Sql, Connection) of
execute(Statement, Acc, Tries) -> {error, Error} ->
case esqlite3:step(Statement) of {error, ?MODULE, Error};
'$done' -> {ok, Stmt} ->
lists:reverse(Acc); case esqlite3:bind(Stmt, Args) of
'$busy' -> {error, Error} -> {error, ?MODULE, Error};
timer:sleep(100), %% This is a bit lame... there is a trigger api for this. ok ->
execute(Statement, Acc, Tries + 1); case esqlite3:fetchone(Stmt) of
V when is_tuple(V) -> {error, Error} -> {error, ?MODULE, Error};
execute(Statement, [V | Acc], 0) _ -> ok
end
end
end. end.
%% @doc Execute a query and return the results
%%
execute(Sql, [], Connection) ->
case esqlite3:prepare(Sql, Connection) of
{error, Error} ->
{error, ?MODULE, Error};
{ok, Stmt} ->
{ok, esqlite3:column_names(Stmt), esqlite3:fetchall(Stmt)}
end;
execute(Sql, Args, Connection) ->
case esqlite3:prepare(Sql, Connection) of
{error, Error} ->
{error, ?MODULE, Error};
{ok, Stmt} ->
case esqlite3:bind(Stmt, Args) of
ok ->
Names = esqlite3:column_names(Stmt),
Result = esqlite3:fetchall(Stmt),
{ok, Names, Result};
{error, Error} ->
{error, ?MODULE, Error}
end
end.
%%
commit(Connection) ->
ok = run(<<"COMMIT;">>, [], Connection).
%%
rollback(Connection) ->
ok = run(<<"ROLLBACK;">>, [], Connection).
%%
tables(Connection) ->
esqlite3:map(fun({TableName}) -> list_to_atom(TableName) end,
<<"SELECT name FROM sqlite_master WHERE type='table' ORDER by name;">>, Connection).
%%
describe_table(TableName, Connection) when is_atom(TableName) ->
esqlite3:map(fun({_Cid, ColumnName, ColumnType, NotNull, Default, PrimaryKey}) ->
#edbc_column_info{name=list_to_atom(ColumnName),
type=ColumnType,
default=Default,
notnull=NotNull =/= 0,
pk=PrimaryKey =/= 0}
end,
[<<"PRAGMA table_info('">>, atom_to_list(TableName), <<"');">>], Connection).