You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2014/08/01 11:06:21 UTC
[8/8] git commit: Add support for iteration
Add support for iteration
A basic iterator with accompanying fold/3 support.
Project: http://git-wip-us.apache.org/repos/asf/couchdb-khash/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-khash/commit/dd6a9035
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-khash/tree/dd6a9035
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-khash/diff/dd6a9035
Branch: refs/heads/windsor-merge
Commit: dd6a9035de3a0c298d8b4945401e42c32c9360ab
Parents: 4c04622
Author: Paul J. Davis <pa...@gmail.com>
Authored: Sat May 11 14:50:05 2013 -0500
Committer: Robert Newson <rn...@apache.org>
Committed: Wed Jul 30 17:16:33 2014 +0100
----------------------------------------------------------------------
c_src/khash.c | 118 +++++++++++++++++++++++++++++++++++++++++++++-
src/khash.erl | 59 +++++++++++++++++------
test/006-iterators.t | 115 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 276 insertions(+), 16 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/dd6a9035/c_src/khash.c
----------------------------------------------------------------------
diff --git a/c_src/khash.c b/c_src/khash.c
index de70490..038c81c 100644
--- a/c_src/khash.c
+++ b/c_src/khash.c
@@ -18,7 +18,10 @@ typedef struct
ERL_NIF_TERM atom_error;
ERL_NIF_TERM atom_value;
ERL_NIF_TERM atom_not_found;
+ ERL_NIF_TERM atom_end_of_table;
+ ERL_NIF_TERM atom_expired_iterator;
ErlNifResourceType* res_hash;
+ ErlNifResourceType* res_iter;
} khash_priv;
@@ -33,11 +36,21 @@ typedef struct
typedef struct
{
int version;
+ unsigned int gen;
hash_t* h;
ErlNifPid p;
} khash_t;
+typedef struct
+{
+ int version;
+ unsigned int gen;
+ khash_t* khash;
+ hscan_t scan;
+} khash_iter_t;
+
+
// This is actually an internal Erlang VM function that
// we're being a bit hacky to get access to. There's a
// pending patch to expose this in the NIF API in newer
@@ -146,6 +159,7 @@ khash_create_int(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM opts)
khash = (khash_t*) enif_alloc_resource(priv->res_hash, sizeof(khash_t));
memset(khash, '\0', sizeof(khash_t));
khash->version = KHASH_VERSION;
+ khash->gen = 0;
khash->h = kl_hash_create(HASHCOUNT_T_MAX, khash_cmp_fun, khash_hash_fun);
@@ -257,6 +271,8 @@ khash_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
kl_hash_free_nodes(khash->h);
+ khash->gen += 1;
+
return priv->atom_ok;
}
@@ -372,6 +388,8 @@ khash_put(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
node->val = enif_make_copy(node->env, argv[2]);
}
+ khash->gen += 1;
+
return priv->atom_ok;
}
@@ -404,6 +422,8 @@ khash_del(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
ret = priv->atom_ok;
}
+ khash->gen += 1;
+
return ret;
}
@@ -430,6 +450,91 @@ khash_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
+static ERL_NIF_TERM
+khash_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ khash_priv* priv = enif_priv_data(env);
+ khash_t* khash;
+ khash_iter_t* iter;
+ ERL_NIF_TERM ret;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_resource(env, argv[0], priv->res_hash, (void**) &khash)) {
+ return enif_make_badarg(env);
+ }
+
+ if(!check_pid(env, khash)) {
+ return enif_make_badarg(env);
+ }
+
+ iter = (khash_iter_t*) enif_alloc_resource(
+ priv->res_iter, sizeof(khash_iter_t));
+ memset(iter, '\0', sizeof(khash_iter_t));
+ iter->version = KHASH_VERSION;
+ iter->gen = khash->gen;
+ iter->khash = khash;
+ kl_hash_scan_begin(&(iter->scan), iter->khash->h);
+
+ // The iterator needs to guarantee that the khash
+ // remains alive for the life of the iterator.
+ enif_keep_resource(khash);
+
+ ret = enif_make_resource(env, iter);
+ enif_release_resource(iter);
+
+ return make_ok(env, priv, ret);
+}
+
+
+static void
+khash_iter_free(ErlNifEnv* env, void* obj)
+{
+ khash_iter_t* iter = (khash_iter_t*) obj;
+ enif_release_resource(iter);
+}
+
+
+static ERL_NIF_TERM
+khash_iter_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ khash_priv* priv = enif_priv_data(env);
+ khash_iter_t* iter;
+ hnode_t* entry;
+ khnode_t* node;
+ ERL_NIF_TERM key;
+ ERL_NIF_TERM val;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_resource(env, argv[0], priv->res_iter, (void**) &iter)) {
+ return enif_make_badarg(env);
+ }
+
+ if(!check_pid(env, iter->khash)) {
+ return enif_make_badarg(env);
+ }
+
+ if(iter->gen != iter->khash->gen) {
+ return make_error(env, priv, priv->atom_expired_iterator);
+ }
+
+ entry = kl_hash_scan_next(&(iter->scan));
+ if(entry == NULL) {
+ return priv->atom_end_of_table;
+ }
+
+ node = (khnode_t*) kl_hnode_getkey(entry);
+ key = enif_make_copy(env, node->key);
+ val = enif_make_copy(env, node->val);
+ return enif_make_tuple2(env, key, val);
+}
+
+
static int
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
{
@@ -442,17 +547,24 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
return 1;
}
- res = enif_open_resource_type(env, mod, mod, khash_free, flags, NULL);
+ res = enif_open_resource_type(env, mod, "", khash_free, flags, NULL);
if(res == NULL) {
return 1;
}
-
new_priv->res_hash = res;
+ res = enif_open_resource_type(env, mod, "", khash_iter_free, flags, NULL);
+ if(res == NULL) {
+ return 1;
+ }
+ new_priv->res_iter = res;
+
new_priv->atom_ok = make_atom(env, "ok");
new_priv->atom_error = make_atom(env, "error");
new_priv->atom_value = make_atom(env, "value");
new_priv->atom_not_found = make_atom(env, "not_found");
+ new_priv->atom_end_of_table = make_atom(env, "end_of_table");
+ new_priv->atom_expired_iterator = make_atom(env, "expired_iterator");
*priv = (void*) new_priv;
@@ -491,6 +603,8 @@ static ErlNifFunc funcs[] = {
{"put", 3, khash_put},
{"del", 2, khash_del},
{"size", 1, khash_size},
+ {"iter", 1, khash_iter},
+ {"iter_next", 1, khash_iter_next}
};
http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/dd6a9035/src/khash.erl
----------------------------------------------------------------------
diff --git a/src/khash.erl b/src/khash.erl
index 5156d39..7b474bd 100644
--- a/src/khash.erl
+++ b/src/khash.erl
@@ -18,7 +18,10 @@
get/3,
put/3,
del/2,
- size/1
+ size/1,
+ iter/1,
+ iter_next/1,
+ fold/3
]).
@@ -26,26 +29,27 @@
-type kv() :: {any(), any()}.
--type hash() :: term().
+-type khash() :: term().
+-type khash_iter() :: term().
-type option() :: [].
--spec new() -> {ok, hash()}.
+-spec new() -> {ok, khash()}.
new() ->
new([]).
--spec new([option()]) -> {ok, hash()}.
+-spec new([option()]) -> {ok, khash()}.
new(_Options) ->
?NOT_LOADED.
--spec from_list([kv()]) -> {ok, hash()}.
+-spec from_list([kv()]) -> {ok, khash()}.
from_list(KVList) ->
from_list(KVList, []).
--spec from_list([kv()], [option()]) -> {ok, hash()}.
+-spec from_list([kv()], [option()]) -> {ok, khash()}.
from_list(KVList, Options) ->
{ok, Hash} = ?MODULE:new(Options),
lists:foreach(fun({Key, Val}) ->
@@ -54,46 +58,73 @@ from_list(KVList, Options) ->
{ok, Hash}.
--spec to_list(hash()) -> [kv()].
+-spec to_list(khash()) -> [kv()].
to_list(_Hash) ->
?NOT_LOADED.
--spec clear(hash()) -> ok.
+-spec clear(khash()) -> ok.
clear(_Hash) ->
?NOT_LOADED.
--spec lookup(hash(), any()) -> {value, any()} | not_found.
+-spec lookup(khash(), any()) -> {value, any()} | not_found.
lookup(_Hash, _Key) ->
?NOT_LOADED.
--spec get(hash(), any()) -> any().
+-spec get(khash(), any()) -> any().
get(Hash, Key) ->
get(Hash, Key, undefined).
--spec get(hash(), any(), any()) -> any().
+-spec get(khash(), any(), any()) -> any().
get(_Hash, _Key, _Default) ->
?NOT_LOADED.
--spec put(hash(), any(), any()) -> ok.
+-spec put(khash(), any(), any()) -> ok.
put(_Hash, _Key, _Value) ->
?NOT_LOADED.
--spec del(hash(), any()) -> ok.
+-spec del(khash(), any()) -> ok.
del(_Hash, _Key) ->
?NOT_LOADED.
--spec size(hash()) -> non_neg_integer().
+-spec size(khash()) -> non_neg_integer().
size(_Hash) ->
?NOT_LOADED.
+-spec iter(khash()) -> {ok, khash_iter()}.
+iter(_Hash) ->
+ ?NOT_LOADED.
+
+
+-spec iter_next(khash_iter()) ->
+ kv() | end_of_table | {error, expired_iterator}.
+iter_next(_Iter) ->
+ ?NOT_LOADED.
+
+
+-spec fold(khash(), fun(), any()) -> any().
+fold(Hash, FoldFun, Acc) ->
+ {ok, Iter} = ?MODULE:iter(Hash),
+ fold_int(Iter, FoldFun, Acc).
+
+
+fold_int(Iter, FoldFun, Acc) ->
+ case ?MODULE:iter_next(Iter) of
+ {Key, Value} ->
+ NewAcc = FoldFun(Key, Value, Acc),
+ fold_int(Iter, FoldFun, NewAcc);
+ end_of_table ->
+ Acc
+ end.
+
+
init() ->
PrivDir = case code:priv_dir(?MODULE) of
{error, _} ->
http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/dd6a9035/test/006-iterators.t
----------------------------------------------------------------------
diff --git a/test/006-iterators.t b/test/006-iterators.t
new file mode 100755
index 0000000..02a7478
--- /dev/null
+++ b/test/006-iterators.t
@@ -0,0 +1,115 @@
+#! /usr/bin/env escript
+%% This file is part of khash released under the MIT license.
+%% See the LICENSE file for more information.
+%% Copyright 2013 Cloudant, Inc <su...@cloudant.com>
+
+-mode(compile).
+
+main([]) ->
+ code:add_pathz("test"),
+ util:run(12, fun() ->
+ test(),
+ ok
+ end).
+
+
+test() ->
+ test_basic(),
+ test_multi(),
+ test_expiration(),
+ test_no_expiration(),
+ ok.
+
+
+test_basic() ->
+ {ok, H} = khash:new(),
+ khash:put(H, foo, bar),
+ {ok, I} = khash:iter(H),
+ etap:is(khash:iter_next(I), {foo,bar}, "Got only kv pair as first element"),
+ etap:is(khash:iter_next(I), end_of_table, "Only the one kv pair exists"),
+ FoldFun = fun(K, V, Acc) -> [{K,V} | Acc] end,
+ etap:is(khash:fold(H, FoldFun, []), [{foo,bar}], "fold works").
+
+
+test_multi() ->
+ {ok, H} = khash:new(),
+ KVs = [{I, I} || I <- lists:seq(1, 10)],
+ lists:foreach(fun({K,V}) -> khash:put(H, K, V) end, KVs),
+ {ok, I} = khash:iter(H),
+ ReadKVs = test_multi_read(I, []),
+ etap:is(ReadKVs, KVs, "Read the same exact key/val pairs").
+
+
+test_multi_read(Iter, KVs) ->
+ case khash:iter_next(Iter) of
+ {K, V} ->
+ test_multi_read(Iter, [{K,V} | KVs]);
+ end_of_table ->
+ lists:sort(KVs)
+ end.
+
+
+test_expiration() ->
+ test_expiration("put", fun(H) ->
+ khash:put(H, foo, bar2),
+ ok
+ end),
+
+ test_expiration("del", fun(H) ->
+ khash:del(H, foo),
+ ok
+ end),
+
+ test_expiration("clear", fun(H) ->
+ khash:clear(H),
+ ok
+ end).
+
+
+test_expiration(FunName, Fun) ->
+ Error = {error, expired_iterator},
+ {ok, H} = khash:new(),
+ khash:put(H, foo, bar),
+ {ok, I} = khash:iter(H),
+ ok = Fun(H),
+ Msg = FunName ++ " expires iterators",
+ etap:is(khash:iter_next(I), Error, Msg).
+
+
+test_no_expiration() ->
+ test_no_expiration("to_list", fun(H) ->
+ [{foo,bar}] = khash:to_list(H),
+ ok
+ end),
+
+ test_no_expiration("lookup", fun(H) ->
+ {value,bar} = khash:lookup(H,foo),
+ ok
+ end),
+
+ test_no_expiration("get", fun(H) ->
+ bar = khash:get(H, foo),
+ ok
+ end),
+
+ test_no_expiration("size", fun(H) ->
+ 1 = khash:size(H),
+ ok
+ end),
+
+ test_no_expiration("iteration", fun(H) ->
+ {ok, I} = khash:iter(H),
+ {foo, bar} = khash:iter_next(I),
+ end_of_table = khash:iter_next(I),
+ ok
+ end).
+
+
+test_no_expiration(FunName, Fun) ->
+ Error = {error, expired_iterator},
+ {ok, H} = khash:new(),
+ khash:put(H, foo, bar),
+ {ok, I} = khash:iter(H),
+ ok = Fun(H),
+ Msg = FunName ++ " doesn't expire iterators",
+ etap:isnt(khash:iter_next(I), Error, Msg).