]> git.saurik.com Git - redis.git/commitdiff
Merge branch 'hmget' of git://github.com/pietern/redis
authorantirez <antirez@gmail.com>
Tue, 13 Apr 2010 16:30:55 +0000 (18:30 +0200)
committerantirez <antirez@gmail.com>
Tue, 13 Apr 2010 16:30:55 +0000 (18:30 +0200)
1  2 
redis.c
test-redis.tcl

diff --combined redis.c
index cbb0993adef7381b817e4c6c991da81e192bebef,095a02e44f834a87b28929aabfbd4b2b888705ea..e78f75aa637c9aa4a0bf3502d539acc17af4e157
+++ b/redis.c
@@@ -704,8 -704,9 +704,9 @@@ static void substrCommand(redisClient *
  static void zrankCommand(redisClient *c);
  static void zrevrankCommand(redisClient *c);
  static void hsetCommand(redisClient *c);
- static void hmsetCommand(redisClient *c);
  static void hgetCommand(redisClient *c);
+ static void hmsetCommand(redisClient *c);
+ static void hmgetCommand(redisClient *c);
  static void hdelCommand(redisClient *c);
  static void hlenCommand(redisClient *c);
  static void zremrangebyrankCommand(redisClient *c);
@@@ -781,9 -782,10 +782,10 @@@ 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},
+     {"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},
      {"hincrby",hincrbyCommand,4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
-     {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
      {"hdel",hdelCommand,3,REDIS_CMD_BULK,NULL,1,1,1},
      {"hlen",hlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
      {"hkeys",hkeysCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
@@@ -1981,7 -1983,7 +1983,7 @@@ static void sendReplyToClient(aeEventLo
  
      /* Use writev() if we have enough buffers to send */
      if (!server.glueoutputbuf &&
 -        listLength(c->reply) > REDIS_WRITEV_THRESHOLD && 
 +        listLength(c->reply) > REDIS_WRITEV_THRESHOLD &&
          !(c->flags & REDIS_MASTER))
      {
          sendReplyToClientWritev(el, fd, privdata, mask);
@@@ -2059,7 -2061,7 +2061,7 @@@ static void sendReplyToClientWritev(aeE
              o = listNodeValue(node);
              objlen = sdslen(o->ptr);
  
 -            if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT) 
 +            if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT)
                  break;
  
              if(ion == REDIS_WRITEV_IOVEC_COUNT)
          }
      }
  
 -    if (totwritten > 0) 
 +    if (totwritten > 0)
          c->lastinteraction = time(NULL);
  
      if (listLength(c->reply) == 0) {
@@@ -2417,7 -2419,7 +2419,7 @@@ again
          if (p) {
              sds query, *argv;
              int argc, j;
 -            
 +
              query = c->querybuf;
              c->querybuf = sdsempty();
              querylen = 1+(p-(query));
@@@ -2959,7 -2961,7 +2961,7 @@@ static int isStringRepresentableAsLong(
      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);
@@@ -3007,7 -3009,7 +3009,7 @@@ static robj *tryObjectEncoding(robj *o
   * If the object is already raw-encoded just increment the ref count. */
  static robj *getDecodedObject(robj *o) {
      robj *dec;
 -    
 +
      if (o->encoding == REDIS_ENCODING_RAW) {
          incrRefCount(o);
          return o;
@@@ -3065,77 -3067,6 +3067,77 @@@ static size_t stringObjectLen(robj *o) 
      }
  }
  
 +static int getDoubleFromObject(redisClient *c, robj *o, double *value) {
 +    double parsedValue;
 +    char *eptr = NULL;
 +
 +    if (o && o->type != REDIS_STRING) {
 +        addReplySds(c,sdsnew("-ERR value is not a double\r\n"));
 +        return REDIS_ERR;
 +    }
 +
 +    if (o == NULL)
 +        parsedValue = 0;
 +    else if (o->encoding == REDIS_ENCODING_RAW)
 +        parsedValue = strtod(o->ptr, &eptr);
 +    else if (o->encoding == REDIS_ENCODING_INT)
 +        parsedValue = (long)o->ptr;
 +    else
 +        redisAssert(1 != 1);
 +
 +    if (eptr != NULL && *eptr != '\0') {
 +        addReplySds(c,sdsnew("-ERR value is not a double\r\n"));
 +        return REDIS_ERR;
 +    }
 +
 +    *value = parsedValue;
 +
 +    return REDIS_OK;
 +}
 +
 +static int getLongLongFromObject(redisClient *c, robj *o, long long *value) {
 +    long long parsedValue;
 +    char *eptr = NULL;
 +
 +    if (o && o->type != REDIS_STRING) {
 +        addReplySds(c,sdsnew("-ERR value is not an integer\r\n"));
 +        return REDIS_ERR;
 +    }
 +
 +    if (o == NULL)
 +        parsedValue = 0;
 +    else if (o->encoding == REDIS_ENCODING_RAW)
 +        parsedValue = strtoll(o->ptr, &eptr, 10);
 +    else if (o->encoding == REDIS_ENCODING_INT)
 +        parsedValue = (long)o->ptr;
 +    else
 +        redisAssert(1 != 1);
 +
 +    if (eptr != NULL && *eptr != '\0') {
 +        addReplySds(c,sdsnew("-ERR value is not an integer\r\n"));
 +        return REDIS_ERR;
 +    }
 +
 +    *value = parsedValue;
 +
 +    return REDIS_OK;
 +}
 +
 +static int getLongFromObject(redisClient *c, robj *o, long *value) {
 +    long long actualValue;
 +
 +    if (getLongLongFromObject(c, o, &actualValue) != REDIS_OK) return REDIS_ERR;
 +
 +    if (actualValue < LONG_MIN || actualValue > LONG_MAX) {
 +        addReplySds(c,sdsnew("-ERR value is out of range\r\n"));
 +        return REDIS_ERR;
 +    }
 +
 +    *value = actualValue;
 +
 +    return REDIS_OK;
 +}
 +
  /*============================ RDB saving/loading =========================== */
  
  static int rdbSaveType(FILE *fp, unsigned char type) {
@@@ -3408,7 -3339,7 +3410,7 @@@ static off_t rdbSavedObjectLen(robj *o
  /* Return the number of pages required to save this object in the swap file */
  static off_t rdbSavedObjectPages(robj *o, FILE *fp) {
      off_t bytes = rdbSavedObjectLen(o,fp);
 -    
 +
      return (bytes+(server.vm_page_size-1))/server.vm_page_size;
  }
  
@@@ -3491,7 -3422,7 +3493,7 @@@ static int rdbSave(char *filename) 
      fflush(fp);
      fsync(fileno(fp));
      fclose(fp);
 -    
 +
      /* Use RENAME to make sure the DB file is changed atomically only
       * if the generate DB file is ok. */
      if (rename(tmpfile,filename) == -1) {
@@@ -3921,7 -3852,7 +3923,7 @@@ static void setnxCommand(redisClient *c
  
  static int getGenericCommand(redisClient *c) {
      robj *o;
 -    
 +
      if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
          return REDIS_OK;
  
@@@ -3952,7 -3883,7 +3954,7 @@@ static void getsetCommand(redisClient *
  
  static void mgetCommand(redisClient *c) {
      int j;
 -  
 +
      addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1));
      for (j = 1; j < c->argc; j++) {
          robj *o = lookupKeyRead(c->db,c->argv[j]);
@@@ -4019,10 -3950,24 +4021,10 @@@ static void incrDecrCommand(redisClien
      long long value;
      int retval;
      robj *o;
 -    
 +
      o = lookupKeyWrite(c->db,c->argv[1]);
 -    if (o == NULL) {
 -        value = 0;
 -    } else {
 -        if (o->type != REDIS_STRING) {
 -            value = 0;
 -        } else {
 -            char *eptr;
  
 -            if (o->encoding == REDIS_ENCODING_RAW)
 -                value = strtoll(o->ptr, &eptr, 10);
 -            else if (o->encoding == REDIS_ENCODING_INT)
 -                value = (long)o->ptr;
 -            else
 -                redisAssert(1 != 1);
 -        }
 -    }
 +    if (getLongLongFromObject(c, o, &value) != REDIS_OK) return;
  
      value += incr;
      o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
@@@ -4049,18 -3994,12 +4051,18 @@@ static void decrCommand(redisClient *c
  }
  
  static void incrbyCommand(redisClient *c) {
 -    long long incr = strtoll(c->argv[2]->ptr, NULL, 10);
 +    long long incr;
 +
 +    if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return;
 +
      incrDecrCommand(c,incr);
  }
  
  static void decrbyCommand(redisClient *c) {
 -    long long incr = strtoll(c->argv[2]->ptr, NULL, 10);
 +    long long incr;
 +
 +    if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return;
 +
      incrDecrCommand(c,-incr);
  }
  
@@@ -4078,7 -4017,7 +4080,7 @@@ static void appendCommand(redisClient *
          totlen = stringObjectLen(c->argv[2]);
      } else {
          dictEntry *de;
 -       
 +
          de = dictFind(c->db->dict,c->argv[1]);
          assert(de != NULL);
  
@@@ -4167,7 -4106,7 +4169,7 @@@ static void existsCommand(redisClient *
  
  static void selectCommand(redisClient *c) {
      int id = atoi(c->argv[1]->ptr);
 -    
 +
      if (selectDb(c,id) == REDIS_ERR) {
          addReplySds(c,sdsnew("-ERR invalid DB index\r\n"));
      } else {
  
  static void randomkeyCommand(redisClient *c) {
      dictEntry *de;
 -   
 +
      while(1) {
          de = dictGetRandomKey(c->db->dict);
          if (!de || expireIfNeeded(c->db,dictGetEntryKey(de)) == 0) break;
@@@ -4305,7 -4244,7 +4307,7 @@@ static void shutdownCommand(redisClien
               * in the next cron() Redis will be notified that the background
               * saving aborted, handling special stuff like slaves pending for
               * synchronization... */
 -            redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit"); 
 +            redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit");
              addReplySds(c,
                  sdsnew("-ERR can't quit, problems saving the DB\r\n"));
          }
@@@ -4449,7 -4388,7 +4451,7 @@@ static void llenCommand(redisClient *c
  
      if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
          checkType(c,o,REDIS_LIST)) return;
 -    
 +
      l = o->ptr;
      addReplyUlong(c,listLength(l));
  }
@@@ -4540,8 -4479,8 +4542,8 @@@ static void lrangeCommand(redisClient *
      listNode *ln;
      robj *ele;
  
 -    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL ||
 -        checkType(c,o,REDIS_LIST)) return;
 +    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
 +         || checkType(c,o,REDIS_LIST)) return;
      list = o->ptr;
      llen = listLength(list);
  
@@@ -4805,7 -4744,7 +4807,7 @@@ static void scardCommand(redisClient *c
  
      if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
          checkType(c,o,REDIS_SET)) return;
 -    
 +
      s = o->ptr;
      addReplyUlong(c,dictSize(s));
  }
@@@ -4874,7 -4813,7 +4876,7 @@@ static void sinterGenericCommand(redisC
                      server.dirty++;
                  addReply(c,shared.czero);
              } else {
 -                addReply(c,shared.nullmultibulk);
 +                addReply(c,shared.emptymultibulk);
              }
              return;
          }
@@@ -5096,7 -5035,7 +5098,7 @@@ static zskiplistNode *zslCreateNode(in
  static zskiplist *zslCreate(void) {
      int j;
      zskiplist *zsl;
 -    
 +
      zsl = zmalloc(sizeof(*zsl));
      zsl->level = 1;
      zsl->length = 0;
@@@ -5429,7 -5368,7 +5431,7 @@@ static void zaddGenericCommand(redisCli
      } else {
          dictEntry *de;
          double *oldscore;
 -        
 +
          /* case 2: Score update operation */
          de = dictFind(zs->dict,ele);
          redisAssert(de != NULL);
  static void zaddCommand(redisClient *c) {
      double scoreval;
  
 -    scoreval = strtod(c->argv[2]->ptr,NULL);
 +    if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return;
 +
      zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
  }
  
  static void zincrbyCommand(redisClient *c) {
      double scoreval;
  
 -    scoreval = strtod(c->argv[2]->ptr,NULL);
 +    if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return;
 +
      zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
  }
  
@@@ -5501,15 -5438,12 +5503,15 @@@ static void zremCommand(redisClient *c
  }
  
  static void zremrangebyscoreCommand(redisClient *c) {
 -    double min = strtod(c->argv[2]->ptr,NULL);
 -    double max = strtod(c->argv[3]->ptr,NULL);
 +    double min;
 +    double max;
      long deleted;
      robj *zsetobj;
      zset *zs;
  
 +    if ((getDoubleFromObject(c, c->argv[2], &min) != REDIS_OK) ||
 +        (getDoubleFromObject(c, c->argv[3], &max) != REDIS_OK)) return;
 +
      if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
          checkType(c,zsetobj,REDIS_ZSET)) return;
  
  }
  
  static void zremrangebyrankCommand(redisClient *c) {
 -    int start = atoi(c->argv[2]->ptr);
 -    int end = atoi(c->argv[3]->ptr);
 +    long start;
 +    long end;
      int llen;
      long deleted;
      robj *zsetobj;
      zset *zs;
  
 +    if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) ||
 +        (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return;
 +
      if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
          checkType(c,zsetobj,REDIS_ZSET)) return;
      zs = zsetobj->ptr;
@@@ -5638,8 -5569,7 +5640,8 @@@ static void zunionInterGenericCommand(r
              if (remaining >= (zsetnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) {
                  j++; remaining--;
                  for (i = 0; i < zsetnum; i++, j++, remaining--) {
 -                    src[i].weight = strtod(c->argv[j]->ptr, NULL);
 +                    if (getDoubleFromObject(c, c->argv[j], &src[i].weight) != REDIS_OK)
 +                        return;
                  }
              } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) {
                  j++; remaining--;
@@@ -5761,8 -5691,8 +5763,8 @@@ static void zinterCommand(redisClient *
  
  static void zrangeGenericCommand(redisClient *c, int reverse) {
      robj *o;
 -    int start = atoi(c->argv[2]->ptr);
 -    int end = atoi(c->argv[3]->ptr);
 +    long start;
 +    long end;
      int withscores = 0;
      int llen;
      int rangelen, j;
      zskiplistNode *ln;
      robj *ele;
  
 +    if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) ||
 +        (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return;
 +
      if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
          withscores = 1;
      } else if (c->argc >= 5) {
          return;
      }
  
 -    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL ||
 -        checkType(c,o,REDIS_ZSET)) 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;
@@@ -5888,7 -5815,7 +5890,7 @@@ static void genericZrangebyscoreCommand
      /* Ok, lookup the key and get the range */
      o = lookupKeyRead(c->db,c->argv[1]);
      if (o == NULL) {
 -        addReply(c,justcount ? shared.czero : shared.nullmultibulk);
 +        addReply(c,justcount ? shared.czero : shared.emptymultibulk);
      } else {
          if (o->type != REDIS_ZSET) {
              addReply(c,shared.wrongtypeerr);
@@@ -6160,8 -6087,7 +6162,8 @@@ static void hincrbyCommand(redisClient 
          }
      }
  
 -    incr = strtoll(c->argv[3]->ptr, NULL, 10);
 +    if (getLongLongFromObject(c, c->argv[3], &incr) != REDIS_OK) return;
 +
      if (o->encoding == REDIS_ENCODING_ZIPMAP) {
          unsigned char *zm = o->ptr;
          unsigned char *zval;
@@@ -6253,6 -6179,55 +6255,55 @@@ static void hgetCommand(redisClient *c
      }
  }
  
+ static void hmgetCommand(redisClient *c) {
+     int i;
+     robj *o = lookupKeyRead(c->db, c->argv[1]);
+     if (o == NULL) {
+         addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2));
+         for (i = 2; i < c->argc; i++) {
+             addReply(c,shared.nullbulk);
+         }
+         return;
+     } else {
+         if (o->type != REDIS_HASH) {
+             addReply(c,shared.wrongtypeerr);
+             return;
+         }
+     }
+     addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2));
+     if (o->encoding == REDIS_ENCODING_ZIPMAP) {
+         unsigned char *zm = o->ptr;
+         unsigned char *v;
+         unsigned int vlen;
+         robj *field;
+         for (i = 2; i < c->argc; i++) {
+             field = getDecodedObject(c->argv[i]);
+             if (zipmapGet(zm,field->ptr,sdslen(field->ptr),&v,&vlen)) {
+                 addReplySds(c,sdscatprintf(sdsempty(),"$%u\r\n", vlen));
+                 addReplySds(c,sdsnewlen(v,vlen));
+                 addReply(c,shared.crlf);
+             } else {
+                 addReply(c,shared.nullbulk);
+             }
+             decrRefCount(field);
+         }
+     } else {
+         dictEntry *de;
+         for (i = 2; i < c->argc; i++) {
+             de = dictFind(o->ptr,c->argv[i]);
+             if (de != NULL) {
+                 addReplyBulk(c,(robj*)dictGetEntryVal(de));
+             } else {
+                 addReply(c,shared.nullbulk);
+             }
+         }
+     }
+ }
  static void hdelCommand(redisClient *c) {
      robj *o;
      int deleted = 0;
@@@ -6296,7 -6271,7 +6347,7 @@@ static void genericHgetallCommand(redis
      robj *o, *lenobj;
      unsigned long count = 0;
  
 -    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL
 +    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
          || checkType(c,o,REDIS_HASH)) return;
  
      lenobj = createObject(REDIS_STRING,NULL);
@@@ -6536,7 -6511,7 +6587,7 @@@ static void sortCommand(redisClient *c
      /* Lookup the key to sort. It must be of the right types */
      sortval = lookupKeyRead(c->db,c->argv[1]);
      if (sortval == NULL) {
 -        addReply(c,shared.nullmultibulk);
 +        addReply(c,shared.emptymultibulk);
          return;
      }
      if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST &&
@@@ -6993,13 -6968,8 +7044,13 @@@ static int deleteIfVolatile(redisDb *db
      return dictDelete(db->dict,key) == DICT_OK;
  }
  
 -static void expireGenericCommand(redisClient *c, robj *key, time_t seconds) {
 +static void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
      dictEntry *de;
 +    time_t seconds;
 +
 +    if (getLongFromObject(c, param, &seconds) != REDIS_OK) return;
 +
 +    seconds -= offset;
  
      de = dictFind(c->db->dict,key);
      if (de == NULL) {
  }
  
  static void expireCommand(redisClient *c) {
 -    expireGenericCommand(c,c->argv[1],strtol(c->argv[2]->ptr,NULL,10));
 +    expireGenericCommand(c,c->argv[1],c->argv[2],0);
  }
  
  static void expireatCommand(redisClient *c) {
 -    expireGenericCommand(c,c->argv[1],strtol(c->argv[2]->ptr,NULL,10)-time(NULL));
 +    expireGenericCommand(c,c->argv[1],c->argv[2],time(NULL));
  }
  
  static void ttlCommand(redisClient *c) {
@@@ -7271,7 -7241,7 +7322,7 @@@ static void blockingPopGenericCommand(r
                       * non-blocking POP operation */
                      robj *argv[2], **orig_argv;
                      int orig_argc;
 -                   
 +
                      /* We need to alter the command arguments before to call
                       * popGenericCommand() as the command takes a single key. */
                      orig_argv = c->argv;
@@@ -7508,7 -7478,7 +7559,7 @@@ static void updateSlavesWaitingBgsave(i
              slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
          } else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
              struct redis_stat buf;
 -           
 +
              if (bgsaveerr != REDIS_OK) {
                  freeClient(slave);
                  redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");
@@@ -8165,7 -8135,7 +8216,7 @@@ static int rewriteAppendOnlyFile(char *
      fflush(fp);
      fsync(fileno(fp));
      fclose(fp);
 -    
 +
      /* Use RENAME to make sure the DB file is changed atomically only
       * if the generate DB file is ok. */
      if (rename(tmpfile,filename) == -1) {
@@@ -8283,7 -8253,7 +8334,7 @@@ static void aofRemoveTempFile(pid_t chi
  static void expandVmSwapFilename(void) {
      char *p = strstr(server.vm_swap_file,"%p");
      sds new;
 -    
 +
      if (!p) return;
      new = sdsempty();
      *p = '\0';
@@@ -8408,7 -8378,7 +8459,7 @@@ static int vmFreePage(off_t page) 
  }
  
  /* Find N contiguous free pages storing the first page of the cluster in *first.
 - * Returns REDIS_OK if it was able to find N contiguous pages, otherwise 
 + * Returns REDIS_OK if it was able to find N contiguous pages, otherwise
   * REDIS_ERR is returned.
   *
   * This function uses a simple algorithm: we try to allocate
   * we try to find less populated places doing a forward jump of
   * REDIS_VM_MAX_RANDOM_JUMP, then we start scanning again a few pages
   * without hurry, and then we jump again and so forth...
 - * 
 + *
   * This function can be improved using a free list to avoid to guess
   * too much, since we could collect data about freed pages.
   *
@@@ -9073,7 -9043,7 +9124,7 @@@ static void *IOThreadEntryPoint(void *a
          listDelNode(server.io_processing,ln);
          listAddNodeTail(server.io_processed,j);
          unlockThreadedIO();
 -        
 +
          /* Signal the main thread there is new stuff to process */
          assert(write(server.io_ready_pipe_write,"x",1) == 1);
      }
@@@ -9151,7 -9121,7 +9202,7 @@@ static void queueIOJob(iojob *j) 
  
  static int vmSwapObjectThreaded(robj *key, robj *val, redisDb *db) {
      iojob *j;
 -    
 +
      assert(key->storage == REDIS_VM_MEMORY);
      assert(key->refcount == 1);
  
@@@ -9195,7 -9165,7 +9246,7 @@@ static int waitForSwappedKey(redisClien
          vmCancelThreadedIOJob(o);
          return 0;
      }
 -    
 +
      /* OK: the key is either swapped, or being loaded just now. */
  
      /* Add the key to the list of keys this client is waiting for.
@@@ -9908,7 -9878,7 +9959,7 @@@ static void *getMcontextEip(ucontext_t 
      return (void*) uc->uc_mcontext->__ss.__rip;
    #else
      return (void*) uc->uc_mcontext->__ss.__eip;
 -  #endif 
 +  #endif
  #elif defined(__i386__) || defined(__X86_64__) || defined(__x86_64__)
      return (void*) uc->uc_mcontext.gregs[REG_EIP]; /* Linux 32/64 bit */
  #elif defined(__ia64__) /* Linux IA64 */
@@@ -9933,7 -9903,7 +9984,7 @@@ static void segvHandler(int sig, siginf
      redisLog(REDIS_WARNING, "%s",infostring);
      /* It's not safe to sdsfree() the returned string under memory
       * corruption conditions. Let it leak as we are going to abort */
 -    
 +
      trace_size = backtrace(trace, 100);
      /* overwrite sigaction with caller's address */
      if (getMcontextEip(uc) != NULL) {
diff --combined test-redis.tcl
index f45c473823c577a92a4b6d1e150a3ac513763edf,6ebcb039f15f9f18bbfc94f4754ce6c26705bd6d..f321258a53783e0005c547fed66ce7664cfc81e5
@@@ -234,7 -234,7 +234,7 @@@ proc main {server port} 
  
      # The following AUTH test should be enabled only when requirepass
      # <PASSWORD> is set in redis.conf and redis-server was started with
 -    # redis.conf as the first argument.  
 +    # redis.conf as the first argument.
  
      #test {AUTH with requirepass in redis.conf} {
      #    $r auth foobared
          $r incrby novar 17179869184
      } {34359738368}
  
 -    test {INCR against key with spaces (no integer encoded)} {
 +    test {INCR fails against key with spaces (no integer encoded)} {
          $r set novar "    11    "
 -        $r incr novar
 -    } {12}
 +        catch {$r incr novar} err
 +        format $err
 +    } {ERR*}
 +
 +    test {INCR fails against a key holding a list} {
 +        $r rpush mylist 1
 +        catch {$r incr novar} err
 +        $r rpop mylist
 +        format $err
 +    } {ERR*}
  
      test {DECRBY over 32bit value with over 32bit increment, negative res} {
          $r set novar 17179869184
      test {SUNION with two sets} {
          lsort [$r sunion set1 set2]
      } [lsort -uniq "[$r smembers set1] [$r smembers set2]"]
 -    
 +
      test {SINTERSTORE with two sets} {
          $r sinterstore setres set1 set2
          lsort [$r smembers setres]
          $r lpush mysavelist world
          $r set myemptykey {}
          $r set mynormalkey {blablablba}
 -        $r zadd mytestzset a 10
 -        $r zadd mytestzset b 20
 -        $r zadd mytestzset c 30
 +        $r zadd mytestzset 10 a
 +        $r zadd mytestzset 20 b
 +        $r zadd mytestzset 30 c
          $r save
      } {OK}
  
          }
          lsort [array names myset]
      } {a b c}
 -    
 +
      test {Create a random list and a random set} {
          set tosort {}
          array set seenrand {}
          $r hmset bighash {*}$args
      } {OK}
  
+     test {HMGET against non existing key and fields} {
+         set rv {}
+         lappend rv [$r hmget doesntexist __123123123__ __456456456__]
+         lappend rv [$r hmget smallhash __123123123__ __456456456__]
+         lappend rv [$r hmget bighash __123123123__ __456456456__]
+         set _ $rv
+     } {{{} {}} {{} {}} {{} {}}}
+     test {HMGET - small hash} {
+         set keys {}
+         set vals {}
+         foreach {k v} [array get smallhash] {
+             lappend keys $k
+             lappend vals $v
+         }
+         set err {}
+         set result [$r hmget smallhash {*}$keys]
+         if {$vals ne $result} {
+             set err "$vals != $result"
+             break
+         }
+         set _ $err
+     } {}
+     test {HMGET - big hash} {
+         set keys {}
+         set vals {}
+         foreach {k v} [array get bighash] {
+             lappend keys $k
+             lappend vals $v
+         }
+         set err {}
+         set result [$r hmget bighash {*}$keys]
+         if {$vals ne $result} {
+             set err "$vals != $result"
+             break
+         }
+         set _ $err
+     } {}
      test {HKEYS - small hash} {
          lsort [$r hkeys smallhash]
      } [lsort [array names smallhash *]]