From 5f2b99554aaec281ca3e5a241eef9644bf04cca6 Mon Sep 17 00:00:00 2001 From: Phillip Toland Date: Fri, 12 Dec 2008 12:39:51 -0600 Subject: [PATCH] Replace bdberl_db with a merged bdberl API. --- ebin/bdberl.app | 2 +- src/bdberl.erl | 282 ++++++++++++++++++++ src/bdberl_db.erl | 68 ----- test/{db_api_SUITE.erl => bdberl_SUITE.erl} | 47 ++-- 4 files changed, 309 insertions(+), 90 deletions(-) create mode 100644 src/bdberl.erl delete mode 100644 src/bdberl_db.erl rename test/{db_api_SUITE.erl => bdberl_SUITE.erl} (59%) diff --git a/ebin/bdberl.app b/ebin/bdberl.app index f7f97fb..922596f 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, bdberl_db ]}, + {modules, [ bdberl_port, bdberl ]}, {registered, []}, {applications, [kernel, stdlib]}, diff --git a/src/bdberl.erl b/src/bdberl.erl new file mode 100644 index 0000000..ac5c12b --- /dev/null +++ b/src/bdberl.erl @@ -0,0 +1,282 @@ +%% ------------------------------------------------------------------- +%% +%% bdberl: Interface to BerkeleyDB +%% Copyright (c) 2008 The Hive. All rights reserved. +%% +%% ------------------------------------------------------------------- +-module(bdberl). + +-export([init/0, + open/2, open/3, + close/1, close/2, + txn_begin/0, txn_begin/1, + txn_commit/0, txn_commit/1, txn_abort/0, + get_cache_size/0, set_cache_size/3, + get_txn_timeout/0, set_txn_timeout/1, + transaction/1, + put/3, put/4, + get/2, get/3, + update/3]). + +-include("bdberl.hrl"). + +init() -> + case erl_ddll:load_driver(code:priv_dir(bdberl), bdberl_drv) of + ok -> ok; + {error, permanent} -> ok % Means that the driver is already active + end, + Port = open_port({spawn, bdberl_drv}, [binary]), + erlang:put(bdb_port, Port), + ok. + +open(Name, Type) -> + open(Name, Type, [create]). + +open(Name, Type, Opts) -> + %% Map database type into an integer code + case Type of + btree -> TypeCode = ?DB_TYPE_BTREE; + hash -> TypeCode = ?DB_TYPE_HASH + end, + Flags = process_flags(lists:umerge(Opts, [auto_commit, threaded])), + Cmd = <>, + case erlang:port_control(get_port(), ?CMD_OPEN_DB, Cmd) of + <> -> + {ok, Db}; + <> -> + {error, Errno} + end. + +close(Db) -> + close(Db, []). + +close(Db, Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + case erlang:port_control(get_port(), ?CMD_CLOSE_DB, Cmd) of + <<0:32/native-integer>> -> + {error, invalid_db}; + <<1:32/native-integer>> -> + ok + end. + +txn_begin() -> + txn_begin([]). + +txn_begin(Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TXN_BEGIN, Cmd), + case decode_rc(Result) of + ok -> ok; + Error -> {error, {txn_begin, Error}} + end. + +txn_commit() -> + txn_commit([]). + +txn_commit(Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TXN_COMMIT, Cmd), + case decode_rc(Result) of + ok -> + receive + ok -> ok; + {error, Reason} -> {error, {txn_commit, decode_rc(Reason)}} + end; + Error -> + {error, {txn_commit, Error}} + end. + +txn_abort() -> + <> = erlang:port_control(get_port(), ?CMD_TXN_ABORT, <<>>), + case decode_rc(Result) of + ok -> + receive + ok -> ok; + {error, Reason} -> {error, {txn_abort, decode_rc(Reason)}} + end; + Error -> + {error, {txn_abort, Error}} + end. + +transaction(Fun) -> + txn_begin(), + try Fun() of + abort -> + txn_abort(), + {error, transaction_aborted}; + Value -> + txn_commit(), + {ok, Value} + catch + _ : Reason -> + txn_abort(), + {error, {transaction_failed, Reason}} + end. + +put(Db, Key, Value) -> + put(Db, Key, Value, []). + +put(Db, Key, Value, Opts) -> + {KeyLen, KeyBin} = to_binary(Key), + {ValLen, ValBin} = to_binary(Value), + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_PUT, Cmd), + case decode_rc(Result) of + ok -> + receive + ok -> ok; + {error, Reason} -> {error, {put, decode_rc(Reason)}} + end; + Error -> + {error, {put, decode_rc(Error)}} + end. + +get(Db, Key) -> + get(Db, Key, []). + +get(Db, Key, Opts) -> + {KeyLen, KeyBin} = to_binary(Key), + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_GET, Cmd), + case decode_rc(Result) of + ok -> + receive + {ok, Bin} -> {ok, binary_to_term(Bin)}; + not_found -> not_found; + {error, Reason} -> {error, {get, decode_rc(Reason)}} + end; + Error -> + {error, {get, decode_rc(Error)}} + 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(F). + +get_cache_size() -> + Cmd = <>, + <> = + erlang:port_control(get_port(), ?CMD_TUNE, Cmd), + case Result of + 0 -> + {ok, Gbytes, Bytes, Ncaches}; + _ -> + {error, Result} + end. + +set_cache_size(Gbytes, Bytes, Ncaches) -> + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TUNE, Cmd), + case Result of + 0 -> + ok; + _ -> + {error, Result} + end. + + +get_txn_timeout() -> + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TUNE, Cmd), + case Result of + 0 -> + {ok, Timeout}; + _ -> + {error, Result} + end. + +set_txn_timeout(Timeout) -> + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TUNE, Cmd), + case Result of + 0 -> + ok; + _ -> + {error, Result} + end. + + +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +get_port() -> + case erlang:get(bdb_port) of + undefined -> + ok = init(), + erlang:get(bdb_port); + Port -> + Port + end. + +%% +%% Decode a integer return value into an atom representation +%% +decode_rc(?ERROR_NONE) -> ok; +decode_rc(?ERROR_ASYNC_PENDING) -> async_pending; +decode_rc(?ERROR_INVALID_DBREF) -> invalid_dbref; +decode_rc(?ERROR_NO_TXN) -> no_txn; +decode_rc(?ERROR_DB_LOCK_NOTGRANTED) -> lock_not_granted; +decode_rc(?ERROR_DB_LOCK_DEADLOCK) -> deadlock; +decode_rc(Rc) -> {unknown, Rc}. + +%% +%% Convert a term into a binary, returning a tuple with the binary and the length of the binary +%% +to_binary(Term) -> + Bin = term_to_binary(Term), + {size(Bin), Bin}. + +%% +%% Given an array of options, produce a single integer with the numeric values +%% of the options joined with binary OR +%% +process_flags([]) -> + 0; +process_flags([Flag|Flags]) -> + flag_value(Flag) bor process_flags(Flags). + +%% +%% Given an option as an atom, return the numeric value +%% +flag_value(Flag) -> + case Flag of + append -> ?DB_APPEND; + auto_commit -> ?DB_AUTO_COMMIT; + consume -> ?DB_CONSUME; + consume_wait -> ?DB_CONSUME_WAIT; + create -> ?DB_CREATE; + exclusive -> ?DB_EXCL; + get_both -> ?DB_GET_BOTH; + ignore_lease -> ?DB_IGNORE_LEASE; + multiple -> ?DB_MULTIPLE; + multiversion -> ?DB_MULTIVERSION; + no_duplicate -> ?DB_NODUPDATA; + no_mmap -> ?DB_NOMMAP; + no_overwrite -> ?DB_NOOVERWRITE; + no_sync -> ?DB_NOSYNC; + read_committed -> ?DB_READ_COMMITTED; + read_uncommitted -> ?DB_READ_UNCOMMITTED; + readonly -> ?DB_RDONLY; + rmw -> ?DB_RMW; + set_recno -> ?DB_SET_RECNO; + threaded -> ?DB_THREAD; + truncate -> ?DB_TRUNCATE; + txn_no_sync -> ?DB_TXN_NOSYNC; + txn_no_wait -> ?DB_TXN_NOWAIT; + txn_snapshot -> ?DB_TXN_SNAPSHOT; + txn_sync -> ?DB_TXN_SYNC; + txn_wait -> ?DB_TXN_WAIT; + txn_write_nosync -> ?DB_TXN_WRITE_NOSYNC + end. + diff --git a/src/bdberl_db.erl b/src/bdberl_db.erl deleted file mode 100644 index 93c3417..0000000 --- a/src/bdberl_db.erl +++ /dev/null @@ -1,68 +0,0 @@ -%% ------------------------------------------------------------------- -%% -%% 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/test/db_api_SUITE.erl b/test/bdberl_SUITE.erl similarity index 59% rename from test/db_api_SUITE.erl rename to test/bdberl_SUITE.erl index 96cd7f7..84c0592 100644 --- a/test/db_api_SUITE.erl +++ b/test/bdberl_SUITE.erl @@ -4,7 +4,7 @@ %% Copyright (c) 2008 The Hive. All rights reserved. %% %% ------------------------------------------------------------------- --module(db_api_SUITE). +-module(bdberl_SUITE). -compile(export_all). @@ -20,14 +20,19 @@ all() -> update_should_save_value_if_successful]. +init_per_suite(Config) -> + ok = bdberl:init(), + Config. + +end_per_suite(_Config) -> + ok. + 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]. + {ok, Db} = bdberl:open("api_test.db", btree, [create, exclusive]), + [{db, Db}|Config]. end_per_testcase(_TestCase, Config) -> - ok = bdberl_db:close(?config(db, Config)), - true = port_close(?config(port, Config)), + ok = bdberl:close(?config(db, Config)), ok = file:delete("api_test.db"). @@ -35,44 +40,44 @@ 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). + not_found = bdberl: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). + ok = bdberl:put(Db, mykey, avalue), + {ok, avalue} = bdberl: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). + F = fun() -> bdberl:put(Db, mykey, avalue) end, + {ok, ok} = bdberl:transaction(F), + {ok, avalue} = bdberl:get(Db, mykey). transaction_should_abort_on_exception(Config) -> Db = ?config(db, Config), F = fun() -> - bdberl_db:put(Db, mykey, should_not_see_this), + bdberl: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). + {error, {transaction_failed, testing}} = bdberl:transaction(F), + not_found = bdberl: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), + bdberl:put(Db, mykey, should_not_see_this), abort end, - {error, transaction_aborted} = bdberl_db:transaction(Db, F), - not_found = bdberl_db:get(Db, mykey). + {error, transaction_aborted} = bdberl:transaction(F), + not_found = bdberl:get(Db, mykey). update_should_save_value_if_successful(Config) -> Db = ?config(db, Config), - ok = bdberl_db:put(Db, mykey, avalue), + ok = bdberl:put(Db, mykey, avalue), F = fun(Key, Value) -> mykey = Key, @@ -80,6 +85,6 @@ update_should_save_value_if_successful(Config) -> newvalue end, - {ok, newvalue} = bdberl_db:update(Db, mykey, F), - {ok, newvalue} = bdberl_db:get(Db, mykey). + {ok, newvalue} = bdberl:update(Db, mykey, F), + {ok, newvalue} = bdberl:get(Db, mykey).