diff --git a/c_src/bdberl_drv.c b/c_src/bdberl_drv.c index 6a27fa8..dae7ae1 100644 --- a/c_src/bdberl_drv.c +++ b/c_src/bdberl_drv.c @@ -21,6 +21,7 @@ */ static int open_database(const char* name, DBTYPE type, unsigned flags, PortData* data, int* errno); static int close_database(int dbref, unsigned flags, PortData* data); +static int delete_database(const char* name); static void tune_system(int target, void* values, BinHelper* bh); @@ -325,7 +326,6 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, char** outbuf, int outbuf_sz) { PortData* d = (PortData*)handle; - switch(cmd) { case CMD_OPEN_DB: @@ -484,11 +484,9 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, int target = UNPACK_INT(inbuf, 0); char* values = UNPACK_BLOB(inbuf, 4); - // Setup a binhelper - BinHelper bh; - // Execute the tuning -- the result to send back to the caller is wrapped // up in the provided binhelper + BinHelper bh; tune_system(target, values, &bh); RETURN_BH(bh, outbuf); } @@ -562,6 +560,21 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, RETURN_INT(0, outbuf); } + case CMD_REMOVE_DB: + { + FAIL_IF_ASYNC_PENDING(d, outbuf); + + // Fail if a txn is open + if (d->txn != 0) + { + RETURN_INT(ERROR_TXN_OPEN, outbuf); + } + + // Inbuf is: << dbname/bytes, 0:8 >> + const char* dbname = UNPACK_STRING(inbuf, 0); + int rc = delete_database(dbname); + RETURN_INT(rc, outbuf); + } } *outbuf = 0; return 0; @@ -722,6 +735,24 @@ static int close_database(int dbref, unsigned flags, PortData* data) } } +static int delete_database(const char* name) +{ + // Go directly to a write lock on the global databases structure + WRITE_LOCK(G_DATABASES_RWLOCK); + + // Make sure the database is not opened by anyone + if (hive_hash_get(G_DATABASES_NAMES, name)) + { + WRITE_UNLOCK(G_DATABASES_RWLOCK); + return ERROR_DB_ACTIVE; + } + + // Good, database doesn't seem to be open -- attempt the delete + int rc = G_DB_ENV->dbremove(G_DB_ENV, 0, name, 0, DB_AUTO_COMMIT); + WRITE_UNLOCK(G_DATABASES_RWLOCK); + return rc; +} + /** * Given a target system parameter/action adjust/return the requested value */ @@ -729,16 +760,6 @@ static void tune_system(int target, void* values, BinHelper* bh) { switch(target) { - case SYSP_CACHESIZE_SET: - { - unsigned int gbytes = UNPACK_INT(values, 0); - unsigned int bytes = UNPACK_INT(values, 4); - unsigned int ncache = UNPACK_INT(values, 8); - int rc = G_DB_ENV->set_cachesize(G_DB_ENV, gbytes, bytes, ncache); - bin_helper_init(bh, 4); - bin_helper_push_int32(bh, rc); - break; - } case SYSP_CACHESIZE_GET: { unsigned int gbytes = 0; @@ -769,6 +790,23 @@ static void tune_system(int target, void* values, BinHelper* bh) bin_helper_push_int32(bh, timeout); break; } + case SYSP_DATA_DIR_GET: + { + const char** dirs = 0; + int rc = G_DB_ENV->get_data_dirs(G_DB_ENV, &dirs); + printf("DATA DIR: %d\n", rc); + bin_helper_init(bh, 64); + bin_helper_push_int32(bh, rc); + if (dirs) + { + while (*dirs != 0) + { + bin_helper_push_string(bh, *dirs); + dirs++; + } + } + break; + } } } diff --git a/c_src/bdberl_drv.h b/c_src/bdberl_drv.h index 5a28953..757d2c5 100644 --- a/c_src/bdberl_drv.h +++ b/c_src/bdberl_drv.h @@ -43,6 +43,7 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, #define CMD_CURSOR_PREV 12 #define CMD_CURSOR_CLOSE 13 #define CMD_PUT_COMMIT 14 +#define CMD_REMOVE_DB 15 /** @@ -68,19 +69,16 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, #define ERROR_NO_TXN (-29004) /* No transaction open on this port */ #define ERROR_CURSOR_OPEN (-29005) /* Cursor already active on this port */ #define ERROR_NO_CURSOR (-29006) /* No cursor open on this port */ +#define ERROR_DB_ACTIVE (-29007) /* Database is currently active; operation requires otherwise */ /** * Tunable system parameters/actions */ -#define SYSP_CACHESIZE_SET 0 #define SYSP_CACHESIZE_GET 1 #define SYSP_TXN_TIMEOUT_SET 2 #define SYSP_TXN_TIMEOUT_GET 3 -#define SYSP_TXN_TIMEOUT 1 -#define SYSP_DEADLOCK_CHECK_INTERVAL 2 -#define SYSP_TRICKLE_INTERVAL 3 -#define SYSP_TRICKLE_PERCENTAGE 4 +#define SYSP_DATA_DIR_GET 4 /** * Driver Entry diff --git a/c_src/bin_helper.c b/c_src/bin_helper.c index 66fd3fa..9f05205 100644 --- a/c_src/bin_helper.c +++ b/c_src/bin_helper.c @@ -14,8 +14,7 @@ static void bin_helper_check_size(BinHelper* bh, int space_needed) { if (bh->offset + space_needed > bh->bin->orig_size) { - // Need to realloc space -- grow by 2 * space_needed - bh->bin = driver_realloc_binary(bh->bin, bh->bin->orig_size + (2 * space_needed)); + bh->bin = driver_realloc_binary(bh->bin, bh->offset + space_needed); } } diff --git a/include/bdberl.hrl b/include/bdberl.hrl index 4b3b675..e12813a 100644 --- a/include/bdberl.hrl +++ b/include/bdberl.hrl @@ -20,15 +20,15 @@ -define(CMD_CURSOR_PREV, 12). -define(CMD_CURSOR_CLOSE, 13). -define(CMD_PUT_COMMIT, 14). - +-define(CMD_REMOVE_DB, 15). -define(DB_TYPE_BTREE, 1). -define(DB_TYPE_HASH, 2). --define(SYSP_CACHESIZE_SET, 0). -define(SYSP_CACHESIZE_GET, 1). -define(SYSP_TXN_TIMEOUT_SET, 2). -define(SYSP_TXN_TIMEOUT_GET, 3). +-define(SYSP_DATA_DIR_GET, 4). -define(STATUS_OK, 0). -define(STATUS_ERROR, 1). @@ -41,6 +41,7 @@ -define(ERROR_NO_TXN, -29004). % No transaction open on this port -define(ERROR_CURSOR_OPEN, -29005). % Cursor already active on this port -define(ERROR_NO_CURSOR, -29006). % No cursor open on this port +-define(ERROR_DB_ACTIVE, -29007). % Database is currently active; operation requires otherwise -define(ERROR_DB_LOCK_NOTGRANTED, -30993). % Lock was busy and not granted -define(ERROR_DB_LOCK_DEADLOCK, -30994). % Deadlock occurred diff --git a/src/bdberl.erl b/src/bdberl.erl index 2aa0f4a..abb33da 100644 --- a/src/bdberl.erl +++ b/src/bdberl.erl @@ -10,7 +10,7 @@ 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_cache_size/0, get_data_dirs/0, get_txn_timeout/0, set_txn_timeout/1, transaction/1, transaction/2, put/3, put/4, @@ -20,6 +20,7 @@ get/2, get/3, get_r/2, get_r/3, update/3, update/4, + delete_database/1, cursor_open/1, cursor_next/0, cursor_prev/0, cursor_current/0, cursor_close/0]). -include("bdberl.hrl"). @@ -236,7 +237,35 @@ cursor_close() -> Reason -> {error, Reason} end. - + +delete_database(Filename) -> + Cmd = list_to_binary(Filename), + <> = erlang:port_control(get_port(), ?CMD_REMOVE_DB, Cmd), + case decode_rc(Rc) of + ok -> + ok; + Reason -> + {error, Reason} + end. + + +get_data_dirs() -> + %% Call into the BDB library and get a list of configured data directories + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TUNE, Cmd), + case decode_rc(Result) of + ok -> + Dirs = [binary_to_list(D) || D <- split_bin(0, Rest, <<>>, [])], + %% Make sure DB_HOME is part of the list + case lists:member(os:getenv("DB_HOME"), Dirs) of + true -> + Dirs; + false -> + [os:getenv("DB_HOME") | Dirs] + end; + Reason -> + {error, Reason} + end. get_cache_size() -> Cmd = <>, @@ -249,17 +278,6 @@ get_cache_size() -> {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), @@ -281,6 +299,7 @@ set_txn_timeout(Timeout) -> end. + %% ==================================================================== %% Internal functions %% ==================================================================== @@ -404,3 +423,17 @@ do_cursor_move(Direction) -> Reason -> {error, Reason} end. + + + +%% +%% Split a binary into pieces, using a single character delimiter +%% +split_bin(_Delimiter, <<>>, <<>>, Acc) -> + lists:reverse(Acc); +split_bin(_Delimiter, <<>>, ItemAcc, Acc) -> + lists:reverse([ItemAcc | Acc]); +split_bin(Delimiter, <>, ItemAcc, Acc) -> + split_bin(Delimiter, Rest, <<>> ,[ItemAcc | Acc]); +split_bin(Delimiter, <>, ItemAcc, Acc) -> + split_bin(Delimiter, Rest, <>, Acc). diff --git a/test/bdberl_SUITE.erl b/test/bdberl_SUITE.erl index b509b20..091d122 100644 --- a/test/bdberl_SUITE.erl +++ b/test/bdberl_SUITE.erl @@ -10,7 +10,7 @@ -include_lib("ct.hrl"). -all() -> +ball() -> [open_should_create_database_if_none_exists, open_should_allow_opening_multiple_databases, close_should_fail_with_invalid_db_handle, @@ -25,20 +25,33 @@ all() -> update_should_accept_args_for_fun, port_should_tune_transaction_timeouts, cursor_should_iterate, cursor_should_fail_if_not_open, - put_commit_should_end_txn]. + put_commit_should_end_txn, + data_dir_should_be_priv_dir, + delete_should_remove_file, + delete_should_fail_if_db_inuse]. +init_per_suite(Config) -> + DbHome = ?config(priv_dir, Config), + os:putenv("DB_HOME", DbHome), + ok = file:write_file(DbHome ++ "DB_CONFIG", <<"set_data_dir ", (list_to_binary(DbHome))/binary, "\n">>), + Config. + +end_per_suite(_Config) -> + ok. + init_per_testcase(_TestCase, Config) -> {ok, Db} = bdberl:open("api_test.db", btree, [create, exclusive]), [{db, Db}|Config]. end_per_testcase(_TestCase, Config) -> ok = bdberl:close(?config(db, Config)), - ok = file:delete("api_test.db"). + ok = file:delete(filename:join([?config(priv_dir, Config), "api_test.db"])). -open_should_create_database_if_none_exists(_Config) -> - true = filelib:is_file("api_test.db"). +open_should_create_database_if_none_exists(Config) -> + DbName = filename:join([?config(priv_dir, Config), "api_test.db"]), + true = filelib:is_file(DbName). open_should_allow_opening_multiple_databases(_Config) -> %% Open up another db -- should use dbref 1 as that's the first available @@ -171,4 +184,23 @@ put_commit_should_end_txn(Config) -> %% Verify data got committed {ok, value1} = bdberl:get(Db, key1). +data_dir_should_be_priv_dir(Config) -> + PrivDir = ?config(priv_dir, Config), + [PrivDir] = bdberl:get_data_dirs(). +delete_should_remove_file(Config) -> + {ok, Db} = bdberl:open("mytest.bdb", btree), + ok = bdberl:close(Db), + + Fname = filename:join([?config(priv_dir, Config), "mytest.bdb"]), + true = filelib:is_file(Fname), + + ok = bdberl:delete_database("mytest.bdb"), + + false = filelib:is_file(Fname). + +delete_should_fail_if_db_inuse(Config) -> + Fname = filename:join([?config(priv_dir, Config), "api_test.db"]), + true = filelib:is_file(Fname), + {error, _} = bdberl:delete_database(Fname), + true = filelib:is_file(Fname).