Skip to content

Commit

Permalink
Add eval command, edit Readme file
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien Moreau committed May 24, 2016
1 parent 65418ee commit 5deb6f4
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 26 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.erl]
indent_style = space
indent_size = 4
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
## Description

eredis_cluster is a wrapper for eredis to support cluster mode of redis 3.0.0+
This project is under development.

## TODO

- Fix/Add specs of functions
- Add safeguard if keys of a pipeline command is not located in the same server
- Improve unit tests
- Improve test suite to demonstrate the case where redis cluster is crashing,
resharding, recovering...

## Compilation && Test

Expand Down Expand Up @@ -51,6 +49,11 @@ eredis_cluster:q(["GET","abc"]).
%% Pipeline
eredis_cluster:qp([["LPUSH", "a", "a"], ["LPUSH", "a", "b"], ["LPUSH", "a", "c"]]).

%% Pipeline in multiple node (keys are sorted by node, a pipeline request is
%% made on each node, then the result is aggregated and returned. The response
%% keep the command order
eredis_cluster:qmn([["GET", "a"], ["GET", "b"], ["GET", "c"]]).

%% Transaction
eredis_cluster:transaction([["LPUSH", "a", "a"], ["LPUSH", "a", "b"], ["LPUSH", "a", "c"]]).

Expand Down Expand Up @@ -84,6 +87,13 @@ eredis_cluster:update_key("abc", Fun).
Fun = fun(Var) -> binary_to_integer(Var) + 1 end,
eredis_cluster:update_hash_field("abc", "efg", Fun).

%% Eval script, both script and hash are necessary to execute the command,
%% the script hash should be precomputed at compile time otherwise, it will
%% execute it at each request. Could be solved by using a macro though.
Script = "return redis.call('set', KEYS[1], ARGV[1]);",
ScriptHash = "4bf5e0d8612687699341ea7db19218e83f77b7cf",
eredis_cluster:eval(Script, ScriptHash, ["abc"], ["123"]).

%% Flush DB
eredis_cluster:flushdb().

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.6
0.5.7
2 changes: 1 addition & 1 deletion src/eredis_cluster.app.src
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{application, eredis_cluster, [
{description, "Eredis Cluster"},
{vsn, "0.5.6"},
{vsn, "0.5.7"},
{modules, []},
{registered, []},
{applications, [
Expand Down
66 changes: 46 additions & 20 deletions src/eredis_cluster.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
-export([update_key/2]).
-export([update_hash_field/3]).
-export([optimistic_locking_transaction/3]).
-export([eval/4]).

-include("eredis_cluster.hrl").

Expand Down Expand Up @@ -81,26 +82,6 @@ transaction(Transaction, Slot, ExpectedValue, Counter) ->
Payload
end.

%% =============================================================================
%% @doc Wrapper function for command using pipelined commands
%% @end
%% =============================================================================
-spec qp(redis_pipeline_command()) -> redis_pipeline_result().
qp(Commands) -> q(Commands).

%% =============================================================================
%% @doc This function execute simple or pipelined command on a single redis node
%% the node will be automatically found according to the key used in the command
%% @end
%% =============================================================================
-spec q(redis_command()) -> redis_result().
q(Command) ->
query(Command).

-spec qk(redis_command(), bitstring()) -> redis_result().
qk(Command, PoolKey) ->
query(Command, PoolKey).

%% =============================================================================
%% @doc Multi node query
%% @end
Expand Down Expand Up @@ -148,6 +129,26 @@ split_by_pools([], _Index, CmdAcc, MapAcc) ->
MapAcc2 = [{Pool, lists:reverse(Mapping)} || {Pool, Mapping} <- MapAcc],
{CmdAcc2, MapAcc2}.

%% =============================================================================
%% @doc Wrapper function for command using pipelined commands
%% @end
%% =============================================================================
-spec qp(redis_pipeline_command()) -> redis_pipeline_result().
qp(Commands) -> q(Commands).

%% =============================================================================
%% @doc This function execute simple or pipelined command on a single redis node
%% the node will be automatically found according to the key used in the command
%% @end
%% =============================================================================
-spec q(redis_command()) -> redis_result().
q(Command) ->
query(Command).

-spec qk(redis_command(), bitstring()) -> redis_result().
qk(Command, PoolKey) ->
query(Command, PoolKey).

query(Command) ->
PoolKey = get_key_from_command(Command),
query(Command, PoolKey).
Expand Down Expand Up @@ -269,6 +270,31 @@ optimistic_locking_transaction(WatchedKey, GetCommand, UpdateFunction) ->
Error
end.

%% =============================================================================
%% @doc Eval command helper, to optimize the query, it will try to execute the
%% script using its hashed value. If no script is found, it will load it and
%% try again.
%% @end
%% =============================================================================
-spec eval(bitstring(), bitstring(), [bitstring()], [bitstring()]) ->
redis_result().
eval(Script, ScriptHash, Keys, Args) ->
KeyNb = length(Keys),
EvalShaCommand = ["EVALSHA", ScriptHash, KeyNb] ++ Keys ++ Args,
Key = if
KeyNb == 0 -> "A"; %Random key
true -> hd(Keys)
end,

case qk(EvalShaCommand, Key) of
{error, <<"NOSCRIPT", _/binary>>} ->
LoadCommand = ["SCRIPT", "LOAD", Script],
[_, Result] = qk([LoadCommand, EvalShaCommand], Key),
Result;
Result ->
Result
end.

%% =============================================================================
%% @doc Perform a given query on all node of a redis cluster
%% @end
Expand Down
9 changes: 9 additions & 0 deletions test/eredis_cluster_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ basic_test_() ->
?assertEqual([{<<"0">>,3},{<<"0">>,4},{<<"0">>,5},{<<"0">>,6},{<<"0">>,7}], lists:sort(IntermediateValues)),
?assertEqual({ok, <<"7">>}, eredis_cluster:q(["hget", "klm", "nop"]))
end
},

{ "eval",
fun () ->
Script = <<"return redis.call('set', KEYS[1], ARGV[1]);">>,
ScriptHash = << << if N >= 10 -> N -10 + $a; true -> N + $0 end >> || <<N:4>> <= crypto:hash(sha, Script) >>,
eredis_cluster:eval(Script, ScriptHash, ["qrs"], ["evaltest"]),
?assertEqual({ok, <<"evaltest">>}, eredis_cluster:q(["get", "qrs"]))
end
}

]
Expand Down

0 comments on commit 5deb6f4

Please sign in to comment.