├── fmacros.h ├── README.md ├── TODO.md ├── Makefile ├── anet.h ├── anet.c ├── redisclient.h ├── test_client.cpp └── redisclient.cpp /fmacros.h: -------------------------------------------------------------------------------- 1 | #ifndef _REDIS_FMACRO_H 2 | #define _REDIS_FMACRO_H 3 | 4 | #define _BSD_SOURCE 5 | #define _XOPEN_SOURCE 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A C++ client for Redis 2 | 3 | - This client has no external dependencies 4 | - It has been tested with g++ on Mac OS X 10.5 and Linux 5 | - It uses anet from Redis itself 6 | 7 | ## Status 8 | 9 | This client is out of date with respect to Redis 1.1. 10 | 11 | ## License 12 | 13 | This client is licensed under the same license as redis. 14 | 15 | ## Author 16 | 17 | Brian Hammond 18 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | command handlers: 3 | - support DEL as vararg 4 | - support MLLEN and MSCARD 5 | - support SDIFF 6 | - support SDIFFSTORE 7 | 8 | unit tests: 9 | - sort with limit 10 | - sort lexicographically 11 | - sort with pattern and weights 12 | 13 | extras: 14 | - benchmarking "test" app 15 | - consistent hashing? 16 | 17 | maybe/someday: 18 | - make all string literals constants so they can be easily changed 19 | - add conveniences that store a std::set in its entirety (same for std::list, std::vector) 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Redis C++ Client Library Makefile 2 | 3 | CFLAGS?= -pedantic -O2 -Wall -W -DNDEBUG 4 | #CFLAGS?= -pedantic -O0 -W -DDEBUG -g 5 | CC = g++ 6 | 7 | CLIENTOBJS = anet.o redisclient.o 8 | LIBNAME = libredisclient.a 9 | 10 | TESTAPP = test_client 11 | TESTAPPOBJS = test_client.o 12 | TESTAPPLIBS = $(LIBNAME) -lstdc++ 13 | 14 | all: $(LIBNAME) $(TESTAPP) 15 | 16 | $(LIBNAME): $(CLIENTOBJS) 17 | ar rcs $(LIBNAME) $(CLIENTOBJS) 18 | 19 | .c.o: 20 | $(CC) -c $(CFLAGS) $< 21 | 22 | .cpp.o: 23 | $(CC) -c $(CFLAGS) $< 24 | 25 | $(TESTAPP): $(LIBNAME) $(TESTAPPOBJS) 26 | $(CC) -o $(TESTAPP) $(TESTAPPOBJS) $(TESTAPPLIBS) 27 | 28 | test: $(TESTAPP) 29 | @./test_client 30 | 31 | check: test 32 | 33 | clean: 34 | rm -rf $(LIBNAME) *.o $(TESTAPP) 35 | 36 | dep: 37 | $(CC) -MM *.c *.cpp 38 | 39 | log: 40 | git log '--pretty=format:%ad %s' --date=short > Changelog 41 | 42 | anet.o: anet.c fmacros.h anet.h 43 | redisclient.o: redisclient.cpp redisclient.h anet.h 44 | 45 | -------------------------------------------------------------------------------- /anet.h: -------------------------------------------------------------------------------- 1 | /* anet.c -- Basic TCP socket stuff made a bit less boring 2 | * 3 | * Copyright (c) 2006-2009, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef ANET_H 32 | #define ANET_H 33 | 34 | #define ANET_OK 0 35 | #define ANET_ERR -1 36 | #define ANET_ERR_LEN 256 37 | 38 | int anetTcpConnect(char *err, char *addr, int port); 39 | int anetTcpNonBlockConnect(char *err, char *addr, int port); 40 | int anetRead(int fd, char *buf, int count); 41 | int anetResolve(char *err, char *host, char *ipbuf); 42 | int anetTcpServer(char *err, int port, char *bindaddr); 43 | int anetAccept(char *err, int serversock, char *ip, int *port); 44 | int anetWrite(int fd, char *buf, int count); 45 | int anetNonBlock(char *err, int fd); 46 | int anetTcpNoDelay(char *err, int fd); 47 | int anetTcpKeepAlive(char *err, int fd); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /anet.c: -------------------------------------------------------------------------------- 1 | /* anet.c -- Basic TCP socket stuff made a bit less boring 2 | * 3 | * Copyright (c) 2006-2009, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include "fmacros.h" 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include "anet.h" 47 | 48 | static void anetSetError(char *err, const char *fmt, ...) 49 | { 50 | va_list ap; 51 | 52 | if (!err) return; 53 | va_start(ap, fmt); 54 | vsnprintf(err, ANET_ERR_LEN, fmt, ap); 55 | va_end(ap); 56 | } 57 | 58 | int anetNonBlock(char *err, int fd) 59 | { 60 | int flags; 61 | 62 | /* Set the socket nonblocking. 63 | * Note that fcntl(2) for F_GETFL and F_SETFL can't be 64 | * interrupted by a signal. */ 65 | if ((flags = fcntl(fd, F_GETFL)) == -1) { 66 | anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno)); 67 | return ANET_ERR; 68 | } 69 | if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { 70 | anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno)); 71 | return ANET_ERR; 72 | } 73 | return ANET_OK; 74 | } 75 | 76 | int anetTcpNoDelay(char *err, int fd) 77 | { 78 | int yes = 1; 79 | if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) 80 | { 81 | anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno)); 82 | return ANET_ERR; 83 | } 84 | return ANET_OK; 85 | } 86 | 87 | int anetSetSendBuffer(char *err, int fd, int buffsize) 88 | { 89 | if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1) 90 | { 91 | anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno)); 92 | return ANET_ERR; 93 | } 94 | return ANET_OK; 95 | } 96 | 97 | int anetTcpKeepAlive(char *err, int fd) 98 | { 99 | int yes = 1; 100 | if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) { 101 | anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno)); 102 | return ANET_ERR; 103 | } 104 | return ANET_OK; 105 | } 106 | 107 | int anetResolve(char *err, char *host, char *ipbuf) 108 | { 109 | struct sockaddr_in sa; 110 | 111 | sa.sin_family = AF_INET; 112 | if (inet_aton(host, &sa.sin_addr) == 0) { 113 | struct hostent *he; 114 | 115 | he = gethostbyname(host); 116 | if (he == NULL) { 117 | anetSetError(err, "can't resolve: %s\n", host); 118 | return ANET_ERR; 119 | } 120 | memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr)); 121 | } 122 | strcpy(ipbuf,inet_ntoa(sa.sin_addr)); 123 | return ANET_OK; 124 | } 125 | 126 | #define ANET_CONNECT_NONE 0 127 | #define ANET_CONNECT_NONBLOCK 1 128 | static int anetTcpGenericConnect(char *err, char *addr, int port, int flags) 129 | { 130 | int s, on = 1; 131 | struct sockaddr_in sa; 132 | 133 | if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 134 | anetSetError(err, "creating socket: %s\n", strerror(errno)); 135 | return ANET_ERR; 136 | } 137 | /* Make sure connection-intensive things like the redis benckmark 138 | * will be able to close/open sockets a zillion of times */ 139 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 140 | 141 | sa.sin_family = AF_INET; 142 | sa.sin_port = htons(port); 143 | if (inet_aton(addr, &sa.sin_addr) == 0) { 144 | struct hostent *he; 145 | 146 | he = gethostbyname(addr); 147 | if (he == NULL) { 148 | anetSetError(err, "can't resolve: %s\n", addr); 149 | close(s); 150 | return ANET_ERR; 151 | } 152 | memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr)); 153 | } 154 | if (flags & ANET_CONNECT_NONBLOCK) { 155 | if (anetNonBlock(err,s) != ANET_OK) 156 | return ANET_ERR; 157 | } 158 | if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { 159 | if (errno == EINPROGRESS && 160 | flags & ANET_CONNECT_NONBLOCK) 161 | return s; 162 | 163 | anetSetError(err, "connect: %s\n", strerror(errno)); 164 | close(s); 165 | return ANET_ERR; 166 | } 167 | return s; 168 | } 169 | 170 | int anetTcpConnect(char *err, char *addr, int port) 171 | { 172 | return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE); 173 | } 174 | 175 | int anetTcpNonBlockConnect(char *err, char *addr, int port) 176 | { 177 | return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK); 178 | } 179 | 180 | /* Like read(2) but make sure 'count' is read before to return 181 | * (unless error or EOF condition is encountered) */ 182 | int anetRead(int fd, char *buf, int count) 183 | { 184 | int nread, totlen = 0; 185 | while(totlen != count) { 186 | nread = read(fd,buf,count-totlen); 187 | if (nread == 0) return totlen; 188 | if (nread == -1) return -1; 189 | totlen += nread; 190 | buf += nread; 191 | } 192 | return totlen; 193 | } 194 | 195 | /* Like write(2) but make sure 'count' is read before to return 196 | * (unless error is encountered) */ 197 | int anetWrite(int fd, char *buf, int count) 198 | { 199 | int nwritten, totlen = 0; 200 | while(totlen != count) { 201 | nwritten = write(fd,buf,count-totlen); 202 | if (nwritten == 0) return totlen; 203 | if (nwritten == -1) return -1; 204 | totlen += nwritten; 205 | buf += nwritten; 206 | } 207 | return totlen; 208 | } 209 | 210 | int anetTcpServer(char *err, int port, char *bindaddr) 211 | { 212 | int s, on = 1; 213 | struct sockaddr_in sa; 214 | 215 | if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 216 | anetSetError(err, "socket: %s\n", strerror(errno)); 217 | return ANET_ERR; 218 | } 219 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { 220 | anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno)); 221 | close(s); 222 | return ANET_ERR; 223 | } 224 | memset(&sa,0,sizeof(sa)); 225 | sa.sin_family = AF_INET; 226 | sa.sin_port = htons(port); 227 | sa.sin_addr.s_addr = htonl(INADDR_ANY); 228 | if (bindaddr) { 229 | if (inet_aton(bindaddr, &sa.sin_addr) == 0) { 230 | anetSetError(err, "Invalid bind address\n"); 231 | close(s); 232 | return ANET_ERR; 233 | } 234 | } 235 | if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { 236 | anetSetError(err, "bind: %s\n", strerror(errno)); 237 | close(s); 238 | return ANET_ERR; 239 | } 240 | if (listen(s, 32) == -1) { 241 | anetSetError(err, "listen: %s\n", strerror(errno)); 242 | close(s); 243 | return ANET_ERR; 244 | } 245 | return s; 246 | } 247 | 248 | int anetAccept(char *err, int serversock, char *ip, int *port) 249 | { 250 | int fd; 251 | struct sockaddr_in sa; 252 | unsigned int saLen; 253 | 254 | while(1) { 255 | saLen = sizeof(sa); 256 | fd = accept(serversock, (struct sockaddr*)&sa, &saLen); 257 | if (fd == -1) { 258 | if (errno == EINTR) 259 | continue; 260 | else { 261 | anetSetError(err, "accept: %s\n", strerror(errno)); 262 | return ANET_ERR; 263 | } 264 | } 265 | break; 266 | } 267 | if (ip) strcpy(ip,inet_ntoa(sa.sin_addr)); 268 | if (port) *port = ntohs(sa.sin_port); 269 | return fd; 270 | } 271 | -------------------------------------------------------------------------------- /redisclient.h: -------------------------------------------------------------------------------- 1 | /* redisclient.h -- a C++ client library for redis. 2 | * 3 | * Copyright (c) 2009, Brian Hammond 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef REDISCLIENT_H 32 | #define REDISCLIENT_H 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | namespace redis 41 | { 42 | enum server_role 43 | { 44 | role_master, 45 | role_slave 46 | }; 47 | 48 | struct server_info 49 | { 50 | std::string version; 51 | bool bgsave_in_progress; 52 | unsigned long connected_clients; 53 | unsigned long connected_slaves; 54 | unsigned long used_memory; 55 | unsigned long changes_since_last_save; 56 | unsigned long last_save_time; 57 | unsigned long total_connections_received; 58 | unsigned long total_commands_processed; 59 | unsigned long uptime_in_seconds; 60 | unsigned long uptime_in_days; 61 | server_role role; 62 | }; 63 | 64 | // Generic error that is thrown when communicating with the redis server. 65 | 66 | class redis_error 67 | { 68 | public: 69 | redis_error(const std::string & err); 70 | operator std::string (); 71 | operator const std::string () const; 72 | private: 73 | std::string err_; 74 | }; 75 | 76 | // Some socket-level I/O or general connection error. 77 | 78 | class connection_error : public redis_error 79 | { 80 | public: 81 | connection_error(const std::string & err); 82 | }; 83 | 84 | // Redis gave us a reply we were not expecting. 85 | // Possibly an internal error (here or in redis, probably here). 86 | 87 | class protocol_error : public redis_error 88 | { 89 | public: 90 | protocol_error(const std::string & err); 91 | }; 92 | 93 | // A key that you expected to exist does not in fact exist. 94 | 95 | class key_error : public redis_error 96 | { 97 | public: 98 | key_error(const std::string & err); 99 | }; 100 | 101 | // A value of an expected type or other semantics was found to be invalid. 102 | 103 | class value_error : public redis_error 104 | { 105 | public: 106 | value_error(const std::string & err); 107 | }; 108 | 109 | // You should construct a 'client' object per connection to a redis-server. 110 | // 111 | // Please read the online redis command reference: 112 | // http://code.google.com/p/redis/wiki/CommandReference 113 | // 114 | // No provisions for customizing the allocator on the string/bulk value type 115 | // (std::string) are provided. If needed, you can always change the 116 | // string_type typedef in your local version. 117 | 118 | class client 119 | { 120 | public: 121 | typedef std::string string_type; 122 | typedef std::vector string_vector; 123 | typedef std::set string_set; 124 | 125 | typedef long int_type; 126 | 127 | explicit client(const string_type & host = "localhost", 128 | unsigned int port = 6379); 129 | 130 | ~client(); 131 | 132 | // 133 | // Connection handling 134 | // 135 | 136 | void auth(const string_type & pass); 137 | 138 | // 139 | // Commands operating on string values 140 | // 141 | // Note that empty string values do not denote nonexistent keys but well, 142 | // empty values! If a nonexistent key is queried, the value returned will 143 | // be missing_value, including when string_vector objects are returned. 144 | // 145 | 146 | static string_type missing_value; 147 | 148 | // set a key to a string value 149 | 150 | void set(const string_type & key, const string_type & value); 151 | 152 | // return the string value of the key 153 | 154 | string_type get(const string_type & key); 155 | 156 | // set a key to a string returning the old value of the key 157 | 158 | string_type getset(const string_type & key, const string_type & value); 159 | 160 | // multi-get, return the strings values of the keys 161 | 162 | void mget(const string_vector & keys, string_vector & out); 163 | 164 | // set a key to a string value if the key does not exist. returns true if 165 | // the key was set, else false. This does not throw since you are ok with 166 | // this failing if the dst key already exists. 167 | 168 | bool setnx(const string_type & key, const string_type & value); 169 | 170 | // increment the integer value of key 171 | // returns new value 172 | 173 | int_type incr(const string_type & key); 174 | 175 | // increment the integer value of key by integer 176 | // returns new value 177 | 178 | int_type incrby(const string_type & key, int_type by); 179 | 180 | // decrement the integer value of key 181 | // returns new value 182 | 183 | int_type decr(const string_type & key); 184 | 185 | // decrement the integer value of key by integer 186 | // returns new value 187 | 188 | int_type decrby(const string_type & key, int_type by); 189 | 190 | // test if a key exists 191 | 192 | bool exists(const string_type & key); 193 | 194 | // delete a key 195 | // throws if doesn't exist 196 | 197 | void del(const string_type & key); 198 | 199 | enum datatype 200 | { 201 | datatype_none, // key doesn't exist 202 | datatype_string, 203 | datatype_list, 204 | datatype_set 205 | }; 206 | 207 | // return the type of the value stored at key 208 | 209 | datatype type(const string_type & key); 210 | 211 | // 212 | // Commands operating on the key space 213 | // 214 | 215 | // find all the keys matching a given pattern 216 | // returns numbers of keys appended to 'out' 217 | 218 | int_type keys(const string_type & pattern, string_vector & out); 219 | 220 | // return a random key from the key space 221 | // returns empty string if db is empty 222 | 223 | string_type randomkey(); 224 | 225 | // rename the old key in the new one, destroying the new key if 226 | // it already exists 227 | 228 | void rename(const string_type & old_name, const string_type & new_name); 229 | 230 | // rename the old key in the new one, if the new key does not already 231 | // exist. This does not throw since you are ok with this failing if the 232 | // new_name key already exists. 233 | 234 | bool renamenx(const string_type & old_name, const string_type & new_name); 235 | 236 | // return the number of keys in the current db 237 | 238 | int_type dbsize(); 239 | 240 | // set a time to live in seconds on a key. 241 | // fails if there's already a timeout on the key. 242 | 243 | // NB: there's currently no generic way to remove a timeout on a key 244 | 245 | void expire(const string_type & key, unsigned int secs); 246 | 247 | // 248 | // Commands operating on lists 249 | // 250 | 251 | // Append an element to the tail of the list value at key 252 | 253 | void rpush(const string_type & key, const string_type & value); 254 | 255 | // Append an element to the head of the list value at key 256 | 257 | void lpush(const string_type & key, const string_type & value); 258 | 259 | // Return the length of the list value at key 260 | // Returns 0 if the list does not exist; see 'exists' 261 | 262 | int_type llen(const string_type & key); 263 | 264 | // Fetch a range of elements from the list at key 265 | // end can be negative for reverse offsets 266 | // Returns number of elements appended to 'out' 267 | 268 | int_type lrange(const string_type & key, 269 | int_type start, 270 | int_type end, 271 | string_vector & out); 272 | 273 | // Fetches the entire list at key. 274 | 275 | int_type get_list(const string_type & key, string_vector & out) 276 | { 277 | return lrange(key, 0, -1, out); 278 | } 279 | 280 | // Trim the list at key to the specified range of elements 281 | 282 | void ltrim(const string_type & key, int_type start, int_type end); 283 | 284 | // Return the element at index position from the list at key 285 | 286 | string_type lindex(const string_type & key, int_type); 287 | 288 | // set a new value as the element at index position of the list at key 289 | 290 | void lset(const string_type & key, 291 | int_type index, 292 | const string_type &); 293 | 294 | // If count is zero all the elements are removed. If count is negative 295 | // elements are removed from tail to head, instead to go from head to tail 296 | // that is the normal behaviour. So for example LREM with count -2 and 297 | // hello as value to remove against the list (a,b,c,hello,x,hello,hello) 298 | // will lave the list (a,b,c,hello,x). Returns the number of removed 299 | // elements if the operation succeeded. 300 | // 301 | // Note: this will not throw if the number of elements removed != count 302 | // since you might want to remove at most count elements by don't care if 303 | // < count elements are removed. See lrem_exact(). 304 | 305 | int_type lrem(const string_type & key, 306 | int_type count, 307 | const string_type & value); 308 | 309 | // An extension of 'lrem' that wants to remove exactly 'count' elements. 310 | // Throws value_error if 'count' elements are not found & removed from the 311 | // list at 'key'. 312 | 313 | void lrem_exact(const string_type & key, 314 | int_type count, 315 | const string_type & value) 316 | { 317 | if (lrem(key, count, value) != count) 318 | throw value_error("failed to remove exactly N elements from list"); 319 | } 320 | 321 | // Return and remove (atomically) the first element of the list at key 322 | 323 | string_type lpop(const string_type & key); 324 | 325 | // Return and remove (atomically) the last element of the list at key 326 | 327 | string_type rpop(const string_type & key); 328 | 329 | // 330 | // Commands operating on sets 331 | // 332 | 333 | // Add the specified member to the set value at key 334 | // returns true if added, or false if already a member of the set. 335 | 336 | void sadd(const string_type & key, const string_type & value); 337 | 338 | // Remove the specified member from the set value at key 339 | // returns true if removed or false if value is not a member of the set. 340 | 341 | void srem(const string_type & key, const string_type & value); 342 | 343 | // Move the specified member from one set to another atomically 344 | // returns true if element was moved, else false (e.g. not found) 345 | 346 | void smove(const string_type & srckey, 347 | const string_type & dstkey, 348 | const string_type & value); 349 | 350 | // Return the number of elements (the cardinality) of the set at key 351 | 352 | int_type scard(const string_type & key); 353 | 354 | // Test if the specified value is a member of the set at key 355 | // Returns false if key doesn't exist or value is not a member of the set at key 356 | 357 | bool sismember(const string_type & key, const string_type & value); 358 | 359 | // Return the intersection between the sets stored at key1, key2, ..., keyN 360 | 361 | int_type sinter(const string_vector & keys, string_set & out); 362 | 363 | // Compute the intersection between the sets stored at key1, key2, ..., 364 | // keyN, and store the resulting set at dstkey 365 | // Returns the number of items in the intersection 366 | 367 | int_type sinterstore(const string_type & dstkey, const string_vector & keys); 368 | 369 | // Return the union between the sets stored at key1, key2, ..., keyN 370 | 371 | int_type sunion(const string_vector & keys, string_set & out); 372 | 373 | // Compute the union between the sets stored at key1, key2, ..., keyN, 374 | // and store the resulting set at dstkey 375 | // Returns the number of items in the intersection 376 | 377 | int_type sunionstore(const string_type & dstkey, const string_vector & keys); 378 | 379 | // Return all the members of the set value at key 380 | 381 | int_type smembers(const string_type & key, string_set & out); 382 | 383 | // 384 | // Multiple databases handling commands 385 | // 386 | 387 | // Select the DB having the specified index 388 | 389 | void select(int_type dbindex); 390 | 391 | // Move the key from the currently selected DB to the DB having as index 392 | // dbindex. Throws if key was already in the db at dbindex or not found in 393 | // currently selected db. 394 | 395 | void move(const string_type & key, int_type dbindex); 396 | 397 | // Remove all the keys of the currently selected DB 398 | 399 | void flushdb(); 400 | 401 | // Remove all the keys from all the databases 402 | 403 | void flushall(); 404 | 405 | // 406 | // Sorting 407 | // Just go read http://code.google.com/p/redis/wiki/SortCommand 408 | // 409 | 410 | enum sort_order 411 | { 412 | sort_order_ascending, 413 | sort_order_descending 414 | }; 415 | 416 | int_type sort(const string_type & key, 417 | string_vector & out, 418 | sort_order order = sort_order_ascending, 419 | bool lexicographically = false); 420 | 421 | int_type sort(const string_type & key, 422 | string_vector & out, 423 | int_type limit_start, 424 | int_type limit_end, 425 | sort_order order = sort_order_ascending, 426 | bool lexicographically = false); 427 | 428 | int_type sort(const string_type & key, 429 | string_vector & out, 430 | const string_type & by_pattern, 431 | int_type limit_start, 432 | int_type limit_end, 433 | const string_vector & get_patterns, 434 | sort_order order = sort_order_ascending, 435 | bool lexicographically = false); 436 | 437 | // 438 | // Persistence control commands 439 | // 440 | 441 | // Synchronously save the DB on disk 442 | 443 | void save(); 444 | 445 | // Asynchronously save the DB on disk 446 | 447 | void bgsave(); 448 | 449 | // Return the UNIX time stamp of the last successfully saving of the 450 | // dataset on disk 451 | 452 | time_t lastsave(); 453 | 454 | // Synchronously save the DB on disk, then shutdown the server. This 455 | // object's connection to the server will be lost on success. Otherwise, 456 | // redis_error is raised. Thus, on success, you should delete or otherwise 457 | // no longer use the object. 458 | 459 | void shutdown(); 460 | 461 | // 462 | // Remote server control commands 463 | // 464 | 465 | // Provide information and statistics about the server 466 | 467 | void info(server_info & out); 468 | 469 | private: 470 | client(const client &); 471 | client & operator=(const client &); 472 | 473 | void send_(const std::string &); 474 | void recv_ok_reply_(); 475 | void recv_int_ok_reply_(); 476 | std::string recv_single_line_reply_(); 477 | int_type recv_bulk_reply_(char prefix); 478 | std::string recv_bulk_reply_(); 479 | int_type recv_multi_bulk_reply_(string_vector & out); 480 | int_type recv_multi_bulk_reply_(string_set & out); 481 | int_type recv_int_reply_(); 482 | 483 | private: 484 | int socket_; 485 | }; 486 | } 487 | 488 | #endif 489 | -------------------------------------------------------------------------------- /test_client.cpp: -------------------------------------------------------------------------------- 1 | #include "redisclient.h" 2 | 3 | #include 4 | 5 | using namespace std; 6 | 7 | #define ASSERT_EQUAL(x,y) assert_equal(x, y, __LINE__) 8 | #define ASSERT_NOT_EQUAL(x,y) assert_not_equal(x, y, __LINE__) 9 | #define ASSERT_GT(x,y) assert_gt(x, y, __LINE__) 10 | 11 | template 12 | void assert_equal(const T & actual, const T & expected, int lineno) 13 | { 14 | #ifndef NDEBUG 15 | cerr << "assert_equal('" << expected << "', '" << actual << "')" << endl; 16 | #endif 17 | 18 | if (expected != actual) 19 | { 20 | cerr << "expected '" << expected << "' got '" << actual << "'" << endl 21 | << "failing test called from line " << lineno << endl; 22 | 23 | exit(1); 24 | } 25 | 26 | #ifndef NDEBUG 27 | cerr << "... OK" << endl; 28 | #endif 29 | } 30 | 31 | template 32 | void assert_not_equal(const T & a, const T & b, int lineno) 33 | { 34 | if (a == b) 35 | { 36 | cerr << "expected inequality" << endl 37 | << "failing test called from line " << lineno << endl; 38 | 39 | exit(1); 40 | } 41 | } 42 | 43 | template 44 | void assert_gt(const T & a, const T & b, int lineno) 45 | { 46 | #ifndef NDEBUG 47 | cerr << "assert_gt('" << a << "', '" << b << "')" << endl; 48 | #endif 49 | 50 | if (a <= b) 51 | { 52 | cerr << "expected '" << a << "' > '" << b << "'" << endl 53 | << "failing test called from line " << lineno << endl; 54 | 55 | exit(1); 56 | } 57 | 58 | #ifndef NDEBUG 59 | cerr << "... OK" << endl; 60 | #endif 61 | } 62 | 63 | void test(const string & name) 64 | { 65 | #ifndef NDEBUG 66 | cerr << "------------------------------" << endl 67 | << "starting test: " << name << endl; 68 | #else 69 | (void) name; 70 | #endif 71 | } 72 | 73 | int main() 74 | { 75 | try 76 | { 77 | redis::client c; 78 | 79 | // Test on high number databases 80 | 81 | c.select(14); 82 | c.flushdb(); 83 | 84 | c.select(15); 85 | c.flushdb(); 86 | 87 | string foo("foo"), bar("bar"), baz("baz"), buz("buz"), goo("goo"); 88 | 89 | test("auth"); 90 | { 91 | // TODO ... needs a conf for redis-server 92 | } 93 | 94 | test("info"); 95 | { 96 | // doesn't throw? then, has valid numbers and known info-keys. 97 | redis::server_info info; 98 | c.info(info); 99 | } 100 | 101 | test("set, get"); 102 | { 103 | c.set(foo, bar); 104 | ASSERT_EQUAL(c.get(foo), bar); 105 | } 106 | 107 | test("getset"); 108 | { 109 | ASSERT_EQUAL(c.getset(foo, baz), bar); 110 | ASSERT_EQUAL(c.get(foo), baz); 111 | } 112 | 113 | test("mget"); 114 | { 115 | string x_val("hello"), y_val("world"); 116 | c.set("x", x_val); 117 | c.set("y", y_val); 118 | redis::client::string_vector keys; 119 | keys.push_back("x"); 120 | keys.push_back("y"); 121 | redis::client::string_vector vals; 122 | c.mget(keys, vals); 123 | ASSERT_EQUAL(vals.size(), size_t(2)); 124 | ASSERT_EQUAL(vals[0], x_val); 125 | ASSERT_EQUAL(vals[1], y_val); 126 | } 127 | 128 | test("setnx"); 129 | { 130 | ASSERT_EQUAL(c.setnx(foo, bar), false); 131 | ASSERT_EQUAL(c.setnx(buz, baz), true); 132 | ASSERT_EQUAL(c.get(buz), baz); 133 | } 134 | 135 | test("incr"); 136 | { 137 | ASSERT_EQUAL(c.incr("goo"), 1L);test("nonexistent (0) -> 1"); 138 | ASSERT_EQUAL(c.incr("goo"), 2L);test("1->2"); 139 | } 140 | 141 | test("decr"); 142 | { 143 | ASSERT_EQUAL(c.decr("goo"), 1L);test("2->1"); 144 | ASSERT_EQUAL(c.decr("goo"), 0L);test("1->0"); 145 | } 146 | 147 | test("incrby"); 148 | { 149 | ASSERT_EQUAL(c.incrby("goo", 3), 3L);test("0->3"); 150 | ASSERT_EQUAL(c.incrby("goo", 2), 5L);test("3->5"); 151 | } 152 | 153 | test("exists"); 154 | { 155 | ASSERT_EQUAL(c.exists("goo"), true); 156 | } 157 | 158 | test("del"); 159 | { 160 | c.del("goo"); 161 | ASSERT_EQUAL(c.exists("goo"), false); 162 | } 163 | 164 | test("type (basic)"); 165 | { 166 | ASSERT_EQUAL(c.type(goo), redis::client::datatype_none);test("we deleted it"); 167 | c.set(goo, "redis"); 168 | ASSERT_EQUAL(c.type(goo), redis::client::datatype_string); 169 | } 170 | 171 | test("keys"); 172 | { 173 | redis::client::string_vector keys; 174 | ASSERT_EQUAL(c.keys("*oo", keys), 2L); 175 | ASSERT_EQUAL(keys.size(), 2UL); 176 | ASSERT_EQUAL(keys[0], foo); 177 | ASSERT_EQUAL(keys[1], goo); 178 | } 179 | 180 | test("randomkey"); 181 | { 182 | ASSERT_GT(c.randomkey().size(), 0UL); 183 | } 184 | 185 | test("rename"); 186 | { 187 | ASSERT_EQUAL(c.exists("foo"), true); 188 | ASSERT_EQUAL(c.exists("doo"), false); 189 | c.rename("foo", "doo"); 190 | ASSERT_EQUAL(c.exists("foo"), false); 191 | ASSERT_EQUAL(c.exists("doo"), true); 192 | } 193 | 194 | test("renamenx"); 195 | { 196 | ASSERT_EQUAL(c.exists("doo"), true); 197 | ASSERT_EQUAL(c.exists("foo"), false); 198 | ASSERT_EQUAL(c.renamenx("doo", "foo"), true); 199 | ASSERT_EQUAL(c.exists("doo"), false); 200 | ASSERT_EQUAL(c.exists("foo"), true); 201 | ASSERT_EQUAL(c.renamenx("goo", "foo"), false); 202 | ASSERT_EQUAL(c.exists("foo"), true); 203 | ASSERT_EQUAL(c.exists("goo"), true); 204 | } 205 | 206 | test("dbsize"); 207 | { 208 | ASSERT_GT(c.dbsize(), 0L); 209 | } 210 | 211 | test("expire"); 212 | { 213 | c.expire("goo", 1); 214 | #ifndef NDEBUG 215 | cerr << "please wait a few seconds.." << endl; 216 | #endif 217 | sleep(2); 218 | ASSERT_EQUAL(c.exists("goo"), false); 219 | } 220 | 221 | test("rpush"); 222 | { 223 | ASSERT_EQUAL(c.exists("list1"), false); 224 | c.rpush("list1", "val1"); 225 | ASSERT_EQUAL(c.llen("list1"), 1L); 226 | ASSERT_EQUAL(c.type("list1"), redis::client::datatype_list); 227 | c.rpush("list1", "val2"); 228 | ASSERT_EQUAL(c.llen("list1"), 2L); 229 | ASSERT_EQUAL(c.lindex("list1", 0), string("val1")); 230 | ASSERT_EQUAL(c.lindex("list1", 1), string("val2")); 231 | } 232 | 233 | test("lpush"); 234 | { 235 | c.del("list1"); 236 | ASSERT_EQUAL(c.exists("list1"), false); 237 | c.lpush("list1", "val1"); 238 | ASSERT_EQUAL(c.type("list1"), redis::client::datatype_list); 239 | ASSERT_EQUAL(c.llen("list1"), 1L); 240 | c.lpush("list1", "val2"); 241 | ASSERT_EQUAL(c.llen("list1"), 2L); 242 | ASSERT_EQUAL(c.lindex("list1", 0), string("val2")); 243 | ASSERT_EQUAL(c.lindex("list1", 1), string("val1")); 244 | } 245 | 246 | test("llen"); 247 | { 248 | c.del("list1"); 249 | ASSERT_EQUAL(c.exists("list1"), false); 250 | ASSERT_EQUAL(c.llen("list1"), 0L); 251 | c.lpush("list1", "x"); 252 | ASSERT_EQUAL(c.llen("list1"), 1L); 253 | c.lpush("list1", "y"); 254 | ASSERT_EQUAL(c.llen("list1"), 2L); 255 | } 256 | 257 | test("lrange"); 258 | { 259 | ASSERT_EQUAL(c.exists("list1"), true); 260 | ASSERT_EQUAL(c.llen("list1"), 2L); 261 | redis::client::string_vector vals; 262 | ASSERT_EQUAL(c.lrange("list1", 0, -1, vals), 2L); 263 | ASSERT_EQUAL(vals.size(), 2UL); 264 | ASSERT_EQUAL(vals[0], string("y")); 265 | ASSERT_EQUAL(vals[1], string("x")); 266 | } 267 | 268 | test("lrange with subset of full list"); 269 | { 270 | ASSERT_EQUAL(c.exists("list1"), true); 271 | ASSERT_EQUAL(c.llen("list1"), 2L); 272 | redis::client::string_vector vals; 273 | ASSERT_EQUAL(c.lrange("list1", 0, 1, vals), 2L); // inclusive, so entire list 274 | ASSERT_EQUAL(vals.size(), 2UL); 275 | ASSERT_EQUAL(vals[0], string("y")); 276 | ASSERT_EQUAL(vals[1], string("x")); 277 | 278 | redis::client::string_vector vals2; 279 | ASSERT_EQUAL(c.lrange("list1", 0, 0, vals2), 1L); // inclusive, so first item 280 | ASSERT_EQUAL(vals2.size(), 1UL); 281 | ASSERT_EQUAL(vals2[0], string("y")); 282 | 283 | redis::client::string_vector vals3; 284 | ASSERT_EQUAL(c.lrange("list1", -1, -1, vals3), 1L); // inclusive, so first item 285 | ASSERT_EQUAL(vals3.size(), 1UL); 286 | ASSERT_EQUAL(vals3[0], string("x")); 287 | } 288 | 289 | test("get_list"); 290 | { 291 | ASSERT_EQUAL(c.exists("list1"), true); 292 | ASSERT_EQUAL(c.llen("list1"), 2L); 293 | redis::client::string_vector vals; 294 | ASSERT_EQUAL(c.get_list("list1", vals), 2L); 295 | ASSERT_EQUAL(vals.size(), 2UL); 296 | ASSERT_EQUAL(vals[0], string("y")); 297 | ASSERT_EQUAL(vals[1], string("x")); 298 | } 299 | 300 | test("ltrim"); 301 | { 302 | ASSERT_EQUAL(c.exists("list1"), true); 303 | ASSERT_EQUAL(c.llen("list1"), 2L); 304 | c.ltrim("list1", 0, 0); 305 | ASSERT_EQUAL(c.exists("list1"), true); 306 | ASSERT_EQUAL(c.llen("list1"), 1L); 307 | redis::client::string_vector vals; 308 | ASSERT_EQUAL(c.get_list("list1", vals), 1L); 309 | ASSERT_EQUAL(vals[0], string("y")); 310 | } 311 | 312 | test("lindex"); 313 | { 314 | ASSERT_EQUAL(c.lindex("list1", 0), string("y")); 315 | c.rpush("list1", "x"); 316 | ASSERT_EQUAL(c.llen("list1"), 2L); 317 | ASSERT_EQUAL(c.lindex("list1", -1), string("x")); 318 | ASSERT_EQUAL(c.lindex("list1", 1), string("x")); 319 | } 320 | 321 | test("lset"); 322 | { 323 | c.lset("list1", 1, "z"); 324 | ASSERT_EQUAL(c.lindex("list1", 1), string("z")); 325 | ASSERT_EQUAL(c.llen("list1"), 2L); 326 | } 327 | 328 | test("lrem"); 329 | { 330 | c.lrem("list1", 1, "z"); 331 | ASSERT_EQUAL(c.llen("list1"), 1L); 332 | ASSERT_EQUAL(c.lindex("list1", 0), string("y")); 333 | 334 | // list1 = [ y ] 335 | ASSERT_EQUAL(c.lrem("list1", 0, "q"), 0L); 336 | 337 | c.rpush("list1", "z"); 338 | c.rpush("list1", "z"); 339 | c.rpush("list1", "z"); 340 | c.rpush("list1", "a"); 341 | // list1 = [ y, z, z, z, a ] 342 | ASSERT_EQUAL(c.lrem("list1", 2, "z"), 2L); 343 | // list1 = [ y, z, a ] 344 | ASSERT_EQUAL(c.llen("list1"), 3L); 345 | ASSERT_EQUAL(c.lindex("list1", 0), string("y")); 346 | ASSERT_EQUAL(c.lindex("list1", 1), string("z")); 347 | ASSERT_EQUAL(c.lindex("list1", 2), string("a")); 348 | 349 | c.rpush("list1", "z"); 350 | // list1 = [ y, z, a, z ] 351 | ASSERT_EQUAL(c.lrem("list1", -1, "z"), 1L); // <0 => rm R to L 352 | // list1 = [ y, z, a ] 353 | ASSERT_EQUAL(c.llen("list1"), 3L); 354 | ASSERT_EQUAL(c.lindex("list1", 0), string("y")); 355 | ASSERT_EQUAL(c.lindex("list1", 1), string("z")); 356 | ASSERT_EQUAL(c.lindex("list1", 2), string("a")); 357 | 358 | // list1 = [ y, z, a ] 359 | // try to remove 5 'a's but there's only 1 ... no problem. 360 | ASSERT_EQUAL(c.lrem("list1", 5, "a"), 1L); 361 | // list1 = [ y, z ] 362 | ASSERT_EQUAL(c.llen("list1"), 2L); 363 | ASSERT_EQUAL(c.lindex("list1", 0), string("y")); 364 | ASSERT_EQUAL(c.lindex("list1", 1), string("z")); 365 | } 366 | 367 | test("lrem_exact"); 368 | { 369 | // list1 = [ y, z ] 370 | 371 | // try to remove 5 'z's but there's only 1 ... now it's a problem. 372 | 373 | bool threw = false; 374 | 375 | try 376 | { 377 | c.lrem_exact("list1", 5, "z"); 378 | } 379 | catch (redis::value_error & e) 380 | { 381 | threw = true; 382 | } 383 | 384 | ASSERT_EQUAL(threw, true); 385 | 386 | // This DOES remove the one 'z' though 387 | // list1 = [ y ] 388 | 389 | ASSERT_EQUAL(c.llen("list1"), 1L); 390 | ASSERT_EQUAL(c.lindex("list1", 0), string("y")); 391 | } 392 | 393 | test("lpop"); 394 | { 395 | ASSERT_EQUAL(c.lpop("list1"), string("y")); 396 | // list1 = [] 397 | ASSERT_EQUAL(c.lpop("list1"), redis::client::missing_value); 398 | } 399 | 400 | test("rpop"); 401 | { 402 | c.rpush("list1", "hello"); 403 | c.rpush("list1", "world"); 404 | ASSERT_EQUAL(c.rpop("list1"), string("world")); 405 | ASSERT_EQUAL(c.rpop("list1"), string("hello")); 406 | ASSERT_EQUAL(c.lpop("list1"), redis::client::missing_value); 407 | } 408 | 409 | test("sadd"); 410 | { 411 | c.sadd("set1", "sval1"); 412 | ASSERT_EQUAL(c.exists("set1"), true); 413 | ASSERT_EQUAL(c.type("set1"), redis::client::datatype_set); 414 | ASSERT_EQUAL(c.sismember("set1", "sval1"), true); 415 | } 416 | 417 | test("srem"); 418 | { 419 | c.srem("set1", "sval1"); 420 | ASSERT_EQUAL(c.exists("set1"), true); 421 | ASSERT_EQUAL(c.type("set1"), redis::client::datatype_set); 422 | ASSERT_EQUAL(c.sismember("set1", "sval1"), false); 423 | } 424 | 425 | test("smove"); 426 | { 427 | c.sadd("set1", "hi"); 428 | // set1 = { hi } 429 | ASSERT_EQUAL(c.exists("set2"), false); 430 | c.smove("set1", "set2", "hi"); 431 | ASSERT_EQUAL(c.sismember("set1", "hi"), false); 432 | ASSERT_EQUAL(c.sismember("set2", "hi"), true); 433 | } 434 | 435 | test("scard"); 436 | { 437 | ASSERT_EQUAL(c.scard("set1"), 0L); 438 | ASSERT_EQUAL(c.scard("set2"), 1L); 439 | } 440 | 441 | test("sismember"); 442 | { 443 | // see above 444 | } 445 | 446 | test("smembers"); 447 | { 448 | c.sadd("set2", "bye"); 449 | redis::client::string_set members; 450 | ASSERT_EQUAL(c.smembers("set2", members), 2L); 451 | ASSERT_EQUAL(members.size(), 2UL); 452 | ASSERT_NOT_EQUAL(members.find("hi"), members.end()); 453 | ASSERT_NOT_EQUAL(members.find("bye"), members.end()); 454 | } 455 | 456 | test("sinter"); 457 | { 458 | c.sadd("set3", "bye"); 459 | c.sadd("set3", "bye2"); 460 | redis::client::string_vector keys; 461 | keys.push_back("set2"); 462 | keys.push_back("set3"); 463 | redis::client::string_set intersection; 464 | ASSERT_EQUAL(c.sinter(keys, intersection), 1L); 465 | ASSERT_EQUAL(intersection.size(), 1UL); 466 | ASSERT_NOT_EQUAL(intersection.find("bye"), intersection.end()); 467 | } 468 | 469 | test("sinterstore"); 470 | { 471 | c.sadd("seta", "1"); 472 | c.sadd("seta", "2"); 473 | c.sadd("seta", "3"); 474 | 475 | c.sadd("setb", "2"); 476 | c.sadd("setb", "3"); 477 | c.sadd("setb", "4"); 478 | 479 | redis::client::string_vector keys; 480 | keys.push_back("seta"); 481 | keys.push_back("setb"); 482 | 483 | ASSERT_EQUAL(c.sinterstore("setc", keys), 2L); 484 | 485 | redis::client::string_set members; 486 | ASSERT_EQUAL(c.smembers("setc", members), 2L); 487 | ASSERT_EQUAL(members.size(), 2UL); 488 | ASSERT_NOT_EQUAL(members.find("2"), members.end()); 489 | ASSERT_NOT_EQUAL(members.find("3"), members.end()); 490 | } 491 | 492 | test("sunion"); 493 | { 494 | c.sadd("setd", "1"); 495 | c.sadd("sete", "2"); 496 | redis::client::string_vector keys; 497 | keys.push_back("setd"); 498 | keys.push_back("sete"); 499 | redis::client::string_set a_union; 500 | ASSERT_EQUAL(c.sunion(keys, a_union), 2L); 501 | ASSERT_EQUAL(a_union.size(), 2UL); 502 | ASSERT_NOT_EQUAL(a_union.find("1"), a_union.end()); 503 | ASSERT_NOT_EQUAL(a_union.find("2"), a_union.end()); 504 | } 505 | 506 | test("sunionstore"); 507 | { 508 | c.sadd("setf", "1"); 509 | c.sadd("setg", "2"); 510 | 511 | redis::client::string_vector keys; 512 | keys.push_back("setf"); 513 | keys.push_back("setg"); 514 | 515 | ASSERT_EQUAL(c.sunionstore("seth", keys), 2L); 516 | 517 | redis::client::string_set members; 518 | ASSERT_EQUAL(c.smembers("seth", members), 2L); 519 | ASSERT_EQUAL(members.size(), 2UL); 520 | ASSERT_NOT_EQUAL(members.find("1"), members.end()); 521 | ASSERT_NOT_EQUAL(members.find("2"), members.end()); 522 | } 523 | 524 | test("move"); 525 | { 526 | c.select(14); 527 | ASSERT_EQUAL(c.exists("ttt"), false); 528 | c.select(15); 529 | c.set("ttt", "uuu"); 530 | c.move("ttt", 14); 531 | c.select(14); 532 | ASSERT_EQUAL(c.exists("ttt"), true); 533 | c.select(15); 534 | ASSERT_EQUAL(c.exists("ttt"), false); 535 | } 536 | 537 | test("move should fail since key exists already"); 538 | { 539 | c.select(14); 540 | c.set("ttt", "xxx"); 541 | c.select(15); 542 | c.set("ttt", "uuu"); 543 | 544 | bool threw = false; 545 | 546 | try 547 | { 548 | c.move("ttt", 14); 549 | } 550 | catch (redis::protocol_error & e) 551 | { 552 | threw = true; 553 | } 554 | 555 | ASSERT_EQUAL(threw, true); 556 | 557 | c.select(14); 558 | ASSERT_EQUAL(c.exists("ttt"), true); 559 | c.select(15); 560 | ASSERT_EQUAL(c.exists("ttt"), true); 561 | } 562 | 563 | test("sort ascending"); 564 | { 565 | c.sadd("sort1", "3"); 566 | c.sadd("sort1", "2"); 567 | c.sadd("sort1", "1"); 568 | 569 | redis::client::string_vector sorted; 570 | ASSERT_EQUAL(c.sort("sort1", sorted), 3L); 571 | ASSERT_EQUAL(sorted.size(), 3UL); 572 | ASSERT_EQUAL(sorted[0], string("1")); 573 | ASSERT_EQUAL(sorted[1], string("2")); 574 | ASSERT_EQUAL(sorted[2], string("3")); 575 | } 576 | 577 | test("sort descending"); 578 | { 579 | redis::client::string_vector sorted; 580 | ASSERT_EQUAL(c.sort("sort1", sorted, redis::client::sort_order_descending), 3L); 581 | ASSERT_EQUAL(sorted.size(), 3UL); 582 | ASSERT_EQUAL(sorted[0], string("3")); 583 | ASSERT_EQUAL(sorted[1], string("2")); 584 | ASSERT_EQUAL(sorted[2], string("1")); 585 | } 586 | 587 | test("sort with limit"); 588 | { 589 | // TODO 590 | } 591 | 592 | test("sort lexicographically"); 593 | { 594 | // TODO 595 | } 596 | 597 | test("sort with pattern and weights"); 598 | { 599 | // TODO 600 | } 601 | 602 | test("save"); 603 | { 604 | c.save(); 605 | } 606 | 607 | test("bgsave"); 608 | { 609 | c.bgsave(); 610 | } 611 | 612 | test("lastsave"); 613 | { 614 | ASSERT_GT(c.lastsave(), 0L); 615 | } 616 | 617 | test("shutdown"); 618 | { 619 | // You can test this if you really want to ... 620 | // c.shutdown(); 621 | } 622 | } 623 | catch (redis::redis_error & e) 624 | { 625 | cerr << "got exception: " << string(e) << endl << "FAIL" << endl; 626 | return 1; 627 | } 628 | 629 | cout << endl << "testing completed successfully" << endl; 630 | return 0; 631 | } 632 | -------------------------------------------------------------------------------- /redisclient.cpp: -------------------------------------------------------------------------------- 1 | /* redisclient.cpp -- a C++ client library for redis. 2 | * 3 | * Copyright (c) 2009, Brian Hammond 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include "redisclient.h" 32 | #include "anet.h" 33 | 34 | #include 35 | 36 | #ifndef NDEBUG 37 | #include 38 | #include 39 | #include 40 | #endif 41 | 42 | #include 43 | #include 44 | 45 | #include 46 | #include 47 | 48 | using namespace std; 49 | 50 | namespace 51 | { 52 | const string whitespace(" \f\n\r\t\v"); 53 | const string CRLF("\r\n"); 54 | 55 | // Modifies in-place. 56 | 57 | inline string & rtrim(string & str, const string & ws = whitespace) 58 | { 59 | string::size_type pos = str.find_last_not_of(ws); 60 | str.erase(pos + 1); 61 | return str; 62 | } 63 | 64 | vector::size_type split(const string & str, char delim, vector & elems) 65 | { 66 | stringstream ss(str); 67 | string item; 68 | vector::size_type n = 0; 69 | while (getline(ss, item, delim)) 70 | { 71 | elems.push_back(item); 72 | ++n; 73 | } 74 | return n; 75 | } 76 | 77 | inline void split_lines(const string & str, vector & elems) 78 | { 79 | split(str, '\n', elems); 80 | for (vector::iterator it = elems.begin(); it != elems.end(); ++it) 81 | rtrim(*it); 82 | } 83 | 84 | #ifndef NDEBUG 85 | 86 | void output_proto_debug(const string & data, bool is_received = true) 87 | { 88 | string escaped_data(data); 89 | size_t pos; 90 | while ((pos = escaped_data.find("\n")) != string::npos) 91 | escaped_data.replace(pos, 1, "\\n"); 92 | while ((pos = escaped_data.find("\r")) != string::npos) 93 | escaped_data.replace(pos, 1, "\\r"); 94 | 95 | cerr 96 | << time(NULL) << ": " 97 | << (is_received ? "RECV '" : "SEND '") 98 | << escaped_data 99 | << "'" 100 | << endl; 101 | } 102 | 103 | #endif 104 | 105 | class makecmd 106 | { 107 | public: 108 | explicit makecmd(const string & initial, bool finalize = false) 109 | { 110 | buffer_ << initial; 111 | if (!finalize) 112 | buffer_ << " "; 113 | } 114 | 115 | template 116 | makecmd & operator<<(T const & datum) 117 | { 118 | buffer_ << datum; 119 | return *this; 120 | } 121 | 122 | template 123 | makecmd & operator<<(const vector & data) 124 | { 125 | size_t n = data.size(); 126 | for (size_t i = 0; i < n; ++i) 127 | { 128 | buffer_ << data[i]; 129 | if (i < n - 1) 130 | buffer_ << " "; 131 | } 132 | return *this; 133 | } 134 | 135 | operator std::string () 136 | { 137 | buffer_ << CRLF; 138 | return buffer_.str(); 139 | } 140 | 141 | private: 142 | ostringstream buffer_; 143 | }; 144 | 145 | // Reads N bytes from given blocking socket. 146 | 147 | string read_n(int socket, ssize_t n) 148 | { 149 | char * buffer = new char[n + 1]; 150 | buffer[n] = '\0'; 151 | 152 | char * bp = buffer; 153 | ssize_t bytes_read = 0; 154 | 155 | while (bytes_read != n) 156 | { 157 | ssize_t bytes_received = 0; 158 | do bytes_received = recv(socket, bp, n - (bp - buffer), 0); 159 | while (bytes_received < 0 && errno == EINTR); 160 | 161 | if (bytes_received == 0) 162 | throw redis::connection_error("connection was closed"); 163 | 164 | bytes_read += bytes_received; 165 | bp += bytes_received; 166 | } 167 | 168 | string str(buffer); 169 | delete [] buffer; 170 | return str; 171 | } 172 | 173 | // Reads a single line of character data from the given blocking socket. 174 | // Returns the line that was read, not including EOL delimiter(s). Both LF 175 | // ('\n') and CRLF ("\r\n") delimiters are supported. If there was an I/O 176 | // error reading from the socket, connection_error is raised. If max_size 177 | // bytes are read before finding an EOL delimiter, a blank string is 178 | // returned. 179 | 180 | string read_line(int socket, ssize_t max_size = 2048) 181 | { 182 | assert(socket > 0); 183 | assert(max_size > 0); 184 | 185 | ostringstream oss; 186 | 187 | enum { buffer_size = 64 }; 188 | char buffer[buffer_size]; 189 | memset(buffer, 0, buffer_size); 190 | 191 | ssize_t total_bytes_read = 0; 192 | bool found_delimiter = false; 193 | 194 | while (total_bytes_read < max_size && !found_delimiter) 195 | { 196 | // Peek at what's available. 197 | 198 | ssize_t bytes_received = 0; 199 | do bytes_received = recv(socket, buffer, buffer_size, MSG_PEEK); 200 | while (bytes_received < 0 && errno == EINTR); 201 | 202 | if (bytes_received == 0) 203 | throw redis::connection_error("connection was closed"); 204 | 205 | // Some data is available; Length might be < buffer_size. 206 | // Look for newline in whatever was read though. 207 | 208 | char * eol = static_cast(memchr(buffer, '\n', bytes_received)); 209 | 210 | // If found, write data from the buffer to the output string. 211 | // Else, write the entire buffer and continue reading more data. 212 | 213 | ssize_t to_read = bytes_received; 214 | 215 | if (eol) 216 | { 217 | to_read = eol - buffer + 1; 218 | oss.write(buffer, to_read); 219 | found_delimiter = true; 220 | } 221 | else 222 | oss.write(buffer, bytes_received); 223 | 224 | // Now read from the socket to remove the peeked data from the socket's 225 | // read buffer. This will not block since we've peeked already and know 226 | // there's data waiting. It might fail if we were interrupted however. 227 | 228 | do bytes_received = recv(socket, buffer, to_read, 0); 229 | while (bytes_received < 0 && errno == EINTR); 230 | } 231 | 232 | // Construct final line string. Remove trailing CRLF-based whitespace. 233 | 234 | string line = oss.str(); 235 | return rtrim(line, CRLF); 236 | } 237 | 238 | template 239 | T value_from_string(const string & data) 240 | { 241 | T value; 242 | 243 | istringstream iss(data); 244 | iss >> value; 245 | if (iss.fail()) 246 | throw redis::value_error("invalid number"); 247 | 248 | return value; 249 | } 250 | 251 | const string status_reply_ok("OK"); 252 | const string prefix_status_reply_error("-ERR "); 253 | const char prefix_status_reply_value = '+'; 254 | const char prefix_single_bulk_reply = '$'; 255 | const char prefix_multi_bulk_reply = '*'; 256 | const char prefix_int_reply = ':'; 257 | 258 | const string server_info_key_version = "redis_version"; 259 | const string server_info_key_bgsave_in_progress = "bgsave_in_progress"; 260 | const string server_info_key_connected_clients = "connected_clients"; 261 | const string server_info_key_connected_slaves = "connected_slaves"; 262 | const string server_info_key_used_memory = "used_memory"; 263 | const string server_info_key_changes_since_last_save = "changes_since_last_save"; 264 | const string server_info_key_last_save_time = "last_save_time"; 265 | const string server_info_key_total_connections_received = "total_connections_received"; 266 | const string server_info_key_total_commands_processed = "total_commands_processed"; 267 | const string server_info_key_uptime_in_seconds = "uptime_in_seconds"; 268 | const string server_info_key_uptime_in_days = "uptime_in_days"; 269 | const string server_info_key_role = "role"; 270 | 271 | const string server_info_value_role_master = "master"; 272 | const string server_info_value_role_slave = "slave"; 273 | } 274 | 275 | namespace redis 276 | { 277 | redis_error::redis_error(const string & err) : err_(err) 278 | { 279 | } 280 | 281 | redis_error::operator std::string () 282 | { 283 | return err_; 284 | } 285 | 286 | redis_error::operator const std::string () const 287 | { 288 | return err_; 289 | } 290 | 291 | connection_error::connection_error(const string & err) : redis_error(err) 292 | { 293 | } 294 | 295 | protocol_error::protocol_error(const string & err) : redis_error(err) 296 | { 297 | } 298 | 299 | key_error::key_error(const string & err) : redis_error(err) 300 | { 301 | } 302 | 303 | value_error::value_error(const string & err) : redis_error(err) 304 | { 305 | } 306 | 307 | client::string_type client::missing_value("**nonexistent-key**"); 308 | 309 | client::client(const string_type & host, unsigned int port) 310 | { 311 | char err[ANET_ERR_LEN]; 312 | socket_ = anetTcpConnect(err, const_cast(host.c_str()), port); 313 | if (socket_ == ANET_ERR) 314 | throw connection_error(err); 315 | anetTcpNoDelay(NULL, socket_); 316 | } 317 | 318 | client::~client() 319 | { 320 | if (socket_ != ANET_ERR) 321 | close(socket_); 322 | } 323 | 324 | void client::auth(const client::string_type & pass) 325 | { 326 | send_(makecmd("AUTH") << pass); 327 | recv_ok_reply_(); 328 | } 329 | 330 | void client::set(const client::string_type & key, 331 | const client::string_type & value) 332 | { 333 | send_(makecmd("SET") << key << ' ' << value.size() << CRLF << value); 334 | recv_ok_reply_(); 335 | } 336 | 337 | client::string_type client::get(const client::string_type & key) 338 | { 339 | send_(makecmd("GET") << key); 340 | return recv_bulk_reply_(); 341 | } 342 | 343 | client::string_type client::getset(const client::string_type & key, 344 | const client::string_type & value) 345 | { 346 | send_(makecmd("GETSET") << key << ' ' << value.size() << CRLF << value); 347 | return recv_bulk_reply_(); 348 | } 349 | 350 | void client::mget(const client::string_vector & keys, string_vector & out) 351 | { 352 | send_(makecmd("MGET") << keys); 353 | recv_multi_bulk_reply_(out); 354 | } 355 | 356 | bool client::setnx(const client::string_type & key, 357 | const client::string_type & value) 358 | { 359 | send_(makecmd("SETNX") << key << ' ' << value.size() << CRLF << value); 360 | return recv_int_reply_() == 1; 361 | } 362 | 363 | client::int_type client::incr(const client::string_type & key) 364 | { 365 | send_(makecmd("INCR") << key); 366 | return recv_int_reply_(); 367 | } 368 | 369 | client::int_type client::incrby(const client::string_type & key, 370 | client::int_type by) 371 | { 372 | send_(makecmd("INCRBY") << key << ' ' << by); 373 | return recv_int_reply_(); 374 | } 375 | 376 | client::int_type client::decr(const client::string_type & key) 377 | { 378 | send_(makecmd("DECR") << key); 379 | return recv_int_reply_(); 380 | } 381 | 382 | client::int_type client::decrby(const client::string_type & key, 383 | client::int_type by) 384 | { 385 | send_(makecmd("DECRBY") << key << ' ' << by); 386 | return recv_int_reply_(); 387 | } 388 | 389 | bool client::exists(const client::string_type & key) 390 | { 391 | send_(makecmd("EXISTS") << key); 392 | return recv_int_reply_() == 1; 393 | } 394 | 395 | void client::del(const client::string_type & key) 396 | { 397 | send_(makecmd("DEL") << key); 398 | recv_int_ok_reply_(); 399 | } 400 | 401 | client::datatype client::type(const client::string_type & key) 402 | { 403 | send_(makecmd("TYPE") << key); 404 | string response = recv_single_line_reply_(); 405 | 406 | if (response == "none") return datatype_none; 407 | if (response == "string") return datatype_string; 408 | if (response == "list") return datatype_list; 409 | if (response == "set") return datatype_set; 410 | 411 | return datatype_none; 412 | } 413 | 414 | client::int_type client::keys(const client::string_type & pattern, 415 | client::string_vector & out) 416 | { 417 | send_(makecmd("KEYS") << pattern); 418 | string resp = recv_bulk_reply_(); 419 | return split(resp, ' ', out); 420 | } 421 | 422 | client::string_type client::randomkey() 423 | { 424 | send_(makecmd("RANDOMKEY", true)); 425 | return recv_single_line_reply_(); 426 | } 427 | 428 | void client::rename(const client::string_type & old_name, 429 | const client::string_type & new_name) 430 | { 431 | send_(makecmd("RENAME") << old_name << ' ' << new_name); 432 | recv_ok_reply_(); 433 | } 434 | 435 | bool client::renamenx(const client::string_type & old_name, 436 | const client::string_type & new_name) 437 | { 438 | send_(makecmd("RENAMENX") << old_name << ' ' << new_name); 439 | return recv_int_reply_() == 1; 440 | } 441 | 442 | client::int_type client::dbsize() 443 | { 444 | send_(makecmd("DBSIZE")); 445 | return recv_int_reply_(); 446 | } 447 | 448 | void client::expire(const string_type & key, unsigned int secs) 449 | { 450 | send_(makecmd("EXPIRE") << key << ' ' << secs); 451 | recv_int_ok_reply_(); 452 | } 453 | 454 | void client::rpush(const client::string_type & key, 455 | const client::string_type & value) 456 | { 457 | send_(makecmd("RPUSH") << key << ' ' << value.length() << CRLF << value); 458 | recv_ok_reply_(); 459 | } 460 | 461 | void client::lpush(const client::string_type & key, 462 | const client::string_type & value) 463 | { 464 | send_(makecmd("LPUSH") << key << ' ' << value.length() << CRLF << value); 465 | recv_ok_reply_(); 466 | } 467 | 468 | client::int_type client::llen(const client::string_type & key) 469 | { 470 | send_(makecmd("LLEN") << key); 471 | return recv_int_reply_(); 472 | } 473 | 474 | client::int_type client::lrange(const client::string_type & key, 475 | client::int_type start, 476 | client::int_type end, 477 | client::string_vector & out) 478 | { 479 | send_(makecmd("LRANGE") << key << ' ' << start << ' ' << end); 480 | return recv_multi_bulk_reply_(out); 481 | } 482 | 483 | void client::ltrim(const client::string_type & key, 484 | client::int_type start, 485 | client::int_type end) 486 | { 487 | send_(makecmd("LTRIM") << key << ' ' << start << ' ' << end); 488 | recv_ok_reply_(); 489 | } 490 | 491 | client::string_type client::lindex(const client::string_type & key, 492 | client::int_type index) 493 | { 494 | send_(makecmd("LINDEX") << key << ' ' << index); 495 | return recv_bulk_reply_(); 496 | } 497 | 498 | void client::lset(const client::string_type & key, 499 | client::int_type index, 500 | const client::string_type & value) 501 | { 502 | send_(makecmd("LSET") << key << ' ' << index << ' ' << value.length() << CRLF << value); 503 | recv_ok_reply_(); 504 | } 505 | 506 | client::int_type client::lrem(const client::string_type & key, 507 | client::int_type count, 508 | const client::string_type & value) 509 | { 510 | send_(makecmd("LREM") << key << ' ' << count << ' ' << value.length() << CRLF << value); 511 | return recv_int_reply_(); 512 | } 513 | 514 | client::string_type client::lpop(const client::string_type & key) 515 | { 516 | send_(makecmd("LPOP") << key); 517 | return recv_bulk_reply_(); 518 | } 519 | 520 | client::string_type client::rpop(const client::string_type & key) 521 | { 522 | send_(makecmd("RPOP") << key); 523 | return recv_bulk_reply_(); 524 | } 525 | 526 | void client::sadd(const client::string_type & key, 527 | const client::string_type & value) 528 | { 529 | send_(makecmd("SADD") << key << ' ' << value.length() << CRLF << value); 530 | recv_int_ok_reply_(); 531 | } 532 | 533 | void client::srem(const client::string_type & key, 534 | const client::string_type & value) 535 | { 536 | send_(makecmd("SREM") << key << ' ' << value.length() << CRLF << value); 537 | recv_int_ok_reply_(); 538 | } 539 | 540 | void client::smove(const client::string_type & srckey, 541 | const client::string_type & dstkey, 542 | const client::string_type & value) 543 | { 544 | send_(makecmd("SMOVE") << srckey << ' ' << dstkey << ' ' << value.length() << CRLF << value); 545 | recv_int_ok_reply_(); 546 | } 547 | 548 | client::int_type client::scard(const client::string_type & key) 549 | { 550 | send_(makecmd("SCARD") << key); 551 | return recv_int_reply_(); 552 | } 553 | 554 | bool client::sismember(const client::string_type & key, 555 | const client::string_type & value) 556 | { 557 | send_(makecmd("SISMEMBER") << key << ' ' << value.length() << CRLF << value); 558 | return recv_int_reply_() == 1; 559 | } 560 | 561 | client::int_type client::sinter(const client::string_vector & keys, client::string_set & out) 562 | { 563 | send_(makecmd("SINTER") << keys); 564 | return recv_multi_bulk_reply_(out); 565 | } 566 | 567 | client::int_type client::sinterstore(const client::string_type & dstkey, 568 | const client::string_vector & keys) 569 | { 570 | send_(makecmd("SINTERSTORE") << dstkey << ' ' << keys); 571 | return recv_int_reply_(); 572 | } 573 | 574 | client::int_type client::sunion(const client::string_vector & keys, 575 | client::string_set & out) 576 | { 577 | send_(makecmd("SUNION") << keys); 578 | return recv_multi_bulk_reply_(out); 579 | } 580 | 581 | client::int_type client::sunionstore(const client::string_type & dstkey, 582 | const client::string_vector & keys) 583 | { 584 | send_(makecmd("SUNIONSTORE") << dstkey << ' ' << keys); 585 | return recv_int_reply_(); 586 | } 587 | 588 | client::int_type client::smembers(const client::string_type & key, 589 | client::string_set & out) 590 | { 591 | send_(makecmd("SMEMBERS") << key); 592 | return recv_multi_bulk_reply_(out); 593 | } 594 | 595 | void client::select(client::int_type dbindex) 596 | { 597 | send_(makecmd("SELECT") << dbindex); 598 | recv_ok_reply_(); 599 | } 600 | 601 | void client::move(const client::string_type & key, 602 | client::int_type dbindex) 603 | { 604 | send_(makecmd("MOVE") << key << ' ' << dbindex); 605 | recv_int_ok_reply_(); 606 | } 607 | 608 | void client::flushdb() 609 | { 610 | send_(makecmd("FLUSHDB", true)); 611 | recv_ok_reply_(); 612 | } 613 | 614 | void client::flushall() 615 | { 616 | send_(makecmd("FLUSHALL", true)); 617 | recv_ok_reply_(); 618 | } 619 | 620 | client::int_type client::sort(const client::string_type & key, 621 | client::string_vector & out, 622 | client::sort_order order, 623 | bool lexicographically) 624 | { 625 | send_(makecmd("SORT") << key 626 | << (order == sort_order_ascending ? " ASC" : " DESC") 627 | << (lexicographically ? " ALPHA" : "")); 628 | 629 | return recv_multi_bulk_reply_(out); 630 | } 631 | 632 | client::int_type client::sort(const client::string_type & key, 633 | client::string_vector & out, 634 | client::int_type limit_start, 635 | client::int_type limit_end, 636 | client::sort_order order, 637 | bool lexicographically) 638 | { 639 | send_(makecmd("SORT") << key 640 | << " LIMIT " << limit_start << ' ' << limit_end 641 | << (order == sort_order_ascending ? " ASC" : " DESC") 642 | << (lexicographically ? " ALPHA" : "")); 643 | 644 | return recv_multi_bulk_reply_(out); 645 | } 646 | 647 | client::int_type client::sort(const client::string_type & key, 648 | client::string_vector & out, 649 | const client::string_type & by_pattern, 650 | client::int_type limit_start, 651 | client::int_type limit_end, 652 | const client::string_vector & get_patterns, 653 | client::sort_order order, 654 | bool lexicographically) 655 | { 656 | makecmd m("SORT"); 657 | 658 | m << key 659 | << " BY " << by_pattern 660 | << " LIMIT " << limit_start << ' ' << limit_end; 661 | 662 | client::string_vector::const_iterator it = get_patterns.begin(); 663 | for ( ; it != get_patterns.end(); ++it) 664 | m << " GET " << *it; 665 | 666 | m << (order == sort_order_ascending ? " ASC" : " DESC") 667 | << (lexicographically ? " ALPHA" : ""); 668 | 669 | send_(m); 670 | 671 | return recv_multi_bulk_reply_(out); 672 | } 673 | 674 | void client::save() 675 | { 676 | send_(makecmd("SAVE", true)); 677 | recv_ok_reply_(); 678 | } 679 | 680 | void client::bgsave() 681 | { 682 | send_(makecmd("BGSAVE", true)); 683 | recv_ok_reply_(); 684 | } 685 | 686 | time_t client::lastsave() 687 | { 688 | send_(makecmd("LASTSAVE", true)); 689 | return recv_int_reply_(); 690 | } 691 | 692 | void client::shutdown() 693 | { 694 | send_(makecmd("SHUTDOWN", true)); 695 | 696 | // we expected to get a connection_error as redis closes the connection on shutdown command. 697 | 698 | try 699 | { 700 | recv_ok_reply_(); 701 | } 702 | catch (connection_error & e) 703 | { 704 | } 705 | } 706 | 707 | void client::info(server_info & out) 708 | { 709 | send_(makecmd("INFO", true)); 710 | string response = recv_bulk_reply_(); 711 | 712 | if (response.empty()) 713 | throw protocol_error("empty"); 714 | 715 | string_vector lines; 716 | split_lines(response, lines); 717 | if (lines.empty()) 718 | throw protocol_error("empty line for info"); 719 | 720 | for (string_vector::const_iterator it = lines.begin(); 721 | it != lines.end(); ++it) 722 | { 723 | const string & line = *it; 724 | string_vector line_parts; 725 | split(line, ':', line_parts); 726 | if (line_parts.size() != 2) 727 | throw protocol_error("unexpected line format for info"); 728 | 729 | const string & key = line_parts[0]; 730 | const string & val = line_parts[1]; 731 | 732 | if (key == server_info_key_version) 733 | out.version = val; 734 | else if (key == server_info_key_bgsave_in_progress) 735 | out.bgsave_in_progress = value_from_string(val) == 1; 736 | else if (key == server_info_key_connected_clients) 737 | out.connected_clients = value_from_string(val); 738 | else if (key == server_info_key_connected_slaves) 739 | out.connected_slaves = value_from_string(val); 740 | else if (key == server_info_key_used_memory) 741 | out.used_memory = value_from_string(val); 742 | else if (key == server_info_key_changes_since_last_save) 743 | out.changes_since_last_save = value_from_string(val); 744 | else if (key == server_info_key_last_save_time) 745 | out.last_save_time = value_from_string(val); 746 | else if (key == server_info_key_total_connections_received) 747 | out.total_connections_received = value_from_string(val); 748 | else if (key == server_info_key_total_commands_processed) 749 | out.total_commands_processed = value_from_string(val); 750 | else if (key == server_info_key_uptime_in_seconds) 751 | out.uptime_in_seconds = value_from_string(val); 752 | else if (key == server_info_key_uptime_in_days) 753 | out.uptime_in_days = value_from_string(val); 754 | else if (key == server_info_key_role) 755 | out.role = val == server_info_value_role_master ? role_master : role_slave; 756 | else 757 | throw protocol_error(string("unexpected info key '") + key + "'"); 758 | } 759 | } 760 | 761 | // 762 | // Private methods 763 | // 764 | 765 | void client::send_(const string & msg) 766 | { 767 | #ifndef NDEBUG 768 | output_proto_debug(msg, false); 769 | #endif 770 | 771 | if (anetWrite(socket_, const_cast(msg.data()), msg.size()) == -1) 772 | throw connection_error(strerror(errno)); 773 | } 774 | 775 | string client::recv_single_line_reply_() 776 | { 777 | string line = read_line(socket_); 778 | 779 | #ifndef NDEBUG 780 | output_proto_debug(line); 781 | #endif 782 | 783 | if (line.empty()) 784 | throw protocol_error("empty single line reply"); 785 | 786 | if (line.find(prefix_status_reply_error) == 0) 787 | { 788 | string error_msg = line.substr(prefix_status_reply_error.length()); 789 | if (error_msg.empty()) 790 | error_msg = "unknown error"; 791 | throw protocol_error(error_msg); 792 | } 793 | 794 | if (line[0] != prefix_status_reply_value) 795 | throw protocol_error("unexpected prefix for status reply"); 796 | 797 | return line.substr(1); 798 | } 799 | 800 | void client::recv_ok_reply_() 801 | { 802 | if (recv_single_line_reply_() != status_reply_ok) 803 | throw protocol_error("expected OK response"); 804 | } 805 | 806 | client::int_type client::recv_bulk_reply_(char prefix) 807 | { 808 | string line = read_line(socket_); 809 | 810 | #ifndef NDEBUG 811 | output_proto_debug(line); 812 | #endif 813 | 814 | if (line[0] != prefix) 815 | throw protocol_error("unexpected prefix for bulk reply"); 816 | 817 | return value_from_string(line.substr(1)); 818 | } 819 | 820 | string client::recv_bulk_reply_() 821 | { 822 | int_type length = recv_bulk_reply_(prefix_single_bulk_reply); 823 | 824 | if (length == -1) 825 | return client::missing_value; 826 | 827 | int_type real_length = length + 2; // CRLF 828 | 829 | string data = read_n(socket_, real_length); 830 | 831 | #ifndef NDEBUG 832 | output_proto_debug(data.substr(0, data.length()-2)); 833 | #endif 834 | 835 | if (data.empty()) 836 | throw protocol_error("invalid bulk reply data; empty"); 837 | 838 | if (data.length() != static_cast(real_length)) 839 | throw protocol_error("invalid bulk reply data; data of unexpected length"); 840 | 841 | data.erase(data.size() - 2); 842 | 843 | return data; 844 | } 845 | 846 | client::int_type client::recv_multi_bulk_reply_(string_vector & out) 847 | { 848 | int_type length = recv_bulk_reply_(prefix_multi_bulk_reply); 849 | 850 | if (length == -1) 851 | throw key_error("no such key"); 852 | 853 | for (int_type i = 0; i < length; ++i) 854 | out.push_back(recv_bulk_reply_()); 855 | 856 | return length; 857 | } 858 | 859 | client::int_type client::recv_multi_bulk_reply_(string_set & out) 860 | { 861 | int_type length = recv_bulk_reply_(prefix_multi_bulk_reply); 862 | 863 | if (length == -1) 864 | throw key_error("no such key"); 865 | 866 | for (int_type i = 0; i < length; ++i) 867 | out.insert(recv_bulk_reply_()); 868 | 869 | return length; 870 | } 871 | 872 | client::int_type client::recv_int_reply_() 873 | { 874 | string line = read_line(socket_); 875 | 876 | #ifndef NDEBUG 877 | output_proto_debug(line); 878 | #endif 879 | 880 | if (line.empty()) 881 | throw protocol_error("invalid integer reply; empty"); 882 | 883 | if (line[0] != prefix_int_reply) 884 | throw protocol_error("unexpected prefix for integer reply"); 885 | 886 | return value_from_string(line.substr(1)); 887 | } 888 | 889 | void client::recv_int_ok_reply_() 890 | { 891 | if (recv_int_reply_() != 1) 892 | throw protocol_error("expecting int reply of 1"); 893 | } 894 | } 895 | --------------------------------------------------------------------------------