+++ /dev/null
-This is a stable release, for beta testing make sure to download the latest source code from Git:
-
- git clone git://github.com/antirez/redis.git
-
-It's also possibe to download the latest source code as a tarball:
-
- http://github.com/antirez/redis/tree/master
-
-(use the download button)
--- /dev/null
+To compile Redis, do the following:
+
+ cd src; make
+
+The compilation will produce a redis-server binary.
+Copy this file where you want.
+
+Run the server using the following command line:
+
+ /path/to/redis-server
+
+This will start a Redis server with the default configuration.
+
+Otherwise if you want to provide your configuration use:
+
+ /path/to/redis-server /path/to/redis.conf
+
+You can find an example redis.conf file in the root directory
+of this source distribution.
--- /dev/null
+# Top level makefile, the real shit is at src/Makefile
+
+TARGETS=32bit noopt test
+
+all:
+ cd src && $(MAKE) $@
+
+install: dummy
+ cd src && $(MAKE) $@
+
+$(TARGETS) clean:
+ cd src && $(MAKE) $@
+
+dummy:
* Support for syslog(3).
* Change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster.
+* Add an explicit test for MULTI/EXEC reloaded in the AOF.
+* Command table -> hash table, with support for command renaming
VM TODO
=======
==========
* LRANGE and other commands are using 32 bit integers for ranges, and overflows are not detected. So LRANGE mylist 0 23498204823094823904823904 will have random effects.
+
+REDIS CLI TODO
+==============
+
+* Computer parsable output generation
+* Memoize return values so that they can be used later as arguments, like $1
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb
+INSTALL_TOP= /usr/local
+INSTALL_BIN= $(INSTALL_TOP)/bin
+INSTALL= cp -p
+
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
32bitgprof:
make PROF="-pg" ARCH="-arch i386"
+
+install: all
+ $(INSTALL) $(PRGNAME) $(INSTALL_BIN)
+ $(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
+ $(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
+ $(INSTALL) $(CHECKDUMPPRGNAME) $(INSTALL_BIN)
+ $(INSTALL) $(CHECKAOFPRGNAME) $(INSTALL_BIN)
* so that Redis will not try to send replies to this client. */
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
c->reply = listCreate();
+ c->watched_keys = listCreate();
listSetFreeMethod(c->reply,decrRefCount);
listSetDupMethod(c->reply,dupClientReplyValue);
initClientMultiState(c);
void freeFakeClient(struct redisClient *c) {
sdsfree(c->querybuf);
listRelease(c->reply);
+ listRelease(c->watched_keys);
freeClientMultiState(c);
zfree(c);
}
}
robj *lookupKeyWrite(redisDb *db, robj *key) {
- deleteIfVolatile(db,key);
- touchWatchedKey(db,key);
+ expireIfNeeded(db,key);
return lookupKey(db,key);
}
return;
incrRefCount(o);
- deleteIfVolatile(c->db,c->argv[2]);
if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
if (nx) {
decrRefCount(o);
dbReplace(c->db,c->argv[2],o);
}
dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[2]);
server.dirty++;
addReply(c,nx ? shared.cone : shared.ok);
}
/* Try to add the element to the target DB */
- deleteIfVolatile(dst,c->argv[1]);
if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
addReply(c,shared.czero);
return;
/* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */
redisAssert(dictFind(db->dict,key->ptr) != NULL);
- if (dictDelete(db->expires,key->ptr) == DICT_OK) {
- return 1;
- } else {
- return 0;
- }
+ return dictDelete(db->expires,key->ptr) == DICT_OK;
}
-int setExpire(redisDb *db, robj *key, time_t when) {
+void setExpire(redisDb *db, robj *key, time_t when) {
dictEntry *de;
/* Reuse the sds from the main dict in the expire dict */
- redisAssert((de = dictFind(db->dict,key->ptr)) != NULL);
- if (dictAdd(db->expires,dictGetEntryKey(de),(void*)when) == DICT_ERR) {
- return 0;
- } else {
- return 1;
- }
+ de = dictFind(db->dict,key->ptr);
+ redisAssert(de != NULL);
+ dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
}
/* Return the expire time of the specified key, or -1 if no expire
return (time_t) dictGetEntryVal(de);
}
+/* Propagate expires into slaves and the AOF file.
+ * When a key expires in the master, a DEL operation for this key is sent
+ * to all the slaves and the AOF file if enabled.
+ *
+ * This way the key expiry is centralized in one place, and since both
+ * AOF and the master->slave link guarantee operation ordering, everything
+ * will be consistent even if we allow write operations against expiring
+ * keys. */
+void propagateExpire(redisDb *db, robj *key) {
+ struct redisCommand *cmd;
+ robj *argv[2];
+
+ cmd = lookupCommand("del");
+ argv[0] = createStringObject("DEL",3);
+ argv[1] = key;
+ incrRefCount(key);
+
+ if (server.appendonly)
+ feedAppendOnlyFile(cmd,db->id,argv,2);
+ if (listLength(server.slaves))
+ replicationFeedSlaves(server.slaves,db->id,argv,2);
+
+ decrRefCount(argv[0]);
+ decrRefCount(argv[1]);
+}
+
int expireIfNeeded(redisDb *db, robj *key) {
time_t when = getExpire(db,key);
+
+ /* If we are running in the context of a slave, return ASAP:
+ * the slave key expiration is controlled by the master that will
+ * send us synthesized DEL operations for expired keys.
+ *
+ * Still we try to return the right information to the caller,
+ * that is, 0 if we think the key should be still valid, 1 if
+ * we think the key is expired at this time. */
+ if (server.masterhost != NULL) {
+ return time(NULL) > when;
+ }
+
if (when < 0) return 0;
/* Return when this key has not expired */
/* Delete the key */
server.stat_expiredkeys++;
server.dirty++;
- return dbDelete(db,key);
-}
-
-int deleteIfVolatile(redisDb *db, robj *key) {
- if (getExpire(db,key) < 0) return 0;
-
- /* Delete the key */
- server.stat_expiredkeys++;
- server.dirty++;
+ propagateExpire(db,key);
return dbDelete(db,key);
}
if (seconds <= 0) {
if (dbDelete(c->db,key)) server.dirty++;
addReply(c, shared.cone);
+ touchWatchedKey(c->db,key);
return;
} else {
time_t when = time(NULL)+seconds;
- if (setExpire(c->db,key,when)) {
- addReply(c,shared.cone);
- server.dirty++;
- } else {
- addReply(c,shared.czero);
- }
+ setExpire(c->db,key,when);
+ addReply(c,shared.cone);
+ touchWatchedKey(c->db,key);
+ server.dirty++;
return;
}
}
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl));
}
+void persistCommand(redisClient *c) {
+ dictEntry *de;
+ de = dictFind(c->db->dict,c->argv[1]->ptr);
+ if (de == NULL) {
+ addReply(c,shared.czero);
+ } else {
+ if (removeExpire(c->db,c->argv[1])) {
+ addReply(c,shared.cone);
+ server.dirty++;
+ } else {
+ addReply(c,shared.czero);
+ }
+ }
+}
* around when there is a child performing saving operations. */
static int dict_can_resize = 1;
-/* ---------------------------- Utility funcitons --------------------------- */
-
-static void _dictPanic(const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- fprintf(stderr, "\nDICT LIBRARY PANIC: ");
- vfprintf(stderr, fmt, ap);
- fprintf(stderr, "\n\n");
- va_end(ap);
-}
-
-/* ------------------------- Heap Management Wrappers------------------------ */
-
-static void *_dictAlloc(size_t size)
-{
- void *p = zmalloc(size);
- if (p == NULL)
- _dictPanic("Out of memory");
- return p;
-}
-
-static void _dictFree(void *ptr) {
- zfree(ptr);
-}
-
/* -------------------------- private prototypes ---------------------------- */
static int _dictExpandIfNeeded(dict *ht);
dict *dictCreate(dictType *type,
void *privDataPtr)
{
- dict *d = _dictAlloc(sizeof(*d));
+ dict *d = zmalloc(sizeof(*d));
_dictInit(d,type,privDataPtr);
return d;
if (dictIsRehashing(d) || d->ht[0].used > size)
return DICT_ERR;
+ /* Allocate the new hashtable and initialize all pointers to NULL */
n.size = realsize;
n.sizemask = realsize-1;
- n.table = _dictAlloc(realsize*sizeof(dictEntry*));
+ n.table = zcalloc(realsize*sizeof(dictEntry*));
n.used = 0;
- /* Initialize all the pointers to NULL */
- memset(n.table, 0, realsize*sizeof(dictEntry*));
-
/* Is this the first initialization? If so it's not really a rehashing
* we just set the first hash table so that it can accept keys. */
if (d->ht[0].table == NULL) {
/* Check if we already rehashed the whole table... */
if (d->ht[0].used == 0) {
- _dictFree(d->ht[0].table);
+ zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
/* Allocates the memory and stores key */
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
- entry = _dictAlloc(sizeof(*entry));
+ entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
dictFreeEntryKey(d, he);
dictFreeEntryVal(d, he);
}
- _dictFree(he);
+ zfree(he);
d->ht[table].used--;
return DICT_OK;
}
nextHe = he->next;
dictFreeEntryKey(d, he);
dictFreeEntryVal(d, he);
- _dictFree(he);
+ zfree(he);
ht->used--;
he = nextHe;
}
}
/* Free the table and the allocated cache structure */
- _dictFree(ht->table);
+ zfree(ht->table);
/* Re-initialize the table */
_dictReset(ht);
return DICT_OK; /* never fails */
{
_dictClear(d,&d->ht[0]);
_dictClear(d,&d->ht[1]);
- _dictFree(d);
+ zfree(d);
}
dictEntry *dictFind(dict *d, const void *key)
dictIterator *dictGetIterator(dict *d)
{
- dictIterator *iter = _dictAlloc(sizeof(*iter));
+ dictIterator *iter = zmalloc(sizeof(*iter));
iter->d = d;
iter->table = 0;
void dictReleaseIterator(dictIterator *iter)
{
if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--;
- _dictFree(iter);
+ zfree(iter);
}
/* Return a random entry from the hash table. Useful to
dict_can_resize = 0;
}
+#if 0
+
+/* The following are just example hash table types implementations.
+ * Not useful for Redis so they are commented out.
+ */
+
/* ----------------------- StringCopy Hash Table Type ------------------------*/
static unsigned int _dictStringCopyHTHashFunction(const void *key)
return dictGenHashFunction(key, strlen(key));
}
-static void *_dictStringCopyHTKeyDup(void *privdata, const void *key)
+static void *_dictStringDup(void *privdata, const void *key)
{
int len = strlen(key);
- char *copy = _dictAlloc(len+1);
+ char *copy = zmalloc(len+1);
DICT_NOTUSED(privdata);
memcpy(copy, key, len);
return copy;
}
-static void *_dictStringKeyValCopyHTValDup(void *privdata, const void *val)
-{
- int len = strlen(val);
- char *copy = _dictAlloc(len+1);
- DICT_NOTUSED(privdata);
-
- memcpy(copy, val, len);
- copy[len] = '\0';
- return copy;
-}
-
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
const void *key2)
{
return strcmp(key1, key2) == 0;
}
-static void _dictStringCopyHTKeyDestructor(void *privdata, void *key)
-{
- DICT_NOTUSED(privdata);
-
- _dictFree((void*)key); /* ATTENTION: const cast */
-}
-
-static void _dictStringKeyValCopyHTValDestructor(void *privdata, void *val)
+static void _dictStringDestructor(void *privdata, void *key)
{
DICT_NOTUSED(privdata);
- _dictFree((void*)val); /* ATTENTION: const cast */
+ zfree(key);
}
dictType dictTypeHeapStringCopyKey = {
- _dictStringCopyHTHashFunction, /* hash function */
- _dictStringCopyHTKeyDup, /* key dup */
- NULL, /* val dup */
- _dictStringCopyHTKeyCompare, /* key compare */
- _dictStringCopyHTKeyDestructor, /* key destructor */
- NULL /* val destructor */
+ _dictStringCopyHTHashFunction, /* hash function */
+ _dictStringDup, /* key dup */
+ NULL, /* val dup */
+ _dictStringCopyHTKeyCompare, /* key compare */
+ _dictStringDestructor, /* key destructor */
+ NULL /* val destructor */
};
/* This is like StringCopy but does not auto-duplicate the key.
* It's used for intepreter's shared strings. */
dictType dictTypeHeapStrings = {
- _dictStringCopyHTHashFunction, /* hash function */
- NULL, /* key dup */
- NULL, /* val dup */
- _dictStringCopyHTKeyCompare, /* key compare */
- _dictStringCopyHTKeyDestructor, /* key destructor */
- NULL /* val destructor */
+ _dictStringCopyHTHashFunction, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ _dictStringCopyHTKeyCompare, /* key compare */
+ _dictStringDestructor, /* key destructor */
+ NULL /* val destructor */
};
/* This is like StringCopy but also automatically handle dynamic
* allocated C strings as values. */
dictType dictTypeHeapStringCopyKeyValue = {
- _dictStringCopyHTHashFunction, /* hash function */
- _dictStringCopyHTKeyDup, /* key dup */
- _dictStringKeyValCopyHTValDup, /* val dup */
- _dictStringCopyHTKeyCompare, /* key compare */
- _dictStringCopyHTKeyDestructor, /* key destructor */
- _dictStringKeyValCopyHTValDestructor, /* val destructor */
+ _dictStringCopyHTHashFunction, /* hash function */
+ _dictStringDup, /* key dup */
+ _dictStringDup, /* val dup */
+ _dictStringCopyHTKeyCompare, /* key compare */
+ _dictStringDestructor, /* key destructor */
+ _dictStringDestructor, /* val destructor */
};
+#endif
*/
#include "fmacros.h"
+
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096
static char *unsupported_term[] = {"dumb","cons25",NULL};
static struct termios orig_termios; /* in order to restore at exit */
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
static int atexit_registered = 0; /* register atexit just 1 time */
-static int history_max_len = 100;
+static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0;
char **history = NULL;
if (nread <= 0) return len;
switch(c) {
case 13: /* enter */
- history_len--;
- return len;
case 4: /* ctrl-d */
history_len--;
- return (len == 0) ? -1 : (int)len;
+ free(history[history_len]);
+ return (len == 0 && c == 4) ? -1 : (int)len;
case 3: /* ctrl-c */
errno = EAGAIN;
return -1;
char *linecopy;
if (history_max_len == 0) return 0;
- if (history == 0) {
+ if (history == NULL) {
history = malloc(sizeof(char*)*history_max_len);
if (history == NULL) return 0;
memset(history,0,(sizeof(char*)*history_max_len));
linecopy = strdup(line);
if (!linecopy) return 0;
if (history_len == history_max_len) {
+ free(history[0]);
memmove(history,history+1,sizeof(char*)*(history_max_len-1));
history_len--;
}
history_len = history_max_len;
return 1;
}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int linenoiseHistorySave(char *filename) {
+ FILE *fp = fopen(filename,"w");
+ int j;
+
+ if (fp == NULL) return -1;
+ for (j = 0; j < history_len; j++)
+ fprintf(fp,"%s\n",history[j]);
+ fclose(fp);
+ return 0;
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+int linenoiseHistoryLoad(char *filename) {
+ FILE *fp = fopen(filename,"r");
+ char buf[LINENOISE_MAX_LINE];
+
+ if (fp == NULL) return -1;
+
+ while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
+ char *p;
+
+ p = strchr(buf,'\r');
+ if (!p) p = strchr(buf,'\n');
+ if (p) *p = '\0';
+ linenoiseHistoryAdd(buf);
+ }
+ fclose(fp);
+ return 0;
+}
#define __LINENOISE_H
char *linenoise(const char *prompt);
-int linenoiseHistoryAdd(char *line);
+int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
+int linenoiseHistorySave(char *filename);
+int linenoiseHistoryLoad(char *filename);
#endif /* __LINENOISE_H */
ln = listSearchKey(server.clients,c);
redisAssert(ln != NULL);
listDelNode(server.clients,ln);
- /* Remove from the list of clients that are now ready to be restarted
- * after waiting for swapped keys */
- if (c->flags & REDIS_IO_WAIT && listLength(c->io_keys) == 0) {
- ln = listSearchKey(server.io_ready_clients,c);
- if (ln) {
+ /* Remove from the list of clients waiting for swapped keys, or ready
+ * to be restarted, but not yet woken up again. */
+ if (c->flags & REDIS_IO_WAIT) {
+ redisAssert(server.vm_enabled);
+ if (listLength(c->io_keys) == 0) {
+ ln = listSearchKey(server.io_ready_clients,c);
+
+ /* When this client is waiting to be woken up (REDIS_IO_WAIT),
+ * it should be present in the list io_ready_clients */
+ redisAssert(ln != NULL);
listDelNode(server.io_ready_clients,ln);
- server.vm_blocked_clients--;
+ } else {
+ while (listLength(c->io_keys)) {
+ ln = listFirst(c->io_keys);
+ dontWaitForSwappedKey(c,ln->value);
+ }
}
- }
- /* Remove from the list of clients waiting for swapped keys */
- while (server.vm_enabled && listLength(c->io_keys)) {
- ln = listFirst(c->io_keys);
- dontWaitForSwappedKey(c,ln->value);
+ server.vm_blocked_clients--;
}
listRelease(c->io_keys);
/* Master/slave cleanup */
#include "redis.h"
#include <pthread.h>
+#include <math.h>
robj *createObject(int type, void *ptr) {
robj *o;
listDelNode(server.objfreelist,head);
if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
} else {
- if (server.vm_enabled)
- pthread_mutex_unlock(&server.obj_freelist_mutex);
+ if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
o = zmalloc(sizeof(*o));
}
o->type = type;
robj *createStringObjectFromLongLong(long long value) {
robj *o;
- if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
+ if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
+ pthread_equal(pthread_self(),server.mainthread)) {
incrRefCount(shared.integers[value]);
o = shared.integers[value];
} else {
case REDIS_HASH: freeHashObject(o); break;
default: redisPanic("Unknown object type"); break;
}
+ o->ptr = NULL; /* defensive programming. We'll see NULL in traces. */
if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex);
if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX ||
!listAddNodeHead(server.objfreelist,o))
/* Check if we can represent this string as a long integer */
if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o;
- /* Ok, this object can be encoded */
- if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
+ /* Ok, this object can be encoded...
+ *
+ * Can I use a shared object? Only if the object is inside a given
+ * range and if this is the main thread, since when VM is enabled we
+ * have the constraint that I/O thread should only handle non-shared
+ * objects, in order to avoid race conditions (we don't have per-object
+ * locking). */
+ if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
+ pthread_equal(pthread_self(),server.mainthread)) {
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
redisAssert(o->type == REDIS_STRING);
if (o->encoding == REDIS_ENCODING_RAW) {
value = strtod(o->ptr, &eptr);
- if (eptr[0] != '\0') return REDIS_ERR;
+ if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr;
} else {
*/
#include "fmacros.h"
+#include "version.h"
#include <stdio.h>
#include <string.h>
int pubsub_mode;
int raw_output;
char *auth;
+ char *historyfile;
} config;
static int cliReadReply(int fd);
config.interactive = 1;
} else if (!strcmp(argv[i],"-c")) {
config.argn_from_stdin = 1;
+ } else if (!strcmp(argv[i],"-v")) {
+ printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
+ exit(0);
} else {
break;
}
}
static void usage() {
- fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n");
+ fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
return sds;
}
-static char **splitArguments(char *line, int *argc) {
- char *p = line;
- char *current = NULL;
- char **vector = NULL;
-
- *argc = 0;
- while(1) {
- /* skip blanks */
- while(*p && isspace(*p)) p++;
- if (*p) {
- /* get a token */
- int inq=0; /* set to 1 if we are in "quotes" */
- int done = 0;
-
- if (current == NULL) current = sdsempty();
- while(!done) {
- if (inq) {
- if (*p == '\\' && *(p+1)) {
- char c;
-
- p++;
- switch(*p) {
- case 'n': c = '\n'; break;
- case 'r': c = '\r'; break;
- case 't': c = '\t'; break;
- case 'b': c = '\b'; break;
- case 'a': c = '\a'; break;
- default: c = *p; break;
- }
- current = sdscatlen(current,&c,1);
- } else if (*p == '"') {
- done = 1;
- } else {
- current = sdscatlen(current,p,1);
- }
- } else {
- switch(*p) {
- case ' ':
- case '\n':
- case '\r':
- case '\t':
- case '\0':
- done=1;
- break;
- case '"':
- inq=1;
- break;
- default:
- current = sdscatlen(current,p,1);
- break;
- }
- }
- if (*p) p++;
- }
- /* add the token to the vector */
- vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
- vector[*argc] = current;
- (*argc)++;
- current = NULL;
- } else {
- return vector;
- }
- }
-}
-
#define LINE_BUFLEN 4096
static void repl() {
int argc, j;
- char *line, **argv;
+ char *line;
+ sds *argv;
while((line = linenoise("redis> ")) != NULL) {
if (line[0] != '\0') {
- argv = splitArguments(line,&argc);
+ argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line);
+ if (config.historyfile) linenoiseHistorySave(config.historyfile);
if (argc > 0) {
if (strcasecmp(argv[0],"quit") == 0 ||
strcasecmp(argv[0],"exit") == 0)
config.pubsub_mode = 0;
config.raw_output = 0;
config.auth = NULL;
+ config.historyfile = NULL;
+
+ if (getenv("HOME") != NULL) {
+ config.historyfile = malloc(256);
+ snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
+ linenoiseHistoryLoad(config.historyfile);
+ }
firstarg = parseOptions(argc,argv);
argc -= firstarg;
{"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0},
{"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
{"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
+ {"strlen",strlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
{"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
{"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
{"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
{"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
+ {"persist",persistCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0},
{"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
{"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0},
void redisLog(int level, const char *fmt, ...) {
va_list ap;
FILE *fp;
+ char *c = ".-*#";
+ char buf[64];
+ time_t now;
+
+ if (level < server.verbosity) return;
fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a");
if (!fp) return;
va_start(ap, fmt);
- if (level >= server.verbosity) {
- char *c = ".-*#";
- char buf[64];
- time_t now;
-
- now = time(NULL);
- strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
- fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
- vfprintf(fp, fmt, ap);
- fprintf(fp,"\n");
- fflush(fp);
- }
+ now = time(NULL);
+ strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
+ fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
+ vfprintf(fp, fmt, ap);
+ fprintf(fp,"\n");
+ fflush(fp);
va_end(ap);
if (server.logfile) fclose(fp);
/* ======================= Cron: called every 100 ms ======================== */
+/* Try to expire a few timed out keys. The algorithm used is adaptive and
+ * will use few CPU cycles if there are few expiring keys, otherwise
+ * it will get more aggressive to avoid that too much memory is used by
+ * keys that can be removed from the keyspace. */
+void activeExpireCycle(void) {
+ int j;
+
+ for (j = 0; j < server.dbnum; j++) {
+ int expired;
+ redisDb *db = server.db+j;
+
+ /* Continue to expire if at the end of the cycle more than 25%
+ * of the keys were expired. */
+ do {
+ long num = dictSize(db->expires);
+ time_t now = time(NULL);
+
+ expired = 0;
+ if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
+ num = REDIS_EXPIRELOOKUPS_PER_CRON;
+ while (num--) {
+ dictEntry *de;
+ time_t t;
+
+ if ((de = dictGetRandomKey(db->expires)) == NULL) break;
+ t = (time_t) dictGetEntryVal(de);
+ if (now > t) {
+ sds key = dictGetEntryKey(de);
+ robj *keyobj = createStringObject(key,sdslen(key));
+
+ propagateExpire(db,keyobj);
+ dbDelete(db,keyobj);
+ decrRefCount(keyobj);
+ expired++;
+ server.stat_expiredkeys++;
+ }
+ }
+ } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
+ }
+}
+
+
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j, loops = server.cronloops++;
REDIS_NOTUSED(eventLoop);
}
}
- /* Try to expire a few timed out keys. The algorithm used is adaptive and
- * will use few CPU cycles if there are few expiring keys, otherwise
- * it will get more aggressive to avoid that too much memory is used by
- * keys that can be removed from the keyspace. */
- for (j = 0; j < server.dbnum; j++) {
- int expired;
- redisDb *db = server.db+j;
-
- /* Continue to expire if at the end of the cycle more than 25%
- * of the keys were expired. */
- do {
- long num = dictSize(db->expires);
- time_t now = time(NULL);
-
- expired = 0;
- if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
- num = REDIS_EXPIRELOOKUPS_PER_CRON;
- while (num--) {
- dictEntry *de;
- time_t t;
-
- if ((de = dictGetRandomKey(db->expires)) == NULL) break;
- t = (time_t) dictGetEntryVal(de);
- if (now > t) {
- sds key = dictGetEntryKey(de);
- robj *keyobj = createStringObject(key,sdslen(key));
-
- dbDelete(db,keyobj);
- decrRefCount(keyobj);
- expired++;
- server.stat_expiredkeys++;
- }
- }
- } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
- }
+ /* Expire a few keys per cycle, only if this is a master.
+ * On slaves we wait for DEL operations synthesized by the master
+ * in order to guarantee a strict consistency. */
+ if (server.masterhost == NULL) activeExpireCycle();
/* Swap a few keys on disk if we are over the memory limit and VM
* is enbled. Try to free objects from the free list first. */
signal(SIGPIPE, SIG_IGN);
setupSigSegvAction();
+ server.mainthread = pthread_self();
server.devnull = fopen("/dev/null","w");
if (server.devnull == NULL) {
redisLog(REDIS_WARNING, "Can't open /dev/null: %s", server.neterr);
void sortCommandTable() {
/* Copy and sort the read-only version of the command table */
- commandTable = (struct redisCommand*)malloc(sizeof(readonlyCommandTable));
+ commandTable = (struct redisCommand*)zmalloc(sizeof(readonlyCommandTable));
memcpy(commandTable,readonlyCommandTable,sizeof(readonlyCommandTable));
qsort(commandTable,
sizeof(readonlyCommandTable)/sizeof(struct redisCommand),
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
+#include <pthread.h>
#include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */
/* Global server state structure */
struct redisServer {
+ pthread_t mainthread;
int port;
int fd;
redisDb *db;
/* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key);
+void propagateExpire(redisDb *db, robj *key);
int expireIfNeeded(redisDb *db, robj *key);
-int deleteIfVolatile(redisDb *db, robj *key);
time_t getExpire(redisDb *db, robj *key);
-int setExpire(redisDb *db, robj *key, time_t when);
+void setExpire(redisDb *db, robj *key, time_t when);
robj *lookupKey(redisDb *db, robj *key);
robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key);
void expireatCommand(redisClient *c);
void getsetCommand(redisClient *c);
void ttlCommand(redisClient *c);
+void persistCommand(redisClient *c);
void slaveofCommand(redisClient *c);
void debugCommand(redisClient *c);
void msetCommand(redisClient *c);
void brpopCommand(redisClient *c);
void appendCommand(redisClient *c);
void substrCommand(redisClient *c);
+void strlenCommand(redisClient *c);
void zrankCommand(redisClient *c);
void zrevrankCommand(redisClient *c);
void hsetCommand(redisClient *c);
void watchCommand(redisClient *c);
void unwatchCommand(redisClient *c);
+#if defined(__GNUC__)
+void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
+void free(void *ptr) __attribute__ ((deprecated));
+void *malloc(size_t size) __attribute__ ((deprecated));
+void *realloc(void *ptr, size_t size) __attribute__ ((deprecated));
+#endif
+
#endif
}
return sdscatlen(s,"\"",1);
}
+
+/* Split a line into arguments, where every argument can be in the
+ * following programming-language REPL-alike form:
+ *
+ * foo bar "newline are supported\n" and "\xff\x00otherstuff"
+ *
+ * The number of arguments is stored into *argc, and an array
+ * of sds is returned. The caller should sdsfree() all the returned
+ * strings and finally zfree() the array itself.
+ *
+ * Note that sdscatrepr() is able to convert back a string into
+ * a quoted string in the same format sdssplitargs() is able to parse.
+ */
+sds *sdssplitargs(char *line, int *argc) {
+ char *p = line;
+ char *current = NULL;
+ char **vector = NULL;
+
+ *argc = 0;
+ while(1) {
+ /* skip blanks */
+ while(*p && isspace(*p)) p++;
+ if (*p) {
+ /* get a token */
+ int inq=0; /* set to 1 if we are in "quotes" */
+ int done = 0;
+
+ if (current == NULL) current = sdsempty();
+ while(!done) {
+ if (inq) {
+ if (*p == '\\' && *(p+1)) {
+ char c;
+
+ p++;
+ switch(*p) {
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'b': c = '\b'; break;
+ case 'a': c = '\a'; break;
+ default: c = *p; break;
+ }
+ current = sdscatlen(current,&c,1);
+ } else if (*p == '"') {
+ done = 1;
+ } else {
+ current = sdscatlen(current,p,1);
+ }
+ } else {
+ switch(*p) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\0':
+ done=1;
+ break;
+ case '"':
+ inq=1;
+ break;
+ default:
+ current = sdscatlen(current,p,1);
+ break;
+ }
+ }
+ if (*p) p++;
+ }
+ /* add the token to the vector */
+ vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
+ vector[*argc] = current;
+ (*argc)++;
+ current = NULL;
+ } else {
+ return vector;
+ }
+ }
+}
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, char *p, size_t len);
+sds *sdssplitargs(char *line, int *argc);
#endif
* SORT result is empty a new key is set and maybe the old content
* replaced. */
server.dirty += 1+outputlen;
+ touchWatchedKey(c->db,storekey);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",outputlen));
}
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
update = hashTypeSet(o,c->argv[2],c->argv[3]);
addReply(c, update ? shared.czero : shared.cone);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
hashTypeSet(o,c->argv[2],c->argv[3]);
addReply(c, shared.cone);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
}
hashTypeSet(o,c->argv[i],c->argv[i+1]);
}
addReply(c, shared.ok);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
hashTypeSet(o,c->argv[2],new);
decrRefCount(new);
addReplyLongLong(c,value);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
if (hashTypeDelete(o,c->argv[2])) {
if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
addReply(c,shared.cone);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
} else {
addReply(c,shared.czero);
return;
}
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
+ touchWatchedKey(c->db,c->argv[1]);
addReply(c,shared.cone);
return;
}
}
listTypePush(lobj,c->argv[2],where);
addReplyLongLong(c,listTypeLength(lobj));
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
} else {
/* Notify client of a failed insert */
}
} else {
listTypePush(subject,val,where);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
decrRefCount(value);
addReply(c,shared.ok);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNodeValue(ln) = value;
incrRefCount(value);
addReply(c,shared.ok);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
} else {
addReplyBulk(c,value);
decrRefCount(value);
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
}
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
- if (end < 0) end = 0;
- /* indexes sanity checks */
+ /* Invariant: start >= 0, so this test will be true when end < 0.
+ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
- /* Out of range start or start > end result in empty list */
addReply(c,shared.emptymultibulk);
return;
}
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
- if (end < 0) end = 0;
- /* indexes sanity checks */
+ /* Invariant: start >= 0, so this test will be true when end < 0.
+ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
/* Out of range start or start > end result in empty list */
ltrim = llen;
redisPanic("Unknown list encoding");
}
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.ok);
}
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
+ if (removed) touchWatchedKey(c->db,c->argv[1]);
}
/* This is the semantic of this command:
/* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
}
}
}
if (setTypeAdd(set,c->argv[2])) {
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
} else {
if (setTypeRemove(set,c->argv[2])) {
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
} else {
/* Remove the src set from the database when empty */
if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[2]);
server.dirty++;
/* Create the destination set when it doesn't exist */
addReplyBulk(c,ele);
decrRefCount(ele);
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
}
if (!setobj) {
zfree(sets);
if (dstkey) {
- if (dbDelete(c->db,dstkey))
+ if (dbDelete(c->db,dstkey)) {
+ touchWatchedKey(c->db,dstkey);
server.dirty++;
+ }
addReply(c,shared.czero);
} else {
addReply(c,shared.emptymultibulk);
decrRefCount(dstset);
addReply(c,shared.czero);
}
+ touchWatchedKey(c->db,dstkey);
server.dirty++;
} else {
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality);
decrRefCount(dstset);
addReply(c,shared.czero);
}
+ touchWatchedKey(c->db,dstkey);
server.dirty++;
}
zfree(sets);
}
}
- touchWatchedKey(c->db,key);
- if (nx) deleteIfVolatile(c->db,key);
retval = dbAdd(c->db,key,val);
if (retval == REDIS_ERR) {
if (!nx) {
} else {
incrRefCount(val);
}
+ touchWatchedKey(c->db,key);
server.dirty++;
removeExpire(c->db,key);
if (expire) setExpire(c->db,key,time(NULL)+seconds);
if (getGenericCommand(c) == REDIS_ERR) return;
dbReplace(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
removeExpire(c->db,c->argv[1]);
}
dbReplace(c->db,c->argv[j],c->argv[j+1]);
incrRefCount(c->argv[j+1]);
removeExpire(c->db,c->argv[j]);
+ touchWatchedKey(c->db,c->argv[j]);
}
server.dirty += (c->argc-1)/2;
addReply(c, nx ? shared.cone : shared.ok);
value += incr;
o = createStringObjectFromLongLong(value);
dbReplace(c->db,c->argv[1],o);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.colon);
addReply(c,o);
}
totlen = sdslen(o->ptr);
}
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",(unsigned long)totlen));
}
decrRefCount(o);
}
+void strlenCommand(redisClient *c) {
+ robj *o;
+
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
+ checkType(c,o,REDIS_STRING)) return;
+ o = getDecodedObject(o);
+ addReplyLongLong(c,sdslen(o->ptr));
+ decrRefCount(o);
+}
zset *zs;
double *score;
- if (isnan(scoreval)) {
- addReplySds(c,sdsnew("-ERR provide score is Not A Number (nan)\r\n"));
- return;
- }
-
zsetobj = lookupKeyWrite(c->db,key);
if (zsetobj == NULL) {
zsetobj = createZsetObject();
}
if (isnan(*score)) {
addReplySds(c,
- sdsnew("-ERR resulting score is Not A Number (nan)\r\n"));
+ sdsnew("-ERR resulting score is not a number (NaN)\r\n"));
zfree(score);
/* Note that we don't need to check if the zset may be empty and
* should be removed here, as we can only obtain Nan as score if
incrRefCount(ele); /* added to hash */
zslInsert(zs->zsl,*score,ele);
incrRefCount(ele); /* added to skiplist */
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
if (doincrement)
addReplyDouble(c,*score);
incrRefCount(ele);
/* Update the score in the hash table */
dictReplace(zs->dict,ele,score);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
} else {
zfree(score);
void zaddCommand(redisClient *c) {
double scoreval;
-
- if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return;
+ if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
}
void zincrbyCommand(redisClient *c) {
double scoreval;
-
- if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return;
+ if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
}
dictDelete(zs->dict,c->argv[2]);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
}
deleted = zslDeleteRangeByScore(zs->zsl,min,max,zs->dict);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
+ if (deleted) touchWatchedKey(c->db,c->argv[1]);
server.dirty += deleted;
addReplyLongLong(c,deleted);
}
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
- if (end < 0) end = 0;
- /* indexes sanity checks */
+ /* Invariant: start >= 0, so this test will be true when end < 0.
+ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
addReply(c,shared.czero);
return;
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
+ if (deleted) touchWatchedKey(c->db,c->argv[1]);
server.dirty += deleted;
addReplyLongLong(c, deleted);
}
inline static void zunionInterAggregate(double *target, double val, int aggregate) {
if (aggregate == REDIS_AGGR_SUM) {
*target = *target + val;
+ /* The result of adding two doubles is NaN when one variable
+ * is +inf and the other is -inf. When these numbers are added,
+ * we maintain the convention of the result being 0.0. */
+ if (isnan(*target)) *target = 0.0;
} else if (aggregate == REDIS_AGGR_MIN) {
*target = val < *target ? val : *target;
} else if (aggregate == REDIS_AGGR_MAX) {
zset *dstzset;
dictIterator *di;
dictEntry *de;
+ int touched = 0;
/* expect setnum input keys to be given */
setnum = atoi(c->argv[2]->ptr);
if (remaining >= (setnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) {
j++; remaining--;
for (i = 0; i < setnum; i++, j++, remaining--) {
- if (getDoubleFromObjectOrReply(c, c->argv[j], &src[i].weight, NULL) != REDIS_OK)
+ if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight,
+ "weight value is not a double") != REDIS_OK)
+ {
+ zfree(src);
return;
+ }
}
} else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) {
j++; remaining--;
redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION);
}
- dbDelete(c->db,dstkey);
+ if (dbDelete(c->db,dstkey)) {
+ touchWatchedKey(c->db,dstkey);
+ touched = 1;
+ server.dirty++;
+ }
if (dstzset->zsl->length) {
dbAdd(c->db,dstkey,dstobj);
addReplyLongLong(c, dstzset->zsl->length);
+ if (!touched) touchWatchedKey(c->db,dstkey);
server.dirty++;
} else {
decrRefCount(dstobj);
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
- if (end < 0) end = 0;
- /* indexes sanity checks */
+ /* Invariant: start >= 0, so this test will be true when end < 0.
+ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
- /* Out of range start or start > end result in empty list */
addReply(c,shared.emptymultibulk);
return;
}
} else {
redisLog(REDIS_NOTICE,"Swap file allocated with success");
}
- server.vm_bitmap = zmalloc((server.vm_pages+7)/8);
+ server.vm_bitmap = zcalloc((server.vm_pages+7)/8);
redisLog(REDIS_VERBOSE,"Allocated %lld bytes page table for %lld pages",
(long long) (server.vm_pages+7)/8, server.vm_pages);
- memset(server.vm_bitmap,0,(server.vm_pages+7)/8);
/* Initialize threaded I/O (used by Virtual Memory) */
server.io_newjobs = listCreate();
listIter li;
struct dictEntry *de;
+ /* The key object might be destroyed when deleted from the c->io_keys
+ * list (and the "key" argument is physically the same object as the
+ * object inside the list), so we need to protect it. */
+ incrRefCount(key);
+
/* Remove the key from the list of keys this client is waiting for. */
listRewind(c->io_keys,&li);
while ((ln = listNext(&li)) != NULL) {
if (listLength(l) == 0)
dictDelete(c->db->io_keys,key);
+ decrRefCount(key);
return listLength(c->io_keys) == 0;
}
#include "zmalloc.h"
#include "ziplist.h"
+int ll2string(char *s, size_t len, long long value);
+
/* Important note: the ZIP_END value is used to depict the end of the
* ziplist structure. When a pointer contains an entry, the first couple
* of bytes contain the encoded length of the previous entry. This length
}
/* Check if string pointed to by 'entry' can be encoded as an integer.
- * Stores the integer value in 'v' and its encoding in 'encoding'.
- * Warning: this function requires a NULL-terminated string! */
-static int zipTryEncoding(unsigned char *entry, long long *v, unsigned char *encoding) {
+ * Stores the integer value in 'v' and its encoding in 'encoding'. */
+static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
long long value;
char *eptr;
+ char buf[32];
+ if (entrylen >= 32 || entrylen == 0) return 0;
if (entry[0] == '-' || (entry[0] >= '0' && entry[0] <= '9')) {
- value = strtoll((char*)entry,&eptr,10);
+ int slen;
+
+ /* Perform a back-and-forth conversion to make sure that
+ * the string turned into an integer is not losing any info. */
+ memcpy(buf,entry,entrylen);
+ buf[entrylen] = '\0';
+ value = strtoll(buf,&eptr,10);
if (eptr[0] != '\0') return 0;
+ slen = ll2string(buf,32,value);
+ if (entrylen != (unsigned)slen || memcmp(buf,entry,slen)) return 0;
+
+ /* Great, the string can be encoded. Check what's the smallest
+ * of our encoding types that can hold this value. */
if (value >= INT16_MIN && value <= INT16_MAX) {
*encoding = ZIP_ENC_INT16;
} else if (value >= INT32_MIN && value <= INT32_MAX) {
}
/* See if the entry can be encoded */
- if (zipTryEncoding(s,&value,&encoding)) {
+ if (zipTryEncoding(s,slen,&value,&encoding)) {
reqlen = zipEncodingSize(encoding);
} else {
reqlen = slen;
}
} else {
/* Try to compare encoded values */
- if (zipTryEncoding(sstr,&sval,&sencoding)) {
+ if (zipTryEncoding(sstr,slen,&sval,&sencoding)) {
if (entry.encoding == sencoding) {
zval = zipLoadInteger(p+entry.headersize,entry.encoding);
return zval == sval;
#endif
}
+void *zcalloc(size_t size) {
+ void *ptr = calloc(1, size+PREFIX_SIZE);
+
+ if (!ptr) zmalloc_oom(size);
+#ifdef HAVE_MALLOC_SIZE
+ increment_used_memory(redis_malloc_size(ptr));
+ return ptr;
+#else
+ *((size_t*)ptr) = size;
+ increment_used_memory(size+PREFIX_SIZE);
+ return (char*)ptr+PREFIX_SIZE;
+#endif
+}
+
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#define _ZMALLOC_H
void *zmalloc(size_t size);
+void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
+start_server {tags {"repl"}} {
+ start_server {} {
+ test {First server should have role slave after SLAVEOF} {
+ r -1 slaveof [srv 0 host] [srv 0 port]
+ after 1000
+ s -1 role
+ } {slave}
+
+ test {MASTER and SLAVE dataset should be identical after complex ops} {
+ createComplexDataset r 10000
+ after 500
+ if {[r debug digest] ne [r -1 debug digest]} {
+ set csv1 [csvdump r]
+ set csv2 [csvdump {r -1}]
+ set fd [open /tmp/repldump1.txt w]
+ puts -nonewline $fd $csv1
+ close $fd
+ set fd [open /tmp/repldump2.txt w]
+ puts -nonewline $fd $csv2
+ close $fd
+ puts "Master - Slave inconsistency"
+ puts "Run diff -u against /tmp/repldump*.txt for more info"
+ }
+ assert_equal [r debug digest] [r -1 debug digest]
+ }
+
+ test {MASTER and SLAVE consistency with expire} {
+ createComplexDataset r 50000 useexpire
+ after 4000 ;# Make sure everything expired before taking the digest
+ if {[r debug digest] ne [r -1 debug digest]} {
+ set csv1 [csvdump r]
+ set csv2 [csvdump {r -1}]
+ set fd [open /tmp/repldump1.txt w]
+ puts -nonewline $fd $csv1
+ close $fd
+ set fd [open /tmp/repldump2.txt w]
+ puts -nonewline $fd $csv2
+ close $fd
+ puts "Master - Slave inconsistency"
+ puts "Run diff -u against /tmp/repldump*.txt for more info"
+ }
+ assert_equal [r debug digest] [r -1 debug digest]
+ }
+ }
+}
+
start_server {tags {"repl"}} {
r set mykey foo
}
proc assert_encoding {enc key} {
- # swapped out value doesn't have encoding, so swap in first
- r debug swapin $key
- assert_match "* encoding:$enc *" [r debug object $key]
+ # Swapped out values don't have an encoding, so make sure that
+ # the value is swapped in before checking the encoding.
+ set dbg [r debug object $key]
+ while {[string match "* swapped at:*" $dbg]} {
+ r debug swapin $key
+ set dbg [r debug object $key]
+ }
+ assert_match "* encoding:$enc *" $dbg
}
proc assert_type {type key} {
# Return value for INFO property
proc status {r property} {
- if {[regexp "\r\n$property:(.*?)\r\n" [$r info] _ value]} {
+ if {[regexp "\r\n$property:(.*?)\r\n" [{*}$r info] _ value]} {
set _ $value
}
}
}
}
-proc createComplexDataset {r ops} {
+proc findKeyWithType {r type} {
+ for {set j 0} {$j < 20} {incr j} {
+ set k [{*}$r randomkey]
+ if {$k eq {}} {
+ return {}
+ }
+ if {[{*}$r type $k] eq $type} {
+ return $k
+ }
+ }
+ return {}
+}
+
+proc createComplexDataset {r ops {opt {}}} {
for {set j 0} {$j < $ops} {incr j} {
set k [randomKey]
+ set k2 [randomKey]
set f [randomValue]
set v [randomValue]
+
+ if {[lsearch -exact $opt useexpire] != -1} {
+ if {rand() < 0.1} {
+ {*}$r expire [randomKey] [randomInt 2]
+ }
+ }
+
randpath {
set d [expr {rand()}]
} {
} {
randpath {set d +inf} {set d -inf}
}
- set t [$r type $k]
+ set t [{*}$r type $k]
if {$t eq {none}} {
randpath {
- $r set $k $v
+ {*}$r set $k $v
} {
- $r lpush $k $v
+ {*}$r lpush $k $v
} {
- $r sadd $k $v
+ {*}$r sadd $k $v
} {
- $r zadd $k $d $v
+ {*}$r zadd $k $d $v
} {
- $r hset $k $f $v
+ {*}$r hset $k $f $v
+ } {
+ {*}$r del $k
}
- set t [$r type $k]
+ set t [{*}$r type $k]
}
switch $t {
# Nothing to do
}
{list} {
- randpath {$r lpush $k $v} \
- {$r rpush $k $v} \
- {$r lrem $k 0 $v} \
- {$r rpop $k} \
- {$r lpop $k}
+ randpath {{*}$r lpush $k $v} \
+ {{*}$r rpush $k $v} \
+ {{*}$r lrem $k 0 $v} \
+ {{*}$r rpop $k} \
+ {{*}$r lpop $k}
}
{set} {
- randpath {$r sadd $k $v} \
- {$r srem $k $v}
+ randpath {{*}$r sadd $k $v} \
+ {{*}$r srem $k $v} \
+ {
+ set otherset [findKeyWithType r set]
+ if {$otherset ne {}} {
+ randpath {
+ {*}$r sunionstore $k2 $k $otherset
+ } {
+ {*}$r sinterstore $k2 $k $otherset
+ } {
+ {*}$r sdiffstore $k2 $k $otherset
+ }
+ }
+ }
}
{zset} {
- randpath {$r zadd $k $d $v} \
- {$r zrem $k $v}
+ randpath {{*}$r zadd $k $d $v} \
+ {{*}$r zrem $k $v} \
+ {
+ set otherzset [findKeyWithType r zset]
+ if {$otherzset ne {}} {
+ randpath {
+ {*}$r zunionstore $k2 2 $k $otherzset
+ } {
+ {*}$r zinterstore $k2 2 $k $otherzset
+ }
+ }
+ }
}
{hash} {
- randpath {$r hset $k $f $v} \
- {$r hdel $k $f}
+ randpath {{*}$r hset $k $f $v} \
+ {{*}$r hdel $k $f}
}
}
}
}
set _ $cmd
}
+
+proc csvdump r {
+ set o {}
+ foreach k [lsort [{*}$r keys *]] {
+ set type [{*}$r type $k]
+ append o [csvstring $k] , [csvstring $type] ,
+ switch $type {
+ string {
+ append o [csvstring [{*}$r get $k]] "\n"
+ }
+ list {
+ foreach e [{*}$r lrange $k 0 -1] {
+ append o [csvstring $e] ,
+ }
+ append o "\n"
+ }
+ set {
+ foreach e [lsort [{*}$r smembers $k]] {
+ append o [csvstring $e] ,
+ }
+ append o "\n"
+ }
+ zset {
+ foreach e [{*}$r zrange $k 0 -1 withscores] {
+ append o [csvstring $e] ,
+ }
+ append o "\n"
+ }
+ hash {
+ set fields [{*}$r hgetall $k]
+ set newfields {}
+ foreach {k v} $fields {
+ lappend newfields [list $k $v]
+ }
+ set fields [lsort -index 0 $newfields]
+ foreach kv $fields {
+ append o [csvstring [lindex $kv 0]] ,
+ append o [csvstring [lindex $kv 1]] ,
+ }
+ append o "\n"
+ }
+ }
+ }
+ return $o
+}
+
+proc csvstring s {
+ return "\"$s\""
+}
execute_tests "unit/expire"
execute_tests "unit/other"
execute_tests "unit/cas"
-
+
+ cleanup
puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
if {$::failed > 0} {
puts "\n*** WARNING!!! $::failed FAILED TESTS ***\n"
+ exit 1
}
-
- cleanup
}
# parse arguments
r get novar2
} {foobared}
- test {SETNX will overwrite EXPIREing key} {
+ test {SETNX against volatile key} {
r set x 10
r expire x 10000
- r setnx x 20
- r get x
- } {20}
+ list [r setnx x 20] [r get x]
+ } {0 10}
test {EXISTS} {
set res {}
list [r msetnx x1 xxx y2 yyy] [r get x1] [r get y2]
} {1 xxx yyy}
- test {MSETNX should remove all the volatile keys even on failure} {
- r mset x 1 y 2 z 3
- r expire y 10000
- r expire z 10000
- list [r msetnx x A y B z C] [r mget x y z]
- } {0 {1 {} {}}}
+ test {STRLEN against non existing key} {
+ r strlen notakey
+ } {0}
+
+ test {STRLEN against integer} {
+ r set myinteger -555
+ r strlen myinteger
+ } {4}
+
+ test {STRLEN against plain string} {
+ r set mystring "foozzz0123456789 baz"
+ r strlen mystring
+ }
}
r ping
r exec
} {PONG}
+
+ test {WATCH will consider touched keys target of EXPIRE} {
+ r del x
+ r set x foo
+ r watch x
+ r expire x 10
+ r multi
+ r ping
+ r exec
+ } {}
+
+ test {WATCH will not consider touched expired keys} {
+ r del x
+ r set x foo
+ r expire x 2
+ r watch x
+ after 3000
+ r multi
+ r ping
+ r exec
+ } {PONG}
}
start_server {tags {"expire"}} {
- test {EXPIRE - don't set timeouts multiple times} {
+ test {EXPIRE - set timeouts multiple times} {
r set x foobar
set v1 [r expire x 5]
set v2 [r ttl x]
set v3 [r expire x 10]
set v4 [r ttl x]
+ r expire x 4
list $v1 $v2 $v3 $v4
- } {1 5 0 5}
+ } {1 5 1 10}
test {EXPIRE - It should be still possible to read 'x'} {
r get x
} {{} 0}
}
- test {EXPIRE - Delete on write policy} {
+ test {EXPIRE - write on expire should work} {
r del x
r lpush x foo
r expire x 1000
r lpush x bar
r lrange x 0 -1
- } {bar}
+ } {bar foo}
test {EXPIREAT - Check for EXPIRE alike behavior} {
r del x
catch {r setex z -10 foo} e
set _ $e
} {*invalid expire*}
+
+ test {PERSIST can undo an EXPIRE} {
+ r set x foo
+ r expire x 50
+ list [r ttl x] [r persist x] [r ttl x] [r get x]
+ } {50 1 -1 foo}
+
+ test {PERSIST returns 0 against non existing or non volatile keys} {
+ r set x foo
+ list [r persist foo] [r persist nokeyatall]
+ } {0 0}
}
set _ $err
} {*invalid*}
- if {![catch {package require sha1}]} {
- test {Check consistency of different data types after a reload} {
- r flushdb
- createComplexDataset r 10000
- set sha1 [r debug digest]
- r debug reload
- set sha1_after [r debug digest]
- expr {$sha1 eq $sha1_after}
- } {1}
-
- test {Same dataset digest if saving/reloading as AOF?} {
- r bgrewriteaof
- waitForBgrewriteaof r
- r debug loadaof
- set sha1_after [r debug digest]
- expr {$sha1 eq $sha1_after}
- } {1}
+ tags {consistency} {
+ if {![catch {package require sha1}]} {
+ test {Check consistency of different data types after a reload} {
+ r flushdb
+ createComplexDataset r 10000
+ set dump [csvdump r]
+ set sha1 [r debug digest]
+ r debug reload
+ set sha1_after [r debug digest]
+ if {$sha1 eq $sha1_after} {
+ set _ 1
+ } else {
+ set newdump [csvdump r]
+ puts "Consistency test failed!"
+ puts "You can inspect the two dumps in /tmp/repldump*.txt"
+
+ set fd [open /tmp/repldump1.txt w]
+ puts $fd $dump
+ close $fd
+ set fd [open /tmp/repldump2.txt w]
+ puts $fd $newdump
+ close $fd
+
+ set _ 0
+ }
+ } {1}
+
+ test {Same dataset digest if saving/reloading as AOF?} {
+ r bgrewriteaof
+ waitForBgrewriteaof r
+ r debug loadaof
+ set sha1_after [r debug digest]
+ if {$sha1 eq $sha1_after} {
+ set _ 1
+ } else {
+ set newdump [csvdump r]
+ puts "Consistency test failed!"
+ puts "You can inspect the two dumps in /tmp/aofdump*.txt"
+
+ set fd [open /tmp/aofdump1.txt w]
+ puts $fd $dump
+ close $fd
+ set fd [open /tmp/aofdump2.txt w]
+ puts $fd $newdump
+ close $fd
+
+ set _ 0
+ }
+ } {1}
+ }
}
test {EXPIRES after a reload (snapshot + append only file)} {
"list-max-ziplist-entries" 256
}
} {
+ # We need a value larger than list-max-ziplist-value to make sure
+ # the list has the right encoding when it is swapped in again.
+ array set largevalue {}
+ set largevalue(ziplist) "hello"
+ set largevalue(linkedlist) [string repeat "hello" 4]
+
test {LPUSH, RPUSH, LLENGTH, LINDEX - ziplist} {
# first lpush then rpush
assert_equal 1 [r lpush myziplist1 a]
}
test {LPUSH, RPUSH, LLENGTH, LINDEX - regular list} {
- # use a string of length 17 to ensure a regular list is used
- set large_value "aaaaaaaaaaaaaaaaa"
-
# first lpush then rpush
- assert_equal 1 [r lpush mylist1 $large_value]
+ assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)]
assert_encoding linkedlist mylist1
assert_equal 2 [r rpush mylist1 b]
assert_equal 3 [r rpush mylist1 c]
assert_equal 3 [r llen mylist1]
- assert_equal $large_value [r lindex mylist1 0]
+ assert_equal $largevalue(linkedlist) [r lindex mylist1 0]
assert_equal b [r lindex mylist1 1]
assert_equal c [r lindex mylist1 2]
# first rpush then lpush
- assert_equal 1 [r rpush mylist2 $large_value]
+ assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)]
assert_encoding linkedlist mylist2
assert_equal 2 [r lpush mylist2 b]
assert_equal 3 [r lpush mylist2 c]
assert_equal 3 [r llen mylist2]
assert_equal c [r lindex mylist2 0]
assert_equal b [r lindex mylist2 1]
- assert_equal $large_value [r lindex mylist2 2]
+ assert_equal $largevalue(linkedlist) [r lindex mylist2 2]
}
test {DEL a list - ziplist} {
proc create_linkedlist {key entries} {
r del $key
- r rpush $key "aaaaaaaaaaaaaaaaa"
foreach entry $entries { r rpush $key $entry }
- assert_equal "aaaaaaaaaaaaaaaaa" [r lpop $key]
assert_encoding linkedlist $key
}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
test "BLPOP, BRPOP: single existing list - $type" {
set rd [redis_deferring_client]
- create_$type blist {a b c d}
+ create_$type blist "a b $large c d"
$rd blpop blist 1
assert_equal {blist a} [$rd read]
test "BLPOP, BRPOP: multiple existing lists - $type" {
set rd [redis_deferring_client]
- create_$type blist1 {a b c}
- create_$type blist2 {d e f}
+ create_$type blist1 "a $large c"
+ create_$type blist2 "d $large f"
$rd blpop blist1 blist2 1
assert_equal {blist1 a} [$rd read]
test "BLPOP, BRPOP: second list has an entry - $type" {
set rd [redis_deferring_client]
r del blist1
- create_$type blist2 {d e f}
+ create_$type blist2 "d $large f"
$rd blpop blist1 blist2 1
assert_equal {blist2 d} [$rd read]
assert_equal 0 [r llen xlist]
}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
test "LPUSHX, RPUSHX - $type" {
- create_$type xlist {b c}
+ create_$type xlist "$large c"
assert_equal 3 [r rpushx xlist d]
assert_equal 4 [r lpushx xlist a]
- assert_equal {a b c d} [r lrange xlist 0 -1]
+ assert_equal "a $large c d" [r lrange xlist 0 -1]
}
test "LINSERT - $type" {
- create_$type xlist {a b c d}
+ create_$type xlist "a $large c d"
assert_equal 5 [r linsert xlist before c zz]
- assert_equal {a b zz c d} [r lrange xlist 0 10]
+ assert_equal "a $large zz c d" [r lrange xlist 0 10]
assert_equal 6 [r linsert xlist after c yy]
- assert_equal {a b zz c yy d} [r lrange xlist 0 10]
+ assert_equal "a $large zz c yy d" [r lrange xlist 0 10]
assert_equal 7 [r linsert xlist after d dd]
assert_equal -1 [r linsert xlist after bad ddd]
- assert_equal {a b zz c yy d dd} [r lrange xlist 0 10]
+ assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10]
assert_equal 8 [r linsert xlist before a aa]
assert_equal -1 [r linsert xlist before bad aaa]
- assert_equal {aa a b zz c yy d dd} [r lrange xlist 0 10]
+ assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10]
# check inserting integer encoded value
assert_equal 9 [r linsert xlist before aa 42]
}
test {LPUSHX, RPUSHX convert from ziplist to list} {
- set large_value "aaaaaaaaaaaaaaaaa"
+ set large $largevalue(linkedlist)
# convert when a large value is pushed
create_ziplist xlist a
- assert_equal 2 [r rpushx xlist $large_value]
+ assert_equal 2 [r rpushx xlist $large]
assert_encoding linkedlist xlist
create_ziplist xlist a
- assert_equal 2 [r lpushx xlist $large_value]
+ assert_equal 2 [r lpushx xlist $large]
assert_encoding linkedlist xlist
# convert when the length threshold is exceeded
}
test {LINSERT convert from ziplist to list} {
- set large_value "aaaaaaaaaaaaaaaaa"
+ set large $largevalue(linkedlist)
# convert when a large value is inserted
create_ziplist xlist a
- assert_equal 2 [r linsert xlist before a $large_value]
+ assert_equal 2 [r linsert xlist before a $large]
assert_encoding linkedlist xlist
create_ziplist xlist a
- assert_equal 2 [r linsert xlist after a $large_value]
+ assert_equal 2 [r linsert xlist after a $large]
assert_encoding linkedlist xlist
# convert when the length threshold is exceeded
assert_error ERR* {r rpush mylist 0}
}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
test "RPOPLPUSH base case - $type" {
r del mylist1 mylist2
- create_$type mylist1 {a b c d}
+ create_$type mylist1 "a $large c d"
assert_equal d [r rpoplpush mylist1 mylist2]
assert_equal c [r rpoplpush mylist1 mylist2]
- assert_equal {a b} [r lrange mylist1 0 -1]
- assert_equal {c d} [r lrange mylist2 0 -1]
+ assert_equal "a $large" [r lrange mylist1 0 -1]
+ assert_equal "c d" [r lrange mylist2 0 -1]
assert_encoding ziplist mylist2
}
test "RPOPLPUSH with the same list as src and dst - $type" {
- create_$type mylist {a b c}
- assert_equal {a b c} [r lrange mylist 0 -1]
+ create_$type mylist "a $large c"
+ assert_equal "a $large c" [r lrange mylist 0 -1]
assert_equal c [r rpoplpush mylist mylist]
- assert_equal {c a b} [r lrange mylist 0 -1]
+ assert_equal "c a $large" [r lrange mylist 0 -1]
}
- foreach othertype {ziplist linkedlist} {
+ foreach {othertype otherlarge} [array get largevalue] {
test "RPOPLPUSH with $type source and existing target $othertype" {
- create_$type srclist {a b c d}
- create_$othertype dstlist {x}
- assert_equal d [r rpoplpush srclist dstlist]
+ create_$type srclist "a b c $large"
+ create_$othertype dstlist "$otherlarge"
+ assert_equal $large [r rpoplpush srclist dstlist]
assert_equal c [r rpoplpush srclist dstlist]
- assert_equal {a b} [r lrange srclist 0 -1]
- assert_equal {c d x} [r lrange dstlist 0 -1]
+ assert_equal "a b" [r lrange srclist 0 -1]
+ assert_equal "c $large $otherlarge" [r lrange dstlist 0 -1]
+
+ # When we rpoplpush'ed a large value, dstlist should be
+ # converted to the same encoding as srclist.
+ if {$type eq "linkedlist"} {
+ assert_encoding linkedlist dstlist
+ }
}
}
}
assert_equal {} [r rpoplpush srclist dstlist]
} {}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
test "Basic LPOP/RPOP - $type" {
- create_$type mylist {0 1 2}
- assert_equal 0 [r lpop mylist]
+ create_$type mylist "$large 1 2"
+ assert_equal $large [r lpop mylist]
assert_equal 2 [r rpop mylist]
assert_equal 1 [r lpop mylist]
assert_equal 0 [r llen mylist]
}
}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
test "LRANGE basics - $type" {
- create_$type mylist {0 1 2 3 4 5 6 7 8 9}
+ create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2]
assert_equal {7 8 9} [r lrange mylist -3 -1]
assert_equal {4} [r lrange mylist 4 4]
}
test "LRANGE inverted indexes - $type" {
- create_$type mylist {0 1 2 3 4 5 6 7 8 9}
+ create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
assert_equal {} [r lrange mylist 6 2]
}
test "LRANGE out of range indexes including the full list - $type" {
- create_$type mylist {1 2 3}
- assert_equal {1 2 3} [r lrange mylist -1000 1000]
+ create_$type mylist "$large 1 2 3"
+ assert_equal "$large 1 2 3" [r lrange mylist -1000 1000]
}
test "LRANGE out of range negative end index - $type" {
- create_$type mylist {1 2 3}
- assert_equal {1} [r lrange mylist 0 -3]
- assert_equal {} [r lrange mylist 0 -4]
+ create_$type mylist "$large 1 2 3"
+ assert_equal $large [r lrange mylist 0 -4]
+ assert_equal {} [r lrange mylist 0 -5]
}
}
assert_equal {} [r lrange nosuchkey 0 1]
}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
proc trim_list {type min max} {
+ upvar 1 large large
r del mylist
- create_$type mylist {1 2 3 4 5}
+ create_$type mylist "1 2 3 4 $large"
r ltrim mylist $min $max
r lrange mylist 0 -1
}
test "LTRIM basics - $type" {
- assert_equal {1} [trim_list $type 0 0]
- assert_equal {1 2} [trim_list $type 0 1]
- assert_equal {1 2 3} [trim_list $type 0 2]
- assert_equal {2 3} [trim_list $type 1 2]
- assert_equal {2 3 4 5} [trim_list $type 1 -1]
- assert_equal {2 3 4} [trim_list $type 1 -2]
- assert_equal {4 5} [trim_list $type -2 -1]
- assert_equal {5} [trim_list $type -1 -1]
- assert_equal {1 2 3 4 5} [trim_list $type -5 -1]
- assert_equal {1 2 3 4 5} [trim_list $type -10 10]
- assert_equal {1 2 3 4 5} [trim_list $type 0 5]
- assert_equal {1 2 3 4 5} [trim_list $type 0 10]
+ assert_equal "1" [trim_list $type 0 0]
+ assert_equal "1 2" [trim_list $type 0 1]
+ assert_equal "1 2 3" [trim_list $type 0 2]
+ assert_equal "2 3" [trim_list $type 1 2]
+ assert_equal "2 3 4 $large" [trim_list $type 1 -1]
+ assert_equal "2 3 4" [trim_list $type 1 -2]
+ assert_equal "4 $large" [trim_list $type -2 -1]
+ assert_equal "$large" [trim_list $type -1 -1]
+ assert_equal "1 2 3 4 $large" [trim_list $type -5 -1]
+ assert_equal "1 2 3 4 $large" [trim_list $type -10 10]
+ assert_equal "1 2 3 4 $large" [trim_list $type 0 5]
+ assert_equal "1 2 3 4 $large" [trim_list $type 0 10]
}
test "LTRIM out of range negative end index - $type" {
set mylist {}
set startlen 32
r del mylist
+
+ # Start with the large value to ensure the
+ # right encoding is used.
+ r rpush mylist $large
+ lappend mylist $large
+
for {set i 0} {$i < $startlen} {incr i} {
set str [randomInt 9223372036854775807]
r rpush mylist $str
lappend mylist $str
}
- # do a push/pop of a large value to convert to a real list
- if {$type eq "list"} {
- r rpush mylist "aaaaaaaaaaaaaaaaa"
- r rpop mylist
- assert_encoding linkedlist mylist
- }
-
- for {set i 0} {$i < 10000} {incr i} {
+ for {set i 0} {$i < 1000} {incr i} {
set min [expr {int(rand()*$startlen)}]
set max [expr {$min+int(rand()*$startlen)}]
set mylist [lrange $mylist $min $max]
}
}
- foreach type {ziplist linkedlist} {
+ foreach {type large} [array get largevalue] {
test "LSET - $type" {
- create_$type mylist {99 98 97 96 95}
+ create_$type mylist "99 98 $large 96 95"
r lset mylist 1 foo
r lset mylist -1 bar
- assert_equal {99 foo 97 96 bar} [r lrange mylist 0 -1]
+ assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1]
}
test "LSET out of range index - $type" {
assert_error ERR*value* {r lset nolist 0 foo}
}
- foreach type {ziplist linkedlist} {
+ foreach {type e} [array get largevalue] {
test "LREM remove all the occurrences - $type" {
- create_$type mylist {foo bar foobar foobared zap bar test foo}
+ create_$type mylist "$e foo bar foobar foobared zap bar test foo"
assert_equal 2 [r lrem mylist 0 bar]
- assert_equal {foo foobar foobared zap test foo} [r lrange mylist 0 -1]
+ assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1]
}
test "LREM remove the first occurrence - $type" {
assert_equal 1 [r lrem mylist 1 foo]
- assert_equal {foobar foobared zap test foo} [r lrange mylist 0 -1]
+ assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1]
}
test "LREM remove non existing element - $type" {
assert_equal 0 [r lrem mylist 1 nosuchelement]
- assert_equal {foobar foobared zap test foo} [r lrange mylist 0 -1]
+ assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1]
}
test "LREM starting from tail with negative count - $type" {
- create_$type mylist {foo bar foobar foobared zap bar test foo foo}
+ create_$type mylist "$e foo bar foobar foobared zap bar test foo foo"
assert_equal 1 [r lrem mylist -1 bar]
- assert_equal {foo bar foobar foobared zap test foo foo} [r lrange mylist 0 -1]
+ assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1]
}
test "LREM starting from tail with negative count (2) - $type" {
assert_equal 2 [r lrem mylist -2 foo]
- assert_equal {foo bar foobar foobared zap test} [r lrange mylist 0 -1]
+ assert_equal "$e foo bar foobar foobared zap test" [r lrange mylist 0 -1]
}
test "LREM deleting objects that may be int encoded - $type" {
- create_$type myotherlist {1 2 3}
+ create_$type myotherlist "$e 1 2 3"
assert_equal 1 [r lrem myotherlist 1 2]
- assert_equal 2 [r llen myotherlist]
+ assert_equal 3 [r llen myotherlist]
}
}
}
list [r zinterstore zsetc 2 zseta zsetb aggregate max] [r zrange zsetc 0 -1 withscores]
} {2 {b 2 c 3}}
+ foreach cmd {ZUNIONSTORE ZINTERSTORE} {
+ test "$cmd with +inf/-inf scores" {
+ r del zsetinf1 zsetinf2
+
+ r zadd zsetinf1 +inf key
+ r zadd zsetinf2 +inf key
+ r $cmd zsetinf3 2 zsetinf1 zsetinf2
+ assert_equal inf [r zscore zsetinf3 key]
+
+ r zadd zsetinf1 -inf key
+ r zadd zsetinf2 +inf key
+ r $cmd zsetinf3 2 zsetinf1 zsetinf2
+ assert_equal 0 [r zscore zsetinf3 key]
+
+ r zadd zsetinf1 +inf key
+ r zadd zsetinf2 -inf key
+ r $cmd zsetinf3 2 zsetinf1 zsetinf2
+ assert_equal 0 [r zscore zsetinf3 key]
+
+ r zadd zsetinf1 -inf key
+ r zadd zsetinf2 -inf key
+ r $cmd zsetinf3 2 zsetinf1 zsetinf2
+ assert_equal -inf [r zscore zsetinf3 key]
+ }
+
+ test "$cmd with NaN weights" {
+ r del zsetinf1 zsetinf2
+
+ r zadd zsetinf1 1.0 key
+ r zadd zsetinf2 1.0 key
+ assert_error "*weight value is not a double*" {
+ r $cmd zsetinf3 2 zsetinf1 zsetinf2 weights nan nan
+ }
+ }
+ }
+
tags {"slow"} {
test {ZSETs skiplist implementation backlink consistency test} {
set diff 0
} {}
}
- test {ZSET element can't be set to nan with ZADD} {
- set e {}
- catch {r zadd myzset nan abc} e
- set _ $e
- } {*Not A Number*}
+ test {ZSET element can't be set to NaN with ZADD} {
+ assert_error "*not a double*" {r zadd myzset nan abc}
+ }
- test {ZSET element can't be set to nan with ZINCRBY} {
- set e {}
- catch {r zincrby myzset nan abc} e
- set _ $e
- } {*Not A Number*}
+ test {ZSET element can't be set to NaN with ZINCRBY} {
+ assert_error "*not a double*" {r zadd myzset nan abc}
+ }
- test {ZINCRBY calls leading to Nan are refused} {
- set e {}
+ test {ZINCRBY calls leading to NaN result in error} {
r zincrby myzset +inf abc
- catch {r zincrby myzset -inf abc} e
- set _ $e
- } {*Not A Number*}
+ assert_error "*NaN*" {r zincrby myzset -inf abc}
+ }
}