From 6c0f14da6e03ce1758d377a97c2b42cd3edd492f Mon Sep 17 00:00:00 2001 From: Steve Vinoski Date: Sun, 19 Feb 2012 20:20:38 -0500 Subject: [PATCH] add fold_keys, and funs for getting keys and values via cursors Change cursor_{next,prev} to return {ok, Key, Value}. Add cursor_{next,prev}_{key,value} functions to allow cursor movement followed by fetch of either key or value. Add fold_keys function. Add unit tests for these changes. Also specify "inorder" on the unit test generator functions, since the tests they specified are intended to be run that way. --- c_src/wterl.c | 95 +++++++++++++-- src/wterl.erl | 332 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 282 insertions(+), 145 deletions(-) diff --git a/c_src/wterl.c b/c_src/wterl.c index 6179348..734ee66 100644 --- a/c_src/wterl.c +++ b/c_src/wterl.c @@ -52,22 +52,31 @@ typedef char Uri[128]; // object names static ERL_NIF_TERM ATOM_ERROR; static ERL_NIF_TERM ATOM_OK; +typedef ERL_NIF_TERM (*CursorRetFun)(ErlNifEnv* env, WT_CURSOR* cursor, int rc); + // Prototypes static ERL_NIF_TERM wterl_conn_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_conn_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_insert(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wterl_cursor_key_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc); +static ERL_NIF_TERM wterl_cursor_kv_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc); static ERL_NIF_TERM wterl_cursor_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM wterl_cursor_np_worker(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], int next); +static ERL_NIF_TERM wterl_cursor_next_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wterl_cursor_next_value(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wterl_cursor_np_worker(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], + CursorRetFun cursor_ret_fun, int next); static ERL_NIF_TERM wterl_cursor_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_prev(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wterl_cursor_prev_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wterl_cursor_prev_value(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_remove(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_reset(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM wterl_cursor_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc); static ERL_NIF_TERM wterl_cursor_search(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_search_near(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_cursor_search_worker(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], int near); static ERL_NIF_TERM wterl_cursor_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wterl_cursor_value_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc); static ERL_NIF_TERM wterl_session_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_session_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM wterl_session_delete(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -89,8 +98,12 @@ static ErlNifFunc nif_funcs[] = {"cursor_close", 1, wterl_cursor_close}, {"cursor_insert", 3, wterl_cursor_insert}, {"cursor_next", 1, wterl_cursor_next}, + {"cursor_next_key", 1, wterl_cursor_next_key}, + {"cursor_next_value", 1, wterl_cursor_next_value}, {"cursor_open", 2, wterl_cursor_open}, {"cursor_prev", 1, wterl_cursor_prev}, + {"cursor_prev_key", 1, wterl_cursor_prev_key}, + {"cursor_prev_value", 1, wterl_cursor_prev_value}, {"cursor_remove", 3, wterl_cursor_remove}, {"cursor_reset", 1, wterl_cursor_reset}, {"cursor_search", 2, wterl_cursor_search}, @@ -492,7 +505,48 @@ static ERL_NIF_TERM wterl_cursor_close(ErlNifEnv* env, int argc, const ERL_NIF_T return enif_make_badarg(env); } -static ERL_NIF_TERM wterl_cursor_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc) +static ERL_NIF_TERM wterl_cursor_key_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc) +{ + if (rc == 0) + { + WT_ITEM raw_key; + rc = cursor->get_key(cursor, &raw_key); + if (rc == 0) + { + ErlNifBinary key; + enif_alloc_binary(raw_key.size, &key); + memcpy(key.data, raw_key.data, raw_key.size); + return enif_make_tuple2(env, ATOM_OK, enif_make_binary(env, &key)); + } + } + return wterl_strerror(env, rc); +} + +static ERL_NIF_TERM wterl_cursor_kv_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc) +{ + if (rc == 0) + { + WT_ITEM raw_key, raw_value; + rc = cursor->get_key(cursor, &raw_key); + if (rc == 0) + { + rc = cursor->get_value(cursor, &raw_value); + if (rc == 0) + { + ErlNifBinary key, value; + enif_alloc_binary(raw_key.size, &key); + memcpy(key.data, raw_key.data, raw_key.size); + enif_alloc_binary(raw_value.size, &value); + memcpy(value.data, raw_value.data, raw_value.size); + return enif_make_tuple3(env, ATOM_OK, + enif_make_binary(env, &key), enif_make_binary(env, &value)); + } + } + } + return wterl_strerror(env, rc); +} + +static ERL_NIF_TERM wterl_cursor_value_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc) { if (rc == 0) { @@ -509,25 +563,46 @@ static ERL_NIF_TERM wterl_cursor_ret(ErlNifEnv* env, WT_CURSOR *cursor, int rc) return wterl_strerror(env, rc); } -static ERL_NIF_TERM wterl_cursor_np_worker(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], int prev) +static ERL_NIF_TERM wterl_cursor_np_worker(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], + CursorRetFun cursor_ret, int prev) { wterl_cursor_handle *cursor_handle; if (enif_get_resource(env, argv[0], wterl_cursor_RESOURCE, (void**)&cursor_handle)) { WT_CURSOR* cursor = cursor_handle->cursor; - return wterl_cursor_ret(env, cursor, prev == 0 ? cursor->next(cursor) : cursor->prev(cursor)); + return cursor_ret(env, cursor, prev == 0 ? cursor->next(cursor) : cursor->prev(cursor)); } return enif_make_badarg(env); } static ERL_NIF_TERM wterl_cursor_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - return wterl_cursor_np_worker(env, argc, argv, 0); + return wterl_cursor_np_worker(env, argc, argv, wterl_cursor_kv_ret, 0); +} + +static ERL_NIF_TERM wterl_cursor_next_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + return wterl_cursor_np_worker(env, argc, argv, wterl_cursor_key_ret, 0); +} + +static ERL_NIF_TERM wterl_cursor_next_value(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + return wterl_cursor_np_worker(env, argc, argv, wterl_cursor_value_ret, 0); } static ERL_NIF_TERM wterl_cursor_prev(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - return wterl_cursor_np_worker(env, argc, argv, 1); + return wterl_cursor_np_worker(env, argc, argv, wterl_cursor_kv_ret, 1); +} + +static ERL_NIF_TERM wterl_cursor_prev_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + return wterl_cursor_np_worker(env, argc, argv, wterl_cursor_key_ret, 1); +} + +static ERL_NIF_TERM wterl_cursor_prev_value(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + return wterl_cursor_np_worker(env, argc, argv, wterl_cursor_value_ret, 1); } static ERL_NIF_TERM wterl_cursor_search_worker(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], int near) @@ -546,9 +621,9 @@ static ERL_NIF_TERM wterl_cursor_search_worker(ErlNifEnv* env, int argc, const E // We currently ignore the less-than, greater-than or equals-to return information // from the cursor.search_near method. - return wterl_cursor_ret(env, cursor, - near == 1 ? - cursor->search_near(cursor, &exact) : cursor->search(cursor)); + return wterl_cursor_value_ret(env, cursor, + near == 1 ? + cursor->search_near(cursor, &exact) : cursor->search(cursor)); } return enif_make_badarg(env); } diff --git a/src/wterl.erl b/src/wterl.erl index ba8ca2d..b5e2d78 100644 --- a/src/wterl.erl +++ b/src/wterl.erl @@ -27,8 +27,12 @@ cursor_close/1, cursor_insert/3, cursor_next/1, + cursor_next_key/1, + cursor_next_value/1, cursor_open/2, cursor_prev/1, + cursor_prev_key/1, + cursor_prev_value/1, cursor_remove/3, cursor_reset/1, cursor_search/2, @@ -55,7 +59,8 @@ session_upgrade/3, session_verify/2, session_verify/3, - config_to_bin/1]). + config_to_bin/1, + fold_keys/3]). -on_load(init/0). @@ -150,9 +155,21 @@ cursor_close(_Cursor) -> cursor_next(_Cursor) -> ?nif_stub. +cursor_next_key(_Cursor) -> + ?nif_stub. + +cursor_next_value(_Cursor) -> + ?nif_stub. + cursor_prev(_Cursor) -> ?nif_stub. +cursor_prev_key(_Cursor) -> + ?nif_stub. + +cursor_prev_value(_Cursor) -> + ?nif_stub. + cursor_search(_Cursor, _Key) -> ?nif_stub. @@ -162,15 +179,23 @@ cursor_search_near(_Cursor, _Key) -> cursor_reset(_Cursor) -> ?nif_stub. -cursor_insert(_Ref, _Key, _Value) -> +cursor_insert(_Cursor, _Key, _Value) -> ?nif_stub. -cursor_update(_Ref, _Key, _Value) -> +cursor_update(_Cursor, _Key, _Value) -> ?nif_stub. -cursor_remove(_Ref, _Key, _Value) -> +cursor_remove(_Cursor, _Key, _Value) -> ?nif_stub. +fold_keys(Cursor, Fun, Acc0) -> + fold_keys(Cursor, Fun, Acc0, cursor_next_key(Cursor)). +fold_keys(_Cursor, _Fun, Acc, not_found) -> + Acc; +fold_keys(Cursor, Fun, Acc, {ok, Key}) -> + fold_keys(Cursor, Fun, Fun(Key, Acc), cursor_next_key(Cursor)). + + %% %% Configuration type information. %% @@ -194,7 +219,8 @@ config_types() -> config_encode(integer, Value) -> try list_to_binary(integer_to_list(Value)) - catch _:_ -> + catch + _:_ -> invalid end; config_encode(string, Value) -> @@ -209,7 +235,7 @@ config_encode(_Type, _Value) -> config_to_bin(Opts) -> config_to_bin(Opts, []). config_to_bin([], Acc) -> - iolist_to_binary([Acc, <<"\0">>]); + iolist_to_binary([Acc, ?EMPTY_CONFIG]); config_to_bin([{Key, Value} | Rest], Acc) -> case lists:keysearch(Key, 1, config_types()) of {value, {Key, Type}} -> @@ -263,17 +289,18 @@ session_test_() -> ok = conn_close(ConnRef) end, fun(ConnRef) -> - [{"open/close a session", - fun() -> - {ok, SRef} = session_open(ConnRef), - ?assertMatch(ok, session_close(SRef)) - end}, - {"create and drop a table", - fun() -> - SRef = open_test_session(ConnRef), - ?assertMatch(ok, session_drop(SRef, "table:test")), - ?assertMatch(ok, session_close(SRef)) - end}] + {inorder, + [{"open/close a session", + fun() -> + {ok, SRef} = session_open(ConnRef), + ?assertMatch(ok, session_close(SRef)) + end}, + {"create and drop a table", + fun() -> + SRef = open_test_session(ConnRef), + ?assertMatch(ok, session_drop(SRef, "table:test")), + ?assertMatch(ok, session_close(SRef)) + end}]} end}. insert_delete_test() -> @@ -305,58 +332,59 @@ various_session_test_() -> fun init_test_table/0, fun stop_test_table/1, fun({_, SRef}) -> - [{"session verify", - fun() -> - ?assertMatch(ok, session_verify(SRef, "table:test")), - ?assertMatch({ok, <<"apple">>}, - session_get(SRef, "table:test", <<"a">>)) - end}, - {"session sync", - fun() -> - ?assertMatch(ok, session_sync(SRef, "table:test")), - ?assertMatch({ok, <<"apple">>}, - session_get(SRef, "table:test", <<"a">>)) - end}, - {"session salvage", - fun() -> - %% =============================================================== - %% KEITH: SKIP SALVAGE FOR NOW, THERE IS SOMETHING WRONG. - %% =============================================================== - %% ok = session_salvage(SRef, "table:test"), - %% {ok, <<"apple">>} = session_get(SRef, "table:test", <<"a">>), - ok - end}, - {"session upgrade", - fun() -> - ?assertMatch(ok, session_upgrade(SRef, "table:test")), - ?assertMatch({ok, <<"apple">>}, - session_get(SRef, "table:test", <<"a">>)) - end}, - {"session rename", - fun() -> - ?assertMatch(ok, - session_rename(SRef, "table:test", "table:new")), - ?assertMatch({ok, <<"apple">>}, - session_get(SRef, "table:new", <<"a">>)), - ?assertMatch(ok, - session_rename(SRef, "table:new", "table:test")), - ?assertMatch({ok, <<"apple">>}, - session_get(SRef, "table:test", <<"a">>)) - end}, - {"session truncate", - fun() -> - ?assertMatch(ok, session_truncate(SRef, "table:test")), - ?assertMatch(not_found, session_get(SRef, "table:test", <<"a">>)) - end}] + {inorder, + [{"session verify", + fun() -> + ?assertMatch(ok, session_verify(SRef, "table:test")), + ?assertMatch({ok, <<"apple">>}, + session_get(SRef, "table:test", <<"a">>)) + end}, + {"session sync", + fun() -> + ?assertMatch(ok, session_sync(SRef, "table:test")), + ?assertMatch({ok, <<"apple">>}, + session_get(SRef, "table:test", <<"a">>)) + end}, + {"session salvage", + fun() -> + %% =============================================================== + %% KEITH: SKIP SALVAGE FOR NOW, THERE IS SOMETHING WRONG. + %% =============================================================== + %% ok = session_salvage(SRef, "table:test"), + %% {ok, <<"apple">>} = session_get(SRef, "table:test", <<"a">>), + ok + end}, + {"session upgrade", + fun() -> + ?assertMatch(ok, session_upgrade(SRef, "table:test")), + ?assertMatch({ok, <<"apple">>}, + session_get(SRef, "table:test", <<"a">>)) + end}, + {"session rename", + fun() -> + ?assertMatch(ok, + session_rename(SRef, "table:test", "table:new")), + ?assertMatch({ok, <<"apple">>}, + session_get(SRef, "table:new", <<"a">>)), + ?assertMatch(ok, + session_rename(SRef, "table:new", "table:test")), + ?assertMatch({ok, <<"apple">>}, + session_get(SRef, "table:test", <<"a">>)) + end}, + {"session truncate", + fun() -> + ?assertMatch(ok, session_truncate(SRef, "table:test")), + ?assertMatch(not_found, session_get(SRef, "table:test", <<"a">>)) + end}]} end}. cursor_open_close_test() -> {ConnRef, SRef} = init_test_table(), {ok, Cursor1} = cursor_open(SRef, "table:test"), - ?assertMatch({ok, <<"apple">>}, cursor_next(Cursor1)), + ?assertMatch({ok, <<"a">>, <<"apple">>}, cursor_next(Cursor1)), ?assertMatch(ok, cursor_close(Cursor1)), {ok, Cursor2} = cursor_open(SRef, "table:test"), - ?assertMatch({ok, <<"gooseberry">>}, cursor_prev(Cursor2)), + ?assertMatch({ok, <<"g">>, <<"gooseberry">>}, cursor_prev(Cursor2)), ?assertMatch(ok, cursor_close(Cursor2)), stop_test_table({ConnRef, SRef}). @@ -365,79 +393,113 @@ various_cursor_test_() -> fun init_test_table/0, fun stop_test_table/1, fun({_, SRef}) -> - [{"move a cursor back and forth", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch({ok, <<"apple">>}, cursor_next(Cursor)), - ?assertMatch({ok, <<"banana">>}, cursor_next(Cursor)), - ?assertMatch({ok, <<"cherry">>}, cursor_next(Cursor)), - ?assertMatch({ok, <<"date">>}, cursor_next(Cursor)), - ?assertMatch({ok, <<"cherry">>}, cursor_prev(Cursor)), - ?assertMatch({ok, <<"date">>}, cursor_next(Cursor)), - ?assertMatch({ok, <<"gooseberry">>}, cursor_next(Cursor)), - ?assertMatch(not_found, cursor_next(Cursor)), - ?assertMatch(ok, cursor_close(Cursor)) - end}, - {"search for an item", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch({ok, <<"banana">>}, cursor_search(Cursor, <<"b">>)), - ?assertMatch(ok, cursor_close(Cursor)) - end}, - {"range search for an item", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch({ok, <<"gooseberry">>}, - cursor_search_near(Cursor, <<"z">>)), - ?assertMatch(ok, cursor_close(Cursor)) - end}, - {"check cursor reset", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch({ok, <<"apple">>}, cursor_next(Cursor)), - ?assertMatch(ok, cursor_reset(Cursor)), - ?assertMatch({ok, <<"apple">>}, cursor_next(Cursor)), - ?assertMatch(ok, cursor_close(Cursor)) - end}, - {"insert/overwrite an item using a cursor", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch(ok, - cursor_insert(Cursor, <<"h">>, <<"huckleberry">>)), - ?assertMatch({ok, <<"huckleberry">>}, - cursor_search(Cursor, <<"h">>)), - ?assertMatch(ok, - cursor_insert(Cursor, <<"g">>, <<"grapefruit">>)), - ?assertMatch({ok, <<"grapefruit">>}, - cursor_search(Cursor, <<"g">>)), - ?assertMatch(ok, cursor_close(Cursor)), - ?assertMatch({ok, <<"grapefruit">>}, - session_get(SRef, "table:test", <<"g">>)), - ?assertMatch({ok, <<"huckleberry">>}, - session_get(SRef, "table:test", <<"h">>)) - end}, - {"update an item using a cursor", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch(ok, - cursor_update(Cursor, <<"g">>, <<"goji berries">>)), - ?assertMatch(not_found, - cursor_update(Cursor, <<"k">>, <<"kumquat">>)), - ?assertMatch(ok, cursor_close(Cursor)), - ?assertMatch({ok, <<"goji berries">>}, - session_get(SRef, "table:test", <<"g">>)) - end}, - {"remove an item using a cursor", - fun() -> - {ok, Cursor} = cursor_open(SRef, "table:test"), - ?assertMatch(ok, - cursor_remove(Cursor, <<"g">>, <<"goji berries">>)), - ?assertMatch(not_found, - cursor_remove(Cursor, <<"l">>, <<"lemon">>)), - ?assertMatch(ok, cursor_close(Cursor)), - ?assertMatch(not_found, - session_get(SRef, "table:test", <<"g">>)) - end}] + {inorder, + [{"move a cursor back and forth, getting key", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch({ok, <<"a">>}, cursor_next_key(Cursor)), + ?assertMatch({ok, <<"b">>}, cursor_next_key(Cursor)), + ?assertMatch({ok, <<"c">>}, cursor_next_key(Cursor)), + ?assertMatch({ok, <<"d">>}, cursor_next_key(Cursor)), + ?assertMatch({ok, <<"c">>}, cursor_prev_key(Cursor)), + ?assertMatch({ok, <<"d">>}, cursor_next_key(Cursor)), + ?assertMatch({ok, <<"g">>}, cursor_next_key(Cursor)), + ?assertMatch(not_found, cursor_next_key(Cursor)), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"move a cursor back and forth, getting value", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch({ok, <<"apple">>}, cursor_next_value(Cursor)), + ?assertMatch({ok, <<"banana">>}, cursor_next_value(Cursor)), + ?assertMatch({ok, <<"cherry">>}, cursor_next_value(Cursor)), + ?assertMatch({ok, <<"date">>}, cursor_next_value(Cursor)), + ?assertMatch({ok, <<"cherry">>}, cursor_prev_value(Cursor)), + ?assertMatch({ok, <<"date">>}, cursor_next_value(Cursor)), + ?assertMatch({ok, <<"gooseberry">>}, cursor_next_value(Cursor)), + ?assertMatch(not_found, cursor_next_value(Cursor)), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"move a cursor back and forth, getting key and value", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch({ok, <<"a">>, <<"apple">>}, cursor_next(Cursor)), + ?assertMatch({ok, <<"b">>, <<"banana">>}, cursor_next(Cursor)), + ?assertMatch({ok, <<"c">>, <<"cherry">>}, cursor_next(Cursor)), + ?assertMatch({ok, <<"d">>, <<"date">>}, cursor_next(Cursor)), + ?assertMatch({ok, <<"c">>, <<"cherry">>}, cursor_prev(Cursor)), + ?assertMatch({ok, <<"d">>, <<"date">>}, cursor_next(Cursor)), + ?assertMatch({ok, <<"g">>, <<"gooseberry">>}, cursor_next(Cursor)), + ?assertMatch(not_found, cursor_next(Cursor)), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"fold keys", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch([<<"g">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>], + fold_keys(Cursor, fun(Key, Acc) -> [Key | Acc] end, [])), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"search for an item", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch({ok, <<"banana">>}, cursor_search(Cursor, <<"b">>)), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"range search for an item", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch({ok, <<"gooseberry">>}, + cursor_search_near(Cursor, <<"z">>)), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"check cursor reset", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch({ok, <<"apple">>}, cursor_next_value(Cursor)), + ?assertMatch(ok, cursor_reset(Cursor)), + ?assertMatch({ok, <<"apple">>}, cursor_next_value(Cursor)), + ?assertMatch(ok, cursor_close(Cursor)) + end}, + {"insert/overwrite an item using a cursor", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch(ok, + cursor_insert(Cursor, <<"h">>, <<"huckleberry">>)), + ?assertMatch({ok, <<"huckleberry">>}, + cursor_search(Cursor, <<"h">>)), + ?assertMatch(ok, + cursor_insert(Cursor, <<"g">>, <<"grapefruit">>)), + ?assertMatch({ok, <<"grapefruit">>}, + cursor_search(Cursor, <<"g">>)), + ?assertMatch(ok, cursor_close(Cursor)), + ?assertMatch({ok, <<"grapefruit">>}, + session_get(SRef, "table:test", <<"g">>)), + ?assertMatch({ok, <<"huckleberry">>}, + session_get(SRef, "table:test", <<"h">>)) + end}, + {"update an item using a cursor", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch(ok, + cursor_update(Cursor, <<"g">>, <<"goji berries">>)), + ?assertMatch(not_found, + cursor_update(Cursor, <<"k">>, <<"kumquat">>)), + ?assertMatch(ok, cursor_close(Cursor)), + ?assertMatch({ok, <<"goji berries">>}, + session_get(SRef, "table:test", <<"g">>)) + end}, + {"remove an item using a cursor", + fun() -> + {ok, Cursor} = cursor_open(SRef, "table:test"), + ?assertMatch(ok, + cursor_remove(Cursor, <<"g">>, <<"goji berries">>)), + ?assertMatch(not_found, + cursor_remove(Cursor, <<"l">>, <<"lemon">>)), + ?assertMatch(ok, cursor_close(Cursor)), + ?assertMatch(not_found, + session_get(SRef, "table:test", <<"g">>)) + end}]} end}. -endif.