提交 57172ffb 编写于 作者: A antirez

CPP client added thanks to Brian Hammond

上级 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
此差异已折叠。
/* 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,12 +4,41 @@ 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|
@r.delete k
end
\ No newline at end of file
end
\ No newline at end of file
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
......@@ -357,6 +355,31 @@ class Redis
write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
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}"
......@@ -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
end
\ No newline at end of file
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
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ExpireCommand: Contents</b><br>&nbsp;&nbsp;<a href="#Expire _key_ _seconds_">Expire _key_ _seconds_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How the expire is removed from a key">How the expire is removed from a key</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">ExpireCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Expire _key_ _seconds_">Expire _key_ _seconds_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingthe dataset and stopping the server does not stop the flow of time as Redisregisters on disk when the key will no longer be available as Unix time, andnot the remaining seconds.</blockquote>
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, the INCR commandor any other command that modify the value stored at key the timeout isremoved from the key and the key becomes non volatile.</blockquote>
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
<pre class="codeblock python" name="code">
% ./redis-cli lpush mylist foobar /Users/antirez/hack/redis
OK
% ./redis-cli lpush mylist hello /Users/antirez/hack/redis
OK
% ./redis-cli expire mylist 10000 /Users/antirez/hack/redis
1
% ./redis-cli lpush mylist newelement
OK
% ./redis-cli lrange mylist 0 -1 /Users/antirez/hack/redis
1. newelement
</pre><blockquote>What happened here is that lpush against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timeof the commands to. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python python" name="code">
1: the timeout was set.
0: the timeout was not set since the key already has an associated timeout, or the key does not exist.
</pre><h2><a name="See also">See also</a></h2>
<ul><li> []</li></ul>
</div>
</div>
</div>
</body>
</html>
......@@ -29,7 +29,7 @@ def redisSha1(opts={})
sha1
end
host = (ARGV[0] or "127.0.0.1")
port = (ARGV[1] or "6379")
host = ARGV[0] || "127.0.0.1"
port = ARGV[1] || "6379"
puts "Performing SHA1 of Redis server #{host} #{port}"
p "Dataset SHA1: #{redisSha1(:host => host, :port => port.to_i)}"
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册