From a30047736806e54af0459a2c41767bf0ebc6a572 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 9 Dec 2010 10:37:35 +0100
Subject: [PATCH 01/41] Undo rename of function names where something went
 wrong

---
 src/t_zset.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 27522367..9539357c 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -263,7 +263,7 @@ zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
  * 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
  * first element. */
-unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
+unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
     zskiplistNode *x;
     unsigned long rank = 0;
     int i;
@@ -287,7 +287,7 @@ unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
 }
 
 /* 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;
     unsigned long traversed = 0;
     int i;
@@ -810,10 +810,10 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
     /* 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);
+        ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start);
     } else {
         ln = start == 0 ?
-            zsl->header->level[0].forward : zslistTypeGetElementByRank(zsl, start+1);
+            zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1);
     }
 
     /* Return the result in form of a multi-bulk reply */
@@ -1039,7 +1039,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
     }
 
     score = dictGetEntryVal(de);
-    rank = zslistTypeGetRank(zsl, *score, c->argv[2]);
+    rank = zslGetRank(zsl, *score, c->argv[2]);
     if (rank) {
         if (reverse) {
             addReplyLongLong(c, zsl->length - rank);

From 22b9bf15949933b351525ae658dc32e40e5784ab Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 7 Dec 2010 23:21:05 +0100
Subject: [PATCH 02/41] Move logic concerned with zset ranges

This also optimizes ZREVRANGEBYSCORE for pathological cases where a
sorted set contains many elements with the same score. Previously,
it would traverse the list from back to front in such a case.
---
 src/t_zset.c             | 163 ++++++++++++++++++++++-----------------
 tests/unit/type/zset.tcl |  16 ++++
 2 files changed, 110 insertions(+), 69 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 9539357c..01b1d035 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -180,6 +180,78 @@ typedef struct {
     int minex, maxex; /* are min or max exclusive? */
 } zrangespec;
 
+static inline int zslValueInMinRange(double value, zrangespec *spec) {
+    return spec->minex ? (value > spec->min) : (value >= spec->min);
+}
+
+static inline int zslValueInMaxRange(double value, zrangespec *spec) {
+    return spec->maxex ? (value < spec->max) : (value <= spec->max);
+}
+
+static inline int zslValueInRange(double value, zrangespec *spec) {
+    return zslValueInMinRange(value,spec) && zslValueInMaxRange(value,spec);
+}
+
+/* Returns if there is a part of the zset is in range. */
+int zslIsInRange(zskiplist *zsl, zrangespec *range) {
+    zskiplistNode *x;
+
+    x = zsl->tail;
+    if (x == NULL || !zslValueInMinRange(x->score,range))
+        return 0;
+    x = zsl->header->level[0].forward;
+    if (x == NULL || !zslValueInMaxRange(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 &&
+            !zslValueInMinRange(x->level[i].forward->score,&range))
+                x = x->level[i].forward;
+    }
+
+    /* The tail is in range, so the previous block should always return a
+     * node that is non-NULL and the last one to be out of range. */
+    x = x->level[0].forward;
+    redisAssert(x != NULL && zslValueInRange(x->score,&range));
+    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 &&
+            zslValueInMaxRange(x->level[i].forward->score,&range))
+                x = x->level[i].forward;
+    }
+
+    /* The header is in range, so the previous block should always return a
+     * node that is non-NULL and in range. */
+    redisAssert(x != NULL && zslValueInRange(x->score,&range));
+    return x;
+}
+
 /* 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.
  * Note that this function takes the reference to the hash table view of the
@@ -243,22 +315,6 @@ unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned
     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.
  * 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
@@ -847,9 +903,18 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
     int withscores = 0;
     unsigned long rangelen = 0;
     void *replylen = NULL;
+    int minidx, maxidx;
 
     /* 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");
         return;
     }
@@ -882,33 +947,11 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
     zsetobj = o->ptr;
     zsl = zsetobj->zsl;
 
-    /* If reversed, assume the elements are sorted from high to low score. */
-    ln = zslFirstWithScore(zsl,range.min);
+    /* If reversed, get the last node in range as starting point. */
     if (reverse) {
-        /* If range.min is out of range, ln will be NULL and we need to use
-         * 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;
-        }
+        ln = zslLastInRange(zsl,range);
     } else {
-        if (range.minex) {
-            /* Find first element with score > range.min */
-            while (ln && ln->score == range.min) ln = ln->level[0].forward;
-        }
+        ln = zslFirstInRange(zsl,range);
     }
 
     /* No "first" element in the specified interval. */
@@ -917,40 +960,24 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
         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 */
+    /* 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--) {
-        if (reverse)
-            ln = ln->backward;
-        else
-            ln = ln->level[0].forward;
+        ln = reverse ? ln->backward : ln->level[0].forward;
     }
 
     while (ln && limit--) {
-        /* Check if this this element is in range. */
+        /* Abort when the node is no longer in range. */
         if (reverse) {
-            if (range.maxex) {
-                /* Element should have score > range.max */
-                if (ln->score <= range.max) break;
-            } else {
-                /* Element should have score >= range.max */
-                if (ln->score < range.max) break;
-            }
+            if (!zslValueInMinRange(ln->score,&range)) break;
         } else {
-            if (range.maxex) {
-                /* Element should have score < range.max */
-                if (ln->score >= range.max) break;
-            } else {
-                /* Element should have score <= range.max */
-                if (ln->score > range.max) break;
-            }
+            if (!zslValueInMaxRange(ln->score,&range)) break;
         }
 
         /* Do our magic */
@@ -961,10 +988,8 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
                 addReplyDouble(c,ln->score);
         }
 
-        if (reverse)
-            ln = ln->backward;
-        else
-            ln = ln->level[0].forward;
+        /* Move to next node */
+        ln = reverse ? ln->backward : ln->level[0].forward;
     }
 
     if (justcount) {
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 6b8fc54a..a54ff37b 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -227,6 +227,22 @@ start_server {tags {"zset"}} {
         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 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 (6 (+inf]
+        assert_equal {} [r zrangebyscore zset (-inf (-6]
+        assert_equal {} [r zrevrangebyscore zset (+inf (6]
+        assert_equal {} [r zrevrangebyscore zset (-6 (-inf]
     }
 
     test "ZRANGEBYSCORE with WITHSCORES" {

From df278b8b0b4bd54b83840d4a151e538c60dc92e9 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 13 Jan 2011 16:06:03 +0100
Subject: [PATCH 03/41] Compiler should decide on inlining

---
 src/t_zset.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 01b1d035..0e7d726b 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -180,15 +180,15 @@ typedef struct {
     int minex, maxex; /* are min or max exclusive? */
 } zrangespec;
 
-static inline int zslValueInMinRange(double value, zrangespec *spec) {
+static int zslValueInMinRange(double value, zrangespec *spec) {
     return spec->minex ? (value > spec->min) : (value >= spec->min);
 }
 
-static inline int zslValueInMaxRange(double value, zrangespec *spec) {
+static int zslValueInMaxRange(double value, zrangespec *spec) {
     return spec->maxex ? (value < spec->max) : (value <= spec->max);
 }
 
-static inline int zslValueInRange(double value, zrangespec *spec) {
+static int zslValueInRange(double value, zrangespec *spec) {
     return zslValueInMinRange(value,spec) && zslValueInMaxRange(value,spec);
 }
 

From 8e1b327706ca39f9efb8967653b365ca81a52425 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Mon, 17 Jan 2011 11:10:30 +0100
Subject: [PATCH 04/41] Test for ranges where min > max

---
 src/t_zset.c             | 4 ++++
 tests/unit/type/zset.tcl | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/src/t_zset.c b/src/t_zset.c
index 0e7d726b..99fe6a8b 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -196,6 +196,10 @@ static int zslValueInRange(double value, zrangespec *spec) {
 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 || !zslValueInMinRange(x->score,range))
         return 0;
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index a54ff37b..fdeebd2f 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -233,12 +233,16 @@ start_server {tags {"zset"}} {
         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]

From 45290ad9bb9c535d6447852327ceb458573afff6 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Feb 2011 11:49:59 +0100
Subject: [PATCH 05/41] Rename zset range functions

---
 src/t_zset.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 99fe6a8b..563b0134 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -180,16 +180,16 @@ typedef struct {
     int minex, maxex; /* are min or max exclusive? */
 } zrangespec;
 
-static int zslValueInMinRange(double value, zrangespec *spec) {
+static int zslValueGteMin(double value, zrangespec *spec) {
     return spec->minex ? (value > spec->min) : (value >= spec->min);
 }
 
-static int zslValueInMaxRange(double value, zrangespec *spec) {
+static int zslValueLteMax(double value, zrangespec *spec) {
     return spec->maxex ? (value < spec->max) : (value <= spec->max);
 }
 
 static int zslValueInRange(double value, zrangespec *spec) {
-    return zslValueInMinRange(value,spec) && zslValueInMaxRange(value,spec);
+    return zslValueGteMin(value,spec) && zslValueLteMax(value,spec);
 }
 
 /* Returns if there is a part of the zset is in range. */
@@ -201,10 +201,10 @@ int zslIsInRange(zskiplist *zsl, zrangespec *range) {
             (range->min == range->max && (range->minex || range->maxex)))
         return 0;
     x = zsl->tail;
-    if (x == NULL || !zslValueInMinRange(x->score,range))
+    if (x == NULL || !zslValueGteMin(x->score,range))
         return 0;
     x = zsl->header->level[0].forward;
-    if (x == NULL || !zslValueInMaxRange(x->score,range))
+    if (x == NULL || !zslValueLteMax(x->score,range))
         return 0;
     return 1;
 }
@@ -222,7 +222,7 @@ zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range) {
     for (i = zsl->level-1; i >= 0; i--) {
         /* Go forward while *OUT* of range. */
         while (x->level[i].forward &&
-            !zslValueInMinRange(x->level[i].forward->score,&range))
+            !zslValueGteMin(x->level[i].forward->score,&range))
                 x = x->level[i].forward;
     }
 
@@ -246,7 +246,7 @@ zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec range) {
     for (i = zsl->level-1; i >= 0; i--) {
         /* Go forward while *IN* range. */
         while (x->level[i].forward &&
-            zslValueInMaxRange(x->level[i].forward->score,&range))
+            zslValueLteMax(x->level[i].forward->score,&range))
                 x = x->level[i].forward;
     }
 
@@ -979,9 +979,9 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
     while (ln && limit--) {
         /* Abort when the node is no longer in range. */
         if (reverse) {
-            if (!zslValueInMinRange(ln->score,&range)) break;
+            if (!zslValueGteMin(ln->score,&range)) break;
         } else {
-            if (!zslValueInMaxRange(ln->score,&range)) break;
+            if (!zslValueLteMax(ln->score,&range)) break;
         }
 
         /* Do our magic */

From 672b0a1b25023fd6df826af6c7570e23d3ac3443 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 12:30:01 +0100
Subject: [PATCH 06/41] Fast conversion of double when representable as long
 long

---
 src/redis.h |  1 +
 src/util.c  | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/src/redis.h b/src/redis.h
index cdddb601..19403218 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -887,6 +887,7 @@ int stringmatchlen(const char *pattern, int patternLen,
 int stringmatch(const char *pattern, const char *string, int nocase);
 long long memtoll(const char *p, int *err);
 int ll2string(char *s, size_t len, long long value);
+int d2string(char *s, size_t len, double value);
 int isStringRepresentableAsLong(sds s, long *longval);
 int isStringRepresentableAsLongLong(sds s, long long *longval);
 int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
diff --git a/src/util.c b/src/util.c
index e304ff83..5cffa072 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1,6 +1,7 @@
 #include "redis.h"
 #include <ctype.h>
 #include <limits.h>
+#include <math.h>
 
 /* Glob-style pattern matching. */
 int stringmatchlen(const char *pattern, int patternLen,
@@ -200,6 +201,45 @@ int ll2string(char *s, size_t len, long long value) {
     return l;
 }
 
+/* Convert a double to a string representation. Returns the number of bytes
+ * required. The representation should always be parsable by stdtod(3). */
+int d2string(char *buf, size_t len, double value) {
+    if (isnan(value)) {
+        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 {
+#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;
+}
+
 /* Check if the sds string 's' can be represented by a long long
  * (that is, is a number that fits into long without any other space or
  * character before or after the digits, so that converting this number

From 9e7cee0ed01246e898eac500330c6a16e9dbfddb Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 16:08:52 +0100
Subject: [PATCH 07/41] Add function to create ziplist-backed sorted set

---
 src/object.c | 8 +++++++-
 src/redis.h  | 1 +
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/object.c b/src/object.c
index e6b02da8..a6bf8a20 100644
--- a/src/object.c
+++ b/src/object.c
@@ -93,12 +93,18 @@ robj *createHashObject(void) {
 
 robj *createZsetObject(void) {
     zset *zs = zmalloc(sizeof(*zs));
-
     zs->dict = dictCreate(&zsetDictType,NULL);
     zs->zsl = zslCreate();
     return createObject(REDIS_ZSET,zs);
 }
 
+robj *createZsetZiplistObject(void) {
+    unsigned char *zl = ziplistNew();
+    robj *o = createObject(REDIS_ZSET,zl);
+    o->encoding = REDIS_ENCODING_ZIPLIST;
+    return o;
+}
+
 void freeStringObject(robj *o) {
     if (o->encoding == REDIS_ENCODING_RAW) {
         sdsfree(o->ptr);
diff --git a/src/redis.h b/src/redis.h
index 19403218..78294d0d 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -730,6 +730,7 @@ robj *createSetObject(void);
 robj *createIntsetObject(void);
 robj *createHashObject(void);
 robj *createZsetObject(void);
+robj *createZsetZiplistObject(void);
 int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg);
 int checkType(redisClient *c, robj *o, int type);
 int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg);

From 21c5b508a48270257567ea223fb0766553501303 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 16:44:22 +0100
Subject: [PATCH 08/41] Initial work for ziplist backed sorted sets

---
 src/t_zset.c | 314 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 244 insertions(+), 70 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 563b0134..e1c61772 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -403,6 +403,157 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
     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 char *zzlFind(robj *zobj, robj *ele, double *score) {
+    unsigned char *zl = zobj->ptr;
+    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. */
+            *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. */
+int zzlDelete(robj *zobj, unsigned char *eptr) {
+    unsigned char *zl = zobj->ptr;
+    unsigned char *p = eptr;
+
+    /* TODO: add function to ziplist API to delete N elements from offset. */
+    zl = ziplistDelete(zl,&p);
+    zl = ziplistDelete(zl,&p);
+    zobj->ptr = zl;
+    return REDIS_OK;
+}
+
+int zzlInsertAt(robj *zobj, robj *ele, double score, unsigned char *eptr) {
+    unsigned char *zl = zobj->ptr;
+    unsigned char *sptr;
+    char scorebuf[128];
+    int scorelen;
+    int 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);
+    }
+
+    zobj->ptr = zl;
+    return REDIS_OK;
+}
+
+/* Insert (element,score) pair in ziplist. This function assumes the element is
+ * not yet present in the list. */
+int zzlInsert(robj *zobj, robj *ele, double score) {
+    unsigned char *zl = zobj->ptr;
+    unsigned char *eptr = ziplistIndex(zl,0), *sptr;
+    double s;
+    int insert = 0;
+
+    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. */
+            insert = 1;
+        } else if (s == score) {
+            /* Ensure lexicographical ordering for elements. */
+            if (zzlCompareElements(eptr,ele->ptr,sdslen(ele->ptr)) < 0)
+                insert = 1;
+        }
+
+        if (insert) {
+            zzlInsertAt(zobj,ele,score,eptr);
+            break;
+        }
+
+        /* Move to next element. */
+        eptr = ziplistNext(zl,sptr);
+    }
+
+    /* Push on tail of list when it was not yet inserted. */
+    if (!insert)
+        zzlInsertAt(zobj,ele,score,eptr);
+
+    decrRefCount(ele);
+    return REDIS_OK;
+}
 
 /*-----------------------------------------------------------------------------
  * Sorted set commands 
@@ -410,90 +561,113 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
 
 /* This generic command implements both ZADD and ZINCRBY. */
 void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int incr) {
-    robj *zsetobj;
-    zset *zs;
-    zskiplistNode *znode;
+    static char *nanerr = "resulting score is not a number (NaN)";
+    robj *zobj;
+    robj *curobj;
+    double curscore = 0.0;
 
-    zsetobj = lookupKeyWrite(c->db,key);
-    if (zsetobj == NULL) {
-        zsetobj = createZsetObject();
-        dbAdd(c->db,key,zsetobj);
+    zobj = lookupKeyWrite(c->db,key);
+    if (zobj == NULL) {
+        zobj = createZsetZiplistObject();
+        dbAdd(c->db,key,zobj);
     } else {
-        if (zsetobj->type != REDIS_ZSET) {
+        if (zobj->type != REDIS_ZSET) {
             addReply(c,shared.wrongtypeerr);
             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 (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        unsigned char *eptr;
 
-        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;
-        }
-    }
+        if ((eptr = zzlFind(zobj,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;
+                }
+            }
 
-    /* We need to remove and re-insert the element when it was already present
-     * in the dictionary, to update the skiplist. Note that we delay adding a
-     * pointer to the score because we want to reference the score in the
-     * skiplist node. */
-    if (dictAdd(zs->dict,ele,NULL) == DICT_OK) {
-        dictEntry *de;
+            /* Remove and re-insert when score changed. */
+            if (score != curscore) {
+                redisAssert(zzlDelete(zobj,eptr) == REDIS_OK);
+                redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
 
-        /* New element */
-        incrRefCount(ele); /* added to hash */
-        znode = zslInsert(zs->zsl,score,ele);
-        incrRefCount(ele); /* added to skiplist */
+                signalModifiedKey(c->db,key);
+                server.dirty++;
+            }
 
-        /* Update the score in the dict entry */
-        de = dictFind(zs->dict,ele);
-        redisAssert(de != NULL);
-        dictGetEntryVal(de) = &znode->score;
-        signalModifiedKey(c->db,c->argv[1]);
-        server.dirty++;
-        if (incr)
-            addReplyDouble(c,score);
-        else
-            addReply(c,shared.cone);
-    } else {
-        dictEntry *de;
-        robj *curobj;
-        double *curscore;
-        int deleted;
+            if (incr) /* ZINCRBY */
+                addReplyDouble(c,score);
+            else /* ZADD */
+                addReply(c,shared.czero);
+        } else {
+            redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
 
-        /* Update score */
-        de = dictFind(zs->dict,ele);
-        redisAssert(de != NULL);
-        curobj = dictGetEntryKey(de);
-        curscore = dictGetEntryVal(de);
-
-        /* When the score is updated, reuse the existing string object to
-         * prevent extra alloc/dealloc of strings on ZINCRBY. */
-        if (score != *curscore) {
-            deleted = zslDelete(zs->zsl,*curscore,curobj);
-            redisAssert(deleted != 0);
-            znode = zslInsert(zs->zsl,score,curobj);
-            incrRefCount(curobj);
-
-            /* Update the score in the current dict entry */
-            dictGetEntryVal(de) = &znode->score;
-            signalModifiedKey(c->db,c->argv[1]);
+            signalModifiedKey(c->db,key);
             server.dirty++;
+
+            if (incr) /* ZINCRBY */
+                addReplyDouble(c,score);
+            else /* ZADD */
+                addReply(c,shared.cone);
         }
-        if (incr)
-            addReplyDouble(c,score);
-        else
-            addReply(c,shared.czero);
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        zset *zs = zobj->ptr;
+        zskiplistNode *znode;
+        dictEntry *de;
+
+        de = dictFind(zs->dict,ele);
+        if (de != NULL) {
+            curobj = dictGetEntryKey(de);
+            curscore = *(double*)dictGetEntryVal(de);
+
+            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;
+                }
+            }
+
+            /* 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++;
+
+            if (incr) /* ZINCRBY */
+                addReplyDouble(c,score);
+            else /* ZADD */
+                addReply(c,shared.cone);
+        }
+    } else {
+        redisPanic("Unknown sorted set encoding");
     }
 }
 

From 3ca7532a2d20ea88109cd4f0c3c527c37e3fe52f Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 16:51:41 +0100
Subject: [PATCH 09/41] Don't encode element argument when dealing with ziplist

---
 src/t_zset.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index e1c61772..9fd524c3 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -560,11 +560,16 @@ int zzlInsert(robj *zobj, robj *ele, double score) {
  *----------------------------------------------------------------------------*/
 
 /* 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) {
     static char *nanerr = "resulting score is not a number (NaN)";
+    robj *key = c->argv[1];
+    robj *ele;
     robj *zobj;
     robj *curobj;
-    double curscore = 0.0;
+    double score, curscore = 0.0;
+
+    if (getDoubleFromObjectOrReply(c,c->argv[2],&score,NULL) != REDIS_OK)
+        return;
 
     zobj = lookupKeyWrite(c->db,key);
     if (zobj == NULL) {
@@ -580,6 +585,8 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
         unsigned char *eptr;
 
+        /* Prefer non-encoded element when dealing with ziplists. */
+        ele = c->argv[3];
         if ((eptr = zzlFind(zobj,ele,&curscore)) != NULL) {
             if (incr) {
                 score += curscore;
@@ -620,6 +627,7 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int
         zskiplistNode *znode;
         dictEntry *de;
 
+        ele = c->argv[3] = tryObjectEncoding(c->argv[3]);
         de = dictFind(zs->dict,ele);
         if (de != NULL) {
             curobj = dictGetEntryKey(de);
@@ -672,17 +680,11 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int
 }
 
 void zaddCommand(redisClient *c) {
-    double scoreval;
-    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);
+    zaddGenericCommand(c,0);
 }
 
 void zincrbyCommand(redisClient *c) {
-    double scoreval;
-    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);
+    zaddGenericCommand(c,1);
 }
 
 void zremCommand(redisClient *c) {

From 0b10e1044496699585b401196dd86e19a9f87cc4 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 17:11:15 +0100
Subject: [PATCH 10/41] Support dual encoding in ZREM

---
 src/t_zset.c | 66 ++++++++++++++++++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 23 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 9fd524c3..790fb573 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -449,6 +449,11 @@ int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int cl
     return cmp;
 }
 
+unsigned int *zzlLength(robj *zobj) {
+    unsigned char *zl = zobj->ptr;
+    return ziplistLen(zl)/2;
+}
+
 unsigned char *zzlFind(robj *zobj, robj *ele, double *score) {
     unsigned char *zl = zobj->ptr;
     unsigned char *eptr = ziplistIndex(zl,0), *sptr;
@@ -460,7 +465,7 @@ unsigned char *zzlFind(robj *zobj, robj *ele, double *score) {
 
         if (ziplistCompare(eptr,ele->ptr,sdslen(ele->ptr))) {
             /* Matching element, pull out score. */
-            *score = zzlGetScore(sptr);
+            if (score != NULL) *score = zzlGetScore(sptr);
             decrRefCount(ele);
             return eptr;
         }
@@ -688,32 +693,47 @@ void zincrbyCommand(redisClient *c) {
 }
 
 void zremCommand(redisClient *c) {
-    robj *zsetobj;
-    zset *zs;
-    dictEntry *de;
-    double curscore;
-    int deleted;
+    robj *key = c->argv[1];
+    robj *ele = c->argv[2];
+    robj *zobj;
 
-    if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
-        checkType(c,zsetobj,REDIS_ZSET)) return;
+    if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
 
-    zs = zsetobj->ptr;
-    c->argv[2] = tryObjectEncoding(c->argv[2]);
-    de = dictFind(zs->dict,c->argv[2]);
-    if (de == NULL) {
-        addReply(c,shared.czero);
-        return;
+    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        unsigned char *eptr;
+
+        if ((eptr = zzlFind(zobj,ele,NULL)) != NULL) {
+            redisAssert(zzlDelete(zobj,eptr) == REDIS_OK);
+            if (zzlLength(zobj) == 0) dbDelete(c->db,key);
+        } else {
+            addReply(c,shared.czero);
+            return;
+        }
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        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 */
-    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]);
+    signalModifiedKey(c->db,key);
     server.dirty++;
     addReply(c,shared.cone);
 }

From 8218db3dae220409f239a5a0adb47d106b178bf9 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 21:36:43 +0100
Subject: [PATCH 11/41] Little less obfuscation

---
 src/t_zset.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 790fb573..1aab4b12 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -524,7 +524,6 @@ int zzlInsert(robj *zobj, robj *ele, double score) {
     unsigned char *zl = zobj->ptr;
     unsigned char *eptr = ziplistIndex(zl,0), *sptr;
     double s;
-    int insert = 0;
 
     ele = getDecodedObject(ele);
     while (eptr != NULL) {
@@ -536,16 +535,14 @@ int zzlInsert(robj *zobj, robj *ele, double 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. */
-            insert = 1;
-        } else if (s == score) {
-            /* Ensure lexicographical ordering for elements. */
-            if (zzlCompareElements(eptr,ele->ptr,sdslen(ele->ptr)) < 0)
-                insert = 1;
-        }
-
-        if (insert) {
             zzlInsertAt(zobj,ele,score,eptr);
             break;
+        } else if (s == score) {
+            /* Ensure lexicographical ordering for elements. */
+            if (zzlCompareElements(eptr,ele->ptr,sdslen(ele->ptr)) < 0) {
+                zzlInsertAt(zobj,ele,score,eptr);
+                break;
+            }
         }
 
         /* Move to next element. */
@@ -553,8 +550,8 @@ int zzlInsert(robj *zobj, robj *ele, double score) {
     }
 
     /* Push on tail of list when it was not yet inserted. */
-    if (!insert)
-        zzlInsertAt(zobj,ele,score,eptr);
+    if (eptr == NULL)
+        zzlInsertAt(zobj,ele,score,NULL);
 
     decrRefCount(ele);
     return REDIS_OK;

From 9f9b60f974cb57923c88a585a75db4b82711288b Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 22:14:46 +0100
Subject: [PATCH 12/41] Typo

---
 src/t_zset.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 1aab4b12..9119e592 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -449,7 +449,7 @@ int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int cl
     return cmp;
 }
 
-unsigned int *zzlLength(robj *zobj) {
+unsigned int zzlLength(robj *zobj) {
     unsigned char *zl = zobj->ptr;
     return ziplistLen(zl)/2;
 }

From 4a14dbbac2eb6148c50212222beb2639ecbc8760 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 22:23:56 +0100
Subject: [PATCH 13/41] Look up and remove elements by range

---
 src/t_zset.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 126 insertions(+), 10 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 9119e592..8c417ac7 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -454,6 +454,87 @@ unsigned int zzlLength(robj *zobj) {
     return ziplistLen(zl)/2;
 }
 
+/* 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(robj *zobj, zrangespec range) {
+    unsigned char *zl = zobj->ptr;
+    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))
+            return eptr;
+
+        /* 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(robj *zobj, zrangespec range) {
+    unsigned char *zl = zobj->ptr;
+    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))
+            return eptr;
+
+        /* 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(robj *zobj, robj *ele, double *score) {
     unsigned char *zl = zobj->ptr;
     unsigned char *eptr = ziplistIndex(zl,0), *sptr;
@@ -557,6 +638,34 @@ int zzlInsert(robj *zobj, robj *ele, double score) {
     return REDIS_OK;
 }
 
+unsigned long zzlDeleteRangeByScore(robj *zobj, zrangespec range) {
+    unsigned char *zl = zobj->ptr;
+    unsigned char *eptr, *sptr;
+    double score;
+    unsigned long deleted = 0;
+
+    eptr = zzlFirstInRange(zobj,range);
+    if (eptr == NULL) return deleted;
+
+
+    /* 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);
+            deleted++;
+        } else {
+            /* No longer in range. */
+            break;
+        }
+    }
+
+    return deleted;
+}
+
 /*-----------------------------------------------------------------------------
  * Sorted set commands 
  *----------------------------------------------------------------------------*/
@@ -736,10 +845,10 @@ void zremCommand(redisClient *c) {
 }
 
 void zremrangebyscoreCommand(redisClient *c) {
+    robj *key = c->argv[1];
+    robj *zobj;
     zrangespec range;
-    long deleted;
-    robj *o;
-    zset *zs;
+    unsigned long deleted;
 
     /* Parse the range arguments. */
     if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
@@ -747,14 +856,21 @@ void zremrangebyscoreCommand(redisClient *c) {
         return;
     }
 
-    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
-        checkType(c,o,REDIS_ZSET)) return;
+    if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
 
-    zs = o->ptr;
-    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]);
+    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        deleted = zzlDeleteRangeByScore(zobj,range);
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        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");
+    }
+
+    if (deleted) signalModifiedKey(c->db,key);
     server.dirty += deleted;
     addReplyLongLong(c,deleted);
 }

From 0f23eb3b10402bd7166c09583333c29aa3c3f55f Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 8 Mar 2011 23:56:59 +0100
Subject: [PATCH 14/41] Properly free encoded sorted set

---
 src/object.c | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/object.c b/src/object.c
index a6bf8a20..c384d600 100644
--- a/src/object.c
+++ b/src/object.c
@@ -138,11 +138,20 @@ void freeSetObject(robj *o) {
 }
 
 void freeZsetObject(robj *o) {
-    zset *zs = o->ptr;
-
-    dictRelease(zs->dict);
-    zslFree(zs->zsl);
-    zfree(zs);
+    zset *zs;
+    switch (o->encoding) {
+    case REDIS_ENCODING_RAW:
+        zs = o->ptr;
+        dictRelease(zs->dict);
+        zslFree(zs->zsl);
+        zfree(zs);
+        break;
+    case REDIS_ENCODING_ZIPLIST:
+        zfree(o->ptr);
+        break;
+    default:
+        redisPanic("Unknown sorted set encoding");
+    }
 }
 
 void freeHashObject(robj *o) {

From 5d1b4fb6983f5acd9cfb6ee5f5715547688448d2 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 00:00:19 +0100
Subject: [PATCH 15/41] Support dual encoding for ZRANGE

---
 src/t_zset.c | 125 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 96 insertions(+), 29 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 8c417ac7..6b2b6d9f 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -666,6 +666,22 @@ unsigned long zzlDeleteRangeByScore(robj *zobj, zrangespec range) {
     return deleted;
 }
 
+/*-----------------------------------------------------------------------------
+ * Common sorted set API
+ *----------------------------------------------------------------------------*/
+
+int zsLength(robj *zobj) {
+    int length = -1;
+    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        length = zzlLength(zobj);
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        length = ((zset*)zobj->ptr)->zsl->length;
+    } else {
+        redisPanic("Unknown sorted set encoding");
+    }
+    return length;
+}
+
 /*-----------------------------------------------------------------------------
  * Sorted set commands 
  *----------------------------------------------------------------------------*/
@@ -1135,16 +1151,13 @@ void zinterstoreCommand(redisClient *c) {
 }
 
 void zrangeGenericCommand(redisClient *c, int reverse) {
-    robj *o;
+    robj *key = c->argv[1];
+    robj *zobj;
+    int withscores = 0;
     long start;
     long end;
-    int withscores = 0;
     int llen;
-    int rangelen, j;
-    zset *zsetobj;
-    zskiplist *zsl;
-    zskiplistNode *ln;
-    robj *ele;
+    int rangelen;
 
     if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
         (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
@@ -1156,13 +1169,11 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
         return;
     }
 
-    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
-         || checkType(c,o,REDIS_ZSET)) return;
-    zsetobj = o->ptr;
-    zsl = zsetobj->zsl;
-    llen = zsl->length;
+    if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL
+         || checkType(c,zobj,REDIS_ZSET)) return;
 
-    /* convert negative indexes */
+    /* Sanitize indexes. */
+    llen = zsLength(zobj);
     if (start < 0) start = llen+start;
     if (end < 0) end = llen+end;
     if (start < 0) start = 0;
@@ -1176,23 +1187,79 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
     if (end >= llen) end = llen-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 : zslGetElementByRank(zsl, llen-start);
-    } else {
-        ln = start == 0 ?
-            zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1);
-    }
-
     /* Return the result in form of a multi-bulk reply */
-    addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen);
-    for (j = 0; j < rangelen; j++) {
-        ele = ln->obj;
-        addReplyBulk(c,ele);
-        if (withscores)
-            addReplyDouble(c,ln->score);
-        ln = reverse ? ln->backward : ln->level[0].forward;
+    addReplyMultiBulkLen(c, withscores ? (rangelen*2) : rangelen);
+
+    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 (reverse)
+            eptr = ziplistIndex(zl,-2-(2*start));
+        else
+            eptr = ziplistIndex(zl,2*start);
+
+        while (rangelen--) {
+            redisAssert(eptr != NULL);
+            redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
+            if (vstr == NULL)
+                addReplyBulkLongLong(c,vlong);
+            else
+                addReplyBulkCBuffer(c,vstr,vlen);
+
+            if (withscores) {
+                sptr = ziplistNext(zl,eptr);
+                redisAssert(sptr != NULL);
+                addReplyDouble(c,zzlGetScore(sptr));
+            }
+
+            if (reverse) {
+                /* Move to previous element by moving to the score of previous
+                 * element. When NULL, we know there also is no element. */
+                sptr = ziplistPrev(zl,eptr);
+                if (sptr != NULL) {
+                    eptr = ziplistPrev(zl,sptr);
+                    redisAssert(eptr != NULL);
+                } else {
+                    eptr = NULL;
+                }
+            } else {
+                sptr = ziplistNext(zl,eptr);
+                redisAssert(sptr != NULL);
+                eptr = ziplistNext(zl,sptr);
+            }
+        }
+
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        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");
     }
 }
 

From 63b7b7fb34984b47971a4ced65ae49b6c7a350fa Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 10:30:55 +0100
Subject: [PATCH 16/41] Support dual encoding for ZREMRANGEBYRANK

---
 src/t_zset.c | 46 +++++++++++++++++++++++++++++++---------------
 1 file changed, 31 insertions(+), 15 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 6b2b6d9f..c71be90a 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -666,6 +666,14 @@ unsigned long zzlDeleteRangeByScore(robj *zobj, zrangespec range) {
     return deleted;
 }
 
+/* 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 long zzlDeleteRangeByRank(robj *zobj, unsigned int start, unsigned int end) {
+    unsigned int num = (end-start)+1;
+    zobj->ptr = ziplistDeleteRange(zobj->ptr,2*(start-1),2*num);
+    return num;
+}
+
 /*-----------------------------------------------------------------------------
  * Common sorted set API
  *----------------------------------------------------------------------------*/
@@ -892,22 +900,21 @@ void zremrangebyscoreCommand(redisClient *c) {
 }
 
 void zremrangebyrankCommand(redisClient *c) {
+    robj *key = c->argv[1];
+    robj *zobj;
     long start;
     long end;
     int llen;
-    long deleted;
-    robj *zsetobj;
-    zset *zs;
+    unsigned long deleted;
 
     if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
         (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
 
-    if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
-        checkType(c,zsetobj,REDIS_ZSET)) return;
-    zs = zsetobj->ptr;
-    llen = zs->zsl->length;
+    if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
 
-    /* convert negative indexes */
+    /* Sanitize indexes. */
+    llen = zsLength(zobj);
     if (start < 0) start = llen+start;
     if (end < 0) end = llen+end;
     if (start < 0) start = 0;
@@ -920,14 +927,23 @@ void zremrangebyrankCommand(redisClient *c) {
     }
     if (end >= llen) end = llen-1;
 
-    /* increment start and end because zsl*Rank functions
-     * use 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,c->argv[1]);
-    if (deleted) signalModifiedKey(c->db,c->argv[1]);
+    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        /* Correct for 1-based rank. */
+        deleted = zzlDeleteRangeByRank(zobj,start+1,end+1);
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        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;
-    addReplyLongLong(c, deleted);
+    addReplyLongLong(c,deleted);
 }
 
 typedef struct {

From 4c5f0966b2e582981d9fdaf3b511c6cf4ac4d4d5 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 11:06:25 +0100
Subject: [PATCH 17/41] Helpers to move around in encoded sorted set

---
 src/t_zset.c | 67 ++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 47 insertions(+), 20 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index c71be90a..8665aa14 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -454,6 +454,44 @@ unsigned int zzlLength(robj *zobj) {
     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) {
@@ -1218,35 +1256,24 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
         else
             eptr = ziplistIndex(zl,2*start);
 
+        redisAssert(eptr != NULL);
+        sptr = ziplistNext(zl,eptr);
+
         while (rangelen--) {
-            redisAssert(eptr != NULL);
+            redisAssert(eptr != NULL && sptr != NULL);
             redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
             if (vstr == NULL)
                 addReplyBulkLongLong(c,vlong);
             else
                 addReplyBulkCBuffer(c,vstr,vlen);
 
-            if (withscores) {
-                sptr = ziplistNext(zl,eptr);
-                redisAssert(sptr != NULL);
+            if (withscores)
                 addReplyDouble(c,zzlGetScore(sptr));
-            }
 
-            if (reverse) {
-                /* Move to previous element by moving to the score of previous
-                 * element. When NULL, we know there also is no element. */
-                sptr = ziplistPrev(zl,eptr);
-                if (sptr != NULL) {
-                    eptr = ziplistPrev(zl,sptr);
-                    redisAssert(eptr != NULL);
-                } else {
-                    eptr = NULL;
-                }
-            } else {
-                sptr = ziplistNext(zl,eptr);
-                redisAssert(sptr != NULL);
-                eptr = ziplistNext(zl,sptr);
-            }
+            if (reverse)
+                zzlPrev(zl,&eptr,&sptr);
+            else
+                zzlNext(zl,&eptr,&sptr);
         }
 
     } else if (zobj->encoding == REDIS_ENCODING_RAW) {

From aff255c81df8bd27ff53a21dd7ece5595f2ed8a9 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 11:29:21 +0100
Subject: [PATCH 18/41] Support dual encoding for ZRANGEBYSCORE et al

---
 src/t_zset.c | 173 +++++++++++++++++++++++++++++++++++----------------
 1 file changed, 121 insertions(+), 52 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 8665aa14..5fe10883 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -1318,10 +1318,8 @@ void zrevrangeCommand(redisClient *c) {
  * If "justcount", only the number of elements in the range is returned. */
 void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
     zrangespec range;
-    robj *o, *emptyreply;
-    zset *zsetobj;
-    zskiplist *zsl;
-    zskiplistNode *ln;
+    robj *key = c->argv[1];
+    robj *emptyreply, *zobj;
     int offset = 0, limit = -1;
     int withscores = 0;
     unsigned long rangelen = 0;
@@ -1365,61 +1363,132 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
 
     /* Ok, lookup the key and get the range */
     emptyreply = justcount ? shared.czero : shared.emptymultibulk;
-    if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL ||
-        checkType(c,o,REDIS_ZSET)) return;
-    zsetobj = o->ptr;
-    zsl = zsetobj->zsl;
+    if ((zobj = lookupKeyReadOrReply(c,key,emptyreply)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
 
-    /* If reversed, get the last node in range as starting point. */
-    if (reverse) {
-        ln = zslLastInRange(zsl,range);
+    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(zobj,range);
+        else
+            eptr = zzlFirstInRange(zobj,range);
+
+        /* No "first" element in the specified interval. */
+        if (eptr == NULL) {
+            addReply(c,emptyreply);
+            return;
+        }
+
+        /* Get score pointer for the first element. */
+        redisAssert(eptr != NULL);
+        sptr = ziplistNext(zl,eptr);
+
+        /* 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;
+            }
+
+            /* Do our magic */
+            rangelen++;
+            if (!justcount) {
+                redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
+                if (vstr == NULL)
+                    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_RAW) {
+        zset *zs = zobj->ptr;
+        zskiplist *zsl = zs->zsl;
+        zskiplistNode *ln;
+
+        /* If reversed, get the last node in range as starting point. */
+        if (reverse)
+            ln = zslLastInRange(zsl,range);
+        else
+            ln = zslFirstInRange(zsl,range);
+
+        /* No "first" element in the specified interval. */
+        if (ln == NULL) {
+            addReply(c,emptyreply);
+            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 (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 {
+                if (!zslValueLteMax(ln->score,&range)) break;
+            }
+
+            /* Do our magic */
+            rangelen++;
+            if (!justcount) {
+                addReplyBulk(c,ln->obj);
+                if (withscores)
+                    addReplyDouble(c,ln->score);
+            }
+
+            /* Move to next node */
+            ln = reverse ? ln->backward : ln->level[0].forward;
+        }
     } else {
-        ln = zslFirstInRange(zsl,range);
-    }
-
-    /* No "first" element in the specified interval. */
-    if (ln == NULL) {
-        addReply(c,emptyreply);
-        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(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 {
-            if (!zslValueLteMax(ln->score,&range)) break;
-        }
-
-        /* Do our magic */
-        rangelen++;
-        if (!justcount) {
-            addReplyBulk(c,ln->obj);
-            if (withscores)
-                addReplyDouble(c,ln->score);
-        }
-
-        /* Move to next node */
-        ln = reverse ? ln->backward : ln->level[0].forward;
+        redisPanic("Unknown sorted set encoding");
     }
 
     if (justcount) {
         addReplyLongLong(c,(long)rangelen);
     } else {
-        setDeferredMultiBulkLength(c,replylen,
-             withscores ? (rangelen*2) : rangelen);
+        if (withscores) rangelen *= 2;
+        setDeferredMultiBulkLength(c,replylen,rangelen);
     }
 }
 

From d1c920c53869907debf8a0f81ea320218fe95214 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 12:37:59 +0100
Subject: [PATCH 19/41] Support dual encoding for more commands

---
 src/t_zset.c | 121 +++++++++++++++++++++++++++++++++------------------
 1 file changed, 79 insertions(+), 42 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 5fe10883..25da7871 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -658,7 +658,7 @@ int zzlInsert(robj *zobj, robj *ele, double score) {
             break;
         } else if (s == score) {
             /* Ensure lexicographical ordering for elements. */
-            if (zzlCompareElements(eptr,ele->ptr,sdslen(ele->ptr)) < 0) {
+            if (zzlCompareElements(eptr,ele->ptr,sdslen(ele->ptr)) > 0) {
                 zzlInsertAt(zobj,ele,score,eptr);
                 break;
             }
@@ -1505,66 +1505,103 @@ void zcountCommand(redisClient *c) {
 }
 
 void zcardCommand(redisClient *c) {
-    robj *o;
-    zset *zs;
+    robj *key = c->argv[1];
+    robj *zobj;
 
-    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
-        checkType(c,o,REDIS_ZSET)) return;
+    if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
 
-    zs = o->ptr;
-    addReplyLongLong(c,zs->zsl->length);
+    addReplyLongLong(c,zzlLength(zobj));
 }
 
 void zscoreCommand(redisClient *c) {
-    robj *o;
-    zset *zs;
-    dictEntry *de;
+    robj *key = c->argv[1];
+    robj *zobj;
+    double score;
 
-    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
-        checkType(c,o,REDIS_ZSET)) return;
+    if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
 
-    zs = o->ptr;
-    c->argv[2] = tryObjectEncoding(c->argv[2]);
-    de = dictFind(zs->dict,c->argv[2]);
-    if (!de) {
-        addReply(c,shared.nullbulk);
+    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        if (zzlFind(zobj,c->argv[2],&score) != NULL)
+            addReplyDouble(c,score);
+        else
+            addReply(c,shared.nullbulk);
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        zset *zs = zobj->ptr;
+        dictEntry *de;
+
+        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 {
-        double *score = dictGetEntryVal(de);
-
-        addReplyDouble(c,*score);
+        redisPanic("Unknown sorted set encoding");
     }
 }
 
 void zrankGenericCommand(redisClient *c, int reverse) {
-    robj *o;
-    zset *zs;
-    zskiplist *zsl;
-    dictEntry *de;
+    robj *key = c->argv[1];
+    robj *ele = c->argv[2];
+    robj *zobj;
+    unsigned long llen;
     unsigned long rank;
-    double *score;
 
-    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
-        checkType(c,o,REDIS_ZSET)) return;
+    if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
+        checkType(c,zobj,REDIS_ZSET)) return;
+    llen = zsLength(zobj);
 
-    zs = o->ptr;
-    zsl = zs->zsl;
-    c->argv[2] = tryObjectEncoding(c->argv[2]);
-    de = dictFind(zs->dict,c->argv[2]);
-    if (!de) {
-        addReply(c,shared.nullbulk);
-        return;
-    }
+    redisAssert(ele->encoding == REDIS_ENCODING_RAW);
+    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+        unsigned char *zl = zobj->ptr;
+        unsigned char *eptr, *sptr;
 
-    score = dictGetEntryVal(de);
-    rank = zslGetRank(zsl, *score, c->argv[2]);
-    if (rank) {
-        if (reverse) {
-            addReplyLongLong(c, zsl->length - rank);
+        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);
+        }
+
+        if (eptr != NULL) {
+            if (reverse)
+                addReplyLongLong(c,llen-rank);
+            else
+                addReplyLongLong(c,rank-1);
         } else {
-            addReplyLongLong(c, rank-1);
+            addReply(c,shared.nullbulk);
+        }
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        zset *zs = zobj->ptr;
+        zskiplist *zsl = zs->zsl;
+        dictEntry *de;
+        double score;
+
+        ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
+        de = dictFind(zs->dict,ele);
+        if (de != NULL) {
+            score = *(double*)dictGetEntryVal(de);
+            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 {
+            addReply(c,shared.nullbulk);
         }
     } else {
-        addReply(c,shared.nullbulk);
+        redisPanic("Unknown sorted set encoding");
     }
 }
 

From e12b27acf72ee7e40a6a39ffc7c109914c584cd9 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 13:16:38 +0100
Subject: [PATCH 20/41] Persistence code for encoded sorted sets

---
 src/rdb.c   | 46 +++++++++++++++++++++++++++++++---------------
 src/redis.h |  2 ++
 2 files changed, 33 insertions(+), 15 deletions(-)

diff --git a/src/rdb.c b/src/rdb.c
index c9fb3e83..62762bee 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -302,24 +302,33 @@ int rdbSaveObject(FILE *fp, robj *o) {
             redisPanic("Unknown set encoding");
         }
     } else if (o->type == REDIS_ZSET) {
-        /* Save a set value */
-        zset *zs = o->ptr;
-        dictIterator *di = dictGetIterator(zs->dict);
-        dictEntry *de;
+        /* Save a sorted set value */
+        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
+            size_t l = ziplistBlobLen((unsigned char*)o->ptr);
 
-        if ((n = rdbSaveLen(fp,dictSize(zs->dict))) == -1) return -1;
-        nwritten += n;
-
-        while((de = dictNext(di)) != NULL) {
-            robj *eleobj = dictGetEntryKey(de);
-            double *score = dictGetEntryVal(de);
-
-            if ((n = rdbSaveStringObject(fp,eleobj)) == -1) return -1;
+            if ((n = rdbSaveRawString(fp,o->ptr,l)) == -1) return -1;
             nwritten += n;
-            if ((n = rdbSaveDoubleValue(fp,*score)) == -1) return -1;
+        } else if (o->encoding == REDIS_ENCODING_RAW) {
+            zset *zs = o->ptr;
+            dictIterator *di = dictGetIterator(zs->dict);
+            dictEntry *de;
+
+            if ((n = rdbSaveLen(fp,dictSize(zs->dict))) == -1) return -1;
             nwritten += n;
+
+            while((de = dictNext(di)) != NULL) {
+                robj *eleobj = dictGetEntryKey(de);
+                double *score = dictGetEntryVal(de);
+
+                if ((n = rdbSaveStringObject(fp,eleobj)) == -1) return -1;
+                nwritten += n;
+                if ((n = rdbSaveDoubleValue(fp,*score)) == -1) return -1;
+                nwritten += n;
+            }
+            dictReleaseIterator(di);
+        } else {
+            redisPanic("Unknown sorted set enoding");
         }
-        dictReleaseIterator(di);
     } else if (o->type == REDIS_HASH) {
         /* Save a hash value */
         if (o->encoding == REDIS_ENCODING_ZIPMAP) {
@@ -386,6 +395,8 @@ int rdbSaveKeyValuePair(FILE *fp, robj *key, robj *val,
         vtype = REDIS_LIST_ZIPLIST;
     else if (vtype == REDIS_SET && val->encoding == REDIS_ENCODING_INTSET)
         vtype = REDIS_SET_INTSET;
+    else if (vtype == REDIS_ZSET && val->encoding == REDIS_ENCODING_ZIPLIST)
+        vtype = REDIS_ZSET_ZIPLIST;
     /* Save type, key, value */
     if (rdbSaveType(fp,vtype) == -1) return -1;
     if (rdbSaveStringObject(fp,key) == -1) return -1;
@@ -811,7 +822,8 @@ robj *rdbLoadObject(int type, FILE *fp) {
         }
     } else if (type == REDIS_HASH_ZIPMAP ||
                type == REDIS_LIST_ZIPLIST ||
-               type == REDIS_SET_INTSET)
+               type == REDIS_SET_INTSET ||
+               type == REDIS_ZSET_ZIPLIST)
     {
         robj *aux = rdbLoadStringObject(fp);
 
@@ -846,6 +858,10 @@ robj *rdbLoadObject(int type, FILE *fp) {
                 if (intsetLen(o->ptr) > server.set_max_intset_entries)
                     setTypeConvert(o,REDIS_ENCODING_HT);
                 break;
+            case REDIS_ZSET_ZIPLIST:
+                o->type = REDIS_ZSET;
+                o->encoding = REDIS_ENCODING_ZIPLIST;
+                break;
             default:
                 redisPanic("Unknown enoding");
                 break;
diff --git a/src/redis.h b/src/redis.h
index 78294d0d..9924cee7 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -70,10 +70,12 @@
 #define REDIS_ZSET 3
 #define REDIS_HASH 4
 #define REDIS_VMPOINTER 8
+
 /* Object types only used for persistence in .rdb files */
 #define REDIS_HASH_ZIPMAP 9
 #define REDIS_LIST_ZIPLIST 10
 #define REDIS_SET_INTSET 11
+#define REDIS_ZSET_ZIPLIST 12
 
 /* Objects encoding. Some kind of objects like Strings and Hashes can be
  * internally represented in multiple ways. The 'encoding' field of the object

From 3ea204e1031a94dafca7f7e4eed2f79ec3bd7fd0 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 14:01:57 +0100
Subject: [PATCH 21/41] Configurable thresholds for encoded sorted sets

---
 redis.conf   |  6 ++++++
 src/config.c | 20 ++++++++++++++++++++
 src/redis.c  |  2 ++
 src/redis.h  |  4 ++++
 4 files changed, 32 insertions(+)

diff --git a/redis.conf b/redis.conf
index a470c98c..e7a01eec 100644
--- a/redis.conf
+++ b/redis.conf
@@ -340,6 +340,12 @@ list-max-ziplist-value 64
 # set in order to use this special memory saving encoding.
 set-max-intset-entries 512
 
+# Similarly to hashes and lists, sorted sets are also specially encoded in
+# order to save a lot of space. This encoding is only used when the length and
+# elements of a sorted set are below the following limits:
+zset-max-ziplist-entries 128
+zset-max-ziplist-value 64
+
 # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
 # order to help rehashing the main Redis hash table (the one mapping top-level
 # keys to values). The hash table implementation redis uses (see dict.c)
diff --git a/src/config.c b/src/config.c
index e0bf1574..fec72a45 100644
--- a/src/config.c
+++ b/src/config.c
@@ -261,6 +261,10 @@ void loadServerConfig(char *filename) {
             server.list_max_ziplist_value = memtoll(argv[1], NULL);
         } else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
             server.set_max_intset_entries = memtoll(argv[1], NULL);
+        } else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
+            server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
+        } else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
+            server.zset_max_ziplist_value = memtoll(argv[1], NULL);
         } else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
             struct redisCommand *cmd = lookupCommand(argv[1]);
             int retval;
@@ -443,6 +447,12 @@ void configSetCommand(redisClient *c) {
     } else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
         server.set_max_intset_entries = ll;
+    } else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-entries")) {
+        if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
+        server.zset_max_ziplist_entries = ll;
+    } else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
+        if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
+        server.zset_max_ziplist_value = ll;
     } else {
         addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
             (char*)c->argv[2]->ptr);
@@ -594,6 +604,16 @@ void configGetCommand(redisClient *c) {
         addReplyBulkLongLong(c,server.set_max_intset_entries);
         matches++;
     }
+    if (stringmatch(pattern,"zset-max-ziplist-entries",0)) {
+        addReplyBulkCString(c,"zset-max-ziplist-entries");
+        addReplyBulkLongLong(c,server.zset_max_ziplist_entries);
+        matches++;
+    }
+    if (stringmatch(pattern,"zset-max-ziplist-value",0)) {
+        addReplyBulkCString(c,"zset-max-ziplist-value");
+        addReplyBulkLongLong(c,server.zset_max_ziplist_value);
+        matches++;
+    }
     setDeferredMultiBulkLength(c,replylen,matches*2);
 }
 
diff --git a/src/redis.c b/src/redis.c
index 866ac360..2b98d40c 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -821,6 +821,8 @@ void initServerConfig() {
     server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
     server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
     server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
+    server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
+    server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
     server.shutdown_asap = 0;
     server.cache_flush_delay = 0;
 
diff --git a/src/redis.h b/src/redis.h
index 9924cee7..9f53583e 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -197,6 +197,8 @@
 #define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
 #define REDIS_LIST_MAX_ZIPLIST_VALUE 64
 #define REDIS_SET_MAX_INTSET_ENTRIES 512
+#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
+#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
 
 /* Sets operations codes */
 #define REDIS_OP_UNION 0
@@ -470,6 +472,8 @@ struct redisServer {
     size_t list_max_ziplist_entries;
     size_t list_max_ziplist_value;
     size_t set_max_intset_entries;
+    size_t zset_max_ziplist_entries;
+    size_t zset_max_ziplist_value;
     time_t unixtime;    /* Unix time sampled every second. */
     /* Virtual memory I/O threads stuff */
     /* An I/O thread process an element taken from the io_jobs queue and

From a669d5e99945b873279eadfcf289181956cb62c3 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 16:13:06 +0100
Subject: [PATCH 22/41] Convert encoding when thresholds overflow

---
 src/t_zset.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 92 insertions(+), 1 deletion(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 25da7871..35d95ba7 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -728,6 +728,85 @@ int zsLength(robj *zobj) {
     return length;
 }
 
+void zsConvert(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_RAW)
+            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_RAW;
+    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        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);
+
+        /* Immediately store pointer to ziplist in object because it will
+         * change because of reallocations when pushing to the ziplist. */
+        zobj->ptr = zl;
+
+        while (node) {
+            ele = getDecodedObject(node->obj);
+            redisAssert(zzlInsertAt(zobj,ele,node->score,NULL) == REDIS_OK);
+            decrRefCount(ele);
+
+            next = node->level[0].forward;
+            zslFreeNode(node);
+            node = next;
+        }
+
+        zfree(zs);
+        zobj->encoding = REDIS_ENCODING_ZIPLIST;
+    } else {
+        redisPanic("Unknown sorted set encoding");
+    }
+}
+
 /*-----------------------------------------------------------------------------
  * Sorted set commands 
  *----------------------------------------------------------------------------*/
@@ -746,7 +825,13 @@ void zaddGenericCommand(redisClient *c, int incr) {
 
     zobj = lookupKeyWrite(c->db,key);
     if (zobj == NULL) {
-        zobj = createZsetZiplistObject();
+        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 {
         if (zobj->type != REDIS_ZSET) {
@@ -785,7 +870,13 @@ void zaddGenericCommand(redisClient *c, int incr) {
             else /* ZADD */
                 addReply(c,shared.czero);
         } else {
+            /* Optimize: check if the element is too large or the list becomes
+             * too long *before* executing zzlInsert. */
             redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
+            if (zzlLength(zobj) > server.zset_max_ziplist_entries)
+                zsConvert(zobj,REDIS_ENCODING_RAW);
+            if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
+                zsConvert(zobj,REDIS_ENCODING_RAW);
 
             signalModifiedKey(c->db,key);
             server.dirty++;

From cc4c964b33ac108c9541e31eb2e2420addf9a82e Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 9 Mar 2011 16:13:39 +0100
Subject: [PATCH 23/41] Fix used function in ZCARD

---
 src/t_zset.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 35d95ba7..3c9ede1c 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -451,6 +451,7 @@ int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int cl
 
 unsigned int zzlLength(robj *zobj) {
     unsigned char *zl = zobj->ptr;
+    redisAssert(zobj->encoding == REDIS_ENCODING_ZIPLIST);
     return ziplistLen(zl)/2;
 }
 
@@ -1602,7 +1603,7 @@ void zcardCommand(redisClient *c) {
     if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL ||
         checkType(c,zobj,REDIS_ZSET)) return;
 
-    addReplyLongLong(c,zzlLength(zobj));
+    addReplyLongLong(c,zsLength(zobj));
 }
 
 void zscoreCommand(redisClient *c) {

From d4e07f171472a99a63c47b1fb6ab8a0a071ae945 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Mar 2011 16:16:27 +0100
Subject: [PATCH 24/41] Add new string to long long function

---
 src/redis.h |  1 +
 src/util.c  | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/src/redis.h b/src/redis.h
index 9f53583e..e32c1807 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -894,6 +894,7 @@ int stringmatchlen(const char *pattern, int patternLen,
 int stringmatch(const char *pattern, const char *string, 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 len, long long *value);
 int d2string(char *s, size_t len, double value);
 int isStringRepresentableAsLong(sds s, long *longval);
 int isStringRepresentableAsLongLong(sds s, long long *longval);
diff --git a/src/util.c b/src/util.c
index 5cffa072..599dc690 100644
--- a/src/util.c
+++ b/src/util.c
@@ -201,6 +201,63 @@ int ll2string(char *s, size_t len, long long value) {
     return l;
 }
 
+/* Convert a string into a long long. Returns 1 if the string could be parsed
+ * into a (non-overflowing) long long, 0 otherwise. The value will be set to
+ * the parsed value when appropriate. */
+int string2ll(char *s, size_t slen, long long *value) {
+    char *p = s;
+    size_t plen = 0;
+    int negative = 0;
+    unsigned long long v;
+
+    if (plen == slen)
+        return 0;
+
+    if (p[0] == '-') {
+        negative = 1;
+        p++; plen++;
+
+        /* Abort on only a negative sign. */
+        if (plen == slen)
+            return 0;
+    }
+
+    /* First digit should be 1-9. */
+    if (p[0] >= '1' && p[0] <= '9') {
+        v = p[0]-'0';
+        p++; plen++;
+    } 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)) /* Overflow. */
+            return 0;
+        if (value != NULL) *value = -v;
+    } else {
+        if (v > LLONG_MAX) /* Overflow. */
+            return 0;
+        if (value != NULL) *value = v;
+    }
+    return 1;
+}
+
 /* Convert a double to a string representation. Returns the number of bytes
  * required. The representation should always be parsable by stdtod(3). */
 int d2string(char *buf, size_t len, double value) {

From bbfe232f607f10655b6f9bf1d8f91830bb3ba413 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Mar 2011 16:17:14 +0100
Subject: [PATCH 25/41] Make zzlLength take a ziplist argument

---
 src/t_zset.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 3c9ede1c..0a35f392 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -449,9 +449,7 @@ int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int cl
     return cmp;
 }
 
-unsigned int zzlLength(robj *zobj) {
-    unsigned char *zl = zobj->ptr;
-    redisAssert(zobj->encoding == REDIS_ENCODING_ZIPLIST);
+unsigned int zzlLength(unsigned char *zl) {
     return ziplistLen(zl)/2;
 }
 
@@ -720,7 +718,7 @@ unsigned long zzlDeleteRangeByRank(robj *zobj, unsigned int start, unsigned int
 int zsLength(robj *zobj) {
     int length = -1;
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
-        length = zzlLength(zobj);
+        length = zzlLength(zobj->ptr);
     } else if (zobj->encoding == REDIS_ENCODING_RAW) {
         length = ((zset*)zobj->ptr)->zsl->length;
     } else {
@@ -874,7 +872,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
             /* Optimize: check if the element is too large or the list becomes
              * too long *before* executing zzlInsert. */
             redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
-            if (zzlLength(zobj) > server.zset_max_ziplist_entries)
+            if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
                 zsConvert(zobj,REDIS_ENCODING_RAW);
             if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
                 zsConvert(zobj,REDIS_ENCODING_RAW);
@@ -965,7 +963,7 @@ void zremCommand(redisClient *c) {
 
         if ((eptr = zzlFind(zobj,ele,NULL)) != NULL) {
             redisAssert(zzlDelete(zobj,eptr) == REDIS_OK);
-            if (zzlLength(zobj) == 0) dbDelete(c->db,key);
+            if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
         } else {
             addReply(c,shared.czero);
             return;

From 56ce42faf168cafeb9dee681ab269b1fb98b197d Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Mar 2011 16:34:52 +0100
Subject: [PATCH 26/41] Generic iterator code for usage in
 ZUNIONSTORE/ZINTERSTORE

---
 src/t_zset.c | 431 ++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 374 insertions(+), 57 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 0a35f392..1ef5e991 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -810,6 +810,12 @@ void zsConvert(robj *zobj, int encoding) {
  * Sorted set commands 
  *----------------------------------------------------------------------------*/
 
+//  if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+//  } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+//  } else {
+//      redisPanic("Unknown sorted set encoding");
+//  }
+
 /* This generic command implements both ZADD and ZINCRBY. */
 void zaddGenericCommand(redisClient *c, int incr) {
     static char *nanerr = "resulting score is not a number (NaN)";
@@ -1075,16 +1081,327 @@ void zremrangebyrankCommand(redisClient *c) {
 }
 
 typedef struct {
-    dict *dict;
+    robj *subject;
+    int type; /* Set, sorted set */
+    int encoding;
     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;
 
-int qsortCompareZsetopsrcByCardinality(const void *s1, const void *s2) {
-    zsetopsrc *d1 = (void*) s1, *d2 = (void*) s2;
-    unsigned long size1, size2;
-    size1 = d1->dict ? dictSize(d1->dict) : 0;
-    size2 = d2->dict ? dictSize(d2->dict) : 0;
-    return size1 - size2;
+
+/* Use dirty flags for pointers that need to be cleaned up in the next
+ * iteration over the zsetopval. The dirty flag for the long long value is
+ * special, since long long values don't need cleanup. Instead, it means that
+ * we already checked that "ell" holds a long long, or tried to convert another
+ * 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_RAW) {
+            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_RAW) {
+            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_RAW) {
+            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,&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_RAW) {
+            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(op->subject,val->ele,score) != NULL) {
+                /* Score is already set by zzlFind. */
+                return 1;
+            } else {
+                return 0;
+            }
+        } else if (op->encoding == REDIS_ENCODING_RAW) {
+            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
@@ -1113,11 +1430,11 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
     int i, j, setnum;
     int aggregate = REDIS_AGGR_SUM;
     zsetopsrc *src;
+    zsetopval zval;
+    robj *tmp;
     robj *dstobj;
     zset *dstzset;
     zskiplistNode *znode;
-    dictIterator *di;
-    dictEntry *de;
     int touched = 0;
 
     /* expect setnum input keys to be given */
@@ -1135,24 +1452,24 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
     }
 
     /* 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++) {
         robj *obj = lookupKeyWrite(c->db,c->argv[j]);
-        if (!obj) {
-            src[i].dict = NULL;
-        } 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 {
+        if (obj != NULL) {
+            if (obj->type != REDIS_ZSET && obj->type != REDIS_SET) {
                 zfree(src);
                 addReply(c,shared.wrongtypeerr);
                 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;
     }
 
@@ -1193,82 +1510,82 @@ 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
      * algorithm's performance */
-    qsort(src,setnum,sizeof(zsetopsrc),qsortCompareZsetopsrcByCardinality);
+    qsort(src,setnum,sizeof(zsetopsrc),zuiCompareByCardinality);
 
     dstobj = createZsetObject();
     dstzset = dstobj->ptr;
 
     if (op == REDIS_OP_INTER) {
-        /* skip going over all entries if the smallest zset is NULL or empty */
-        if (src[0].dict && dictSize(src[0].dict) > 0) {
-            /* precondition: as src[0].dict is non-empty and the zsets are ordered
-             * from small to large, all src[i > 0].dict are non-empty too */
-            di = dictGetIterator(src[0].dict);
-            while((de = dictNext(di)) != NULL) {
+        /* Skip everything if the smallest input is empty. */
+        if (zuiLength(&src[0]) > 0) {
+            /* Precondition: as src[0] is non-empty and the inputs are ordered
+             * by size, all src[i > 0] are non-empty too. */
+            while (zuiNext(&src[0],&zval)) {
                 double score, value;
 
-                score = src[0].weight * zunionInterDictValue(de);
+                score = src[0].weight * zval.score;
                 for (j = 1; j < setnum; j++) {
-                    dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
-                    if (other) {
-                        value = src[j].weight * zunionInterDictValue(other);
+                    if (zuiFind(&src[j],&zval,&value)) {
+                        value *= src[j].weight;
                         zunionInterAggregate(&score,value,aggregate);
                     } else {
                         break;
                     }
                 }
 
-                /* Only continue when present in every source dict. */
+                /* Only continue when present in every input. */
                 if (j == setnum) {
-                    robj *o = dictGetEntryKey(de);
-                    znode = zslInsert(dstzset->zsl,score,o);
-                    incrRefCount(o); /* added to skiplist */
-                    dictAdd(dstzset->dict,o,&znode->score);
-                    incrRefCount(o); /* added to dictionary */
+                    tmp = zuiObjectFromValue(&zval);
+                    znode = zslInsert(dstzset->zsl,score,tmp);
+                    incrRefCount(tmp); /* added to skiplist */
+                    dictAdd(dstzset->dict,tmp,&znode->score);
+                    incrRefCount(tmp); /* added to dictionary */
                 }
             }
-            dictReleaseIterator(di);
         }
     } else if (op == REDIS_OP_UNION) {
         for (i = 0; i < setnum; i++) {
-            if (!src[i].dict) continue;
+            if (zuiLength(&src[0]) == 0)
+                continue;
 
-            di = dictGetIterator(src[i].dict);
-            while((de = dictNext(di)) != NULL) {
+            while (zuiNext(&src[i],&zval)) {
                 double score, value;
 
-                /* skip key when already processed */
-                if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL)
+                /* Skip key when already processed */
+                if (dictFind(dstzset->dict,zuiObjectFromValue(&zval)) != NULL)
                     continue;
 
-                /* initialize score */
-                score = src[i].weight * zunionInterDictValue(de);
+                /* Initialize score */
+                score = src[i].weight * zval.score;
 
-                /* because the zsets are sorted by size, its only possible
-                 * for sets at larger indices to hold this entry */
+                /* Because the inputs are sorted by size, it's only possible
+                 * for sets at larger indices to hold this element. */
                 for (j = (i+1); j < setnum; j++) {
-                    dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
-                    if (other) {
-                        value = src[j].weight * zunionInterDictValue(other);
+                    if (zuiFind(&src[j],&zval,&value)) {
+                        value *= src[j].weight;
                         zunionInterAggregate(&score,value,aggregate);
                     }
                 }
 
-                robj *o = dictGetEntryKey(de);
-                znode = zslInsert(dstzset->zsl,score,o);
-                incrRefCount(o); /* added to skiplist */
-                dictAdd(dstzset->dict,o,&znode->score);
-                incrRefCount(o); /* added to dictionary */
+                tmp = zuiObjectFromValue(&zval);
+                znode = zslInsert(dstzset->zsl,score,tmp);
+                incrRefCount(zval.ele); /* added to skiplist */
+                dictAdd(dstzset->dict,tmp,&znode->score);
+                incrRefCount(zval.ele); /* added to dictionary */
             }
-            dictReleaseIterator(di);
         }
     } else {
-        /* unknown operator */
-        redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION);
+        redisPanic("Unknown operator");
     }
 
+    for (i = 0; i < setnum; i++)
+        zuiClearIterator(&src[i]);
+
     if (dbDelete(c->db,dstkey)) {
         signalModifiedKey(c->db,dstkey);
         touched = 1;

From dba3a153a7ac9a1ec81e8f3034714d4900235a00 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Mar 2011 16:53:20 +0100
Subject: [PATCH 27/41] Remove comment

---
 src/t_zset.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 1ef5e991..b77a3424 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -810,12 +810,6 @@ void zsConvert(robj *zobj, int encoding) {
  * Sorted set commands 
  *----------------------------------------------------------------------------*/
 
-//  if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
-//  } else if (zobj->encoding == REDIS_ENCODING_RAW) {
-//  } else {
-//      redisPanic("Unknown sorted set encoding");
-//  }
-
 /* This generic command implements both ZADD and ZINCRBY. */
 void zaddGenericCommand(redisClient *c, int incr) {
     static char *nanerr = "resulting score is not a number (NaN)";

From 255eebe22167e00f74e359bc71718225d6bd70c8 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Mar 2011 17:02:05 +0100
Subject: [PATCH 28/41] Convert encoding of result when in limits

---
 src/t_zset.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index b77a3424..a2882702 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -1426,6 +1426,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
     zsetopsrc *src;
     zsetopval zval;
     robj *tmp;
+    unsigned int maxelelen = 0;
     robj *dstobj;
     zset *dstzset;
     zskiplistNode *znode;
@@ -1539,6 +1540,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
                     incrRefCount(tmp); /* added to skiplist */
                     dictAdd(dstzset->dict,tmp,&znode->score);
                     incrRefCount(tmp); /* added to dictionary */
+
+                    if (tmp->encoding == REDIS_ENCODING_RAW)
+                        if (sdslen(tmp->ptr) > maxelelen)
+                            maxelelen = sdslen(tmp->ptr);
                 }
             }
         }
@@ -1571,6 +1576,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
                 incrRefCount(zval.ele); /* added to skiplist */
                 dictAdd(dstzset->dict,tmp,&znode->score);
                 incrRefCount(zval.ele); /* added to dictionary */
+
+                if (tmp->encoding == REDIS_ENCODING_RAW)
+                    if (sdslen(tmp->ptr) > maxelelen)
+                        maxelelen = sdslen(tmp->ptr);
             }
         }
     } else {
@@ -1586,13 +1595,18 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
         server.dirty++;
     }
     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)
+                zsConvert(dstobj,REDIS_ENCODING_ZIPLIST);
+
         dbAdd(c->db,dstkey,dstobj);
-        addReplyLongLong(c, dstzset->zsl->length);
+        addReplyLongLong(c,zsLength(dstobj));
         if (!touched) signalModifiedKey(c->db,dstkey);
         server.dirty++;
     } else {
         decrRefCount(dstobj);
-        addReply(c, shared.czero);
+        addReply(c,shared.czero);
     }
     zfree(src);
 }

From df26a0ae0b419124efad82df148b84c6c6164615 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Thu, 10 Mar 2011 17:50:13 +0100
Subject: [PATCH 29/41] Encode sorted set after loading from dump

---
 src/rdb.c    | 15 +++++++++++++++
 src/redis.h  |  3 +++
 src/t_zset.c | 20 ++++++++++----------
 3 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/src/rdb.c b/src/rdb.c
index 62762bee..d2c902d7 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -756,11 +756,13 @@ robj *rdbLoadObject(int type, FILE *fp) {
     } else if (type == REDIS_ZSET) {
         /* Read list/set value */
         size_t zsetlen;
+        size_t maxelelen = 0;
         zset *zs;
 
         if ((zsetlen = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL;
         o = createZsetObject();
         zs = o->ptr;
+
         /* Load every single element of the list/set */
         while(zsetlen--) {
             robj *ele;
@@ -770,10 +772,21 @@ robj *rdbLoadObject(int type, FILE *fp) {
             if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
             ele = tryObjectEncoding(ele);
             if (rdbLoadDoubleValue(fp,&score) == -1) return NULL;
+
+            /* Don't care about integer-encoded strings. */
+            if (ele->encoding == REDIS_ENCODING_RAW &&
+                sdslen(ele->ptr) > maxelelen)
+                    maxelelen = sdslen(ele->ptr);
+
             znode = zslInsert(zs->zsl,score,ele);
             dictAdd(zs->dict,ele,&znode->score);
             incrRefCount(ele); /* added to skiplist */
         }
+
+        /* Convert *after* loading, since sorted sets are not stored ordered. */
+        if (zsetLength(o) <= server.zset_max_ziplist_entries &&
+            maxelelen <= server.zset_max_ziplist_value)
+                zsetConvert(o,REDIS_ENCODING_ZIPLIST);
     } else if (type == REDIS_HASH) {
         size_t hashlen;
 
@@ -861,6 +874,8 @@ robj *rdbLoadObject(int type, FILE *fp) {
             case REDIS_ZSET_ZIPLIST:
                 o->type = REDIS_ZSET;
                 o->encoding = REDIS_ENCODING_ZIPLIST;
+                if (zsetLength(o) > server.zset_max_ziplist_entries)
+                    zsetConvert(o,REDIS_ENCODING_RAW);
                 break;
             default:
                 redisPanic("Unknown enoding");
diff --git a/src/redis.h b/src/redis.h
index e32c1807..73ac1b60 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -799,6 +799,9 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal);
 zskiplist *zslCreate(void);
 void zslFree(zskiplist *zsl);
 zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
+int zzlInsert(robj *zobj, robj *ele, double score);
+unsigned int zsetLength(robj *zobj);
+void zsetConvert(robj *zobj, int encoding);
 
 /* Core functions */
 void freeMemoryIfNeeded(void);
diff --git a/src/t_zset.c b/src/t_zset.c
index a2882702..93fb93bf 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -715,7 +715,7 @@ unsigned long zzlDeleteRangeByRank(robj *zobj, unsigned int start, unsigned int
  * Common sorted set API
  *----------------------------------------------------------------------------*/
 
-int zsLength(robj *zobj) {
+unsigned int zsetLength(robj *zobj) {
     int length = -1;
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
         length = zzlLength(zobj->ptr);
@@ -727,7 +727,7 @@ int zsLength(robj *zobj) {
     return length;
 }
 
-void zsConvert(robj *zobj, int encoding) {
+void zsetConvert(robj *zobj, int encoding) {
     zset *zs;
     zskiplistNode *node, *next;
     robj *ele;
@@ -873,9 +873,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
              * too long *before* executing zzlInsert. */
             redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
             if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
-                zsConvert(zobj,REDIS_ENCODING_RAW);
+                zsetConvert(zobj,REDIS_ENCODING_RAW);
             if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
-                zsConvert(zobj,REDIS_ENCODING_RAW);
+                zsetConvert(zobj,REDIS_ENCODING_RAW);
 
             signalModifiedKey(c->db,key);
             server.dirty++;
@@ -1042,7 +1042,7 @@ void zremrangebyrankCommand(redisClient *c) {
         checkType(c,zobj,REDIS_ZSET)) return;
 
     /* Sanitize indexes. */
-    llen = zsLength(zobj);
+    llen = zsetLength(zobj);
     if (start < 0) start = llen+start;
     if (end < 0) end = llen+end;
     if (start < 0) start = 0;
@@ -1598,10 +1598,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
         /* Convert to ziplist when in limits. */
         if (dstzset->zsl->length <= server.zset_max_ziplist_entries &&
             maxelelen <= server.zset_max_ziplist_value)
-                zsConvert(dstobj,REDIS_ENCODING_ZIPLIST);
+                zsetConvert(dstobj,REDIS_ENCODING_ZIPLIST);
 
         dbAdd(c->db,dstkey,dstobj);
-        addReplyLongLong(c,zsLength(dstobj));
+        addReplyLongLong(c,zsetLength(dstobj));
         if (!touched) signalModifiedKey(c->db,dstkey);
         server.dirty++;
     } else {
@@ -1642,7 +1642,7 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
          || checkType(c,zobj,REDIS_ZSET)) return;
 
     /* Sanitize indexes. */
-    llen = zsLength(zobj);
+    llen = zsetLength(zobj);
     if (start < 0) start = llen+start;
     if (end < 0) end = llen+end;
     if (start < 0) start = 0;
@@ -1926,7 +1926,7 @@ void zcardCommand(redisClient *c) {
     if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL ||
         checkType(c,zobj,REDIS_ZSET)) return;
 
-    addReplyLongLong(c,zsLength(zobj));
+    addReplyLongLong(c,zsetLength(zobj));
 }
 
 void zscoreCommand(redisClient *c) {
@@ -1968,7 +1968,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
 
     if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
         checkType(c,zobj,REDIS_ZSET)) return;
-    llen = zsLength(zobj);
+    llen = zsetLength(zobj);
 
     redisAssert(ele->encoding == REDIS_ENCODING_RAW);
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {

From 8588bfa370749b24922c0c8f477c562736626421 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Fri, 11 Mar 2011 17:06:07 +0100
Subject: [PATCH 30/41] Make zzl API unaware of the robj where the ziplist is
 stored

---
 src/redis.h  |  2 +-
 src/t_zset.c | 85 +++++++++++++++++++++++-----------------------------
 2 files changed, 39 insertions(+), 48 deletions(-)

diff --git a/src/redis.h b/src/redis.h
index 73ac1b60..8ed7216d 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -799,7 +799,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal);
 zskiplist *zslCreate(void);
 void zslFree(zskiplist *zsl);
 zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
-int zzlInsert(robj *zobj, robj *ele, double score);
+unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
 unsigned int zsetLength(robj *zobj);
 void zsetConvert(robj *zobj, int encoding);
 
diff --git a/src/t_zset.c b/src/t_zset.c
index 93fb93bf..65c99622 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -519,8 +519,7 @@ int zzlIsInRange(unsigned char *zl, zrangespec *range) {
 
 /* Find pointer to the first element contained in the specified range.
  * Returns NULL when no element is contained in the range. */
-unsigned char *zzlFirstInRange(robj *zobj, zrangespec range) {
-    unsigned char *zl = zobj->ptr;
+unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec range) {
     unsigned char *eptr = ziplistIndex(zl,0), *sptr;
     double score;
 
@@ -544,8 +543,7 @@ unsigned char *zzlFirstInRange(robj *zobj, zrangespec range) {
 
 /* Find pointer to the last element contained in the specified range.
  * Returns NULL when no element is contained in the range. */
-unsigned char *zzlLastInRange(robj *zobj, zrangespec range) {
-    unsigned char *zl = zobj->ptr;
+unsigned char *zzlLastInRange(unsigned char *zl, zrangespec range) {
     unsigned char *eptr = ziplistIndex(zl,-2), *sptr;
     double score;
 
@@ -572,8 +570,7 @@ unsigned char *zzlLastInRange(robj *zobj, zrangespec range) {
     return NULL;
 }
 
-unsigned char *zzlFind(robj *zobj, robj *ele, double *score) {
-    unsigned char *zl = zobj->ptr;
+unsigned char *zzlFind(unsigned char *zl, robj *ele, double *score) {
     unsigned char *eptr = ziplistIndex(zl,0), *sptr;
 
     ele = getDecodedObject(ele);
@@ -598,19 +595,16 @@ unsigned char *zzlFind(robj *zobj, robj *ele, double *score) {
 
 /* Delete (element,score) pair from ziplist. Use local copy of eptr because we
  * don't want to modify the one given as argument. */
-int zzlDelete(robj *zobj, unsigned char *eptr) {
-    unsigned char *zl = zobj->ptr;
+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);
-    zobj->ptr = zl;
-    return REDIS_OK;
+    return zl;
 }
 
-int zzlInsertAt(robj *zobj, robj *ele, double score, unsigned char *eptr) {
-    unsigned char *zl = zobj->ptr;
+unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, robj *ele, double score) {
     unsigned char *sptr;
     char scorebuf[128];
     int scorelen;
@@ -632,14 +626,12 @@ int zzlInsertAt(robj *zobj, robj *ele, double score, unsigned char *eptr) {
         zl = ziplistInsert(zl,sptr,(unsigned char*)scorebuf,scorelen);
     }
 
-    zobj->ptr = zl;
-    return REDIS_OK;
+    return zl;
 }
 
 /* Insert (element,score) pair in ziplist. This function assumes the element is
  * not yet present in the list. */
-int zzlInsert(robj *zobj, robj *ele, double score) {
-    unsigned char *zl = zobj->ptr;
+unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score) {
     unsigned char *eptr = ziplistIndex(zl,0), *sptr;
     double s;
 
@@ -653,12 +645,12 @@ int zzlInsert(robj *zobj, robj *ele, double 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. */
-            zzlInsertAt(zobj,ele,score,eptr);
+            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) {
-                zzlInsertAt(zobj,ele,score,eptr);
+                zl = zzlInsertAt(zl,eptr,ele,score);
                 break;
             }
         }
@@ -669,21 +661,21 @@ int zzlInsert(robj *zobj, robj *ele, double score) {
 
     /* Push on tail of list when it was not yet inserted. */
     if (eptr == NULL)
-        zzlInsertAt(zobj,ele,score,NULL);
+        zl = zzlInsertAt(zl,NULL,ele,score);
 
     decrRefCount(ele);
-    return REDIS_OK;
+    return zl;
 }
 
-unsigned long zzlDeleteRangeByScore(robj *zobj, zrangespec range) {
-    unsigned char *zl = zobj->ptr;
+unsigned char *zzlDeleteRangeByScore(unsigned char *zl, zrangespec range, unsigned long *deleted) {
     unsigned char *eptr, *sptr;
     double score;
-    unsigned long deleted = 0;
+    unsigned long num = 0;
 
-    eptr = zzlFirstInRange(zobj,range);
-    if (eptr == NULL) return deleted;
+    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. */
@@ -693,22 +685,24 @@ unsigned long zzlDeleteRangeByScore(robj *zobj, zrangespec range) {
             /* Delete both the element and the score. */
             zl = ziplistDelete(zl,&eptr);
             zl = ziplistDelete(zl,&eptr);
-            deleted++;
+            num++;
         } else {
             /* No longer in range. */
             break;
         }
     }
 
-    return deleted;
+    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 long zzlDeleteRangeByRank(robj *zobj, unsigned int start, unsigned int end) {
+unsigned char *zzlDeleteRangeByRank(unsigned char *zl, unsigned int start, unsigned int end, unsigned long *deleted) {
     unsigned int num = (end-start)+1;
-    zobj->ptr = ziplistDeleteRange(zobj->ptr,2*(start-1),2*num);
-    return num;
+    if (deleted) *deleted = num;
+    zl = ziplistDeleteRange(zl,2*(start-1),2*num);
+    return zl;
 }
 
 /*-----------------------------------------------------------------------------
@@ -785,13 +779,9 @@ void zsetConvert(robj *zobj, int encoding) {
         zfree(zs->zsl->header);
         zfree(zs->zsl);
 
-        /* Immediately store pointer to ziplist in object because it will
-         * change because of reallocations when pushing to the ziplist. */
-        zobj->ptr = zl;
-
         while (node) {
             ele = getDecodedObject(node->obj);
-            redisAssert(zzlInsertAt(zobj,ele,node->score,NULL) == REDIS_OK);
+            zl = zzlInsertAt(zl,NULL,ele,node->score);
             decrRefCount(ele);
 
             next = node->level[0].forward;
@@ -800,6 +790,7 @@ void zsetConvert(robj *zobj, int encoding) {
         }
 
         zfree(zs);
+        zobj->ptr = zl;
         zobj->encoding = REDIS_ENCODING_ZIPLIST;
     } else {
         redisPanic("Unknown sorted set encoding");
@@ -844,7 +835,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
 
         /* Prefer non-encoded element when dealing with ziplists. */
         ele = c->argv[3];
-        if ((eptr = zzlFind(zobj,ele,&curscore)) != NULL) {
+        if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
             if (incr) {
                 score += curscore;
                 if (isnan(score)) {
@@ -857,8 +848,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
 
             /* Remove and re-insert when score changed. */
             if (score != curscore) {
-                redisAssert(zzlDelete(zobj,eptr) == REDIS_OK);
-                redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
+                zobj->ptr = zzlDelete(zobj->ptr,eptr);
+                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
 
                 signalModifiedKey(c->db,key);
                 server.dirty++;
@@ -871,7 +862,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
         } else {
             /* Optimize: check if the element is too large or the list becomes
              * too long *before* executing zzlInsert. */
-            redisAssert(zzlInsert(zobj,ele,score) == REDIS_OK);
+            zobj->ptr = zzlInsert(zobj->ptr,ele,score);
             if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
                 zsetConvert(zobj,REDIS_ENCODING_RAW);
             if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
@@ -961,8 +952,8 @@ void zremCommand(redisClient *c) {
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
         unsigned char *eptr;
 
-        if ((eptr = zzlFind(zobj,ele,NULL)) != NULL) {
-            redisAssert(zzlDelete(zobj,eptr) == REDIS_OK);
+        if ((eptr = zzlFind(zobj->ptr,ele,NULL)) != NULL) {
+            zobj->ptr = zzlDelete(zobj->ptr,eptr);
             if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
         } else {
             addReply(c,shared.czero);
@@ -1012,7 +1003,7 @@ void zremrangebyscoreCommand(redisClient *c) {
         checkType(c,zobj,REDIS_ZSET)) return;
 
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
-        deleted = zzlDeleteRangeByScore(zobj,range);
+        zobj->ptr = zzlDeleteRangeByScore(zobj->ptr,range,&deleted);
     } else if (zobj->encoding == REDIS_ENCODING_RAW) {
         zset *zs = zobj->ptr;
         deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
@@ -1057,7 +1048,7 @@ void zremrangebyrankCommand(redisClient *c) {
 
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
         /* Correct for 1-based rank. */
-        deleted = zzlDeleteRangeByRank(zobj,start+1,end+1);
+        zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
     } else if (zobj->encoding == REDIS_ENCODING_RAW) {
         zset *zs = zobj->ptr;
 
@@ -1372,7 +1363,7 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
         zuiObjectFromValue(val);
 
         if (op->encoding == REDIS_ENCODING_ZIPLIST) {
-            if (zzlFind(op->subject,val->ele,score) != NULL) {
+            if (zzlFind(it->zl.zl,val->ele,score) != NULL) {
                 /* Score is already set by zzlFind. */
                 return 1;
             } else {
@@ -1791,9 +1782,9 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
 
         /* If reversed, get the last node in range as starting point. */
         if (reverse)
-            eptr = zzlLastInRange(zobj,range);
+            eptr = zzlLastInRange(zl,range);
         else
-            eptr = zzlFirstInRange(zobj,range);
+            eptr = zzlFirstInRange(zl,range);
 
         /* No "first" element in the specified interval. */
         if (eptr == NULL) {
@@ -1938,7 +1929,7 @@ void zscoreCommand(redisClient *c) {
         checkType(c,zobj,REDIS_ZSET)) return;
 
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
-        if (zzlFind(zobj,c->argv[2],&score) != NULL)
+        if (zzlFind(zobj->ptr,c->argv[2],&score) != NULL)
             addReplyDouble(c,score);
         else
             addReply(c,shared.nullbulk);

From e53ca04b50b86ef158a75c54ae9ee8b17e31719c Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Fri, 11 Mar 2011 18:17:53 +0100
Subject: [PATCH 31/41] Test for empty inner range when looking for elements in
 range

---
 src/t_zset.c             | 32 ++++++++++++++++++++++----------
 tests/unit/type/zset.tcl |  6 ++++++
 2 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 65c99622..b8a961eb 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -226,10 +226,12 @@ zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range) {
                 x = x->level[i].forward;
     }
 
-    /* The tail is in range, so the previous block should always return a
-     * node that is non-NULL and the last one to be out of range. */
+    /* This is an inner range, so the next node cannot be NULL. */
     x = x->level[0].forward;
-    redisAssert(x != NULL && zslValueInRange(x->score,&range));
+    redisAssert(x != NULL);
+
+    /* Check if score <= max. */
+    if (!zslValueLteMax(x->score,&range)) return NULL;
     return x;
 }
 
@@ -250,9 +252,11 @@ zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec range) {
                 x = x->level[i].forward;
     }
 
-    /* The header is in range, so the previous block should always return a
-     * node that is non-NULL and in range. */
-    redisAssert(x != NULL && zslValueInRange(x->score,&range));
+    /* 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;
 }
 
@@ -531,8 +535,12 @@ unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec range) {
         redisAssert(sptr != NULL);
 
         score = zzlGetScore(sptr);
-        if (zslValueGteMin(score,&range))
-            return eptr;
+        if (zslValueGteMin(score,&range)) {
+            /* Check if score <= max. */
+            if (zslValueLteMax(score,&range))
+                return eptr;
+            return NULL;
+        }
 
         /* Move to next element. */
         eptr = ziplistNext(zl,sptr);
@@ -555,8 +563,12 @@ unsigned char *zzlLastInRange(unsigned char *zl, zrangespec range) {
         redisAssert(sptr != NULL);
 
         score = zzlGetScore(sptr);
-        if (zslValueLteMax(score,&range))
-            return eptr;
+        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. */
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index fdeebd2f..6218a95c 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -247,6 +247,12 @@ start_server {tags {"zset"}} {
         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" {

From 72690afdd26b118589ddec3915cdb4790b9d4812 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Sat, 12 Mar 2011 14:48:29 +0100
Subject: [PATCH 32/41] Remove unused function

---
 src/t_zset.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index b8a961eb..63657890 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -188,10 +188,6 @@ static int zslValueLteMax(double value, zrangespec *spec) {
     return spec->maxex ? (value < spec->max) : (value <= spec->max);
 }
 
-static int zslValueInRange(double value, zrangespec *spec) {
-    return zslValueGteMin(value,spec) && zslValueLteMax(value,spec);
-}
-
 /* Returns if there is a part of the zset is in range. */
 int zslIsInRange(zskiplist *zsl, zrangespec *range) {
     zskiplistNode *x;

From 69298a05eb23cbbf60f9008faa2e11866ab4352a Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Sun, 13 Mar 2011 18:15:57 +0100
Subject: [PATCH 33/41] Offset should be size_t

---
 src/t_zset.c  |  2 +-
 src/ziplist.c | 14 ++++++++------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/t_zset.c b/src/t_zset.c
index 63657890..03a79ce9 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -616,7 +616,7 @@ unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, robj *ele, do
     unsigned char *sptr;
     char scorebuf[128];
     int scorelen;
-    int offset;
+    size_t offset;
 
     redisAssert(ele->encoding == REDIS_ENCODING_RAW);
     scorelen = d2string(scorebuf,sizeof(scorebuf),score);
diff --git a/src/ziplist.c b/src/ziplist.c
index 524f7238..44d63c78 100644
--- a/src/ziplist.c
+++ b/src/ziplist.c
@@ -375,8 +375,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
  * updated, i.e. consecutive fields MAY need an update. */
 static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
-    unsigned int curlen = ZIPLIST_BYTES(zl), rawlen, rawlensize;
-    unsigned int offset, noffset, extra;
+    size_t curlen = ZIPLIST_BYTES(zl), rawlen, rawlensize;
+    size_t offset, noffset, extra;
     unsigned char *np;
     zlentry cur, next;
 
@@ -431,7 +431,8 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
 /* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
 static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
     unsigned int i, totlen, deleted = 0;
-    int offset, nextdiff = 0;
+    size_t offset;
+    int nextdiff = 0;
     zlentry first, tail;
 
     first = zipEntry(p);
@@ -483,8 +484,9 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
 
 /* Insert item at "p". */
 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;
-    unsigned int offset, nextdiff = 0;
+    size_t curlen = ZIPLIST_BYTES(zl), reqlen, prevlen = 0;
+    size_t offset;
+    int nextdiff = 0;
     unsigned char encoding = 0;
     long long value;
     zlentry entry, tail;
@@ -668,7 +670,7 @@ unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char
  * Also update *p in place, to be able to iterate over the
  * ziplist, while deleting entries. */
 unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) {
-    unsigned int offset = *p-zl;
+    size_t offset = *p-zl;
     zl = __ziplistDelete(zl,*p,1);
 
     /* Store pointer to current element in p, because ziplistDelete will

From 9ec4ea20a71f075a7fc9a2e1d613c339353ee553 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Mon, 14 Mar 2011 10:50:35 +0100
Subject: [PATCH 34/41] Test both sorted set encodings for every test

---
 tests/unit/type/zset.tcl | 999 +++++++++++++++++++++------------------
 1 file changed, 530 insertions(+), 469 deletions(-)

diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 6218a95c..31604412 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -6,292 +6,540 @@ start_server {tags {"zset"}} {
         }
     }
 
-    test {ZSET basic ZADD and score update} {
-        r zadd ztmp 10 x
-        r zadd ztmp 20 y
-        r zadd ztmp 30 z
-        set aux1 [r zrange ztmp 0 -1]
-        r zadd ztmp 1 y
-        set aux2 [r zrange ztmp 0 -1]
-        list $aux1 $aux2
-    } {{x y z} {y x z}}
-
-    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" {
-        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 basics} {
-        r zadd zranktmp 10 x
-        r zadd zranktmp 20 y
-        r zadd zranktmp 30 z
-        list [r zrank zranktmp x] [r zrank zranktmp y] [r zrank zranktmp z]
-    } {0 1 2}
-
-    test {ZREVRANK basics} {
-        list [r zrevrank zranktmp x] [r zrevrank zranktmp y] [r zrevrank zranktmp z]
-    } {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
+    proc basics {encoding} {
+        if {$encoding == "ziplist"} {
+            r config set zset-max-ziplist-entries 128
+            r config set zset-max-ziplist-value 64
+        } elseif {$encoding == "raw"} {
+            r config set zset-max-ziplist-entries 0
+            r config set zset-max-ziplist-value 0
+        } else {
+            puts "Unknown sorted set encoding"
+            exit
         }
-        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
+
+        test "Check encoding - $encoding" {
+            r del ztmp
+            r zadd ztmp 10 x
+            assert_encoding $encoding ztmp
+        }
+
+        test "ZSET basic ZADD and score update - $encoding" {
+            r del ztmp
+            r zadd ztmp 10 x
+            r zadd ztmp 20 y
+            r zadd ztmp 30 z
+            assert_equal {x y z} [r zrange ztmp 0 -1]
+
+            r zadd ztmp 1 y
+            assert_equal {y x z} [r zrange ztmp 0 -1]
+        }
+
+        test "ZSET element can't be set to NaN with ZADD - $encoding" {
+            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" {
+            r zincrby myzset +inf abc
+            assert_error "*NaN*" {r zincrby myzset -inf abc}
+        }
+
+        test "ZCARD basics - $encoding" {
+            assert_equal 3 [r zcard ztmp]
+            assert_equal 0 [r zcard zdoesntexist]
+        }
+
+        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}
+                r zremrangebyscore zset $min $max
             }
-        }
-        set _ $err
-    } {}
 
-    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
+            # 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]
         }
-        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
+
+        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
             }
-        }
-        set _ $err
-    } {}
 
-    test {ZSETs stress tester - sorting is working well?} {
-        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 < 1000} {incr i} {
-                if {$test == 0} {
-                    set score [expr rand()]
-                } else {
-                    set score [expr int(rand()*10)]
+            # 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 - $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]
+            }
+
+            test "$cmd with NaN weights $encoding" {
+                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
                 }
-                set auxarray($i) $score
-                r zadd myzset $score $i
-                # Random update
-                if {[expr rand()] < .2} {
-                    set j [expr int(rand()*1000)]
+            }
+        }
+    }
+
+    basics ziplist
+    basics raw
+
+    proc stressers {encoding} {
+        if {$encoding == "ziplist"} {
+            # Little extra to allow proper fuzzing in the sorting stresser
+            r config set zset-max-ziplist-entries 256
+            r config set zset-max-ziplist-value 64
+            set elements 128
+        } elseif {$encoding == "raw"} {
+            r config set zset-max-ziplist-entries 0
+            r config set zset-max-ziplist-value 0
+            set elements 1000
+        } else {
+            puts "Unknown sorted set encoding"
+            exit
+        }
+
+        test "ZSCORE - $encoding" {
+            r del zscoretest
+            set aux {}
+            for {set i 0} {$i < $elements} {incr i} {
+                set score [expr rand()]
+                lappend aux $score
+                r zadd zscoretest $score $i
+            }
+
+            assert_encoding $encoding zscoretest
+            for {set i 0} {$i < $elements} {incr i} {
+                assert_equal [lindex $aux $i] [r zscore zscoretest $i]
+            }
+        }
+
+        test "ZSCORE after a DEBUG RELOAD - $encoding" {
+            r del zscoretest
+            set aux {}
+            for {set i 0} {$i < $elements} {incr i} {
+                set score [expr rand()]
+                lappend aux $score
+                r zadd zscoretest $score $i
+            }
+
+            r debug reload
+            assert_encoding $encoding zscoretest
+            for {set i 0} {$i < $elements} {incr i} {
+                assert_equal [lindex $aux $i] [r zscore zscoretest $i]
+            }
+        }
+
+        test "ZSET sorting stresser - $encoding" {
+            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($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]
-            }
-            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
+                    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
         }
-        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" {
-        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}
-    }
-
-    tags {"slow"} {
-        test {ZRANGEBYSCORE fuzzy test, 100 ranges in 1000 elements sorted set} {
+        test "ZRANGEBYSCORE fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
             set err {}
             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
             }
+
+            assert_encoding $encoding zset
             for {set i 0} {$i < 100} {incr i} {
                 set min [expr rand()]
                 set max [expr rand()]
@@ -363,198 +611,17 @@ start_server {tags {"zset"}} {
                     }
                 }
             }
-            set _ $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
+            assert_equal {} $err
         }
 
-        # 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} {
+        test "ZSETs skiplist implementation backlink consistency test - $encoding" {
             set diff 0
-            set elements 10000
             for {set j 0} {$j < $elements} {incr j} {
                 r zadd myzset [expr rand()] "Element-$j"
                 r zrem myzset "Element-[expr int(rand()*$elements)]"
             }
+
+            assert_encoding $encoding myzset
             set l1 [r zrange myzset 0 -1]
             set l2 [r zrevrange myzset 0 -1]
             for {set j 0} {$j < [llength $l1]} {incr j} {
@@ -562,20 +629,22 @@ start_server {tags {"zset"}} {
                     incr diff
                 }
             }
-            format $diff
-        } {0}
+            assert_equal 0 $diff
+        }
 
-        test {ZSETs ZRANK augmented skip list stress testing} {
+        test "ZSETs ZRANK augmented skip list stress testing - $encoding" {
             set err {}
             r del myzset
-            for {set k 0} {$k < 10000} {incr k} {
-                set i [expr {$k%1000}]
+            for {set k 0} {$k < 2000} {incr k} {
+                set i [expr {$k % $elements}]
                 if {[expr rand()] < .2} {
                     r zrem myzset $i
                 } else {
                     set score [expr rand()]
                     r zadd myzset $score $i
+                    assert_encoding $encoding myzset
                 }
+
                 set card [r zcard myzset]
                 if {$card > 0} {
                     set index [randomInt $card]
@@ -587,20 +656,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} {
-        r zincrby myzset +inf abc
-        assert_error "*NaN*" {r zincrby myzset -inf abc}
+    tags {"slow"} {
+        stressers ziplist
+        stressers raw
     }
 }

From dddf5335f47d62d5e74308faff8dcb5d0575286c Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Mon, 14 Mar 2011 13:30:06 +0100
Subject: [PATCH 35/41] Fix DEBUG DIGEST, SORT and AOF rewrite

---
 src/aof.c   | 58 +++++++++++++++++++++++++++++++++++++++-----------
 src/debug.c | 61 +++++++++++++++++++++++++++++++++++++++++------------
 src/redis.h |  3 +++
 src/sort.c  |  3 +++
 4 files changed, 100 insertions(+), 25 deletions(-)

diff --git a/src/aof.c b/src/aof.c
index 8ce6cd12..cd7c48d3 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -429,21 +429,55 @@ int rewriteAppendOnlyFile(char *filename) {
                 }
             } else if (o->type == REDIS_ZSET) {
                 /* Emit the ZADDs needed to rebuild the sorted set */
-                zset *zs = o->ptr;
-                dictIterator *di = dictGetIterator(zs->dict);
-                dictEntry *de;
+                char cmd[]="*4\r\n$4\r\nZADD\r\n";
 
-                while((de = dictNext(di)) != NULL) {
-                    char cmd[]="*4\r\n$4\r\nZADD\r\n";
-                    robj *eleobj = dictGetEntryKey(de);
-                    double *score = dictGetEntryVal(de);
+                if (o->encoding == REDIS_ENCODING_ZIPLIST) {
+                    unsigned char *zl = o->ptr;
+                    unsigned char *eptr, *sptr;
+                    unsigned char *vstr;
+                    unsigned int vlen;
+                    long long vll;
+                    double score;
 
-                    if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
-                    if (fwriteBulkObject(fp,&key) == 0) goto werr;
-                    if (fwriteBulkDouble(fp,*score) == 0) goto werr;
-                    if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
+                    eptr = ziplistIndex(zl,0);
+                    redisAssert(eptr != NULL);
+                    sptr = ziplistNext(zl,eptr);
+                    redisAssert(sptr != NULL);
+
+                    while (eptr != NULL) {
+                        redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
+                        score = zzlGetScore(sptr);
+
+                        if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
+                        if (fwriteBulkObject(fp,&key) == 0) goto werr;
+                        if (fwriteBulkDouble(fp,score) == 0) goto werr;
+                        if (vstr != NULL) {
+                            if (fwriteBulkString(fp,(char*)vstr,vlen) == 0)
+                                goto werr;
+                        } else {
+                            if (fwriteBulkLongLong(fp,vll) == 0)
+                                goto werr;
+                        }
+                        zzlNext(zl,&eptr,&sptr);
+                    }
+                } else if (o->encoding == REDIS_ENCODING_RAW) {
+                    zset *zs = o->ptr;
+                    dictIterator *di = dictGetIterator(zs->dict);
+                    dictEntry *de;
+
+                    while((de = dictNext(di)) != NULL) {
+                        robj *eleobj = dictGetEntryKey(de);
+                        double *score = dictGetEntryVal(de);
+
+                        if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
+                        if (fwriteBulkObject(fp,&key) == 0) goto werr;
+                        if (fwriteBulkDouble(fp,*score) == 0) goto werr;
+                        if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
+                    }
+                    dictReleaseIterator(di);
+                } else {
+                    redisPanic("Unknown sorted set encoding");
                 }
-                dictReleaseIterator(di);
             } else if (o->type == REDIS_HASH) {
                 char cmd[]="*4\r\n$4\r\nHSET\r\n";
 
diff --git a/src/debug.c b/src/debug.c
index c1fc26cf..940a0380 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -127,22 +127,57 @@ void computeDatasetDigest(unsigned char *final) {
                 }
                 setTypeReleaseIterator(si);
             } else if (o->type == REDIS_ZSET) {
-                zset *zs = o->ptr;
-                dictIterator *di = dictGetIterator(zs->dict);
-                dictEntry *de;
+                unsigned char eledigest[20];
 
-                while((de = dictNext(di)) != NULL) {
-                    robj *eleobj = dictGetEntryKey(de);
-                    double *score = dictGetEntryVal(de);
-                    unsigned char eledigest[20];
+                if (o->encoding == REDIS_ENCODING_ZIPLIST) {
+                    unsigned char *zl = o->ptr;
+                    unsigned char *eptr, *sptr;
+                    unsigned char *vstr;
+                    unsigned int vlen;
+                    long long vll;
+                    double score;
 
-                    snprintf(buf,sizeof(buf),"%.17g",*score);
-                    memset(eledigest,0,20);
-                    mixObjectDigest(eledigest,eleobj);
-                    mixDigest(eledigest,buf,strlen(buf));
-                    xorDigest(digest,eledigest,20);
+                    eptr = ziplistIndex(zl,0);
+                    redisAssert(eptr != NULL);
+                    sptr = ziplistNext(zl,eptr);
+                    redisAssert(sptr != NULL);
+
+                    while (eptr != NULL) {
+                        redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
+                        score = zzlGetScore(sptr);
+
+                        memset(eledigest,0,20);
+                        if (vstr != NULL) {
+                            mixDigest(eledigest,vstr,vlen);
+                        } else {
+                            ll2string(buf,sizeof(buf),vll);
+                            mixDigest(eledigest,buf,strlen(buf));
+                        }
+
+                        snprintf(buf,sizeof(buf),"%.17g",score);
+                        mixDigest(eledigest,buf,strlen(buf));
+                        xorDigest(digest,eledigest,20);
+                        zzlNext(zl,&eptr,&sptr);
+                    }
+                } else if (o->encoding == REDIS_ENCODING_RAW) {
+                    zset *zs = o->ptr;
+                    dictIterator *di = dictGetIterator(zs->dict);
+                    dictEntry *de;
+
+                    while((de = dictNext(di)) != NULL) {
+                        robj *eleobj = dictGetEntryKey(de);
+                        double *score = dictGetEntryVal(de);
+
+                        snprintf(buf,sizeof(buf),"%.17g",*score);
+                        memset(eledigest,0,20);
+                        mixObjectDigest(eledigest,eleobj);
+                        mixDigest(eledigest,buf,strlen(buf));
+                        xorDigest(digest,eledigest,20);
+                    }
+                    dictReleaseIterator(di);
+                } else {
+                    redisPanic("Unknown sorted set encoding");
                 }
-                dictReleaseIterator(di);
             } else if (o->type == REDIS_HASH) {
                 hashTypeIterator *hi;
                 robj *obj;
diff --git a/src/redis.h b/src/redis.h
index 8ed7216d..1d6d49dc 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -800,6 +800,9 @@ zskiplist *zslCreate(void);
 void zslFree(zskiplist *zsl);
 zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
 unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
+double zzlGetScore(unsigned char *sptr);
+void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
+void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
 unsigned int zsetLength(robj *zobj);
 void zsetConvert(robj *zobj, int encoding);
 
diff --git a/src/sort.c b/src/sort.c
index 1cf8932e..1a3fecb6 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -199,6 +199,9 @@ void sortCommand(redisClient *c) {
         j++;
     }
 
+    /* Destructively convert encoded sorted sets for SORT. */
+    if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_RAW);
+
     /* Load the sorting vector with all the objects to sort */
     switch(sortval->type) {
     case REDIS_LIST: vectorlen = listTypeLength(sortval); break;

From 48991620f71485a5fa056736796b620eab1387a9 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Mon, 21 Mar 2011 23:54:35 +0100
Subject: [PATCH 36/41] Remove sorted set when empty after ZREMRANGEBY*

---
 src/t_zset.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/t_zset.c b/src/t_zset.c
index 03a79ce9..f630ff02 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -1012,6 +1012,7 @@ void zremrangebyscoreCommand(redisClient *c) {
 
     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_RAW) {
         zset *zs = zobj->ptr;
         deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
@@ -1057,6 +1058,7 @@ void zremrangebyrankCommand(redisClient *c) {
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
         /* Correct for 1-based rank. */
         zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
+        if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
     } else if (zobj->encoding == REDIS_ENCODING_RAW) {
         zset *zs = zobj->ptr;
 

From 04a10b1a6d372bd7a1105150194881eccbfd5620 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Tue, 22 Mar 2011 09:28:40 +0100
Subject: [PATCH 37/41] Test that sorted sets are removed when empty

---
 tests/unit/type/zset.tcl | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 31604412..6e32a4b8 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -53,6 +53,18 @@ start_server {tags {"zset"}} {
             assert_equal 0 [r zcard zdoesntexist]
         }
 
+        test "ZREM removes key after last element is removed" {
+            r del ztmp
+            r zadd ztmp 10 x
+            r zadd ztmp 20 y
+
+            assert_equal 1 [r exists ztmp]
+            assert_equal 0 [r zrem ztmp z]
+            assert_equal 1 [r zrem ztmp y]
+            assert_equal 1 [r zrem ztmp x]
+            assert_equal 0 [r exists ztmp]
+        }
+
         test "ZRANGE basics - $encoding" {
             r del ztmp
             r zadd ztmp 1 a
@@ -244,6 +256,7 @@ start_server {tags {"zset"}} {
         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
             }
 
@@ -290,6 +303,10 @@ start_server {tags {"zset"}} {
             # 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" {
@@ -301,6 +318,7 @@ start_server {tags {"zset"}} {
         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
             }
 
@@ -323,6 +341,10 @@ start_server {tags {"zset"}} {
             # 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" {

From 4cc4d1648b3b4c01bf7568694a88e2ef3f70b2bf Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 6 Apr 2011 16:15:15 +0200
Subject: [PATCH 38/41] Typo

---
 src/rdb.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/rdb.c b/src/rdb.c
index 27390b9c..d186a9c5 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -327,7 +327,7 @@ int rdbSaveObject(FILE *fp, robj *o) {
             }
             dictReleaseIterator(di);
         } else {
-            redisPanic("Unknown sorted set enoding");
+            redisPanic("Unknown sorted set encoding");
         }
     } else if (o->type == REDIS_HASH) {
         /* Save a hash value */

From 100ed062c0e3fa7d1a369de083aee619e27c8b2b Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 6 Apr 2011 16:17:07 +0200
Subject: [PATCH 39/41] Test for ENCODING_SKIPLIST instead of ENCODING_RAW

---
 src/aof.c                |  2 +-
 src/debug.c              |  2 +-
 src/object.c             |  2 +-
 src/rdb.c                |  2 +-
 src/sort.c               |  2 +-
 src/t_zset.c             | 38 +++++++++++++++++++-------------------
 tests/unit/type/zset.tcl |  8 ++++----
 7 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/src/aof.c b/src/aof.c
index cd7c48d3..5d75c374 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -460,7 +460,7 @@ int rewriteAppendOnlyFile(char *filename) {
                         }
                         zzlNext(zl,&eptr,&sptr);
                     }
-                } else if (o->encoding == REDIS_ENCODING_RAW) {
+                } else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
                     zset *zs = o->ptr;
                     dictIterator *di = dictGetIterator(zs->dict);
                     dictEntry *de;
diff --git a/src/debug.c b/src/debug.c
index 940a0380..080e2b2e 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -159,7 +159,7 @@ void computeDatasetDigest(unsigned char *final) {
                         xorDigest(digest,eledigest,20);
                         zzlNext(zl,&eptr,&sptr);
                     }
-                } else if (o->encoding == REDIS_ENCODING_RAW) {
+                } else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
                     zset *zs = o->ptr;
                     dictIterator *di = dictGetIterator(zs->dict);
                     dictEntry *de;
diff --git a/src/object.c b/src/object.c
index 4bf1df01..c7c90c54 100644
--- a/src/object.c
+++ b/src/object.c
@@ -144,7 +144,7 @@ void freeSetObject(robj *o) {
 void freeZsetObject(robj *o) {
     zset *zs;
     switch (o->encoding) {
-    case REDIS_ENCODING_RAW:
+    case REDIS_ENCODING_SKIPLIST:
         zs = o->ptr;
         dictRelease(zs->dict);
         zslFree(zs->zsl);
diff --git a/src/rdb.c b/src/rdb.c
index d186a9c5..353620f2 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -308,7 +308,7 @@ int rdbSaveObject(FILE *fp, robj *o) {
 
             if ((n = rdbSaveRawString(fp,o->ptr,l)) == -1) return -1;
             nwritten += n;
-        } else if (o->encoding == REDIS_ENCODING_RAW) {
+        } else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
             zset *zs = o->ptr;
             dictIterator *di = dictGetIterator(zs->dict);
             dictEntry *de;
diff --git a/src/sort.c b/src/sort.c
index 1a3fecb6..ff275c95 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -200,7 +200,7 @@ void sortCommand(redisClient *c) {
     }
 
     /* Destructively convert encoded sorted sets for SORT. */
-    if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_RAW);
+    if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);
 
     /* Load the sorting vector with all the objects to sort */
     switch(sortval->type) {
diff --git a/src/t_zset.c b/src/t_zset.c
index f630ff02..929714ca 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -721,7 +721,7 @@ unsigned int zsetLength(robj *zobj) {
     int length = -1;
     if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
         length = zzlLength(zobj->ptr);
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         length = ((zset*)zobj->ptr)->zsl->length;
     } else {
         redisPanic("Unknown sorted set encoding");
@@ -743,7 +743,7 @@ void zsetConvert(robj *zobj, int encoding) {
         unsigned int vlen;
         long long vlong;
 
-        if (encoding != REDIS_ENCODING_RAW)
+        if (encoding != REDIS_ENCODING_SKIPLIST)
             redisPanic("Unknown target encoding");
 
         zs = zmalloc(sizeof(*zs));
@@ -772,8 +772,8 @@ void zsetConvert(robj *zobj, int encoding) {
 
         zfree(zobj->ptr);
         zobj->ptr = zs;
-        zobj->encoding = REDIS_ENCODING_RAW;
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+        zobj->encoding = REDIS_ENCODING_SKIPLIST;
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         unsigned char *zl = ziplistNew();
 
         if (encoding != REDIS_ENCODING_ZIPLIST)
@@ -872,9 +872,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
              * 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_RAW);
+                zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
             if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
-                zsetConvert(zobj,REDIS_ENCODING_RAW);
+                zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
 
             signalModifiedKey(c->db,key);
             server.dirty++;
@@ -884,7 +884,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
             else /* ZADD */
                 addReply(c,shared.cone);
         }
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
         zskiplistNode *znode;
         dictEntry *de;
@@ -967,7 +967,7 @@ void zremCommand(redisClient *c) {
             addReply(c,shared.czero);
             return;
         }
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
         dictEntry *de;
         double score;
@@ -1013,7 +1013,7 @@ void zremrangebyscoreCommand(redisClient *c) {
     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_RAW) {
+    } 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);
@@ -1059,7 +1059,7 @@ void zremrangebyrankCommand(redisClient *c) {
         /* Correct for 1-based rank. */
         zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
         if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
 
         /* Correct for 1-based rank. */
@@ -1159,7 +1159,7 @@ void zuiInitIterator(zsetopsrc *op) {
                 it->zl.sptr = ziplistNext(it->zl.zl,it->zl.eptr);
                 redisAssert(it->zl.sptr != NULL);
             }
-        } else if (op->encoding == REDIS_ENCODING_RAW) {
+        } 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 {
@@ -1187,7 +1187,7 @@ void zuiClearIterator(zsetopsrc *op) {
         iterzset *it = &op->iter.zset;
         if (op->encoding == REDIS_ENCODING_ZIPLIST) {
             REDIS_NOTUSED(it); /* skip */
-        } else if (op->encoding == REDIS_ENCODING_RAW) {
+        } else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
             REDIS_NOTUSED(it); /* skip */
         } else {
             redisPanic("Unknown sorted set encoding");
@@ -1214,7 +1214,7 @@ int zuiLength(zsetopsrc *op) {
         iterzset *it = &op->iter.zset;
         if (op->encoding == REDIS_ENCODING_ZIPLIST) {
             return zzlLength(it->zl.zl);
-        } else if (op->encoding == REDIS_ENCODING_RAW) {
+        } else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
             return it->sl.zs->zsl->length;
         } else {
             redisPanic("Unknown sorted set encoding");
@@ -1267,7 +1267,7 @@ int zuiNext(zsetopsrc *op, zsetopval *val) {
 
             /* Move to next element. */
             zzlNext(it->zl.zl,&it->zl.eptr,&it->zl.sptr);
-        } else if (op->encoding == REDIS_ENCODING_RAW) {
+        } else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
             if (it->sl.node == NULL)
                 return 0;
             val->ele = it->sl.node->obj;
@@ -1379,7 +1379,7 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
             } else {
                 return 0;
             }
-        } else if (op->encoding == REDIS_ENCODING_RAW) {
+        } else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
             dictEntry *de;
             if ((de = dictFind(it->sl.zs->dict,val->ele)) != NULL) {
                 *score = *(double*)dictGetEntryVal(de);
@@ -1692,7 +1692,7 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
                 zzlNext(zl,&eptr,&sptr);
         }
 
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
         zskiplist *zsl = zs->zsl;
         zskiplistNode *ln;
@@ -1849,7 +1849,7 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
             else
                 zzlNext(zl,&eptr,&sptr);
         }
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
         zskiplist *zsl = zs->zsl;
         zskiplistNode *ln;
@@ -1943,7 +1943,7 @@ void zscoreCommand(redisClient *c) {
             addReplyDouble(c,score);
         else
             addReply(c,shared.nullbulk);
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
         dictEntry *de;
 
@@ -1997,7 +1997,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
         } else {
             addReply(c,shared.nullbulk);
         }
-    } else if (zobj->encoding == REDIS_ENCODING_RAW) {
+    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
         zset *zs = zobj->ptr;
         zskiplist *zsl = zs->zsl;
         dictEntry *de;
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 6e32a4b8..4fa7af1b 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -10,7 +10,7 @@ start_server {tags {"zset"}} {
         if {$encoding == "ziplist"} {
             r config set zset-max-ziplist-entries 128
             r config set zset-max-ziplist-value 64
-        } elseif {$encoding == "raw"} {
+        } elseif {$encoding == "skiplist"} {
             r config set zset-max-ziplist-entries 0
             r config set zset-max-ziplist-value 0
         } else {
@@ -458,7 +458,7 @@ start_server {tags {"zset"}} {
     }
 
     basics ziplist
-    basics raw
+    basics skiplist
 
     proc stressers {encoding} {
         if {$encoding == "ziplist"} {
@@ -466,7 +466,7 @@ start_server {tags {"zset"}} {
             r config set zset-max-ziplist-entries 256
             r config set zset-max-ziplist-value 64
             set elements 128
-        } elseif {$encoding == "raw"} {
+        } elseif {$encoding == "skiplist"} {
             r config set zset-max-ziplist-entries 0
             r config set zset-max-ziplist-value 0
             set elements 1000
@@ -684,6 +684,6 @@ start_server {tags {"zset"}} {
 
     tags {"slow"} {
         stressers ziplist
-        stressers raw
+        stressers skiplist
     }
 }

From d4d3a70da2c9be4c5aa67a0be735568dbe436568 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 6 Apr 2011 16:38:29 +0200
Subject: [PATCH 40/41] Update target encoding for sorted set from rdb

---
 src/rdb.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/rdb.c b/src/rdb.c
index 353620f2..2557f5b8 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -875,10 +875,10 @@ robj *rdbLoadObject(int type, FILE *fp) {
                 o->type = REDIS_ZSET;
                 o->encoding = REDIS_ENCODING_ZIPLIST;
                 if (zsetLength(o) > server.zset_max_ziplist_entries)
-                    zsetConvert(o,REDIS_ENCODING_RAW);
+                    zsetConvert(o,REDIS_ENCODING_SKIPLIST);
                 break;
             default:
-                redisPanic("Unknown enoding");
+                redisPanic("Unknown encoding");
                 break;
         }
     } else {

From 02e600653216cebc0746bfbb86b353667a843591 Mon Sep 17 00:00:00 2001
From: Pieter Noordhuis <pcnoordhuis@gmail.com>
Date: Wed, 6 Apr 2011 16:39:18 +0200
Subject: [PATCH 41/41] Explicitly zero zval since it is stored on the stack

---
 src/t_zset.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/t_zset.c b/src/t_zset.c
index 929714ca..7ce60349 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -1515,6 +1515,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
 
     dstobj = createZsetObject();
     dstzset = dstobj->ptr;
+    memset(&zval, 0, sizeof(zval));
 
     if (op == REDIS_OP_INTER) {
         /* Skip everything if the smallest input is empty. */