1%% 2%% Erlang Redis client 3%% 4%% Usage: 5%% {ok, Client} = eredis:start_link(). 6%% {ok, <<"OK">>} = eredis:q(Client, ["SET", "foo", "bar"]). 7%% {ok, <<"bar">>} = eredis:q(Client, ["GET", "foo"]). 8 9-module(eredis). 10-include("eredis.hrl"). 11 12%% Default timeout for calls to the client gen_server 13%% Specified in http://www.erlang.org/doc/man/gen_server.html#call-3 14-define(TIMEOUT, 5000). 15 16-export([start_link/0, start_link/1, start_link/2, start_link/3, start_link/4, 17 start_link/5, start_link/6, stop/1, q/2, q/3, qp/2, qp/3, q_noreply/2, 18 q_async/2, q_async/3]). 19 20%% Exported for testing 21-export([create_multibulk/1]). 22 23%% Type of gen_server process id 24-type client() :: pid() | 25 atom() | 26 {atom(),atom()} | 27 {global,term()} | 28 {via,atom(),term()}. 29 30%% 31%% PUBLIC API 32%% 33 34start_link() -> 35 start_link("127.0.0.1", 6379, 0, ""). 36 37start_link(Host, Port) -> 38 start_link(Host, Port, 0, ""). 39 40start_link(Host, Port, Database) -> 41 start_link(Host, Port, Database, ""). 42 43start_link(Host, Port, Database, Password) -> 44 start_link(Host, Port, Database, Password, 100). 45 46start_link(Host, Port, Database, Password, ReconnectSleep) -> 47 start_link(Host, Port, Database, Password, ReconnectSleep, ?TIMEOUT). 48 49start_link(Host, Port, Database, Password, ReconnectSleep, ConnectTimeout) 50 when is_list(Host), 51 is_integer(Port), 52 is_integer(Database) orelse Database == undefined, 53 is_list(Password), 54 is_integer(ReconnectSleep) orelse ReconnectSleep =:= no_reconnect, 55 is_integer(ConnectTimeout) -> 56 57 eredis_client:start_link(Host, Port, Database, Password, 58 ReconnectSleep, ConnectTimeout). 59 60%% @doc: Callback for starting from poolboy 61-spec start_link(server_args()) -> {ok, Pid::pid()} | {error, Reason::term()}. 62start_link(Args) -> 63 Host = proplists:get_value(host, Args, "127.0.0.1"), 64 Port = proplists:get_value(port, Args, 6379), 65 Database = proplists:get_value(database, Args, 0), 66 Password = proplists:get_value(password, Args, ""), 67 ReconnectSleep = proplists:get_value(reconnect_sleep, Args, 100), 68 ConnectTimeout = proplists:get_value(connect_timeout, Args, ?TIMEOUT), 69 start_link(Host, Port, Database, Password, ReconnectSleep, ConnectTimeout). 70 71stop(Client) -> 72 eredis_client:stop(Client). 73 74-spec q(Client::client(), Command::[any()]) -> 75 {ok, return_value()} | {error, Reason::binary() | no_connection}. 76%% @doc: Executes the given command in the specified connection. The 77%% command must be a valid Redis command and may contain arbitrary 78%% data which will be converted to binaries. The returned values will 79%% always be binaries. 80q(Client, Command) -> 81 call(Client, Command, ?TIMEOUT). 82 83q(Client, Command, Timeout) -> 84 call(Client, Command, Timeout). 85 86 87-spec qp(Client::client(), Pipeline::pipeline()) -> 88 [{ok, return_value()} | {error, Reason::binary()}] | 89 {error, no_connection}. 90%% @doc: Executes the given pipeline (list of commands) in the 91%% specified connection. The commands must be valid Redis commands and 92%% may contain arbitrary data which will be converted to binaries. The 93%% values returned by each command in the pipeline are returned in a list. 94qp(Client, Pipeline) -> 95 pipeline(Client, Pipeline, ?TIMEOUT). 96 97qp(Client, Pipeline, Timeout) -> 98 pipeline(Client, Pipeline, Timeout). 99 100-spec q_noreply(Client::client(), Command::[any()]) -> ok. 101%% @doc Executes the command but does not wait for a response and ignores any errors. 102%% @see q/2 103q_noreply(Client, Command) -> 104 cast(Client, Command). 105 106-spec q_async(Client::client(), Command::[any()]) -> ok. 107% @doc Executes the command, and sends a message to this process with the response (with either error or success). Message is of the form `{response, Reply}', where `Reply' is the reply expected from `q/2'. 108q_async(Client, Command) -> 109 q_async(Client, Command, self()). 110 111-spec q_async(Client::client(), Command::[any()], Pid::pid()|atom()) -> ok. 112%% @doc Executes the command, and sends a message to `Pid' with the response (with either or success). 113%% @see 1_async/2 114q_async(Client, Command, Pid) when is_pid(Pid) -> 115 Request = {request, create_multibulk(Command), Pid}, 116 gen_server:cast(Client, Request). 117 118%% 119%% INTERNAL HELPERS 120%% 121 122call(Client, Command, Timeout) -> 123 Request = {request, create_multibulk(Command)}, 124 gen_server:call(Client, Request, Timeout). 125 126pipeline(_Client, [], _Timeout) -> 127 []; 128pipeline(Client, Pipeline, Timeout) -> 129 Request = {pipeline, [create_multibulk(Command) || Command <- Pipeline]}, 130 gen_server:call(Client, Request, Timeout). 131 132cast(Client, Command) -> 133 Request = {request, create_multibulk(Command)}, 134 gen_server:cast(Client, Request). 135 136-spec create_multibulk(Args::[any()]) -> Command::iolist(). 137%% @doc: Creates a multibulk command with all the correct size headers 138create_multibulk(Args) -> 139 ArgCount = [<<$*>>, integer_to_list(length(Args)), <<?NL>>], 140 ArgsBin = lists:map(fun to_bulk/1, lists:map(fun to_binary/1, Args)), 141 142 [ArgCount, ArgsBin]. 143 144to_bulk(B) when is_binary(B) -> 145 [<<$$>>, integer_to_list(iolist_size(B)), <<?NL>>, B, <<?NL>>]. 146 147%% @doc: Convert given value to binary. Fallbacks to 148%% term_to_binary/1. For floats, throws {cannot_store_floats, Float} 149%% as we do not want floats to be stored in Redis. Your future self 150%% will thank you for this. 151to_binary(X) when is_list(X) -> list_to_binary(X); 152to_binary(X) when is_atom(X) -> atom_to_binary(X, utf8); 153to_binary(X) when is_binary(X) -> X; 154to_binary(X) when is_integer(X) -> integer_to_binary(X); 155to_binary(X) when is_float(X) -> throw({cannot_store_floats, X}); 156to_binary(X) -> term_to_binary(X). 157