diff --git a/.gitignore b/.gitignore index 175fca5..cf58051 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.tmproj c_src/system test/logs +test/test.cover diff --git a/ebin/bdberl.app b/ebin/bdberl.app index 8845fd3..f7f97fb 100644 --- a/ebin/bdberl.app +++ b/ebin/bdberl.app @@ -1,7 +1,7 @@ {application, bdberl, [{description, "Berkeley DB Erlang Driver"}, {vsn, "1"}, - {modules, [ bdberl_port ]}, + {modules, [ bdberl_port, bdberl_db ]}, {registered, []}, {applications, [kernel, stdlib]}, diff --git a/src/bdberl_db.erl b/src/bdberl_db.erl new file mode 100644 index 0000000..93c3417 --- /dev/null +++ b/src/bdberl_db.erl @@ -0,0 +1,68 @@ +%% ------------------------------------------------------------------- +%% +%% bdberl: API Interface +%% Copyright (c) 2008 The Hive. All rights reserved. +%% +%% ------------------------------------------------------------------- +-module(bdberl_db). + + +-export([open/3, open/4, + close/1, close/2, + put/3, put/4, + get/2, get/3, + transaction/2, + update/3]). + + +open(Port, Name, Type) -> + open(Port, Name, Type, [create]). + +open(Port, Name, Type, Opts) -> + case bdberl_port:open_database(Port, Name, Type, Opts) of + {ok, DbRef} -> {ok, {db, Port, DbRef}}; + {error, Reason} -> {error, Reason} + end. + +close(Db) -> + close(Db, []). + +close({db, Port, DbRef}, Opts) -> + bdberl_port:close_database(Port, DbRef, Opts). + +put(Db, Key, Value) -> + put(Db, Key, Value, []). + +put({db, Port, DbRef}, Key, Value, Opts) -> + bdberl_port:put(Port, DbRef, Key, Value, Opts). + +get(Db, Key) -> + get(Db, Key, []). + +get({db, Port, DbRef}, Key, Opts) -> + bdberl_port:get(Port, DbRef, Key, Opts). + + +transaction({db, Port, _DbRef}, Fun) -> + bdberl_port:txn_begin(Port), + try Fun() of + abort -> + bdberl_port:txn_abort(Port), + {error, transaction_aborted}; + Value -> + bdberl_port:txn_commit(Port), + {ok, Value} + catch + _ : Reason -> + bdberl_port:txn_abort(Port), + {error, {transaction_failed, Reason}} + end. + +update(Db, Key, Fun) -> + F = fun() -> + {ok, Value} = get(Db, Key, [rmw]), + NewValue = Fun(Key, Value), + ok = put(Db, Key, NewValue), + NewValue + end, + transaction(Db, F). diff --git a/src/bdberl_port.erl b/src/bdberl_port.erl index d53b5e3..b701f45 100644 --- a/src/bdberl_port.erl +++ b/src/bdberl_port.erl @@ -8,7 +8,7 @@ -export([new/0, open_database/3, open_database/4, - close_database/2, + close_database/2, close_database/3, txn_begin/1, txn_begin/2, txn_commit/1, txn_commit/2, txn_abort/1, get_cache_size/1, set_cache_size/4, diff --git a/test/cover.spec b/test/cover.spec new file mode 100644 index 0000000..e6d48cb --- /dev/null +++ b/test/cover.spec @@ -0,0 +1,3 @@ +{export, ["test/test.cover"]}. +{level, details}. +{incl_dirs_r, ["ebin"]}. diff --git a/test/db_api_SUITE.erl b/test/db_api_SUITE.erl new file mode 100644 index 0000000..96cd7f7 --- /dev/null +++ b/test/db_api_SUITE.erl @@ -0,0 +1,85 @@ +%% ------------------------------------------------------------------- +%% +%% bdberl: DB API Tests +%% Copyright (c) 2008 The Hive. All rights reserved. +%% +%% ------------------------------------------------------------------- +-module(db_api_SUITE). + +-compile(export_all). + +-include_lib("ct.hrl"). + +all() -> + [open_should_create_database_if_none_exists, + get_should_fail_when_getting_a_nonexistant_record, + get_should_return_a_value_when_getting_a_valid_record, + transaction_should_commit_on_success, + transaction_should_abort_on_exception, + transaction_should_abort_on_user_abort, + update_should_save_value_if_successful]. + + +init_per_testcase(_TestCase, Config) -> + {ok, Port} = bdberl_port:new(), + {ok, Db} = bdberl_db:open(Port, "api_test.db", btree, [create, exclusive]), + [{port, Port},{db, Db}|Config]. + +end_per_testcase(_TestCase, Config) -> + ok = bdberl_db:close(?config(db, Config)), + true = port_close(?config(port, Config)), + ok = file:delete("api_test.db"). + + +open_should_create_database_if_none_exists(_Config) -> + true = filelib:is_file("api_test.db"). + +get_should_fail_when_getting_a_nonexistant_record(Config) -> + not_found = bdberl_db:get(?config(db, Config), bad_key). + +get_should_return_a_value_when_getting_a_valid_record(Config) -> + Db = ?config(db, Config), + ok = bdberl_db:put(Db, mykey, avalue), + {ok, avalue} = bdberl_db:get(Db, mykey). + +transaction_should_commit_on_success(Config) -> + Db = ?config(db, Config), + F = fun() -> bdberl_db:put(Db, mykey, avalue) end, + {ok, ok} = bdberl_db:transaction(Db, F), + {ok, avalue} = bdberl_db:get(Db, mykey). + +transaction_should_abort_on_exception(Config) -> + Db = ?config(db, Config), + + F = fun() -> + bdberl_db:put(Db, mykey, should_not_see_this), + throw(testing) + end, + + {error, {transaction_failed, testing}} = bdberl_db:transaction(Db, F), + not_found = bdberl_db:get(Db, mykey). + +transaction_should_abort_on_user_abort(Config) -> + Db = ?config(db, Config), + + F = fun() -> + bdberl_db:put(Db, mykey, should_not_see_this), + abort + end, + + {error, transaction_aborted} = bdberl_db:transaction(Db, F), + not_found = bdberl_db:get(Db, mykey). + +update_should_save_value_if_successful(Config) -> + Db = ?config(db, Config), + ok = bdberl_db:put(Db, mykey, avalue), + + F = fun(Key, Value) -> + mykey = Key, + avalue = Value, + newvalue + end, + + {ok, newvalue} = bdberl_db:update(Db, mykey, F), + {ok, newvalue} = bdberl_db:get(Db, mykey). +