diff --git a/src/eredis_cluster.erl b/src/eredis_cluster.erl index 1da3a9f..4b9a650 100644 --- a/src/eredis_cluster.erl +++ b/src/eredis_cluster.erl @@ -11,8 +11,10 @@ % Generic redis call -export([q/1, qp/1, qw/2, qk/2, qa/1, qmn/1, transaction/1, transaction/2]). +-export([get_key_slot/1]). + % Specific redis command implementation --export([flushdb/0]). +-export([flushdb/0, load_script/1, scan/5]). % Helper functions -export([update_key/2]). @@ -170,6 +172,20 @@ query(Command) -> query(_, undefined) -> {error, invalid_cluster_command}; +%% Load LUA script to all instances: +query(Command , load_script) -> + case qa(Command) of + Result when is_list(Result) -> + case proplists:lookup(error,Result) of + none -> + [{ok,SHA1}|_] = Result, + {ok, SHA1}; + Error -> + Error + end; + Result -> + Result + end; query(Command, PoolKey) -> Slot = get_key_slot(PoolKey), Transaction = fun(Worker) -> qw(Worker, Command) end, @@ -190,9 +206,9 @@ query(Transaction, Slot, Counter) -> end. handle_transaction_result(Result, Version) -> - case Result of + case Result of % If we detect a node went down, we should probably refresh the slot - % mapping. + % mapping. {error, no_connection} -> eredis_cluster_monitor:refresh_mapping(Version), retry; @@ -298,7 +314,7 @@ optimistic_locking_transaction(WatchedKey, GetCommand, UpdateFunction) -> RedisResult = qw(Worker, [["MULTI"]] ++ UpdateCommand ++ [["EXEC"]]), {lists:last(RedisResult), Result} end, - case transaction(Transaction, Slot, {ok, undefined}, ?OL_TRANSACTION_TTL) of + case transaction(Transaction, Slot, {ok, undefined}, ?OL_TRANSACTION_TTL) of {{ok, undefined}, _} -> {error, resource_busy}; {{ok, TransactionResult}, UpdateResult} -> @@ -365,6 +381,39 @@ flushdb() -> Error end. +%% ============================================================================= +%% @doc Load LUA script to all master nodes in the Redis cluster. +%% @end +%% ============================================================================= +-spec load_script(string()) -> redis_result(). +load_script(Script) -> + Command = ["SCRIPT", "LOAD", Script], + query(Command , load_script). + +%% ============================================================================= +%% @doc Perform scan command on a specified node in the Redis cluster. +%% @end +%% ============================================================================= +-spec scan(Pool::atom(), ScanCursor::integer(), ScanPattern::string(), + ScanCount::integer(), RetryCounter::integer()) + -> redis_result() | {error, Reason::bitstring()}. +scan(_, _, _, _, ?REDIS_CLUSTER_REQUEST_TTL) -> + {error, no_connection}; +scan(Pool, ScanCursor, ScanPattern, ScanCount, RetryCounter) when is_list(ScanPattern) -> + Command = ["scan", ScanCursor, "match", ScanPattern, "count", ScanCount], + Transaction = fun(Worker) -> qw(Worker, Command) end, + + throttle_retries(RetryCounter), + Result = eredis_cluster_pool:transaction(Pool, Transaction), + + State = eredis_cluster_monitor:get_state(), + case handle_transaction_result(Result, + eredis_cluster_monitor:get_state_version(State)) + of + retry -> scan(Pool, ScanCursor, ScanPattern, ScanCount, RetryCounter + 1); + Result -> Result + end. + %% ============================================================================= %% @doc Return the hash slot from the key %% @end diff --git a/test/eredis_cluster_tests.erl b/test/eredis_cluster_tests.erl index 7b8a7ce..dbbfb0f 100644 --- a/test/eredis_cluster_tests.erl +++ b/test/eredis_cluster_tests.erl @@ -163,6 +163,37 @@ basic_test_() -> eredis_cluster:eval(Script, ScriptHash, ["qrs"], ["evaltest"]), ?assertEqual({ok, <<"evaltest">>}, eredis_cluster:q(["get", "qrs"])) end + }, + + { "load script and evalsha", + fun () -> + eredis_cluster:q(["hset", "klm", "rst", 10]), + S = "local k = KEYS[1] + if redis.call('exists', k) == 1 then + return redis.call('hvals', k) + end", + {ok, Hash} = eredis_cluster:load_script(S), + ?assertEqual({ok, [<<"10">>, <<"7">>]}, eredis_cluster:eval(S, Hash, ["klm"], [])) + end + }, + + { "scan", + fun () -> + Key1 = "scan{1}return1", + Key2 = "scan{1}return2", + Key3 = "noscan{1}return", + eredis_cluster:q(["set", Key1, "test"]), + eredis_cluster:q(["set", Key2, "test"]), + eredis_cluster:q(["set", Key3, "test"]), + + Pool = element(1, eredis_cluster_monitor:get_pool_by_slot(eredis_cluster:get_key_slot(Key1))), + {ok,[<<_Cursor>>, RetKeys]} = eredis_cluster:scan(Pool, 0, "scan{1}return*", 5000, 0), + + StrKeys = lists:map(fun(Key) -> binary_to_list(Key) end, RetKeys), + ?assertEqual(true, lists:member(Key1, StrKeys)), + ?assertEqual(true, lists:member(Key2, StrKeys)), + ?assertEqual(false, lists:member(Key3, StrKeys)) + end } ]