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

@@ -90,11 +90,19 @@ make_ok_tuple(ErlNifEnv *env, ERL_NIF_TERM value)
}
static ERL_NIF_TERM
make_error_tuple(ErlNifEnv *env, const char *reason)
make_error_tuple(ErlNifEnv *env, const char *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
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);
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);
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);
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");
}
@@ -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);
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);
stmt->connection = conn;
@@ -299,7 +307,7 @@ do_bind(ErlNifEnv *env, sqlite3 *db, sqlite3_stmt *stmt, const ERL_NIF_TERM arg)
if(r == -1)
return make_error_tuple(env, "wrong_type");
if(r != SQLITE_OK)
return make_error_tuple(env, sqlite3_errmsg(db));
return make_sqlite3_error_tuple(env, sqlite3_errmsg(db));
list = tail;
}
@@ -413,7 +421,7 @@ do_close(ErlNifEnv *env, esqlite_connection *conn, const ERL_NIF_TERM arg)
rc = sqlite3_close(conn->db);
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;
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,
step/1, step/2,
bind/2, bind/3,
fetchone/1,
fetchall/1,
column_names/1, column_names/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).
@@ -56,8 +58,16 @@ open(Filename, Timeout) ->
%% @doc Execute a sql statement, returns a list with tuples.
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),
do_steps(Statement).
fetchall(Statement);
q(Sql, Args, Connection) ->
{ok, Statement} = prepare(Sql, Connection),
ok = bind(Statement, Args),
fetchall(Statement).
%%
map(F, Sql, Connection) ->
@@ -101,13 +111,21 @@ map_s(F, Statement) when is_function(F, 2) ->
[F(ColumnNames, Row) | map_s(F, Statement)]
end.
%%
fetchone(Statement) ->
case try_step(Statement, 0) of
'$done' -> ok;
Row when is_tuple(Row) ->
Row
end.
%%
do_steps(Statement) ->
fetchall(Statement) ->
case try_step(Statement, 0) of
'$done' ->
[];
Row when is_tuple(Row) ->
[Row | do_steps(Statement)]
[Row | fetchall(Statement)]
end.
%% Try the step, when the database is busy,

View File

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

View File

@@ -1,41 +1,78 @@
%%
%% Generic database interface. Sort of...
%%
%% Inspired by python's db api
%%
-module(gen_db).
-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) ->
[{handle_open, 1},
{handle_execute, 3},
{handle_close, 1}];
[{open, 1},
{close, 1},
{commit, 1},
{rollback, 1},
{run, 3},
{execute, 3},
{tables, 1},
{describe_table, 2}];
behaviour_info(_Other) ->
undefined.
-record(gen_connection, {module, connection}).
-record(connection, {driver, connection_data}).
%% @doc Open a connection to a new database
%%
open(Module, ModuleArgs) ->
{ok, Conn} = Module:handle_open(ModuleArgs),
{ok, #gen_connection{module=Module, connection=Conn}}.
%% @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).
open(Driver, Args) ->
{ok, ConnectionData} = Driver:open(Args),
{ok, #connection{driver=Driver, connection_data=ConnectionData}}.
%% @doc Close a database connection.
%%
close(#gen_connection{module=Module, connection=Connection}) ->
Module:handle_close(Connection).
close(#connection{driver=Driver, connection_data=Data}) ->
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).
-include("edbc.hrl").
-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
%%
handle_open(DatabaseName) ->
esqlite3:open(DatabaseName).
%% @doc Execute a query and return the results
%%
handle_execute(Operation, Args, Connection) ->
{ok, Stmt} = esqlite3:prepare(Connection, Operation),
ok = esqlite3:bind(Stmt, Args),
Answer = execute(Stmt),
%% TODO Finalize the statement.
Answer.
open([DatabaseName]) ->
{ok, C} = esqlite3:open(DatabaseName),
ok = esqlite3:exec(<<"BEGIN TRANSACTION;">>, C),
{ok, C}.
%% @doc Close the connection
%%
handle_close(Connection) ->
esqlite:close(Connection).
close(Connection) ->
ok = esqlite3:close(Connection).
%% @doc
%% @doc
%%
execute(Statement) ->
execute(Statement, [], 0).
%% @doc
%%
execute(_Statement, _Acc, Tries) when Tries > 5 ->
throw(too_many_tries);
execute(Statement, Acc, Tries) ->
case esqlite3:step(Statement) of
'$done' ->
lists:reverse(Acc);
'$busy' ->
timer:sleep(100), %% This is a bit lame... there is a trigger api for this.
execute(Statement, Acc, Tries + 1);
V when is_tuple(V) ->
execute(Statement, [V | Acc], 0)
run(Sql, [], Connection) ->
case esqlite3:exec(Sql, Connection) of
{error, Error} -> {error, ?MODULE, Error};
ok -> ok
end;
run(Sql, Args, Connection) ->
case esqlite3:prepare(Sql, Connection) of
{error, Error} ->
{error, ?MODULE, Error};
{ok, Stmt} ->
case esqlite3:bind(Stmt, Args) of
{error, Error} -> {error, ?MODULE, Error};
ok ->
case esqlite3:fetchone(Stmt) of
{error, Error} -> {error, ?MODULE, Error};
_ -> ok
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).