]> git.saurik.com Git - redis.git/blobdiff - redis.c
multi bulk input protocol fixed
[redis.git] / redis.c
diff --git a/redis.c b/redis.c
index 607c827a4cb63d48ff1b342ef7949d6db60d4486..74986528c3573e690dfd158a852d506794ea5249 100644 (file)
--- a/redis.c
+++ b/redis.c
@@ -27,7 +27,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#define REDIS_VERSION "0.900"
+#define REDIS_VERSION "1.001"
 
 #include "fmacros.h"
 #include "config.h"
 #define REDIS_SET 2
 #define REDIS_HASH 3
 
+/* Objects encoding */
+#define REDIS_ENCODING_RAW 0    /* Raw representation */
+#define REDIS_ENCODING_INT 1    /* Encoded as integer */
+
 /* Object types only used for dumping to disk */
 #define REDIS_EXPIRETIME 253
 #define REDIS_SELECTDB 254
 /* A redis object, that is a type able to hold a string / list / set */
 typedef struct redisObject {
     void *ptr;
-    int type;
+    unsigned char type;
+    unsigned char encoding;
+    unsigned char notused[2];
     int refcount;
 } robj;
 
@@ -200,9 +206,10 @@ typedef struct redisClient {
     redisDb *db;
     int dictid;
     sds querybuf;
-    robj **argv;
-    int argc;
+    robj **argv, **mbargv;
+    int argc, mbargc;
     int bulklen;            /* bulk read len. -1 if not in bulk read mode */
+    int multibulk;          /* multi bulk command format active */
     list *reply;
     int sentlen;
     time_t lastinteraction; /* time of the last interaction, used for timeout */
@@ -263,7 +270,7 @@ struct redisServer {
     redisClient *master;    /* client that is master for this slave */
     int replstate;
     unsigned int maxclients;
-    unsigned int maxmemory;
+    unsigned long maxmemory;
     /* Sort parameters - qsort_r() is only available under BSD so we
      * have to take this state global, in order to pass it to sortCompare() */
     int sort_desc;
@@ -323,6 +330,8 @@ static robj *createStringObject(char *ptr, size_t len);
 static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc);
 static int syncWithMaster(void);
 static robj *tryObjectSharing(robj *o);
+static int tryObjectEncoding(robj *o);
+static robj *getDecodedObject(const robj *o);
 static int removeExpire(redisDb *db, robj *key);
 static int expireIfNeeded(redisDb *db, robj *key);
 static int deleteIfVolatile(redisDb *db, robj *key);
@@ -334,6 +343,8 @@ static void freeMemoryIfNeeded(void);
 static int processCommand(redisClient *c);
 static void setupSigSegvAction(void);
 static void rdbRemoveTempFile(pid_t childpid);
+static size_t stringObjectLen(robj *o);
+static void processInputBuffer(redisClient *c);
 
 static void authCommand(redisClient *c);
 static void pingCommand(redisClient *c);
@@ -389,10 +400,13 @@ static void infoCommand(redisClient *c);
 static void mgetCommand(redisClient *c);
 static void monitorCommand(redisClient *c);
 static void expireCommand(redisClient *c);
-static void getSetCommand(redisClient *c);
+static void getsetCommand(redisClient *c);
 static void ttlCommand(redisClient *c);
 static void slaveofCommand(redisClient *c);
 static void debugCommand(redisClient *c);
+static void msetCommand(redisClient *c);
+static void msetnxCommand(redisClient *c);
+
 /*================================= Globals ================================= */
 
 /* Global vars */
@@ -431,7 +445,9 @@ static struct redisCommand cmdTable[] = {
     {"smembers",sinterCommand,2,REDIS_CMD_INLINE},
     {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
     {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
-    {"getset",getSetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
+    {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
+    {"mset",msetCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
+    {"msetnx",msetnxCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
     {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE},
     {"select",selectCommand,2,REDIS_CMD_INLINE},
     {"move",moveCommand,3,REDIS_CMD_INLINE},
@@ -634,32 +650,68 @@ static void dictRedisObjectDestructor(void *privdata, void *val)
     decrRefCount(val);
 }
 
-static int dictSdsKeyCompare(void *privdata, const void *key1,
+static int dictObjKeyCompare(void *privdata, const void *key1,
         const void *key2)
 {
     const robj *o1 = key1, *o2 = key2;
     return sdsDictKeyCompare(privdata,o1->ptr,o2->ptr);
 }
 
-static unsigned int dictSdsHash(const void *key) {
+static unsigned int dictObjHash(const void *key) {
     const robj *o = key;
     return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr));
 }
 
+static int dictEncObjKeyCompare(void *privdata, const void *key1,
+        const void *key2)
+{
+    const robj *o1 = key1, *o2 = key2;
+
+    if (o1->encoding == REDIS_ENCODING_RAW &&
+        o2->encoding == REDIS_ENCODING_RAW)
+        return sdsDictKeyCompare(privdata,o1->ptr,o2->ptr);
+    else {
+        robj *dec1, *dec2;
+        int cmp;
+
+        dec1 = o1->encoding != REDIS_ENCODING_RAW ?
+            getDecodedObject(o1) : (robj*)o1;
+        dec2 = o2->encoding != REDIS_ENCODING_RAW ?
+            getDecodedObject(o2) : (robj*)o2;
+        cmp = sdsDictKeyCompare(privdata,dec1->ptr,dec2->ptr);
+        if (dec1 != o1) decrRefCount(dec1);
+        if (dec2 != o2) decrRefCount(dec2);
+        return cmp;
+    }
+}
+
+static unsigned int dictEncObjHash(const void *key) {
+    const robj *o = key;
+
+    if (o->encoding == REDIS_ENCODING_RAW)
+        return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr));
+    else {
+        robj *dec = getDecodedObject(o);
+        unsigned int hash = dictGenHashFunction(dec->ptr, sdslen((sds)dec->ptr));
+        decrRefCount(dec);
+        return hash;
+    }
+}
+
 static dictType setDictType = {
-    dictSdsHash,               /* hash function */
+    dictEncObjHash,            /* hash function */
     NULL,                      /* key dup */
     NULL,                      /* val dup */
-    dictSdsKeyCompare,         /* key compare */
+    dictEncObjKeyCompare,      /* key compare */
     dictRedisObjectDestructor, /* key destructor */
     NULL                       /* val destructor */
 };
 
 static dictType hashDictType = {
-    dictSdsHash,                /* hash function */
+    dictObjHash,                /* hash function */
     NULL,                       /* key dup */
     NULL,                       /* val dup */
-    dictSdsKeyCompare,          /* key compare */
+    dictObjKeyCompare,          /* key compare */
     dictRedisObjectDestructor,  /* key destructor */
     dictRedisObjectDestructor   /* val destructor */
 };
@@ -1072,7 +1124,7 @@ static void loadServerConfig(char *filename) {
         } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
             server.maxclients = atoi(argv[1]);
         } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
-            server.maxmemory = atoi(argv[1]);
+            server.maxmemory = strtoll(argv[1], NULL, 10);
         } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
             server.masterhost = sdsnew(argv[1]);
             server.masterport = atoi(argv[2]);
@@ -1124,7 +1176,10 @@ static void freeClientArgv(redisClient *c) {
 
     for (j = 0; j < c->argc; j++)
         decrRefCount(c->argv[j]);
+    for (j = 0; j < c->mbargc; j++)
+        decrRefCount(c->mbargv[j]);
     c->argc = 0;
+    c->mbargc = 0;
 }
 
 static void freeClient(redisClient *c) {
@@ -1152,6 +1207,7 @@ static void freeClient(redisClient *c) {
         server.replstate = REDIS_REPL_CONNECT;
     }
     zfree(c->argv);
+    zfree(c->mbargv);
     zfree(c);
 }
 
@@ -1254,6 +1310,7 @@ static struct redisCommand *lookupCommand(char *name) {
 static void resetClient(redisClient *c) {
     freeClientArgv(c);
     c->bulklen = -1;
+    c->multibulk = 0;
 }
 
 /* If this function gets called we already read a whole
@@ -1271,6 +1328,82 @@ static int processCommand(redisClient *c) {
     /* Free some memory if needed (maxmemory setting) */
     if (server.maxmemory) freeMemoryIfNeeded();
 
+    /* Handle the multi bulk command type. This is an alternative protocol
+     * supported by Redis in order to receive commands that are composed of
+     * multiple binary-safe "bulk" arguments. The latency of processing is
+     * a bit higher but this allows things like multi-sets, so if this
+     * protocol is used only for MSET and similar commands this is a big win. */
+    if (c->multibulk == 0 && c->argc == 1 && ((char*)(c->argv[0]->ptr))[0] == '*') {
+        c->multibulk = atoi(((char*)c->argv[0]->ptr)+1);
+        if (c->multibulk <= 0) {
+            resetClient(c);
+            return 1;
+        } else {
+            decrRefCount(c->argv[c->argc-1]);
+            c->argc--;
+            return 1;
+        }
+    } else if (c->multibulk) {
+        if (c->bulklen == -1) {
+            if (((char*)c->argv[0]->ptr)[0] != '$') {
+                addReplySds(c,sdsnew("-ERR multi bulk protocol error\r\n"));
+                resetClient(c);
+                return 1;
+            } else {
+                int bulklen = atoi(((char*)c->argv[0]->ptr)+1);
+                decrRefCount(c->argv[0]);
+                if (bulklen < 0 || bulklen > 1024*1024*1024) {
+                    c->argc--;
+                    addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
+                    resetClient(c);
+                    return 1;
+                }
+                c->argc--;
+                c->bulklen = bulklen+2; /* add two bytes for CR+LF */
+                /*
+                if (sdslen(c->querybuf) > 0)
+                    processInputBuffer(c);
+                    */
+                return 1;
+            }
+        } else {
+            c->mbargv = zrealloc(c->mbargv,(sizeof(robj*))*(c->mbargc+1));
+            c->mbargv[c->mbargc] = c->argv[0];
+            c->mbargc++;
+            c->argc--;
+            c->multibulk--;
+            if (c->multibulk == 0) {
+                robj **auxargv;
+                int auxargc;
+
+                /* Here we need to swap the multi-bulk argc/argv with the
+                 * normal argc/argv of the client structure. */
+                auxargv = c->argv;
+                c->argv = c->mbargv;
+                c->mbargv = auxargv;
+
+                auxargc = c->argc;
+                c->argc = c->mbargc;
+                c->mbargc = auxargc;
+
+                /* We need to set bulklen to something different than -1
+                 * in order for the code below to process the command without
+                 * to try to read the last argument of a bulk command as
+                 * a special argument. */
+                c->bulklen = 0;
+                /* continue below and process the command */
+            } else {
+                c->bulklen = -1;
+                /*
+                if (sdslen(c->querybuf) > 0)
+                    processInputBuffer(c);
+                    */
+                return 1;
+            }
+        }
+    }
+    /* -- end of multi bulk commands processing -- */
+
     /* The QUIT command is handled as a special case. Normal command
      * procs are unable to close the client connection safely */
     if (!strcasecmp(c->argv[0]->ptr,"quit")) {
@@ -1319,6 +1452,10 @@ static int processCommand(redisClient *c) {
         for(j = 1; j < c->argc; j++)
             c->argv[j] = tryObjectSharing(c->argv[j]);
     }
+    /* Let's try to encode the bulk object to save space. */
+    if (cmd->flags & REDIS_CMD_BULK)
+        tryObjectEncoding(c->argv[c->argc-1]);
+
     /* Check if the user is authenticated */
     if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
         addReplySds(c,sdsnew("-ERR operation not permitted\r\n"));
@@ -1364,7 +1501,8 @@ static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int di
             robj *lenobj;
 
             lenobj = createObject(REDIS_STRING,
-                sdscatprintf(sdsempty(),"%d\r\n",sdslen(argv[j]->ptr)));
+                sdscatprintf(sdsempty(),"%d\r\n",
+                    stringObjectLen(argv[j])));
             lenobj->refcount = 0;
             outv[outc++] = lenobj;
         }
@@ -1413,34 +1551,7 @@ static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int di
     if (outv != static_outv) zfree(outv);
 }
 
-static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
-    redisClient *c = (redisClient*) privdata;
-    char buf[REDIS_IOBUF_LEN];
-    int nread;
-    REDIS_NOTUSED(el);
-    REDIS_NOTUSED(mask);
-
-    nread = read(fd, buf, REDIS_IOBUF_LEN);
-    if (nread == -1) {
-        if (errno == EAGAIN) {
-            nread = 0;
-        } else {
-            redisLog(REDIS_DEBUG, "Reading from client: %s",strerror(errno));
-            freeClient(c);
-            return;
-        }
-    } else if (nread == 0) {
-        redisLog(REDIS_DEBUG, "Client closed connection");
-        freeClient(c);
-        return;
-    }
-    if (nread) {
-        c->querybuf = sdscatlen(c->querybuf, buf, nread);
-        c->lastinteraction = time(NULL);
-    } else {
-        return;
-    }
-
+static void processInputBuffer(redisClient *c) {
 again:
     if (c->bulklen == -1) {
         /* Read the first line of the query */
@@ -1507,12 +1618,45 @@ again:
             c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2);
             c->argc++;
             c->querybuf = sdsrange(c->querybuf,c->bulklen,-1);
-            processCommand(c);
+            /* Process the command. If the client is still valid after
+             * the processing and there is more data in the buffer
+             * try to parse it. */
+            if (processCommand(c) && sdslen(c->querybuf)) goto again;
             return;
         }
     }
 }
 
+static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
+    redisClient *c = (redisClient*) privdata;
+    char buf[REDIS_IOBUF_LEN];
+    int nread;
+    REDIS_NOTUSED(el);
+    REDIS_NOTUSED(mask);
+
+    nread = read(fd, buf, REDIS_IOBUF_LEN);
+    if (nread == -1) {
+        if (errno == EAGAIN) {
+            nread = 0;
+        } else {
+            redisLog(REDIS_DEBUG, "Reading from client: %s",strerror(errno));
+            freeClient(c);
+            return;
+        }
+    } else if (nread == 0) {
+        redisLog(REDIS_DEBUG, "Client closed connection");
+        freeClient(c);
+        return;
+    }
+    if (nread) {
+        c->querybuf = sdscatlen(c->querybuf, buf, nread);
+        c->lastinteraction = time(NULL);
+    } else {
+        return;
+    }
+    processInputBuffer(c);
+}
+
 static int selectDb(redisClient *c, int id) {
     if (id < 0 || id >= server.dbnum)
         return REDIS_ERR;
@@ -1537,6 +1681,9 @@ static redisClient *createClient(int fd) {
     c->argc = 0;
     c->argv = NULL;
     c->bulklen = -1;
+    c->multibulk = 0;
+    c->mbargc = 0;
+    c->mbargv = NULL;
     c->sentlen = 0;
     c->flags = 0;
     c->lastinteraction = time(NULL);
@@ -1560,8 +1707,12 @@ static void addReply(redisClient *c, robj *obj) {
          c->replstate == REDIS_REPL_ONLINE) &&
         aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
         sendReplyToClient, c, NULL) == AE_ERR) return;
+    if (obj->encoding != REDIS_ENCODING_RAW) {
+        obj = getDecodedObject(obj);
+    } else {
+        incrRefCount(obj);
+    }
     if (!listAddNodeTail(c->reply,obj)) oom("listAddNodeTail");
-    incrRefCount(obj);
 }
 
 static void addReplySds(redisClient *c, sds s) {
@@ -1570,6 +1721,26 @@ static void addReplySds(redisClient *c, sds s) {
     decrRefCount(o);
 }
 
+static void addReplyBulkLen(redisClient *c, robj *obj) {
+    size_t len;
+
+    if (obj->encoding == REDIS_ENCODING_RAW) {
+        len = sdslen(obj->ptr);
+    } else {
+        long n = (long)obj->ptr;
+
+        len = 1;
+        if (n < 0) {
+            len++;
+            n = -n;
+        }
+        while((n = n/10) != 0) {
+            len++;
+        }
+    }
+    addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",len));
+}
+
 static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
     int cport, cfd;
     char cip[128];
@@ -1618,6 +1789,7 @@ static robj *createObject(int type, void *ptr) {
     }
     if (!o) oom("createObject");
     o->type = type;
+    o->encoding = REDIS_ENCODING_RAW;
     o->ptr = ptr;
     o->refcount = 1;
     return o;
@@ -1642,7 +1814,9 @@ static robj *createSetObject(void) {
 }
 
 static void freeStringObject(robj *o) {
-    sdsfree(o->ptr);
+    if (o->encoding == REDIS_ENCODING_RAW) {
+        sdsfree(o->ptr);
+    }
 }
 
 static void freeListObject(robj *o) {
@@ -1686,6 +1860,36 @@ static void decrRefCount(void *obj) {
     }
 }
 
+static robj *lookupKey(redisDb *db, robj *key) {
+    dictEntry *de = dictFind(db->dict,key);
+    return de ? dictGetEntryVal(de) : NULL;
+}
+
+static robj *lookupKeyRead(redisDb *db, robj *key) {
+    expireIfNeeded(db,key);
+    return lookupKey(db,key);
+}
+
+static robj *lookupKeyWrite(redisDb *db, robj *key) {
+    deleteIfVolatile(db,key);
+    return lookupKey(db,key);
+}
+
+static int deleteKey(redisDb *db, robj *key) {
+    int retval;
+
+    /* We need to protect key from destruction: after the first dictDelete()
+     * it may happen that 'key' is no longer valid if we don't increment
+     * it's count. This may happen when we get the object reference directly
+     * from the hash table with dictRandomKey() or dict iterators */
+    incrRefCount(key);
+    if (dictSize(db->expires)) dictDelete(db->expires,key);
+    retval = dictDelete(db->dict,key);
+    decrRefCount(key);
+
+    return retval == DICT_OK;
+}
+
 /* Try to share an object against the shared objects pool */
 static robj *tryObjectSharing(robj *o) {
     struct dictEntry *de;
@@ -1731,34 +1935,98 @@ static robj *tryObjectSharing(robj *o) {
     }
 }
 
-static robj *lookupKey(redisDb *db, robj *key) {
-    dictEntry *de = dictFind(db->dict,key);
-    return de ? dictGetEntryVal(de) : NULL;
+/* Check if the nul-terminated string 's' can be represented by a long
+ * (that is, is a number that fits into long without any other space or
+ * character before or after the digits).
+ *
+ * If so, the function returns REDIS_OK and *longval is set to the value
+ * of the number. Otherwise REDIS_ERR is returned */
+static int isStringRepresentableAsLong(char *s, long *longval) {
+    char buf[32], *endptr;
+    long value;
+    int slen;
+    
+    value = strtol(s, &endptr, 10);
+    if (endptr[0] != '\0') return REDIS_ERR;
+    slen = snprintf(buf,32,"%ld",value);
+
+    /* If the number converted back into a string is not identical
+     * then it's not possible to encode the string as integer */
+    if (strlen(buf) != (unsigned)slen || memcmp(buf,s,slen)) return REDIS_ERR;
+    if (longval) *longval = value;
+    return REDIS_OK;
 }
 
-static robj *lookupKeyRead(redisDb *db, robj *key) {
-    expireIfNeeded(db,key);
-    return lookupKey(db,key);
+/* Try to encode a string object in order to save space */
+static int tryObjectEncoding(robj *o) {
+    long value;
+    sds s = o->ptr;
+
+    if (o->encoding != REDIS_ENCODING_RAW)
+        return REDIS_ERR; /* Already encoded */
+
+    /* It's not save to encode shared objects: shared objects can be shared
+     * everywhere in the "object space" of Redis. Encoded objects can only
+     * appear as "values" (and not, for instance, as keys) */
+     if (o->refcount > 1) return REDIS_ERR;
+
+    /* Currently we try to encode only strings */
+    assert(o->type == REDIS_STRING);
+
+    /* Check if we can represent this string as a long integer */
+    if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return REDIS_ERR;
+
+    /* Ok, this object can be encoded */
+    o->encoding = REDIS_ENCODING_INT;
+    sdsfree(o->ptr);
+    o->ptr = (void*) value;
+    return REDIS_OK;
 }
 
-static robj *lookupKeyWrite(redisDb *db, robj *key) {
-    deleteIfVolatile(db,key);
-    return lookupKey(db,key);
+/* Get a decoded version of an encoded object (returned as a new object) */
+static robj *getDecodedObject(const robj *o) {
+    robj *dec;
+    
+    assert(o->encoding != REDIS_ENCODING_RAW);
+    if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
+        char buf[32];
+
+        snprintf(buf,32,"%ld",(long)o->ptr);
+        dec = createStringObject(buf,strlen(buf));
+        return dec;
+    } else {
+        assert(1 != 1);
+    }
 }
 
-static int deleteKey(redisDb *db, robj *key) {
-    int retval;
+static int compareStringObjects(robj *a, robj *b) {
+    assert(a->type == REDIS_STRING && b->type == REDIS_STRING);
 
-    /* We need to protect key from destruction: after the first dictDelete()
-     * it may happen that 'key' is no longer valid if we don't increment
-     * it's count. This may happen when we get the object reference directly
-     * from the hash table with dictRandomKey() or dict iterators */
-    incrRefCount(key);
-    if (dictSize(db->expires)) dictDelete(db->expires,key);
-    retval = dictDelete(db->dict,key);
-    decrRefCount(key);
+    if (a->encoding == REDIS_ENCODING_INT && b->encoding == REDIS_ENCODING_INT){
+        return (long)a->ptr - (long)b->ptr;
+    } else {
+        int retval;
 
-    return retval == DICT_OK;
+        incrRefCount(a);
+        incrRefCount(b);
+        if (a->encoding != REDIS_ENCODING_RAW) a = getDecodedObject(a);
+        if (b->encoding != REDIS_ENCODING_RAW) b = getDecodedObject(a);
+        retval = sdscmp(a->ptr,b->ptr);
+        decrRefCount(a);
+        decrRefCount(b);
+        return retval;
+    }
+}
+
+static size_t stringObjectLen(robj *o) {
+    assert(o->type == REDIS_STRING);
+    if (o->encoding == REDIS_ENCODING_RAW) {
+        return sdslen(o->ptr);
+    } else {
+        char buf[32];
+
+        return snprintf(buf,32,"%ld",(long)o->ptr);
+    }
 }
 
 /*============================ DB saving/loading ============================ */
@@ -1865,10 +2133,12 @@ writeerr:
 
 /* Save a string objet as [len][data] on disk. If the object is a string
  * representation of an integer value we try to safe it in a special form */
-static int rdbSaveStringObject(FILE *fp, robj *obj) {
-    size_t len = sdslen(obj->ptr);
+static int rdbSaveStringObjectRaw(FILE *fp, robj *obj) {
+    size_t len;
     int enclen;
 
+    len = sdslen(obj->ptr);
+
     /* Try integer encoding */
     if (len <= 11) {
         unsigned char buf[5];
@@ -1880,7 +2150,7 @@ static int rdbSaveStringObject(FILE *fp, robj *obj) {
 
     /* Try LZF compression - under 20 bytes it's unable to compress even
      * aaaaaaaaaaaaaaaaaa so skip it */
-    if (1 && len > 20) {
+    if (len > 20) {
         int retval;
 
         retval = rdbSaveLzfStringObject(fp,obj);
@@ -1895,6 +2165,21 @@ static int rdbSaveStringObject(FILE *fp, robj *obj) {
     return 0;
 }
 
+/* Like rdbSaveStringObjectRaw() but handle encoded objects */
+static int rdbSaveStringObject(FILE *fp, robj *obj) {
+    int retval;
+    robj *dec;
+
+    if (obj->encoding != REDIS_ENCODING_RAW) {
+        dec = getDecodedObject(obj);
+        retval = rdbSaveStringObjectRaw(fp,dec);
+        decrRefCount(dec);
+        return retval;
+    } else {
+        return rdbSaveStringObjectRaw(fp,obj);
+    }
+}
+
 /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
 static int rdbSave(char *filename) {
     dictIterator *di = NULL;
@@ -2212,6 +2497,7 @@ static int rdbLoad(char *filename) {
         if (type == REDIS_STRING) {
             /* Read string value */
             if ((o = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
+            tryObjectEncoding(o);
         } else if (type == REDIS_LIST || type == REDIS_SET) {
             /* Read list/set value */
             uint32_t listlen;
@@ -2224,6 +2510,7 @@ static int rdbLoad(char *filename) {
                 robj *ele;
 
                 if ((ele = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
+                tryObjectEncoding(ele);
                 if (type == REDIS_LIST) {
                     if (!listAddNodeTail((list*)o->ptr,ele))
                         oom("listAddNodeTail");
@@ -2277,8 +2564,7 @@ static void pingCommand(redisClient *c) {
 }
 
 static void echoCommand(redisClient *c) {
-    addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",
-        (int)sdslen(c->argv[1]->ptr)));
+    addReplyBulkLen(c,c->argv[1]);
     addReply(c,c->argv[1]);
     addReply(c,shared.crlf);
 }
@@ -2323,14 +2609,14 @@ static void getCommand(redisClient *c) {
         if (o->type != REDIS_STRING) {
             addReply(c,shared.wrongtypeerr);
         } else {
-            addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(o->ptr)));
+            addReplyBulkLen(c,o);
             addReply(c,o);
             addReply(c,shared.crlf);
         }
     }
 }
 
-static void getSetCommand(redisClient *c) {
+static void getsetCommand(redisClient *c) {
     getCommand(c);
     if (dictAdd(c->db->dict,c->argv[1],c->argv[2]) == DICT_ERR) {
         dictReplace(c->db->dict,c->argv[1],c->argv[2]);
@@ -2354,7 +2640,7 @@ static void mgetCommand(redisClient *c) {
             if (o->type != REDIS_STRING) {
                 addReply(c,shared.nullbulk);
             } else {
-                addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(o->ptr)));
+                addReplyBulkLen(c,o);
                 addReply(c,o);
                 addReply(c,shared.crlf);
             }
@@ -2376,12 +2662,18 @@ static void incrDecrCommand(redisClient *c, long long incr) {
         } else {
             char *eptr;
 
-            value = strtoll(o->ptr, &eptr, 10);
+            if (o->encoding == REDIS_ENCODING_RAW)
+                value = strtoll(o->ptr, &eptr, 10);
+            else if (o->encoding == REDIS_ENCODING_INT)
+                value = (long)o->ptr;
+            else
+                assert(1 != 1);
         }
     }
 
     value += incr;
     o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
+    tryObjectEncoding(o);
     retval = dictAdd(c->db->dict,c->argv[1],o);
     if (retval == DICT_ERR) {
         dictReplace(c->db->dict,c->argv[1],o);
@@ -2742,7 +3034,7 @@ static void lindexCommand(redisClient *c) {
                 addReply(c,shared.nullbulk);
             } else {
                 robj *ele = listNodeValue(ln);
-                addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr)));
+                addReplyBulkLen(c,ele);
                 addReply(c,ele);
                 addReply(c,shared.crlf);
             }
@@ -2802,7 +3094,7 @@ static void popGenericCommand(redisClient *c, int where) {
                 addReply(c,shared.nullbulk);
             } else {
                 robj *ele = listNodeValue(ln);
-                addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr)));
+                addReplyBulkLen(c,ele);
                 addReply(c,ele);
                 addReply(c,shared.crlf);
                 listDelNode(list,ln);
@@ -2858,7 +3150,7 @@ static void lrangeCommand(redisClient *c) {
             addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen));
             for (j = 0; j < rangelen; j++) {
                 ele = listNodeValue(ln);
-                addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr)));
+                addReplyBulkLen(c,ele);
                 addReply(c,ele);
                 addReply(c,shared.crlf);
                 ln = ln->next;
@@ -2941,7 +3233,7 @@ static void lremCommand(redisClient *c) {
                 robj *ele = listNodeValue(ln);
 
                 next = fromtail ? ln->prev : ln->next;
-                if (sdscmp(ele->ptr,c->argv[3]->ptr) == 0) {
+                if (compareStringObjects(ele,c->argv[3]) == 0) {
                     listDelNode(list,ln);
                     server.dirty++;
                     removed++;
@@ -3090,7 +3382,7 @@ static void spopCommand(redisClient *c) {
         } else {
             robj *ele = dictGetEntryKey(de);
 
-            addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(ele->ptr)));
+            addReplyBulkLen(c,ele);
             addReply(c,ele);
             addReply(c,shared.crlf);
             dictDelete(set->ptr,ele);
@@ -3171,7 +3463,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r
             continue; /* at least one set does not contain the member */
         ele = dictGetEntryKey(de);
         if (!dstkey) {
-            addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(ele->ptr)));
+            addReplyBulkLen(c,ele);
             addReply(c,ele);
             addReply(c,shared.crlf);
             cardinality++;
@@ -3280,8 +3572,7 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu
             robj *ele;
 
             ele = dictGetEntryKey(de);
-            addReplySds(c,sdscatprintf(sdsempty(),
-                    "$%d\r\n",sdslen(ele->ptr)));
+            addReplyBulkLen(c,ele);
             addReply(c,ele);
             addReply(c,shared.crlf);
         }
@@ -3357,6 +3648,12 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
         char buf[REDIS_SORTKEY_MAX+1];
     } keyname;
 
+    if (subst->encoding == REDIS_ENCODING_RAW)
+        incrRefCount(subst);
+    else {
+        subst = getDecodedObject(subst);
+    }
+
     spat = pattern->ptr;
     ssub = subst->ptr;
     if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL;
@@ -3376,6 +3673,8 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
     keyobj.type = REDIS_STRING;
     keyobj.ptr = ((char*)&keyname)+(sizeof(long)*2);
 
+    decrRefCount(subst);
+
     /* printf("lookup '%s' => %p\n", keyname.buf,de); */
     return lookupKeyRead(db,&keyobj);
 }
@@ -3413,7 +3712,20 @@ static int sortCompare(const void *s1, const void *s2) {
             }
         } else {
             /* Compare elements directly */
-            cmp = strcoll(so1->obj->ptr,so2->obj->ptr);
+            if (so1->obj->encoding == REDIS_ENCODING_RAW &&
+                so2->obj->encoding == REDIS_ENCODING_RAW) {
+                cmp = strcoll(so1->obj->ptr,so2->obj->ptr);
+            } else {
+                robj *dec1, *dec2;
+
+                dec1 = so1->obj->encoding == REDIS_ENCODING_RAW ?
+                    so1->obj : getDecodedObject(so1->obj);
+                dec2 = so2->obj->encoding == REDIS_ENCODING_RAW ?
+                    so2->obj : getDecodedObject(so2->obj);
+                cmp = strcoll(dec1->ptr,dec2->ptr);
+                if (dec1 != so1->obj) decrRefCount(dec1);
+                if (dec2 != so2->obj) decrRefCount(dec2);
+            }
         }
     }
     return server.sort_desc ? -cmp : cmp;
@@ -3543,13 +3855,33 @@ static void sortCommand(redisClient *c) {
                 byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
                 if (!byval || byval->type != REDIS_STRING) continue;
                 if (alpha) {
-                    vector[j].u.cmpobj = byval;
-                    incrRefCount(byval);
+                    if (byval->encoding == REDIS_ENCODING_RAW) {
+                        vector[j].u.cmpobj = byval;
+                        incrRefCount(byval);
+                    } else {
+                        vector[j].u.cmpobj = getDecodedObject(byval);
+                    }
                 } else {
-                    vector[j].u.score = strtod(byval->ptr,NULL);
+                    if (byval->encoding == REDIS_ENCODING_RAW) {
+                        vector[j].u.score = strtod(byval->ptr,NULL);
+                    } else {
+                        if (byval->encoding == REDIS_ENCODING_INT) {
+                            vector[j].u.score = (long)byval->ptr;
+                        } else
+                            assert(1 != 1);
+                    }
                 }
             } else {
-                if (!alpha) vector[j].u.score = strtod(vector[j].obj->ptr,NULL);
+                if (!alpha) {
+                    if (vector[j].obj->encoding == REDIS_ENCODING_RAW)
+                        vector[j].u.score = strtod(vector[j].obj->ptr,NULL);
+                    else {
+                        if (vector[j].obj->encoding == REDIS_ENCODING_INT)
+                            vector[j].u.score = (long) vector[j].obj->ptr;
+                        else
+                            assert(1 != 1);
+                    }
+                }
             }
         }
     }
@@ -3581,8 +3913,7 @@ static void sortCommand(redisClient *c) {
     for (j = start; j <= end; j++) {
         listNode *ln;
         if (!getop) {
-            addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",
-                sdslen(vector[j].obj->ptr)));
+            addReplyBulkLen(c,vector[j].obj);
             addReply(c,vector[j].obj);
             addReply(c,shared.crlf);
         }
@@ -3596,8 +3927,7 @@ static void sortCommand(redisClient *c) {
                 if (!val || val->type != REDIS_STRING) {
                     addReply(c,shared.nullbulk);
                 } else {
-                    addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",
-                        sdslen(val->ptr)));
+                    addReplyBulkLen(c,val);
                     addReply(c,val);
                     addReply(c,shared.crlf);
                 }
@@ -3624,6 +3954,7 @@ static void infoCommand(redisClient *c) {
     
     info = sdscatprintf(sdsempty(),
         "redis_version:%s\r\n"
+        "arch_bits:%s\r\n"
         "uptime_in_seconds:%d\r\n"
         "uptime_in_days:%d\r\n"
         "connected_clients:%d\r\n"
@@ -3636,6 +3967,7 @@ static void infoCommand(redisClient *c) {
         "total_commands_processed:%lld\r\n"
         "role:%s\r\n"
         ,REDIS_VERSION,
+        (sizeof(long) == 8) ? "64" : "32",
         uptime,
         uptime/(3600*24),
         listLength(server.clients)-listLength(server.slaves),
@@ -3782,6 +4114,42 @@ static void ttlCommand(redisClient *c) {
     addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl));
 }
 
+static void msetGenericCommand(redisClient *c, int nx) {
+    int j;
+
+    if ((c->argc % 2) == 0) {
+        addReplySds(c,sdsnew("-ERR wrong number of arguments\r\n"));
+        return;
+    }
+    /* Handle the NX flag. The MSETNX semantic is to return zero and don't
+     * set nothing at all if at least one already key exists. */
+    if (nx) {
+        for (j = 1; j < c->argc; j += 2) {
+            if (dictFind(c->db->dict,c->argv[j]) != NULL) {
+                addReply(c, shared.czero);
+                return;
+            }
+        }
+    }
+
+    for (j = 1; j < c->argc; j += 2) {
+        dictAdd(c->db->dict,c->argv[j],c->argv[j+1]);
+        incrRefCount(c->argv[j]);
+        incrRefCount(c->argv[j+1]);
+        removeExpire(c->db,c->argv[j]);
+    }
+    server.dirty += (c->argc-1)/2;
+    addReply(c, nx ? shared.cone : shared.ok);
+}
+
+static void msetCommand(redisClient *c) {
+    msetGenericCommand(c,0);
+}
+
+static void msetnxCommand(redisClient *c) {
+    msetGenericCommand(c,1);
+}
+
 /* =============================== Replication  ============================= */
 
 static int syncWrite(int fd, char *ptr, ssize_t size, int timeout) {
@@ -4178,8 +4546,8 @@ static void debugCommand(redisClient *c) {
         key = dictGetEntryKey(de);
         val = dictGetEntryVal(de);
         addReplySds(c,sdscatprintf(sdsempty(),
-            "+Key at:%p refcount:%d, value at:%p refcount:%d\r\n",
-                key, key->refcount, val, val->refcount));
+            "+Key at:%p refcount:%d, value at:%p refcount:%d encoding:%d\r\n",
+                key, key->refcount, val, val->refcount, val->encoding));
     } else {
         addReplySds(c,sdsnew(
             "-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>]\r\n"));
@@ -4188,6 +4556,11 @@ static void debugCommand(redisClient *c) {
 
 #ifdef HAVE_BACKTRACE
 static struct redisFunctionSym symsTable[] = {
+{"compareStringObjects", (unsigned long)compareStringObjects},
+{"isStringRepresentableAsLong", (unsigned long)isStringRepresentableAsLong},
+{"dictEncObjKeyCompare", (unsigned long)dictEncObjKeyCompare},
+{"dictEncObjHash", (unsigned long)dictEncObjHash},
+{"incrDecrCommand", (unsigned long)incrDecrCommand},
 {"freeStringObject", (unsigned long)freeStringObject},
 {"freeListObject", (unsigned long)freeListObject},
 {"freeSetObject", (unsigned long)freeSetObject},
@@ -4195,6 +4568,8 @@ static struct redisFunctionSym symsTable[] = {
 {"createObject", (unsigned long)createObject},
 {"freeClient", (unsigned long)freeClient},
 {"rdbLoad", (unsigned long)rdbLoad},
+{"rdbSaveStringObject", (unsigned long)rdbSaveStringObject},
+{"rdbSaveStringObjectRaw", (unsigned long)rdbSaveStringObjectRaw},
 {"addReply", (unsigned long)addReply},
 {"addReplySds", (unsigned long)addReplySds},
 {"incrRefCount", (unsigned long)incrRefCount},
@@ -4203,6 +4578,8 @@ static struct redisFunctionSym symsTable[] = {
 {"replicationFeedSlaves", (unsigned long)replicationFeedSlaves},
 {"syncWithMaster", (unsigned long)syncWithMaster},
 {"tryObjectSharing", (unsigned long)tryObjectSharing},
+{"tryObjectEncoding", (unsigned long)tryObjectEncoding},
+{"getDecodedObject", (unsigned long)getDecodedObject},
 {"removeExpire", (unsigned long)removeExpire},
 {"expireIfNeeded", (unsigned long)expireIfNeeded},
 {"deleteIfVolatile", (unsigned long)deleteIfVolatile},
@@ -4265,7 +4642,7 @@ static struct redisFunctionSym symsTable[] = {
 {"mgetCommand", (unsigned long)mgetCommand},
 {"monitorCommand", (unsigned long)monitorCommand},
 {"expireCommand", (unsigned long)expireCommand},
-{"getSetCommand", (unsigned long)getSetCommand},
+{"getsetCommand", (unsigned long)getsetCommand},
 {"ttlCommand", (unsigned long)ttlCommand},
 {"slaveofCommand", (unsigned long)slaveofCommand},
 {"debugCommand", (unsigned long)debugCommand},
@@ -4273,6 +4650,9 @@ static struct redisFunctionSym symsTable[] = {
 {"setupSigSegvAction", (unsigned long)setupSigSegvAction},
 {"readQueryFromClient", (unsigned long)readQueryFromClient},
 {"rdbRemoveTempFile", (unsigned long)rdbRemoveTempFile},
+{"msetGenericCommand", (unsigned long)msetGenericCommand},
+{"msetCommand", (unsigned long)msetCommand},
+{"msetnxCommand", (unsigned long)msetnxCommand},
 {NULL,0}
 };