Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

ZREV* ready

  • Loading branch information...
commit edc1f388d5f63891d9aef4ff52aca4b2cc3e2b49 1 parent e047f40
Fernando 'Brujo' Benavides authored
View
72 src/edis_command_runner.erl
@@ -835,6 +835,78 @@ run_command(<<"ZREMRANGEBYSCORE">>, [Key, Min, Max], State) ->
tcp_number(0, State)
end;
run_command(<<"ZREMRANGEBYSCORE">>, _, _State) -> throw(bad_arg_num);
+run_command(<<"ZREVRANGE">>, [Key, Start, Stop], State) ->
+ Reply =
+ [Member ||
+ {_Score, Member} <- edis_db:zrev_range(State#state.db, Key,
+ edis_util:binary_to_integer(Start, 0),
+ edis_util:binary_to_integer(Stop, 0))],
+ tcp_multi_bulk(Reply, State);
+run_command(<<"ZREVRANGE">>, [Key, Start, Stop, Option], State) ->
+ case edis_util:upper(Option) of
+ <<"WITHSCORES">> ->
+ Reply =
+ lists:flatten(
+ [[Member, Score] ||
+ {Score, Member} <- edis_db:zrev_range(State#state.db, Key,
+ edis_util:binary_to_integer(Start, 0),
+ edis_util:binary_to_integer(Stop, 0))]),
+ tcp_multi_bulk(Reply, State);
+ _ ->
+ throw(syntax)
+ end;
+run_command(<<"ZREVRANGE">>, _, _State) -> throw(bad_arg_num);
+run_command(<<"ZREVRANGEBYSCORE">>, [Key, Min, Max | Options], State) when 0 =< length(Options),
+ length(Options) =< 4->
+ {ShowScores, Limit} =
+ case lists:map(fun edis_util:upper/1, Options) of
+ [] -> {false, undefined};
+ [<<"WITHSCORES">>] -> {true, undefined};
+ [<<"LIMIT">>, Offset, Count] ->
+ {false, {edis_util:binary_to_integer(Offset, 0),
+ edis_util:binary_to_integer(Count, 0)}};
+ [<<"WITHSCORES">>, <<"LIMIT">>, Offset, Count] ->
+ {true, {edis_util:binary_to_integer(Offset, 0), edis_util:binary_to_integer(Count, 0)}};
+ [<<"LIMIT">>, Offset, Count, <<"WITHSCORES">>] ->
+ {true, {edis_util:binary_to_integer(Offset, 0), edis_util:binary_to_integer(Count, 0)}};
+ _ ->
+ throw(syntax)
+ end,
+
+ Range =
+ try edis_db:zrev_range_by_score(State#state.db,
+ Key, parse_float_limit(Min), parse_float_limit(Max))
+ catch
+ _:not_float ->
+ throw({not_float, "min or max"})
+ end,
+
+ Reply =
+ case {ShowScores, Limit} of
+ {false, undefined} ->
+ [Member || {_Score, Member} <- Range];
+ {true, undefined} ->
+ lists:flatten([[Member, Score] || {Score, Member} <- Range]);
+ {_, {_Off, 0}} ->
+ [];
+ {_, {Off, _Lim}} when Off < 0 ->
+ [];
+ {_, {Off, _Lim}} when Off >= length(Range) ->
+ [];
+ {false, {Off, Lim}} when Lim < 0 ->
+ [Member || {_Score, Member} <- lists:nthtail(Off, Range)];
+ {true, {Off, Lim}} when Lim < 0 ->
+ lists:flatten([[Member, Score] || {Score, Member} <- lists:nthtail(Off, Range)]);
+ {false, {Off, Lim}} ->
+ [Member || {_Score, Member} <- lists:sublist(Range, Off+1, Lim)];
+ {true, {Off, Lim}} ->
+ lists:flatten([[Member, Score] || {Score, Member} <- lists:sublist(Range, Off+1, Lim)])
+ end,
+ tcp_multi_bulk(Reply, State);
+run_command(<<"ZREVRANGEBYSCORE">>, _, _State) -> throw(bad_arg_num);
+run_command(<<"ZREVRANK">>, [Key, Member], State) ->
+ tcp_number(edis_db:zrev_rank(State#state.db, Key, Member), State);
+run_command(<<"ZREVRANK">>, _, _State) -> throw(bad_arg_num);
%% -- Server ---------------------------------------------------------------------------------------
run_command(<<"CONFIG">>, [SubCommand | Rest], State) ->
View
134 src/edis_db.erl
@@ -51,7 +51,8 @@
-export([sadd/3, scard/2, sdiff/2, sdiff_store/3, sinter/2, sinter_store/3, sismember/3, smembers/2,
smove/4, spop/2, srand_member/2, srem/3, sunion/2, sunion_store/3]).
-export([zadd/3, zcard/2, zcount/4, zincr/4, zinter_store/4, zrange/4, zrange_by_score/4, zrank/3,
- zrem/3, zrem_range_by_rank/4, zrem_range_by_score/4]).
+ zrem/3, zrem_range_by_rank/4, zrem_range_by_score/4, zrev_range/4, zrev_range_by_score/4,
+ zrev_rank/3]).
%% =================================================================================================
%% External functions
@@ -431,6 +432,18 @@ zrem_range_by_rank(Db, Key, Start, Stop) ->
zrem_range_by_score(Db, Key, Min, Max) ->
make_call(Db, {zrem_range_by_score, Key, Min, Max}).
+-spec zrev_range(atom(), binary(), integer(), integer()) -> [{float(), binary()}].
+zrev_range(Db, Key, Start, Stop) ->
+ make_call(Db, {zrev_range, Key, Start, Stop}).
+
+-spec zrev_range_by_score(atom(), binary(), float_limit(), float_limit()) -> non_neg_integer().
+zrev_range_by_score(Db, Key, Min, Max) ->
+ make_call(Db, {zrev_range_by_score, Key, Min, Max}).
+
+-spec zrev_rank(atom(), binary(), binary()) -> undefined | non_neg_integer().
+zrev_rank(Db, Key, Member) ->
+ make_call(Db, {zrev_rank, Key, Member}).
+
%% =================================================================================================
%% Server functions
%% =================================================================================================
@@ -1674,12 +1687,12 @@ handle_call({zrange, Key, Start, Stop}, _From, State) ->
Stop when Stop < (-1)*L -> 0;
Stop -> L + 1 + Stop
end,
- case StopPos - StartPos + 1 of
- Len when Len =< 0 -> {ok, []};
- Len ->
+ case StopPos of
+ StopPos when StopPos < StartPos -> {ok, []};
+ StopPos ->
+ Iterator = zsets:iterator(Value),
{ok,
- zsets:to_list(
- zsets:subset(Value, StartPos, Len))}
+ zsets_range(StartPos, StopPos, Iterator)}
end;
not_found -> {ok, []};
{error, Reason} -> {error, Reason}
@@ -1748,6 +1761,64 @@ handle_call({zrem_range_by_score, Key, Min, Max}, From, State) ->
OtherReply ->
OtherReply
end;
+handle_call({zrev_range, Key, Start, Stop}, _From, State) ->
+ Reply =
+ try
+ case get_item(State#state.db, zset, Key) of
+ #edis_item{value = Value} ->
+ L = zsets:size(Value),
+ StartPos =
+ case Start of
+ Start when Start >= L -> throw(empty);
+ Start when Start >= 0 -> Start + 1;
+ Start when Start < (-1)*L -> 1;
+ Start -> L + 1 + Start
+ end,
+ StopPos =
+ case Stop of
+ Stop when Stop >= 0, Stop >= L -> L;
+ Stop when Stop >= 0 -> Stop + 1;
+ Stop when Stop < (-1)*L -> 0;
+ Stop -> L + 1 + Stop
+ end,
+ case StopPos of
+ StopPos when StopPos < StartPos -> {ok, []};
+ StopPos ->
+ Iterator = zsets:iterator(Value, backwards),
+ {ok,
+ zsets_range(StartPos, StopPos, Iterator)}
+ end;
+ not_found -> {ok, []};
+ {error, Reason} -> {error, Reason}
+ end
+ catch
+ _:empty -> {ok, []}
+ end,
+ {reply, Reply, stamp(Key, State)};
+handle_call({zrev_range_by_score, Key, Min, Max}, _From, State) ->
+ Reply =
+ case get_item(State#state.db, zset, Key) of
+ #edis_item{value = Value} ->
+ Iterator = zsets:iterator(Value, backwards),
+ {ok, zsets_list(Min, Max, Iterator)};
+ not_found -> {ok, 0};
+ {error, Reason} -> {error, Reason}
+ end,
+ {reply, Reply, stamp(Key, State)};
+handle_call({zrev_rank, Key, Member}, _From, State) ->
+ Reply =
+ case get_item(State#state.db, zset, Key) of
+ #edis_item{value = Value} ->
+ case zsets:find(Member, Value) of
+ error -> {ok, undefined};
+ {ok, Score} ->
+ Iterator = zsets:iterator(Value, backwards),
+ {ok, zsets_count(infinity, {exc, Score}, Iterator)}
+ end;
+ not_found -> {ok, undefined};
+ {error, Reason} -> {error, Reason}
+ end,
+ {reply, Reply, stamp(Key, State)};
handle_call(X, _From, State) ->
{stop, {unexpected_request, X}, {unexpected_request, X}, State}.
@@ -2004,38 +2075,61 @@ zsets_count(Min, Max, Iterator, Acc) ->
case zsets:next(Iterator) of
none -> Acc;
{Score, _Value, NextIterator} ->
- case {check_limit(min, Min, Score), check_limit(max, Max, Score)} of
+ case {check_limit(min, Min, Score, zsets:direction(NextIterator)),
+ check_limit(max, Max, Score, zsets:direction(NextIterator))} of
{in, in} -> zsets_count(Min, Max, NextIterator, Acc + 1);
{_, out} -> Acc;
{out, in} -> zsets_count(Min, Max, NextIterator, Acc)
end
end.
+zsets_range(Start, Stop, Iter) ->
+ lists:reverse(zsets_range(Start, Stop, zsets:next(Iter), 1, [])).
+zsets_range(_Start, _Stop, none, _, Acc) -> Acc;
+zsets_range(Start, Stop, {_Score, _Member, Iter}, Pos, Acc) when Pos < Start ->
+ zsets_range(Start, Stop, zsets:next(Iter), Pos+1, Acc);
+zsets_range(Start, Stop, {Score, Member, Iter}, Pos, Acc) when Pos =< Stop ->
+ zsets_range(Start, Stop, zsets:next(Iter), Pos+1, [{Score, Member} | Acc]);
+zsets_range(_, _, _, _, Acc) -> Acc.
+
zsets_list(Min, Max, Iterator) ->
lists:reverse(zsets_list(Min, Max, Iterator, [])).
zsets_list(Min, Max, Iterator, Acc) ->
case zsets:next(Iterator) of
none -> Acc;
{Score, Member, NextIterator} ->
- case {check_limit(min, Min, Score), check_limit(max, Max, Score)} of
+ case {check_limit(min, Min, Score, zsets:direction(NextIterator)),
+ check_limit(max, Max, Score, zsets:direction(NextIterator))} of
{in, in} -> zsets_list(Min, Max, NextIterator, [{Score, Member}|Acc]);
{_, out} -> Acc;
{out, in} -> zsets_list(Min, Max, NextIterator, Acc)
end
end.
-check_limit(min, neg_infinity, _) -> in;
-check_limit(min, infinity, _) -> out;
-check_limit(min, {exc, Min}, Score) when Min < Score -> in;
-check_limit(min, {exc, Min}, Score) when Min >= Score -> out;
-check_limit(min, {inc, Min}, Score) when Min =< Score -> in;
-check_limit(min, {inc, Min}, Score) when Min > Score -> out;
-check_limit(max, neg_infinity, _) -> out;
-check_limit(max, infinity, _) -> in;
-check_limit(max, {exc, Max}, Score) when Max > Score -> in;
-check_limit(max, {exc, Max}, Score) when Max =< Score -> out;
-check_limit(max, {inc, Max}, Score) when Max >= Score -> in;
-check_limit(max, {inc, Max}, Score) when Max < Score -> out.
+check_limit(min, neg_infinity, _, forward) -> in;
+check_limit(min, infinity, _, forward) -> out;
+check_limit(min, {exc, Min}, Score, forward) when Min < Score -> in;
+check_limit(min, {exc, Min}, Score, forward) when Min >= Score -> out;
+check_limit(min, {inc, Min}, Score, forward) when Min =< Score -> in;
+check_limit(min, {inc, Min}, Score, forward) when Min > Score -> out;
+check_limit(max, neg_infinity, _, forward) -> out;
+check_limit(max, infinity, _, forward) -> in;
+check_limit(max, {exc, Max}, Score, forward) when Max > Score -> in;
+check_limit(max, {exc, Max}, Score, forward) when Max =< Score -> out;
+check_limit(max, {inc, Max}, Score, forward) when Max >= Score -> in;
+check_limit(max, {inc, Max}, Score, forward) when Max < Score -> out;
+check_limit(min, neg_infinity, _, backwards) -> out;
+check_limit(min, infinity, _, backwards) -> in;
+check_limit(min, {exc, Min}, Score, backwards) when Min > Score -> in;
+check_limit(min, {exc, Min}, Score, backwards) when Min =< Score -> out;
+check_limit(min, {inc, Min}, Score, backwards) when Min >= Score -> in;
+check_limit(min, {inc, Min}, Score, backwards) when Min < Score -> out;
+check_limit(max, neg_infinity, _, backwards) -> in;
+check_limit(max, infinity, _, backwards) -> out;
+check_limit(max, {exc, Max}, Score, backwards) when Max < Score -> in;
+check_limit(max, {exc, Max}, Score, backwards) when Max >= Score -> out;
+check_limit(max, {inc, Max}, Score, backwards) when Max =< Score -> in;
+check_limit(max, {inc, Max}, Score, backwards) when Max > Score -> out.
zsets_weighted_intersection(_Aggregate, [{ZSet, Weight}]) ->
zsets:map(fun(Score, _) -> Score * Weight end, ZSet);
View
41 src/edis_gb_trees.erl
@@ -0,0 +1,41 @@
+%%%-------------------------------------------------------------------
+%%% @author Fernando Benavides <fernando.benavides@inakanetworks.com>
+%%% @author Chad DePue <chad@inakanetworks.com>
+%%% @copyright (C) 2011 InakaLabs SRL
+%%% @doc Extension for gb_trees with a couple of extra functions
+%%% @end
+%%%-------------------------------------------------------------------
+-module(edis_gb_trees).
+-extends(gb_trees).
+-author('Fernando Benavides <fernando.benavides@inakanetworks.com>').
+-author('Chad DePue <chad@inakanetworks.com>').
+
+-export([rev_iterator/1, previous/1]).
+
+-type edis_gb_tree() :: gb_tree().
+
+%% @doc Returns a reverse iterator. It's just like the one returned by {@link iterator/1} but it
+%% traverses the tree in the exact opposite direction
+-spec rev_iterator(edis_gb_tree()) -> [gb_trees:gb_tree_node()].
+rev_iterator({_, T}) ->
+ rev_iterator_1(T).
+
+rev_iterator_1(T) ->
+ rev_iterator(T, []).
+
+%% The rev_iterator structure is really just a list corresponding to
+%% the call stack of an post-order traversal. This is quite fast.
+
+rev_iterator({_, _, _, nil} = T, As) ->
+ [T | As];
+rev_iterator({_, _, _, R} = T, As) ->
+ rev_iterator(R, [T | As]);
+rev_iterator(nil, As) ->
+ As.
+
+%% @doc Like {@link next/1} for reverse iterators
+-spec previous([gb_trees:gb_tree_node()]) -> 'none' | {term(), term(), [gb_trees:gb_tree_node()]}.
+previous([{X, V, T, _} | As]) ->
+ {X, V, rev_iterator(T, As)};
+previous([]) ->
+ none.
View
81 src/zsets.erl
@@ -10,20 +10,24 @@
-author('Chad DePue <chad@inakanetworks.com>').
-record(zset, {dict :: dict(),
- tree :: gb_tree()}).
+ tree :: edis_gb_trees:edis_gb_tree()}).
-opaque zset(_Scores, _Members) :: #zset{}.
--opaque iterator(_Scores, _Members) :: gb_tree:iter().
+-type direction() :: forward | backwards.
+-record(zset_iterator, {direction = forward :: direction(),
+ iterator :: edis_gb_tree:iter()}).
+
+-opaque iterator(_Scores, _Members) :: #zset_iterator{}.
-export_type([zset/2, iterator/2]).
-export([new/0, enter/2, enter/3, size/1, find/2, delete_any/2]).
--export([iterator/1, next/1, map/2, to_list/1, subset/3]).
+-export([iterator/1, iterator/2, direction/1, next/1, map/2, to_list/1]).
-export([intersection/2, intersection/3]).
%% @doc Creates an empty {@link zset(any(), any())}
-spec new() -> zset(any(), any()).
new() ->
- #zset{dict = dict:new(), tree = gb_trees:empty()}.
+ #zset{dict = dict:new(), tree = edis_gb_trees:empty()}.
%% @equiv enter(Score, Member, ZSet)
-spec enter({Score, Member}, zset(Scores, Members)) -> zset(Scores, Members) when is_subtype(Score, Scores), is_subtype(Member, Members).
@@ -37,11 +41,12 @@ enter(Score, Member, ZSet = #zset{}) ->
case dict:find(Member, ZSet#zset.dict) of
error ->
ZSet#zset{dict = dict:store(Member, Score, ZSet#zset.dict),
- tree = gb_trees:enter({Score, Member}, undefined, ZSet#zset.tree)};
+ tree = edis_gb_trees:enter({Score, Member}, undefined, ZSet#zset.tree)};
{ok, PrevScore} ->
ZSet#zset{dict = dict:store(Member, Score, ZSet#zset.dict),
- tree = gb_trees:enter({Score, Member}, undefined,
- gb_trees:delete({PrevScore, Member}, ZSet#zset.tree))}
+ tree = edis_gb_trees:enter({Score, Member}, undefined,
+ edis_gb_trees:delete({PrevScore, Member},
+ ZSet#zset.tree))}
end.
%% @doc Removes the node with key Key from Tree1 if the key is present in the tree, otherwise does
@@ -52,29 +57,47 @@ delete_any(Member, ZSet) ->
error -> ZSet;
{ok, Score} ->
ZSet#zset{dict = dict:erase(Member, ZSet#zset.dict),
- tree = gb_trees:delete_any({Score, Member}, ZSet#zset.tree)}
+ tree = edis_gb_trees:delete_any({Score, Member}, ZSet#zset.tree)}
end.
%% @doc Returns the size of the zset
-spec size(zset(any(), any())) -> non_neg_integer().
size(ZSet) ->
- gb_trees:size(ZSet#zset.tree).
+ edis_gb_trees:size(ZSet#zset.tree).
-%% @doc Returns an iterator that can be used for traversing the entries of Tree; see {@link next/1}.
+%% @equiv iterator(ZSet, forward).
-spec iterator(zset(Scores, Members)) -> iterator(Scores, Members).
iterator(ZSet) ->
- gb_trees:iterator(ZSet#zset.tree).
+ iterator(ZSet, forward).
+
+%% @doc Returns an iterator that can be used for traversing the entries of Tree; see {@link next/1}.
+-spec iterator(zset(Scores, Members), direction()) -> iterator(Scores, Members).
+iterator(ZSet, forward) ->
+ #zset_iterator{direction = forward,
+ iterator = edis_gb_trees:iterator(ZSet#zset.tree)};
+iterator(ZSet, backwards) ->
+ #zset_iterator{direction = backwards,
+ iterator = edis_gb_trees:rev_iterator(ZSet#zset.tree)}.
%% @doc Returns {Score, Member, Iter2} where Score is the smallest score referred to by the iterator
%% Iter1, and Iter2 is the new iterator to be used for traversing the remaining nodes, or the atom
%% none if no nodes remain.
-spec next(iterator(Scores, Members)) -> none | {Scores, Members, iterator(Scores, Members)}.
next(Iter1) ->
- case gb_trees:next(Iter1) of
+ Function =
+ case Iter1#zset_iterator.direction of
+ forward -> next;
+ backwards -> previous
+ end,
+ case edis_gb_trees:Function(Iter1#zset_iterator.iterator) of
none -> none;
- {{Score, Member}, _, Iter2} -> {Score, Member, Iter2}
+ {{Score, Member}, _, Iter2} -> {Score, Member, Iter1#zset_iterator{iterator = Iter2}}
end.
-
+
+%% @doc Returns the direction of the iterator
+-spec direction(iterator(_Scores, _Members)) -> direction().
+direction(Iter) -> Iter#zset_iterator.direction.
+
%% @doc This function searches for a key in a zset. Returns {ok, Score} where Score is the score
%% associated with Member, or error if the key is not present.
-spec find(Member, iterator(Scores, Members)) -> Scores when is_subtype(Member, Members).
@@ -103,17 +126,7 @@ map(Fun, ZSet) ->
%% @doc Converts the sorted set into a list of {Score, Member} pairs
-spec to_list(zset(Scores, Members)) -> [{Scores, Members}].
to_list(ZSet) ->
- gb_trees:keys(ZSet#zset.tree).
-
-%% @doc Returns the sub-zset of ZSet1 starting at Start and with (max) Len elements.
-%% It is not an error for Start+Len to exceed the length of the list.
--spec subset(zset(Scores, Members), pos_integer(), non_neg_integer()) -> zset(Scores, Members).
-subset(_ZSet, _Start, 0) -> new();
-subset(ZSet, Start, Len) ->
- Iter = iterator(ZSet),
- NewIter = skip(Start-1, Iter),
- take(Len, NewIter).
-
+ edis_gb_trees:keys(ZSet#zset.tree).
%% =================================================================================================
%% Private functions
@@ -131,20 +144,4 @@ intersection(Aggregate, [{M1, S1} | D1], [{M2, _S2} | D2], Acc) when M1 >= M2 ->
%% @private
map(_Fun, none, Acc) -> Acc;
map(Fun, {Score, Member, Iter}, Acc) ->
- map(Fun, next(Iter), enter(Fun(Score, Member), Member, Acc)).
-
-%% @private
-skip(0, Iter) -> Iter;
-skip(N, Iter) ->
- case next(Iter) of
- none -> Iter;
- {_S, _M, NextIter} -> skip(N-1, NextIter)
- end.
-
-%% @private
-take(Len, Iter) ->
- take(Len, next(Iter), new()).
-take(0, _Step, Acc) -> Acc;
-take(_N, none, Acc) -> Acc;
-take(N, {Score, Member, Iter}, Acc) ->
- take(N-1, next(Iter), enter(Score, Member, Acc)).
+ map(Fun, next(Iter), enter(Fun(Score, Member), Member, Acc)).
Please sign in to comment.
Something went wrong with that request. Please try again.