Commit 1259672f authored by antirez's avatar antirez
Browse files

client libs removed from Redis git

parent 5762b7f0
#include "redisclient.h"
#include <iostream>
using namespace std;
#define ASSERT_EQUAL(x,y) assert_equal(x, y, __LINE__)
#define ASSERT_NOT_EQUAL(x,y) assert_not_equal(x, y, __LINE__)
#define ASSERT_GT(x,y) assert_gt(x, y, __LINE__)
template <typename T>
void assert_equal(const T & actual, const T & expected, int lineno)
{
#ifndef NDEBUG
cerr << "assert_equal('" << expected << "', '" << actual << "')" << endl;
#endif
if (expected != actual)
{
cerr << "expected '" << expected << "' got '" << actual << "'" << endl
<< "failing test called from line " << lineno << endl;
exit(1);
}
#ifndef NDEBUG
cerr << "... OK" << endl;
#endif
}
template <typename T>
void assert_not_equal(const T & a, const T & b, int lineno)
{
if (a == b)
{
cerr << "expected inequality" << endl
<< "failing test called from line " << lineno << endl;
exit(1);
}
}
template <typename T>
void assert_gt(const T & a, const T & b, int lineno)
{
#ifndef NDEBUG
cerr << "assert_gt('" << a << "', '" << b << "')" << endl;
#endif
if (a <= b)
{
cerr << "expected '" << a << "' > '" << b << "'" << endl
<< "failing test called from line " << lineno << endl;
exit(1);
}
#ifndef NDEBUG
cerr << "... OK" << endl;
#endif
}
void test(const string & name)
{
#ifndef NDEBUG
cerr << "------------------------------" << endl
<< "starting test: " << name << endl;
#endif
}
int main(int argc, char ** argv)
{
try
{
redis::client c;
// Test on high number databases
c.select(14);
c.flushdb();
c.select(15);
c.flushdb();
string foo("foo"), bar("bar"), baz("baz"), buz("buz"), goo("goo");
test("auth");
{
// TODO ... needs a conf for redis-server
}
test("info");
{
// doesn't throw? then, has valid numbers and known info-keys.
redis::server_info info;
c.info(info);
}
test("set, get");
{
c.set(foo, bar);
ASSERT_EQUAL(c.get(foo), bar);
}
test("getset");
{
ASSERT_EQUAL(c.getset(foo, baz), bar);
ASSERT_EQUAL(c.get(foo), baz);
}
test("mget");
{
string x_val("hello"), y_val("world");
c.set("x", x_val);
c.set("y", y_val);
redis::client::string_vector keys;
keys.push_back("x");
keys.push_back("y");
redis::client::string_vector vals;
c.mget(keys, vals);
ASSERT_EQUAL(vals.size(), size_t(2));
ASSERT_EQUAL(vals[0], x_val);
ASSERT_EQUAL(vals[1], y_val);
}
test("setnx");
{
ASSERT_EQUAL(c.setnx(foo, bar), false);
ASSERT_EQUAL(c.setnx(buz, baz), true);
ASSERT_EQUAL(c.get(buz), baz);
}
test("incr");
{
ASSERT_EQUAL(c.incr("goo"), 1L);test("nonexistent (0) -> 1");
ASSERT_EQUAL(c.incr("goo"), 2L);test("1->2");
}
test("decr");
{
ASSERT_EQUAL(c.decr("goo"), 1L);test("2->1");
ASSERT_EQUAL(c.decr("goo"), 0L);test("1->0");
}
test("incrby");
{
ASSERT_EQUAL(c.incrby("goo", 3), 3L);test("0->3");
ASSERT_EQUAL(c.incrby("goo", 2), 5L);test("3->5");
}
test("exists");
{
ASSERT_EQUAL(c.exists("goo"), true);
}
test("del");
{
c.del("goo");
ASSERT_EQUAL(c.exists("goo"), false);
}
test("type (basic)");
{
ASSERT_EQUAL(c.type(goo), redis::client::datatype_none);test("we deleted it");
c.set(goo, "redis");
ASSERT_EQUAL(c.type(goo), redis::client::datatype_string);
}
test("keys");
{
redis::client::string_vector keys;
ASSERT_EQUAL(c.keys("*oo", keys), 2L);
ASSERT_EQUAL(keys.size(), 2UL);
ASSERT_EQUAL(keys[0], foo);
ASSERT_EQUAL(keys[1], goo);
}
test("randomkey");
{
ASSERT_GT(c.randomkey().size(), 0UL);
}
test("rename");
{
ASSERT_EQUAL(c.exists("foo"), true);
ASSERT_EQUAL(c.exists("doo"), false);
c.rename("foo", "doo");
ASSERT_EQUAL(c.exists("foo"), false);
ASSERT_EQUAL(c.exists("doo"), true);
}
test("renamenx");
{
ASSERT_EQUAL(c.exists("doo"), true);
ASSERT_EQUAL(c.exists("foo"), false);
ASSERT_EQUAL(c.renamenx("doo", "foo"), true);
ASSERT_EQUAL(c.exists("doo"), false);
ASSERT_EQUAL(c.exists("foo"), true);
ASSERT_EQUAL(c.renamenx("goo", "foo"), false);
ASSERT_EQUAL(c.exists("foo"), true);
ASSERT_EQUAL(c.exists("goo"), true);
}
test("dbsize");
{
ASSERT_GT(c.dbsize(), 0L);
}
test("expire");
{
c.expire("goo", 1);
#ifndef NDEBUG
cerr << "please wait a few seconds.." << endl;
#endif
sleep(2);
ASSERT_EQUAL(c.exists("goo"), false);
}
test("rpush");
{
ASSERT_EQUAL(c.exists("list1"), false);
c.rpush("list1", "val1");
ASSERT_EQUAL(c.llen("list1"), 1L);
ASSERT_EQUAL(c.type("list1"), redis::client::datatype_list);
c.rpush("list1", "val2");
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", 0), string("val1"));
ASSERT_EQUAL(c.lindex("list1", 1), string("val2"));
}
test("lpush");
{
c.del("list1");
ASSERT_EQUAL(c.exists("list1"), false);
c.lpush("list1", "val1");
ASSERT_EQUAL(c.type("list1"), redis::client::datatype_list);
ASSERT_EQUAL(c.llen("list1"), 1L);
c.lpush("list1", "val2");
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", 0), string("val2"));
ASSERT_EQUAL(c.lindex("list1", 1), string("val1"));
}
test("llen");
{
c.del("list1");
ASSERT_EQUAL(c.exists("list1"), false);
ASSERT_EQUAL(c.llen("list1"), 0L);
c.lpush("list1", "x");
ASSERT_EQUAL(c.llen("list1"), 1L);
c.lpush("list1", "y");
ASSERT_EQUAL(c.llen("list1"), 2L);
}
test("lrange");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.lrange("list1", 0, -1, vals), 2L);
ASSERT_EQUAL(vals.size(), 2UL);
ASSERT_EQUAL(vals[0], string("y"));
ASSERT_EQUAL(vals[1], string("x"));
}
test("lrange with subset of full list");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.lrange("list1", 0, 1, vals), 2L); // inclusive, so entire list
ASSERT_EQUAL(vals.size(), 2UL);
ASSERT_EQUAL(vals[0], string("y"));
ASSERT_EQUAL(vals[1], string("x"));
redis::client::string_vector vals2;
ASSERT_EQUAL(c.lrange("list1", 0, 0, vals2), 1L); // inclusive, so first item
ASSERT_EQUAL(vals2.size(), 1UL);
ASSERT_EQUAL(vals2[0], string("y"));
redis::client::string_vector vals3;
ASSERT_EQUAL(c.lrange("list1", -1, -1, vals3), 1L); // inclusive, so first item
ASSERT_EQUAL(vals3.size(), 1UL);
ASSERT_EQUAL(vals3[0], string("x"));
}
test("get_list");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.get_list("list1", vals), 2L);
ASSERT_EQUAL(vals.size(), 2UL);
ASSERT_EQUAL(vals[0], string("y"));
ASSERT_EQUAL(vals[1], string("x"));
}
test("ltrim");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
c.ltrim("list1", 0, 0);
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 1L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.get_list("list1", vals), 1L);
ASSERT_EQUAL(vals[0], string("y"));
}
test("lindex");
{
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
c.rpush("list1", "x");
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", -1), string("x"));
ASSERT_EQUAL(c.lindex("list1", 1), string("x"));
}
test("lset");
{
c.lset("list1", 1, "z");
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
ASSERT_EQUAL(c.llen("list1"), 2L);
}
test("lrem");
{
c.lrem("list1", 1, "z");
ASSERT_EQUAL(c.llen("list1"), 1L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
// list1 = [ y ]
ASSERT_EQUAL(c.lrem("list1", 0, "q"), 0L);
c.rpush("list1", "z");
c.rpush("list1", "z");
c.rpush("list1", "z");
c.rpush("list1", "a");
// list1 = [ y, z, z, z, a ]
ASSERT_EQUAL(c.lrem("list1", 2, "z"), 2L);
// list1 = [ y, z, a ]
ASSERT_EQUAL(c.llen("list1"), 3L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
ASSERT_EQUAL(c.lindex("list1", 2), string("a"));
c.rpush("list1", "z");
// list1 = [ y, z, a, z ]
ASSERT_EQUAL(c.lrem("list1", -1, "z"), 1L); // <0 => rm R to L
// list1 = [ y, z, a ]
ASSERT_EQUAL(c.llen("list1"), 3L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
ASSERT_EQUAL(c.lindex("list1", 2), string("a"));
// list1 = [ y, z, a ]
// try to remove 5 'a's but there's only 1 ... no problem.
ASSERT_EQUAL(c.lrem("list1", 5, "a"), 1L);
// list1 = [ y, z ]
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
}
test("lrem_exact");
{
// list1 = [ y, z ]
// try to remove 5 'z's but there's only 1 ... now it's a problem.
bool threw = false;
try
{
c.lrem_exact("list1", 5, "z");
}
catch (redis::value_error & e)
{
threw = true;
}
ASSERT_EQUAL(threw, true);
// This DOES remove the one 'z' though
// list1 = [ y ]
ASSERT_EQUAL(c.llen("list1"), 1L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
}
test("lpop");
{
ASSERT_EQUAL(c.lpop("list1"), string("y"));
// list1 = []
ASSERT_EQUAL(c.lpop("list1"), redis::client::missing_value);
}
test("rpop");
{
c.rpush("list1", "hello");
c.rpush("list1", "world");
ASSERT_EQUAL(c.rpop("list1"), string("world"));
ASSERT_EQUAL(c.rpop("list1"), string("hello"));
ASSERT_EQUAL(c.lpop("list1"), redis::client::missing_value);
}
test("sadd");
{
c.sadd("set1", "sval1");
ASSERT_EQUAL(c.exists("set1"), true);
ASSERT_EQUAL(c.type("set1"), redis::client::datatype_set);
ASSERT_EQUAL(c.sismember("set1", "sval1"), true);
}
test("srem");
{
c.srem("set1", "sval1");
ASSERT_EQUAL(c.exists("set1"), true);
ASSERT_EQUAL(c.type("set1"), redis::client::datatype_set);
ASSERT_EQUAL(c.sismember("set1", "sval1"), false);
}
test("smove");
{
c.sadd("set1", "hi");
// set1 = { hi }
ASSERT_EQUAL(c.exists("set2"), false);
c.smove("set1", "set2", "hi");
ASSERT_EQUAL(c.sismember("set1", "hi"), false);
ASSERT_EQUAL(c.sismember("set2", "hi"), true);
}
test("scard");
{
ASSERT_EQUAL(c.scard("set1"), 0L);
ASSERT_EQUAL(c.scard("set2"), 1L);
}
test("sismember");
{
// see above
}
test("smembers");
{
c.sadd("set2", "bye");
redis::client::string_set members;
ASSERT_EQUAL(c.smembers("set2", members), 2L);
ASSERT_EQUAL(members.size(), 2UL);
ASSERT_NOT_EQUAL(members.find("hi"), members.end());
ASSERT_NOT_EQUAL(members.find("bye"), members.end());
}
test("sinter");
{
c.sadd("set3", "bye");
c.sadd("set3", "bye2");
redis::client::string_vector keys;
keys.push_back("set2");
keys.push_back("set3");
redis::client::string_set intersection;
ASSERT_EQUAL(c.sinter(keys, intersection), 1L);
ASSERT_EQUAL(intersection.size(), 1UL);
ASSERT_NOT_EQUAL(intersection.find("bye"), intersection.end());
}
test("sinterstore");
{
c.sadd("seta", "1");
c.sadd("seta", "2");
c.sadd("seta", "3");
c.sadd("setb", "2");
c.sadd("setb", "3");
c.sadd("setb", "4");
redis::client::string_vector keys;
keys.push_back("seta");
keys.push_back("setb");
ASSERT_EQUAL(c.sinterstore("setc", keys), 2L);
redis::client::string_set members;
ASSERT_EQUAL(c.smembers("setc", members), 2L);
ASSERT_EQUAL(members.size(), 2UL);
ASSERT_NOT_EQUAL(members.find("2"), members.end());
ASSERT_NOT_EQUAL(members.find("3"), members.end());
}
test("sunion");
{
c.sadd("setd", "1");
c.sadd("sete", "2");
redis::client::string_vector keys;
keys.push_back("setd");
keys.push_back("sete");
redis::client::string_set a_union;
ASSERT_EQUAL(c.sunion(keys, a_union), 2L);
ASSERT_EQUAL(a_union.size(), 2UL);
ASSERT_NOT_EQUAL(a_union.find("1"), a_union.end());
ASSERT_NOT_EQUAL(a_union.find("2"), a_union.end());
}
test("sunionstore");
{
c.sadd("setf", "1");
c.sadd("setg", "2");
redis::client::string_vector keys;
keys.push_back("setf");
keys.push_back("setg");
ASSERT_EQUAL(c.sunionstore("seth", keys), 2L);
redis::client::string_set members;
ASSERT_EQUAL(c.smembers("seth", members), 2L);
ASSERT_EQUAL(members.size(), 2UL);
ASSERT_NOT_EQUAL(members.find("1"), members.end());
ASSERT_NOT_EQUAL(members.find("2"), members.end());
}
test("move");
{
c.select(14);
ASSERT_EQUAL(c.exists("ttt"), false);
c.select(15);
c.set("ttt", "uuu");
c.move("ttt", 14);
c.select(14);
ASSERT_EQUAL(c.exists("ttt"), true);
c.select(15);
ASSERT_EQUAL(c.exists("ttt"), false);
}
test("move should fail since key exists already");
{
c.select(14);
c.set("ttt", "xxx");
c.select(15);
c.set("ttt", "uuu");
bool threw = false;
try
{
c.move("ttt", 14);
}
catch (redis::protocol_error & e)
{
threw = true;
}
ASSERT_EQUAL(threw, true);
c.select(14);
ASSERT_EQUAL(c.exists("ttt"), true);
c.select(15);
ASSERT_EQUAL(c.exists("ttt"), true);
}
test("sort ascending");
{
c.sadd("sort1", "3");
c.sadd("sort1", "2");
c.sadd("sort1", "1");
redis::client::string_vector sorted;
ASSERT_EQUAL(c.sort("sort1", sorted), 3L);
ASSERT_EQUAL(sorted.size(), 3UL);
ASSERT_EQUAL(sorted[0], string("1"));
ASSERT_EQUAL(sorted[1], string("2"));
ASSERT_EQUAL(sorted[2], string("3"));
}
test("sort descending");
{
redis::client::string_vector sorted;
ASSERT_EQUAL(c.sort("sort1", sorted, redis::client::sort_order_descending), 3L);
ASSERT_EQUAL(sorted.size(), 3UL);
ASSERT_EQUAL(sorted[0], string("3"));
ASSERT_EQUAL(sorted[1], string("2"));
ASSERT_EQUAL(sorted[2], string("1"));
}
test("sort with limit");
{
// TODO
}
test("sort lexicographically");
{
// TODO
}
test("sort with pattern and weights");
{
// TODO
}
test("save");
{
c.save();
}
test("bgsave");
{
c.bgsave();
}
test("lastsave");
{
ASSERT_GT(c.lastsave(), 0L);
}
test("shutdown");
{
// You can test this if you really want to ...
// c.shutdown();
}
}
catch (redis::redis_error & e)
{
cerr << "got exception: " << string(e) << endl << "FAIL" << endl;
return 1;
}
cout << endl << "testing completed successfully" << endl;
return 0;
}
repo: 9e1f35ed7fdc7b3da7f5ff66a71d1975b85e2ae5
node: 85e28ca5597e22ff1dde18ed4625f41923128993
syntax: glob
*.beam
\ No newline at end of file
Copyright (c) 2009
adroll.com
Valentino Volonghi
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LIBDIR=`erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell`
all:
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
clean: clean_tests
(cd src;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
clean_tests:
(cd test;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
test: clean
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
(cd test;$(MAKE) test)
testrun: all
mkdir -p ebin/
(cd test;$(MAKE) test)
install: all
mkdir -p ${LIBDIR}/erldis-0.0.1/{ebin,include}
for i in ebin/*.beam; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
for i in include/*.hrl; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
-record(redis, {socket,buffer=[],reply_caller,calls=0,remaining=0,pstate=empty,results=[]}).
include ../support/include.mk
all: $(EBIN_FILES)
debug:
$(MAKE) DEBUG=-DDEBUG
clean:
rm -rf $(EBIN_FILES) erl_crash.dump
-module(client).
-behavior(gen_server).
-export([start/1, start/2, connect/1, connect/2, asend/2, send/3, send/2,
disconnect/1, ssend/3, str/1, format/1, sformat/1, ssend/2,
get_all_results/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-include("erldis.hrl").
-define(EOL, "\r\n").
%% Helpers
str(X) when is_list(X) ->
X;
str(X) when is_atom(X) ->
atom_to_list(X);
str(X) when is_binary(X) ->
binary_to_list(X);
str(X) when is_integer(X) ->
integer_to_list(X);
str(X) when is_float(X) ->
float_to_list(X).
format([], Result) ->
string:join(lists:reverse(Result), ?EOL);
format([Line|Rest], Result) ->
JoinedLine = string:join([str(X) || X <- Line], " "),
format(Rest, [JoinedLine|Result]).
format(Lines) ->
format(Lines, []).
sformat(Line) ->
format([Line], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Exported API
start(Host) ->
connect(Host).
start(Host, Port) ->
connect(Host, Port).
connect(Host) ->
connect(Host, 6379).
connect(Host, Port) ->
gen_server:start_link(?MODULE, [Host, Port], []).
% This is the simple send with a single row of commands
ssend(Client, Cmd) -> ssend(Client, Cmd, []).
ssend(Client, Cmd, Args) ->
gen_server:cast(Client, {send, sformat([Cmd|Args])}).
% This is the complete send with multiple rows
send(Client, Cmd) -> send(Client, Cmd, []).
send(Client, Cmd, Args) ->
gen_server:cast(Client, {send,
string:join([str(Cmd), format(Args)], " ")}).
% asynchronous send, we don't care about the result.
asend(Client, Cmd) ->
gen_server:cast(Client, {asend, Cmd}).
disconnect(Client) ->
gen_server:call(Client, disconnect).
get_all_results(Client) ->
gen_server:call(Client, get_all_results).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% gen_server callbacks
init([Host, Port]) ->
process_flag(trap_exit, true),
ConnectOptions = [list, {active, once}, {packet, line}, {nodelay, true}],
case gen_tcp:connect(Host, Port, ConnectOptions) of
{error, Why} ->
{error, {socket_error, Why}};
{ok, Socket} ->
{ok, #redis{socket=Socket, calls=0}}
end.
handle_call({send, Cmd}, From, State) ->
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
{noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, lists:nth(1, V)) end,
remaining=1}};
handle_call(disconnect, _From, State) ->
{stop, normal, ok, State};
handle_call(get_all_results, From, State) ->
case State#redis.calls of
0 ->
% answers came earlier than we could start listening...
% Very unlikely but totally possible.
{reply, lists:reverse(State#redis.results), State#redis{results=[], calls=0}};
_ ->
% We are here earlier than results came, so just make
% ourselves wait until stuff is ready.
{noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, V) end}}
end;
handle_call(_, _From, State) -> {noreply, State}.
handle_cast({asend, Cmd}, State) ->
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
{noreply, State};
handle_cast({send, Cmd}, State=#redis{remaining=Remaining, calls=Calls}) ->
% how we should do here: if remaining is already != 0 then we'll
% let handle_info take care of keeping track how many remaining things
% there are. If instead it's 0 we are the first call so let's just
% do it.
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
case Remaining of
0 ->
{noreply, State#redis{remaining=1, calls=1}};
_ ->
{noreply, State#redis{calls=Calls+1}}
end;
handle_cast(_Msg, State) -> {noreply, State}.
trim2({ok, S}) ->
string:substr(S, 1, length(S)-2);
trim2(S) ->
trim2({ok, S}).
% This function helps with pipelining by creating a pubsub system with
% the caller. The caller could submit multiple requests and not listen
% until later when all or some of them have been answered, at that
% point 2 conditions can be true:
% 1) We still need to process more things in this response chain
% 2) We are finished.
%
% And these 2 are together with the following 2:
% 1) We called get_all_results before the end of the responses.
% 2) We called get_all_results after the end of the responses.
%
% If there's stuff missing in the chain we just push results, this also
% happens when there's nothing more to process BUT we haven't requested
% results yet.
% In case we have requested results: if requests are not yet ready we
% just push them, otherwise we finally answer all of them.
save_or_reply(Result, State=#redis{calls=Calls, results=Results, reply_caller=ReplyCaller}) ->
case Calls of
0 ->
% We don't reverse results here because if all the requests
% come in and then we submit another one, if we reverse
% they will be scrambled in the results field of the record.
% instead if we wait just before we reply they will be
% in the right order.
FullResults = [Result|Results],
NewState = case ReplyCaller of
undefined ->
State#redis{results=FullResults};
_ ->
ReplyCaller(lists:reverse(FullResults)),
State#redis{results=[]}
end,
NewState#redis{remaining=0, pstate=empty,
reply_caller=undefined, buffer=[],
calls=0};
_ ->
State#redis{results=[Result|Results], remaining=1, pstate=empty, buffer=[], calls=Calls}
end.
handle_info({tcp, Socket, Data}, State=#redis{calls=Calls}) ->
Trimmed = trim2(Data),
NewState = case {State#redis.remaining-1, proto:parse(State#redis.pstate, Trimmed)} of
% This line contained an error code. Next line will hold
% The error message that we will parse.
{0, error} ->
State#redis{remaining=1, pstate=error};
% The stateful parser just started and tells us the number
% of results that we will have to parse for those calls
% where more than one result is expected. The next
% line will start with the first item to read.
{0, {hold, Remaining}} ->
case Remaining of
nil ->
save_or_reply(nil, State#redis{calls=Calls-1});
_ ->
% Reset the remaining value to the number of results that we need to parse.
State#redis{remaining=Remaining, pstate=read}
end;
% We either had only one thing to read or we are at the
% end of the stuff that we need to read. either way
% just pack up the buffer and send.
{0, {read, NBytes}} ->
CurrentValue = case NBytes of
nil ->
nil;
_ ->
inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
CV = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
inet:setopts(Socket, [{packet, line}]), % go back to line mode
CV
end,
OldBuffer = State#redis.buffer,
case OldBuffer of
[] ->
save_or_reply(CurrentValue, State#redis{calls=Calls-1});
_ ->
save_or_reply(lists:reverse([CurrentValue|OldBuffer]), State#redis{calls=Calls-1})
end;
% The stateful parser tells us to read some bytes
{N, {read, NBytes}} ->
% annoying repetition... I should reuse this code.
CurrentValue = case NBytes of
nil ->
nil;
_ ->
inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
CV = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
inet:setopts(Socket, [{packet, line}]), % go back to line mode
CV
end,
OldBuffer = State#redis.buffer,
State#redis{remaining=N, buffer=[CurrentValue|OldBuffer], pstate=read};
% Simple return values contained in a single line
{0, Value} ->
save_or_reply(Value, State#redis{calls=Calls-1})
end,
inet:setopts(Socket, [{active, once}]),
{noreply, NewState};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
case State#redis.socket of
undefined ->
pass;
Socket ->
gen_tcp:close(Socket)
end,
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-module(erldis).
-compile(export_all).
-define(EOL, "\r\n").
%% helpers
flatten({error, Message}) ->
{error, Message};
flatten(List) when is_list(List)->
lists:flatten(List).
%% exposed API
connect(Host) ->
client:connect(Host).
quit(Client) ->
client:asend(Client, "QUIT"),
client:disconnect(Client).
%% Commands operating on string values
internal_set_like(Client, Command, Key, Value) ->
client:send(Client, Command, [[Key, length(Value)],
[Value]]).
get_all_results(Client) -> client:get_all_results(Client).
auth(Client, Password) -> client:ssend(Client, auth, [Password]).
set(Client, Key, Value) -> internal_set_like(Client, set, Key, Value).
get(Client, Key) -> client:ssend(Client, get, [Key]).
getset(Client, Key, Value) -> internal_set_like(Client, getset, Key, Value).
mget(Client, Keys) -> client:ssend(Client, mget, Keys).
setnx(Client, Key, Value) -> internal_set_like(Client, setnx, Key, Value).
incr(Client, Key) -> client:ssend(Client, incr, [Key]).
incrby(Client, Key, By) -> client:ssend(Client, incrby, [Key, By]).
decr(Client, Key) -> client:ssend(Client, decr, [Key]).
decrby(Client, Key, By) -> client:ssend(Client, decrby, [Key, By]).
%% Commands operating on every value
exists(Client, Key) -> client:ssend(Client, exists, [Key]).
del(Client, Key) -> client:ssend(Client, del, [Key]).
type(Client, Key) -> client:ssend(Client, type, [Key]).
keys(Client, Pattern) -> client:ssend(Client, keys, [Pattern]).
randomkey(Client, Key) -> client:ssend(Client, randomkey, [Key]).
rename(Client, OldKey, NewKey) -> client:ssend(Client, rename, [OldKey, NewKey]).
renamenx(Client, OldKey, NewKey) -> client:ssend(Client, renamenx, [OldKey, NewKey]).
dbsize(Client) -> client:ssend(Client, dbsize).
expire(Client, Key, Seconds) -> client:ssend(Client, expire, [Key, Seconds]).
ttl(Client, Key) -> client:ssend(Client, ttl, [Key]).
%% Commands operating on lists
rpush(Client, Key, Value) -> internal_set_like(Client, rpush, Key, Value).
lpush(Client, Key, Value) -> internal_set_like(Client, lpush, Key, Value).
llen(Client, Key) -> client:ssend(Client, llen, [Key]).
lrange(Client, Key, Start, End) -> client:ssend(Client, lrange, [Key, Start, End]).
ltrim(Client, Key, Start, End) -> client:ssend(Client, ltrim, [Key, Start, End]).
lindex(Client, Key, Index) -> client:ssend(Client, lindex, [Key, Index]).
lset(Client, Key, Index, Value) ->
client:send(Client, lset, [[Key, Index, length(Value)],
[Value]]).
lrem(Client, Key, Number, Value) ->
client:send(Client, lrem, [[Key, Number, length(Value)],
[Value]]).
lpop(Client, Key) -> client:ssend(Client, lpop, [Key]).
rpop(Client, Key) -> client:ssend(Client, rpop, [Key]).
%% Commands operating on sets
sadd(Client, Key, Value) -> internal_set_like(Client, sadd, Key, Value).
srem(Client, Key, Value) -> internal_set_like(Client, srem, Key, Value).
smove(Client, SrcKey, DstKey, Member) -> client:send(Client, smove, [[SrcKey, DstKey, length(Member)],
[Member]]).
scard(Client, Key) -> client:ssend(Client, scard, [Key]).
sismember(Client, Key, Value) -> internal_set_like(Client, sismember, Key, Value).
sintersect(Client, Keys) -> client:ssend(Client, sinter, Keys).
sinter(Client, Keys) -> sintersect(Client, Keys).
sinterstore(Client, DstKey, Keys) -> client:ssend(Client, sinterstore, [DstKey|Keys]).
sunion(Client, Keys) -> client:ssend(Client, sunion, Keys).
sunionstore(Client, DstKey, Keys) -> client:ssend(Client, sunionstore, [DstKey|Keys]).
sdiff(Client, Keys) -> client:ssend(Client, sdiff, Keys).
sdiffstore(Client, DstKey, Keys) -> client:ssend(Client, sdiffstore, [DstKey|Keys]).
smembers(Client, Key) -> client:ssend(Client, smembers, [Key]).
%% Multiple DB commands
select(Client, Index) -> client:ssend(Client, select, [Index]).
move(Client, Key, DBIndex) -> client:ssend(Client, move, [Key, DBIndex]).
flushdb(Client) -> client:ssend(Client, flushdb).
flushall(Client) -> client:ssend(Client, flushall).
%% Commands operating on both lists and sets
sort(Client, Key) -> client:ssend(Client, sort, [Key]).
sort(Client, Key, Extra) -> client:ssend(Client, sort, [Key, Extra]).
%% Persistence control commands
save(Client) -> client:ssend(Client, save).
bgsave(Client) -> client:ssend(Client, bgsave).
lastsave(Client) -> client:ssend(Client, lastsave).
shutdown(Client) -> client:asend(Client, shutdown).
%% Remote server control commands
info(Client) -> client:ssend(Client, info).
slaveof(Client, Host, Port) -> client:ssend(Client, slaveof, [Host, Port]).
slaveof(Client) -> client:ssend(Client, slaveof, ["no one"]).
-module(proto).
-export([parse/2]).
parse(empty, "+OK") ->
ok;
parse(empty, "+PONG") ->
pong;
parse(empty, ":0") ->
false;
parse(empty, ":1") ->
true;
parse(empty, "-" ++ Message) ->
{error, Message};
parse(empty, "$-1") ->
{read, nil};
parse(empty, "*-1") ->
{hold, nil};
parse(empty, "$" ++ BulkSize) ->
{read, list_to_integer(BulkSize)};
parse(read, "$" ++ BulkSize) ->
{read, list_to_integer(BulkSize)};
parse(empty, "*" ++ MultiBulkSize) ->
{hold, list_to_integer(MultiBulkSize)};
parse(empty, Message) ->
convert(Message).
convert(":" ++ Message) ->
list_to_integer(Message);
% in case the message is not OK or PONG it's a
% real value that we don't know how to convert
% to an atom, so just pass it as is and remove
% the +
convert("+" ++ Message) ->
Message;
convert(Message) ->
Message.
## -*- makefile -*-
ERL := erl
ERLC := $(ERL)c
INCLUDE_DIRS := ../include $(wildcard ../deps/*/include)
EBIN_DIRS := $(wildcard ../deps/*/ebin)
ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %)
ifndef no_debug_info
ERLC_FLAGS += +debug_info
endif
ifdef debug
ERLC_FLAGS += -Ddebug
endif
ifdef test
ERLC_FLAGS += -DTEST
endif
EBIN_DIR := ../ebin
DOC_DIR := ../doc
EMULATOR := beam
ERL_TEMPLATE := $(wildcard *.et)
ERL_SOURCES := $(wildcard *.erl)
ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl)
ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.beam)
ERL_TEMPLATES := $(ERL_TEMPLATE:%.et=$(EBIN_DIR)/%.beam)
ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR))
APP_FILES := $(wildcard *.app)
EBIN_FILES = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) $(ERL_TEMPLATES)
MODULES = $(ERL_SOURCES:%.erl=%)
../ebin/%.app: %.app
cp $< $@
$(EBIN_DIR)/%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $<
$(EBIN_DIR)/%.$(EMULATOR): %.et
$(ERL) -noshell -pa ../../elib/erltl/ebin/ -eval "erltl:compile(atom_to_list('$<'), [{outdir, \"../ebin\"}, report_errors, report_warnings, nowarn_unused_vars])." -s init stop
./%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o . $<
$(DOC_DIR)/%.html: %.erl
$(ERL) -noshell -run edoc file $< -run init stop
mv *.html $(DOC_DIR)
include ../support/include.mk
all: $(EBIN_FILES)
clean:
rm -rf $(EBIN_FILES) erl_crash.dump
test: $(MODULES)
./$(MODULES):
@echo "Running tests for $@"
erl -pa ../ebin -run $@ test -run init stop -noshell
-module(erldis_tests).
-include_lib("eunit/include/eunit.hrl").
-include("erldis.hrl").
quit_test() ->
{ok, Client} = erldis:connect("localhost"),
ok = erldis:quit(Client),
false = is_process_alive(Client).
utils_test() ->
?assertEqual(client:str(1), "1"),
?assertEqual(client:str(atom), "atom"),
?assertEqual(client:format([[1, 2, 3]]), "1 2 3"),
?assertEqual(client:format([[1,2,3], [4,5,6]]), "1 2 3\r\n4 5 6").
basic_test() ->
{ok, Client} = erldis:connect("localhost"),
erldis:flushall(Client),
erldis:get(Client, "pippo"),
erldis:set(Client, "hello", "kitty!"),
erldis:setnx(Client, "foo", "bar"),
erldis:setnx(Client, "foo", "bar"),
[ok, nil, ok, true, false] = erldis:get_all_results(Client),
erldis:exists(Client, "hello"),
erldis:exists(Client, "foo"),
erldis:get(Client, "foo"),
erldis:mget(Client, ["hello", "foo"]),
erldis:del(Client, "hello"),
erldis:del(Client, "foo"),
erldis:exists(Client, "hello"),
erldis:exists(Client, "foo"),
[true, true, "bar", ["kitty!", "bar"], true, true, false, false] = erldis:get_all_results(Client),
erldis:set(Client, "pippo", "pluto"),
erldis:sadd(Client, "pippo", "paperino"),
% foo doesn't exist, the result will be nil
erldis:lrange(Client, "foo", 1, 2),
erldis:lrange(Client, "pippo", 1, 2),
[ok,
{error, "ERR Operation against a key holding the wrong kind of value"},
nil,
{error, "ERR Operation against a key holding the wrong kind of value"}
] = erldis:get_all_results(Client),
erldis:del(Client, "pippo"),
[true] = erldis:get_all_results(Client),
erldis:rpush(Client, "a_list", "1"),
erldis:rpush(Client, "a_list", "2"),
erldis:rpush(Client, "a_list", "3"),
erldis:rpush(Client, "a_list", "1"),
erldis:lrem(Client, "a_list", 1, "1"),
erldis:lrange(Client, "a_list", 0, 2),
[ok, ok, ok, ok, true, ["2", "3", "1"]] = erldis:get_all_results(Client),
erldis:sort(Client, "a_list"),
erldis:sort(Client, "a_list", "DESC"),
erldis:lrange(Client, "a_list", 0, 2),
erldis:sort(Client, "a_list", "LIMIT 0 2 ASC"),
[["1", "2", "3"], ["3", "2", "1"], ["2", "3", "1"],
["1", "2"]] = erldis:get_all_results(Client),
ok = erldis:quit(Client).
% inline_tests(Client) ->
% [?_assertMatch(ok, erldis:set(Client, "hello", "kitty!")),
% ?_assertMatch(false, erldis:setnx(Client, "hello", "kitty!")),
% ?_assertMatch(true, erldis:exists(Client, "hello")),
% ?_assertMatch(true, erldis:del(Client, "hello")),
% ?_assertMatch(false, erldis:exists(Client, "hello")),
%
% ?_assertMatch(true, erldis:setnx(Client, "hello", "kitty!")),
% ?_assertMatch(true, erldis:exists(Client, "hello")),
% ?_assertMatch("kitty!", erldis:get(Client, "hello")),
% ?_assertMatch(true, erldis:del(Client, "hello")),
%
%
% ?_assertMatch(1, erldis:incr(Client, "pippo"))
% ,?_assertMatch(2, erldis:incr(Client, "pippo"))
% ,?_assertMatch(1, erldis:decr(Client, "pippo"))
% ,?_assertMatch(0, erldis:decr(Client, "pippo"))
% ,?_assertMatch(-1, erldis:decr(Client, "pippo"))
%
% ,?_assertMatch(6, erldis:incrby(Client, "pippo", 7))
% ,?_assertMatch(2, erldis:decrby(Client, "pippo", 4))
% ,?_assertMatch(-2, erldis:decrby(Client, "pippo", 4))
% ,?_assertMatch(true, erldis:del(Client, "pippo"))
% ].
-module(proto_tests).
-include_lib("eunit/include/eunit.hrl").
parse_test() ->
ok = proto:parse(empty, "+OK"),
pong = proto:parse(empty, "+PONG"),
false = proto:parse(empty, ":0"),
true = proto:parse(empty, ":1"),
{error, "1"} = proto:parse(empty, "-1").
Copyright (c) 2009
Daniele Alessandri
http://www.clorophilla.net/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
redis-lua
-------------------------------------------------------------------------------
A Lua client library for the redis key value storage system.
\ No newline at end of file
local _G = _G
local require, error, type, print = require, error, type, print
local table, pairs, tostring, tonumber = table, pairs, tostring, tonumber
module('Redis')
local socket = require('socket') -- requires LuaSocket as a dependency
local redis_commands = {}
local network, request, response, utils = {}, {}, {}, {}, {}
local protocol = { newline = '\r\n', ok = 'OK', err = 'ERR', null = 'nil' }
local function toboolean(value) return value == 1 end
local function load_methods(proto, methods)
local redis = _G.setmetatable ({}, _G.getmetatable(proto))
for i, v in pairs(proto) do redis[i] = v end
for i, v in pairs(methods) do redis[i] = v end
return redis
end
-- ############################################################################
function network.write(client, buffer)
local _, err = client.socket:send(buffer)
if err then error(err) end
end
function network.read(client, len)
if len == nil then len = '*l' end
local line, err = client.socket:receive(len)
if not err then return line else error('Connection error: ' .. err) end
end
-- ############################################################################
function response.read(client)
local res = network.read(client)
local prefix = res:sub(1, -#res)
local response_handler = protocol.prefixes[prefix]
if not response_handler then
error("Unknown response prefix: " .. prefix)
else
return response_handler(client, res)
end
end
function response.status(client, data)
local sub = data:sub(2)
if sub == protocol.ok then return true else return sub end
end
function response.error(client, data)
local err_line = data:sub(2)
if err_line:sub(1, 3) == protocol.err then
error("Redis error: " .. err_line:sub(5))
else
error("Redis error: " .. err_line)
end
end
function response.bulk(client, data)
local str = data:sub(2)
local len = tonumber(str)
if not len then
error('Cannot parse ' .. str .. ' as data length.')
else
if len == -1 then return nil end
local next_chunk = network.read(client, len + 2)
return next_chunk:sub(1, -3);
end
end
function response.multibulk(client, data)
local str = data:sub(2)
-- TODO: add a check if the returned value is indeed a number
local list_count = tonumber(str)
if list_count == -1 then
return nil
else
local list = {}
if list_count > 0 then
for i = 1, list_count do
table.insert(list, i, response.bulk(client, network.read(client)))
end
end
return list
end
end
function response.integer(client, data)
local res = data:sub(2)
local number = tonumber(res)
if not number then
if res == protocol.null then
return nil
else
error('Cannot parse ' .. res .. ' as numeric response.')
end
end
return number
end
protocol.prefixes = {
['+'] = response.status,
['-'] = response.error,
['$'] = response.bulk,
['*'] = response.multibulk,
[':'] = response.integer,
}
-- ############################################################################
function request.raw(client, buffer)
-- TODO: optimize
local bufferType = type(buffer)
if bufferType == 'string' then
network.write(client, buffer)
elseif bufferType == 'table' then
network.write(client, table.concat(buffer))
else
error('Argument error: ' .. bufferType)
end
return response.read(client)
end
function request.inline(client, command, ...)
if arg.n == 0 then
network.write(client, command .. protocol.newline)
else
local arguments = arg
arguments.n = nil
if #arguments > 0 then
arguments = table.concat(arguments, ' ')
else
arguments = ''
end
network.write(client, command .. ' ' .. arguments .. protocol.newline)
end
return response.read(client)
end
function request.bulk(client, command, ...)
local arguments = arg
local data = tostring(table.remove(arguments))
arguments.n = nil
-- TODO: optimize
if #arguments > 0 then
arguments = table.concat(arguments, ' ')
else
arguments = ''
end
return request.raw(client, {
command, ' ', arguments, ' ', #data, protocol.newline, data, protocol.newline
})
end
-- ############################################################################
local function custom(command, send, parse)
return function(self, ...)
local reply = send(self, command, ...)
if parse then
return parse(reply, command, ...)
else
return reply
end
end
end
local function bulk(command, reader)
return custom(command, request.bulk, reader)
end
local function inline(command, reader)
return custom(command, request.inline, reader)
end
-- ############################################################################
function connect(host, port)
local client_socket = socket.connect(host, port)
if not client_socket then
error('Could not connect to ' .. host .. ':' .. port)
end
local redis_client = {
socket = client_socket,
raw_cmd = function(self, buffer)
return request.raw(self, buffer .. protocol.newline)
end,
}
return load_methods(redis_client, redis_commands)
end
-- ############################################################################
redis_commands = {
-- miscellaneous commands
ping = inline('PING',
function(response)
if response == 'PONG' then return true else return false end
end
),
echo = bulk('ECHO'),
-- TODO: the server returns an empty -ERR on authentication failure
auth = inline('AUTH'),
-- connection handling
quit = custom('QUIT',
function(client, command)
-- let's fire and forget! the connection is closed as soon
-- as the QUIT command is received by the server.
network.write(client, command .. protocol.newline)
end
),
-- commands operating on string values
set = bulk('SET'),
set_preserve = bulk('SETNX', toboolean),
get = inline('GET'),
get_multiple = inline('MGET'),
get_set = bulk('GETSET'),
increment = inline('INCR'),
increment_by = inline('INCRBY'),
decrement = inline('DECR'),
decrement_by = inline('DECRBY'),
exists = inline('EXISTS', toboolean),
delete = inline('DEL', toboolean),
type = inline('TYPE'),
-- commands operating on the key space
keys = inline('KEYS',
function(response)
local keys = {}
response:gsub('%w+', function(key)
table.insert(keys, key)
end)
return keys
end
),
random_key = inline('RANDOMKEY'),
rename = inline('RENAME'),
rename_preserve = inline('RENAMENX'),
expire = inline('EXPIRE', toboolean),
database_size = inline('DBSIZE'),
time_to_live = inline('TTL'),
-- commands operating on lists
push_tail = bulk('RPUSH'),
push_head = bulk('LPUSH'),
list_length = inline('LLEN'),
list_range = inline('LRANGE'),
list_trim = inline('LTRIM'),
list_index = inline('LINDEX'),
list_set = bulk('LSET'),
list_remove = bulk('LREM'),
pop_first = inline('LPOP'),
pop_last = inline('RPOP'),
-- commands operating on sets
set_add = bulk('SADD'),
set_remove = bulk('SREM'),
set_move = bulk('SMOVE'),
set_cardinality = inline('SCARD'),
set_is_member = inline('SISMEMBER'),
set_intersection = inline('SINTER'),
set_intersection_store = inline('SINTERSTORE'),
set_union = inline('SUNION'),
set_union_store = inline('SUNIONSTORE'),
set_diff = inline('SDIFF'),
set_diff_store = inline('SDIFFSTORE'),
set_members = inline('SMEMBERS'),
-- multiple databases handling commands
select_database = inline('SELECT'),
move_key = inline('MOVE'),
flush_database = inline('FLUSHDB'),
flush_databases = inline('FLUSHALL'),
-- sorting
--[[
TODO: should we pass sort parameters as a table? e.g:
params = {
by = 'weight_*',
get = 'object_*',
limit = { 0, 10 },
sort = { 'desc', 'alpha' }
}
--]]
sort = custom('SORT',
function(client, command, params)
-- TODO: here we will put the logic needed to serialize the params
-- table to be sent as the argument of the SORT command.
return request.inline(client, command, params)
end
),
-- persistence control commands
save = inline('SAVE'),
background_save = inline('BGSAVE'),
last_save = inline('LASTSAVE'),
shutdown = custom('SHUTDOWN',
function(client, command)
-- let's fire and forget! the connection is closed as soon
-- as the SHUTDOWN command is received by the server.
network.write(client, command .. protocol.newline)
end
),
-- remote server control commands
info = inline('INFO',
function(response)
local info = {}
response:gsub('([^\r\n]*)\r\n', function(kv)
local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
info[k] = v
end)
return info
end
),
slave_of = inline('SLAVEOF'),
slave_of_no_one = custom('SLAVEOF',
function(client, command)
return request.inline(client, command, 'NO ONE')
end
),
}
Revision history for Redis
0.01 Sun Mar 22 19:02:17 CET 2009
First version, tracking git://github.com/antirez/redis
0.08 Tue Mar 24 22:38:59 CET 2009
This version supports new protocol introduced in beta 8
Version bump to be in-sync with Redis version
Changes
MANIFEST
Makefile.PL
README
lib/Redis.pm
t/00-load.t
t/pod-coverage.t
t/pod.t
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Redis',
AUTHOR => 'Dobrica Pavlinusic <dpavlin@rot13.org>',
VERSION_FROM => 'lib/Redis.pm',
ABSTRACT_FROM => 'lib/Redis.pm',
PL_FILES => {},
PREREQ_PM => {
'Test::More' => 0,
'IO::Socket::INET' => 0,
'Data::Dump' => 0,
'Carp' => 0,
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Redis-*' },
);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment