+/*-----------------------------------------------------------------------------
+ * 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");
+ }
+}