Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
57172ffb
Commit
57172ffb
authored
May 09, 2009
by
antirez
Browse files
CPP client added thanks to Brian Hammond
parent
93ea3759
Changes
22
Hide whitespace changes
Inline
Side-by-side
TODO
View file @
57172ffb
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.
...
...
benchmark.c
View file @
57172ffb
...
...
@@ -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
);
...
...
client-libraries/cpp/Makefile
0 → 100644
View file @
57172ffb
# 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
client-libraries/cpp/README.rst
0 → 100644
View file @
57172ffb
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
client-libraries/cpp/TODO
0 → 100644
View file @
57172ffb
+ 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)
client-libraries/cpp/anet.c
0 → 100644
View file @
57172ffb
/* 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
;
}
client-libraries/cpp/anet.h
0 → 100644
View file @
57172ffb
/* 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
client-libraries/cpp/fmacros.h
0 → 100644
View file @
57172ffb
#ifndef _REDIS_FMACRO_H
#define _REDIS_FMACRO_H
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#endif
client-libraries/cpp/redisclient.cpp
0 → 100644
View file @
57172ffb
/* redisclient.cpp -- 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.
*/
#include "redisclient.h"
#include "anet.h"
#include <sstream>
#ifndef NDEBUG
#include <algorithm>
#include <iostream>
#include <ctime>
#endif
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <sys/errno.h>
#include <sys/socket.h>
using
namespace
std
;
namespace
{
const
string
whitespace
(
"
\f\n\r\t\v
"
);
const
string
CRLF
(
"
\r\n
"
);
// Modifies in-place.
inline
string
&
rtrim
(
string
&
str
,
const
string
&
ws
=
whitespace
)
{
string
::
size_type
pos
=
str
.
find_last_not_of
(
ws
);
str
.
erase
(
pos
+
1
);
return
str
;
}
vector
<
string
>::
size_type
split
(
const
string
&
str
,
char
delim
,
vector
<
string
>
&
elems
)
{
stringstream
ss
(
str
);
string
item
;
vector
<
string
>::
size_type
n
=
0
;
while
(
getline
(
ss
,
item
,
delim
))
{
elems
.
push_back
(
item
);
++
n
;
}
return
n
;
}
inline
void
split_lines
(
const
string
&
str
,
vector
<
string
>
&
elems
)
{
split
(
str
,
'\n'
,
elems
);
for
(
vector
<
string
>::
iterator
it
=
elems
.
begin
();
it
!=
elems
.
end
();
++
it
)
rtrim
(
*
it
);
}
#ifndef NDEBUG
void
output_proto_debug
(
const
string
&
data
,
bool
is_received
=
true
)
{
string
escaped_data
(
data
);
size_t
pos
;
while
((
pos
=
escaped_data
.
find
(
"
\n
"
))
!=
string
::
npos
)
escaped_data
.
replace
(
pos
,
1
,
"
\\
n"
);
while
((
pos
=
escaped_data
.
find
(
"
\r
"
))
!=
string
::
npos
)
escaped_data
.
replace
(
pos
,
1
,
"
\\
r"
);
cerr
<<
time
(
NULL
)
<<
": "
<<
(
is_received
?
"RECV '"
:
"SEND '"
)
<<
escaped_data
<<
"'"
<<
endl
;
}
#endif
class
makecmd
{
public:
explicit
makecmd
(
const
string
&
initial
,
bool
finalize
=
false
)
{
buffer_
<<
initial
;
if
(
!
finalize
)
buffer_
<<
" "
;
}
template
<
typename
T
>
makecmd
&
operator
<<
(
T
const
&
datum
)
{
buffer_
<<
datum
;
return
*
this
;
}
template
<
typename
T
>
makecmd
&
operator
<<
(
const
vector
<
T
>
&
data
)
{
size_t
n
=
data
.
size
();
for
(
size_t
i
=
0
;
i
<
n
;
++
i
)
{
buffer_
<<
data
[
i
];
if
(
i
<
n
-
1
)
buffer_
<<
" "
;
}
return
*
this
;
}
operator
std
::
string
()
{
buffer_
<<
CRLF
;
return
buffer_
.
str
();
}
private:
ostringstream
buffer_
;
};
// Reads N bytes from given blocking socket.
string
read_n
(
int
socket
,
ssize_t
n
)
{
char
*
buffer
=
new
char
[
n
+
1
];
buffer
[
n
]
=
'\0'
;
char
*
bp
=
buffer
;
ssize_t
bytes_read
=
0
;
while
(
bytes_read
!=
n
)
{
ssize_t
bytes_received
=
0
;
do
bytes_received
=
recv
(
socket
,
bp
,
n
-
(
bp
-
buffer
),
0
);
while
(
bytes_received
<
0
&&
errno
==
EINTR
);
if
(
bytes_received
==
0
)
throw
redis
::
connection_error
(
"connection was closed"
);
bytes_read
+=
bytes_received
;
bp
+=
bytes_received
;
}
string
str
(
buffer
);
delete
[]
buffer
;
return
str
;
}
// Reads a single line of character data from the given blocking socket.
// Returns the line that was read, not including EOL delimiter(s). Both LF
// ('\n') and CRLF ("\r\n") delimiters are supported. If there was an I/O
// error reading from the socket, connection_error is raised. If max_size
// bytes are read before finding an EOL delimiter, a blank string is
// returned.
string
read_line
(
int
socket
,
ssize_t
max_size
=
2048
)
{
assert
(
socket
>
0
);
assert
(
max_size
>
0
);
ostringstream
oss
;
enum
{
buffer_size
=
64
};
char
buffer
[
buffer_size
];
memset
(
buffer
,
0
,
buffer_size
);
ssize_t
total_bytes_read
=
0
;
bool
found_delimiter
=
false
;
while
(
total_bytes_read
<
max_size
&&
!
found_delimiter
)
{
// Peek at what's available.
ssize_t
bytes_received
=
0
;
do
bytes_received
=
recv
(
socket
,
buffer
,
buffer_size
,
MSG_PEEK
);
while
(
bytes_received
<
0
&&
errno
==
EINTR
);
if
(
bytes_received
==
0
)
throw
redis
::
connection_error
(
"connection was closed"
);
// Some data is available; Length might be < buffer_size.
// Look for newline in whatever was read though.
char
*
eol
=
static_cast
<
char
*>
(
memchr
(
buffer
,
'\n'
,
bytes_received
));
// If found, write data from the buffer to the output string.
// Else, write the entire buffer and continue reading more data.
ssize_t
to_read
=
bytes_received
;
if
(
eol
)
{
to_read
=
eol
-
buffer
+
1
;
oss
.
write
(
buffer
,
to_read
);
found_delimiter
=
true
;
}
else
oss
.
write
(
buffer
,
bytes_received
);
// Now read from the socket to remove the peeked data from the socket's
// read buffer. This will not block since we've peeked already and know
// there's data waiting. It might fail if we were interrupted however.
do
bytes_received
=
recv
(
socket
,
buffer
,
to_read
,
0
);
while
(
bytes_received
<
0
&&
errno
==
EINTR
);
}
// Construct final line string. Remove trailing CRLF-based whitespace.
string
line
=
oss
.
str
();
return
rtrim
(
line
,
CRLF
);
}
unsigned
long
unsigned_number_from_string
(
const
string
&
data
)
{
errno
=
0
;
unsigned
long
value
=
strtoul
(
data
.
c_str
(),
NULL
,
10
);
if
(
value
==
ULONG_MAX
&&
errno
==
ERANGE
)
throw
redis
::
value_error
(
"invalid number; out of range of long"
);
if
(
value
==
0
&&
errno
==
EINVAL
)
throw
redis
::
value_error
(
"invalid number; unrecognized format"
);
return
value
;
}
redis
::
client
::
int_type
number_from_string
(
const
string
&
data
)
{
errno
=
0
;
redis
::
client
::
int_type
value
=
strtol
(
data
.
c_str
(),
NULL
,
10
);
if
((
value
==
LONG_MAX
||
value
==
LONG_MIN
)
&&
errno
==
ERANGE
)
throw
redis
::
value_error
(
"invalid number; out of range of long"
);
if
(
value
==
0
&&
errno
==
EINVAL
)
throw
redis
::
value_error
(
"invalid number; unrecognized format"
);
return
value
;
}
const
string
status_reply_ok
(
"OK"
);
const
string
prefix_status_reply_error
(
"-ERR "
);
const
char
prefix_status_reply_value
=
'+'
;
const
char
prefix_single_bulk_reply
=
'$'
;
const
char
prefix_multi_bulk_reply
=
'*'
;
const
char
prefix_int_reply
=
':'
;
const
string
server_info_key_version
=
"redis_version"
;
const
string
server_info_key_bgsave_in_progress
=
"bgsave_in_progress"
;
const
string
server_info_key_connected_clients
=
"connected_clients"
;
const
string
server_info_key_connected_slaves
=
"connected_slaves"
;
const
string
server_info_key_used_memory
=
"used_memory"
;
const
string
server_info_key_changes_since_last_save
=
"changes_since_last_save"
;
const
string
server_info_key_last_save_time
=
"last_save_time"
;
const
string
server_info_key_total_connections_received
=
"total_connections_received"
;
const
string
server_info_key_total_commands_processed
=
"total_commands_processed"
;
const
string
server_info_key_uptime_in_seconds
=
"uptime_in_seconds"
;
const
string
server_info_key_uptime_in_days
=
"uptime_in_days"
;
}
namespace
redis
{
redis_error
::
redis_error
(
const
string
&
err
)
:
err_
(
err
)
{
}
redis_error
::
operator
std
::
string
()
{
return
err_
;
}
redis_error
::
operator
const
std
::
string
()
const
{
return
err_
;
}
connection_error
::
connection_error
(
const
string
&
err
)
:
redis_error
(
err
)
{
}
protocol_error
::
protocol_error
(
const
string
&
err
)
:
redis_error
(
err
)
{
}
key_error
::
key_error
(
const
string
&
err
)
:
redis_error
(
err
)
{
}
value_error
::
value_error
(
const
string
&
err
)
:
redis_error
(
err
)
{
}
client
::
string_type
client
::
missing_value
(
"**nonexistent-key**"
);
client
::
client
(
const
string_type
&
host
,
unsigned
int
port
)
{
char
err
[
ANET_ERR_LEN
];
socket_
=
anetTcpConnect
(
err
,
const_cast
<
char
*>
(
host
.
c_str
()),
port
);
if
(
socket_
==
ANET_ERR
)
throw
connection_error
(
err
);
anetTcpNoDelay
(
NULL
,
socket_
);
}
client
::~
client
()
{
if
(
socket_
!=
ANET_ERR
)
close
(
socket_
);
}
void
client
::
auth
(
const
client
::
string_type
&
pass
)
{
send_
(
makecmd
(
"AUTH"
)
<<
pass
);
recv_ok_reply_
();
}
void
client
::
set
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"SET"
)
<<
key
<<
' '
<<
value
.
size
()
<<
CRLF
<<
value
);
recv_ok_reply_
();
}
client
::
string_type
client
::
get
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"GET"
)
<<
key
);
return
recv_bulk_reply_
();
}
client
::
string_type
client
::
getset
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"GETSET"
)
<<
key
<<
' '
<<
value
.
size
()
<<
CRLF
<<
value
);
return
recv_bulk_reply_
();
}
void
client
::
mget
(
const
client
::
string_vector
&
keys
,
string_vector
&
out
)
{
send_
(
makecmd
(
"MGET"
)
<<
keys
);
recv_multi_bulk_reply_
(
out
);
}
bool
client
::
setnx
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"SETNX"
)
<<
key
<<
' '
<<
value
.
size
()
<<
CRLF
<<
value
);
return
recv_int_reply_
()
==
1
;
}
client
::
int_type
client
::
incr
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"INCR"
)
<<
key
);
return
recv_int_reply_
();
}
client
::
int_type
client
::
incrby
(
const
client
::
string_type
&
key
,
client
::
int_type
by
)
{
send_
(
makecmd
(
"INCRBY"
)
<<
key
<<
' '
<<
by
);
return
recv_int_reply_
();
}
client
::
int_type
client
::
decr
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"DECR"
)
<<
key
);
return
recv_int_reply_
();
}
client
::
int_type
client
::
decrby
(
const
client
::
string_type
&
key
,
client
::
int_type
by
)
{
send_
(
makecmd
(
"DECRBY"
)
<<
key
<<
' '
<<
by
);
return
recv_int_reply_
();
}
bool
client
::
exists
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"EXISTS"
)
<<
key
);
return
recv_int_reply_
()
==
1
;
}
void
client
::
del
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"DEL"
)
<<
key
);
recv_int_ok_reply_
();
}
client
::
datatype
client
::
type
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"TYPE"
)
<<
key
);
string
response
=
recv_single_line_reply_
();
if
(
response
==
"none"
)
return
datatype_none
;
if
(
response
==
"string"
)
return
datatype_string
;
if
(
response
==
"list"
)
return
datatype_list
;
if
(
response
==
"set"
)
return
datatype_set
;
return
datatype_none
;
}
client
::
int_type
client
::
keys
(
const
client
::
string_type
&
pattern
,
client
::
string_vector
&
out
)
{
send_
(
makecmd
(
"KEYS"
)
<<
pattern
);
string
resp
=
recv_bulk_reply_
();
return
split
(
resp
,
' '
,
out
);
}
client
::
string_type
client
::
randomkey
()
{
send_
(
makecmd
(
"RANDOMKEY"
,
true
));
return
recv_single_line_reply_
();
}
void
client
::
rename
(
const
client
::
string_type
&
old_name
,
const
client
::
string_type
&
new_name
)
{
send_
(
makecmd
(
"RENAME"
)
<<
old_name
<<
' '
<<
new_name
);
recv_ok_reply_
();
}
bool
client
::
renamenx
(
const
client
::
string_type
&
old_name
,
const
client
::
string_type
&
new_name
)
{
send_
(
makecmd
(
"RENAMENX"
)
<<
old_name
<<
' '
<<
new_name
);
return
recv_int_reply_
()
==
1
;
}
client
::
int_type
client
::
dbsize
()
{
send_
(
makecmd
(
"DBSIZE"
));
return
recv_int_reply_
();
}
void
client
::
expire
(
const
string_type
&
key
,
unsigned
int
secs
)
{
send_
(
makecmd
(
"EXPIRE"
)
<<
key
<<
' '
<<
secs
);
recv_int_ok_reply_
();
}
void
client
::
rpush
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"RPUSH"
)
<<
key
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
recv_ok_reply_
();
}
void
client
::
lpush
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"LPUSH"
)
<<
key
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
recv_ok_reply_
();
}
client
::
int_type
client
::
llen
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"LLEN"
)
<<
key
);
return
recv_int_reply_
();
}
client
::
int_type
client
::
lrange
(
const
client
::
string_type
&
key
,
client
::
int_type
start
,
client
::
int_type
end
,
client
::
string_vector
&
out
)
{
send_
(
makecmd
(
"LRANGE"
)
<<
key
<<
' '
<<
start
<<
' '
<<
end
);
return
recv_multi_bulk_reply_
(
out
);
}
void
client
::
ltrim
(
const
client
::
string_type
&
key
,
client
::
int_type
start
,
client
::
int_type
end
)
{
send_
(
makecmd
(
"LTRIM"
)
<<
key
<<
' '
<<
start
<<
' '
<<
end
);
recv_ok_reply_
();
}
client
::
string_type
client
::
lindex
(
const
client
::
string_type
&
key
,
client
::
int_type
index
)
{
send_
(
makecmd
(
"LINDEX"
)
<<
key
<<
' '
<<
index
);
return
recv_bulk_reply_
();
}
void
client
::
lset
(
const
client
::
string_type
&
key
,
client
::
int_type
index
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"LSET"
)
<<
key
<<
' '
<<
index
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
recv_ok_reply_
();
}
client
::
int_type
client
::
lrem
(
const
client
::
string_type
&
key
,
client
::
int_type
count
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"LREM"
)
<<
key
<<
' '
<<
count
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
return
recv_int_reply_
();
}
client
::
string_type
client
::
lpop
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"LPOP"
)
<<
key
);
return
recv_bulk_reply_
();
}
client
::
string_type
client
::
rpop
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"RPOP"
)
<<
key
);
return
recv_bulk_reply_
();
}
void
client
::
sadd
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"SADD"
)
<<
key
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
recv_int_ok_reply_
();
}
void
client
::
srem
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"SREM"
)
<<
key
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
recv_int_ok_reply_
();
}
void
client
::
smove
(
const
client
::
string_type
&
srckey
,
const
client
::
string_type
&
dstkey
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"SMOVE"
)
<<
srckey
<<
' '
<<
dstkey
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
recv_int_ok_reply_
();
}
client
::
int_type
client
::
scard
(
const
client
::
string_type
&
key
)
{
send_
(
makecmd
(
"SCARD"
)
<<
key
);
return
recv_int_reply_
();
}
bool
client
::
sismember
(
const
client
::
string_type
&
key
,
const
client
::
string_type
&
value
)
{
send_
(
makecmd
(
"SISMEMBER"
)
<<
key
<<
' '
<<
value
.
length
()
<<
CRLF
<<
value
);
return
recv_int_reply_
()
==
1
;
}
client
::
int_type
client
::
sinter
(
const
client
::
string_vector
&
keys
,
client
::
string_set
&
out
)
{
send_
(
makecmd
(
"SINTER"
)
<<
keys
);
return
recv_multi_bulk_reply_
(
out
);
}
void
client
::
sinterstore
(
const
client
::
string_type
&
dstkey
,
const
client
::
string_vector
&
keys
)
{
send_
(
makecmd
(
"SINTERSTORE"
)
<<
dstkey
<<
' '
<<
keys
);
recv_ok_reply_
();
}
client
::
int_type
client
::
sunion
(
const
client
::
string_vector
&
keys
,
client
::
string_set
&
out
)
{
send_
(
makecmd
(
"SUNION"
)
<<
keys
);
return
recv_multi_bulk_reply_
(
out
);
}
void
client
::
sunionstore
(
const
client
::
string_type
&
dstkey
,
const
client
::
string_vector
&
keys
)
{
send_
(
makecmd
(
"SUNIONSTORE"
)
<<
dstkey
<<
' '
<<
keys
);
recv_ok_reply_
();
}
client
::
int_type
client
::
smembers
(
const
client
::
string_type
&
key
,
client
::
string_set
&
out
)
{
send_
(
makecmd
(
"SMEMBERS"
)
<<
key
);
return
recv_multi_bulk_reply_
(
out
);
}
void
client
::
select
(
client
::
int_type
dbindex
)
{
send_
(
makecmd
(
"SELECT"
)
<<
dbindex
);
recv_ok_reply_
();
}
void
client
::
move
(
const
client
::
string_type
&
key
,
client
::
int_type
dbindex
)
{
send_
(
makecmd
(
"MOVE"
)
<<
key
<<
' '
<<
dbindex
);
recv_int_ok_reply_
();
}
void
client
::
flushdb
()
{
send_
(
makecmd
(
"FLUSHDB"
,
true
));
recv_ok_reply_
();
}
void
client
::
flushall
()
{
send_
(
makecmd
(
"FLUSHALL"
,
true
));
recv_ok_reply_
();
}
client
::
int_type
client
::
sort
(
const
client
::
string_type
&
key
,
client
::
string_vector
&
out
,
client
::
sort_order
order
,
bool
lexicographically
)
{
send_
(
makecmd
(
"SORT"
)
<<
key
<<
(
order
==
sort_order_ascending
?
" ASC"
:
" DESC"
)
<<
(
lexicographically
?
" ALPHA"
:
""
));
return
recv_multi_bulk_reply_
(
out
);
}
client
::
int_type
client
::
sort
(
const
client
::
string_type
&
key
,
client
::
string_vector
&
out
,
client
::
int_type
limit_start
,
client
::
int_type
limit_end
,
client
::
sort_order
order
,
bool
lexicographically
)
{
send_
(
makecmd
(
"SORT"
)
<<
key
<<
" LIMIT "
<<
limit_start
<<
' '
<<
limit_end
<<
(
order
==
sort_order_ascending
?
" ASC"
:
" DESC"
)
<<
(
lexicographically
?
" ALPHA"
:
""
));
return
recv_multi_bulk_reply_
(
out
);
}
client
::
int_type
client
::
sort
(
const
client
::
string_type
&
key
,
client
::
string_vector
&
out
,
const
client
::
string_type
&
by_pattern
,
client
::
int_type
limit_start
,
client
::
int_type
limit_end
,
const
client
::
string_type
&
get_pattern
,
client
::
sort_order
order
,
bool
lexicographically
)
{
send_
(
makecmd
(
"SORT"
)
<<
key
<<
" BY "
<<
by_pattern
<<
" LIMIT "
<<
limit_start
<<
' '
<<
limit_end
<<
" GET "
<<
get_pattern
<<
(
order
==
sort_order_ascending
?
" ASC"
:
" DESC"
)
<<
(
lexicographically
?
" ALPHA"
:
""
));
return
recv_multi_bulk_reply_
(
out
);
}
void
client
::
save
()
{
send_
(
makecmd
(
"SAVE"
,
true
));
recv_ok_reply_
();
e
.
g
.
}
void
client
::
bgsave
()
{
send_
(
makecmd
(
"BGSAVE"
,
true
));
recv_ok_reply_
();
}
time_t
client
::
lastsave
()
{
send_
(
makecmd
(
"LASTSAVE"
,
true
));
return
recv_int_reply_
();
}
void
client
::
shutdown
()
{
send_
(
makecmd
(
"SHUTDOWN"
,
true
));
// we expected to get a connection_error as redis closes the connection on shutdown command.
try
{
recv_ok_reply_
();
}
catch
(
connection_error
&
e
)
{
}
}
void
client
::
info
(
server_info
&
out
)
{
send_
(
makecmd
(
"INFO"
,
true
));
string
response
=
recv_bulk_reply_
();
if
(
response
.
empty
())
throw
protocol_error
(
"empty"
);
string_vector
lines
;
split_lines
(
response
,
lines
);
if
(
lines
.
empty
())
throw
protocol_error
(
"empty line for info"
);
for
(
string_vector
::
const_iterator
it
=
lines
.
begin
();
it
!=
lines
.
end
();
++
it
)
{
const
string
&
line
=
*
it
;
string_vector
line_parts
;
split
(
line
,
':'
,
line_parts
);
if
(
line_parts
.
size
()
!=
2
)
throw
protocol_error
(
"unexpected line format for info"
);
const
string
&
key
=
line_parts
[
0
];
const
string
&
val
=
line_parts
[
1
];
if
(
key
==
server_info_key_version
)
out
.
version
=
val
;
else
if
(
key
==
server_info_key_bgsave_in_progress
)
out
.
bgsave_in_progress
=
unsigned_number_from_string
(
val
)
==
1
;
else
if
(
key
==
server_info_key_connected_clients
)
out
.
connected_clients
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_connected_slaves
)
out
.
connected_slaves
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_used_memory
)
out
.
used_memory
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_changes_since_last_save
)
out
.
changes_since_last_save
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_last_save_time
)
out
.
last_save_time
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_total_connections_received
)
out
.
total_connections_received
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_total_commands_processed
)
out
.
total_commands_processed
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_uptime_in_seconds
)
out
.
uptime_in_seconds
=
unsigned_number_from_string
(
val
);
else
if
(
key
==
server_info_key_uptime_in_days
)
out
.
uptime_in_days
=
unsigned_number_from_string
(
val
);
else
throw
protocol_error
(
string
(
"unexpected info key '"
)
+
key
+
"'"
);
}
}
//
// Private methods
//
void
client
::
send_
(
const
string
&
msg
)
{
#ifndef NDEBUG
output_proto_debug
(
msg
,
false
);
#endif
if
(
anetWrite
(
socket_
,
const_cast
<
char
*>
(
msg
.
data
()),
msg
.
size
())
==
-
1
)
throw
connection_error
(
strerror
(
errno
));
}
string
client
::
recv_single_line_reply_
()
{
string
line
=
read_line
(
socket_
);
#ifndef NDEBUG
output_proto_debug
(
line
);
#endif
if
(
line
.
empty
())
throw
protocol_error
(
"empty single line reply"
);
if
(
line
.
find
(
prefix_status_reply_error
)
==
0
)
{
string
error_msg
=
line
.
substr
(
prefix_status_reply_error
.
length
());
if
(
error_msg
.
empty
())
error_msg
=
"unknown error"
;
throw
protocol_error
(
error_msg
);
}
if
(
line
[
0
]
!=
prefix_status_reply_value
)
throw
protocol_error
(
"unexpected prefix for status reply"
);
return
line
.
substr
(
1
);
}
void
client
::
recv_ok_reply_
()
{
if
(
recv_single_line_reply_
()
!=
status_reply_ok
)
throw
protocol_error
(
"expected OK response"
);
}
client
::
int_type
client
::
recv_bulk_reply_
(
char
prefix
)
{
string
line
=
read_line
(
socket_
);
#ifndef NDEBUG
output_proto_debug
(
line
);
#endif
if
(
line
[
0
]
!=
prefix
)
throw
protocol_error
(
"unexpected prefix for bulk reply"
);
return
number_from_string
(
line
.
substr
(
1
));
}
string
client
::
recv_bulk_reply_
()
{
int_type
length
=
recv_bulk_reply_
(
prefix_single_bulk_reply
);
if
(
length
==
-
1
)
return
client
::
missing_value
;
int_type
real_length
=
length
+
2
;
// CRLF
string
data
=
read_n
(
socket_
,
real_length
);
#ifndef NDEBUG
output_proto_debug
(
data
.
substr
(
0
,
data
.
length
()
-
2
));
#endif
if
(
data
.
empty
())
throw
protocol_error
(
"invalid bulk reply data; empty"
);
if
(
data
.
length
()
!=
static_cast
<
string
::
size_type
>
(
real_length
))
throw
protocol_error
(
"invalid bulk reply data; data of unexpected length"
);
data
.
erase
(
data
.
size
()
-
2
);
return
data
;
}
client
::
int_type
client
::
recv_multi_bulk_reply_
(
string_vector
&
out
)
{
int_type
length
=
recv_bulk_reply_
(
prefix_multi_bulk_reply
);
if
(
length
==
-
1
)
throw
key_error
(
"no such key"
);
for
(
int_type
i
=
0
;
i
<
length
;
++
i
)
out
.
push_back
(
recv_bulk_reply_
());
return
length
;
}
client
::
int_type
client
::
recv_multi_bulk_reply_
(
string_set
&
out
)
{
int_type
length
=
recv_bulk_reply_
(
prefix_multi_bulk_reply
);
if
(
length
==
-
1
)
throw
key_error
(
"no such key"
);
for
(
int_type
i
=
0
;
i
<
length
;
++
i
)
out
.
insert
(
recv_bulk_reply_
());
return
length
;
}
client
::
int_type
client
::
recv_int_reply_
()
{
string
line
=
read_line
(
socket_
);
#ifndef NDEBUG
output_proto_debug
(
line
);
#endif
if
(
line
.
empty
())
throw
protocol_error
(
"invalid integer reply; empty"
);
if
(
line
[
0
]
!=
prefix_int_reply
)
throw
protocol_error
(
"unexpected prefix for integer reply"
);
return
number_from_string
(
line
.
substr
(
1
));
}
void
client
::
recv_int_ok_reply_
()
{
if
(
recv_int_reply_
()
!=
1
)
throw
protocol_error
(
"expecting int reply of 1"
);
}
}
client-libraries/cpp/redisclient.h
0 → 100644
View file @
57172ffb
/* 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
client-libraries/cpp/test_client.cpp
0 → 100644
View file @
57172ffb
#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
;
}
client-libraries/ruby/.gitignore
View file @
57172ffb
nohup.out
redis/*
rdsrv
pkg/*
\ No newline at end of file
client-libraries/ruby/Rakefile
View file @
57172ffb
...
...
@@ -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
|
...
...
client-libraries/ruby/bench.rb
View file @
57172ffb
...
...
@@ -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
client-libraries/ruby/lib/pipeline.rb
0 → 100644
View file @
57172ffb
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
client-libraries/ruby/lib/redis.rb
View file @
57172ffb
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
...
...
client-libraries/ruby/lib/server.rb
View file @
57172ffb
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
##
...
...
client-libraries/ruby/redis-rb.gemspec
View file @
57172ffb
# -*- 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"
]
...
...
client-libraries/ruby/spec/redis_spec.rb
View file @
57172ffb
...
...
@@ -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
client-libraries/update-cpp-client.sh
0 → 100755
View file @
57172ffb
#!/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
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment