Add support for cursor_get() which is used to position the cursor as
well as to get items based on its location.
This commit is contained in:
parent
a29ccf2f22
commit
27438453bb
|
@ -110,7 +110,10 @@ static void do_async_put(void* arg);
|
|||
static void do_async_get(void* arg);
|
||||
static void do_async_del(void* arg);
|
||||
static void do_async_txnop(void* arg);
|
||||
static void do_async_cursor_put(void* arg);
|
||||
static void do_async_cursor_get(void* arg);
|
||||
static void do_async_cursor_del(void* arg);
|
||||
static void do_async_cursor_cnp(void* arg);
|
||||
static void do_async_truncate(void* arg);
|
||||
static void do_sync_data_dirs_info(PortData *p);
|
||||
static void do_sync_driver_info(PortData *d);
|
||||
|
@ -775,6 +778,56 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
|||
RETURN_INT(0, outbuf);
|
||||
}
|
||||
}
|
||||
case CMD_CURSOR_GET:
|
||||
case CMD_CURSOR_PUT:
|
||||
case CMD_CURSOR_DEL:
|
||||
{
|
||||
FAIL_IF_ASYNC_PENDING(d, outbuf);
|
||||
FAIL_IF_NO_CURSOR(d, outbuf);
|
||||
|
||||
// Inbuf is <<Flags:32/native, KeyLen:32/native, KeyBin/bytes>>,
|
||||
|
||||
// If the working buffer is large enough, copy the data to put/get into it. Otherwise, realloc
|
||||
// until it is large enough
|
||||
if (d->work_buffer_sz < inbuf_sz)
|
||||
{
|
||||
d->work_buffer = driver_realloc(d->work_buffer, inbuf_sz);
|
||||
d->work_buffer_sz = inbuf_sz;
|
||||
}
|
||||
|
||||
// Copy the payload into place
|
||||
memcpy(d->work_buffer, inbuf, inbuf_sz);
|
||||
d->work_buffer_offset = inbuf_sz;
|
||||
|
||||
// Mark the port as busy and then choose the appropriate async operation
|
||||
d->async_op = cmd;
|
||||
TPoolJobFunc fn;
|
||||
switch(cmd) {
|
||||
case CMD_CURSOR_PUT:
|
||||
{
|
||||
fn = &do_async_cursor_put;
|
||||
}
|
||||
break;
|
||||
case CMD_CURSOR_DEL:
|
||||
{
|
||||
fn = &do_async_cursor_del;
|
||||
}
|
||||
break;
|
||||
case CMD_CURSOR_GET:
|
||||
{
|
||||
fn = &do_async_cursor_get;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(cmd);
|
||||
}
|
||||
// Now schedule the operation to run
|
||||
bdberl_general_tpool_run(fn, d, 0, &d->async_job);
|
||||
|
||||
// Let caller know operation is in progress
|
||||
// Outbuf is: <<0:32>>
|
||||
RETURN_INT(0, outbuf);
|
||||
}
|
||||
case CMD_CURSOR_CURR:
|
||||
case CMD_CURSOR_NEXT:
|
||||
case CMD_CURSOR_PREV:
|
||||
|
@ -784,7 +837,7 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
|||
|
||||
// Schedule the operation
|
||||
d->async_op = cmd;
|
||||
bdberl_general_tpool_run(&do_async_cursor_get, d, 0, &d->async_job);
|
||||
bdberl_general_tpool_run(&do_async_cursor_cnp, d, 0, &d->async_job);
|
||||
|
||||
// Let caller know operation is in progress
|
||||
RETURN_INT(0, outbuf);
|
||||
|
@ -1683,7 +1736,84 @@ static void do_async_txnop(void* arg)
|
|||
}
|
||||
|
||||
|
||||
static void do_async_cursor_put(void* arg)
|
||||
{
|
||||
PortData* d = (PortData*)arg;
|
||||
assert(d->cursor != NULL);
|
||||
DBGCMD(d, "cursor_put/2 not yet implemented..."); /* TODO: implement this. */
|
||||
bdberl_async_cleanup_and_send_rc(d, ERROR_DB_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
static void do_async_cursor_get(void* arg)
|
||||
{
|
||||
// Payload is: << Flags:32, KeyLen:32, Key:KeyLen >>
|
||||
PortData* d = (PortData*)arg;
|
||||
assert(d->cursor != NULL);
|
||||
|
||||
// Extract operation flags
|
||||
unsigned flags = UNPACK_INT(d->work_buffer, 0);
|
||||
|
||||
// Setup DBTs
|
||||
DBT key;
|
||||
DBT value;
|
||||
memset(&key, '\0', sizeof(DBT));
|
||||
memset(&value, '\0', sizeof(DBT));
|
||||
|
||||
// Parse payload into DBT
|
||||
key.size = UNPACK_INT(d->work_buffer, 4);
|
||||
key.data = UNPACK_BLOB(d->work_buffer, 8);
|
||||
|
||||
// Allocate a buffer for the output value
|
||||
value.flags = DB_DBT_MALLOC;
|
||||
|
||||
// Execute the operation
|
||||
DBGCMD(d, "d->cursor->get(%p, %p, %p, %08X);", d->cursor, &key, &value, flags);
|
||||
int rc = d->cursor->get(d->cursor, &key, &value, flags);
|
||||
DBGCMDRC(d, rc);
|
||||
|
||||
// Check CRC - first 4 bytes are CRC of rest of bytes
|
||||
if (rc == 0)
|
||||
{
|
||||
assert(value.size >= 4);
|
||||
uint32_t calc_crc32 = bdberl_crc32(value.data+4, value.size-4);
|
||||
uint32_t buf_crc32 = *(uint32_t*) value.data;
|
||||
|
||||
if (calc_crc32 != buf_crc32)
|
||||
{
|
||||
DBGCMD(d, "CRC-32 error on get data - buffer %08X calculated %08X.",
|
||||
buf_crc32, calc_crc32);
|
||||
rc = ERROR_INVALID_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup cursor as necessary
|
||||
if (rc && rc != DB_NOTFOUND && d->txn)
|
||||
{
|
||||
DBG("cursor flags=%d rc=%d\n", flags, rc);
|
||||
|
||||
d->cursor->close(d->cursor);
|
||||
d->cursor = 0;
|
||||
abort_txn(d);
|
||||
}
|
||||
|
||||
async_cleanup_and_send_kv(d, rc, &key, &value);
|
||||
|
||||
// Finally, clean up value buffer (driver_send_term made a copy)
|
||||
free(value.data);
|
||||
}
|
||||
|
||||
|
||||
static void do_async_cursor_del(void* arg)
|
||||
{
|
||||
PortData* d = (PortData*)arg;
|
||||
assert(d->cursor != NULL);
|
||||
DBGCMD(d, "cursor_del/2 not yet implemented..."); /* TODO: implement this. */
|
||||
bdberl_async_cleanup_and_send_rc(d, ERROR_DB_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
static void do_async_cursor_cnp(void* arg)
|
||||
{
|
||||
// Payload is: << DbRef:32, Flags:32, KeyLen:32, Key:KeyLen >>
|
||||
PortData* d = (PortData*)arg;
|
||||
|
|
|
@ -53,6 +53,9 @@
|
|||
#define CMD_CURSOR_NEXT 12
|
||||
#define CMD_CURSOR_PREV 13
|
||||
#define CMD_CURSOR_CLOSE 14
|
||||
#define CMD_CURSOR_GET 35 /* TODO: renumber these next 3 and match them with bdberl.hrl */
|
||||
#define CMD_CURSOR_PUT 36
|
||||
#define CMD_CURSOR_DEL 37
|
||||
#define CMD_PUT_COMMIT 15
|
||||
#define CMD_REMOVE_DB 16
|
||||
#define CMD_TRUNCATE 17
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
-define(CMD_CURSOR_NEXT, 12).
|
||||
-define(CMD_CURSOR_PREV, 13).
|
||||
-define(CMD_CURSOR_CLOSE, 14).
|
||||
-define(CMD_CURSOR_GET, 35). %% TODO: renumber these 3 and match them to bdberl_drv.h
|
||||
-define(CMD_CURSOR_PUT, 36).
|
||||
-define(CMD_CURSOR_DEL, 37).
|
||||
-define(CMD_PUT_COMMIT, 15).
|
||||
-define(CMD_REMOVE_DB, 16).
|
||||
-define(CMD_TRUNCATE, 17).
|
||||
|
|
146
src/bdberl.erl
146
src/bdberl.erl
|
@ -67,6 +67,7 @@
|
|||
truncate/0, truncate/1,
|
||||
delete_database/1,
|
||||
cursor_open/1, cursor_next/0, cursor_prev/0, cursor_current/0, cursor_close/0,
|
||||
cursor_get/0, cursor_get/1, cursor_get/2, %TODO: cursor_del/2, cursor_del/3, cursor_put/2, cursor_put/3,
|
||||
driver_info/0,
|
||||
register_logger/0,
|
||||
stop/0]).
|
||||
|
@ -1248,9 +1249,7 @@ cursor_prev() ->
|
|||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Retrieves key/data pairs from the database.
|
||||
%%
|
||||
%% Returns the key/data pair to which the cursor refers.
|
||||
%% Retrieves the key/data pair to which the cursor refers.
|
||||
%%
|
||||
%% Modifications to the database during a sequential scan will be
|
||||
%% reflected in the scan; that is, records inserted behind a cursor will
|
||||
|
@ -1270,6 +1269,132 @@ cursor_current() ->
|
|||
do_cursor_move(?CMD_CURSOR_CURR).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Retrieves the key/data pair to which the cursor refers.
|
||||
%%
|
||||
%% Modifications to the database during a sequential scan will be
|
||||
%% reflected in the scan; that is, records inserted behind a cursor will
|
||||
%% not be returned while records inserted in front of a cursor will be
|
||||
%% returned.
|
||||
%%
|
||||
%% If this function fails for any reason, the state of the cursor will
|
||||
%% be unchanged.
|
||||
%%
|
||||
%% @spec cursor_get() -> not_found | {ok, Key, Value} | {error, Error}
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec cursor_get() -> {ok, db_key(), db_value()} | not_found | db_error().
|
||||
|
||||
cursor_get() ->
|
||||
cursor_current().
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Positions the cursor at the key and retrieves that key/data pair.
|
||||
%%
|
||||
%% Modifications to the database during a sequential scan will be
|
||||
%% reflected in the scan; that is, records inserted behind a cursor will
|
||||
%% not be returned while records inserted in front of a cursor will be
|
||||
%% returned.
|
||||
%%
|
||||
%% If this function fails for any reason, the state of the cursor will
|
||||
%% be unchanged.
|
||||
%%
|
||||
%% @spec cursor_get(Key) -> not_found | {ok, Key, Value} | {error, Error}
|
||||
%% where
|
||||
%% Key = term()
|
||||
%%
|
||||
%% @equiv cursor_get(Key, [])
|
||||
%% @see cursor_get/2
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec cursor_get(Key :: db_key()) -> not_found | {ok, db_key(), db_value()} | db_error().
|
||||
|
||||
cursor_get(Key) ->
|
||||
cursor_get(Key, [db_set]).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Positions the cursor at the key and retrieves that key/data pair.
|
||||
%%
|
||||
%% Modifications to the database during a sequential scan will be
|
||||
%% reflected in the scan; that is, records inserted behind a cursor will
|
||||
%% not be returned while records inserted in front of a cursor will be
|
||||
%% returned.
|
||||
%%
|
||||
%% If this function fails for any reason, the state of the cursor will
|
||||
%% be unchanged.
|
||||
%%
|
||||
%% === Options ===
|
||||
%%
|
||||
%% <dl>
|
||||
%% <dt>db_current</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_first</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_get_both</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_get_both_range</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_last</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_next</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_next_dup</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_next_nodup</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_prev</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_prev_dup</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_prev_nodup</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_set</dt>
|
||||
%% <dd></dd>
|
||||
%% <dt>db_set_rance</dt>
|
||||
%% <dd>TODO... finish the doc, add other DB_?? flags</dd>
|
||||
%% </dl>
|
||||
%%
|
||||
%% @spec cursor_get(Key, Opts) -> not_found | {ok, Key, Value} | {error, Error}
|
||||
%% where
|
||||
%% Key = term()
|
||||
%% Opts = [atom()]
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec cursor_get(Key :: db_key(), Opts :: db_flags()) ->
|
||||
not_found | {ok, db_key(), db_value()} | db_error().
|
||||
|
||||
cursor_get(Key, Opts) ->
|
||||
{KeyLen, KeyBin} = to_binary(Key),
|
||||
Flags = process_flags(Opts),
|
||||
Cmd = <<Flags:32/native, KeyLen:32/native, KeyBin/bytes>>,
|
||||
<<Result:32/signed-native>> = erlang:port_control(get_port(), ?CMD_CURSOR_GET, Cmd),
|
||||
case decode_rc(Result) of
|
||||
ok ->
|
||||
receive
|
||||
{ok, _, Bin} ->
|
||||
<<Crc:32/native, Payload/binary>> = Bin,
|
||||
case erlang:crc32(Payload) of
|
||||
Crc ->
|
||||
{ok, binary_to_term(Payload)};
|
||||
CrcOther ->
|
||||
error_logger:warning_msg("Invalid CRC: ~p ~p\n", [Crc, CrcOther]),
|
||||
{error, invalid_crc}
|
||||
end;
|
||||
not_found -> not_found;
|
||||
{error, Reason} -> {error, Reason}
|
||||
end;
|
||||
Error ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Closes the cursor.
|
||||
|
@ -2257,7 +2382,20 @@ flag_value(Flag) ->
|
|||
txn_snapshot -> ?DB_TXN_SNAPSHOT;
|
||||
txn_sync -> ?DB_TXN_SYNC;
|
||||
txn_wait -> ?DB_TXN_WAIT;
|
||||
txn_write_nosync -> ?DB_TXN_WRITE_NOSYNC
|
||||
txn_write_nosync -> ?DB_TXN_WRITE_NOSYNC;
|
||||
db_current -> ?DB_CURRENT;
|
||||
db_first -> ?DB_FIRST;
|
||||
db_get_both -> ?DB_GET_BOTH;
|
||||
db_get_both_range -> ?DB_GET_BOTH_RANGE;
|
||||
db_last -> ?DB_LAST;
|
||||
db_next -> ?DB_NEXT;
|
||||
db_next_dup -> ?DB_NEXT_DUP;
|
||||
db_next_nodup -> ?DB_NEXT_NODUP;
|
||||
db_prev -> ?DB_PREV;
|
||||
db_prev_dup -> ?DB_PREV_DUP;
|
||||
db_prev_nodup -> ?DB_PREV_NODUP;
|
||||
db_set -> ?DB_SET;
|
||||
db_set_range -> ?DB_SET_RANGE
|
||||
end.
|
||||
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ all() ->
|
|||
get_should_return_a_value_when_getting_a_valid_record,
|
||||
put_should_succeed_with_manual_transaction,
|
||||
put_should_rollback_with_failed_manual_transaction,
|
||||
% del_should_remove_a_value,
|
||||
% del_should_remove_a_value, %TODO: why is this disabled
|
||||
transaction_should_commit_on_success,
|
||||
transaction_should_abort_on_exception,
|
||||
transaction_should_abort_on_user_abort,
|
||||
|
@ -53,7 +53,7 @@ all() ->
|
|||
update_should_save_value_if_successful,
|
||||
update_should_accept_args_for_fun,
|
||||
port_should_return_transaction_timeouts,
|
||||
cursor_should_iterate, cursor_should_fail_if_not_open,
|
||||
cursor_should_iterate, cursor_get_should_pos, cursor_should_fail_if_not_open,
|
||||
put_commit_should_end_txn,
|
||||
data_dir_should_be_priv_dir,
|
||||
delete_should_remove_file,
|
||||
|
@ -223,7 +223,7 @@ cursor_should_iterate(Config) ->
|
|||
{ok, key3, value3} = bdberl:cursor_next(),
|
||||
not_found = bdberl:cursor_next(),
|
||||
|
||||
%% Validate that the "current" key is key3
|
||||
%% Validate that the current key is key3
|
||||
{ok, key3, value3} = bdberl:cursor_current(),
|
||||
|
||||
%% Now move backwards (should jump to key2, since we are "on" key3)
|
||||
|
@ -233,10 +233,35 @@ cursor_should_iterate(Config) ->
|
|||
|
||||
ok = bdberl:cursor_close().
|
||||
|
||||
cursor_get_should_pos(Config) ->
|
||||
Db = ?config(db, Config),
|
||||
|
||||
%% Store some sample values in the db
|
||||
ok = bdberl:put(Db, key1, value1),
|
||||
ok = bdberl:put(Db, key2, value2),
|
||||
ok = bdberl:put(Db, key3, value3),
|
||||
ok = bdberl:put(Db, key4, value4),
|
||||
|
||||
%% Validate that the cursor is positioned properly, then
|
||||
%% returns the next value.
|
||||
ok = bdberl:cursor_open(Db),
|
||||
{ok, value2} = bdberl:cursor_get(key2),
|
||||
{ok, key3, value3} = bdberl:cursor_next(),
|
||||
{ok, value2} = bdberl:cursor_get(key2),
|
||||
{ok, key3, value3} = bdberl:cursor_next(),
|
||||
{ok, value1} = bdberl:cursor_get(key1),
|
||||
{ok, key2, value2} = bdberl:cursor_next(),
|
||||
{ok, key3, value3} = bdberl:cursor_next(),
|
||||
{ok, key4, value4} = bdberl:cursor_next(),
|
||||
not_found = bdberl:cursor_next(),
|
||||
|
||||
ok = bdberl:cursor_close().
|
||||
|
||||
cursor_should_fail_if_not_open(_Config) ->
|
||||
{error, no_cursor} = bdberl:cursor_next(),
|
||||
{error, no_cursor} = bdberl:cursor_prev(),
|
||||
{error, no_cursor} = bdberl:cursor_current(),
|
||||
{error, no_cursor} = bdberl:cursor_get(),
|
||||
{error, no_cursor} = bdberl:cursor_close().
|
||||
|
||||
put_commit_should_end_txn(Config) ->
|
||||
|
|
Loading…
Reference in a new issue