├── .gitignore ├── Makefile ├── README.md ├── itests ├── reddy_itest_keys.erl ├── reddy_itest_lists.erl ├── reddy_itests.erl └── reddy_itests.hrl ├── rebar └── src ├── reddy.app.src ├── reddy.hrl ├── reddy_app.erl ├── reddy_conn.erl ├── reddy_conn_sup.erl ├── reddy_hashes.erl ├── reddy_keys.erl ├── reddy_lists.erl ├── reddy_ops.erl ├── reddy_ops.hrl ├── reddy_pool.erl ├── reddy_pool_sup.erl ├── reddy_protocol.erl ├── reddy_server.erl ├── reddy_sets.erl ├── reddy_strings.erl ├── reddy_sup.erl ├── reddy_time.erl └── reddy_types.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/* 2 | .eunit/* 3 | itest_ebin/* 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: compile test 2 | 3 | compile: 4 | @./rebar compile 5 | 6 | test: 7 | @./rebar eunit 8 | 9 | eunit: 10 | @./rebar eunit 11 | 12 | clean: 13 | @./rebar clean 14 | @rm -rf itest_ebin 15 | 16 | itest_compile: 17 | @mkdir -p itest_ebin 18 | @cd itests;erlc -o ../itest_ebin *.erl 19 | 20 | itest: compile itest_compile 21 | erl -noshell -boot start_sasl -pa itest_ebin -pa ebin -eval 'reddy_itests:run().' -s erlang halt 22 | @rm -rf itest_ebin 23 | 24 | help: 25 | @echo 'Available targets are:' 26 | @echo ' all: compile & run unit tests (RECOMMENDED)' 27 | @echo ' compile: compile code only' 28 | @echo ' itest: executes integration tests (requires a running redis server)' 29 | @echo ' Use env vars $$REDIS_HOST and $$REDIS_PORT to use a specific' 30 | @echo ' server for integration tests.' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### What is reddy? 2 | reddy is simply my attempt at writing a better redis client in Erlang. It tries to address shortcomings 3 | I saw in other clients like: 4 | 5 | * Too low-level: Yes, you can manually shove binaries over a socket and make it work but that's hardly 6 | a satisfying solution. reddy strives to provide a low-friction, self-documenting API. redis' excellent 7 | command [docs](http://redis.io/commands) should serve equally well as docs for reddy's API. 8 | 9 | * No support for multiple connections: While reddy is packaged as a proper OTP application it doesn't 10 | make any assumptions about how you'd like to use it. Start as many connections as you need. 11 | 12 | * No asynchronous API: reddy combines redis' native pipelining with Erlang's actor concurrency to provide 13 | solid support for executing redis operations asynchronously. 14 | 15 | 16 | ### Synchronous API 17 | 1> application:start(reddy) 18 | ok 19 | 2> {ok, Conn} = reddy_conn:connect("127.0.0.1", 6379). 20 | {ok, <0.50.0>} 21 | 3> reddy_lists:lpush(Conn, <<"foo">>, <<"1">>). 22 | 1 23 | 4> reddy_lists:lpush(Conn, <<"foo">>, <<"2">>). 24 | 2 25 | 5> reddy_lists:lpop(Conn, <<"foo">>). 26 | <<"2">> 27 | 28 | ### Async API 29 | 1> application:start(reddy) 30 | ok 31 | 2> {ok, Conn} = reddy_conn:connect("127.0.0.1", 6379). 32 | {ok, <0.50.0>} 33 | 3> {ok, ResultId} = reddy_lists:lpush_(Conn, <<"bar">>, <<"1">>, true). 34 | {ok, #Ref<0.0.100>} 35 | 4> receive {ResultId, Result} -> Result end. 36 | 1 37 | 38 | ### Async fire n' forget API 39 | 1> application:start(reddy) 40 | ok 41 | 2> {ok, Conn} = reddy_conn:connect("127.0.0.1", 6379). 42 | {ok, <0.50.0>} 43 | %% Last function arg indicates whether or not the return value 44 | %% should be sent to the caller. Response is always parsed to prevent 45 | %% memory consumption due to accumulated pipelined responses. 46 | 3> ok = reddy_lists:lpush_(Conn, <<"bar">>, <<"1">>, false). 47 | ok 48 | 4> 49 | _Note: the trailing underscore on the function name indicates it is an async operation._ 50 | 51 | ### Connection pools 52 | 1> application:start(reddy) 53 | ok 54 | 2> reddy_pool:new_pool(production, [{ip, "127.0.0.1"}, {port, 6379}, {count, 10}]). 55 | {ok, <0.51.0>} 56 | 3> reddy_lists:lpush(production, <<"foo">>, 1). 57 | 1 58 | 4> {ok, ResultId} = reddy_lists:lpop_(production, <<"foo">>, true) 59 | {ok,#Ref<0.0.0.159>} 60 | 5> receive {ResultId, Results} -> Results end. 61 | <<"1">> 62 | 6> 63 | 64 | ### Set hashes as proplists 65 | 1> application:start(reddy) 66 | ok 67 | 2> {ok, Conn} = reddy_conn:connect("127.0.0.1", 6379). 68 | {ok, <0.50.0>} 69 | 3> reddy_hashes:hmset(C, <<"foo">>, [{today, <<"Tuesday">>}, {redis_is, <<"awesome">>}]). 70 | ok 71 | 4> reddy_hashes:hvals(C, <<"foo">>). 72 | [<<"Tuesday">>,<<"awesome">>] 73 | 74 | ### TODO 75 | * Support for ordered sets, pub/sub, transactions, and server commands 76 | * Connection pool support for keys 77 | * Pool management & introspection API 78 | * Keep-alive logic for pooled connections 79 | * More tests (!!!) 80 | -------------------------------------------------------------------------------- /itests/reddy_itest_keys.erl: -------------------------------------------------------------------------------- 1 | -module(reddy_itest_keys). 2 | 3 | -define(TEST_KEY1, <<"reddy_keys1">>). 4 | -define(TEST_KEY2, <<"reddy_keys2">>). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | -include("reddy_itests.hrl"). 8 | 9 | missing_key_test() -> 10 | {ok, C} = ?CONNECT(), 11 | reddy_keys:del(C, [?TEST_KEY1]), 12 | ?assertMatch(0, reddy_keys:exists(C, ?TEST_KEY1)), 13 | reddy_conn:close(C). 14 | 15 | del_key_test() -> 16 | {ok, C} = ?CONNECT(), 17 | reddy_keys:del(C, [?TEST_KEY1]), 18 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 19 | ?assertMatch(1, reddy_keys:exists(C, ?TEST_KEY1)), 20 | ?assertMatch(1, reddy_keys:del(C, [?TEST_KEY1])), 21 | reddy_keys:del(C, [?TEST_KEY1]), 22 | reddy_conn:close(C). 23 | 24 | expire_test() -> 25 | {ok, C} = ?CONNECT(), 26 | reddy_keys:del(C, [?TEST_KEY1]), 27 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 28 | ?assertMatch(1, reddy_keys:expire(C, ?TEST_KEY1, 1)), 29 | %% Give redis time to expire the key 30 | ?WAIT(2000), 31 | ?assertMatch(0, reddy_keys:exists(C, ?TEST_KEY1)), 32 | reddy_conn:close(C). 33 | 34 | expireat_test() -> 35 | {ok, C} = ?CONNECT(), 36 | reddy_keys:del(C, [?TEST_KEY1]), 37 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 38 | ?assertMatch(1, reddy_keys:expireat(C, ?TEST_KEY1, erlang:now())), 39 | %% Give redis time to expire the key 40 | ?WAIT(2000), 41 | ?assertMatch(0, reddy_keys:exists(C, ?TEST_KEY1)), 42 | reddy_conn:close(C). 43 | 44 | expireat_future_test() -> 45 | {ok, C} = ?CONNECT(), 46 | reddy_keys:del(C, [?TEST_KEY1]), 47 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 48 | Expiry = reddy_time:future_to_unixts(erlang:now(), 1), 49 | ?assertMatch(1, reddy_keys:expireat(C, ?TEST_KEY1, Expiry)), 50 | %% Give redis time to expire the key 51 | ?WAIT(2000), 52 | ?assertMatch(0, reddy_keys:exists(C, ?TEST_KEY1)), 53 | reddy_conn:close(C). 54 | 55 | keys_test() -> 56 | {ok, C} = ?CONNECT(), 57 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 58 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 59 | reddy_lists:lpush(C, ?TEST_KEY2, <<"two">>), 60 | ?assertMatch([?TEST_KEY1, ?TEST_KEY2], reddy_keys:keys(C, <<"reddy_*">>)), 61 | ?assertMatch([], reddy_keys:keys(C, <<"nokeys*">>)), 62 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 63 | reddy_conn:close(C). 64 | 65 | ttl_persist_test() -> 66 | {ok, C} = ?CONNECT(), 67 | reddy_keys:del(C, [?TEST_KEY1]), 68 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 69 | ?assertMatch(1, reddy_keys:expire(C, ?TEST_KEY1, 5)), 70 | ?assertMatch(5, reddy_keys:ttl(C, ?TEST_KEY1)), 71 | ?assertMatch(1, reddy_keys:persist(C, ?TEST_KEY1)), 72 | ?assertMatch(-1, reddy_keys:ttl(C, ?TEST_KEY1)), 73 | reddy_keys:del(C, [?TEST_KEY1]), 74 | reddy_conn:close(C). 75 | 76 | random_key_test() -> 77 | {ok, C} = ?CONNECT(), 78 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 79 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 80 | reddy_lists:lpush(C, ?TEST_KEY2, <<"two">>), 81 | Key = reddy_keys:randomkey(C), 82 | ?assert(lists:member(Key, [?TEST_KEY1, ?TEST_KEY2])), 83 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 84 | reddy_conn:close(C). 85 | 86 | rename_test() -> 87 | {ok, C} = ?CONNECT(), 88 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 89 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 90 | ?assertMatch(ok, reddy_keys:rename(C, ?TEST_KEY1, ?TEST_KEY2)), 91 | ?assertMatch([], reddy_keys:keys(C, ?TEST_KEY1)), 92 | ?assertMatch([?TEST_KEY2], reddy_keys:keys(C, ?TEST_KEY2)), 93 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 94 | reddy_conn:close(C). 95 | 96 | renamenx_test() -> 97 | {ok, C} = ?CONNECT(), 98 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 99 | ?assertMatch({error, _}, reddy_keys:renamenx(C, ?TEST_KEY1, ?TEST_KEY2)), 100 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 101 | ?assertMatch(1, reddy_keys:renamenx(C, ?TEST_KEY1, ?TEST_KEY2)), 102 | reddy_lists:lpush(C, ?TEST_KEY1, <<"one">>), 103 | ?assertMatch(0, reddy_keys:renamenx(C, ?TEST_KEY1, ?TEST_KEY2)), 104 | reddy_keys:del(C, [?TEST_KEY1, ?TEST_KEY2]), 105 | reddy_conn:close(C). 106 | -------------------------------------------------------------------------------- /itests/reddy_itest_lists.erl: -------------------------------------------------------------------------------- 1 | -module(reddy_itest_lists). 2 | 3 | -define(TEST_KEY1, <<"reddy_lists1">>). 4 | -define(TEST_KEY2, <<"reddy_lists2">>). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | -include("reddy_itests.hrl"). 8 | 9 | create_and_delete_test() -> 10 | {ok, C} = ?CONNECT(), 11 | reddy_keys:del(C, [?TEST_KEY1]), 12 | ?assertMatch(0, reddy_keys:exists(C, ?TEST_KEY1)), 13 | ?assertMatch(1, reddy_lists:lpush(C, ?TEST_KEY1, 1)), 14 | ?assertMatch(1, reddy_keys:exists(C, ?TEST_KEY1)), 15 | ?assertMatch(1, reddy_keys:del(C, [?TEST_KEY1])), 16 | ?assertMatch(0, reddy_keys:exists(C, ?TEST_KEY1)), 17 | reddy_keys:del(C, [?TEST_KEY1]), 18 | reddy_conn:close(C). 19 | 20 | lpush_and_lpop_test() -> 21 | {ok, C} = ?CONNECT(), 22 | reddy_keys:del(C, [?TEST_KEY1]), 23 | ?assertMatch(1, reddy_lists:lpush(C, ?TEST_KEY1, 1)), 24 | ?assertMatch(2, reddy_lists:lpush(C, ?TEST_KEY1, 2)), 25 | ?assertMatch(3, reddy_lists:lpush(C, ?TEST_KEY1, 3)), 26 | ?assertMatch(<<"3">>, reddy_lists:lpop(C, ?TEST_KEY1)), 27 | ?assertMatch(<<"2">>, reddy_lists:lpop(C, ?TEST_KEY1)), 28 | ?assertMatch(<<"1">>, reddy_lists:lpop(C, ?TEST_KEY1)), 29 | reddy_keys:del(C, [?TEST_KEY1]), 30 | reddy_conn:close(C). 31 | 32 | rpush_and_rpop_test() -> 33 | {ok, C} = ?CONNECT(), 34 | reddy_keys:del(C, [?TEST_KEY1]), 35 | ?assertMatch(1, reddy_lists:rpush(C, ?TEST_KEY1, 1)), 36 | ?assertMatch(2, reddy_lists:rpush(C, ?TEST_KEY1, 2)), 37 | ?assertMatch(3, reddy_lists:rpush(C, ?TEST_KEY1, 3)), 38 | ?assertMatch(<<"3">>, reddy_lists:rpop(C, ?TEST_KEY1)), 39 | ?assertMatch(<<"2">>, reddy_lists:rpop(C, ?TEST_KEY1)), 40 | ?assertMatch(<<"1">>, reddy_lists:rpop(C, ?TEST_KEY1)), 41 | reddy_keys:del(C, [?TEST_KEY1]), 42 | reddy_conn:close(C). 43 | 44 | linsert_lrange_test() -> 45 | {ok, C} = ?CONNECT(), 46 | reddy_keys:del(C, [?TEST_KEY1]), 47 | ?assertMatch(1, reddy_lists:rpush(C, ?TEST_KEY1, <<"Hello">>)), 48 | ?assertMatch(2, reddy_lists:rpush(C, ?TEST_KEY1, <<"World">>)), 49 | ?assertMatch(3, reddy_lists:linsert(C, ?TEST_KEY1, "BEFORE", <<"World">>, <<"There">>)), 50 | ?assertMatch([<<"Hello">>, 51 | <<"There">>, 52 | <<"World">>], reddy_lists:lrange(C, ?TEST_KEY1, 0, -1)), 53 | reddy_keys:del(C, [?TEST_KEY1]), 54 | reddy_conn:close(C). 55 | 56 | lrem_test() -> 57 | {ok, C} = ?CONNECT(), 58 | reddy_keys:del(C, [?TEST_KEY1]), 59 | reddy_lists:rpush(C, ?TEST_KEY1, <<"a">>), 60 | reddy_lists:rpush(C, ?TEST_KEY1, <<"a">>), 61 | reddy_lists:rpush(C, ?TEST_KEY1, <<"b">>), 62 | ?assertMatch(2, reddy_lists:lrem(C, ?TEST_KEY1, 0, <<"a">>)), 63 | ?assertMatch(1, reddy_lists:llen(C, ?TEST_KEY1)), 64 | reddy_keys:del(C, [?TEST_KEY1]), 65 | reddy_conn:close(C). 66 | 67 | rpoplpush_test() -> 68 | {ok, C} = ?CONNECT(), 69 | reddy_keys:del(C, [?TEST_KEY1]), 70 | reddy_keys:del(C, [?TEST_KEY2]), 71 | reddy_lists:lpush(C, ?TEST_KEY1, <<"World!">>), 72 | reddy_lists:lpush(C, ?TEST_KEY1, <<"Hello">>), 73 | ?assertMatch(<<"World!">>, reddy_lists:rpoplpush(C, ?TEST_KEY1, ?TEST_KEY2)), 74 | reddy_keys:del(C, [?TEST_KEY1]), 75 | reddy_keys:del(C, [?TEST_KEY2]), 76 | reddy_conn:close(C). 77 | 78 | blpop_two_conn_test() -> 79 | {ok, C1} = ?CONNECT(), 80 | {ok, C2} = ?CONNECT(), 81 | reddy_keys:del(C1, [?TEST_KEY1]), 82 | Me = self(), 83 | spawn(fun() -> 84 | Result = reddy_lists:blpop(C1, ?TEST_KEY1, 1), 85 | Me ! Result end), 86 | reddy_lists:rpush(C2, ?TEST_KEY1, <<"foo">>), 87 | ?assertMatch([?TEST_KEY1, <<"foo">>], receive 88 | M -> M 89 | end), 90 | reddy_keys:del(C2, [?TEST_KEY1]), 91 | reddy_conn:close(C1), 92 | reddy_conn:close(C2). 93 | 94 | blpop_one_conn_test() -> 95 | {ok, C1} = ?CONNECT(), 96 | reddy_keys:del(C1, [?TEST_KEY1]), 97 | Me = self(), 98 | spawn(fun() -> 99 | Result = reddy_lists:blpop(C1, ?TEST_KEY1, 1), 100 | Me ! Result end), 101 | reddy_lists:rpush(C1, ?TEST_KEY1, <<"foo">>), 102 | ?assertMatch([?TEST_KEY1, <<"foo">>], receive 103 | M -> M 104 | end), 105 | reddy_keys:del(C1, [?TEST_KEY1]), 106 | reddy_conn:close(C1). 107 | 108 | pooled_lpush_test() -> 109 | ?POOL(test_pool, 2), 110 | reddy_keys:del(test_pool, [?TEST_KEY1]), 111 | ?assertMatch(1, reddy_lists:lpush(test_pool, ?TEST_KEY1, 1)), 112 | ?assertMatch(2, reddy_lists:lpush(test_pool, ?TEST_KEY1, 2)), 113 | ?assertMatch(3, reddy_lists:lpush(test_pool, ?TEST_KEY1, 3)), 114 | ?assertMatch(<<"3">>, reddy_lists:lpop(test_pool, ?TEST_KEY1)), 115 | ?assertMatch(<<"2">>, reddy_lists:lpop(test_pool, ?TEST_KEY1)), 116 | ?assertMatch(<<"1">>, reddy_lists:lpop(test_pool, ?TEST_KEY1)), 117 | reddy_keys:del(test_pool, [?TEST_KEY1]), 118 | reddy_pool:close(test_pool). 119 | -------------------------------------------------------------------------------- /itests/reddy_itests.erl: -------------------------------------------------------------------------------- 1 | -module(reddy_itests). 2 | 3 | -define(DEFAULT_HOST, "127.0.0.1"). 4 | -define(DEFAULT_PORT, "6379"). 5 | 6 | -define(LIST_KEY, <<"list_test1">>). 7 | 8 | -include_lib("eunit/include/eunit.hrl"). 9 | 10 | -export([run/0]). 11 | 12 | 13 | run() -> 14 | insure_host_port(), 15 | application:start(reddy), 16 | eunit:test(?MODULE). 17 | 18 | all_test_() -> 19 | [{module, reddy_itest_lists}, 20 | {module, reddy_itest_keys}]. 21 | 22 | insure_host_port() -> 23 | Host = case os:getenv("REDIS_HOST") of 24 | false -> 25 | os:putenv("REDIS_HOST", ?DEFAULT_HOST), 26 | ?DEFAULT_HOST; 27 | H -> 28 | H 29 | end, 30 | Port = case os:getenv("REDIS_PORT") of 31 | false -> 32 | os:putenv("REDIS_PORT", ?DEFAULT_PORT), 33 | ?DEFAULT_PORT; 34 | P -> 35 | P 36 | end, 37 | ?debugFmt("~n~n~n~n~n----------~n Using redis server at ~s:~s for integration tests~n----------~n~n~n~n", [Host, Port]), 38 | timer:sleep(5000). 39 | -------------------------------------------------------------------------------- /itests/reddy_itests.hrl: -------------------------------------------------------------------------------- 1 | -define(CONNECT(), reddy_conn:connect(os:getenv("REDIS_HOST"), list_to_integer(os:getenv("REDIS_PORT")))). 2 | -define(POOL(Name, Count), reddy_pool:new_pool(Name, [{ip, os:getenv("REDIS_HOST")}, {port, list_to_integer(os:getenv("REDIS_PORT"))}, 3 | {count, Count}])). 4 | -define(WAIT(X), fun() -> timer:sleep(X) end()). 5 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevsmith/reddy/dfdb5ef82dbf81969ec0e115f8df3517b292c6e7/rebar -------------------------------------------------------------------------------- /src/reddy.app.src: -------------------------------------------------------------------------------- 1 | {application, reddy, 2 | [ 3 | {description, "Another redis client"}, 4 | {vsn, "0.1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib, 9 | sasl 10 | ]}, 11 | {mod, {reddy_app, []}}, 12 | {env, []} 13 | ]}. 14 | -------------------------------------------------------------------------------- /src/reddy.hrl: -------------------------------------------------------------------------------- 1 | -record(reddy_op, {name, 2 | resp_type, 3 | args=[]}). 4 | 5 | -define(REDDY_EOL, [13, 10]). 6 | -------------------------------------------------------------------------------- /src/reddy_app.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_app). 25 | 26 | -behaviour(application). 27 | 28 | %% Application callbacks 29 | -export([start/2, stop/1]). 30 | 31 | start(_StartType, _StartArgs) -> 32 | reddy_sup:start_link(). 33 | 34 | stop(_State) -> 35 | ets:delete(reddy_pools), 36 | ok. 37 | -------------------------------------------------------------------------------- /src/reddy_conn.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_conn). 25 | 26 | -behaviour(gen_server). 27 | 28 | -include("reddy.hrl"). 29 | 30 | %% API 31 | -export([connect/2, 32 | connect/3, 33 | close/1, 34 | start_link/4, 35 | sync/3, 36 | async/3, 37 | async/4, 38 | async/5]). 39 | 40 | %% gen_server callbacks 41 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 42 | terminate/2, code_change/3]). 43 | 44 | -record(state, {cmd_queue=queue:new(), 45 | tracefile, 46 | sock}). 47 | 48 | connect(Addr, Port) -> 49 | connect(Addr, Port, []). 50 | 51 | connect(Addr, Port, Opts) -> 52 | reddy_conn_sup:new_conn(self(), Addr, Port, Opts). 53 | 54 | close(Pid) -> 55 | gen_server:call(Pid, shutdown, infinity). 56 | 57 | start_link(Owner, Addr, Port, Opts) -> 58 | gen_server:start_link(?MODULE, [Owner, Addr, Port, Opts], []). 59 | 60 | sync(Pid, Cmd, Args) -> 61 | case async(Pid, Cmd, Args) of 62 | {ok, Ref} -> 63 | receive 64 | {Ref, Result} -> 65 | Result 66 | end; 67 | Error -> 68 | Error 69 | end. 70 | 71 | async(Pid, Cmd, Args) -> 72 | async(Pid, Cmd, Args, true). 73 | 74 | async(Pid, Cmd, Args, WantsReturn) -> 75 | async(Pid, self(), Cmd, Args, WantsReturn). 76 | 77 | async(Pid, Caller, Cmd, Args, WantsReturn) -> 78 | Op = reddy_ops:create(Cmd, Args), 79 | case catch gen_server:call(Pid, {enqueue, Op, Caller, WantsReturn}, infinity) of 80 | {'EXIT', {noproc, _, _}} -> 81 | {error, closed}; 82 | Result -> 83 | Result 84 | end. 85 | 86 | %%=============================================== 87 | %% gen_server callbacks 88 | %%=============================================== 89 | 90 | init([Owner, Addr, Port, Opts]) -> 91 | case gen_tcp:connect(Addr, Port, [binary, {packet, line}, {active, once}]) of 92 | {ok, Sock} -> 93 | erlang:monitor(process, Owner), 94 | case proplists:get_value(trace_file, Opts) of 95 | undefined -> 96 | {ok, #state{sock=Sock}}; 97 | Path -> 98 | {ok, #state{sock=Sock, tracefile=Path}} 99 | end; 100 | Error -> 101 | {stop, Error} 102 | end. 103 | 104 | handle_call(shutdown, _From, State) -> 105 | {stop, normal, ok, State}; 106 | handle_call({enqueue, Op, Caller, WantsReturn}, _From, #state{sock=Sock, cmd_queue=Queue, 107 | tracefile=File}=State) -> 108 | Ref = erlang:make_ref(), 109 | Bin = reddy_protocol:to_iolist(Op), 110 | log_client(File, Bin), 111 | case gen_tcp:send(Sock, Bin) of 112 | ok -> 113 | Reply = if 114 | WantsReturn -> 115 | {ok, Ref}; 116 | true -> 117 | ok 118 | end, 119 | {reply, Reply, State#state{cmd_queue=queue:in({Caller, Ref, 120 | Op#reddy_op.resp_type, WantsReturn}, Queue)}}; 121 | Error -> 122 | {reply, Error, State} 123 | end; 124 | 125 | handle_call(_Request, _From, State) -> 126 | {reply, ignore, State}. 127 | 128 | handle_cast(_Msg, State) -> 129 | {noreply, State}. 130 | 131 | handle_info({'DOWN', _MRef, _Type, _Object, _Info}, State) -> 132 | {stop, normal, State}; 133 | handle_info({tcp, Sock, Data}, #state{cmd_queue=Queue, tracefile=File}=State) -> 134 | log_server(File, Data), 135 | inet:setopts(Sock, [{packet, raw}]), 136 | case queue:is_empty(Queue) of 137 | true -> 138 | error_logger:warning_msg("Discarding data: ~p~n", [Data]), 139 | inet:setopts(Sock, [binary, {active, once}, {packet, line}]), 140 | {noreply, State}; 141 | false -> 142 | Body = strip_return_char(Data), 143 | {{value, {Caller, Ref, ReturnType, WantsReturn}}, Queue1} = queue:out(Queue), 144 | Result = parse_response(Sock, File, ReturnType, Body), 145 | if 146 | WantsReturn -> 147 | Caller ! {Ref, Result}; 148 | true -> 149 | ok 150 | end, 151 | inet:setopts(Sock, [binary, {active, once}, {packet, line}]), 152 | {noreply, State#state{cmd_queue=Queue1}} 153 | end; 154 | handle_info({tcp_closed, _Sock}, State) -> 155 | error_logger:warning_msg("Closing connection due to server termination~n"), 156 | {stop, normal, State}; 157 | handle_info({tcp_error, Error, _Sock}, State) -> 158 | error_logger:warning_msg("Closing connection due to network error: ~p~n", [Error]), 159 | {stop, normal, State}; 160 | 161 | handle_info(_Info, State) -> 162 | {noreply, State}. 163 | 164 | terminate(_Reason, _State) -> 165 | ok. 166 | 167 | code_change(_OldVsn, State, _Extra) -> 168 | {ok, State}. 169 | 170 | %% Internal functions 171 | parse_response(_Sock, _File, status, Data) -> 172 | reddy_protocol:parse_status(Data); 173 | parse_response(_Sock, _File, integer, Data) -> 174 | reddy_protocol:parse_integer(Data); 175 | parse_response(Sock, File, bulk, Data) -> 176 | inet:setopts(Sock, [{active, false}, {packet, raw}]), 177 | case reddy_protocol:parse_bulk_size(Data) of 178 | {error, Reason} -> 179 | {error, Reason}; 180 | -1 -> 181 | undefined; 182 | Size -> 183 | {ok, Body} = gen_tcp:recv(Sock, Size + 2, 0), 184 | log_server(File, Body), 185 | strip_return_char(Body) 186 | end; 187 | parse_response(Sock, File, multi_bulk, Data) -> 188 | inet:setopts(Sock, [{active, false}, {packet, raw}]), 189 | case reddy_protocol:parse_multi_bulk_count(Data) of 190 | -1 -> 191 | undefined; 192 | Count -> 193 | read_multi_bulk_body(Sock, File, Count, []) 194 | end. 195 | 196 | read_multi_bulk_body(_Sock, _File, 0, Accum) -> 197 | lists:reverse(Accum); 198 | read_multi_bulk_body(Sock, File, Count, Accum) -> 199 | inet:setopts(Sock, [{packet, line}]), 200 | {ok, Data} = gen_tcp:recv(Sock, 0, 0), 201 | log_server(File, Data), 202 | Body = strip_return_char(Data), 203 | read_multi_bulk_body(Sock, File, Count - 1, [parse_response(Sock, File, bulk, Body)|Accum]). 204 | 205 | strip_return_char(Data) -> 206 | BodySize = size(Data) - 2, 207 | <> = Data, 208 | Body. 209 | 210 | log_server(undefined, _Output) -> 211 | ok; 212 | log_server(File, Output) -> 213 | file:write_file(File, ["-----SERVER-----\n", Output], [append]). 214 | 215 | log_client(undefined, _Output) -> 216 | ok; 217 | log_client(File, Output) -> 218 | file:write_file(File, ["-----CLIENT-----\n", Output], [append]). 219 | -------------------------------------------------------------------------------- /src/reddy_conn_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_conn_sup). 25 | 26 | -behaviour(supervisor). 27 | 28 | %% API 29 | -export([start_link/0, 30 | new_conn/4]). 31 | 32 | -define(SERVER, ?MODULE). 33 | 34 | %% Supervisor callbacks 35 | -export([init/1]). 36 | 37 | start_link() -> 38 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 39 | 40 | new_conn(Owner, Addr, Port, Opts) -> 41 | supervisor:start_child(?SERVER, [Owner, Addr, Port, Opts]). 42 | 43 | init([]) -> 44 | Child = {reddy_conn, {reddy_conn, start_link, []}, 45 | temporary, brutal_kill, worker, [reddy_conn]}, 46 | Strategy = {simple_one_for_one, 0, 1}, 47 | {ok, {Strategy, [Child]}}. 48 | -------------------------------------------------------------------------------- /src/reddy_hashes.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_hashes). 25 | 26 | -include("reddy_ops.hrl"). 27 | 28 | -export([hdel/3, 29 | hdel_/4, 30 | hexists/3, 31 | hexists_/4, 32 | hget/3, 33 | hget_/4, 34 | hgetall/2, 35 | hgetall_/3, 36 | hincrby/4, 37 | hincrby_/5, 38 | hkeys/2, 39 | hkeys_/3, 40 | hlen/2, 41 | hlen_/3, 42 | hmget/3, 43 | hmget_/4, 44 | hmset/3, 45 | hmset_/4, 46 | hset/4, 47 | hset_/5, 48 | hsetnx/4, 49 | hsetnx_/5, 50 | hvals/2, 51 | hvals_/3]). 52 | 53 | hdel(Conn, Key, Field) when is_pid(Conn) -> 54 | reddy_conn:sync(Conn, ?HDEL, [Key, Field]); 55 | hdel(Pool, Key, Field) when is_atom(Pool) -> 56 | ?WITH_POOL(Pool, hdel, [Key, Field]). 57 | 58 | hdel_(Conn, Key, Field, WantsReturn) when is_pid(Conn) -> 59 | reddy_conn:async(Conn, ?HDEL, [Key, Field], WantsReturn); 60 | hdel_(Pool, Key, Field, WantsReturn) when is_atom(Pool) -> 61 | ?WITH_POOL(Pool, hdel_, [Key, Field, WantsReturn]). 62 | 63 | hexists(Conn, Key, Field) when is_pid(Conn) -> 64 | reddy_conn:sync(Conn, ?HEXISTS, [Key, Field]); 65 | hexists(Pool, Key, Field) when is_atom(Pool) -> 66 | ?WITH_POOL(Pool, hexists, [Key, Field]). 67 | 68 | hexists_(Conn, Key, Field, WantsReturn) when is_pid(Conn) -> 69 | reddy_conn:async(Conn, ?HEXISTS, [Key, Field], WantsReturn); 70 | hexists_(Pool, Key, Field, WantsReturn) when is_atom(Pool) -> 71 | ?WITH_POOL(Pool, hexists_, [Key, Field, WantsReturn]). 72 | 73 | hget(Conn, Key, Field) when is_pid(Conn) -> 74 | reddy_conn:sync(Conn, ?HGET, [Key, Field]); 75 | hget(Pool, Key, Field) when is_atom(Pool) -> 76 | ?WITH_POOL(Pool, hget, [Key, Field]). 77 | 78 | hget_(Conn, Key, Field, WantsReturn) when is_pid(Conn) -> 79 | reddy_conn:async(Conn, ?HGET, [Key, Field], WantsReturn); 80 | hget_(Pool, Key, Field, WantsReturn) when is_atom(Pool) -> 81 | ?WITH_POOL(Pool, hget_, [Key, Field, WantsReturn]). 82 | 83 | hgetall(Conn, Key) when is_pid(Conn) -> 84 | reddy_conn:sync(Conn, ?HGETALL, [Key]); 85 | hgetall(Pool, Key) when is_atom(Pool) -> 86 | ?WITH_POOL(Pool, hgetall, [Key]). 87 | 88 | hgetall_(Conn, Key, WantsReturn) when is_pid(Conn) -> 89 | reddy_conn:async(Conn, ?HGETALL, [Key], WantsReturn); 90 | hgetall_(Pool, Key, WantsReturn) when is_atom(Pool) -> 91 | ?WITH_POOL(Pool, hgetall_, [Key, WantsReturn]). 92 | 93 | hincrby(Conn, Key, Field, Increment) when is_pid(Conn) -> 94 | reddy_conn:sync(Conn, ?HINCRBY, [Key, Field, Increment]); 95 | hincrby(Pool, Key, Field, Increment) when is_atom(Pool) -> 96 | ?WITH_POOL(Pool, hincrby, [Key, Field, Increment]). 97 | 98 | hincrby_(Conn, Key, Field, Increment, WantsReturn) when is_pid(Conn) -> 99 | reddy_conn:async(Conn, ?HINCRBY, [Key, Field, Increment], WantsReturn); 100 | hincrby_(Pool, Key, Field, Increment, WantsReturn) when is_atom(Pool) -> 101 | ?WITH_POOL(Pool, hincrby_, [Key, Field, Increment, WantsReturn]). 102 | 103 | hkeys(Conn, Key) when is_pid(Conn) -> 104 | reddy_conn:sync(Conn, ?HKEYS, [Key]); 105 | hkeys(Pool, Key) when is_atom(Pool) -> 106 | ?WITH_POOL(Pool, hkeys, [Key]). 107 | 108 | hkeys_(Conn, Key, WantsReturn) when is_pid(Conn) -> 109 | reddy_conn:async(Conn, ?HKEYS, [Key], WantsReturn); 110 | hkeys_(Pool, Key, WantsReturn) when is_atom(Pool) -> 111 | ?WITH_POOL(Pool, hkeys_, [Key, WantsReturn]). 112 | 113 | hlen(Conn, Key) when is_pid(Conn) -> 114 | reddy_conn:sync(Conn, ?HLEN, [Key]); 115 | hlen(Pool, Key) when is_atom(Pool) -> 116 | ?WITH_POOL(Pool, hlen, [Key]). 117 | 118 | hlen_(Conn, Key, WantsReturn) when is_pid(Conn) -> 119 | reddy_conn:async(Conn, ?HLEN, [Key], WantsReturn); 120 | hlen_(Pool, Key, WantsReturn) when is_atom(Pool) -> 121 | ?WITH_POOL(Pool, hlen_, [Key, WantsReturn]). 122 | 123 | hmget(Conn, Key, Fields) when is_list(Fields), 124 | is_pid(Conn) -> 125 | reddy_conn:sync(Conn, ?HMGET, [Key, Fields]); 126 | hmget(Pool, Key, Fields) when is_list(Fields), 127 | is_atom(Pool) -> 128 | ?WITH_POOL(Pool, hmget, [Key, Fields]). 129 | 130 | hmget_(Conn, Key, Fields, WantsReturn) when is_list(Fields), 131 | is_pid(Conn) -> 132 | reddy_conn:async(Conn, ?HMGET, [Key, Fields], WantsReturn); 133 | hmget_(Pool, Key, Fields, WantsReturn) when is_list(Fields), 134 | is_atom(Pool) -> 135 | ?WITH_POOL(Pool, hmget_, [Key, Fields, WantsReturn]). 136 | 137 | hmset(Conn, Key, FieldValuePairs) when is_list(FieldValuePairs), 138 | is_pid(Conn) -> 139 | reddy_conn:sync(Conn, ?HMSET, [Key, reddy_types:convert_field_value_pairs(FieldValuePairs)]); 140 | hmset(Pool, Key, FieldValuePairs) when is_list(FieldValuePairs), 141 | is_atom(Pool) -> 142 | ?WITH_POOL(Pool, hmset, [Key, FieldValuePairs]). 143 | 144 | hmset_(Conn, Key, FieldValuePairs, WantsReturn) when is_list(FieldValuePairs), 145 | is_pid(Conn) -> 146 | reddy_conn:async(Conn, ?HMSET, [Key, reddy_types:convert_field_value_pairs(FieldValuePairs)], WantsReturn); 147 | hmset_(Pool, Key, FieldValuePairs, WantsReturn) when is_list(FieldValuePairs), 148 | is_atom(Pool) -> 149 | ?WITH_POOL(Pool, hmset_, [Key, FieldValuePairs, WantsReturn]). 150 | 151 | hset(Conn, Key, Field, Value) when is_pid(Conn) -> 152 | reddy_conn:sync(Conn, ?HSET, [Key, Field, Value]); 153 | hset(Pool, Key, Field, Value) when is_atom(Pool) -> 154 | ?WITH_POOL(Pool, hset, [Key, Field, Value]). 155 | 156 | hset_(Conn, Key, Field, Value, WantsReturn) when is_pid(Conn) -> 157 | reddy_conn:async(Conn, ?HSET, [Key, Field, Value], WantsReturn); 158 | hset_(Pool, Key, Field, Value, WantsReturn) when is_atom(Pool) -> 159 | ?WITH_POOL(Pool, hset_, [Key, Field, Value, WantsReturn]). 160 | 161 | hsetnx(Conn, Key, Field, Value) when is_pid(Conn) -> 162 | reddy_conn:sync(Conn, ?HSETNX, [Key, Field, Value]); 163 | hsetnx(Pool, Key, Field, Value) when is_atom(Pool) -> 164 | ?WITH_POOL(Pool, hsetnx, [Key, Field, Value]). 165 | 166 | hsetnx_(Conn, Key, Field, Value, WantsReturn) when is_pid(Conn) -> 167 | reddy_conn:async(Conn, ?HSETNX, [Key, Field, Value], WantsReturn); 168 | hsetnx_(Pool, Key, Field, Value, WantsReturn) when is_atom(Pool) -> 169 | ?WITH_POOL(Pool, hsetnx_, [Key, Field, Value, WantsReturn]). 170 | 171 | hvals(Conn, Key) when is_pid(Conn) -> 172 | reddy_conn:sync(Conn, ?HVALS, [Key]); 173 | hvals(Pool, Key) when is_atom(Pool) -> 174 | ?WITH_POOL(Pool, hvals, [Key]). 175 | 176 | hvals_(Conn, Key, WantsReturn) when is_pid(Conn) -> 177 | reddy_conn:async(Conn, ?HVALS, [Key], WantsReturn); 178 | hvals_(Pool, Key, WantsReturn) when is_atom(Pool) -> 179 | ?WITH_POOL(Pool, hvals_, [Key, WantsReturn]). 180 | -------------------------------------------------------------------------------- /src/reddy_keys.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_keys). 25 | 26 | -include("reddy_ops.hrl"). 27 | 28 | -export([del/2, 29 | del_/3, 30 | exists/2, 31 | exists_/3, 32 | expire/3, 33 | expire_/4, 34 | expireat/3, 35 | expireat_/4, 36 | keys/2, 37 | keys_/3, 38 | move/3, 39 | move_/4, 40 | persist/2, 41 | persist_/3, 42 | randomkey/1, 43 | randomkey_/2, 44 | rename/3, 45 | rename_/4, 46 | renamenx/3, 47 | renamenx_/4, 48 | ttl/2, 49 | ttl_/3, 50 | type/2, 51 | type_/3]). 52 | 53 | del(Conn, Keys) when is_list(Keys), is_pid(Conn) -> 54 | reddy_conn:sync(Conn, ?DEL, Keys); 55 | del(Pool, Keys) when is_atom(Pool) -> 56 | ?WITH_POOL(Pool, del, [Keys]). 57 | 58 | del_(Conn, Keys, WantsReturn) when is_list(Keys), is_pid(Conn) -> 59 | reddy_conn:async(Conn, ?DEL, Keys, WantsReturn). 60 | 61 | exists(Conn, Key) when is_pid(Conn) -> 62 | reddy_conn:sync(Conn, ?EXISTS, [Key]). 63 | exists_(Conn, Key, WantsReturn) when is_pid(Conn) -> 64 | reddy_conn:async(Conn, ?EXISTS, [Key], WantsReturn). 65 | 66 | expire(Conn, Key, Seconds) when is_pid(Conn) -> 67 | reddy_conn:sync(Conn, ?EXPIRE, [Key, Seconds]). 68 | 69 | expire_(Conn, Key, Seconds, WantsReturn) when is_pid(Conn) -> 70 | reddy_conn:async(Conn, ?EXPIRE, [Key, Seconds], WantsReturn). 71 | 72 | expireat(Conn, Key, Timestamp) when is_pid(Conn) -> 73 | reddy_conn:sync(Conn, ?EXPIREAT, [Key, convert_ts(Timestamp)]). 74 | 75 | expireat_(Conn, Key, Timestamp, WantsReturn) when is_pid(Conn) -> 76 | reddy_conn:async(Conn, ?EXPIREAT, [Key, convert_ts(Timestamp)], WantsReturn). 77 | 78 | keys(Conn, Pattern) when is_pid(Conn) -> 79 | reddy_conn:sync(Conn, ?KEYS, [Pattern]). 80 | 81 | keys_(Conn, Pattern, WantsReturn) when is_pid(Conn) -> 82 | reddy_conn:async(Conn, ?KEYS, [Pattern], WantsReturn). 83 | 84 | move(Conn, Key, Db) when is_pid(Conn) -> 85 | reddy_conn:sync(Conn, ?MOVE, [Key, Db]). 86 | 87 | move_(Conn, Key, Db, WantsReturn) when is_pid(Conn) -> 88 | reddy_conn:async(Conn, ?MOVE, [Key, Db], WantsReturn). 89 | 90 | persist(Conn, Key) when is_pid(Conn) -> 91 | reddy_conn:sync(Conn, ?PERSIST, [Key]). 92 | 93 | persist_(Conn, Key, WantsReturn) when is_pid(Conn) -> 94 | reddy_conn:async(Conn, ?PERSIST, [Key], WantsReturn). 95 | 96 | randomkey(Conn) when is_pid(Conn) -> 97 | reddy_conn:sync(Conn, ?RANDOMKEY, []). 98 | 99 | randomkey_(Conn, WantsReturn) when is_pid(Conn) -> 100 | reddy_conn:asysnc(Conn, ?RANDOMKEY, [], WantsReturn). 101 | 102 | rename(Conn, Key, NewKey) when is_pid(Conn) -> 103 | reddy_conn:sync(Conn, ?RENAME, [Key, NewKey]). 104 | 105 | rename_(Conn, Key, NewKey, WantsReturn) when is_pid(Conn) -> 106 | reddy_conn:async(Conn, ?RENAME, [Key, NewKey], WantsReturn). 107 | 108 | renamenx(Conn, Key, NewKey) when is_pid(Conn) -> 109 | reddy_conn:sync(Conn, ?RENAMENX, [Key, NewKey]). 110 | 111 | renamenx_(Conn, Key, NewKey, WantsReturn) when is_pid(Conn) -> 112 | reddy_conn:async(Conn, ?RENAMENX, [Key, NewKey], WantsReturn). 113 | 114 | ttl(Conn, Key) when is_pid(Conn) -> 115 | reddy_conn:sync(Conn, ?TTL, [Key]). 116 | 117 | ttl_(Conn, Key, WantsReturn) when is_pid(Conn) -> 118 | reddy_conn:async(Conn, ?TTL, [Key], WantsReturn). 119 | 120 | type(Conn, Key) when is_pid(Conn) -> 121 | reddy_conn:sync(Conn, ?TYPE, [Key]). 122 | 123 | type_(Conn, Key, WantsReturn) when is_pid(Conn) -> 124 | reddy_conn:async(Conn, ?TYPE, [Key], WantsReturn). 125 | 126 | %% Internal functions 127 | convert_ts(TS={_, _, _}) -> 128 | reddy_time:now_to_unixts(TS); 129 | convert_ts(TS) -> 130 | TS. 131 | -------------------------------------------------------------------------------- /src/reddy_lists.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_lists). 25 | 26 | -include("reddy_ops.hrl"). 27 | 28 | -export([blpop/3, 29 | brpop/3, 30 | brpoplpush/4, 31 | lindex/3, 32 | lindex_/4, 33 | linsert/5, 34 | linsert_/6, 35 | llen/2, 36 | llen_/3, 37 | lpop/2, 38 | lpop_/3, 39 | lpush/3, 40 | lpush_/4, 41 | lpushx/3, 42 | lpushx_/4, 43 | lrange/4, 44 | lrange_/5, 45 | lrem/4, 46 | lrem_/5, 47 | lset/4, 48 | lset_/5, 49 | ltrim/4, 50 | ltrim_/5, 51 | rpop/2, 52 | rpop_/3, 53 | rpoplpush/3, 54 | rpoplpush_/4, 55 | rpush/3, 56 | rpush_/4, 57 | rpushx/3, 58 | rpushx_/4]). 59 | 60 | blpop(Conn, Keys, Timeout) when is_pid(Conn) -> 61 | reddy_conn:sync(Conn, ?BLPOP, [Keys, Timeout]); 62 | blpop(Pool, Keys, Timeout) when is_atom(Pool) -> 63 | ?WITH_POOL(Pool, blpop, [Keys, Timeout]). 64 | 65 | brpop(Conn, Keys, Timeout) when is_pid(Conn) -> 66 | reddy_conn:sync(Conn, ?BRPOP, [Keys, Timeout]); 67 | brpop(Pool, Keys, Timeout) when is_atom(Pool) -> 68 | ?WITH_POOL(Pool, brpop, [Keys, Timeout]). 69 | 70 | brpoplpush(Conn, Source, Dest, Timeout) when is_pid(Conn) -> 71 | reddy_conn:sync(Conn, ?BRPOPLPUSH, [Source, Dest, Timeout]); 72 | brpoplpush(Pool, Source, Dest, Timeout) when is_atom(Pool) -> 73 | ?WITH_POOL(Pool, brpoplpush, [Source, Dest, Timeout]). 74 | 75 | lindex(Conn, Key, Index) when is_pid(Conn) -> 76 | reddy_conn:sync(Conn, ?LINDEX, [Key, Index]); 77 | lindex(Pool, Key, Index) when is_atom(Pool) -> 78 | ?WITH_POOL(Pool, lindex, [Key, Index]). 79 | 80 | lindex_(Conn, Key, Index, WantsReturn) when is_pid(Conn) -> 81 | reddy_conn:async(Conn, ?LINDEX, [Key, Index], WantsReturn); 82 | lindex_(Pool, Key, Index, WantsReturn) when is_atom(Pool) -> 83 | ?WITH_POOL(Pool, lindex_, [Key, Index, WantsReturn]). 84 | 85 | linsert(Conn, Key, BeforeOrAfter, Pivot, Value) when is_pid(Conn), 86 | BeforeOrAfter =:= "BEFORE" 87 | orelse BeforeOrAfter =:= "AFTER" -> 88 | reddy_conn:sync(Conn, ?LINSERT, [Key, BeforeOrAfter, Pivot, Value]); 89 | linsert(Pool, Key, BeforeOrAfter, Pivot, Value) when is_atom(Pool), 90 | BeforeOrAfter =:= "BEFORE" 91 | orelse BeforeOrAfter =:= "AFTER" -> 92 | ?WITH_POOL(Pool, linsert, [Key, BeforeOrAfter, Pivot, Value]). 93 | 94 | linsert_(Conn, Key, BeforeOrAfter, Pivot, Value, WantsReturn) when is_pid(Conn), 95 | BeforeOrAfter =:= "BEFORE" 96 | orelse BeforeOrAfter =:= "AFTER" -> 97 | reddy_conn:async(Conn, ?LINSERT, [Key, BeforeOrAfter, Pivot, Value], WantsReturn); 98 | linsert_(Pool, Key, BeforeOrAfter, Pivot, Value, WantsReturn) when is_atom(Pool), 99 | BeforeOrAfter =:= "BEFORE" 100 | orelse BeforeOrAfter =:= "AFTER" -> 101 | ?WITH_POOL(Pool, linsert_, [Key, BeforeOrAfter, Pivot, Value, WantsReturn]). 102 | 103 | llen(Conn, Key) when is_pid(Conn) -> 104 | reddy_conn:sync(Conn, ?LLEN, [Key]); 105 | llen(Pool, Key) when is_atom(Pool) -> 106 | ?WITH_POOL(Pool, llen, [Key]). 107 | 108 | llen_(Conn, Key, WantsReturn) when is_pid(Conn) -> 109 | reddy_conn:async(Conn, ?LLEN, [Key], WantsReturn); 110 | llen_(Pool, Key, WantsReturn) when is_atom(Pool) -> 111 | ?WITH_POOL(Pool, llen_, [Key, WantsReturn]). 112 | 113 | lpop(Conn, Key) when is_pid(Conn) -> 114 | reddy_conn:sync(Conn, ?LPOP, [Key]); 115 | lpop(Pool, Key) when is_atom(Pool) -> 116 | ?WITH_POOL(Pool, lpop, [Key]). 117 | 118 | lpop_(Conn, Key, WantsReturn) when is_pid(Conn) -> 119 | reddy_conn:async(Conn, ?LPOP, [Key], WantsReturn); 120 | lpop_(Pool, Key, WantsReturn) when is_atom(Pool) -> 121 | ?WITH_POOL(Pool, lpop_, [Key, WantsReturn]). 122 | 123 | lpush(Conn, Key, Value) when is_pid(Conn) -> 124 | reddy_conn:sync(Conn, ?LPUSH, [Key, Value]); 125 | lpush(Pool, Key, Value) when is_atom(Pool) -> 126 | ?WITH_POOL(Pool, lpush, [Key, Value]). 127 | 128 | lpush_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 129 | reddy_conn:async(Conn, ?LPUSH, [Key, Value], WantsReturn); 130 | lpush_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 131 | ?WITH_POOL(Pool, lpush_, [Key, Value, WantsReturn]). 132 | 133 | lpushx(Conn, Key, Value) when is_pid(Conn) -> 134 | reddy_conn:sync(Conn, ?LPUSHX, [Key, Value]); 135 | lpushx(Pool, Key, Value) when is_atom(Pool) -> 136 | ?WITH_POOL(Pool, lpushx, [Key, Value]). 137 | 138 | lpushx_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 139 | reddy_conn:async(Conn, ?LPUSHX, [Key, Value], WantsReturn); 140 | lpushx_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 141 | ?WITH_POOL(Pool, lpushx_, [Key, Value, WantsReturn]). 142 | 143 | lrange(Conn, Key, Start, Stop) when is_pid(Conn) -> 144 | reddy_conn:sync(Conn, ?LRANGE, [Key, Start, Stop]); 145 | lrange(Pool, Key, Start, Stop) when is_atom(Pool) -> 146 | ?WITH_POOL(Pool, lrange, [Key, Start, Stop]). 147 | 148 | lrange_(Conn, Key, Start, Stop, WantsReturn) when is_pid(Conn) -> 149 | reddy_conn:async(Conn, ?LRANGE, [Key, Start, Stop], WantsReturn); 150 | lrange_(Pool, Key, Start, Stop, WantsReturn) when is_atom(Pool) -> 151 | ?WITH_POOL(Pool, lrange_, [Key, Start, Stop, WantsReturn]). 152 | 153 | lrem(Conn, Key, Count, Value) when is_pid(Conn) -> 154 | reddy_conn:sync(Conn, ?LREM, [Key, Count, Value]); 155 | lrem(Pool, Key, Count, Value) when is_atom(Pool) -> 156 | ?WITH_POOL(Pool, lrem, [Key, Count, Value]). 157 | 158 | lrem_(Conn, Key, Count, Value, WantsReturn) when is_pid(Conn) -> 159 | reddy_conn:async(Conn, ?LREM, [Key, Count, Value], WantsReturn); 160 | lrem_(Pool, Key, Count, Value, WantsReturn) when is_atom(Pool) -> 161 | ?WITH_POOL(Pool, lrem_, [Key, Count, Value, WantsReturn]). 162 | 163 | lset(Conn, Key, Index, Value) when is_pid(Conn) -> 164 | reddy_conn:sync(Conn, ?LSET, [Key, Index, Value]); 165 | lset(Pool, Key, Index, Value) when is_atom(Pool) -> 166 | ?WITH_POOL(Pool, lset, [Key, Index, Value]). 167 | 168 | lset_(Conn, Key, Index, Value, WantsReturn) when is_pid(Conn) -> 169 | reddy_conn:async(Conn, ?LSET, [Key, Index, Value], WantsReturn); 170 | lset_(Pool, Key, Index, Value, WantsReturn) when is_atom(Pool) -> 171 | ?WITH_POOL(Pool, lset_, [Key, Index, Value, WantsReturn]). 172 | 173 | ltrim(Conn, Key, Start, Stop) when is_pid(Conn) -> 174 | reddy_conn:sync(Conn, ?LTRIM, [Key, Start, Stop]); 175 | ltrim(Pool, Key, Start, Stop) when is_atom(Pool) -> 176 | ?WITH_POOL(Pool, ltrim, [Key, Start, Stop]). 177 | 178 | ltrim_(Conn, Key, Start, Stop, WantsReturn) when is_pid(Conn) -> 179 | reddy_conn:async(Conn, ?LTRIM, [Key, Start, Stop], WantsReturn); 180 | ltrim_(Pool, Key, Start, Stop, WantsReturn) when is_atom(Pool) -> 181 | ?WITH_POOL(Pool, ltrim_, [Key, Start, Stop, WantsReturn]). 182 | 183 | rpop(Conn, Key) when is_pid(Conn) -> 184 | reddy_conn:sync(Conn, ?RPOP, [Key]); 185 | rpop(Pool, Key) when is_atom(Pool) -> 186 | ?WITH_POOL(Pool, rpop, [Key]). 187 | 188 | rpop_(Conn, Key, WantsReturn) when is_pid(Conn) -> 189 | reddy_conn:async(Conn, ?RPOP, [Key], WantsReturn); 190 | rpop_(Pool, Key, WantsReturn) when is_atom(Pool) -> 191 | ?WITH_POOL(Pool, rpop_, [Key, WantsReturn]). 192 | 193 | rpoplpush(Conn, Source, Dest) when is_pid(Conn) -> 194 | reddy_conn:sync(Conn, ?RPOPLPUSH, [Source, Dest]); 195 | rpoplpush(Pool, Source, Dest) when is_atom(Pool) -> 196 | ?WITH_POOL(Pool, rpoplpush, [Source, Dest]). 197 | 198 | rpoplpush_(Conn, Source, Dest, WantsReturn) when is_pid(Conn) -> 199 | reddy_conn:async(Conn, ?RPOPLPUSH, [Source, Dest], WantsReturn); 200 | rpoplpush_(Pool, Source, Dest, WantsReturn) when is_atom(Pool) -> 201 | ?WITH_POOL(Pool, rpoplpush_, [Source, Dest, WantsReturn]). 202 | 203 | rpush(Conn, Key, Value) when is_pid(Conn) -> 204 | reddy_conn:sync(Conn, ?RPUSH, [Key, Value]); 205 | rpush(Pool, Key, Value) when is_atom(Pool) -> 206 | ?WITH_POOL(Pool, rpush, [Key, Value]). 207 | 208 | rpush_(Conn, Key, Value, WantsValue) when is_pid(Conn) -> 209 | reddy_conn:async(Conn, ?RPUSH, [Key, Value], WantsValue); 210 | rpush_(Pool, Key, Value, WantsValue) when is_atom(Pool) -> 211 | ?WITH_POOL(Pool, rpush_, [Key, Value, WantsValue]). 212 | 213 | rpushx(Conn, Key, Value) when is_pid(Conn) -> 214 | reddy_conn:sync(Conn, ?RPUSHX, [Key, Value]); 215 | rpushx(Pool, Key, Value) when is_atom(Pool) -> 216 | ?WITH_POOL(Pool, rpushx, [Key, Value]). 217 | 218 | rpushx_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 219 | reddy_conn:async(Conn, ?RPUSHX, [Key, Value], WantsReturn); 220 | rpushx_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 221 | ?WITH_POOL(Pool, rpushx_, [Key, Value, WantsReturn]). 222 | -------------------------------------------------------------------------------- /src/reddy_ops.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_ops). 25 | 26 | -include("reddy.hrl"). 27 | -include("reddy_ops.hrl"). 28 | 29 | -export([create/2]). 30 | 31 | create(Cmd, Args) when is_binary(Cmd) -> 32 | create(binary_to_list(Cmd), Args); 33 | 34 | %% List ops 35 | create(Op=?BLPOP, Args) -> 36 | #reddy_op{name=Op, 37 | args=lists:flatten(Args), 38 | resp_type=multi_bulk}; 39 | create(Op=?BRPOP, Args) -> 40 | #reddy_op{name=Op, 41 | args=lists:flatten(Args), 42 | resp_type=multi_bulk}; 43 | create(Op=?BRPOPLPUSH, Args) -> 44 | #reddy_op{name=Op, 45 | args=Args, 46 | resp_type=multi_bulk}; 47 | create(Op=?LINDEX, Args) -> 48 | #reddy_op{name=Op, 49 | args=Args, 50 | resp_type=bulk}; 51 | create(Op=?LINSERT, Args) -> 52 | #reddy_op{name=Op, 53 | args=Args, 54 | resp_type=integer}; 55 | create(Op=?LLEN, Args) -> 56 | #reddy_op{name=Op, 57 | args=Args, 58 | resp_type=integer}; 59 | create(Op=?LPOP, Args) -> 60 | #reddy_op{name=Op, 61 | args=Args, 62 | resp_type=bulk}; 63 | create(Op=?LPUSH, Args) -> 64 | #reddy_op{name=Op, 65 | args=Args, 66 | resp_type=integer}; 67 | create(Op=?LPUSHX, Args) -> 68 | #reddy_op{name=Op, 69 | args=Args, 70 | resp_type=integer}; 71 | create(Op=?LRANGE, Args) -> 72 | #reddy_op{name=Op, 73 | args=Args, 74 | resp_type=multi_bulk}; 75 | create(Op=?LREM, Args) -> 76 | #reddy_op{name=Op, 77 | args=Args, 78 | resp_type=integer}; 79 | create(Op=?LSET, Args) -> 80 | #reddy_op{name=Op, 81 | args=Args, 82 | resp_type=status}; 83 | 84 | create(Op=?LTRIM, Args) -> 85 | #reddy_op{name=Op, 86 | args=Args, 87 | resp_type=status}; 88 | 89 | create(Op=?RPOP, Args) -> 90 | #reddy_op{name=Op, 91 | args=Args, 92 | resp_type=bulk}; 93 | 94 | create(Op=?RPOPLPUSH, Args) -> 95 | #reddy_op{name=Op, 96 | args=Args, 97 | resp_type=bulk}; 98 | 99 | create(Op=?RPUSH, Args) -> 100 | #reddy_op{name=Op, 101 | args=Args, 102 | resp_type=integer}; 103 | 104 | create(Op=?RPUSHX, Args) -> 105 | #reddy_op{name=Op, 106 | args=Args, 107 | resp_type=integer}; 108 | 109 | %% Key ops 110 | create(Op=?DEL, Args) -> 111 | #reddy_op{name=Op, 112 | args=Args, 113 | resp_type=integer}; 114 | 115 | create(Op=?EXISTS, Args) -> 116 | #reddy_op{name=Op, 117 | args=Args, 118 | resp_type=integer}; 119 | 120 | create(Op=?EXPIRE, Args) -> 121 | #reddy_op{name=Op, 122 | args=Args, 123 | resp_type=integer}; 124 | 125 | create(Op=?EXPIREAT, Args) -> 126 | #reddy_op{name=Op, 127 | args=Args, 128 | resp_type=integer}; 129 | 130 | create(Op=?KEYS, Args) -> 131 | #reddy_op{name=Op, 132 | args=Args, 133 | resp_type=multi_bulk}; 134 | 135 | create(Op=?MOVE, Args) -> 136 | #reddy_op{name=Op, 137 | args=Args, 138 | resp_type=integer}; 139 | 140 | create(Op=?PERSIST, Args) -> 141 | #reddy_op{name=Op, 142 | args=Args, 143 | resp_type=integer}; 144 | 145 | create(Op=?RANDOMKEY, _Args) -> 146 | #reddy_op{name=Op, 147 | args=[], 148 | resp_type=bulk}; 149 | 150 | create(Op=?RENAME, Args) -> 151 | #reddy_op{name=Op, 152 | args=Args, 153 | resp_type=status}; 154 | 155 | create(Op=?RENAMENX, Args) -> 156 | #reddy_op{name=Op, 157 | args=Args, 158 | resp_type=integer}; 159 | 160 | create(Op=?TTL, Args) -> 161 | #reddy_op{name=Op, 162 | args=Args, 163 | resp_type=integer}; 164 | 165 | create(Op=?TYPE, Args) -> 166 | #reddy_op{name=Op, 167 | args=Args, 168 | resp_type=status}; 169 | 170 | %% Set ops 171 | create(Op=?SADD, Args) -> 172 | #reddy_op{name=Op, 173 | args=Args, 174 | resp_type=integer}; 175 | 176 | create(Op=?SCARD, Args) -> 177 | #reddy_op{name=Op, 178 | args=Args, 179 | resp_type=integer}; 180 | 181 | create(Op=?SDIFF, Args) -> 182 | #reddy_op{name=Op, 183 | args=Args, 184 | resp_type=multi_bulk}; 185 | 186 | create(Op=?SDIFFSTORE, Args) -> 187 | #reddy_op{name=Op, 188 | args=lists:flatten(Args), 189 | resp_type=integer}; 190 | 191 | create(Op=?SINTER, Args) -> 192 | #reddy_op{name=Op, 193 | args=Args, 194 | resp_type=multi_bulk}; 195 | 196 | create(Op=?SINTERSTORE, Args) -> 197 | #reddy_op{name=Op, 198 | args=lists:flatten(Args), 199 | resp_type=integer}; 200 | 201 | create(Op=?SISMEMBER, Args) -> 202 | #reddy_op{name=Op, 203 | args=Args, 204 | resp_type=integer}; 205 | 206 | create(Op=?SMEMBERS, Args) -> 207 | #reddy_op{name=Op, 208 | args=Args, 209 | resp_type=multi_bulk}; 210 | 211 | create(Op=?SMOVE, Args) -> 212 | #reddy_op{name=Op, 213 | args=Args, 214 | resp_type=integer}; 215 | 216 | create(Op=?SPOP, Args) -> 217 | #reddy_op{name=Op, 218 | args=Args, 219 | resp_type=bulk}; 220 | 221 | create(Op=?SRANDMEMBER, Args) -> 222 | #reddy_op{name=Op, 223 | args=Args, 224 | resp_type=bulk}; 225 | 226 | create(Op=?SREM, Args) -> 227 | #reddy_op{name=Op, 228 | args=Args, 229 | resp_type=integer}; 230 | 231 | create(Op=?SUNION, Args) -> 232 | #reddy_op{name=Op, 233 | args=Args, 234 | resp_type=multi_bulk}; 235 | 236 | create(Op=?SUNIONSTORE, Args) -> 237 | #reddy_op{name=Op, 238 | args=lists:flatten(Args), 239 | resp_type=integer}; 240 | 241 | %% Hash ops 242 | create(Op=?HDEL, Args) -> 243 | #reddy_op{name=Op, 244 | args=Args, 245 | resp_type=integer}; 246 | 247 | create(Op=?HEXISTS, Args) -> 248 | #reddy_op{name=Op, 249 | args=Args, 250 | resp_type=integer}; 251 | 252 | create(Op=?HGET, Args) -> 253 | #reddy_op{name=Op, 254 | args=Args, 255 | resp_type=integer}; 256 | 257 | create(Op=?HGETALL, Args) -> 258 | #reddy_op{name=Op, 259 | args=Args, 260 | resp_type=multi_bulk}; 261 | 262 | create(Op=?HINCRBY, Args) -> 263 | #reddy_op{name=Op, 264 | args=Args, 265 | resp_type=integer}; 266 | 267 | create(Op=?HKEYS, Args) -> 268 | #reddy_op{name=Op, 269 | args=Args, 270 | resp_type=multi_bulk}; 271 | 272 | create(Op=?HLEN, Args) -> 273 | #reddy_op{name=Op, 274 | args=Args, 275 | resp_type=integer}; 276 | 277 | create(Op=?HMGET, Args) -> 278 | #reddy_op{name=Op, 279 | args=lists:flatten(Args), 280 | resp_type=multi_bulk}; 281 | 282 | create(Op=?HMSET, Args) -> 283 | #reddy_op{name=Op, 284 | args=lists:flatten(Args), 285 | resp_type=status}; 286 | 287 | create(Op=?HSET, Args) -> 288 | #reddy_op{name=Op, 289 | args=Args, 290 | resp_type=integer}; 291 | 292 | create(Op=?HSETNX, Args) -> 293 | #reddy_op{name=Op, 294 | args=Args, 295 | resp_type=integer}; 296 | 297 | create(Op=?HVALS, Args) -> 298 | #reddy_op{name=Op, 299 | args=Args, 300 | resp_type=multi_bulk}; 301 | 302 | %% String ops 303 | create(Op=?APPEND, Args) -> 304 | #reddy_op{name=Op, 305 | args=Args, 306 | resp_type=integer}; 307 | create(Op=?DECR, Args) -> 308 | #reddy_op{name=Op, 309 | args=Args, 310 | resp_type=integer}; 311 | create(Op=?DECRBY, Args) -> 312 | #reddy_op{name=Op, 313 | args=Args, 314 | resp_type=integer}; 315 | create(Op=?GET, Args) -> 316 | #reddy_op{name=Op, 317 | args=Args, 318 | resp_type=bulk}; 319 | create(Op=?GETBIT, Args) -> 320 | #reddy_op{name=Op, 321 | args=Args, 322 | resp_type=integer}; 323 | create(Op=?GETRANGE, Args) -> 324 | #reddy_op{name=Op, 325 | args=Args, 326 | resp_type=bulk}; 327 | create(Op=?GETSET, Args) -> 328 | #reddy_op{name=Op, 329 | args=Args, 330 | resp_type=bulk}; 331 | create(Op=?INCR, Args) -> 332 | #reddy_op{name=Op, 333 | args=Args, 334 | resp_type=integer}; 335 | create(Op=?INCRBY, Args) -> 336 | #reddy_op{name=Op, 337 | args=Args, 338 | resp_type=integer}; 339 | create(Op=?MGET, Args) -> 340 | #reddy_op{name=Op, 341 | args=Args, 342 | resp_type=multi_bulk}; 343 | create(Op=?MSETNX, Args) -> 344 | #reddy_op{name=Op, 345 | args=Args, 346 | resp_type=integer}; 347 | create(Op=?SET, Args) -> 348 | #reddy_op{name=Op, 349 | args=Args, 350 | resp_type=status}; 351 | create(Op=?SETBIT, Args) -> 352 | #reddy_op{name=Op, 353 | args=Args, 354 | resp_type=integer}; 355 | create(Op=?SETEX, Args) -> 356 | #reddy_op{name=Op, 357 | args=Args, 358 | resp_type=status}; 359 | create(Op=?SETNX, Args) -> 360 | #reddy_op{name=Op, 361 | args=Args, 362 | resp_type=integer}; 363 | create(Op=?SETRANGE, Args) -> 364 | #reddy_op{name=Op, 365 | args=Args, 366 | resp_type=integer}; 367 | create(Op=?STRLEN, Args) -> 368 | #reddy_op{name=Op, 369 | args=Args, 370 | resp_type=integer}; 371 | 372 | %% Server ops 373 | create(Op=?INFO, _Args) -> 374 | #reddy_op{name=Op, 375 | args=[], 376 | resp_type=bulk}; 377 | create(Op=?AUTH, Args) -> 378 | #reddy_op{name=Op, 379 | args=Args, 380 | resp_type=status}; 381 | 382 | %% Health ops 383 | create(Op=?PING, _Args) -> 384 | #reddy_op{name=Op, 385 | args=[], 386 | resp_type=status}; 387 | create(Op=?ECHO, Args) -> 388 | #reddy_op{name=Op, 389 | args=Args, 390 | resp_type=bulk}. 391 | -------------------------------------------------------------------------------- /src/reddy_ops.hrl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | %% List ops 25 | -define(BLPOP, "BLPOP"). 26 | -define(BRPOP, "BRPOP"). 27 | -define(BRPOPLPUSH, "BRPOPLPUSH"). 28 | -define(LINDEX, "LINDEX"). 29 | -define(LINSERT, "LINSERT"). 30 | -define(LLEN, "LLEN"). 31 | -define(LPOP, "LPOP"). 32 | -define(LPUSH, "LPUSH"). 33 | -define(LPUSHX, "LPUSHX"). 34 | -define(LRANGE, "LRANGE"). 35 | -define(LREM, "LREM"). 36 | -define(LSET, "LSET"). 37 | -define(LTRIM, "LTRIM"). 38 | -define(RPOP, "RPOP"). 39 | -define(RPOPLPUSH, "RPOPLPUSH"). 40 | -define(RPUSH, "RPUSH"). 41 | -define(RPUSHX, "RPUSHX"). 42 | 43 | %% Keys ops 44 | -define(DEL, "DEL"). 45 | -define(EXISTS, "EXISTS"). 46 | -define(EXPIRE, "EXPIRE"). 47 | -define(EXPIREAT, "EXPIREAT"). 48 | -define(KEYS, "KEYS"). 49 | -define(MOVE, "MOVE"). 50 | -define(PERSIST, "PERSIST"). 51 | -define(RANDOMKEY, "RANDOMKEY"). 52 | -define(RENAME, "RENAME"). 53 | -define(RENAMENX, "RENAMENX"). 54 | -define(TTL, "TTL"). 55 | -define(TYPE, "TYPE"). 56 | 57 | %% Set ops 58 | -define(SADD, "SADD"). 59 | -define(SCARD, "SCARD"). 60 | -define(SDIFF, "SDIFF"). 61 | -define(SDIFFSTORE, "SDIFFSTORE"). 62 | -define(SINTER, "SINTER"). 63 | -define(SINTERSTORE, "SINTERSTORE"). 64 | -define(SISMEMBER, "SISMEMBER"). 65 | -define(SMEMBERS, "SMEMBERS"). 66 | -define(SMOVE, "SMOVE"). 67 | -define(SPOP, "SPOP"). 68 | -define(SRANDMEMBER, "SRANDMEMBER"). 69 | -define(SREM, "SREM"). 70 | -define(SUNION, "SUNION"). 71 | -define(SUNIONSTORE, "SUNIONSTORE"). 72 | 73 | %% Hash ops 74 | -define(HDEL, "HDEL"). 75 | -define(HEXISTS, "HEXISTS"). 76 | -define(HGET, "HGET"). 77 | -define(HGETALL, "HGETALL"). 78 | -define(HINCRBY, "HINCRBY"). 79 | -define(HKEYS, "HKEYS"). 80 | -define(HLEN, "HLEN"). 81 | -define(HMGET, "HMGET"). 82 | -define(HMSET, "HMSET"). 83 | -define(HSET, "HSET"). 84 | -define(HSETNX, "HSETNX"). 85 | -define(HVALS, "HVALS"). 86 | 87 | %% String ops 88 | -define(APPEND, "APPEND"). 89 | -define(DECR, "DECR"). 90 | -define(DECRBY, "DECRBY"). 91 | -define(GET, "GET"). 92 | -define(GETBIT, "GETBIT"). 93 | -define(GETRANGE, "GETRANGE"). 94 | -define(GETSET, "GETSET"). 95 | -define(INCR, "INCR"). 96 | -define(INCRBY, "INCRBY"). 97 | -define(MGET, "MGET"). 98 | -define(MSET, "MSET"). 99 | -define(MSETNX, "MSETNX"). 100 | -define(SET, "SET"). 101 | -define(SETBIT, "SETBIT"). 102 | -define(SETEX, "SETEX"). 103 | -define(SETNX, "SETNX"). 104 | -define(SETRANGE, "SETRANGE"). 105 | -define(STRLEN, "STRLEN"). 106 | 107 | %% Server ops 108 | -define(INFO, "INFO"). 109 | -define(AUTH, "AUTH"). 110 | 111 | %% Health ops 112 | -define(PING, "PING"). 113 | -define(ECHO, "ECHO"). 114 | 115 | %% Pool macros 116 | -define(WITH_POOL(Pool, Fun, Args), fun() -> reddy_pool:with_pool(Pool, ?MODULE, Fun, Args) end()). 117 | -------------------------------------------------------------------------------- /src/reddy_pool.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_pool). 25 | 26 | -behaviour(gen_server). 27 | 28 | %% API 29 | -export([start_link/2, 30 | with_pool/4, 31 | new_pool/2, 32 | check_out/1, 33 | check_in/1, 34 | close/1]). 35 | 36 | -record('DOWN', {mref, 37 | type, 38 | obj, 39 | info}). 40 | 41 | %% gen_server callbacks 42 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 43 | terminate/2, code_change/3]). 44 | 45 | -define(SERVER, ?MODULE). 46 | 47 | -record(state, {addr, 48 | port, 49 | pass, 50 | avail, 51 | waiting=queue:new(), 52 | owner_to_child=dict:new(), 53 | child_to_owner=dict:new()}). 54 | 55 | new_pool(PoolName, Options) -> 56 | reddy_pool_sup:new_pool(PoolName, Options). 57 | 58 | with_pool(Pool, Mod, Fun, Args) -> 59 | try 60 | case check_out(Pool) of 61 | {ok, Conn} -> 62 | erlang:apply(Mod, Fun, [Conn|Args]); 63 | Error -> 64 | Error 65 | end 66 | after 67 | check_in(Pool) 68 | end. 69 | 70 | check_out(PoolName) -> 71 | gen_server:call(PoolName, {checkout, self()}, infinity). 72 | check_in(PoolName) -> 73 | gen_server:call(PoolName, {checkin, self()}, infinity). 74 | 75 | close(PoolName) -> 76 | gen_server:call(PoolName, close, infinity). 77 | 78 | start_link(PoolName, Options) -> 79 | gen_server:start_link({local, PoolName}, ?MODULE, [Options], []). 80 | 81 | %%=============================================== 82 | %% gen_server callbacks 83 | %%=============================================== 84 | 85 | init([Options]) -> 86 | Ip = proplists:get_value(ip, Options), 87 | Port = proplists:get_value(port, Options), 88 | Pass = proplists:get_value(pass, Options), 89 | Count = proplists:get_value(count, Options), 90 | case Ip =:= undefined orelse Port =:= undefined orelse Count =:= undefined of 91 | true -> 92 | {stop, {error, badarg}}; 93 | false -> 94 | case start_children(Ip, Port, Pass, Count, []) of 95 | {ok, Children} -> 96 | {ok, #state{addr=Ip, port=Port, avail=Children}}; 97 | Error -> 98 | {stop, Error} 99 | end 100 | end. 101 | 102 | handle_call({checkout, Owner}, From, #state{avail=[], waiting=Queue}=State) -> 103 | {noreply, State#state{waiting=queue:in({Owner, From}, Queue)}}; 104 | handle_call({checkout, Owner}, _From, #state{avail=[H|T], owner_to_child=Owners, 105 | child_to_owner=Children}=State) -> 106 | case dict:find(Owner, Owners) of 107 | {ok, {Conn, _MRef}} -> 108 | {reply, {ok, Conn}, State}; 109 | error -> 110 | {Owners1, Children1} = child_out(H, Owner, Owners, Children), 111 | {reply, {ok, H}, State#state{owner_to_child=Owners1, child_to_owner=Children1, avail=T}} 112 | end; 113 | handle_call({checkin, Owner}, _From, #state{avail=Avail, owner_to_child=Owners, 114 | child_to_owner=Children}=State) -> 115 | case dict:find(Owner, Owners) of 116 | error -> 117 | {reply, {error, not_checked_out}, State}; 118 | {ok, {Conn, MRef}} -> 119 | erlang:demonitor(MRef, [flush]), 120 | Owners1 = dict:erase(Owner, Owners), 121 | Children1 = dict:erase(Conn, Children), 122 | State1 = State#state{owner_to_child=Owners1, child_to_owner=Children1, 123 | avail=Avail ++ [Conn]}, 124 | {reply, ok, dequeue_waiting(State1)} 125 | end; 126 | 127 | handle_call(close, _From, State) -> 128 | {stop, normal, ok, State}; 129 | handle_call(_Request, _From, State) -> 130 | {reply, ignore, State}. 131 | 132 | handle_cast(_Msg, State) -> 133 | {noreply, State}. 134 | 135 | handle_info(#'DOWN'{obj=Pid}, #state{owner_to_child=Owners, child_to_owner=Children}=State) -> 136 | State1 = case dict:find(Pid, Owners) of 137 | error -> 138 | case dict:find(Pid, Children) of 139 | error -> 140 | State; 141 | {ok, _} -> 142 | dequeue_waiting(crashed_child(Pid, State)) 143 | end; 144 | {ok, _} -> 145 | dequeue_waiting(crashed_owner(Pid, State)) 146 | end, 147 | {noreply, State1}. 148 | 149 | terminate(_Reason, _State) -> 150 | ok. 151 | 152 | code_change(_OldVsn, State, _Extra) -> 153 | {ok, State}. 154 | 155 | %% Internal functions 156 | crashed_child(Pid, #state{addr=Addr, port=Port, avail=Avail, child_to_owner=Children, 157 | owner_to_child=Owners}=State) -> 158 | {Owner, MRef} = dict:fetch(Pid, Children), 159 | erlang:demonitor(MRef, [flush]), 160 | Owners1 = dict:erase(Owner, Owners), 161 | Children1 = dict:erase(Pid, Children), 162 | case reddy_conn:connect(Addr, Port) of 163 | {ok, Conn} -> 164 | State#state{child_to_owner=Children1, owner_to_child=Owners1, 165 | avail=Avail ++ [Conn]}; 166 | Error -> 167 | error_logger:warning_msg("Replacement for crashed redis connection failed: ~p~n", [Error]), 168 | State#state{child_to_owner=Children1, owner_to_child=Owners1} 169 | end. 170 | 171 | crashed_owner(Pid, #state{avail=Avail, child_to_owner=Children, owner_to_child=Owners}=State) -> 172 | {Owner, MRef} = dict:fetch(Pid, Children), 173 | erlang:demonitor(MRef, [flush]), 174 | Children1 = dict:erase(Pid, Children), 175 | Owners1 = dict:erase(Owner, Owners), 176 | State#state{child_to_owner=Children1, owner_to_child=Owners1, avail=Avail ++ [Pid]}. 177 | 178 | dequeue_waiting(#state{avail=[H|T], owner_to_child=Owners, child_to_owner=Children, 179 | waiting=Queue}=State) -> 180 | case queue:is_empty(Queue) of 181 | true -> 182 | State; 183 | false -> 184 | {{value, {Owner, From}}, Queue1} = queue:out(Queue), 185 | case erlang:is_process_alive(Owner) of 186 | false -> 187 | dequeue_waiting(State#state{waiting=Queue1}); 188 | true -> 189 | {Owners1, Children1} = child_out(H, Owner, Owners, Children), 190 | gen_server:reply(From, {ok, H}), 191 | State#state{owner_to_child=Owners1, child_to_owner=Children1, avail=T, waiting=Queue1} 192 | end 193 | end. 194 | 195 | child_out(Conn, Owner, Owners, Children) -> 196 | MRef = erlang:monitor(process, Owner), 197 | Owners1 = dict:store(Owner, {Conn, MRef}, Owners), 198 | Children1 = dict:store(Conn, {Owner, MRef}, Children), 199 | {Owners1, Children1}. 200 | 201 | start_children(_Addr, _Port, _Pass, 0, Accum) -> 202 | {ok, Accum}; 203 | start_children(Addr, Port, Pass, Count, Accum) -> 204 | case reddy_conn:connect(Addr, Port) of 205 | {ok, Pid} -> 206 | case do_auth(Pid, Pass) of 207 | ok -> 208 | erlang:monitor(process, Pid), 209 | start_children(Addr, Port, Pass, Count - 1, [Pid|Accum]); 210 | AuthError -> 211 | AuthError 212 | end; 213 | Error -> 214 | Error 215 | end. 216 | 217 | do_auth(_Pid, undefined) -> 218 | ok; 219 | do_auth(Pid, Pass) -> 220 | reddy_server:auth(Pid, Pass). 221 | -------------------------------------------------------------------------------- /src/reddy_pool_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_pool_sup). 25 | 26 | -behaviour(supervisor). 27 | 28 | %% API 29 | -export([start_link/0, 30 | new_pool/2]). 31 | 32 | -define(SERVER, ?MODULE). 33 | 34 | %% Supervisor callbacks 35 | -export([init/1]). 36 | 37 | start_link() -> 38 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 39 | 40 | new_pool(Name, Options) -> 41 | supervisor:start_child(?SERVER, [Name, Options]). 42 | 43 | init([]) -> 44 | Child = {reddy_pool, {reddy_pool, start_link, []}, 45 | temporary, brutal_kill, worker, [reddy_pool]}, 46 | Strategy = {simple_one_for_one, 0, 1}, 47 | {ok, {Strategy, [Child]}}. 48 | -------------------------------------------------------------------------------- /src/reddy_protocol.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_protocol). 25 | 26 | -include("reddy.hrl"). 27 | 28 | -ifdef(TEST). 29 | -include_lib("eunit/include/eunit.hrl"). 30 | -endif. 31 | 32 | -define(LTOSTRB(X), integer_to_list(size(X))). 33 | -define(LTOSTR(X), integer_to_list(length(X))). 34 | -define(BTOI(X), list_to_integer(binary_to_list(X))). 35 | 36 | -export([parse_status/1, 37 | parse_integer/1, 38 | parse_bulk_size/1, 39 | parse_multi_bulk_count/1]). 40 | -export([to_iolist/1, to_binary/1]). 41 | 42 | to_iolist(#reddy_op{name=Op, args=Args}) -> 43 | TotalSize = length(Args) + 1, 44 | EncodedArgs = [encode_arg(Arg) || Arg <- Args], 45 | [encode_op(Op, TotalSize)|EncodedArgs]. 46 | 47 | to_binary(Op) when is_record(Op, reddy_op) -> 48 | iolist_to_binary(to_iolist(Op)). 49 | 50 | %% Internal functions 51 | encode_arg(Arg) when is_integer(Arg) -> 52 | Arg1 = integer_to_list(Arg), 53 | Size = ?LTOSTR(Arg1), 54 | ["\$", Size, ?REDDY_EOL, Arg1, ?REDDY_EOL]; 55 | encode_arg(Arg) when is_float(Arg) -> 56 | [Arg1] = io_lib:format("~w", Arg), 57 | Size = ?LTOSTR(Arg1), 58 | ["\$", Size, ?REDDY_EOL, Arg1, ?REDDY_EOL]; 59 | encode_arg(Arg) when is_binary(Arg) -> 60 | Size = ?LTOSTRB(Arg), 61 | ["\$", Size, ?REDDY_EOL, Arg, ?REDDY_EOL]; 62 | encode_arg(Arg) when is_list(Arg) -> 63 | Size = ?LTOSTR(Arg), 64 | ["\$", Size, ?REDDY_EOL, Arg, ?REDDY_EOL]. 65 | 66 | encode_op(Op, ArgCount) -> 67 | OpSize = ?LTOSTR(Op), 68 | ["*", integer_to_list(ArgCount), ?REDDY_EOL, "\$", OpSize, ?REDDY_EOL, Op, ?REDDY_EOL]. 69 | 70 | parse_status(<<"+OK">>) -> 71 | ok; 72 | parse_status(<<"+PONG">>) -> 73 | ok; 74 | parse_status(<<"+", Reason/binary>>) -> 75 | {ok, Reason}; 76 | parse_status(<<"-", Reason/binary>>) -> 77 | {error, Reason}. 78 | 79 | parse_integer(<<":", Number/binary>>) -> 80 | ?BTOI(Number); 81 | parse_integer(<<"$-1">>) -> 82 | 0; 83 | parse_integer(<<"-ERR ", Reason/binary>>) -> 84 | {error, Reason}. 85 | 86 | parse_bulk_size(<<"\$", Number/binary>>) -> 87 | ?BTOI(Number); 88 | parse_bulk_size(<<"-ERR ", Reason/binary>>) -> 89 | {error, Reason}. 90 | 91 | parse_multi_bulk_count(<<"*", Number/binary>>) -> 92 | ?BTOI(Number); 93 | parse_multi_bulk_count(<<"-ERR ", Reason/binary>>) -> 94 | {error, Reason}. 95 | 96 | -ifdef(TEST). 97 | status_test() -> 98 | [?assertMatch(ok, parse_status(<<"+OK">>)), 99 | ?assertMatch(ok, parse_status(<<"+PONG">>)), 100 | ?assertMatch({ok, <<"string">>}, parse_status(<<"+string">>)), 101 | ?assertMatch({error, <<"BADAUTH">>}, parse_status(<<"-BADAUTH">>)), 102 | ?assertError(function_clause, parse_status(<<"\$4">>)), 103 | ?assertError(function_clause, parse_status(<<"\$3">>))]. 104 | 105 | integer_test() -> 106 | [?assertMatch(100, parse_integer(<<":100">>)), 107 | ?assertMatch(-1, parse_integer(<<":-1">>)), 108 | ?assertMatch(0, parse_integer(<<"$-1">>)), 109 | ?assertError(function_clause, parse_integer(<<"+OK">>)), 110 | ?assertError(function_clause, parse_integer(<<"-ERR">>)), 111 | ?assertError(function_clause, parse_integer(<<"\$3">>))]. 112 | 113 | bulk_size_test() -> 114 | [?assertMatch(3, parse_bulk_size(<<"\$3">>)), 115 | ?assertMatch(-1, parse_bulk_size(<<"\$-1">>)), 116 | ?assertError(function_clause, parse_bulk_size(<<"+OK">>)), 117 | ?assertError(function_clause, parse_bulk_size(<<"-ERR">>)), 118 | ?assertError(function_clause, parse_bulk_size(<<":100">>)), 119 | ?assertError(function_clause, parse_bulk_size(<<"*22">>))]. 120 | 121 | multi_bulk_test() -> 122 | [?assertMatch(1033, parse_multi_bulk_count(<<"*1033">>)), 123 | ?assertMatch(-1, parse_multi_bulk_count(<<"*-1">>)), 124 | ?assertError(function_clause, parse_multi_bulk_count(<<"+OK">>)), 125 | ?assertError(function_clause, parse_multi_bulk_count(<<"-ERR">>)), 126 | ?assertError(function_clause, parse_multi_bulk_count(<<":100">>)), 127 | ?assertError(function_clause, parse_multi_bulk_count(<<"\$22">>))]. 128 | 129 | to_binary_test() -> 130 | ?assertMatch(<<"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n">>, 131 | to_binary(#reddy_op{name="SET", 132 | args=[<<"mykey">>, <<"myvalue">>]})). 133 | 134 | -endif. 135 | -------------------------------------------------------------------------------- /src/reddy_server.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_server). 25 | 26 | -include("reddy_ops.hrl"). 27 | 28 | -export([info/1, 29 | auth/2]). 30 | 31 | auth(Conn, Password) when is_pid(Conn) -> 32 | reddy_conn:sync(Conn, ?AUTH, [Password]). 33 | 34 | 35 | info(Conn) when is_pid(Conn) -> 36 | Info = binary_to_list(reddy_conn:sync(Conn, ?INFO, [])), 37 | Lines = string:tokens(Info, "\r\n"), 38 | [list_to_tuple(string:tokens(Line, ":")) || Line <- Lines]; 39 | info(Pool) -> 40 | case reddy_pool:check_out(Pool) of 41 | {ok, Conn} -> 42 | Result = info(Conn), 43 | reddy_pool:check_in(Pool), 44 | Result; 45 | Error -> 46 | Error 47 | end. 48 | -------------------------------------------------------------------------------- /src/reddy_sets.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_sets). 25 | 26 | -include("reddy_ops.hrl"). 27 | 28 | -export([sadd/3, 29 | sadd_/4, 30 | scard/2, 31 | scard_/3, 32 | sdiff/2, 33 | sdiff_/3, 34 | sdiffstore/3, 35 | sdiffstore_/4, 36 | sinter/2, 37 | sinter_/3, 38 | sinterstore/3, 39 | sinterstore_/4, 40 | sismember/3, 41 | sismember_/4, 42 | smembers/2, 43 | smembers_/3, 44 | smove/4, 45 | smove_/5, 46 | spop/2, 47 | spop_/3, 48 | srandmember/2, 49 | srandmember_/3, 50 | srem/3, 51 | srem_/4, 52 | sunion/2, 53 | sunion_/3, 54 | sunionstore/3, 55 | sunionstore_/4]). 56 | 57 | sadd(Conn, Key, Member) when is_pid(Conn) -> 58 | reddy_conn:sync(Conn, ?SADD, [Key, Member]); 59 | sadd(Pool, Key, Member) when is_atom(Pool) -> 60 | ?WITH_POOL(Pool, sadd, [Key, Member]). 61 | 62 | sadd_(Conn, Key, Member, WantsReturn) when is_pid(Conn) -> 63 | reddy_conn:async(Conn, ?SADD, [Key, Member], WantsReturn); 64 | sadd_(Pool, Key, Member, WantsReturn) when is_atom(Pool) -> 65 | ?WITH_POOL(Pool, sadd_, [Key, Member, WantsReturn]). 66 | 67 | scard(Conn, Key) when is_pid(Conn) -> 68 | reddy_conn:sync(Conn, ?SCARD, [Key]); 69 | scard(Pool, Key) when is_atom(Pool) -> 70 | ?WITH_POOL(Pool, scard, [Key]). 71 | 72 | scard_(Conn, Key, WantsReturn) when is_pid(Conn) -> 73 | reddy_conn:async(Conn, ?SCARD, [Key], WantsReturn); 74 | scard_(Pool, Key, WantsReturn) when is_atom(Pool) -> 75 | ?WITH_POOL(Pool, scard_, [Key, WantsReturn]). 76 | 77 | sdiff(Conn, Keys) when is_pid(Conn) -> 78 | reddy_conn:sync(Conn, ?SDIFF, Keys); 79 | sdiff(Pool, Keys) when is_atom(Pool) -> 80 | ?WITH_POOL(Pool, sdiff, [Keys]). 81 | 82 | sdiff_(Conn, Keys, WantsReturn) when is_pid(Conn) -> 83 | reddy_conn:async(Conn, ?SDIFF, Keys, WantsReturn); 84 | sdiff_(Pool, Keys, WantsReturn) when is_atom(Pool) -> 85 | ?WITH_POOL(Pool, sdiff_, [Keys, WantsReturn]). 86 | 87 | sdiffstore(Conn, Dest, Keys) when is_pid(Conn) -> 88 | reddy_conn:sync(Conn, ?SDIFFSTORE, [Dest, Keys]); 89 | sdiffstore(Pool, Dest, Keys) when is_atom(Pool) -> 90 | ?WITH_POOL(Pool, sdiffstore, [Dest, Keys]). 91 | 92 | sdiffstore_(Conn, Dest, Keys, WantsReturn) when is_pid(Conn) -> 93 | reddy_conn:async(Conn, ?SDIFFSTORE, [Dest, Keys], WantsReturn); 94 | sdiffstore_(Pool, Dest, Keys, WantsReturn) when is_atom(Pool) -> 95 | ?WITH_POOL(Pool, sdiffstore_, [Dest, Keys, WantsReturn]). 96 | 97 | sinter(Conn, Keys) when is_pid(Conn) -> 98 | reddy_conn:sync(Conn, ?SINTER, Keys); 99 | sinter(Pool, Keys) when is_atom(Pool) -> 100 | ?WITH_POOL(Pool, sinter, [Keys]). 101 | 102 | sinter_(Conn, Keys, WantsReturn) when is_pid(Conn) -> 103 | reddy_conn:async(Conn, ?SINTER, Keys, WantsReturn); 104 | sinter_(Pool, Keys, WantsReturn) when is_atom(Pool) -> 105 | ?WITH_POOL(Pool, sinter_, [Keys, WantsReturn]). 106 | 107 | sinterstore(Conn, Dest, Keys) when is_pid(Conn) -> 108 | reddy_conn:sync(Conn, ?SINTERSTORE, [Dest, Keys]); 109 | sinterstore(Pool, Dest, Keys) when is_atom(Pool) -> 110 | ?WITH_POOL(Pool, sinterstore, [Dest, Keys]). 111 | 112 | sinterstore_(Conn, Dest, Keys, WantsReturn) when is_pid(Conn) -> 113 | reddy_conn:async(Conn, ?SINTERSTORE, [Dest, Keys], WantsReturn); 114 | sinterstore_(Pool, Dest, Keys, WantsReturn) when is_atom(Pool) -> 115 | ?WITH_POOL(Pool, sinterstore_, [Dest, Keys, WantsReturn]). 116 | 117 | sismember(Conn, Key, Member) when is_pid(Conn) -> 118 | reddy_conn:sync(Conn, ?SISMEMBER, [Key, Member]); 119 | sismember(Pool, Key, Member) when is_atom(Pool) -> 120 | ?WITH_POOL(Pool, sismember, [Key, Member]). 121 | 122 | sismember_(Conn, Key, Member, WantsReturn) when is_pid(Conn) -> 123 | reddy_conn:async(Conn, ?SISMEMBER, [Key, Member], WantsReturn); 124 | sismember_(Pool, Key, Member, WantsReturn) when is_atom(Pool) -> 125 | ?WITH_POOL(Pool, sismember_, [Key, Member, WantsReturn]). 126 | 127 | smembers(Conn, Key) when is_pid(Conn) -> 128 | reddy_conn:sync(Conn, ?SMEMBERS, [Key]); 129 | smembers(Pool, Key) when is_atom(Pool) -> 130 | ?WITH_POOL(Pool, smembers, [Key]). 131 | 132 | smembers_(Conn, Key, WantsReturn) when is_pid(Conn) -> 133 | reddy_conn:async(Conn, ?SMEMBERS, [Key], WantsReturn); 134 | smembers_(Pool, Key, WantsReturn) when is_atom(Pool) -> 135 | ?WITH_POOL(Pool, smembers_, [Key, WantsReturn]). 136 | 137 | smove(Conn, Source, Dest, Member) when is_pid(Conn) -> 138 | reddy_conn:sync(Conn, ?SMOVE, [Source, Dest, Member]); 139 | smove(Pool, Source, Dest, Member) when is_atom(Pool) -> 140 | ?WITH_POOL(Pool, smove, [Source, Dest, Member]). 141 | 142 | smove_(Conn, Source, Dest, Member, WantsReturn) when is_pid(Conn) -> 143 | reddy_conn:async(Conn, ?SMOVE, [Source, Dest, Member], WantsReturn); 144 | smove_(Pool, Source, Dest, Member, WantsReturn) when is_atom(Pool) -> 145 | ?WITH_POOL(Pool, smove_, [Source, Dest, Member, WantsReturn]). 146 | 147 | spop(Conn, Key) when is_pid(Conn) -> 148 | reddy_conn:sync(Conn, ?SPOP, [Key]); 149 | spop(Pool, Key) when is_atom(Pool) -> 150 | ?WITH_POOL(Pool, spop, [Key]). 151 | 152 | spop_(Conn, Key, WantsReturn) when is_pid(Conn) -> 153 | reddy_conn:async(Conn, ?SPOP, [Key], WantsReturn); 154 | spop_(Pool, Key, WantsReturn) when is_atom(Pool) -> 155 | ?WITH_POOL(Pool, spop_, [Key, WantsReturn]). 156 | 157 | srandmember(Conn, Key) when is_pid(Conn) -> 158 | reddy_conn:sync(Conn, ?SRANDMEMBER, [Key]); 159 | srandmember(Pool, Key) when is_atom(Pool) -> 160 | ?WITH_POOL(Pool, srandmember, [Key]). 161 | 162 | srandmember_(Conn, Key, WantsReturn) when is_pid(Conn) -> 163 | reddy_conn:async(Conn, ?SRANDMEMBER, [Key], WantsReturn); 164 | srandmember_(Pool, Key, WantsReturn) when is_atom(Pool) -> 165 | ?WITH_POOL(Pool, srandmember_, [Key, WantsReturn]). 166 | 167 | srem(Conn, Key, Member) when is_pid(Conn) -> 168 | reddy_conn:sync(Conn, ?SREM, [Key, Member]); 169 | srem(Pool, Key, Member) when is_atom(Pool) -> 170 | ?WITH_POOL(Pool, srem, [Key, Member]). 171 | 172 | srem_(Conn, Key, Member, WantsReturn) when is_pid(Conn) -> 173 | reddy_conn:async(Conn, ?SREM, [Key, Member], WantsReturn); 174 | srem_(Pool, Key, Member, WantsReturn) when is_atom(Pool) -> 175 | ?WITH_POOL(Pool, srem_, [Key, Member, WantsReturn]). 176 | 177 | sunion(Conn, Keys) when is_pid(Conn) -> 178 | reddy_conn:sync(Conn, ?SUNION, Keys); 179 | sunion(Pool, Keys) when is_atom(Pool) -> 180 | ?WITH_POOL(Pool, sunion, Keys). 181 | 182 | sunion_(Conn, Keys, WantsReturn) when is_pid(Conn) -> 183 | reddy_conn:async(Conn, ?SUNION, Keys, WantsReturn); 184 | sunion_(Pool, Keys, WantsReturn) when is_atom(Pool) -> 185 | ?WITH_POOL(Pool, sunion_, [Keys, WantsReturn]). 186 | 187 | sunionstore(Conn, Dest, Keys) when is_pid(Conn) -> 188 | reddy_conn:sync(Conn, ?SUNIONSTORE, [Dest, Keys]); 189 | sunionstore(Pool, Dest, Keys) when is_atom(Pool) -> 190 | ?WITH_POOL(Pool, sunionstore, [Dest, Keys]). 191 | 192 | sunionstore_(Conn, Dest, Keys, WantsReturn) when is_pid(Conn) -> 193 | reddy_conn:async(Conn, ?SUNIONSTORE, [Dest, Keys], WantsReturn); 194 | sunionstore_(Pool, Dest, Keys, WantsReturn) when is_atom(Pool) -> 195 | ?WITH_POOL(Pool, sunionstore_, [Dest, Keys, WantsReturn]). 196 | -------------------------------------------------------------------------------- /src/reddy_strings.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_strings). 25 | 26 | -include("reddy_ops.hrl"). 27 | 28 | -export([append/3, 29 | append_/4, 30 | decr/2, 31 | decr_/3, 32 | decrby/3, 33 | decrby_/4, 34 | get/2, 35 | get_/3, 36 | getbit/3, 37 | getbit_/4, 38 | getrange/4, 39 | getrange_/5, 40 | getset/3, 41 | getset_/4, 42 | incr/2, 43 | incr_/3, 44 | incrby/3, 45 | incrby_/4, 46 | mget/2, 47 | mget_/3, 48 | mset/2, 49 | mset_/3, 50 | msetnx/2, 51 | msetnx_/3, 52 | set/3, 53 | set_/4, 54 | setbit/4, 55 | setbit_/5, 56 | setex/4, 57 | setex_/5, 58 | setnx/3, 59 | setnx_/4, 60 | setrange/4, 61 | setrange_/5, 62 | strlen/2, 63 | strlen_/3]). 64 | 65 | append(Conn, Key, Value) when is_pid(Conn) -> 66 | reddy_conn:sync(Conn, ?APPEND, [Key, Value]); 67 | append(Pool, Key, Value) when is_atom(Pool) -> 68 | ?WITH_POOL(Pool, append, [Key, Value]). 69 | 70 | append_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 71 | reddy_conn:async(Conn, ?APPEND, [Key, Value], WantsReturn); 72 | append_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 73 | ?WITH_POOL(Pool, append_, [Key, Value, WantsReturn]). 74 | 75 | decr(Conn, Key) when is_pid(Conn) -> 76 | reddy_conn:sync(Conn, ?DECR, [Key]); 77 | decr(Pool, Key) when is_atom(Pool) -> 78 | ?WITH_POOL(Pool, decr, [Key]). 79 | 80 | decr_(Conn, Key, WantsReturn) when is_pid(Conn) -> 81 | reddy_conn:async(Conn, ?DECR, [Key], WantsReturn); 82 | decr_(Pool, Key, WantsReturn) when is_atom(Pool) -> 83 | ?WITH_POOL(Pool, decr_, [Key, WantsReturn]). 84 | 85 | decrby(Conn, Key, Decrement) when is_pid(Conn) -> 86 | reddy_conn:sync(Conn, ?DECRBY, [Key, Decrement]); 87 | decrby(Pool, Key, Decrement) when is_atom(Pool) -> 88 | ?WITH_POOL(Pool, decrby, [Key, Decrement]). 89 | 90 | decrby_(Conn, Key, Decrement, WantsReturn) when is_pid(Conn) -> 91 | reddy_conn:async(Conn, ?DECRBY, [Key, Decrement], WantsReturn); 92 | decrby_(Pool, Key, Decrement, WantsReturn) when is_atom(Pool) -> 93 | ?WITH_POOL(Pool, decrby_, [Key, Decrement, WantsReturn]). 94 | 95 | get(Conn, Key) when is_pid(Conn) -> 96 | reddy_conn:sync(Conn, ?GET, [Key]); 97 | get(Pool, Key) when is_atom(Pool) -> 98 | ?WITH_POOL(Pool, get, [Key]). 99 | 100 | get_(Conn, Key, WantsReturn) when is_pid(Conn) -> 101 | reddy_conn:async(Conn, ?GET, [Key], WantsReturn); 102 | get_(Pool, Key, WantsReturn) when is_atom(Pool) -> 103 | ?WITH_POOL(Pool, get_, [Key, WantsReturn]). 104 | 105 | getbit(Conn, Key, Offset) when is_pid(Conn) -> 106 | reddy_conn:sync(Conn, ?GETBIT, [Key, Offset]); 107 | getbit(Pool, Key, Offset) when is_atom(Pool) -> 108 | ?WITH_POOL(Pool, getbit, [Key, Offset]). 109 | 110 | getbit_(Conn, Key, Offset, WantsReturn) when is_pid(Conn) -> 111 | reddy_conn:async(Conn, ?GETBIT, [Key, Offset], WantsReturn); 112 | getbit_(Pool, Key, Offset, WantsReturn) when is_atom(Pool) -> 113 | ?WITH_POOL(Pool, getbit_, [Key, Offset, WantsReturn]). 114 | 115 | getrange(Conn, Key, Start, End) when is_pid(Conn) -> 116 | reddy_conn:sync(Conn, ?GETRANGE, [Key, Start, End]); 117 | getrange(Pool, Key, Start, End) when is_atom(Pool) -> 118 | ?WITH_POOL(Pool, getrange, [Key, Start, End]). 119 | 120 | getrange_(Conn, Key, Start, End, WantsReturn) when is_pid(Conn) -> 121 | reddy_conn:async(Conn, ?GETRANGE, [Key, Start, End], WantsReturn); 122 | getrange_(Pool, Key, Start, End, WantsReturn) when is_atom(Pool) -> 123 | ?WITH_POOL(Pool, getrange_, [Key, Start, End, WantsReturn]). 124 | 125 | getset(Conn, Key, Value) when is_pid(Conn) -> 126 | reddy_conn:sync(Conn, ?GETSET, [Key, Value]); 127 | getset(Pool, Key, Value) when is_atom(Pool) -> 128 | ?WITH_POOL(Pool, getset, [Key, Value]). 129 | 130 | getset_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 131 | reddy_conn:async(Conn, ?GETSET, [Key, Value], WantsReturn); 132 | getset_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 133 | ?WITH_POOL(Pool, getset_, [Key, Value, WantsReturn]). 134 | 135 | incr(Conn, Key) when is_pid(Conn) -> 136 | reddy_conn:sync(Conn, ?INCR, [Key]); 137 | incr(Pool, Key) when is_atom(Pool) -> 138 | ?WITH_POOL(Pool, incr, [Key]). 139 | 140 | incr_(Conn, Key, WantsReturn) when is_pid(Conn) -> 141 | reddy_conn:async(Conn, ?INCR, [Key], WantsReturn); 142 | incr_(Pool, Key, WantsReturn) when is_atom(Pool) -> 143 | ?WITH_POOL(Pool, incr_, [Key, WantsReturn]). 144 | 145 | incrby(Conn, Key, Increment) when is_pid(Conn) -> 146 | reddy_conn:sync(Conn, ?INCRBY, [Key, Increment]); 147 | incrby(Pool, Key, Increment) when is_atom(Pool) -> 148 | ?WITH_POOL(Pool, incrby, [Key, Increment]). 149 | 150 | incrby_(Conn, Key, Increment, WantsReturn) when is_pid(Conn) -> 151 | reddy_conn:async(Conn, ?INCRBY, [Key, Increment], WantsReturn); 152 | incrby_(Pool, Key, Increment, WantsReturn) when is_atom(Pool) -> 153 | ?WITH_POOL(Pool, incrby_, [Key, Increment, WantsReturn]). 154 | 155 | mget(Conn, Keys) when is_pid(Conn), 156 | is_list(Keys) -> 157 | reddy_conn:sync(Conn, ?MGET, Keys); 158 | mget(Pool, Keys) when is_atom(Pool), 159 | is_list(Keys) -> 160 | ?WITH_POOL(Pool, mget, [Keys]). 161 | 162 | mget_(Conn, Keys, WantsReturn) when is_pid(Conn), 163 | is_list(Keys) -> 164 | reddy_conn:async(Conn, ?MGET, Keys, WantsReturn); 165 | mget_(Pool, Keys, WantsReturn) when is_atom(Pool), 166 | is_list(Keys) -> 167 | ?WITH_POOL(Pool, mget_, [Keys, WantsReturn]). 168 | 169 | mset(Conn, KeyValuePairs) when is_pid(Conn), 170 | is_list(KeyValuePairs) -> 171 | reddy_conn:sync(Conn, ?MSET, reddy_types:convert_field_value_pairs(KeyValuePairs)); 172 | mset(Pool, KeyValuePairs) when is_atom(Pool), 173 | is_list(KeyValuePairs) -> 174 | ?WITH_POOL(Pool, mset, [KeyValuePairs]). 175 | 176 | mset_(Conn, KeyValuePairs, WantsReturn) when is_pid(Conn), 177 | is_list(KeyValuePairs) -> 178 | reddy_conn:async(Conn, ?MSET, reddy_types:convert_field_value_pairs(KeyValuePairs), WantsReturn); 179 | mset_(Pool, KeyValuePairs, WantsReturn) when is_atom(Pool), 180 | is_list(KeyValuePairs) -> 181 | ?WITH_POOL(Pool, mset_, [KeyValuePairs, WantsReturn]). 182 | 183 | msetnx(Conn, KeyValuePairs) when is_pid(Conn), 184 | is_list(KeyValuePairs) -> 185 | reddy_conn:sync(Conn, ?MSETNX, reddy_types:convert_field_value_pairs(KeyValuePairs)); 186 | msetnx(Pool, KeyValuePairs) when is_atom(Pool), 187 | is_list(KeyValuePairs) -> 188 | ?WITH_POOL(Pool, msetnx, [KeyValuePairs]). 189 | 190 | 191 | msetnx_(Conn, KeyValuePairs, WantsReturn) when is_pid(Conn), 192 | is_list(KeyValuePairs) -> 193 | reddy_conn:async(Conn, ?MSETNX, reddy_types:convert_field_value_pairs(KeyValuePairs), WantsReturn); 194 | msetnx_(Pool, KeyValuePairs, WantsReturn) when is_atom(Pool), 195 | is_list(KeyValuePairs) -> 196 | ?WITH_POOL(Pool, msetnx_, [KeyValuePairs, WantsReturn]). 197 | 198 | set(Conn, Key, Value) when is_pid(Conn) -> 199 | reddy_conn:sync(Conn, ?SET, [Key, Value]); 200 | set(Pool, Key, Value) when is_atom(Pool) -> 201 | ?WITH_POOL(Pool, set, [Key, Value]). 202 | 203 | set_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 204 | reddy_conn:async(Conn, ?SET, [Key, Value], WantsReturn); 205 | set_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 206 | ?WITH_POOL(Pool, set_, [Key, Value, WantsReturn]). 207 | 208 | setbit(Conn, Key, Offset, Value) when is_pid(Conn) -> 209 | reddy_conn:sync(Conn, ?SETBIT, [Key, Offset, Value]); 210 | setbit(Pool, Key, Offset, Value) when is_atom(Pool) -> 211 | ?WITH_POOL(Pool, setbit, [Key, Offset, Value]). 212 | 213 | setbit_(Conn, Key, Offset, Value, WantsReturn) when is_pid(Conn) -> 214 | reddy_conn:async(Conn, ?SETBIT, [Key, Offset, Value], WantsReturn); 215 | setbit_(Pool, Key, Offset, Value, WantsReturn) when is_atom(Pool) -> 216 | ?WITH_POOL(Pool, setbit_, [Key, Offset, Value, WantsReturn]). 217 | 218 | setex(Conn, Key, Seconds, Value) when is_pid(Conn) -> 219 | reddy_conn:sync(Conn, ?SETEX, [Key, Seconds, Value]); 220 | setex(Pool, Key, Seconds, Value) when is_atom(Pool) -> 221 | ?WITH_POOL(Pool, setex, [Key, Seconds, Value]). 222 | 223 | setex_(Conn, Key, Seconds, Value, WantsReturn) when is_pid(Conn) -> 224 | reddy_conn:async(Conn, ?SETEX, [Key, Seconds, Value], WantsReturn); 225 | setex_(Pool, Key, Seconds, Value, WantsReturn) when is_atom(Pool) -> 226 | ?WITH_POOL(Pool, setex_, [Key, Seconds, Value, WantsReturn]). 227 | 228 | setnx(Conn, Key, Value) when is_pid(Conn) -> 229 | reddy_conn:sync(Conn, ?SETNX, [Key, Value]); 230 | setnx(Pool, Key, Value) when is_atom(Pool) -> 231 | ?WITH_POOL(Pool, setnx, [Key, Value]). 232 | 233 | setnx_(Conn, Key, Value, WantsReturn) when is_pid(Conn) -> 234 | reddy_conn:async(Conn, ?SETNX, [Key, Value], WantsReturn); 235 | setnx_(Pool, Key, Value, WantsReturn) when is_atom(Pool) -> 236 | ?WITH_POOL(Pool, setnx_, [Key, Value, WantsReturn]). 237 | 238 | setrange(Conn, Key, Offset, Value) when is_pid(Conn) -> 239 | reddy_conn:sync(Conn, ?SETRANGE, [Key, Offset, Value]); 240 | setrange(Pool, Key, Offset, Value) when is_atom(Pool) -> 241 | ?WITH_POOL(Pool, setrange, [Key, Offset, Value]). 242 | 243 | setrange_(Conn, Key, Offset, Value, WantsReturn) when is_pid(Conn) -> 244 | reddy_conn:async(Conn, ?SETRANGE, [Key, Offset, Value], WantsReturn); 245 | setrange_(Pool, Key, Offset, Value, WantsReturn) when is_atom(Pool) -> 246 | ?WITH_POOL(Pool, setrange_, [Key, Offset, Value, WantsReturn]). 247 | 248 | strlen(Conn, Key) when is_pid(Conn) -> 249 | reddy_conn:sync(Conn, ?STRLEN, [Key]); 250 | strlen(Pool, Key) when is_atom(Pool) -> 251 | ?WITH_POOL(Pool, strlen, [Key]). 252 | 253 | strlen_(Conn, Key, WantsReturn) when is_pid(Conn) -> 254 | reddy_conn:async(Conn, ?STRLEN, [Key], WantsReturn); 255 | strlen_(Pool, Key, WantsReturn) when is_atom(Pool) -> 256 | ?WITH_POOL(Pool, strlen_, [Key, WantsReturn]). 257 | -------------------------------------------------------------------------------- /src/reddy_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_sup). 25 | 26 | -behaviour(supervisor). 27 | 28 | %% API 29 | -export([start_link/0]). 30 | 31 | %% Supervisor callbacks 32 | -export([init/1]). 33 | 34 | -define(SERVER, ?MODULE). 35 | 36 | start_link() -> 37 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 38 | 39 | init([]) -> 40 | RestartStrategy = one_for_one, 41 | MaxRestarts = 3, 42 | MaxSecondsBetweenRestarts = 120, 43 | 44 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, 45 | 46 | Restart = permanent, 47 | Shutdown = 2000, 48 | Type = supervisor, 49 | 50 | Children = [{'reddy_conn_sup', {reddy_conn_sup, start_link, []}, 51 | Restart, Shutdown, Type, [reddy_conn_sup]}, 52 | {'reddy_pool_sup', {reddy_pool_sup, start_link, []}, 53 | Restart, Shutdown, Type, [reddy_pool_sup]}], 54 | 55 | {ok, {SupFlags, Children}}. 56 | -------------------------------------------------------------------------------- /src/reddy_time.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_time). 25 | 26 | -export([now_to_unixts/1, 27 | future_to_unixts/2]). 28 | 29 | now_to_unixts({Mega, Secs, _}) -> 30 | Mega * 1000000 + Secs. 31 | 32 | future_to_unixts({Mega, Secs, _}, Offset) -> 33 | (Mega * 1000000 + Secs) + Offset. 34 | -------------------------------------------------------------------------------- /src/reddy_types.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 Kevin Smith 2 | %% 3 | %% Permission is hereby granted, free of charge, to any person 4 | %% obtaining a copy of this software and associated documentation 5 | %% files (the "Software"), to deal in the Software without 6 | %% restriction, including without limitation the rights to use, 7 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | %% copies of the Software, and to permit persons to whom the 9 | %% Software is furnished to do so, subject to the following 10 | %% conditions: 11 | %% 12 | %% The above copyright notice and this permission notice shall be 13 | %% included in all copies or substantial portions of the Software. 14 | %% 15 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | %% OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -module(reddy_types). 25 | 26 | -export([convert_field_value_pairs/1]). 27 | 28 | convert_field_value_pairs(FVPairs) -> 29 | convert_field_value_pairs(FVPairs, []). 30 | 31 | convert_field_value_pairs([], Accum) -> 32 | lists:reverse(Accum); 33 | convert_field_value_pairs([{Field, Value}|T], Accum) -> 34 | Field1 = if 35 | is_atom(Field) -> 36 | list_to_binary(atom_to_list(Field)); 37 | true -> 38 | Field 39 | end, 40 | convert_field_value_pairs(T, [[Field1, Value]|Accum]); 41 | convert_field_value_pairs([H|T], Accum) -> 42 | convert_field_value_pairs(T, [H|Accum]). 43 | --------------------------------------------------------------------------------