Busy with creating an edbc lib
This commit is contained in:
@@ -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
4
src/edbc.hrl
Normal file
@@ -0,0 +1,4 @@
|
||||
%%
|
||||
%%
|
||||
|
||||
-record(edbc_column_info, {name, type, default, notnull, pk}).
|
||||
@@ -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,
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
108
src/sqlite.erl
108
src/sqlite.erl
@@ -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).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user