+/* Find the first node having a score equal or greater than the specified one.
+ * Returns NULL if there is no match. */
+static zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
+ zskiplistNode *x;
+ int i;
+
+ x = zsl->header;
+ for (i = zsl->level-1; i >= 0; i--) {
+ while (x->forward[i] && x->forward[i]->score < score)
+ x = x->forward[i];
+ }
+ /* 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->forward[0];
+}
+
+/* The actual Z-commands implementations */
+
+static void zaddCommand(redisClient *c) {
+ robj *zsetobj;
+ zset *zs;
+ double *score;
+
+ zsetobj = lookupKeyWrite(c->db,c->argv[1]);
+ if (zsetobj == NULL) {
+ zsetobj = createZsetObject();
+ dictAdd(c->db->dict,c->argv[1],zsetobj);
+ incrRefCount(c->argv[1]);
+ } else {
+ if (zsetobj->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+ }
+ score = zmalloc(sizeof(double));
+ *score = strtod(c->argv[2]->ptr,NULL);
+ zs = zsetobj->ptr;
+ if (dictAdd(zs->dict,c->argv[3],score) == DICT_OK) {
+ /* case 1: New element */
+ incrRefCount(c->argv[3]); /* added to hash */
+ zslInsert(zs->zsl,*score,c->argv[3]);
+ incrRefCount(c->argv[3]); /* added to skiplist */
+ server.dirty++;
+ addReply(c,shared.cone);
+ } else {
+ dictEntry *de;
+ double *oldscore;
+
+ /* case 2: Score update operation */
+ de = dictFind(zs->dict,c->argv[3]);
+ assert(de != NULL);
+ oldscore = dictGetEntryVal(de);
+ if (*score != *oldscore) {
+ int deleted;
+
+ deleted = zslDelete(zs->zsl,*oldscore,c->argv[3]);
+ assert(deleted != 0);
+ zslInsert(zs->zsl,*score,c->argv[3]);
+ incrRefCount(c->argv[3]);
+ dictReplace(zs->dict,c->argv[3],score);
+ server.dirty++;
+ } else {
+ zfree(score);
+ }
+ addReply(c,shared.czero);
+ }
+}
+
+static void zremCommand(redisClient *c) {
+ robj *zsetobj;
+ zset *zs;
+
+ zsetobj = lookupKeyWrite(c->db,c->argv[1]);
+ if (zsetobj == NULL) {
+ addReply(c,shared.czero);
+ } else {
+ dictEntry *de;
+ double *oldscore;
+ int deleted;
+
+ if (zsetobj->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+ zs = zsetobj->ptr;
+ de = dictFind(zs->dict,c->argv[2]);
+ if (de == NULL) {
+ addReply(c,shared.czero);
+ return;
+ }
+ /* Delete from the skiplist */
+ oldscore = dictGetEntryVal(de);
+ deleted = zslDelete(zs->zsl,*oldscore,c->argv[2]);
+ assert(deleted != 0);
+
+ /* Delete from the hash table */
+ dictDelete(zs->dict,c->argv[2]);
+ if (htNeedsResize(zs->dict)) dictResize(zs->dict);
+ server.dirty++;
+ addReply(c,shared.cone);
+ }
+}
+
+static void zremrangebyscoreCommand(redisClient *c) {
+ double min = strtod(c->argv[2]->ptr,NULL);
+ double max = strtod(c->argv[3]->ptr,NULL);
+ robj *zsetobj;
+ zset *zs;
+
+ zsetobj = lookupKeyWrite(c->db,c->argv[1]);
+ if (zsetobj == NULL) {
+ addReply(c,shared.czero);
+ } else {
+ long deleted;
+
+ if (zsetobj->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+ zs = zsetobj->ptr;
+ deleted = zslDeleteRange(zs->zsl,min,max,zs->dict);
+ if (htNeedsResize(zs->dict)) dictResize(zs->dict);
+ server.dirty += deleted;
+ addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",deleted));
+ }
+}
+
+static void zrangeGenericCommand(redisClient *c, int reverse) {
+ robj *o;
+ int start = atoi(c->argv[2]->ptr);
+ int end = atoi(c->argv[3]->ptr);
+
+ o = lookupKeyRead(c->db,c->argv[1]);
+ if (o == NULL) {
+ addReply(c,shared.nullmultibulk);
+ } else {
+ if (o->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ } else {
+ zset *zsetobj = o->ptr;
+ zskiplist *zsl = zsetobj->zsl;
+ zskiplistNode *ln;
+
+ int llen = zsl->length;
+ int rangelen, j;
+ robj *ele;
+
+ /* convert negative indexes */
+ if (start < 0) start = llen+start;
+ if (end < 0) end = llen+end;
+ if (start < 0) start = 0;
+ if (end < 0) end = 0;
+
+ /* indexes sanity checks */
+ if (start > end || start >= llen) {
+ /* Out of range start or start > end result in empty list */
+ addReply(c,shared.emptymultibulk);
+ return;
+ }
+ if (end >= llen) end = llen-1;
+ rangelen = (end-start)+1;
+
+ /* Return the result in form of a multi-bulk reply */
+ if (reverse) {
+ ln = zsl->tail;
+ while (start--)
+ ln = ln->backward;
+ } else {
+ ln = zsl->header->forward[0];
+ while (start--)
+ ln = ln->forward[0];
+ }
+
+ addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen));
+ for (j = 0; j < rangelen; j++) {
+ ele = ln->obj;
+ addReplyBulkLen(c,ele);
+ addReply(c,ele);
+ addReply(c,shared.crlf);
+ ln = reverse ? ln->backward : ln->forward[0];
+ }
+ }
+ }
+}
+
+static void zrangeCommand(redisClient *c) {
+ zrangeGenericCommand(c,0);
+}
+
+static void zrevrangeCommand(redisClient *c) {
+ zrangeGenericCommand(c,1);
+}
+
+static void zrangebyscoreCommand(redisClient *c) {
+ robj *o;
+ double min = strtod(c->argv[2]->ptr,NULL);
+ double max = strtod(c->argv[3]->ptr,NULL);
+
+ o = lookupKeyRead(c->db,c->argv[1]);
+ if (o == NULL) {
+ addReply(c,shared.nullmultibulk);
+ } else {
+ if (o->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ } else {
+ zset *zsetobj = o->ptr;
+ zskiplist *zsl = zsetobj->zsl;
+ zskiplistNode *ln;
+ robj *ele, *lenobj;
+ unsigned int rangelen = 0;
+
+ /* Get the first node with the score >= min */
+ ln = zslFirstWithScore(zsl,min);
+ if (ln == NULL) {
+ /* No element matching the speciifed interval */
+ addReply(c,shared.emptymultibulk);
+ 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 */
+ lenobj = createObject(REDIS_STRING,NULL);
+ addReply(c,lenobj);
+
+ while(ln && ln->score <= max) {
+ ele = ln->obj;
+ addReplyBulkLen(c,ele);
+ addReply(c,ele);
+ addReply(c,shared.crlf);
+ ln = ln->forward[0];
+ rangelen++;
+ }
+ lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",rangelen);
+ }
+ }
+}
+
+static void zcardCommand(redisClient *c) {
+ robj *o;
+ zset *zs;
+
+ o = lookupKeyRead(c->db,c->argv[1]);
+ if (o == NULL) {
+ addReply(c,shared.czero);
+ return;
+ } else {
+ if (o->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ } else {
+ zs = o->ptr;
+ addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",zs->zsl->length));
+ }
+ }
+}
+
+static void zscoreCommand(redisClient *c) {
+ robj *o;
+ zset *zs;
+
+ o = lookupKeyRead(c->db,c->argv[1]);
+ if (o == NULL) {
+ addReply(c,shared.nullbulk);
+ return;
+ } else {
+ if (o->type != REDIS_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ } else {
+ dictEntry *de;
+
+ zs = o->ptr;
+ de = dictFind(zs->dict,c->argv[2]);
+ if (!de) {
+ addReply(c,shared.nullbulk);
+ } else {
+ char buf[128];
+ double *score = dictGetEntryVal(de);
+
+ snprintf(buf,sizeof(buf),"%.17g",*score);
+ addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n%s\r\n",
+ strlen(buf),buf));
+ }
+ }
+ }
+}
+
+/* ========================= Non type-specific commands ==================== */
+
+static void flushdbCommand(redisClient *c) {
+ server.dirty += dictSize(c->db->dict);
+ dictEmpty(c->db->dict);
+ dictEmpty(c->db->expires);
+ addReply(c,shared.ok);
+}
+
+static void flushallCommand(redisClient *c) {
+ server.dirty += emptyDb();
+ addReply(c,shared.ok);
+ rdbSave(server.dbfilename);
+ server.dirty++;
+}
+
+static redisSortOperation *createSortOperation(int type, robj *pattern) {
+ redisSortOperation *so = zmalloc(sizeof(*so));
+ so->type = type;
+ so->pattern = pattern;
+ return so;
+}
+
+/* Return the value associated to the key with a name obtained
+ * substituting the first occurence of '*' in 'pattern' with 'subst' */
+static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
+ char *p;
+ sds spat, ssub;
+ robj keyobj;
+ int prefixlen, sublen, postfixlen;
+ /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */
+ struct {
+ long len;
+ long free;
+ char buf[REDIS_SORTKEY_MAX+1];
+ } keyname;
+
+ if (subst->encoding == REDIS_ENCODING_RAW)
+ incrRefCount(subst);
+ else {
+ subst = getDecodedObject(subst);
+ }
+
+ spat = pattern->ptr;
+ ssub = subst->ptr;
+ if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL;
+ p = strchr(spat,'*');
+ if (!p) return NULL;
+
+ prefixlen = p-spat;
+ sublen = sdslen(ssub);
+ postfixlen = sdslen(spat)-(prefixlen+1);
+ memcpy(keyname.buf,spat,prefixlen);
+ memcpy(keyname.buf+prefixlen,ssub,sublen);
+ memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen);
+ keyname.buf[prefixlen+sublen+postfixlen] = '\0';
+ keyname.len = prefixlen+sublen+postfixlen;
+
+ keyobj.refcount = 1;
+ keyobj.type = REDIS_STRING;
+ keyobj.ptr = ((char*)&keyname)+(sizeof(long)*2);
+
+ decrRefCount(subst);
+
+ /* printf("lookup '%s' => %p\n", keyname.buf,de); */
+ return lookupKeyRead(db,&keyobj);
+}
+
+/* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
+ * the additional parameter is not standard but a BSD-specific we have to
+ * pass sorting parameters via the global 'server' structure */
+static int sortCompare(const void *s1, const void *s2) {
+ const redisSortObject *so1 = s1, *so2 = s2;
+ int cmp;
+
+ if (!server.sort_alpha) {
+ /* Numeric sorting. Here it's trivial as we precomputed scores */
+ if (so1->u.score > so2->u.score) {
+ cmp = 1;
+ } else if (so1->u.score < so2->u.score) {
+ cmp = -1;
+ } else {
+ cmp = 0;
+ }
+ } else {
+ /* Alphanumeric sorting */
+ if (server.sort_bypattern) {
+ if (!so1->u.cmpobj || !so2->u.cmpobj) {
+ /* At least one compare object is NULL */
+ if (so1->u.cmpobj == so2->u.cmpobj)
+ cmp = 0;
+ else if (so1->u.cmpobj == NULL)
+ cmp = -1;
+ else
+ cmp = 1;
+ } else {
+ /* We have both the objects, use strcoll */
+ cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
+ }
+ } else {
+ /* Compare elements directly */
+ if (so1->obj->encoding == REDIS_ENCODING_RAW &&
+ so2->obj->encoding == REDIS_ENCODING_RAW) {
+ cmp = strcoll(so1->obj->ptr,so2->obj->ptr);
+ } else {
+ robj *dec1, *dec2;
+
+ dec1 = so1->obj->encoding == REDIS_ENCODING_RAW ?
+ so1->obj : getDecodedObject(so1->obj);
+ dec2 = so2->obj->encoding == REDIS_ENCODING_RAW ?
+ so2->obj : getDecodedObject(so2->obj);
+ cmp = strcoll(dec1->ptr,dec2->ptr);
+ if (dec1 != so1->obj) decrRefCount(dec1);
+ if (dec2 != so2->obj) decrRefCount(dec2);
+ }
+ }
+ }
+ return server.sort_desc ? -cmp : cmp;
+}
+
+/* The SORT command is the most complex command in Redis. Warning: this code
+ * is optimized for speed and a bit less for readability */
+static void sortCommand(redisClient *c) {
+ list *operations;
+ int outputlen = 0;
+ int desc = 0, alpha = 0;
+ int limit_start = 0, limit_count = -1, start, end;
+ int j, dontsort = 0, vectorlen;
+ int getop = 0; /* GET operation counter */
+ robj *sortval, *sortby = NULL, *storekey = NULL;