Commit bfe85f7c authored by antirez's avatar antirez
Browse files

Merge branch 'unstable'

parents b8513c93 6b52ad87
...@@ -42,6 +42,16 @@ struct sdshdr { ...@@ -42,6 +42,16 @@ struct sdshdr {
char buf[]; char buf[];
}; };
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
sds sdsnewlen(const void *init, size_t initlen); sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init); sds sdsnew(const char *init);
sds sdsempty(); sds sdsempty();
...@@ -74,5 +84,6 @@ void sdstoupper(sds s); ...@@ -74,5 +84,6 @@ void sdstoupper(sds s);
sds sdsfromlonglong(long long value); sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, char *p, size_t len); sds sdscatrepr(sds s, char *p, size_t len);
sds *sdssplitargs(char *line, int *argc); sds *sdssplitargs(char *line, int *argc);
void sdssplitargs_free(sds *argv, int argc);
#endif #endif
...@@ -199,6 +199,9 @@ void sortCommand(redisClient *c) { ...@@ -199,6 +199,9 @@ void sortCommand(redisClient *c) {
j++; j++;
} }
/* Destructively convert encoded sorted sets for SORT. */
if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);
/* Load the sorting vector with all the objects to sort */ /* Load the sorting vector with all the objects to sort */
switch(sortval->type) { switch(sortval->type) {
case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
......
...@@ -107,6 +107,7 @@ int syncReadLine(int fd, char *ptr, ssize_t size, int timeout) { ...@@ -107,6 +107,7 @@ int syncReadLine(int fd, char *ptr, ssize_t size, int timeout) {
int fwriteBulkString(FILE *fp, char *s, unsigned long len) { int fwriteBulkString(FILE *fp, char *s, unsigned long len) {
char cbuf[128]; char cbuf[128];
int clen; int clen;
cbuf[0] = '$'; cbuf[0] = '$';
clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,len); clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,len);
cbuf[clen++] = '\r'; cbuf[clen++] = '\r';
...@@ -117,6 +118,19 @@ int fwriteBulkString(FILE *fp, char *s, unsigned long len) { ...@@ -117,6 +118,19 @@ int fwriteBulkString(FILE *fp, char *s, unsigned long len) {
return 1; return 1;
} }
/* Write a multi bulk count in the form "*<count>\r\n" */
int fwriteBulkCount(FILE *fp, char prefix, int count) {
char cbuf[128];
int clen;
cbuf[0] = prefix;
clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,count);
cbuf[clen++] = '\r';
cbuf[clen++] = '\n';
if (fwrite(cbuf,clen,1,fp) == 0) return 0;
return 1;
}
/* Write a double value in bulk format $<count>\r\n<payload>\r\n */ /* Write a double value in bulk format $<count>\r\n<payload>\r\n */
int fwriteBulkDouble(FILE *fp, double d) { int fwriteBulkDouble(FILE *fp, double d) {
char buf[128], dbuf[128]; char buf[128], dbuf[128];
......
...@@ -396,17 +396,22 @@ void hmgetCommand(redisClient *c) { ...@@ -396,17 +396,22 @@ void hmgetCommand(redisClient *c) {
void hdelCommand(redisClient *c) { void hdelCommand(redisClient *c) {
robj *o; robj *o;
int j, deleted = 0;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return; checkType(c,o,REDIS_HASH)) return;
if (hashTypeDelete(o,c->argv[2])) { for (j = 2; j < c->argc; j++) {
if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); if (hashTypeDelete(o,c->argv[j])) {
addReply(c,shared.cone); if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
deleted++;
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]);
server.dirty++; server.dirty += deleted;
} else {
addReply(c,shared.czero);
} }
addReplyLongLong(c,deleted);
} }
void hlenCommand(redisClient *c) { void hlenCommand(redisClient *c) {
......
...@@ -259,30 +259,35 @@ void listTypeConvert(robj *subject, int enc) { ...@@ -259,30 +259,35 @@ void listTypeConvert(robj *subject, int enc) {
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
void pushGenericCommand(redisClient *c, int where) { void pushGenericCommand(redisClient *c, int where) {
int j, addlen = 0, pushed = 0;
robj *lobj = lookupKeyWrite(c->db,c->argv[1]); robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]); int may_have_waiting_clients = (lobj == NULL);
if (lobj == NULL) {
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { if (lobj && lobj->type != REDIS_LIST) {
addReply(c,shared.cone); addReply(c,shared.wrongtypeerr);
return; return;
} }
lobj = createZiplistObject();
dbAdd(c->db,c->argv[1],lobj); for (j = 2; j < c->argc; j++) {
} else { c->argv[j] = tryObjectEncoding(c->argv[j]);
if (lobj->type != REDIS_LIST) { if (may_have_waiting_clients) {
addReply(c,shared.wrongtypeerr); if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) {
return; addlen++;
continue;
} else {
may_have_waiting_clients = 0;
}
} }
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { if (!lobj) {
signalModifiedKey(c->db,c->argv[1]); lobj = createZiplistObject();
addReply(c,shared.cone); dbAdd(c->db,c->argv[1],lobj);
return;
} }
listTypePush(lobj,c->argv[j],where);
pushed++;
} }
listTypePush(lobj,c->argv[2],where); addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0));
addReplyLongLong(c,listTypeLength(lobj)); if (pushed) signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]); server.dirty += pushed;
server.dirty++;
} }
void lpushCommand(redisClient *c) { void lpushCommand(redisClient *c) {
......
...@@ -218,9 +218,9 @@ void setTypeConvert(robj *setobj, int enc) { ...@@ -218,9 +218,9 @@ void setTypeConvert(robj *setobj, int enc) {
void saddCommand(redisClient *c) { void saddCommand(redisClient *c) {
robj *set; robj *set;
int j, added = 0;
set = lookupKeyWrite(c->db,c->argv[1]); set = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (set == NULL) { if (set == NULL) {
set = setTypeCreate(c->argv[2]); set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set); dbAdd(c->db,c->argv[1],set);
...@@ -230,30 +230,34 @@ void saddCommand(redisClient *c) { ...@@ -230,30 +230,34 @@ void saddCommand(redisClient *c) {
return; return;
} }
} }
if (setTypeAdd(set,c->argv[2])) {
signalModifiedKey(c->db,c->argv[1]); for (j = 2; j < c->argc; j++) {
server.dirty++; c->argv[j] = tryObjectEncoding(c->argv[j]);
addReply(c,shared.cone); if (setTypeAdd(set,c->argv[j])) added++;
} else {
addReply(c,shared.czero);
} }
if (added) signalModifiedKey(c->db,c->argv[1]);
server.dirty += added;
addReplyLongLong(c,added);
} }
void sremCommand(redisClient *c) { void sremCommand(redisClient *c) {
robj *set; robj *set;
int j, deleted = 0;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return; checkType(c,set,REDIS_SET)) return;
c->argv[2] = tryObjectEncoding(c->argv[2]); for (j = 2; j < c->argc; j++) {
if (setTypeRemove(set,c->argv[2])) { if (setTypeRemove(set,c->argv[j])) {
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]); if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
deleted++;
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]);
server.dirty++; server.dirty += deleted;
addReply(c,shared.cone);
} else {
addReply(c,shared.czero);
} }
addReplyLongLong(c,deleted);
} }
void smoveCommand(redisClient *c) { void smoveCommand(redisClient *c) {
......
...@@ -174,11 +174,81 @@ int zslDelete(zskiplist *zsl, double score, robj *obj) { ...@@ -174,11 +174,81 @@ int zslDelete(zskiplist *zsl, double score, robj *obj) {
return 0; /* not found */ return 0; /* not found */
} }
/* Struct to hold a inclusive/exclusive range spec. */ static int zslValueGteMin(double value, zrangespec *spec) {
typedef struct { return spec->minex ? (value > spec->min) : (value >= spec->min);
double min, max; }
int minex, maxex; /* are min or max exclusive? */
} zrangespec; static int zslValueLteMax(double value, zrangespec *spec) {
return spec->maxex ? (value < spec->max) : (value <= spec->max);
}
/* Returns if there is a part of the zset is in range. */
int zslIsInRange(zskiplist *zsl, zrangespec *range) {
zskiplistNode *x;
/* Test for ranges that will always be empty. */
if (range->min > range->max ||
(range->min == range->max && (range->minex || range->maxex)))
return 0;
x = zsl->tail;
if (x == NULL || !zslValueGteMin(x->score,range))
return 0;
x = zsl->header->level[0].forward;
if (x == NULL || !zslValueLteMax(x->score,range))
return 0;
return 1;
}
/* Find the first node that is contained in the specified range.
* Returns NULL when no element is contained in the range. */
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range) {
zskiplistNode *x;
int i;
/* If everything is out of range, return early. */
if (!zslIsInRange(zsl,&range)) return NULL;
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
/* Go forward while *OUT* of range. */
while (x->level[i].forward &&
!zslValueGteMin(x->level[i].forward->score,&range))
x = x->level[i].forward;
}
/* This is an inner range, so the next node cannot be NULL. */
x = x->level[0].forward;
redisAssert(x != NULL);
/* Check if score <= max. */
if (!zslValueLteMax(x->score,&range)) return NULL;
return x;
}
/* Find the last node that is contained in the specified range.
* Returns NULL when no element is contained in the range. */
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec range) {
zskiplistNode *x;
int i;
/* If everything is out of range, return early. */
if (!zslIsInRange(zsl,&range)) return NULL;
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
/* Go forward while *IN* range. */
while (x->level[i].forward &&
zslValueLteMax(x->level[i].forward->score,&range))
x = x->level[i].forward;
}
/* This is an inner range, so this node cannot be NULL. */
redisAssert(x != NULL);
/* Check if score >= min. */
if (!zslValueGteMin(x->score,&range)) return NULL;
return x;
}
/* Delete all the elements with score between min and max from the skiplist. /* Delete all the elements with score between min and max from the skiplist.
* Min and mx are inclusive, so a score >= min || score <= max is deleted. * Min and mx are inclusive, so a score >= min || score <= max is deleted.
...@@ -243,27 +313,11 @@ unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned ...@@ -243,27 +313,11 @@ unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned
return removed; return removed;
} }
/* Find the first node having a score equal or greater than the specified one.
* Returns NULL if there is no match. */
zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
zskiplistNode *x;
int i;
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward && x->level[i].forward->score < score)
x = x->level[i].forward;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
return x->level[0].forward;
}
/* Find the rank for an element by both score and key. /* Find the rank for an element by both score and key.
* Returns 0 when the element cannot be found, rank otherwise. * Returns 0 when the element cannot be found, rank otherwise.
* Note that the rank is 1-based due to the span of zsl->header to the * Note that the rank is 1-based due to the span of zsl->header to the
* first element. */ * first element. */
unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) { unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
zskiplistNode *x; zskiplistNode *x;
unsigned long rank = 0; unsigned long rank = 0;
int i; int i;
...@@ -287,7 +341,7 @@ unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) { ...@@ -287,7 +341,7 @@ unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
} }
/* Finds an element by its rank. The rank argument needs to be 1-based. */ /* Finds an element by its rank. The rank argument needs to be 1-based. */
zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) { zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
zskiplistNode *x; zskiplistNode *x;
unsigned long traversed = 0; unsigned long traversed = 0;
int i; int i;
...@@ -343,150 +397,603 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) { ...@@ -343,150 +397,603 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
return REDIS_OK; return REDIS_OK;
} }
/*-----------------------------------------------------------------------------
* Ziplist-backed sorted set API
*----------------------------------------------------------------------------*/
double zzlGetScore(unsigned char *sptr) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
char buf[128];
double score;
redisAssert(sptr != NULL);
redisAssert(ziplistGet(sptr,&vstr,&vlen,&vlong));
if (vstr) {
memcpy(buf,vstr,vlen);
buf[vlen] = '\0';
score = strtod(buf,NULL);
} else {
score = vlong;
}
return score;
}
/* Compare element in sorted set with given element. */
int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int clen) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
unsigned char vbuf[32];
int minlen, cmp;
redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
if (vstr == NULL) {
/* Store string representation of long long in buf. */
vlen = ll2string((char*)vbuf,sizeof(vbuf),vlong);
vstr = vbuf;
}
minlen = (vlen < clen) ? vlen : clen;
cmp = memcmp(vstr,cstr,minlen);
if (cmp == 0) return vlen-clen;
return cmp;
}
unsigned int zzlLength(unsigned char *zl) {
return ziplistLen(zl)/2;
}
/* Move to next entry based on the values in eptr and sptr. Both are set to
* NULL when there is no next entry. */
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr) {
unsigned char *_eptr, *_sptr;
redisAssert(*eptr != NULL && *sptr != NULL);
_eptr = ziplistNext(zl,*sptr);
if (_eptr != NULL) {
_sptr = ziplistNext(zl,_eptr);
redisAssert(_sptr != NULL);
} else {
/* No next entry. */
_sptr = NULL;
}
*eptr = _eptr;
*sptr = _sptr;
}
/* Move to the previous entry based on the values in eptr and sptr. Both are
* set to NULL when there is no next entry. */
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr) {
unsigned char *_eptr, *_sptr;
redisAssert(*eptr != NULL && *sptr != NULL);
_sptr = ziplistPrev(zl,*eptr);
if (_sptr != NULL) {
_eptr = ziplistPrev(zl,_sptr);
redisAssert(_eptr != NULL);
} else {
/* No previous entry. */
_eptr = NULL;
}
*eptr = _eptr;
*sptr = _sptr;
}
/* Returns if there is a part of the zset is in range. Should only be used
* internally by zzlFirstInRange and zzlLastInRange. */
int zzlIsInRange(unsigned char *zl, zrangespec *range) {
unsigned char *p;
double score;
/* Test for ranges that will always be empty. */
if (range->min > range->max ||
(range->min == range->max && (range->minex || range->maxex)))
return 0;
p = ziplistIndex(zl,-1); /* Last score. */
redisAssert(p != NULL);
score = zzlGetScore(p);
if (!zslValueGteMin(score,range))
return 0;
p = ziplistIndex(zl,1); /* First score. */
redisAssert(p != NULL);
score = zzlGetScore(p);
if (!zslValueLteMax(score,range))
return 0;
return 1;
}
/* Find pointer to the first element contained in the specified range.
* Returns NULL when no element is contained in the range. */
unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec range) {
unsigned char *eptr = ziplistIndex(zl,0), *sptr;
double score;
/* If everything is out of range, return early. */
if (!zzlIsInRange(zl,&range)) return NULL;
while (eptr != NULL) {
sptr = ziplistNext(zl,eptr);
redisAssert(sptr != NULL);
score = zzlGetScore(sptr);
if (zslValueGteMin(score,&range)) {
/* Check if score <= max. */
if (zslValueLteMax(score,&range))
return eptr;
return NULL;
}
/* Move to next element. */
eptr = ziplistNext(zl,sptr);
}
return NULL;
}
/* Find pointer to the last element contained in the specified range.
* Returns NULL when no element is contained in the range. */
unsigned char *zzlLastInRange(unsigned char *zl, zrangespec range) {
unsigned char *eptr = ziplistIndex(zl,-2), *sptr;
double score;
/* If everything is out of range, return early. */
if (!zzlIsInRange(zl,&range)) return NULL;
while (eptr != NULL) {
sptr = ziplistNext(zl,eptr);
redisAssert(sptr != NULL);
score = zzlGetScore(sptr);
if (zslValueLteMax(score,&range)) {
/* Check if score >= min. */
if (zslValueGteMin(score,&range))
return eptr;
return NULL;
}
/* Move to previous element by moving to the score of previous element.
* When this returns NULL, we know there also is no element. */
sptr = ziplistPrev(zl,eptr);
if (sptr != NULL)
redisAssert((eptr = ziplistPrev(zl,sptr)) != NULL);
else
eptr = NULL;
}
return NULL;
}
unsigned char *zzlFind(unsigned char *zl, robj *ele, double *score) {
unsigned char *eptr = ziplistIndex(zl,0), *sptr;
ele = getDecodedObject(ele);
while (eptr != NULL) {
sptr = ziplistNext(zl,eptr);
redisAssert(sptr != NULL);
if (ziplistCompare(eptr,ele->ptr,sdslen(ele->ptr))) {
/* Matching element, pull out score. */
if (score != NULL) *score = zzlGetScore(sptr);
decrRefCount(ele);
return eptr;
}
/* Move to next element. */
eptr = ziplistNext(zl,sptr);
}
decrRefCount(ele);
return NULL;
}
/* Delete (element,score) pair from ziplist. Use local copy of eptr because we
* don't want to modify the one given as argument. */
unsigned char *zzlDelete(unsigned char *zl, unsigned char *eptr) {
unsigned char *p = eptr;
/* TODO: add function to ziplist API to delete N elements from offset. */
zl = ziplistDelete(zl,&p);
zl = ziplistDelete(zl,&p);
return zl;
}
unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, robj *ele, double score) {
unsigned char *sptr;
char scorebuf[128];
int scorelen;
size_t offset;
redisAssert(ele->encoding == REDIS_ENCODING_RAW);
scorelen = d2string(scorebuf,sizeof(scorebuf),score);
if (eptr == NULL) {
zl = ziplistPush(zl,ele->ptr,sdslen(ele->ptr),ZIPLIST_TAIL);
zl = ziplistPush(zl,(unsigned char*)scorebuf,scorelen,ZIPLIST_TAIL);
} else {
/* Keep offset relative to zl, as it might be re-allocated. */
offset = eptr-zl;
zl = ziplistInsert(zl,eptr,ele->ptr,sdslen(ele->ptr));
eptr = zl+offset;
/* Insert score after the element. */
redisAssert((sptr = ziplistNext(zl,eptr)) != NULL);
zl = ziplistInsert(zl,sptr,(unsigned char*)scorebuf,scorelen);
}
return zl;
}
/* Insert (element,score) pair in ziplist. This function assumes the element is
* not yet present in the list. */
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score) {
unsigned char *eptr = ziplistIndex(zl,0), *sptr;
double s;
ele = getDecodedObject(ele);
while (eptr != NULL) {
sptr = ziplistNext(zl,eptr);
redisAssert(sptr != NULL);
s = zzlGetScore(sptr);
if (s > score) {
/* First element with score larger than score for element to be
* inserted. This means we should take its spot in the list to
* maintain ordering. */
zl = zzlInsertAt(zl,eptr,ele,score);
break;
} else if (s == score) {
/* Ensure lexicographical ordering for elements. */
if (zzlCompareElements(eptr,ele->ptr,sdslen(ele->ptr)) > 0) {
zl = zzlInsertAt(zl,eptr,ele,score);
break;
}
}
/* Move to next element. */
eptr = ziplistNext(zl,sptr);
}
/* Push on tail of list when it was not yet inserted. */
if (eptr == NULL)
zl = zzlInsertAt(zl,NULL,ele,score);
decrRefCount(ele);
return zl;
}
unsigned char *zzlDeleteRangeByScore(unsigned char *zl, zrangespec range, unsigned long *deleted) {
unsigned char *eptr, *sptr;
double score;
unsigned long num = 0;
if (deleted != NULL) *deleted = 0;
eptr = zzlFirstInRange(zl,range);
if (eptr == NULL) return zl;
/* When the tail of the ziplist is deleted, eptr will point to the sentinel
* byte and ziplistNext will return NULL. */
while ((sptr = ziplistNext(zl,eptr)) != NULL) {
score = zzlGetScore(sptr);
if (zslValueLteMax(score,&range)) {
/* Delete both the element and the score. */
zl = ziplistDelete(zl,&eptr);
zl = ziplistDelete(zl,&eptr);
num++;
} else {
/* No longer in range. */
break;
}
}
if (deleted != NULL) *deleted = num;
return zl;
}
/* Delete all the elements with rank between start and end from the skiplist.
* Start and end are inclusive. Note that start and end need to be 1-based */
unsigned char *zzlDeleteRangeByRank(unsigned char *zl, unsigned int start, unsigned int end, unsigned long *deleted) {
unsigned int num = (end-start)+1;
if (deleted) *deleted = num;
zl = ziplistDeleteRange(zl,2*(start-1),2*num);
return zl;
}
/*-----------------------------------------------------------------------------
* Common sorted set API
*----------------------------------------------------------------------------*/
unsigned int zsetLength(robj *zobj) {
int length = -1;
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
length = zzlLength(zobj->ptr);
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
length = ((zset*)zobj->ptr)->zsl->length;
} else {
redisPanic("Unknown sorted set encoding");
}
return length;
}
void zsetConvert(robj *zobj, int encoding) {
zset *zs;
zskiplistNode *node, *next;
robj *ele;
double score;
if (zobj->encoding == encoding) return;
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl = zobj->ptr;
unsigned char *eptr, *sptr;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (encoding != REDIS_ENCODING_SKIPLIST)
redisPanic("Unknown target encoding");
zs = zmalloc(sizeof(*zs));
zs->dict = dictCreate(&zsetDictType,NULL);
zs->zsl = zslCreate();
eptr = ziplistIndex(zl,0);
redisAssert(eptr != NULL);
sptr = ziplistNext(zl,eptr);
redisAssert(sptr != NULL);
while (eptr != NULL) {
score = zzlGetScore(sptr);
redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
if (vstr == NULL)
ele = createStringObjectFromLongLong(vlong);
else
ele = createStringObject((char*)vstr,vlen);
/* Has incremented refcount since it was just created. */
node = zslInsert(zs->zsl,score,ele);
redisAssert(dictAdd(zs->dict,ele,&node->score) == DICT_OK);
incrRefCount(ele); /* Added to dictionary. */
zzlNext(zl,&eptr,&sptr);
}
zfree(zobj->ptr);
zobj->ptr = zs;
zobj->encoding = REDIS_ENCODING_SKIPLIST;
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
unsigned char *zl = ziplistNew();
if (encoding != REDIS_ENCODING_ZIPLIST)
redisPanic("Unknown target encoding");
/* Approach similar to zslFree(), since we want to free the skiplist at
* the same time as creating the ziplist. */
zs = zobj->ptr;
dictRelease(zs->dict);
node = zs->zsl->header->level[0].forward;
zfree(zs->zsl->header);
zfree(zs->zsl);
while (node) {
ele = getDecodedObject(node->obj);
zl = zzlInsertAt(zl,NULL,ele,node->score);
decrRefCount(ele);
next = node->level[0].forward;
zslFreeNode(node);
node = next;
}
zfree(zs);
zobj->ptr = zl;
zobj->encoding = REDIS_ENCODING_ZIPLIST;
} else {
redisPanic("Unknown sorted set encoding");
}
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Sorted set commands * Sorted set commands
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* This generic command implements both ZADD and ZINCRBY. */ /* This generic command implements both ZADD and ZINCRBY. */
void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int incr) { void zaddGenericCommand(redisClient *c, int incr) {
robj *zsetobj; static char *nanerr = "resulting score is not a number (NaN)";
zset *zs; robj *key = c->argv[1];
zskiplistNode *znode; robj *ele;
robj *zobj;
robj *curobj;
double score, curscore = 0.0;
zsetobj = lookupKeyWrite(c->db,key); if (getDoubleFromObjectOrReply(c,c->argv[2],&score,NULL) != REDIS_OK)
if (zsetobj == NULL) { return;
zsetobj = createZsetObject();
dbAdd(c->db,key,zsetobj); zobj = lookupKeyWrite(c->db,key);
if (zobj == NULL) {
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr))
{
zobj = createZsetObject();
} else {
zobj = createZsetZiplistObject();
}
dbAdd(c->db,key,zobj);
} else { } else {
if (zsetobj->type != REDIS_ZSET) { if (zobj->type != REDIS_ZSET) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
} }
} }
zs = zsetobj->ptr;
/* Since both ZADD and ZINCRBY are implemented here, we need to increment
* the score first by the current score if ZINCRBY is called. */
if (incr) {
/* Read the old score. If the element was not present starts from 0 */
dictEntry *de = dictFind(zs->dict,ele);
if (de != NULL)
score += *(double*)dictGetEntryVal(de);
if (isnan(score)) {
addReplyError(c,"resulting score is not a number (NaN)");
/* 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
* there was already an element in the sorted set. */
return;
}
}
/* We need to remove and re-insert the element when it was already present if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
* in the dictionary, to update the skiplist. Note that we delay adding a unsigned char *eptr;
* pointer to the score because we want to reference the score in the
* skiplist node. */ /* Prefer non-encoded element when dealing with ziplists. */
if (dictAdd(zs->dict,ele,NULL) == DICT_OK) { ele = c->argv[3];
dictEntry *de; if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
if (incr) {
score += curscore;
if (isnan(score)) {
addReplyError(c,nanerr);
/* Don't need to check if the sorted set is empty, because
* we know it has at least one element. */
return;
}
}
/* New element */ /* Remove and re-insert when score changed. */
incrRefCount(ele); /* added to hash */ if (score != curscore) {
znode = zslInsert(zs->zsl,score,ele); zobj->ptr = zzlDelete(zobj->ptr,eptr);
incrRefCount(ele); /* added to skiplist */ zobj->ptr = zzlInsert(zobj->ptr,ele,score);
/* Update the score in the dict entry */ signalModifiedKey(c->db,key);
de = dictFind(zs->dict,ele); server.dirty++;
redisAssert(de != NULL); }
dictGetEntryVal(de) = &znode->score;
signalModifiedKey(c->db,c->argv[1]); if (incr) /* ZINCRBY */
server.dirty++; addReplyDouble(c,score);
if (incr) else /* ZADD */
addReplyDouble(c,score); addReply(c,shared.czero);
else } else {
addReply(c,shared.cone); /* Optimize: check if the element is too large or the list becomes
} else { * too long *before* executing zzlInsert. */
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
signalModifiedKey(c->db,key);
server.dirty++;
if (incr) /* ZINCRBY */
addReplyDouble(c,score);
else /* ZADD */
addReply(c,shared.cone);
}
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
zskiplistNode *znode;
dictEntry *de; dictEntry *de;
robj *curobj;
double *curscore;
int deleted;
/* Update score */ ele = c->argv[3] = tryObjectEncoding(c->argv[3]);
de = dictFind(zs->dict,ele); de = dictFind(zs->dict,ele);
redisAssert(de != NULL); if (de != NULL) {
curobj = dictGetEntryKey(de); curobj = dictGetEntryKey(de);
curscore = dictGetEntryVal(de); curscore = *(double*)dictGetEntryVal(de);
/* When the score is updated, reuse the existing string object to if (incr) {
* prevent extra alloc/dealloc of strings on ZINCRBY. */ score += curscore;
if (score != *curscore) { if (isnan(score)) {
deleted = zslDelete(zs->zsl,*curscore,curobj); addReplyError(c,nanerr);
redisAssert(deleted != 0); /* Don't need to check if the sorted set is empty, because
znode = zslInsert(zs->zsl,score,curobj); * we know it has at least one element. */
incrRefCount(curobj); return;
}
/* Update the score in the current dict entry */ }
dictGetEntryVal(de) = &znode->score;
signalModifiedKey(c->db,c->argv[1]); /* Remove and re-insert when score changed. We can safely delete
* the key object from the skiplist, since the dictionary still has
* a reference to it. */
if (score != curscore) {
redisAssert(zslDelete(zs->zsl,curscore,curobj));
znode = zslInsert(zs->zsl,score,curobj);
incrRefCount(curobj); /* Re-inserted in skiplist. */
dictGetEntryVal(de) = &znode->score; /* Update score ptr. */
signalModifiedKey(c->db,key);
server.dirty++;
}
if (incr) /* ZINCRBY */
addReplyDouble(c,score);
else /* ZADD */
addReply(c,shared.czero);
} else {
znode = zslInsert(zs->zsl,score,ele);
incrRefCount(ele); /* Inserted in skiplist. */
redisAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
incrRefCount(ele); /* Added to dictionary. */
signalModifiedKey(c->db,key);
server.dirty++; server.dirty++;
if (incr) /* ZINCRBY */
addReplyDouble(c,score);
else /* ZADD */
addReply(c,shared.cone);
} }
if (incr) } else {
addReplyDouble(c,score); redisPanic("Unknown sorted set encoding");
else
addReply(c,shared.czero);
} }
} }
void zaddCommand(redisClient *c) { void zaddCommand(redisClient *c) {
double scoreval; zaddGenericCommand(c,0);
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
c->argv[3] = tryObjectEncoding(c->argv[3]);
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
} }
void zincrbyCommand(redisClient *c) { void zincrbyCommand(redisClient *c) {
double scoreval; zaddGenericCommand(c,1);
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
c->argv[3] = tryObjectEncoding(c->argv[3]);
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
} }
void zremCommand(redisClient *c) { void zremCommand(redisClient *c) {
robj *zsetobj; robj *key = c->argv[1];
zset *zs; robj *ele = c->argv[2];
dictEntry *de; robj *zobj;
double curscore;
int deleted;
if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
checkType(c,zsetobj,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
zs = zsetobj->ptr; if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
c->argv[2] = tryObjectEncoding(c->argv[2]); unsigned char *eptr;
de = dictFind(zs->dict,c->argv[2]);
if (de == NULL) { if ((eptr = zzlFind(zobj->ptr,ele,NULL)) != NULL) {
addReply(c,shared.czero); zobj->ptr = zzlDelete(zobj->ptr,eptr);
return; if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
} else {
addReply(c,shared.czero);
return;
}
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
dictEntry *de;
double score;
de = dictFind(zs->dict,ele);
if (de != NULL) {
/* Delete from the skiplist */
score = *(double*)dictGetEntryVal(de);
redisAssert(zslDelete(zs->zsl,score,ele));
/* Delete from the hash table */
dictDelete(zs->dict,ele);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,key);
} else {
addReply(c,shared.czero);
return;
}
} else {
redisPanic("Unknown sorted set encoding");
} }
/* Delete from the skiplist */
curscore = *(double*)dictGetEntryVal(de);
deleted = zslDelete(zs->zsl,curscore,c->argv[2]);
redisAssert(deleted != 0);
/* Delete from the hash table */ signalModifiedKey(c->db,key);
dictDelete(zs->dict,c->argv[2]);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,shared.cone); addReply(c,shared.cone);
} }
void zremrangebyscoreCommand(redisClient *c) { void zremrangebyscoreCommand(redisClient *c) {
robj *key = c->argv[1];
robj *zobj;
zrangespec range; zrangespec range;
long deleted; unsigned long deleted;
robj *o;
zset *zs;
/* Parse the range arguments. */ /* Parse the range arguments. */
if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
...@@ -494,35 +1001,42 @@ void zremrangebyscoreCommand(redisClient *c) { ...@@ -494,35 +1001,42 @@ void zremrangebyscoreCommand(redisClient *c) {
return; return;
} }
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
checkType(c,o,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
zobj->ptr = zzlDeleteRangeByScore(zobj->ptr,range,&deleted);
if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,key);
} else {
redisPanic("Unknown sorted set encoding");
}
zs = o->ptr; if (deleted) signalModifiedKey(c->db,key);
deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
if (deleted) signalModifiedKey(c->db,c->argv[1]);
server.dirty += deleted; server.dirty += deleted;
addReplyLongLong(c,deleted); addReplyLongLong(c,deleted);
} }
void zremrangebyrankCommand(redisClient *c) { void zremrangebyrankCommand(redisClient *c) {
robj *key = c->argv[1];
robj *zobj;
long start; long start;
long end; long end;
int llen; int llen;
long deleted; unsigned long deleted;
robj *zsetobj;
zset *zs;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
checkType(c,zsetobj,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
zs = zsetobj->ptr;
llen = zs->zsl->length;
/* convert negative indexes */ /* Sanitize indexes. */
llen = zsetLength(zobj);
if (start < 0) start = llen+start; if (start < 0) start = llen+start;
if (end < 0) end = llen+end; if (end < 0) end = llen+end;
if (start < 0) start = 0; if (start < 0) start = 0;
...@@ -535,27 +1049,348 @@ void zremrangebyrankCommand(redisClient *c) { ...@@ -535,27 +1049,348 @@ void zremrangebyrankCommand(redisClient *c) {
} }
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
/* increment start and end because zsl*Rank functions if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
* use 1-based rank */ /* Correct for 1-based rank. */
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict); zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
if (htNeedsResize(zs->dict)) dictResize(zs->dict); if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]); } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
if (deleted) signalModifiedKey(c->db,c->argv[1]); zset *zs = zobj->ptr;
/* Correct for 1-based rank. */
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,key);
} else {
redisPanic("Unknown sorted set encoding");
}
if (deleted) signalModifiedKey(c->db,key);
server.dirty += deleted; server.dirty += deleted;
addReplyLongLong(c, deleted); addReplyLongLong(c,deleted);
} }
typedef struct { typedef struct {
dict *dict; robj *subject;
int type; /* Set, sorted set */
int encoding;
double weight; double weight;
union {
/* Set iterators. */
union _iterset {
struct {
intset *is;
int ii;
} is;
struct {
dict *dict;
dictIterator *di;
dictEntry *de;
} ht;
} set;
/* Sorted set iterators. */
union _iterzset {
struct {
unsigned char *zl;
unsigned char *eptr, *sptr;
} zl;
struct {
zset *zs;
zskiplistNode *node;
} sl;
} zset;
} iter;
} zsetopsrc; } zsetopsrc;
int qsortCompareZsetopsrcByCardinality(const void *s1, const void *s2) {
zsetopsrc *d1 = (void*) s1, *d2 = (void*) s2; /* Use dirty flags for pointers that need to be cleaned up in the next
unsigned long size1, size2; * iteration over the zsetopval. The dirty flag for the long long value is
size1 = d1->dict ? dictSize(d1->dict) : 0; * special, since long long values don't need cleanup. Instead, it means that
size2 = d2->dict ? dictSize(d2->dict) : 0; * we already checked that "ell" holds a long long, or tried to convert another
return size1 - size2; * representation into a long long value. When this was successful,
* OPVAL_VALID_LL is set as well. */
#define OPVAL_DIRTY_ROBJ 1
#define OPVAL_DIRTY_LL 2
#define OPVAL_VALID_LL 4
/* Store value retrieved from the iterator. */
typedef struct {
int flags;
unsigned char _buf[32]; /* Private buffer. */
robj *ele;
unsigned char *estr;
unsigned int elen;
long long ell;
double score;
} zsetopval;
typedef union _iterset iterset;
typedef union _iterzset iterzset;
void zuiInitIterator(zsetopsrc *op) {
if (op->subject == NULL)
return;
if (op->type == REDIS_SET) {
iterset *it = &op->iter.set;
if (op->encoding == REDIS_ENCODING_INTSET) {
it->is.is = op->subject->ptr;
it->is.ii = 0;
} else if (op->encoding == REDIS_ENCODING_HT) {
it->ht.dict = op->subject->ptr;
it->ht.di = dictGetIterator(op->subject->ptr);
it->ht.de = dictNext(it->ht.di);
} else {
redisPanic("Unknown set encoding");
}
} else if (op->type == REDIS_ZSET) {
iterzset *it = &op->iter.zset;
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
it->zl.zl = op->subject->ptr;
it->zl.eptr = ziplistIndex(it->zl.zl,0);
if (it->zl.eptr != NULL) {
it->zl.sptr = ziplistNext(it->zl.zl,it->zl.eptr);
redisAssert(it->zl.sptr != NULL);
}
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
it->sl.zs = op->subject->ptr;
it->sl.node = it->sl.zs->zsl->header->level[0].forward;
} else {
redisPanic("Unknown sorted set encoding");
}
} else {
redisPanic("Unsupported type");
}
}
void zuiClearIterator(zsetopsrc *op) {
if (op->subject == NULL)
return;
if (op->type == REDIS_SET) {
iterset *it = &op->iter.set;
if (op->encoding == REDIS_ENCODING_INTSET) {
REDIS_NOTUSED(it); /* skip */
} else if (op->encoding == REDIS_ENCODING_HT) {
dictReleaseIterator(it->ht.di);
} else {
redisPanic("Unknown set encoding");
}
} else if (op->type == REDIS_ZSET) {
iterzset *it = &op->iter.zset;
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
REDIS_NOTUSED(it); /* skip */
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
REDIS_NOTUSED(it); /* skip */
} else {
redisPanic("Unknown sorted set encoding");
}
} else {
redisPanic("Unsupported type");
}
}
int zuiLength(zsetopsrc *op) {
if (op->subject == NULL)
return 0;
if (op->type == REDIS_SET) {
iterset *it = &op->iter.set;
if (op->encoding == REDIS_ENCODING_INTSET) {
return intsetLen(it->is.is);
} else if (op->encoding == REDIS_ENCODING_HT) {
return dictSize(it->ht.dict);
} else {
redisPanic("Unknown set encoding");
}
} else if (op->type == REDIS_ZSET) {
iterzset *it = &op->iter.zset;
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
return zzlLength(it->zl.zl);
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
return it->sl.zs->zsl->length;
} else {
redisPanic("Unknown sorted set encoding");
}
} else {
redisPanic("Unsupported type");
}
}
/* Check if the current value is valid. If so, store it in the passed structure
* and move to the next element. If not valid, this means we have reached the
* end of the structure and can abort. */
int zuiNext(zsetopsrc *op, zsetopval *val) {
if (op->subject == NULL)
return 0;
if (val->flags & OPVAL_DIRTY_ROBJ)
decrRefCount(val->ele);
bzero(val,sizeof(zsetopval));
if (op->type == REDIS_SET) {
iterset *it = &op->iter.set;
if (op->encoding == REDIS_ENCODING_INTSET) {
if (!intsetGet(it->is.is,it->is.ii,(int64_t*)&val->ell))
return 0;
val->score = 1.0;
/* Move to next element. */
it->is.ii++;
} else if (op->encoding == REDIS_ENCODING_HT) {
if (it->ht.de == NULL)
return 0;
val->ele = dictGetEntryKey(it->ht.de);
val->score = 1.0;
/* Move to next element. */
it->ht.de = dictNext(it->ht.di);
} else {
redisPanic("Unknown set encoding");
}
} else if (op->type == REDIS_ZSET) {
iterzset *it = &op->iter.zset;
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
/* No need to check both, but better be explicit. */
if (it->zl.eptr == NULL || it->zl.sptr == NULL)
return 0;
redisAssert(ziplistGet(it->zl.eptr,&val->estr,&val->elen,&val->ell));
val->score = zzlGetScore(it->zl.sptr);
/* Move to next element. */
zzlNext(it->zl.zl,&it->zl.eptr,&it->zl.sptr);
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
if (it->sl.node == NULL)
return 0;
val->ele = it->sl.node->obj;
val->score = it->sl.node->score;
/* Move to next element. */
it->sl.node = it->sl.node->level[0].forward;
} else {
redisPanic("Unknown sorted set encoding");
}
} else {
redisPanic("Unsupported type");
}
return 1;
}
int zuiLongLongFromValue(zsetopval *val) {
if (!(val->flags & OPVAL_DIRTY_LL)) {
val->flags |= OPVAL_DIRTY_LL;
if (val->ele != NULL) {
if (val->ele->encoding == REDIS_ENCODING_INT) {
val->ell = (long)val->ele->ptr;
val->flags |= OPVAL_VALID_LL;
} else if (val->ele->encoding == REDIS_ENCODING_RAW) {
if (string2ll(val->ele->ptr,sdslen(val->ele->ptr),&val->ell))
val->flags |= OPVAL_VALID_LL;
} else {
redisPanic("Unsupported element encoding");
}
} else if (val->estr != NULL) {
if (string2ll((char*)val->estr,val->elen,&val->ell))
val->flags |= OPVAL_VALID_LL;
} else {
/* The long long was already set, flag as valid. */
val->flags |= OPVAL_VALID_LL;
}
}
return val->flags & OPVAL_VALID_LL;
}
robj *zuiObjectFromValue(zsetopval *val) {
if (val->ele == NULL) {
if (val->estr != NULL) {
val->ele = createStringObject((char*)val->estr,val->elen);
} else {
val->ele = createStringObjectFromLongLong(val->ell);
}
val->flags |= OPVAL_DIRTY_ROBJ;
}
return val->ele;
}
int zuiBufferFromValue(zsetopval *val) {
if (val->estr == NULL) {
if (val->ele != NULL) {
if (val->ele->encoding == REDIS_ENCODING_INT) {
val->elen = ll2string((char*)val->_buf,sizeof(val->_buf),(long)val->ele->ptr);
val->estr = val->_buf;
} else if (val->ele->encoding == REDIS_ENCODING_RAW) {
val->elen = sdslen(val->ele->ptr);
val->estr = val->ele->ptr;
} else {
redisPanic("Unsupported element encoding");
}
} else {
val->elen = ll2string((char*)val->_buf,sizeof(val->_buf),val->ell);
val->estr = val->_buf;
}
}
return 1;
}
/* Find value pointed to by val in the source pointer to by op. When found,
* return 1 and store its score in target. Return 0 otherwise. */
int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
if (op->subject == NULL)
return 0;
if (op->type == REDIS_SET) {
iterset *it = &op->iter.set;
if (op->encoding == REDIS_ENCODING_INTSET) {
if (zuiLongLongFromValue(val) && intsetFind(it->is.is,val->ell)) {
*score = 1.0;
return 1;
} else {
return 0;
}
} else if (op->encoding == REDIS_ENCODING_HT) {
zuiObjectFromValue(val);
if (dictFind(it->ht.dict,val->ele) != NULL) {
*score = 1.0;
return 1;
} else {
return 0;
}
} else {
redisPanic("Unknown set encoding");
}
} else if (op->type == REDIS_ZSET) {
iterzset *it = &op->iter.zset;
zuiObjectFromValue(val);
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
if (zzlFind(it->zl.zl,val->ele,score) != NULL) {
/* Score is already set by zzlFind. */
return 1;
} else {
return 0;
}
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
dictEntry *de;
if ((de = dictFind(it->sl.zs->dict,val->ele)) != NULL) {
*score = *(double*)dictGetEntryVal(de);
return 1;
} else {
return 0;
}
} else {
redisPanic("Unknown sorted set encoding");
}
} else {
redisPanic("Unsupported type");
}
}
int zuiCompareByCardinality(const void *s1, const void *s2) {
return zuiLength((zsetopsrc*)s1) - zuiLength((zsetopsrc*)s2);
} }
#define REDIS_AGGR_SUM 1 #define REDIS_AGGR_SUM 1
...@@ -584,11 +1419,12 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { ...@@ -584,11 +1419,12 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
int i, j, setnum; int i, j, setnum;
int aggregate = REDIS_AGGR_SUM; int aggregate = REDIS_AGGR_SUM;
zsetopsrc *src; zsetopsrc *src;
zsetopval zval;
robj *tmp;
unsigned int maxelelen = 0;
robj *dstobj; robj *dstobj;
zset *dstzset; zset *dstzset;
zskiplistNode *znode; zskiplistNode *znode;
dictIterator *di;
dictEntry *de;
int touched = 0; int touched = 0;
/* expect setnum input keys to be given */ /* expect setnum input keys to be given */
...@@ -606,24 +1442,24 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { ...@@ -606,24 +1442,24 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
} }
/* read keys to be used for input */ /* read keys to be used for input */
src = zmalloc(sizeof(zsetopsrc) * setnum); src = zcalloc(sizeof(zsetopsrc) * setnum);
for (i = 0, j = 3; i < setnum; i++, j++) { for (i = 0, j = 3; i < setnum; i++, j++) {
robj *obj = lookupKeyWrite(c->db,c->argv[j]); robj *obj = lookupKeyWrite(c->db,c->argv[j]);
if (!obj) { if (obj != NULL) {
src[i].dict = NULL; if (obj->type != REDIS_ZSET && obj->type != REDIS_SET) {
} else {
if (obj->type == REDIS_ZSET) {
src[i].dict = ((zset*)obj->ptr)->dict;
} else if (obj->type == REDIS_SET) {
src[i].dict = (obj->ptr);
} else {
zfree(src); zfree(src);
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
} }
src[i].subject = obj;
src[i].type = obj->type;
src[i].encoding = obj->encoding;
} else {
src[i].subject = NULL;
} }
/* default all weights to 1 */ /* Default all weights to 1. */
src[i].weight = 1.0; src[i].weight = 1.0;
} }
...@@ -664,95 +1500,109 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { ...@@ -664,95 +1500,109 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
} }
} }
for (i = 0; i < setnum; i++)
zuiInitIterator(&src[i]);
/* sort sets from the smallest to largest, this will improve our /* sort sets from the smallest to largest, this will improve our
* algorithm's performance */ * algorithm's performance */
qsort(src,setnum,sizeof(zsetopsrc),qsortCompareZsetopsrcByCardinality); qsort(src,setnum,sizeof(zsetopsrc),zuiCompareByCardinality);
dstobj = createZsetObject(); dstobj = createZsetObject();
dstzset = dstobj->ptr; dstzset = dstobj->ptr;
memset(&zval, 0, sizeof(zval));
if (op == REDIS_OP_INTER) { if (op == REDIS_OP_INTER) {
/* skip going over all entries if the smallest zset is NULL or empty */ /* Skip everything if the smallest input is empty. */
if (src[0].dict && dictSize(src[0].dict) > 0) { if (zuiLength(&src[0]) > 0) {
/* precondition: as src[0].dict is non-empty and the zsets are ordered /* Precondition: as src[0] is non-empty and the inputs are ordered
* from small to large, all src[i > 0].dict are non-empty too */ * by size, all src[i > 0] are non-empty too. */
di = dictGetIterator(src[0].dict); while (zuiNext(&src[0],&zval)) {
while((de = dictNext(di)) != NULL) {
double score, value; double score, value;
score = src[0].weight * zunionInterDictValue(de); score = src[0].weight * zval.score;
for (j = 1; j < setnum; j++) { for (j = 1; j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (zuiFind(&src[j],&zval,&value)) {
if (other) { value *= src[j].weight;
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(&score,value,aggregate); zunionInterAggregate(&score,value,aggregate);
} else { } else {
break; break;
} }
} }
/* Only continue when present in every source dict. */ /* Only continue when present in every input. */
if (j == setnum) { if (j == setnum) {
robj *o = dictGetEntryKey(de); tmp = zuiObjectFromValue(&zval);
znode = zslInsert(dstzset->zsl,score,o); znode = zslInsert(dstzset->zsl,score,tmp);
incrRefCount(o); /* added to skiplist */ incrRefCount(tmp); /* added to skiplist */
dictAdd(dstzset->dict,o,&znode->score); dictAdd(dstzset->dict,tmp,&znode->score);
incrRefCount(o); /* added to dictionary */ incrRefCount(tmp); /* added to dictionary */
if (tmp->encoding == REDIS_ENCODING_RAW)
if (sdslen(tmp->ptr) > maxelelen)
maxelelen = sdslen(tmp->ptr);
} }
} }
dictReleaseIterator(di);
} }
} else if (op == REDIS_OP_UNION) { } else if (op == REDIS_OP_UNION) {
for (i = 0; i < setnum; i++) { for (i = 0; i < setnum; i++) {
if (!src[i].dict) continue; if (zuiLength(&src[0]) == 0)
continue;
di = dictGetIterator(src[i].dict); while (zuiNext(&src[i],&zval)) {
while((de = dictNext(di)) != NULL) {
double score, value; double score, value;
/* skip key when already processed */ /* Skip key when already processed */
if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) if (dictFind(dstzset->dict,zuiObjectFromValue(&zval)) != NULL)
continue; continue;
/* initialize score */ /* Initialize score */
score = src[i].weight * zunionInterDictValue(de); score = src[i].weight * zval.score;
/* because the zsets are sorted by size, its only possible /* Because the inputs are sorted by size, it's only possible
* for sets at larger indices to hold this entry */ * for sets at larger indices to hold this element. */
for (j = (i+1); j < setnum; j++) { for (j = (i+1); j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (zuiFind(&src[j],&zval,&value)) {
if (other) { value *= src[j].weight;
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(&score,value,aggregate); zunionInterAggregate(&score,value,aggregate);
} }
} }
robj *o = dictGetEntryKey(de); tmp = zuiObjectFromValue(&zval);
znode = zslInsert(dstzset->zsl,score,o); znode = zslInsert(dstzset->zsl,score,tmp);
incrRefCount(o); /* added to skiplist */ incrRefCount(zval.ele); /* added to skiplist */
dictAdd(dstzset->dict,o,&znode->score); dictAdd(dstzset->dict,tmp,&znode->score);
incrRefCount(o); /* added to dictionary */ incrRefCount(zval.ele); /* added to dictionary */
if (tmp->encoding == REDIS_ENCODING_RAW)
if (sdslen(tmp->ptr) > maxelelen)
maxelelen = sdslen(tmp->ptr);
} }
dictReleaseIterator(di);
} }
} else { } else {
/* unknown operator */ redisPanic("Unknown operator");
redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION);
} }
for (i = 0; i < setnum; i++)
zuiClearIterator(&src[i]);
if (dbDelete(c->db,dstkey)) { if (dbDelete(c->db,dstkey)) {
signalModifiedKey(c->db,dstkey); signalModifiedKey(c->db,dstkey);
touched = 1; touched = 1;
server.dirty++; server.dirty++;
} }
if (dstzset->zsl->length) { if (dstzset->zsl->length) {
/* Convert to ziplist when in limits. */
if (dstzset->zsl->length <= server.zset_max_ziplist_entries &&
maxelelen <= server.zset_max_ziplist_value)
zsetConvert(dstobj,REDIS_ENCODING_ZIPLIST);
dbAdd(c->db,dstkey,dstobj); dbAdd(c->db,dstkey,dstobj);
addReplyLongLong(c, dstzset->zsl->length); addReplyLongLong(c,zsetLength(dstobj));
if (!touched) signalModifiedKey(c->db,dstkey); if (!touched) signalModifiedKey(c->db,dstkey);
server.dirty++; server.dirty++;
} else { } else {
decrRefCount(dstobj); decrRefCount(dstobj);
addReply(c, shared.czero); addReply(c,shared.czero);
} }
zfree(src); zfree(src);
} }
...@@ -766,16 +1616,13 @@ void zinterstoreCommand(redisClient *c) { ...@@ -766,16 +1616,13 @@ void zinterstoreCommand(redisClient *c) {
} }
void zrangeGenericCommand(redisClient *c, int reverse) { void zrangeGenericCommand(redisClient *c, int reverse) {
robj *o; robj *key = c->argv[1];
robj *zobj;
int withscores = 0;
long start; long start;
long end; long end;
int withscores = 0;
int llen; int llen;
int rangelen, j; int rangelen;
zset *zsetobj;
zskiplist *zsl;
zskiplistNode *ln;
robj *ele;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
...@@ -787,13 +1634,11 @@ void zrangeGenericCommand(redisClient *c, int reverse) { ...@@ -787,13 +1634,11 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
return; return;
} }
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_ZSET)) return; || checkType(c,zobj,REDIS_ZSET)) return;
zsetobj = o->ptr;
zsl = zsetobj->zsl;
llen = zsl->length;
/* convert negative indexes */ /* Sanitize indexes. */
llen = zsetLength(zobj);
if (start < 0) start = llen+start; if (start < 0) start = llen+start;
if (end < 0) end = llen+end; if (end < 0) end = llen+end;
if (start < 0) start = 0; if (start < 0) start = 0;
...@@ -807,23 +1652,68 @@ void zrangeGenericCommand(redisClient *c, int reverse) { ...@@ -807,23 +1652,68 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
rangelen = (end-start)+1; rangelen = (end-start)+1;
/* check if starting point is trivial, before searching
* the element in log(N) time */
if (reverse) {
ln = start == 0 ? zsl->tail : zslistTypeGetElementByRank(zsl, llen-start);
} else {
ln = start == 0 ?
zsl->header->level[0].forward : zslistTypeGetElementByRank(zsl, start+1);
}
/* Return the result in form of a multi-bulk reply */ /* Return the result in form of a multi-bulk reply */
addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen); addReplyMultiBulkLen(c, withscores ? (rangelen*2) : rangelen);
for (j = 0; j < rangelen; j++) {
ele = ln->obj; if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
addReplyBulk(c,ele); unsigned char *zl = zobj->ptr;
if (withscores) unsigned char *eptr, *sptr;
addReplyDouble(c,ln->score); unsigned char *vstr;
ln = reverse ? ln->backward : ln->level[0].forward; unsigned int vlen;
long long vlong;
if (reverse)
eptr = ziplistIndex(zl,-2-(2*start));
else
eptr = ziplistIndex(zl,2*start);
redisAssert(eptr != NULL);
sptr = ziplistNext(zl,eptr);
while (rangelen--) {
redisAssert(eptr != NULL && sptr != NULL);
redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
if (vstr == NULL)
addReplyBulkLongLong(c,vlong);
else
addReplyBulkCBuffer(c,vstr,vlen);
if (withscores)
addReplyDouble(c,zzlGetScore(sptr));
if (reverse)
zzlPrev(zl,&eptr,&sptr);
else
zzlNext(zl,&eptr,&sptr);
}
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
zskiplist *zsl = zs->zsl;
zskiplistNode *ln;
robj *ele;
/* Check if starting point is trivial, before doing log(N) lookup. */
if (reverse) {
ln = zsl->tail;
if (start > 0)
ln = zslGetElementByRank(zsl,llen-start);
} else {
ln = zsl->header->level[0].forward;
if (start > 0)
ln = zslGetElementByRank(zsl,start+1);
}
while(rangelen--) {
redisAssert(ln != NULL);
ele = ln->obj;
addReplyBulk(c,ele);
if (withscores)
addReplyDouble(c,ln->score);
ln = reverse ? ln->backward : ln->level[0].forward;
}
} else {
redisPanic("Unknown sorted set encoding");
} }
} }
...@@ -839,17 +1729,24 @@ void zrevrangeCommand(redisClient *c) { ...@@ -839,17 +1729,24 @@ void zrevrangeCommand(redisClient *c) {
* If "justcount", only the number of elements in the range is returned. */ * If "justcount", only the number of elements in the range is returned. */
void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) { void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
zrangespec range; zrangespec range;
robj *o, *emptyreply; robj *key = c->argv[1];
zset *zsetobj; robj *emptyreply, *zobj;
zskiplist *zsl;
zskiplistNode *ln;
int offset = 0, limit = -1; int offset = 0, limit = -1;
int withscores = 0; int withscores = 0;
unsigned long rangelen = 0; unsigned long rangelen = 0;
void *replylen = NULL; void *replylen = NULL;
int minidx, maxidx;
/* Parse the range arguments. */ /* Parse the range arguments. */
if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { if (reverse) {
/* Range is given as [max,min] */
maxidx = 2; minidx = 3;
} else {
/* Range is given as [min,max] */
minidx = 2; maxidx = 3;
}
if (zslParseRange(c->argv[minidx],c->argv[maxidx],&range) != REDIS_OK) {
addReplyError(c,"min or max is not a double"); addReplyError(c,"min or max is not a double");
return; return;
} }
...@@ -877,101 +1774,132 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) { ...@@ -877,101 +1774,132 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
/* Ok, lookup the key and get the range */ /* Ok, lookup the key and get the range */
emptyreply = justcount ? shared.czero : shared.emptymultibulk; emptyreply = justcount ? shared.czero : shared.emptymultibulk;
if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,emptyreply)) == NULL ||
checkType(c,o,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
zsetobj = o->ptr;
zsl = zsetobj->zsl; if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl = zobj->ptr;
unsigned char *eptr, *sptr;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
double score;
/* If reversed, get the last node in range as starting point. */
if (reverse)
eptr = zzlLastInRange(zl,range);
else
eptr = zzlFirstInRange(zl,range);
/* If reversed, assume the elements are sorted from high to low score. */ /* No "first" element in the specified interval. */
ln = zslFirstWithScore(zsl,range.min); if (eptr == NULL) {
if (reverse) { addReply(c,emptyreply);
/* If range.min is out of range, ln will be NULL and we need to use return;
* the tail of the skiplist as first node of the range. */
if (ln == NULL) ln = zsl->tail;
/* zslFirstWithScore returns the first element with where with
* score >= range.min, so backtrack to make sure the element we use
* here has score <= range.min. */
while (ln && ln->score > range.min) ln = ln->backward;
/* Move to the right element according to the range spec. */
if (range.minex) {
/* Find last element with score < range.min */
while (ln && ln->score == range.min) ln = ln->backward;
} else {
/* Find last element with score <= range.min */
while (ln && ln->level[0].forward &&
ln->level[0].forward->score == range.min)
ln = ln->level[0].forward;
}
} else {
if (range.minex) {
/* Find first element with score > range.min */
while (ln && ln->score == range.min) ln = ln->level[0].forward;
} }
}
/* No "first" element in the specified interval. */ /* Get score pointer for the first element. */
if (ln == NULL) { redisAssert(eptr != NULL);
addReply(c,emptyreply); sptr = ziplistNext(zl,eptr);
return;
} /* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
if (!justcount)
replylen = addDeferredMultiBulkLength(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
while (eptr && offset--)
if (reverse)
zzlPrev(zl,&eptr,&sptr);
else
zzlNext(zl,&eptr,&sptr);
while (eptr && limit--) {
score = zzlGetScore(sptr);
/* Abort when the node is no longer in range. */
if (reverse) {
if (!zslValueGteMin(score,&range)) break;
} else {
if (!zslValueLteMax(score,&range)) break;
}
/* We don't know in advance how many matching elements there /* Do our magic */
* are in the list, so we push this object that will represent rangelen++;
* the multi-bulk length in the output buffer, and will "fix" if (!justcount) {
* it later */ redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
if (!justcount) if (vstr == NULL)
replylen = addDeferredMultiBulkLength(c); addReplyBulkLongLong(c,vlong);
else
addReplyBulkCBuffer(c,vstr,vlen);
if (withscores)
addReplyDouble(c,score);
}
/* Move to next node */
if (reverse)
zzlPrev(zl,&eptr,&sptr);
else
zzlNext(zl,&eptr,&sptr);
}
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
zskiplist *zsl = zs->zsl;
zskiplistNode *ln;
/* If there is an offset, just traverse the number of elements without /* If reversed, get the last node in range as starting point. */
* checking the score because that is done in the next loop. */
while(ln && offset--) {
if (reverse) if (reverse)
ln = ln->backward; ln = zslLastInRange(zsl,range);
else else
ln = ln->level[0].forward; ln = zslFirstInRange(zsl,range);
}
while (ln && limit--) { /* No "first" element in the specified interval. */
/* Check if this this element is in range. */ if (ln == NULL) {
if (reverse) { addReply(c,emptyreply);
if (range.maxex) { return;
/* Element should have score > range.max */ }
if (ln->score <= range.max) break;
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
if (!justcount)
replylen = addDeferredMultiBulkLength(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
while (ln && offset--)
ln = reverse ? ln->backward : ln->level[0].forward;
while (ln && limit--) {
/* Abort when the node is no longer in range. */
if (reverse) {
if (!zslValueGteMin(ln->score,&range)) break;
} else { } else {
/* Element should have score >= range.max */ if (!zslValueLteMax(ln->score,&range)) break;
if (ln->score < range.max) break;
} }
} else {
if (range.maxex) { /* Do our magic */
/* Element should have score < range.max */ rangelen++;
if (ln->score >= range.max) break; if (!justcount) {
} else { addReplyBulk(c,ln->obj);
/* Element should have score <= range.max */ if (withscores)
if (ln->score > range.max) break; addReplyDouble(c,ln->score);
} }
}
/* Do our magic */ /* Move to next node */
rangelen++; ln = reverse ? ln->backward : ln->level[0].forward;
if (!justcount) {
addReplyBulk(c,ln->obj);
if (withscores)
addReplyDouble(c,ln->score);
} }
} else {
if (reverse) redisPanic("Unknown sorted set encoding");
ln = ln->backward;
else
ln = ln->level[0].forward;
} }
if (justcount) { if (justcount) {
addReplyLongLong(c,(long)rangelen); addReplyLongLong(c,(long)rangelen);
} else { } else {
setDeferredMultiBulkLength(c,replylen, if (withscores) rangelen *= 2;
withscores ? (rangelen*2) : rangelen); setDeferredMultiBulkLength(c,replylen,rangelen);
} }
} }
...@@ -988,66 +1916,103 @@ void zcountCommand(redisClient *c) { ...@@ -988,66 +1916,103 @@ void zcountCommand(redisClient *c) {
} }
void zcardCommand(redisClient *c) { void zcardCommand(redisClient *c) {
robj *o; robj *key = c->argv[1];
zset *zs; robj *zobj;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL ||
checkType(c,o,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
zs = o->ptr; addReplyLongLong(c,zsetLength(zobj));
addReplyLongLong(c,zs->zsl->length);
} }
void zscoreCommand(redisClient *c) { void zscoreCommand(redisClient *c) {
robj *o; robj *key = c->argv[1];
zset *zs; robj *zobj;
dictEntry *de; double score;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
zs = o->ptr; if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
c->argv[2] = tryObjectEncoding(c->argv[2]); if (zzlFind(zobj->ptr,c->argv[2],&score) != NULL)
de = dictFind(zs->dict,c->argv[2]); addReplyDouble(c,score);
if (!de) { else
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
double *score = dictGetEntryVal(de); zset *zs = zobj->ptr;
dictEntry *de;
addReplyDouble(c,*score); c->argv[2] = tryObjectEncoding(c->argv[2]);
de = dictFind(zs->dict,c->argv[2]);
if (de != NULL) {
score = *(double*)dictGetEntryVal(de);
addReplyDouble(c,score);
} else {
addReply(c,shared.nullbulk);
}
} else {
redisPanic("Unknown sorted set encoding");
} }
} }
void zrankGenericCommand(redisClient *c, int reverse) { void zrankGenericCommand(redisClient *c, int reverse) {
robj *o; robj *key = c->argv[1];
zset *zs; robj *ele = c->argv[2];
zskiplist *zsl; robj *zobj;
dictEntry *de; unsigned long llen;
unsigned long rank; unsigned long rank;
double *score;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_ZSET)) return; checkType(c,zobj,REDIS_ZSET)) return;
llen = zsetLength(zobj);
redisAssert(ele->encoding == REDIS_ENCODING_RAW);
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl = zobj->ptr;
unsigned char *eptr, *sptr;
eptr = ziplistIndex(zl,0);
redisAssert(eptr != NULL);
sptr = ziplistNext(zl,eptr);
redisAssert(sptr != NULL);
rank = 1;
while(eptr != NULL) {
if (ziplistCompare(eptr,ele->ptr,sdslen(ele->ptr)))
break;
rank++;
zzlNext(zl,&eptr,&sptr);
}
zs = o->ptr; if (eptr != NULL) {
zsl = zs->zsl; if (reverse)
c->argv[2] = tryObjectEncoding(c->argv[2]); addReplyLongLong(c,llen-rank);
de = dictFind(zs->dict,c->argv[2]); else
if (!de) { addReplyLongLong(c,rank-1);
addReply(c,shared.nullbulk); } else {
return; addReply(c,shared.nullbulk);
} }
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
zskiplist *zsl = zs->zsl;
dictEntry *de;
double score;
score = dictGetEntryVal(de); ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
rank = zslistTypeGetRank(zsl, *score, c->argv[2]); de = dictFind(zs->dict,ele);
if (rank) { if (de != NULL) {
if (reverse) { score = *(double*)dictGetEntryVal(de);
addReplyLongLong(c, zsl->length - rank); rank = zslGetRank(zsl,score,ele);
redisAssert(rank); /* Existing elements always have a rank. */
if (reverse)
addReplyLongLong(c,llen-rank);
else
addReplyLongLong(c,rank-1);
} else { } else {
addReplyLongLong(c, rank-1); addReply(c,shared.nullbulk);
} }
} else { } else {
addReply(c,shared.nullbulk); redisPanic("Unknown sorted set encoding");
} }
} }
......
#include "redis.h" #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include <math.h>
#include "util.h"
/* Glob-style pattern matching. */ /* Glob-style pattern matching. */
int stringmatchlen(const char *pattern, int patternLen, int stringmatchlen(const char *pattern, int patternLen,
...@@ -200,44 +204,237 @@ int ll2string(char *s, size_t len, long long value) { ...@@ -200,44 +204,237 @@ int ll2string(char *s, size_t len, long long value) {
return l; return l;
} }
/* Check if the sds string 's' can be represented by a long long /* Convert a string into a long long. Returns 1 if the string could be parsed
* (that is, is a number that fits into long without any other space or * into a (non-overflowing) long long, 0 otherwise. The value will be set to
* character before or after the digits, so that converting this number * the parsed value when appropriate. */
* back to a string will result in the same bytes as the original string). int string2ll(char *s, size_t slen, long long *value) {
* char *p = s;
* If so, the function returns REDIS_OK and *llongval is set to the value size_t plen = 0;
* of the number. Otherwise REDIS_ERR is returned */ int negative = 0;
int isStringRepresentableAsLongLong(sds s, long long *llongval) { unsigned long long v;
char buf[32], *endptr;
long long value; if (plen == slen)
int slen; return 0;
value = strtoll(s, &endptr, 10); /* Special case: first and only digit is 0. */
if (endptr[0] != '\0') return REDIS_ERR; if (slen == 1 && p[0] == '0') {
slen = ll2string(buf,32,value); if (value != NULL) *value = 0;
return 1;
/* If the number converted back into a string is not identical }
* then it's not possible to encode the string as integer */
if (sdslen(s) != (unsigned)slen || memcmp(buf,s,slen)) return REDIS_ERR; if (p[0] == '-') {
if (llongval) *llongval = value; negative = 1;
return REDIS_OK; p++; plen++;
/* Abort on only a negative sign. */
if (plen == slen)
return 0;
}
/* First digit should be 1-9, otherwise the string should just be 0. */
if (p[0] >= '1' && p[0] <= '9') {
v = p[0]-'0';
p++; plen++;
} else if (p[0] == '0' && slen == 1) {
*value = 0;
return 1;
} else {
return 0;
}
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
if (v > (ULLONG_MAX / 10)) /* Overflow. */
return 0;
v *= 10;
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
return 0;
v += p[0]-'0';
p++; plen++;
}
/* Return if not all bytes were used. */
if (plen < slen)
return 0;
if (negative) {
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
return 0;
if (value != NULL) *value = -v;
} else {
if (v > LLONG_MAX) /* Overflow. */
return 0;
if (value != NULL) *value = v;
}
return 1;
} }
int isStringRepresentableAsLong(sds s, long *longval) { /* Convert a string into a long. Returns 1 if the string could be parsed into a
long long ll; * (non-overflowing) long, 0 otherwise. The value will be set to the parsed
* value when appropriate. */
int string2l(char *s, size_t slen, long *lval) {
long long llval;
if (isStringRepresentableAsLongLong(s,&ll) == REDIS_ERR) return REDIS_ERR; if (!string2ll(s,slen,&llval))
if (ll < LONG_MIN || ll > LONG_MAX) return REDIS_ERR; return 0;
*longval = (long)ll;
return REDIS_OK; if (llval < LONG_MIN || llval > LONG_MAX)
return 0;
*lval = (long)llval;
return 1;
} }
int isObjectRepresentableAsLongLong(robj *o, long long *llongval) { /* Convert a double to a string representation. Returns the number of bytes
redisAssert(o->type == REDIS_STRING); * required. The representation should always be parsable by stdtod(3). */
if (o->encoding == REDIS_ENCODING_INT) { int d2string(char *buf, size_t len, double value) {
if (llongval) *llongval = (long) o->ptr; if (isnan(value)) {
return REDIS_OK; len = snprintf(buf,len,"nan");
} else if (isinf(value)) {
if (value < 0)
len = snprintf(buf,len,"-inf");
else
len = snprintf(buf,len,"inf");
} else if (value == 0) {
/* See: http://en.wikipedia.org/wiki/Signed_zero, "Comparisons". */
if (1.0/value < 0)
len = snprintf(buf,len,"-0");
else
len = snprintf(buf,len,"0");
} else { } else {
return isStringRepresentableAsLongLong(o->ptr,llongval); #if (DBL_MANT_DIG >= 52) && (LLONG_MAX == 0x7fffffffffffffffLL)
/* Check if the float is in a safe range to be casted into a
* long long. We are assuming that long long is 64 bit here.
* Also we are assuming that there are no implementations around where
* double has precision < 52 bit.
*
* Under this assumptions we test if a double is inside an interval
* where casting to long long is safe. Then using two castings we
* make sure the decimal part is zero. If all this is true we use
* integer printing function that is much faster. */
double min = -4503599627370495; /* (2^52)-1 */
double max = 4503599627370496; /* -(2^52) */
if (val > min && val < max && value == ((double)((long long)value)))
len = ll2string(buf,len,(long long)value);
else
#endif
len = snprintf(buf,len,"%.17g",value);
} }
return len;
}
#ifdef UTIL_TEST_MAIN
#include <assert.h>
void test_string2ll(void) {
char buf[32];
long long v;
/* May not start with +. */
strcpy(buf,"+1");
assert(string2ll(buf,strlen(buf),&v) == 0);
/* Leading space. */
strcpy(buf," 1");
assert(string2ll(buf,strlen(buf),&v) == 0);
/* Trailing space. */
strcpy(buf,"1 ");
assert(string2ll(buf,strlen(buf),&v) == 0);
/* May not start with 0. */
strcpy(buf,"01");
assert(string2ll(buf,strlen(buf),&v) == 0);
strcpy(buf,"-1");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == -1);
strcpy(buf,"0");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == 0);
strcpy(buf,"1");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == 1);
strcpy(buf,"99");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == 99);
strcpy(buf,"-99");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == -99);
strcpy(buf,"-9223372036854775808");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == LLONG_MIN);
strcpy(buf,"-9223372036854775809"); /* overflow */
assert(string2ll(buf,strlen(buf),&v) == 0);
strcpy(buf,"9223372036854775807");
assert(string2ll(buf,strlen(buf),&v) == 1);
assert(v == LLONG_MAX);
strcpy(buf,"9223372036854775808"); /* overflow */
assert(string2ll(buf,strlen(buf),&v) == 0);
}
void test_string2l(void) {
char buf[32];
long v;
/* May not start with +. */
strcpy(buf,"+1");
assert(string2l(buf,strlen(buf),&v) == 0);
/* May not start with 0. */
strcpy(buf,"01");
assert(string2l(buf,strlen(buf),&v) == 0);
strcpy(buf,"-1");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == -1);
strcpy(buf,"0");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == 0);
strcpy(buf,"1");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == 1);
strcpy(buf,"99");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == 99);
strcpy(buf,"-99");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == -99);
#if LONG_MAX != LLONG_MAX
strcpy(buf,"-2147483648");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == LONG_MIN);
strcpy(buf,"-2147483649"); /* overflow */
assert(string2l(buf,strlen(buf),&v) == 0);
strcpy(buf,"2147483647");
assert(string2l(buf,strlen(buf),&v) == 1);
assert(v == LONG_MAX);
strcpy(buf,"2147483648"); /* overflow */
assert(string2l(buf,strlen(buf),&v) == 0);
#endif
}
int main(int argc, char **argv) {
test_string2ll();
test_string2l();
return 0;
} }
#endif
#ifndef __REDIS_UTIL_H
#define __REDIS_UTIL_H
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase);
long long memtoll(const char *p, int *err);
int ll2string(char *s, size_t len, long long value);
int string2ll(char *s, size_t slen, long long *value);
int string2l(char *s, size_t slen, long *value);
int d2string(char *buf, size_t len, double value);
#endif
#define REDIS_VERSION "2.3.0" #define REDIS_VERSION "2.9.0"
...@@ -67,11 +67,10 @@ ...@@ -67,11 +67,10 @@
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include "zmalloc.h" #include "zmalloc.h"
#include "util.h"
#include "ziplist.h" #include "ziplist.h"
#include "endian.h" #include "endian.h"
int ll2string(char *s, size_t len, long long value);
#define ZIP_END 255 #define ZIP_END 255
#define ZIP_BIGLEN 254 #define ZIP_BIGLEN 254
...@@ -252,22 +251,9 @@ static int zipPrevLenByteDiff(unsigned char *p, unsigned int len) { ...@@ -252,22 +251,9 @@ static int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
* Stores the integer value in 'v' and its encoding in '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) { static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
long long value; long long value;
char *eptr;
char buf[32];
if (entrylen >= 32 || entrylen == 0) return 0; if (entrylen >= 32 || entrylen == 0) return 0;
if (entry[0] == '-' || (entry[0] >= '0' && entry[0] <= '9')) { if (string2ll((char*)entry,entrylen,&value)) {
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 /* Great, the string can be encoded. Check what's the smallest
* of our encoding types that can hold this value. */ * of our encoding types that can hold this value. */
if (value >= INT16_MIN && value <= INT16_MAX) { if (value >= INT16_MIN && value <= INT16_MAX) {
...@@ -385,8 +371,8 @@ static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) { ...@@ -385,8 +371,8 @@ static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
* The pointer "p" points to the first entry that does NOT need to be * The pointer "p" points to the first entry that does NOT need to be
* updated, i.e. consecutive fields MAY need an update. */ * updated, i.e. consecutive fields MAY need an update. */
static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) { static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
unsigned int curlen = ZIPLIST_BYTES(zl), rawlen, rawlensize; size_t curlen = ZIPLIST_BYTES(zl), rawlen, rawlensize;
unsigned int offset, noffset, extra; size_t offset, noffset, extra;
unsigned char *np; unsigned char *np;
zlentry cur, next; zlentry cur, next;
...@@ -408,12 +394,17 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p ...@@ -408,12 +394,17 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
offset = p-zl; offset = p-zl;
extra = rawlensize-next.prevrawlensize; extra = rawlensize-next.prevrawlensize;
zl = ziplistResize(zl,curlen+extra); zl = ziplistResize(zl,curlen+extra);
ZIPLIST_TAIL_OFFSET(zl) += extra;
p = zl+offset; p = zl+offset;
/* Move the tail to the back. */ /* Current pointer and offset for next element. */
np = p+rawlen; np = p+rawlen;
noffset = np-zl; noffset = np-zl;
/* Update tail offset when next element is not the tail element. */
if ((zl+ZIPLIST_TAIL_OFFSET(zl)) != np)
ZIPLIST_TAIL_OFFSET(zl) += extra;
/* Move the tail to the back. */
memmove(np+rawlensize, memmove(np+rawlensize,
np+next.prevrawlensize, np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-1); curlen-noffset-next.prevrawlensize-1);
...@@ -441,7 +432,8 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p ...@@ -441,7 +432,8 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */ /* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) { static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
unsigned int i, totlen, deleted = 0; unsigned int i, totlen, deleted = 0;
int offset, nextdiff = 0; size_t offset;
int nextdiff = 0;
zlentry first, tail; zlentry first, tail;
first = zipEntry(p); first = zipEntry(p);
...@@ -493,8 +485,9 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig ...@@ -493,8 +485,9 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
/* Insert item at "p". */ /* Insert item at "p". */
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
unsigned int curlen = ZIPLIST_BYTES(zl), reqlen, prevlen = 0; size_t curlen = ZIPLIST_BYTES(zl), reqlen, prevlen = 0;
unsigned int offset, nextdiff = 0; size_t offset;
int nextdiff = 0;
unsigned char encoding = 0; unsigned char encoding = 0;
long long value; long long value;
zlentry entry, tail; zlentry entry, tail;
...@@ -678,7 +671,7 @@ unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char ...@@ -678,7 +671,7 @@ unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char
* Also update *p in place, to be able to iterate over the * Also update *p in place, to be able to iterate over the
* ziplist, while deleting entries. */ * ziplist, while deleting entries. */
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) { unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) {
unsigned int offset = *p-zl; size_t offset = *p-zl;
zl = __ziplistDelete(zl,*p,1); zl = __ziplistDelete(zl,*p,1);
/* Store pointer to current element in p, because ziplistDelete will /* Store pointer to current element in p, because ziplistDelete will
...@@ -887,7 +880,7 @@ void pop(unsigned char *zl, int where) { ...@@ -887,7 +880,7 @@ void pop(unsigned char *zl, int where) {
} }
} }
void randstring(char *target, unsigned int min, unsigned int max) { int randstring(char *target, unsigned int min, unsigned int max) {
int p, len = min+rand()%(max-min+1); int p, len = min+rand()%(max-min+1);
int minval, maxval; int minval, maxval;
switch(rand() % 3) { switch(rand() % 3) {
...@@ -909,10 +902,9 @@ void randstring(char *target, unsigned int min, unsigned int max) { ...@@ -909,10 +902,9 @@ void randstring(char *target, unsigned int min, unsigned int max) {
while(p < len) while(p < len)
target[p++] = minval+rand()%(maxval-minval+1); target[p++] = minval+rand()%(maxval-minval+1);
return; return len;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned char *zl, *p; unsigned char *zl, *p;
unsigned char *entry; unsigned char *entry;
...@@ -1245,6 +1237,7 @@ int main(int argc, char **argv) { ...@@ -1245,6 +1237,7 @@ int main(int argc, char **argv) {
int i,j,len,where; int i,j,len,where;
unsigned char *p; unsigned char *p;
char buf[1024]; char buf[1024];
int buflen;
list *ref; list *ref;
listNode *refnode; listNode *refnode;
...@@ -1253,10 +1246,6 @@ int main(int argc, char **argv) { ...@@ -1253,10 +1246,6 @@ int main(int argc, char **argv) {
unsigned int slen; unsigned int slen;
long long sval; long long sval;
/* In the regression for the cascade bug, it was triggered
* with a random seed of 2. */
srand(2);
for (i = 0; i < 20000; i++) { for (i = 0; i < 20000; i++) {
zl = ziplistNew(); zl = ziplistNew();
ref = listCreate(); ref = listCreate();
...@@ -1266,31 +1255,32 @@ int main(int argc, char **argv) { ...@@ -1266,31 +1255,32 @@ int main(int argc, char **argv) {
/* Create lists */ /* Create lists */
for (j = 0; j < len; j++) { for (j = 0; j < len; j++) {
where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL; where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
switch(rand() % 4) { if (rand() % 2) {
case 0: buflen = randstring(buf,1,sizeof(buf)-1);
sprintf(buf,"%lld",(0LL + rand()) >> 20); } else {
break; switch(rand() % 3) {
case 1: case 0:
sprintf(buf,"%lld",(0LL + rand())); buflen = sprintf(buf,"%lld",(0LL + rand()) >> 20);
break; break;
case 2: case 1:
sprintf(buf,"%lld",(0LL + rand()) << 20); buflen = sprintf(buf,"%lld",(0LL + rand()));
break; break;
case 3: case 2:
randstring(buf,0,256); buflen = sprintf(buf,"%lld",(0LL + rand()) << 20);
break; break;
default: default:
assert(NULL); assert(NULL);
}
} }
/* Add to ziplist */ /* Add to ziplist */
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), where); zl = ziplistPush(zl, (unsigned char*)buf, buflen, where);
/* Add to reference list */ /* Add to reference list */
if (where == ZIPLIST_HEAD) { if (where == ZIPLIST_HEAD) {
listAddNodeHead(ref,sdsnew(buf)); listAddNodeHead(ref,sdsnewlen(buf, buflen));
} else if (where == ZIPLIST_TAIL) { } else if (where == ZIPLIST_TAIL) {
listAddNodeTail(ref,sdsnew(buf)); listAddNodeTail(ref,sdsnewlen(buf, buflen));
} else { } else {
assert(NULL); assert(NULL);
} }
...@@ -1305,12 +1295,13 @@ int main(int argc, char **argv) { ...@@ -1305,12 +1295,13 @@ int main(int argc, char **argv) {
assert(ziplistGet(p,&sstr,&slen,&sval)); assert(ziplistGet(p,&sstr,&slen,&sval));
if (sstr == NULL) { if (sstr == NULL) {
sprintf(buf,"%lld",sval); buflen = sprintf(buf,"%lld",sval);
} else { } else {
memcpy(buf,sstr,slen); buflen = slen;
buf[slen] = '\0'; memcpy(buf,sstr,buflen);
buf[buflen] = '\0';
} }
assert(strcmp(buf,listNodeValue(refnode)) == 0); assert(memcmp(buf,listNodeValue(refnode),buflen) == 0);
} }
zfree(zl); zfree(zl);
listRelease(ref); listRelease(ref);
......
...@@ -31,13 +31,14 @@ tags {"aof"} { ...@@ -31,13 +31,14 @@ tags {"aof"} {
} }
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
test {Unfinished MULTI: Server should not have been started} { test "Unfinished MULTI: Server should not have been started" {
is_alive $srv assert_equal 0 [is_alive $srv]
} {0} }
test {Unfinished MULTI: Server should have logged an error} { test "Unfinished MULTI: Server should have logged an error" {
exec cat [dict get $srv stdout] | tail -n1 set result [exec cat [dict get $srv stdout] | tail -n1]
} {*Unexpected end of file reading the append only file*} assert_match "*Unexpected end of file reading the append only file*" $result
}
} }
## Test that the server exits when the AOF contains a short read ## Test that the server exits when the AOF contains a short read
...@@ -47,36 +48,57 @@ tags {"aof"} { ...@@ -47,36 +48,57 @@ tags {"aof"} {
} }
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
test {Short read: Server should not have been started} { test "Short read: Server should not have been started" {
is_alive $srv assert_equal 0 [is_alive $srv]
} {0} }
test {Short read: Server should have logged an error} { test "Short read: Server should have logged an error" {
exec cat [dict get $srv stdout] | tail -n1 set result [exec cat [dict get $srv stdout] | tail -n1]
} {*Bad file format reading the append only file*} assert_match "*Bad file format reading the append only file*" $result
}
} }
## Test that redis-check-aof indeed sees this AOF is not valid ## Test that redis-check-aof indeed sees this AOF is not valid
test {Short read: Utility should confirm the AOF is not valid} { test "Short read: Utility should confirm the AOF is not valid" {
catch { catch {
exec src/redis-check-aof $aof_path exec src/redis-check-aof $aof_path
} str } result
set _ $str assert_match "*not valid*" $result
} {*not valid*} }
test {Short read: Utility should be able to fix the AOF} { test "Short read: Utility should be able to fix the AOF" {
exec echo y | src/redis-check-aof --fix $aof_path set result [exec echo y | src/redis-check-aof --fix $aof_path]
} {*Successfully truncated AOF*} assert_match "*Successfully truncated AOF*" $result
}
## Test that the server can be started using the truncated AOF ## Test that the server can be started using the truncated AOF
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
test {Fixed AOF: Server should have been started} { test "Fixed AOF: Server should have been started" {
is_alive $srv assert_equal 1 [is_alive $srv]
} {1} }
test "Fixed AOF: Keyspace should contain values that were parsable" {
set client [redis [dict get $srv host] [dict get $srv port]]
assert_equal "hello" [$client get foo]
assert_equal "" [$client get bar]
}
}
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
create_aof {
append_to_aof [formatCommand sadd set foo]
append_to_aof [formatCommand sadd set bar]
append_to_aof [formatCommand spop set]
}
start_server_aof [list dir $server_path] {
test "AOF+SPOP: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
test {Fixed AOF: Keyspace should contain values that were parsable} { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port]]
list [$client get foo] [$client get bar] assert_equal 1 [$client scard set]
} {hello {}} }
} }
} }
...@@ -214,7 +214,7 @@ proc start_server {options {code undefined}} { ...@@ -214,7 +214,7 @@ proc start_server {options {code undefined}} {
# find out the pid # find out the pid
while {![info exists pid]} { while {![info exists pid]} {
regexp {^\[(\d+)\]} [exec head -n1 $stdout] _ pid regexp {\[(\d+)\]} [exec cat $stdout] _ pid
after 100 after 100
} }
......
...@@ -127,6 +127,7 @@ proc execute_everything {} { ...@@ -127,6 +127,7 @@ proc execute_everything {} {
# execute_tests "integration/redis-cli" # execute_tests "integration/redis-cli"
execute_tests "unit/pubsub" execute_tests "unit/pubsub"
return; # No diskstore tests for now...
# run tests with diskstore enabled # run tests with diskstore enabled
puts "\nRunning diskstore tests... this is slow, press Ctrl+C if not interested.." puts "\nRunning diskstore tests... this is slow, press Ctrl+C if not interested.."
set ::diskstore 1 set ::diskstore 1
......
...@@ -226,6 +226,15 @@ start_server {tags {"hash"}} { ...@@ -226,6 +226,15 @@ start_server {tags {"hash"}} {
set _ $rv set _ $rv
} {0 0 1 0 {} 1 0 {}} } {0 0 1 0 {} 1 0 {}}
test {HDEL - more than a single value} {
set rv {}
r del myhash
r hmset myhash a 1 b 2 c 3
assert_equal 0 [r hdel myhash x y]
assert_equal 2 [r hdel myhash a c f]
r hgetall myhash
} {b 2}
test {HEXISTS} { test {HEXISTS} {
set rv {} set rv {}
set k [lindex [array names smallhash *] 0] set k [lindex [array names smallhash *] 0]
......
...@@ -55,6 +55,13 @@ start_server { ...@@ -55,6 +55,13 @@ start_server {
assert_equal $largevalue(linkedlist) [r lindex mylist2 2] assert_equal $largevalue(linkedlist) [r lindex mylist2 2]
} }
test {Variadic RPUSH/LPUSH} {
r del mylist
assert_equal 4 [r lpush mylist a b c d]
assert_equal 8 [r rpush mylist 0 1 2 3]
assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1]
}
test {DEL a list - ziplist} { test {DEL a list - ziplist} {
assert_equal 1 [r del myziplist2] assert_equal 1 [r del myziplist2]
assert_equal 0 [r exists myziplist2] assert_equal 0 [r exists myziplist2]
...@@ -142,6 +149,15 @@ start_server { ...@@ -142,6 +149,15 @@ start_server {
} }
} }
test "BLPOP with variadic LPUSH" {
set rd [redis_deferring_client]
r del blist target
$rd blpop blist 0
assert_equal 2 [r lpush blist foo bar]
assert_equal {blist foo} [$rd read]
assert_equal bar [lindex [r lrange blist 0 -1] 0]
}
test "BRPOPLPUSH with zero timeout should block indefinitely" { test "BRPOPLPUSH with zero timeout should block indefinitely" {
set rd [redis_deferring_client] set rd [redis_deferring_client]
r del blist target r del blist target
......
...@@ -59,6 +59,13 @@ start_server { ...@@ -59,6 +59,13 @@ start_server {
assert_encoding hashtable myset assert_encoding hashtable myset
} }
test {Variadic SADD} {
r del myset
assert_equal 3 [r sadd myset a b c]
assert_equal 2 [r sadd myset A a b c B]
assert_equal [lsort {A a b c B}] [lsort [r smembers myset]]
}
test "Set encoding after DEBUG RELOAD" { test "Set encoding after DEBUG RELOAD" {
r del myintset myhashset mylargeintset r del myintset myhashset mylargeintset
for {set i 0} {$i < 100} {incr i} { r sadd myintset $i } for {set i 0} {$i < 100} {incr i} { r sadd myintset $i }
...@@ -90,6 +97,14 @@ start_server { ...@@ -90,6 +97,14 @@ start_server {
assert_equal {3 5} [lsort [r smembers myset]] assert_equal {3 5} [lsort [r smembers myset]]
} }
test {SREM with multiple arguments} {
r del myset
r sadd myset a b c d
assert_equal 0 [r srem myset k k k]
assert_equal 2 [r srem myset b d x y]
lsort [r smembers myset]
} {a c}
foreach {type} {hashtable intset} { foreach {type} {hashtable intset} {
for {set i 1} {$i <= 5} {incr i} { for {set i 1} {$i <= 5} {incr i} {
r del [format "set%d" $i] r del [format "set%d" $i]
......
...@@ -6,266 +6,562 @@ start_server {tags {"zset"}} { ...@@ -6,266 +6,562 @@ start_server {tags {"zset"}} {
} }
} }
test {ZSET basic ZADD and score update} { proc basics {encoding} {
r zadd ztmp 10 x if {$encoding == "ziplist"} {
r zadd ztmp 20 y r config set zset-max-ziplist-entries 128
r zadd ztmp 30 z r config set zset-max-ziplist-value 64
set aux1 [r zrange ztmp 0 -1] } elseif {$encoding == "skiplist"} {
r zadd ztmp 1 y r config set zset-max-ziplist-entries 0
set aux2 [r zrange ztmp 0 -1] r config set zset-max-ziplist-value 0
list $aux1 $aux2 } else {
} {{x y z} {y x z}} puts "Unknown sorted set encoding"
exit
test {ZCARD basics} { }
r zcard ztmp
} {3}
test {ZCARD non existing key} {
r zcard ztmp-blabla
} {0}
test "ZRANGE basics" {
r del ztmp
r zadd ztmp 1 a
r zadd ztmp 2 b
r zadd ztmp 3 c
r zadd ztmp 4 d
assert_equal {a b c d} [r zrange ztmp 0 -1]
assert_equal {a b c} [r zrange ztmp 0 -2]
assert_equal {b c d} [r zrange ztmp 1 -1]
assert_equal {b c} [r zrange ztmp 1 -2]
assert_equal {c d} [r zrange ztmp -2 -1]
assert_equal {c} [r zrange ztmp -2 -2]
# out of range start index
assert_equal {a b c} [r zrange ztmp -5 2]
assert_equal {a b} [r zrange ztmp -5 1]
assert_equal {} [r zrange ztmp 5 -1]
assert_equal {} [r zrange ztmp 5 -2]
# out of range end index
assert_equal {a b c d} [r zrange ztmp 0 5]
assert_equal {b c d} [r zrange ztmp 1 5]
assert_equal {} [r zrange ztmp 0 -5]
assert_equal {} [r zrange ztmp 1 -5]
# withscores
assert_equal {a 1 b 2 c 3 d 4} [r zrange ztmp 0 -1 withscores]
}
test "ZREVRANGE basics" { test "Check encoding - $encoding" {
r del ztmp r del ztmp
r zadd ztmp 1 a r zadd ztmp 10 x
r zadd ztmp 2 b assert_encoding $encoding ztmp
r zadd ztmp 3 c }
r zadd ztmp 4 d
assert_equal {d c b a} [r zrevrange ztmp 0 -1]
assert_equal {d c b} [r zrevrange ztmp 0 -2]
assert_equal {c b a} [r zrevrange ztmp 1 -1]
assert_equal {c b} [r zrevrange ztmp 1 -2]
assert_equal {b a} [r zrevrange ztmp -2 -1]
assert_equal {b} [r zrevrange ztmp -2 -2]
# out of range start index
assert_equal {d c b} [r zrevrange ztmp -5 2]
assert_equal {d c} [r zrevrange ztmp -5 1]
assert_equal {} [r zrevrange ztmp 5 -1]
assert_equal {} [r zrevrange ztmp 5 -2]
# out of range end index
assert_equal {d c b a} [r zrevrange ztmp 0 5]
assert_equal {c b a} [r zrevrange ztmp 1 5]
assert_equal {} [r zrevrange ztmp 0 -5]
assert_equal {} [r zrevrange ztmp 1 -5]
# withscores
assert_equal {d 4 c 3 b 2 a 1} [r zrevrange ztmp 0 -1 withscores]
}
test {ZRANK basics} { test "ZSET basic ZADD and score update - $encoding" {
r zadd zranktmp 10 x r del ztmp
r zadd zranktmp 20 y r zadd ztmp 10 x
r zadd zranktmp 30 z r zadd ztmp 20 y
list [r zrank zranktmp x] [r zrank zranktmp y] [r zrank zranktmp z] r zadd ztmp 30 z
} {0 1 2} assert_equal {x y z} [r zrange ztmp 0 -1]
test {ZREVRANK basics} { r zadd ztmp 1 y
list [r zrevrank zranktmp x] [r zrevrank zranktmp y] [r zrevrank zranktmp z] assert_equal {y x z} [r zrange ztmp 0 -1]
} {2 1 0}
test {ZRANK - after deletion} {
r zrem zranktmp y
list [r zrank zranktmp x] [r zrank zranktmp z]
} {0 1}
test {ZSCORE} {
set aux {}
set err {}
for {set i 0} {$i < 1000} {incr i} {
set score [expr rand()]
lappend aux $score
r zadd zscoretest $score $i
}
for {set i 0} {$i < 1000} {incr i} {
if {[r zscore zscoretest $i] != [lindex $aux $i]} {
set err "Expected score was [lindex $aux $i] but got [r zscore zscoretest $i] for element $i"
break
}
} }
set _ $err
} {} test "ZSET element can't be set to NaN with ZADD - $encoding" {
assert_error "*not a double*" {r zadd myzset nan abc}
test {ZSCORE after a DEBUG RELOAD} {
set aux {}
set err {}
r del zscoretest
for {set i 0} {$i < 1000} {incr i} {
set score [expr rand()]
lappend aux $score
r zadd zscoretest $score $i
}
r debug reload
for {set i 0} {$i < 1000} {incr i} {
if {[r zscore zscoretest $i] != [lindex $aux $i]} {
set err "Expected score was [lindex $aux $i] but got [r zscore zscoretest $i] for element $i"
break
}
} }
set _ $err
} {}
test {ZSETs stress tester - sorting is working well?} { test "ZSET element can't be set to NaN with ZINCRBY" {
set delta 0 assert_error "*not a double*" {r zadd myzset nan abc}
for {set test 0} {$test < 2} {incr test} { }
unset -nocomplain auxarray
array set auxarray {} test "ZINCRBY calls leading to NaN result in error" {
set auxlist {} r zincrby myzset +inf abc
r del myzset assert_error "*NaN*" {r zincrby myzset -inf abc}
for {set i 0} {$i < 1000} {incr i} { }
if {$test == 0} {
set score [expr rand()] test "ZCARD basics - $encoding" {
} else { assert_equal 3 [r zcard ztmp]
set score [expr int(rand()*10)] assert_equal 0 [r zcard zdoesntexist]
} }
set auxarray($i) $score
r zadd myzset $score $i test "ZREM removes key after last element is removed" {
# Random update r del ztmp
if {[expr rand()] < .2} { r zadd ztmp 10 x
set j [expr int(rand()*1000)] r zadd ztmp 20 y
if {$test == 0} {
set score [expr rand()] assert_equal 1 [r exists ztmp]
} else { assert_equal 0 [r zrem ztmp z]
set score [expr int(rand()*10)] assert_equal 1 [r zrem ztmp y]
} assert_equal 1 [r zrem ztmp x]
set auxarray($j) $score assert_equal 0 [r exists ztmp]
r zadd myzset $score $j }
}
test "ZRANGE basics - $encoding" {
r del ztmp
r zadd ztmp 1 a
r zadd ztmp 2 b
r zadd ztmp 3 c
r zadd ztmp 4 d
assert_equal {a b c d} [r zrange ztmp 0 -1]
assert_equal {a b c} [r zrange ztmp 0 -2]
assert_equal {b c d} [r zrange ztmp 1 -1]
assert_equal {b c} [r zrange ztmp 1 -2]
assert_equal {c d} [r zrange ztmp -2 -1]
assert_equal {c} [r zrange ztmp -2 -2]
# out of range start index
assert_equal {a b c} [r zrange ztmp -5 2]
assert_equal {a b} [r zrange ztmp -5 1]
assert_equal {} [r zrange ztmp 5 -1]
assert_equal {} [r zrange ztmp 5 -2]
# out of range end index
assert_equal {a b c d} [r zrange ztmp 0 5]
assert_equal {b c d} [r zrange ztmp 1 5]
assert_equal {} [r zrange ztmp 0 -5]
assert_equal {} [r zrange ztmp 1 -5]
# withscores
assert_equal {a 1 b 2 c 3 d 4} [r zrange ztmp 0 -1 withscores]
}
test "ZREVRANGE basics - $encoding" {
r del ztmp
r zadd ztmp 1 a
r zadd ztmp 2 b
r zadd ztmp 3 c
r zadd ztmp 4 d
assert_equal {d c b a} [r zrevrange ztmp 0 -1]
assert_equal {d c b} [r zrevrange ztmp 0 -2]
assert_equal {c b a} [r zrevrange ztmp 1 -1]
assert_equal {c b} [r zrevrange ztmp 1 -2]
assert_equal {b a} [r zrevrange ztmp -2 -1]
assert_equal {b} [r zrevrange ztmp -2 -2]
# out of range start index
assert_equal {d c b} [r zrevrange ztmp -5 2]
assert_equal {d c} [r zrevrange ztmp -5 1]
assert_equal {} [r zrevrange ztmp 5 -1]
assert_equal {} [r zrevrange ztmp 5 -2]
# out of range end index
assert_equal {d c b a} [r zrevrange ztmp 0 5]
assert_equal {c b a} [r zrevrange ztmp 1 5]
assert_equal {} [r zrevrange ztmp 0 -5]
assert_equal {} [r zrevrange ztmp 1 -5]
# withscores
assert_equal {d 4 c 3 b 2 a 1} [r zrevrange ztmp 0 -1 withscores]
}
test "ZRANK/ZREVRANK basics - $encoding" {
r del zranktmp
r zadd zranktmp 10 x
r zadd zranktmp 20 y
r zadd zranktmp 30 z
assert_equal 0 [r zrank zranktmp x]
assert_equal 1 [r zrank zranktmp y]
assert_equal 2 [r zrank zranktmp z]
assert_equal "" [r zrank zranktmp foo]
assert_equal 2 [r zrevrank zranktmp x]
assert_equal 1 [r zrevrank zranktmp y]
assert_equal 0 [r zrevrank zranktmp z]
assert_equal "" [r zrevrank zranktmp foo]
}
test "ZRANK - after deletion - $encoding" {
r zrem zranktmp y
assert_equal 0 [r zrank zranktmp x]
assert_equal 1 [r zrank zranktmp z]
}
test "ZINCRBY - can create a new sorted set - $encoding" {
r del zset
r zincrby zset 1 foo
assert_equal {foo} [r zrange zset 0 -1]
assert_equal 1 [r zscore zset foo]
}
test "ZINCRBY - increment and decrement - $encoding" {
r zincrby zset 2 foo
r zincrby zset 1 bar
assert_equal {bar foo} [r zrange zset 0 -1]
r zincrby zset 10 bar
r zincrby zset -5 foo
r zincrby zset -5 bar
assert_equal {foo bar} [r zrange zset 0 -1]
assert_equal -2 [r zscore zset foo]
assert_equal 6 [r zscore zset bar]
}
proc create_default_zset {} {
create_zset zset {-inf a 1 b 2 c 3 d 4 e 5 f +inf g}
}
test "ZRANGEBYSCORE/ZREVRANGEBYSCORE/ZCOUNT basics" {
create_default_zset
# inclusive range
assert_equal {a b c} [r zrangebyscore zset -inf 2]
assert_equal {b c d} [r zrangebyscore zset 0 3]
assert_equal {d e f} [r zrangebyscore zset 3 6]
assert_equal {e f g} [r zrangebyscore zset 4 +inf]
assert_equal {c b a} [r zrevrangebyscore zset 2 -inf]
assert_equal {d c b} [r zrevrangebyscore zset 3 0]
assert_equal {f e d} [r zrevrangebyscore zset 6 3]
assert_equal {g f e} [r zrevrangebyscore zset +inf 4]
assert_equal 3 [r zcount zset 0 3]
# exclusive range
assert_equal {b} [r zrangebyscore zset (-inf (2]
assert_equal {b c} [r zrangebyscore zset (0 (3]
assert_equal {e f} [r zrangebyscore zset (3 (6]
assert_equal {f} [r zrangebyscore zset (4 (+inf]
assert_equal {b} [r zrevrangebyscore zset (2 (-inf]
assert_equal {c b} [r zrevrangebyscore zset (3 (0]
assert_equal {f e} [r zrevrangebyscore zset (6 (3]
assert_equal {f} [r zrevrangebyscore zset (+inf (4]
assert_equal 2 [r zcount zset (0 (3]
# test empty ranges
r zrem zset a
r zrem zset g
# inclusive
assert_equal {} [r zrangebyscore zset 4 2]
assert_equal {} [r zrangebyscore zset 6 +inf]
assert_equal {} [r zrangebyscore zset -inf -6]
assert_equal {} [r zrevrangebyscore zset +inf 6]
assert_equal {} [r zrevrangebyscore zset -6 -inf]
# exclusive
assert_equal {} [r zrangebyscore zset (4 (2]
assert_equal {} [r zrangebyscore zset 2 (2]
assert_equal {} [r zrangebyscore zset (2 2]
assert_equal {} [r zrangebyscore zset (6 (+inf]
assert_equal {} [r zrangebyscore zset (-inf (-6]
assert_equal {} [r zrevrangebyscore zset (+inf (6]
assert_equal {} [r zrevrangebyscore zset (-6 (-inf]
# empty inner range
assert_equal {} [r zrangebyscore zset 2.4 2.6]
assert_equal {} [r zrangebyscore zset (2.4 2.6]
assert_equal {} [r zrangebyscore zset 2.4 (2.6]
assert_equal {} [r zrangebyscore zset (2.4 (2.6]
}
test "ZRANGEBYSCORE with WITHSCORES" {
create_default_zset
assert_equal {b 1 c 2 d 3} [r zrangebyscore zset 0 3 withscores]
assert_equal {d 3 c 2 b 1} [r zrevrangebyscore zset 3 0 withscores]
}
test "ZRANGEBYSCORE with LIMIT" {
create_default_zset
assert_equal {b c} [r zrangebyscore zset 0 10 LIMIT 0 2]
assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 3]
assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 10]
assert_equal {} [r zrangebyscore zset 0 10 LIMIT 20 10]
assert_equal {f e} [r zrevrangebyscore zset 10 0 LIMIT 0 2]
assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 3]
assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 10]
assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT 20 10]
}
test "ZRANGEBYSCORE with LIMIT and WITHSCORES" {
create_default_zset
assert_equal {e 4 f 5} [r zrangebyscore zset 2 5 LIMIT 2 3 WITHSCORES]
assert_equal {d 3 c 2} [r zrevrangebyscore zset 5 2 LIMIT 2 3 WITHSCORES]
}
test "ZRANGEBYSCORE with non-value min or max" {
assert_error "*not a double*" {r zrangebyscore fooz str 1}
assert_error "*not a double*" {r zrangebyscore fooz 1 str}
assert_error "*not a double*" {r zrangebyscore fooz 1 NaN}
}
test "ZREMRANGEBYSCORE basics" {
proc remrangebyscore {min max} {
create_zset zset {1 a 2 b 3 c 4 d 5 e}
assert_equal 1 [r exists zset]
r zremrangebyscore zset $min $max
} }
foreach {item score} [array get auxarray] {
lappend auxlist [list $score $item] # inner range
assert_equal 3 [remrangebyscore 2 4]
assert_equal {a e} [r zrange zset 0 -1]
# start underflow
assert_equal 1 [remrangebyscore -10 1]
assert_equal {b c d e} [r zrange zset 0 -1]
# end overflow
assert_equal 1 [remrangebyscore 5 10]
assert_equal {a b c d} [r zrange zset 0 -1]
# switch min and max
assert_equal 0 [remrangebyscore 4 2]
assert_equal {a b c d e} [r zrange zset 0 -1]
# -inf to mid
assert_equal 3 [remrangebyscore -inf 3]
assert_equal {d e} [r zrange zset 0 -1]
# mid to +inf
assert_equal 3 [remrangebyscore 3 +inf]
assert_equal {a b} [r zrange zset 0 -1]
# -inf to +inf
assert_equal 5 [remrangebyscore -inf +inf]
assert_equal {} [r zrange zset 0 -1]
# exclusive min
assert_equal 4 [remrangebyscore (1 5]
assert_equal {a} [r zrange zset 0 -1]
assert_equal 3 [remrangebyscore (2 5]
assert_equal {a b} [r zrange zset 0 -1]
# exclusive max
assert_equal 4 [remrangebyscore 1 (5]
assert_equal {e} [r zrange zset 0 -1]
assert_equal 3 [remrangebyscore 1 (4]
assert_equal {d e} [r zrange zset 0 -1]
# exclusive min and max
assert_equal 3 [remrangebyscore (1 (5]
assert_equal {a e} [r zrange zset 0 -1]
# destroy when empty
assert_equal 5 [remrangebyscore 1 5]
assert_equal 0 [r exists zset]
}
test "ZREMRANGEBYSCORE with non-value min or max" {
assert_error "*not a double*" {r zremrangebyscore fooz str 1}
assert_error "*not a double*" {r zremrangebyscore fooz 1 str}
assert_error "*not a double*" {r zremrangebyscore fooz 1 NaN}
}
test "ZREMRANGEBYRANK basics" {
proc remrangebyrank {min max} {
create_zset zset {1 a 2 b 3 c 4 d 5 e}
assert_equal 1 [r exists zset]
r zremrangebyrank zset $min $max
} }
set sorted [lsort -command zlistAlikeSort $auxlist]
set auxlist {} # inner range
foreach x $sorted { assert_equal 3 [remrangebyrank 1 3]
lappend auxlist [lindex $x 1] assert_equal {a e} [r zrange zset 0 -1]
# start underflow
assert_equal 1 [remrangebyrank -10 0]
assert_equal {b c d e} [r zrange zset 0 -1]
# start overflow
assert_equal 0 [remrangebyrank 10 -1]
assert_equal {a b c d e} [r zrange zset 0 -1]
# end underflow
assert_equal 0 [remrangebyrank 0 -10]
assert_equal {a b c d e} [r zrange zset 0 -1]
# end overflow
assert_equal 5 [remrangebyrank 0 10]
assert_equal {} [r zrange zset 0 -1]
# destroy when empty
assert_equal 5 [remrangebyrank 0 4]
assert_equal 0 [r exists zset]
}
test "ZUNIONSTORE against non-existing key doesn't set destination - $encoding" {
r del zseta
assert_equal 0 [r zunionstore dst_key 1 zseta]
assert_equal 0 [r exists dst_key]
}
test "ZUNIONSTORE basics - $encoding" {
r del zseta zsetb zsetc
r zadd zseta 1 a
r zadd zseta 2 b
r zadd zseta 3 c
r zadd zsetb 1 b
r zadd zsetb 2 c
r zadd zsetb 3 d
assert_equal 4 [r zunionstore zsetc 2 zseta zsetb]
assert_equal {a 1 b 3 d 3 c 5} [r zrange zsetc 0 -1 withscores]
}
test "ZUNIONSTORE with weights - $encoding" {
assert_equal 4 [r zunionstore zsetc 2 zseta zsetb weights 2 3]
assert_equal {a 2 b 7 d 9 c 12} [r zrange zsetc 0 -1 withscores]
}
test "ZUNIONSTORE with a regular set and weights - $encoding" {
r del seta
r sadd seta a
r sadd seta b
r sadd seta c
assert_equal 4 [r zunionstore zsetc 2 seta zsetb weights 2 3]
assert_equal {a 2 b 5 c 8 d 9} [r zrange zsetc 0 -1 withscores]
}
test "ZUNIONSTORE with AGGREGATE MIN - $encoding" {
assert_equal 4 [r zunionstore zsetc 2 zseta zsetb aggregate min]
assert_equal {a 1 b 1 c 2 d 3} [r zrange zsetc 0 -1 withscores]
}
test "ZUNIONSTORE with AGGREGATE MAX - $encoding" {
assert_equal 4 [r zunionstore zsetc 2 zseta zsetb aggregate max]
assert_equal {a 1 b 2 c 3 d 3} [r zrange zsetc 0 -1 withscores]
}
test "ZINTERSTORE basics - $encoding" {
assert_equal 2 [r zinterstore zsetc 2 zseta zsetb]
assert_equal {b 3 c 5} [r zrange zsetc 0 -1 withscores]
}
test "ZINTERSTORE with weights - $encoding" {
assert_equal 2 [r zinterstore zsetc 2 zseta zsetb weights 2 3]
assert_equal {b 7 c 12} [r zrange zsetc 0 -1 withscores]
}
test "ZINTERSTORE with a regular set and weights - $encoding" {
r del seta
r sadd seta a
r sadd seta b
r sadd seta c
assert_equal 2 [r zinterstore zsetc 2 seta zsetb weights 2 3]
assert_equal {b 5 c 8} [r zrange zsetc 0 -1 withscores]
}
test "ZINTERSTORE with AGGREGATE MIN - $encoding" {
assert_equal 2 [r zinterstore zsetc 2 zseta zsetb aggregate min]
assert_equal {b 1 c 2} [r zrange zsetc 0 -1 withscores]
}
test "ZINTERSTORE with AGGREGATE MAX - $encoding" {
assert_equal 2 [r zinterstore zsetc 2 zseta zsetb aggregate max]
assert_equal {b 2 c 3} [r zrange zsetc 0 -1 withscores]
}
foreach cmd {ZUNIONSTORE ZINTERSTORE} {
test "$cmd with +inf/-inf scores - $encoding" {
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]
} }
set fromredis [r zrange myzset 0 -1]
set delta 0 test "$cmd with NaN weights $encoding" {
for {set i 0} {$i < [llength $fromredis]} {incr i} { r del zsetinf1 zsetinf2
if {[lindex $fromredis $i] != [lindex $auxlist $i]} {
incr delta 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
} }
} }
} }
format $delta
} {0}
test {ZINCRBY - can create a new sorted set} {
r del zset
r zincrby zset 1 foo
list [r zrange zset 0 -1] [r zscore zset foo]
} {foo 1}
test {ZINCRBY - increment and decrement} {
r zincrby zset 2 foo
r zincrby zset 1 bar
set v1 [r zrange zset 0 -1]
r zincrby zset 10 bar
r zincrby zset -5 foo
r zincrby zset -5 bar
set v2 [r zrange zset 0 -1]
list $v1 $v2 [r zscore zset foo] [r zscore zset bar]
} {{bar foo} {foo bar} -2 6}
proc create_default_zset {} {
create_zset zset {-inf a 1 b 2 c 3 d 4 e 5 f +inf g}
} }
test "ZRANGEBYSCORE/ZREVRANGEBYSCORE/ZCOUNT basics" { basics ziplist
create_default_zset basics skiplist
# inclusive range proc stressers {encoding} {
assert_equal {a b c} [r zrangebyscore zset -inf 2] if {$encoding == "ziplist"} {
assert_equal {b c d} [r zrangebyscore zset 0 3] # Little extra to allow proper fuzzing in the sorting stresser
assert_equal {d e f} [r zrangebyscore zset 3 6] r config set zset-max-ziplist-entries 256
assert_equal {e f g} [r zrangebyscore zset 4 +inf] r config set zset-max-ziplist-value 64
assert_equal {c b a} [r zrevrangebyscore zset 2 -inf] set elements 128
assert_equal {d c b} [r zrevrangebyscore zset 3 0] } elseif {$encoding == "skiplist"} {
assert_equal {f e d} [r zrevrangebyscore zset 6 3] r config set zset-max-ziplist-entries 0
assert_equal {g f e} [r zrevrangebyscore zset +inf 4] r config set zset-max-ziplist-value 0
assert_equal 3 [r zcount zset 0 3] set elements 1000
} else {
# exclusive range puts "Unknown sorted set encoding"
assert_equal {b} [r zrangebyscore zset (-inf (2] exit
assert_equal {b c} [r zrangebyscore zset (0 (3] }
assert_equal {e f} [r zrangebyscore zset (3 (6]
assert_equal {f} [r zrangebyscore zset (4 (+inf]
assert_equal {b} [r zrevrangebyscore zset (2 (-inf]
assert_equal {c b} [r zrevrangebyscore zset (3 (0]
assert_equal {f e} [r zrevrangebyscore zset (6 (3]
assert_equal {f} [r zrevrangebyscore zset (+inf (4]
assert_equal 2 [r zcount zset (0 (3]
}
test "ZRANGEBYSCORE with WITHSCORES" { test "ZSCORE - $encoding" {
create_default_zset r del zscoretest
assert_equal {b 1 c 2 d 3} [r zrangebyscore zset 0 3 withscores] set aux {}
assert_equal {d 3 c 2 b 1} [r zrevrangebyscore zset 3 0 withscores] for {set i 0} {$i < $elements} {incr i} {
} set score [expr rand()]
lappend aux $score
r zadd zscoretest $score $i
}
test "ZRANGEBYSCORE with LIMIT" { assert_encoding $encoding zscoretest
create_default_zset for {set i 0} {$i < $elements} {incr i} {
assert_equal {b c} [r zrangebyscore zset 0 10 LIMIT 0 2] assert_equal [lindex $aux $i] [r zscore zscoretest $i]
assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 3] }
assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 10] }
assert_equal {} [r zrangebyscore zset 0 10 LIMIT 20 10]
assert_equal {f e} [r zrevrangebyscore zset 10 0 LIMIT 0 2]
assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 3]
assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 10]
assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT 20 10]
}
test "ZRANGEBYSCORE with LIMIT and WITHSCORES" { test "ZSCORE after a DEBUG RELOAD - $encoding" {
create_default_zset r del zscoretest
assert_equal {e 4 f 5} [r zrangebyscore zset 2 5 LIMIT 2 3 WITHSCORES] set aux {}
assert_equal {d 3 c 2} [r zrevrangebyscore zset 5 2 LIMIT 2 3 WITHSCORES] for {set i 0} {$i < $elements} {incr i} {
} set score [expr rand()]
lappend aux $score
r zadd zscoretest $score $i
}
test "ZRANGEBYSCORE with non-value min or max" { r debug reload
assert_error "*not a double*" {r zrangebyscore fooz str 1} assert_encoding $encoding zscoretest
assert_error "*not a double*" {r zrangebyscore fooz 1 str} for {set i 0} {$i < $elements} {incr i} {
assert_error "*not a double*" {r zrangebyscore fooz 1 NaN} assert_equal [lindex $aux $i] [r zscore zscoretest $i]
} }
}
tags {"slow"} { test "ZSET sorting stresser - $encoding" {
test {ZRANGEBYSCORE fuzzy test, 100 ranges in 1000 elements sorted set} { set delta 0
for {set test 0} {$test < 2} {incr test} {
unset -nocomplain auxarray
array set auxarray {}
set auxlist {}
r del myzset
for {set i 0} {$i < $elements} {incr i} {
if {$test == 0} {
set score [expr rand()]
} else {
set score [expr int(rand()*10)]
}
set auxarray($i) $score
r zadd myzset $score $i
# Random update
if {[expr rand()] < .2} {
set j [expr int(rand()*1000)]
if {$test == 0} {
set score [expr rand()]
} else {
set score [expr int(rand()*10)]
}
set auxarray($j) $score
r zadd myzset $score $j
}
}
foreach {item score} [array get auxarray] {
lappend auxlist [list $score $item]
}
set sorted [lsort -command zlistAlikeSort $auxlist]
set auxlist {}
foreach x $sorted {
lappend auxlist [lindex $x 1]
}
assert_encoding $encoding myzset
set fromredis [r zrange myzset 0 -1]
set delta 0
for {set i 0} {$i < [llength $fromredis]} {incr i} {
if {[lindex $fromredis $i] != [lindex $auxlist $i]} {
incr delta
}
}
}
assert_equal 0 $delta
}
test "ZRANGEBYSCORE fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
set err {} set err {}
r del zset r del zset
for {set i 0} {$i < 1000} {incr i} { for {set i 0} {$i < $elements} {incr i} {
r zadd zset [expr rand()] $i r zadd zset [expr rand()] $i
} }
assert_encoding $encoding zset
for {set i 0} {$i < 100} {incr i} { for {set i 0} {$i < 100} {incr i} {
set min [expr rand()] set min [expr rand()]
set max [expr rand()] set max [expr rand()]
...@@ -337,198 +633,17 @@ start_server {tags {"zset"}} { ...@@ -337,198 +633,17 @@ start_server {tags {"zset"}} {
} }
} }
} }
set _ $err assert_equal {} $err
} {}
}
test "ZREMRANGEBYSCORE basics" {
proc remrangebyscore {min max} {
create_zset zset {1 a 2 b 3 c 4 d 5 e}
r zremrangebyscore zset $min $max
}
# inner range
assert_equal 3 [remrangebyscore 2 4]
assert_equal {a e} [r zrange zset 0 -1]
# start underflow
assert_equal 1 [remrangebyscore -10 1]
assert_equal {b c d e} [r zrange zset 0 -1]
# end overflow
assert_equal 1 [remrangebyscore 5 10]
assert_equal {a b c d} [r zrange zset 0 -1]
# switch min and max
assert_equal 0 [remrangebyscore 4 2]
assert_equal {a b c d e} [r zrange zset 0 -1]
# -inf to mid
assert_equal 3 [remrangebyscore -inf 3]
assert_equal {d e} [r zrange zset 0 -1]
# mid to +inf
assert_equal 3 [remrangebyscore 3 +inf]
assert_equal {a b} [r zrange zset 0 -1]
# -inf to +inf
assert_equal 5 [remrangebyscore -inf +inf]
assert_equal {} [r zrange zset 0 -1]
# exclusive min
assert_equal 4 [remrangebyscore (1 5]
assert_equal {a} [r zrange zset 0 -1]
assert_equal 3 [remrangebyscore (2 5]
assert_equal {a b} [r zrange zset 0 -1]
# exclusive max
assert_equal 4 [remrangebyscore 1 (5]
assert_equal {e} [r zrange zset 0 -1]
assert_equal 3 [remrangebyscore 1 (4]
assert_equal {d e} [r zrange zset 0 -1]
# exclusive min and max
assert_equal 3 [remrangebyscore (1 (5]
assert_equal {a e} [r zrange zset 0 -1]
}
test "ZREMRANGEBYSCORE with non-value min or max" {
assert_error "*not a double*" {r zremrangebyscore fooz str 1}
assert_error "*not a double*" {r zremrangebyscore fooz 1 str}
assert_error "*not a double*" {r zremrangebyscore fooz 1 NaN}
}
test "ZREMRANGEBYRANK basics" {
proc remrangebyrank {min max} {
create_zset zset {1 a 2 b 3 c 4 d 5 e}
r zremrangebyrank zset $min $max
}
# inner range
assert_equal 3 [remrangebyrank 1 3]
assert_equal {a e} [r zrange zset 0 -1]
# start underflow
assert_equal 1 [remrangebyrank -10 0]
assert_equal {b c d e} [r zrange zset 0 -1]
# start overflow
assert_equal 0 [remrangebyrank 10 -1]
assert_equal {a b c d e} [r zrange zset 0 -1]
# end underflow
assert_equal 0 [remrangebyrank 0 -10]
assert_equal {a b c d e} [r zrange zset 0 -1]
# end overflow
assert_equal 5 [remrangebyrank 0 10]
assert_equal {} [r zrange zset 0 -1]
}
test {ZUNIONSTORE against non-existing key doesn't set destination} {
r del zseta
list [r zunionstore dst_key 1 zseta] [r exists dst_key]
} {0 0}
test {ZUNIONSTORE basics} {
r del zseta zsetb zsetc
r zadd zseta 1 a
r zadd zseta 2 b
r zadd zseta 3 c
r zadd zsetb 1 b
r zadd zsetb 2 c
r zadd zsetb 3 d
list [r zunionstore zsetc 2 zseta zsetb] [r zrange zsetc 0 -1 withscores]
} {4 {a 1 b 3 d 3 c 5}}
test {ZUNIONSTORE with weights} {
list [r zunionstore zsetc 2 zseta zsetb weights 2 3] [r zrange zsetc 0 -1 withscores]
} {4 {a 2 b 7 d 9 c 12}}
test {ZUNIONSTORE with a regular set and weights} {
r del seta
r sadd seta a
r sadd seta b
r sadd seta c
list [r zunionstore zsetc 2 seta zsetb weights 2 3] [r zrange zsetc 0 -1 withscores]
} {4 {a 2 b 5 c 8 d 9}}
test {ZUNIONSTORE with AGGREGATE MIN} {
list [r zunionstore zsetc 2 zseta zsetb aggregate min] [r zrange zsetc 0 -1 withscores]
} {4 {a 1 b 1 c 2 d 3}}
test {ZUNIONSTORE with AGGREGATE MAX} {
list [r zunionstore zsetc 2 zseta zsetb aggregate max] [r zrange zsetc 0 -1 withscores]
} {4 {a 1 b 2 c 3 d 3}}
test {ZINTERSTORE basics} {
list [r zinterstore zsetc 2 zseta zsetb] [r zrange zsetc 0 -1 withscores]
} {2 {b 3 c 5}}
test {ZINTERSTORE with weights} {
list [r zinterstore zsetc 2 zseta zsetb weights 2 3] [r zrange zsetc 0 -1 withscores]
} {2 {b 7 c 12}}
test {ZINTERSTORE with a regular set and weights} {
r del seta
r sadd seta a
r sadd seta b
r sadd seta c
list [r zinterstore zsetc 2 seta zsetb weights 2 3] [r zrange zsetc 0 -1 withscores]
} {2 {b 5 c 8}}
test {ZINTERSTORE with AGGREGATE MIN} {
list [r zinterstore zsetc 2 zseta zsetb aggregate min] [r zrange zsetc 0 -1 withscores]
} {2 {b 1 c 2}}
test {ZINTERSTORE with AGGREGATE MAX} {
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 - $encoding" {
test {ZSETs skiplist implementation backlink consistency test} {
set diff 0 set diff 0
set elements 10000
for {set j 0} {$j < $elements} {incr j} { for {set j 0} {$j < $elements} {incr j} {
r zadd myzset [expr rand()] "Element-$j" r zadd myzset [expr rand()] "Element-$j"
r zrem myzset "Element-[expr int(rand()*$elements)]" r zrem myzset "Element-[expr int(rand()*$elements)]"
} }
assert_encoding $encoding myzset
set l1 [r zrange myzset 0 -1] set l1 [r zrange myzset 0 -1]
set l2 [r zrevrange myzset 0 -1] set l2 [r zrevrange myzset 0 -1]
for {set j 0} {$j < [llength $l1]} {incr j} { for {set j 0} {$j < [llength $l1]} {incr j} {
...@@ -536,20 +651,22 @@ start_server {tags {"zset"}} { ...@@ -536,20 +651,22 @@ start_server {tags {"zset"}} {
incr diff incr diff
} }
} }
format $diff assert_equal 0 $diff
} {0} }
test {ZSETs ZRANK augmented skip list stress testing} { test "ZSETs ZRANK augmented skip list stress testing - $encoding" {
set err {} set err {}
r del myzset r del myzset
for {set k 0} {$k < 10000} {incr k} { for {set k 0} {$k < 2000} {incr k} {
set i [expr {$k%1000}] set i [expr {$k % $elements}]
if {[expr rand()] < .2} { if {[expr rand()] < .2} {
r zrem myzset $i r zrem myzset $i
} else { } else {
set score [expr rand()] set score [expr rand()]
r zadd myzset $score $i r zadd myzset $score $i
assert_encoding $encoding myzset
} }
set card [r zcard myzset] set card [r zcard myzset]
if {$card > 0} { if {$card > 0} {
set index [randomInt $card] set index [randomInt $card]
...@@ -561,20 +678,12 @@ start_server {tags {"zset"}} { ...@@ -561,20 +678,12 @@ start_server {tags {"zset"}} {
} }
} }
} }
set _ $err assert_equal {} $err
} {} }
}
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} {
assert_error "*not a double*" {r zadd myzset nan abc}
} }
test {ZINCRBY calls leading to NaN result in error} { tags {"slow"} {
r zincrby myzset +inf abc stressers ziplist
assert_error "*NaN*" {r zincrby myzset -inf abc} stressers skiplist
} }
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment