]> git.saurik.com Git - redis.git/blobdiff - redis.c
enable hash dereference in SORT on BY and GET
[redis.git] / redis.c
diff --git a/redis.c b/redis.c
index 5b4dcf5f87efb06e17d4d15c401f51f0ca4da06b..86f83bc55da5e561358992ba11a4466fe475ebaf 100644 (file)
--- a/redis.c
+++ b/redis.c
@@ -705,6 +705,7 @@ static void substrCommand(redisClient *c);
 static void zrankCommand(redisClient *c);
 static void zrevrankCommand(redisClient *c);
 static void hsetCommand(redisClient *c);
+static void hsetnxCommand(redisClient *c);
 static void hgetCommand(redisClient *c);
 static void hmsetCommand(redisClient *c);
 static void hmgetCommand(redisClient *c);
@@ -783,6 +784,7 @@ static struct redisCommand cmdTable[] = {
     {"zrank",zrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
     {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
     {"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
+    {"hsetnx",hsetnxCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
     {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
     {"hmset",hmsetCommand,-4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
     {"hmget",hmgetCommand,-3,REDIS_CMD_BULK,NULL,1,1,1},
@@ -6064,8 +6066,10 @@ static void hashTryConversion(robj *subject, robj **argv, int start, int end) {
 }
 
 /* Get the value from a hash identified by key. Returns either a string
- * object or NULL if the value cannot be found. The refcount of the object
- * is always increased by 1 when the value was found. */
+ * object or NULL if the value cannot be found.
+ * Note: the refcount for objects retrieved from a zipmap is set to 0.
+ * This is done, so addReply will increment and clean up the object.
+ * Make sure to clean up the object when it isn't added to a reply. */
 static robj *hashGet(robj *o, robj *key) {
     robj *value = NULL;
     if (o->encoding == REDIS_ENCODING_ZIPMAP) {
@@ -6074,13 +6078,13 @@ static robj *hashGet(robj *o, robj *key) {
         key = getDecodedObject(key);
         if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) {
             value = createStringObject((char*)v,vlen);
+            value->refcount = 0;
         }
         decrRefCount(key);
     } else {
         dictEntry *de = dictFind(o->ptr,key);
         if (de != NULL) {
             value = dictGetEntryVal(de);
-            incrRefCount(value);
         }
     }
     return value;
@@ -6199,8 +6203,9 @@ static inline int hashNext(hashIterator *hi) {
     return REDIS_OK;
 }
 
-/* Get key or value object at current iteration position. This always
- * increases the refcount of the field object by 1. */
+/* Get key or value object at current iteration position.
+ * See comments at hashGet for a discussion on the refcount for
+ * keys and values retrieved from zipmaps. */
 static inline robj *hashCurrent(hashIterator *hi, int what) {
     robj *o;
     if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
@@ -6209,13 +6214,13 @@ static inline robj *hashCurrent(hashIterator *hi, int what) {
         } else {
             o = createStringObject((char*)hi->zv,hi->zvlen);
         }
+        o->refcount = 0;
     } else {
         if (what & REDIS_HASH_KEY) {
             o = dictGetEntryKey(hi->de);
         } else {
             o = dictGetEntryVal(hi->de);
         }
-        incrRefCount(o);
     }
     return o;
 }
@@ -6237,16 +6242,30 @@ static robj *hashLookupWriteOrCreate(redisClient *c, robj *key) {
 
 /* ============================= Hash commands ============================== */
 static void hsetCommand(redisClient *c) {
-    int update = 0;
+    int update;
     robj *o;
 
     if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
     hashTryConversion(o,c->argv,2,3);
     update = hashReplace(o,c->argv[2],c->argv[3]);
-    addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",update == 0));
+    addReply(c, update ? shared.czero : shared.cone);
     server.dirty++;
 }
 
+static void hsetnxCommand(redisClient *c) {
+    robj *o;
+    if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
+    hashTryConversion(o,c->argv,2,3);
+
+    if (hashExists(o, c->argv[2])) {
+        addReply(c, shared.czero);
+    } else {
+        hashReplace(o,c->argv[2],c->argv[3]);
+        addReply(c, shared.cone);
+        server.dirty++;
+    }
+}
+
 static void hmsetCommand(redisClient *c) {
     int i;
     robj *o;
@@ -6277,7 +6296,12 @@ static void hincrbyCommand(redisClient *c) {
             value = (long)current->ptr;
         else
             redisAssert(1 != 1);
-        decrRefCount(current);
+
+        /* clean up object when it was retrieved from a zipmap */
+        if (current->refcount == 0) {
+            current->refcount = 1;
+            decrRefCount(current);
+        }
     } else {
         value = 0;
     }
@@ -6297,7 +6321,6 @@ static void hgetCommand(redisClient *c) {
 
     if ((value = hashGet(o,c->argv[2])) != NULL) {
         addReplyBulk(c,value);
-        decrRefCount(value);
     } else {
         addReply(c,shared.nullbulk);
     }
@@ -6318,7 +6341,6 @@ static void hmgetCommand(redisClient *c) {
     for (i = 2; i < c->argc; i++) {
         if (o != NULL && (value = hashGet(o,c->argv[i])) != NULL) {
             addReplyBulk(c,value);
-            decrRefCount(value);
         } else {
             addReply(c,shared.nullbulk);
         }
@@ -6364,13 +6386,11 @@ static void genericHgetallCommand(redisClient *c, int flags) {
         if (flags & REDIS_HASH_KEY) {
             obj = hashCurrent(&hi,REDIS_HASH_KEY);
             addReplyBulk(c,obj);
-            decrRefCount(obj);
             count++;
         }
         if (flags & REDIS_HASH_VALUE) {
             obj = hashCurrent(&hi,REDIS_HASH_VALUE);
             addReplyBulk(c,obj);
-            decrRefCount(obj);
             count++;
         }
     }
@@ -6450,16 +6470,16 @@ static redisSortOperation *createSortOperation(int type, robj *pattern) {
 /* Return the value associated to the key with a name obtained
  * substituting the first occurence of '*' in 'pattern' with 'subst' */
 static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
-    char *p;
+    char *p, *f;
     sds spat, ssub;
-    robj keyobj;
-    int prefixlen, sublen, postfixlen;
+    robj keyobj, fieldobj, *o;
+    int prefixlen, sublen, postfixlen, fieldlen;
     /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */
     struct {
         long len;
         long free;
         char buf[REDIS_SORTKEY_MAX+1];
-    } keyname;
+    } keyname, fieldname;
 
     /* If the pattern is "#" return the substitution object itself in order
      * to implement the "SORT ... GET #" feature. */
@@ -6481,20 +6501,40 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
         return NULL;
     }
 
+    /* Find out if we're dealing with a hash dereference. */
+    if ((f = strstr(p+1, "->")) != NULL) {
+        fieldlen = sdslen(spat)-(f-spat);
+        /* this also copies \0 character */
+        memcpy(fieldname.buf,f+2,fieldlen-1);
+        fieldname.len = fieldlen-2;
+    } else {
+        fieldlen = 0;
+    }
+
     prefixlen = p-spat;
     sublen = sdslen(ssub);
-    postfixlen = sdslen(spat)-(prefixlen+1);
+    postfixlen = sdslen(spat)-(prefixlen+1)-fieldlen;
     memcpy(keyname.buf,spat,prefixlen);
     memcpy(keyname.buf+prefixlen,ssub,sublen);
     memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen);
     keyname.buf[prefixlen+sublen+postfixlen] = '\0';
     keyname.len = prefixlen+sublen+postfixlen;
-
-    initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2))
     decrRefCount(subst);
 
-    /* printf("lookup '%s' => %p\n", keyname.buf,de); */
-    return lookupKeyRead(db,&keyobj);
+    /* Lookup substituted key */
+    initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2));
+    o = lookupKeyRead(db,&keyobj);
+
+    /* Retrieve value from hash by the field name */
+    if (o != NULL && fieldlen > 0) {
+        if (o->type != REDIS_HASH || fieldname.len < 1) {
+            return NULL;
+        }
+        initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(long)*2));
+        o = hashGet(o, &fieldobj);
+    }
+
+    return o;
 }
 
 /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
@@ -6663,36 +6703,37 @@ static void sortCommand(redisClient *c) {
     /* Now it's time to load the right scores in the sorting vector */
     if (dontsort == 0) {
         for (j = 0; j < vectorlen; j++) {
+            robj *byval;
             if (sortby) {
-                robj *byval;
-
+                /* lookup value to sort by */
                 byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
                 if (!byval || byval->type != REDIS_STRING) continue;
-                if (alpha) {
-                    vector[j].u.cmpobj = getDecodedObject(byval);
+            } else {
+                /* use object itself to sort by */
+                byval = vector[j].obj;
+            }
+
+            if (alpha) {
+                /* getDecodedObject increments refcount, so the corresponding
+                 * decrRefCount will clean up values coming from a zipmap. */
+                vector[j].u.cmpobj = getDecodedObject(byval);
+            } else {
+                if (byval->encoding == REDIS_ENCODING_RAW) {
+                    vector[j].u.score = strtod(byval->ptr,NULL);
                 } else {
-                    if (byval->encoding == REDIS_ENCODING_RAW) {
-                        vector[j].u.score = strtod(byval->ptr,NULL);
-                    } else {
-                        /* Don't need to decode the object if it's
-                         * integer-encoded (the only encoding supported) so
-                         * far. We can just cast it */
-                        if (byval->encoding == REDIS_ENCODING_INT) {
-                            vector[j].u.score = (long)byval->ptr;
-                        } else
-                            redisAssert(1 != 1);
-                    }
+                    /* Don't need to decode the object if it's
+                     * integer-encoded (the only encoding supported) so
+                     * far. We can just cast it */
+                    if (byval->encoding == REDIS_ENCODING_INT) {
+                        vector[j].u.score = (long)byval->ptr;
+                    } else
+                        redisAssert(1 != 1);
                 }
-            } else {
-                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
-                            redisAssert(1 != 1);
-                    }
+
+                /* clean up immediately if this value came from a zipmap */
+                if (byval->refcount == 0) {
+                    byval->refcount = 1;
+                    decrRefCount(byval);
                 }
             }
         }