Commit 57172ffb authored by antirez's avatar antirez
Browse files

CPP client added thanks to Brian Hammond

parent 93ea3759
BEFORE REDIS 1.0.0-rc1
- Contrib dir with RHL for Centos and other contributions like init scripts
- Update the FAQ with max number of keys in a DB and the overcommit thing
- Add number of keys for every DB in INFO
- maxmemory support in config file.
......
......@@ -62,6 +62,7 @@ static struct config {
int donerequests;
int keysize;
int datasize;
int randomkeys;
aeEventLoop *el;
char *hostip;
int hostport;
......@@ -341,6 +342,8 @@ void parseOptions(int argc, char **argv) {
i++;
if (config.datasize < 1) config.datasize=1;
if (config.datasize > 1024*1024) config.datasize = 1024*1024;
} else if (!strcmp(argv[i],"-r")) {
config.randomkeys = 1;
} else if (!strcmp(argv[i],"-q")) {
config.quiet = 1;
} else if (!strcmp(argv[i],"-l")) {
......@@ -354,6 +357,7 @@ void parseOptions(int argc, char **argv) {
printf(" -n <requests> Total number of requests (default 10000)\n");
printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
printf(" -r Use random keys for SET/GET/INCR\n");
printf(" -q Quiet. Just show query/sec values\n");
printf(" -l Loop. Run the tests forever\n");
exit(1);
......@@ -374,6 +378,7 @@ int main(int argc, char **argv) {
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
config.randomkeys = 0;
config.quiet = 0;
config.loop = 0;
config.latency = NULL;
......@@ -402,7 +407,7 @@ int main(int argc, char **argv) {
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo %d\r\n",config.datasize);
c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
{
char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize);
......@@ -418,7 +423,7 @@ int main(int argc, char **argv) {
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo\r\n");
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
c->replytype = REPLY_BULK;
c->readlen = -1;
createMissingClients(c);
......@@ -428,7 +433,7 @@ int main(int argc, char **argv) {
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter\r\n");
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
c->replytype = REPLY_INT;
createMissingClients(c);
aeMain(config.el);
......
# Redis C++ Client Library Makefile
#CFLAGS?= -pedantic -O2 -Wall -W -DNDEBUG
CFLAGS?= -pedantic -O0 -W -DDEBUG -g
CC = g++
CLIENTOBJS = anet.o redisclient.o
LIBNAME = libredisclient.a
TESTAPP = test_client
TESTAPPOBJS = test_client.o
TESTAPPLIBS = $(LIBNAME) -lstdc++
all: $(LIBNAME) $(TESTAPP)
$(LIBNAME): $(CLIENTOBJS)
ar rcs $(LIBNAME) $(CLIENTOBJS)
.c.o:
$(CC) -c $(CFLAGS) $<
.cpp.o:
$(CC) -c $(CFLAGS) $<
$(TESTAPP): $(LIBNAME) $(TESTAPPOBJS)
$(CC) -o $(TESTAPP) $(TESTAPPOBJS) $(TESTAPPLIBS)
test: $(TESTAPP)
@./test_client
check: test
clean:
rm -rf $(LIBNAME) *.o $(TESTAPP)
dep:
$(CC) -MM *.c *.cpp
log:
git log '--pretty=format:%ad %s' --date=short > Changelog
anet.o: anet.c fmacros.h anet.h
redisclient.o: redisclient.cpp redisclient.h anet.h
redis-cpp-client
================
* A C++ client for the Redis_ key-value database (which is hosted at github_).
* This client has no external dependencies other than g++ (no Boost for instance).
* It uses anet from antirez_ (redis' author), which is bundled.
* This client is licensed under the same license as redis.
* Tested on Linux and Mac OS X.
* This is a work in progress. I will update this README when the client is "done".
If I had to put a version number on it right now, I'd call it version 0.85
.. _Redis: http://code.google.com/p/redis/
.. _github: http://github.com/antirez/redis/tree/master
.. _antirez: https://github.com/antirez
+ finish command implementations
= finish unit tests
Only a few left, to test the SORT command's edge cases (e.g. BY pattern)
+ determine if we should not use bool return values and instead throw redis_error. (latter).
+ maybe more fine-grained exceptions (not just redis_error but operation_not_permitted_error, etc.)
- benchmarking
- consistent hashing?
- make all string literals constants so they can be easily changed (minor)
- add conveniences that store a std::set in its entirety (same for std::list, std::vector)
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include "anet.h"
static void anetSetError(char *err, const char *fmt, ...)
{
va_list ap;
if (!err) return;
va_start(ap, fmt);
vsnprintf(err, ANET_ERR_LEN, fmt, ap);
va_end(ap);
}
int anetNonBlock(char *err, int fd)
{
int flags;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
return ANET_ERR;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpNoDelay(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
{
anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetResolve(char *err, char *host, char *ipbuf)
{
struct sockaddr_in sa;
sa.sin_family = AF_INET;
if (inet_aton(host, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(host);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", host);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
return ANET_OK;
}
#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s\n", strerror(errno));
return ANET_ERR;
}
/* Make sure connection-intensive things like the redis benckmark
* will be able to close/open sockets a zillion of times */
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if (inet_aton(addr, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(addr);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", addr);
close(s);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
if (flags & ANET_CONNECT_NONBLOCK) {
if (anetNonBlock(err,s) != ANET_OK)
return ANET_ERR;
}
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS &&
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetTcpConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
}
int anetTcpNonBlockConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
}
/* Like read(2) but make sure 'count' is read before to return
* (unless error or EOF condition is encountered) */
int anetRead(int fd, char *buf, int count)
{
int nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
if (nread == -1) return -1;
totlen += nread;
buf += nread;
}
return totlen;
}
/* Like write(2) but make sure 'count' is read before to return
* (unless error is encountered) */
int anetWrite(int fd, char *buf, int count)
{
int nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
if (nwritten == -1) return -1;
totlen += nwritten;
buf += nwritten;
}
return totlen;
}
int anetTcpServer(char *err, int port, char *bindaddr)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "socket: %s\n", strerror(errno));
return ANET_ERR;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr) {
if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "Invalid bind address\n");
close(s);
return ANET_ERR;
}
}
if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
anetSetError(err, "bind: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, 32) == -1) {
anetSetError(err, "listen: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetAccept(char *err, int serversock, char *ip, int *port)
{
int fd;
struct sockaddr_in sa;
unsigned int saLen;
while(1) {
saLen = sizeof(sa);
fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
if (fd == -1) {
if (errno == EINTR)
continue;
else {
anetSetError(err, "accept: %s\n", strerror(errno));
return ANET_ERR;
}
}
break;
}
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
return fd;
}
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANET_H
#define ANET_H
#define ANET_OK 0
#define ANET_ERR -1
#define ANET_ERR_LEN 256
int anetTcpConnect(char *err, char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf);
int anetTcpServer(char *err, int port, char *bindaddr);
int anetAccept(char *err, int serversock, char *ip, int *port);
int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd);
int anetTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
#endif
#ifndef _REDIS_FMACRO_H
#define _REDIS_FMACRO_H
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#endif
This diff is collapsed.
/* redisclient.h -- a C++ client library for redis.
*
* Copyright (c) 2009, Brian Hammond <brian at fictorial dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef REDISCLIENT_H
#define REDISCLIENT_H
#include <string>
#include <vector>
#include <set>
#include <stdexcept>
#include <ctime>
namespace redis
{
struct server_info
{
std::string version;
bool bgsave_in_progress;
unsigned long connected_clients;
unsigned long connected_slaves;
unsigned long used_memory;
unsigned long changes_since_last_save;
unsigned long last_save_time;
unsigned long total_connections_received;
unsigned long total_commands_processed;
unsigned long uptime_in_seconds;
unsigned long uptime_in_days;
};
// Generic error that is thrown when communicating with the redis server.
class redis_error
{
public:
redis_error(const std::string & err);
operator std::string ();
operator const std::string () const;
private:
std::string err_;
};
// Some socket-level I/O or general connection error.
class connection_error : public redis_error
{
public:
connection_error(const std::string & err);
};
// Redis gave us a reply we were not expecting.
// Possibly an internal error (here or in redis, probably here).
class protocol_error : public redis_error
{
public:
protocol_error(const std::string & err);
};
// A key that you expected to exist does not in fact exist.
class key_error : public redis_error
{
public:
key_error(const std::string & err);
};
// A value of an expected type or other semantics was found to be invalid.
class value_error : public redis_error
{
public:
value_error(const std::string & err);
};
// You should construct a 'client' object per connection to a redis-server.
//
// Please read the online redis command reference:
// http://code.google.com/p/redis/wiki/CommandReference
//
// No provisions for customizing the allocator on the string/bulk value type
// (std::string) are provided. If needed, you can always change the
// string_type typedef in your local version.
class client
{
public:
typedef std::string string_type;
typedef std::vector<string_type> string_vector;
typedef std::set<string_type> string_set;
typedef long int_type;
explicit client(const string_type & host = "localhost",
unsigned int port = 6379);
~client();
//
// Connection handling
//
void auth(const string_type & pass);
//
// Commands operating on string values
//
// Note that empty string values do not denote nonexistent keys but well,
// empty values! If a nonexistent key is queried, the value returned will
// be missing_value, including when string_vector objects are returned.
//
static string_type missing_value;
// set a key to a string value
void set(const string_type & key, const string_type & value);
// return the string value of the key
string_type get(const string_type & key);
// set a key to a string returning the old value of the key
string_type getset(const string_type & key, const string_type & value);
// multi-get, return the strings values of the keys
void mget(const string_vector & keys, string_vector & out);
// set a key to a string value if the key does not exist. returns true if
// the key was set, else false. This does not throw since you are ok with
// this failing if the dst key already exists.
bool setnx(const string_type & key, const string_type & value);
// increment the integer value of key
// returns new value
int_type incr(const string_type & key);
// increment the integer value of key by integer
// returns new value
int_type incrby(const string_type & key, int_type by);
// decrement the integer value of key
// returns new value
int_type decr(const string_type & key);
// decrement the integer value of key by integer
// returns new value
int_type decrby(const string_type & key, int_type by);
// test if a key exists
bool exists(const string_type & key);
// delete a key
// throws if doesn't exist
void del(const string_type & key);
enum datatype
{
datatype_none, // key doesn't exist
datatype_string,
datatype_list,
datatype_set
};
// return the type of the value stored at key
datatype type(const string_type & key);
//
// Commands operating on the key space
//
// find all the keys matching a given pattern
// returns numbers of keys appended to 'out'
int_type keys(const string_type & pattern, string_vector & out);
// return a random key from the key space
// returns empty string if db is empty
string_type randomkey();
// rename the old key in the new one, destroying the new key if
// it already exists
void rename(const string_type & old_name, const string_type & new_name);
// rename the old key in the new one, if the new key does not already
// exist. This does not throw since you are ok with this failing if the
// new_name key already exists.
bool renamenx(const string_type & old_name, const string_type & new_name);
// return the number of keys in the current db
int_type dbsize();
// set a time to live in seconds on a key.
// fails if there's already a timeout on the key.
// NB: there's currently no generic way to remove a timeout on a key
void expire(const string_type & key, unsigned int secs);
//
// Commands operating on lists
//
// Append an element to the tail of the list value at key
void rpush(const string_type & key, const string_type & value);
// Append an element to the head of the list value at key
void lpush(const string_type & key, const string_type & value);
// Return the length of the list value at key
// Returns 0 if the list does not exist; see 'exists'
int_type llen(const string_type & key);
// Fetch a range of elements from the list at key
// end can be negative for reverse offsets
// Returns number of elements appended to 'out'
int_type lrange(const string_type & key,
int_type start,
int_type end,
string_vector & out);
// Fetches the entire list at key.
int_type get_list(const string_type & key, string_vector & out)
{
return lrange(key, 0, -1, out);
}
// Trim the list at key to the specified range of elements
void ltrim(const string_type & key, int_type start, int_type end);
// Return the element at index position from the list at key
string_type lindex(const string_type & key, int_type);
// set a new value as the element at index position of the list at key
void lset(const string_type & key,
int_type index,
const string_type &);
// If count is zero all the elements are removed. If count is negative
// elements are removed from tail to head, instead to go from head to tail
// that is the normal behaviour. So for example LREM with count -2 and
// hello as value to remove against the list (a,b,c,hello,x,hello,hello)
// will lave the list (a,b,c,hello,x). Returns the number of removed
// elements if the operation succeeded.
//
// Note: this will not throw if the number of elements removed != count
// since you might want to remove at most count elements by don't care if
// < count elements are removed. See lrem_exact().
int_type lrem(const string_type & key,
int_type count,
const string_type & value);
// An extension of 'lrem' that wants to remove exactly 'count' elements.
// Throws value_error if 'count' elements are not found & removed from the
// list at 'key'.
void lrem_exact(const string_type & key,
int_type count,
const string_type & value)
{
if (lrem(key, count, value) != count)
throw value_error("failed to remove exactly N elements from list");
}
// Return and remove (atomically) the first element of the list at key
string_type lpop(const string_type & key);
// Return and remove (atomically) the last element of the list at key
string_type rpop(const string_type & key);
//
// Commands operating on sets
//
// Add the specified member to the set value at key
// returns true if added, or false if already a member of the set.
void sadd(const string_type & key, const string_type & value);
// Remove the specified member from the set value at key
// returns true if removed or false if value is not a member of the set.
void srem(const string_type & key, const string_type & value);
// Move the specified member from one set to another atomically
// returns true if element was moved, else false (e.g. not found)
void smove(const string_type & srckey,
const string_type & dstkey,
const string_type & value);
// Return the number of elements (the cardinality) of the set at key
int_type scard(const string_type & key);
// Test if the specified value is a member of the set at key
// Returns false if key doesn't exist or value is not a member of the set at key
bool sismember(const string_type & key, const string_type & value);
// Return the intersection between the sets stored at key1, key2, ..., keyN
int_type sinter(const string_vector & keys, string_set & out);
// Compute the intersection between the sets stored at key1, key2, ...,
// keyN, and store the resulting set at dstkey
void sinterstore(const string_type & dstkey, const string_vector & keys);
// Return the union between the sets stored at key1, key2, ..., keyN
int_type sunion(const string_vector & keys, string_set & out);
// Compute the union between the sets stored at key1, key2, ..., keyN,
// and store the resulting set at dstkey
void sunionstore(const string_type & dstkey, const string_vector & keys);
// Return all the members of the set value at key
int_type smembers(const string_type & key, string_set & out);
//
// Multiple databases handling commands
//
// Select the DB having the specified index
void select(int_type dbindex);
// Move the key from the currently selected DB to the DB having as index
// dbindex. Throws if key was already in the db at dbindex or not found in
// currently selected db.
void move(const string_type & key, int_type dbindex);
// Remove all the keys of the currently selected DB
void flushdb();
// Remove all the keys from all the databases
void flushall();
//
// Sorting
// Just go read http://code.google.com/p/redis/wiki/SortCommand
//
enum sort_order
{
sort_order_ascending,
sort_order_descending
};
int_type sort(const string_type & key,
string_vector & out,
sort_order order = sort_order_ascending,
bool lexicographically = false);
int_type sort(const string_type & key,
string_vector & out,
int_type limit_start,
int_type limit_end,
sort_order order = sort_order_ascending,
bool lexicographically = false);
int_type sort(const string_type & key,
string_vector & out,
const string_type & by_pattern,
int_type limit_start,
int_type limit_end,
const string_type & get_pattern,
sort_order order = sort_order_ascending,
bool lexicographically = false);
//
// Persistence control commands
//
// Synchronously save the DB on disk
void save();
// Asynchronously save the DB on disk
void bgsave();
// Return the UNIX time stamp of the last successfully saving of the
// dataset on disk
time_t lastsave();
// Synchronously save the DB on disk, then shutdown the server. This
// object's connection to the server will be lost on success. Otherwise,
// redis_error is raised. Thus, on success, you should delete or otherwise
// no longer use the object.
void shutdown();
//
// Remote server control commands
//
// Provide information and statistics about the server
void info(server_info & out);
private:
client(const client &);
client & operator=(const client &);
void send_(const std::string &);
void recv_ok_reply_();
void recv_int_ok_reply_();
std::string recv_single_line_reply_();
int_type recv_bulk_reply_(char prefix);
std::string recv_bulk_reply_();
int_type recv_multi_bulk_reply_(string_vector & out);
int_type recv_multi_bulk_reply_(string_set & out);
int_type recv_int_reply_();
private:
int socket_;
};
}
#endif
#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");
c.sinterstore("setc", keys);
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");
c.sunionstore("seth", keys);
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;
}
nohup.out
redis/*
rdsrv
pkg/*
\ No newline at end of file
......@@ -7,10 +7,11 @@ require 'tasks/redis.tasks'
GEM = 'redis'
GEM_VERSION = '0.0.3'
AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley']
EMAIL = "ez@engineyard.com"
HOMEPAGE = "http://github.com/ezmobius/redis-rb"
GEM_NAME = 'redis'
GEM_VERSION = '0.0.3.3'
AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark']
EMAIL = "matt.clark@punchstock.com"
HOMEPAGE = "http://github.com/winescout/redis-rb"
SUMMARY = "Ruby client library for redis key value storage server"
spec = Gem::Specification.new do |s|
......
......@@ -4,10 +4,39 @@ require 'redis'
times = 20000
@r = Redis.new
@r = Redis.new#(:debug => true)
@r['foo'] = "The first line we sent to the server is some text"
Benchmark.bmbm do |x|
x.report("set") { 20000.times {|i| @r["foo#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]} }
x.report("set") do
20000.times do |i|
@r["set#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
end
end
x.report("set (pipelined)") do
@r.pipelined do |pipeline|
20000.times do |i|
pipeline["set_pipelined#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
end
end
end
x.report("push+trim") do
20000.times do |i|
@r.push_head "push_trim#{i}", i
@r.list_trim "push_trim#{i}", 0, 30
end
end
x.report("push+trim (pipelined)") do
@r.pipelined do |pipeline|
20000.times do |i|
pipeline.push_head "push_trim_pipelined#{i}", i
pipeline.list_trim "push_trim_pipelined#{i}", 0, 30
end
end
end
end
@r.keys('*').each do |k|
......
require "redis"
class Redis
class Pipeline < Redis
BUFFER_SIZE = 50_000
def initialize(redis)
@redis = redis
@commands = []
end
def get_response
end
def write(data)
@commands << data
write_and_read if @commands.size >= BUFFER_SIZE
end
def finish
write_and_read
end
def write_and_read
@redis.write @commands.join
@redis.read_socket
@commands.clear
end
end
end
\ No newline at end of file
require 'socket'
require 'set'
require File.join(File.dirname(__FILE__),'server')
require File.join(File.dirname(__FILE__),'pipeline')
class RedisError < StandardError
......@@ -22,7 +23,13 @@ class Redis
@opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts)
$debug = @opts[:debug]
@db = @opts[:db]
@server = Server.new(@opts[:host], @opts[:port])
@server = Server.new(@opts[:host], @opts[:port], (@opts[:timeout]||10))
end
def pipelined
pipeline = Pipeline.new(self)
yield pipeline
pipeline.finish
end
def to_s
......@@ -39,9 +46,10 @@ class Redis
def with_socket_management(server, &block)
begin
block.call(server.socket)
socket = server.socket
block.call(socket)
#Timeout or server down
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED, Timeout::Error => e
server.close
puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
retry
......@@ -116,7 +124,7 @@ class Redis
def bulk_reply
begin
x = read.chomp
x = read
puts "bulk_reply read value is #{x.inspect}" if $debug
return x
rescue => e
......@@ -260,16 +268,6 @@ class Redis
get_response == OK
end
def list_length(key)
write "LLEN #{key}\r\n"
case i = get_response
when -2
raise RedisError, "key: #{key} does not hold a list value"
else
i
end
end
def list_range(key, start, ending)
write "LRANGE #{key} #{start} #{ending}\r\n"
get_response
......@@ -358,6 +356,31 @@ class Redis
get_response
end
def set_union(*keys)
write "SUNION #{keys.join(' ')}\r\n"
Set.new(get_response)
end
def set_union_store(destkey, *keys)
write "SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n"
get_response
end
def set_diff(*keys)
write "SDIFF #{keys.join(' ')}\r\n"
Set.new(get_response)
end
def set_diff_store(destkey, *keys)
write "SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n"
get_response
end
def set_move(srckey, destkey, member)
write "SMOVE #{srckey} #{destkey} #{member.to_s.size}\r\n#{member}\r\n"
get_response == 1
end
def sort(key, opts={})
cmd = "SORT #{key}"
cmd << " BY #{opts[:by]}" if opts[:by]
......@@ -466,6 +489,28 @@ class Redis
buff[0..-3]
end
def read_socket
begin
socket = @server.socket
while res = socket.read(8096)
break if res.size != 8096
end
#Timeout or server down
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e
server.close
puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
retry
rescue Timeout::Error => e
#BTM - Ignore this error so we don't go into an endless loop
puts "Client (#{server.inspect}) Timeout\n" if $debug
#Server down
rescue NoMethodError => e
puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
raise Errno::ECONNREFUSED
#exit
end
end
def read_proto
with_socket_management(@server) do |socket|
if res = socket.gets
......
begin
# Timeout code is courtesy of Ruby memcache-client
# http://github.com/mperham/memcache-client/tree
# Try to use the SystemTimer gem instead of Ruby's timeout library
# when running on something that looks like Ruby 1.8.x. See:
# http://ph7spot.com/articles/system_timer
# We don't want to bother trying to load SystemTimer on jruby and
# ruby 1.9+.
if defined?(JRUBY_VERSION) || (RUBY_VERSION >= '1.9')
require 'timeout'
RedisTimer = Timeout
else
require 'system_timer'
RedisTimer = SystemTimer
end
rescue LoadError => e
puts "[redis-rb] Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}"
require 'timeout'
RedisTimer = Timeout
end
##
# This class represents a redis server instance.
......@@ -38,7 +59,7 @@ class Server
# Create a new Redis::Server object for the redis instance
# listening on the given host and port.
def initialize(host, port = DEFAULT_PORT)
def initialize(host, port = DEFAULT_PORT, timeout = 10)
raise ArgumentError, "No host specified" if host.nil? or host.empty?
raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
......@@ -48,7 +69,7 @@ class Server
@sock = nil
@retry = nil
@status = 'NOT CONNECTED'
@timeout = 1
@timeout = timeout
end
##
......@@ -83,23 +104,34 @@ class Server
puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
mark_dead err
end
return @sock
@sock
end
def connect_to(host, port, timeout=nil)
addrs = Socket.getaddrinfo(host, nil)
addr = addrs.detect { |ad| ad[0] == 'AF_INET' }
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
socket = TCPSocket.new(host, port, 0)
if timeout
secs = Integer(timeout)
usecs = Integer((timeout - secs) * 1_000_000)
optval = [secs, usecs].pack("l_2")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
socket.instance_eval <<-EOR
alias :blocking_gets :gets
def gets(*args)
RedisTimer.timeout(#{timeout}) do
self.blocking_gets(*args)
end
end
alias :blocking_read :read
def read(*args)
RedisTimer.timeout(#{timeout}) do
self.blocking_read(*args)
end
end
alias :blocking_write :write
def write(*args)
RedisTimer.timeout(#{timeout}) do
self.blocking_write(*args)
end
end
EOR
end
sock.connect(Socket.pack_sockaddr_in(port, addr[3]))
sock
socket
end
##
......
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{redis-rb}
s.version = "0.0.3"
s.name = %q{redis}
s.version = "0.0.3.4"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley"]
s.autorequire = %q{redis}
s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark"]
#s.autorequire = %q{redis}
s.date = %q{2009-03-31}
s.description = %q{Ruby client library for redis key value storage server}
s.email = %q{ez@engineyard.com}
s.extra_rdoc_files = ["LICENSE"]
s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/redis.rb", "lib/dist_redis.rb", "lib/hash_ring.rb", "lib/server.rb", "lib/better_timeout.rb", "spec/redis_spec.rb", "spec/spec_helper.rb"]
s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/redis.rb", "lib/dist_redis.rb", "lib/hash_ring.rb", "lib/pipeline.rb", "lib/server.rb", "spec/redis_spec.rb", "spec/spec_helper.rb"]
s.has_rdoc = true
s.homepage = %q{http://github.com/winescout/redis-rb}
s.require_paths = ["lib"]
......
......@@ -39,6 +39,19 @@ describe "redis" do
@r['foo'].should == 'nik'
end
it "should properly handle trailing newline characters" do
@r['foo'] = "bar\n"
@r['foo'].should == "bar\n"
end
it "should store and retrieve all possible characters at the beginning and the end of a string" do
(0..255).each do |char_idx|
string = "#{char_idx.chr}---#{char_idx.chr}"
@r['foo'] = string
@r['foo'].should == string
end
end
it "should be able to SET a key with an expiry" do
@r.set('foo', 'bar', 1)
@r['foo'].should == 'bar'
......@@ -271,11 +284,60 @@ describe "redis" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.set_add "set2", 'key2'
@r.set_inter_store('newone', 'set', 'set2')
@r.set_inter_store('newone', 'set', 'set2').should == 'OK'
@r.set_members('newone').should == Set.new(['key2'])
@r.delete('set')
end
#
it "should be able to do set union" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.set_add "set2", 'key2'
@r.set_add "set2", 'key3'
@r.set_union('set', 'set2').should == Set.new(['key1','key2','key3'])
@r.delete('set')
end
#
it "should be able to do set union and store the results in a key" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.set_add "set2", 'key2'
@r.set_add "set2", 'key3'
@r.set_union_store('newone', 'set', 'set2').should == 'OK'
@r.set_members('newone').should == Set.new(['key1','key2','key3'])
@r.delete('set')
end
# these don't seem to be implemented in redis head?
# it "should be able to do set difference" do
# @r.set_add "set", 'key1'
# @r.set_add "set", 'key2'
# @r.set_add "set2", 'key2'
# @r.set_add "set2", 'key3'
# @r.set_diff('set', 'set2').should == Set.new(['key1','key3'])
# @r.delete('set')
# end
# #
# it "should be able to do set difference and store the results in a key" do
# @r.set_add "set", 'key1'
# @r.set_add "set", 'key2'
# @r.set_add "set2", 'key2'
# @r.set_add "set2", 'key3'
# count = @r.set_diff_store('newone', 'set', 'set2')
# count.should == 3
# @r.set_members('newone').should == Set.new(['key1','key3'])
# @r.delete('set')
# end
#
it "should be able move elements from one set to another" do
@r.set_add 'set1', 'a'
@r.set_add 'set1', 'b'
@r.set_add 'set2', 'x'
@r.set_move('set1', 'set2', 'a').should == true
@r.set_member?('set2', 'a').should == true
@r.delete('set1')
end
#
it "should be able to do crazy SORT queries" do
@r['dog_1'] = 'louie'
@r.push_tail 'dogs', 1
......@@ -334,4 +396,15 @@ describe "redis" do
end
end
it "should be able to pipeline writes" do
@r.pipelined do |pipeline|
pipeline.push_head "list", "hello"
pipeline.push_head "list", 42
end
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.pop_head('list').should == '42'
@r.delete('list')
end
end
#!/bin/sh
rm -rf temp
mkdir temp
cd temp
git clone git://github.com/fictorial/redis-cpp-client.git
cd redis-cpp-client
rm -rf .git
cd ..
cd ..
rm -rf cpp
mv temp/redis-cpp-client cpp
rm -rf temp
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