Busy with creating an edbc lib
This commit is contained in:
@@ -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
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,
|
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,
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
106
src/sqlite.erl
106
src/sqlite.erl
@@ -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).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user