#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */
#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */
+static char* strencoding[] = {
+ "raw", "int", "zipmap", "hashtable"
+};
+
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME 253
#define REDIS_SELECTDB 254
static void zrevrankCommand(redisClient *c);
static void hsetCommand(redisClient *c);
static void hgetCommand(redisClient *c);
+static void hdelCommand(redisClient *c);
static void zremrangebyrankCommand(redisClient *c);
+static void zunionCommand(redisClient *c);
+static void zinterCommand(redisClient *c);
/*================================= Globals ================================= */
{"zrem",zremCommand,3,REDIS_CMD_BULK,1,1,1},
{"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE,1,1,1},
{"zremrangebyrank",zremrangebyrankCommand,4,REDIS_CMD_INLINE,1,1,1},
+ {"zunion",zunionCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,0,0,0},
+ {"zinter",zinterCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,0,0,0},
{"zrange",zrangeCommand,-4,REDIS_CMD_INLINE,1,1,1},
{"zrangebyscore",zrangebyscoreCommand,-4,REDIS_CMD_INLINE,1,1,1},
{"zcount",zcountCommand,4,REDIS_CMD_INLINE,1,1,1},
{"zrevrange",zrevrangeCommand,-4,REDIS_CMD_INLINE,1,1,1},
{"zcard",zcardCommand,2,REDIS_CMD_INLINE,1,1,1},
{"zscore",zscoreCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1},
- {"zrank",zrankCommand,3,REDIS_CMD_INLINE,1,1,1},
- {"zrevrank",zrevrankCommand,3,REDIS_CMD_INLINE,1,1,1},
+ {"zrank",zrankCommand,3,REDIS_CMD_BULK,1,1,1},
+ {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,1,1,1},
{"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1},
{"hget",hgetCommand,3,REDIS_CMD_BULK,1,1,1},
+ {"hdel",hdelCommand,3,REDIS_CMD_BULK,1,1,1},
{"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1},
{"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1},
{"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1},
static robj *rdbLoadObject(int type, FILE *fp) {
robj *o;
+ redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp));
if (type == REDIS_STRING) {
/* Read string value */
if ((o = rdbLoadStringObject(fp)) == NULL) return NULL;
rangelen = (end-start)+1;
/* Return the result */
- addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",rangelen));
+ addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen));
range = sdsnewlen((char*)o->ptr+start,rangelen);
addReplySds(c,range);
addReply(c,shared.crlf);
#define REDIS_OP_UNION 0
#define REDIS_OP_DIFF 1
+#define REDIS_OP_INTER 2
static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj *dstkey, int op) {
dict **dv = zmalloc(sizeof(dict*)*setsnum);
}
}
+typedef struct {
+ dict *dict;
+ double weight;
+} zsetopsrc;
+
+static 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;
+}
+
+static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
+ int i, j, zsetnum;
+ zsetopsrc *src;
+ robj *dstobj;
+ zset *dstzset;
+ dictIterator *di;
+ dictEntry *de;
+
+ /* expect zsetnum input keys to be given */
+ zsetnum = atoi(c->argv[2]->ptr);
+ if (zsetnum < 1) {
+ addReplySds(c,sdsnew("-ERR at least 1 input key is needed for ZUNION/ZINTER\r\n"));
+ return;
+ }
+
+ /* test if the expected number of keys would overflow */
+ if (3+zsetnum > c->argc) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* read keys to be used for input */
+ src = zmalloc(sizeof(zsetopsrc) * zsetnum);
+ for (i = 0, j = 3; i < zsetnum; i++, j++) {
+ robj *zsetobj = lookupKeyWrite(c->db,c->argv[j]);
+ if (!zsetobj) {
+ src[i].dict = NULL;
+ } else {
+ if (zsetobj->type != REDIS_ZSET) {
+ zfree(src);
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+ src[i].dict = ((zset*)zsetobj->ptr)->dict;
+ }
+
+ /* default all weights to 1 */
+ src[i].weight = 1.0;
+ }
+
+ /* parse optional extra arguments */
+ if (j < c->argc) {
+ int remaining = c->argc-j;
+
+ while (remaining) {
+ if (!strcasecmp(c->argv[j]->ptr,"weights")) {
+ j++; remaining--;
+ if (remaining < zsetnum) {
+ zfree(src);
+ addReplySds(c,sdsnew("-ERR not enough weights for ZUNION/ZINTER\r\n"));
+ return;
+ }
+ for (i = 0; i < zsetnum; i++, j++, remaining--) {
+ src[i].weight = strtod(c->argv[j]->ptr, NULL);
+ }
+ } else {
+ zfree(src);
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ }
+ }
+
+ dstobj = createZsetObject();
+ dstzset = dstobj->ptr;
+
+ if (op == REDIS_OP_INTER) {
+ /* sort sets from the smallest to largest, this will improve our
+ * algorithm's performance */
+ qsort(src,zsetnum,sizeof(zsetopsrc), qsortCompareZsetopsrcByCardinality);
+
+ /* 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) {
+ double *score = zmalloc(sizeof(double));
+ *score = 0.0;
+
+ for (j = 0; j < zsetnum; j++) {
+ dictEntry *other = (j == 0) ? de : dictFind(src[j].dict,dictGetEntryKey(de));
+ if (other) {
+ *score = *score + src[j].weight * (*(double*)dictGetEntryVal(other));
+ } else {
+ break;
+ }
+ }
+
+ /* skip entry when not present in every source dict */
+ if (j != zsetnum) {
+ zfree(score);
+ } else {
+ robj *o = dictGetEntryKey(de);
+ dictAdd(dstzset->dict,o,score);
+ incrRefCount(o); /* added to dictionary */
+ zslInsert(dstzset->zsl,*score,o);
+ incrRefCount(o); /* added to skiplist */
+ }
+ }
+ dictReleaseIterator(di);
+ }
+ } else if (op == REDIS_OP_UNION) {
+ for (i = 0; i < zsetnum; i++) {
+ if (!src[i].dict) continue;
+
+ di = dictGetIterator(src[i].dict);
+ while((de = dictNext(di)) != NULL) {
+ /* skip key when already processed */
+ if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) continue;
+
+ double *score = zmalloc(sizeof(double));
+ *score = 0.0;
+ for (j = 0; j < zsetnum; j++) {
+ if (!src[j].dict) continue;
+
+ dictEntry *other = (i == j) ? de : dictFind(src[j].dict,dictGetEntryKey(de));
+ if (other) {
+ *score = *score + src[j].weight * (*(double*)dictGetEntryVal(other));
+ }
+ }
+
+ robj *o = dictGetEntryKey(de);
+ dictAdd(dstzset->dict,o,score);
+ incrRefCount(o); /* added to dictionary */
+ zslInsert(dstzset->zsl,*score,o);
+ incrRefCount(o); /* added to skiplist */
+ }
+ dictReleaseIterator(di);
+ }
+ } else {
+ /* unknown operator */
+ redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION);
+ }
+
+ deleteKey(c->db,dstkey);
+ dictAdd(c->db->dict,dstkey,dstobj);
+ incrRefCount(dstkey);
+
+ addReplyLong(c, dstzset->zsl->length);
+ server.dirty++;
+ zfree(src);
+}
+
+static void zunionCommand(redisClient *c) {
+ zunionInterGenericCommand(c,c->argv[1], REDIS_OP_UNION);
+}
+
+static void zinterCommand(redisClient *c) {
+ zunionInterGenericCommand(c,c->argv[1], REDIS_OP_INTER);
+}
+
static void zrangeGenericCommand(redisClient *c, int reverse) {
robj *o;
int start = atoi(c->argv[2]->ptr);
return;
}
}
+ /* We want to convert the zipmap into an hash table right now if the
+ * entry to be added is too big. Note that we check if the object
+ * is integer encoded before to try fetching the length in the test below.
+ * This is because integers are small, but currently stringObjectLen()
+ * performs a slow conversion: not worth it. */
+ if (o->encoding == REDIS_ENCODING_ZIPMAP &&
+ ((c->argv[2]->encoding == REDIS_ENCODING_RAW &&
+ sdslen(c->argv[2]->ptr) > server.hash_max_zipmap_value) ||
+ (c->argv[3]->encoding == REDIS_ENCODING_RAW &&
+ sdslen(c->argv[3]->ptr) > server.hash_max_zipmap_value)))
+ {
+ convertToRealHash(o);
+ }
+
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
robj *valobj = getDecodedObject(c->argv[3]);
valobj->ptr,sdslen(valobj->ptr),&update);
decrRefCount(valobj);
o->ptr = zm;
+
+ /* And here there is the second check for hash conversion...
+ * we want to do it only if the operation was not just an update as
+ * zipmapLen() is O(N). */
+ if (!update && zipmapLen(zm) > server.hash_max_zipmap_entries)
+ convertToRealHash(o);
} else {
+ tryObjectEncoding(c->argv[2]);
+ /* note that c->argv[3] is already encoded, as the latest arg
+ * of a bulk command is always integer encoded if possible. */
if (dictAdd(o->ptr,c->argv[2],c->argv[3]) == DICT_OK) {
incrRefCount(c->argv[2]);
} else {
addReply(c,shared.nullbulk);
return;
} else {
+ if (o->type != REDIS_HASH) {
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
unsigned char *val;
}
}
+static void hdelCommand(redisClient *c) {
+ robj *o = lookupKeyRead(c->db,c->argv[1]);
+
+ if (o == NULL) {
+ addReply(c,shared.czero);
+ return;
+ } else {
+ int deleted = 0;
+
+ if (o->type != REDIS_HASH) {
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+
+ if (o->encoding == REDIS_ENCODING_ZIPMAP) {
+ o->ptr = zipmapDel((unsigned char*) o->ptr,
+ (unsigned char*) c->argv[2]->ptr,
+ sdslen(c->argv[2]->ptr), &deleted);
+ } else {
+ deleted = dictDelete((dict*)o->ptr,c->argv[2]) == DICT_OK;
+ }
+ addReply(c,deleted ? shared.cone : shared.czero);
+ }
+}
+
static void convertToRealHash(robj *o) {
unsigned char *key, *val, *p, *zm = o->ptr;
unsigned int klen, vlen;
time_t uptime = time(NULL)-server.stat_starttime;
int j;
char hmem[64];
+
+ server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES;
+ server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
bytesToHuman(hmem,zmalloc_used_memory());
info = sdscatprintf(sdsempty(),
"bgrewriteaof_in_progress:%d\r\n"
"total_connections_received:%lld\r\n"
"total_commands_processed:%lld\r\n"
+ "hash_max_zipmap_entries:%ld\r\n"
+ "hash_max_zipmap_value:%ld\r\n"
"vm_enabled:%d\r\n"
"role:%s\r\n"
,REDIS_VERSION,
server.bgrewritechildpid != -1,
server.stat_numconnections,
server.stat_numcommands,
+ server.hash_max_zipmap_entries,
+ server.hash_max_zipmap_value,
server.vm_enabled != 0,
server.masterhost == NULL ? "master" : "slave"
);
char buf[1024], tmpfile[256], authcmd[1024];
long dumpsize;
int fd = anetTcpConnect(NULL,server.masterhost,server.masterport);
- int dfd;
+ int dfd, maxtries = 5;
if (fd == -1) {
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
dumpsize = strtol(buf+1,NULL,10);
redisLog(REDIS_NOTICE,"Receiving %ld bytes data dump from MASTER",dumpsize);
/* Read the bulk write data on a temp file */
- snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random());
- dfd = open(tmpfile,O_CREAT|O_WRONLY,0644);
+ while(maxtries--) {
+ snprintf(tmpfile,256,
+ "temp-%d.%ld.rdb",(int)time(NULL),(long int)getpid());
+ dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
+ if (dfd != -1) break;
+ sleep(1);
+ }
if (dfd == -1) {
close(fd);
redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
}
/* Write an object into a file in the bulk format $<count>\r\n<payload>\r\n */
-static int fwriteBulk(FILE *fp, robj *obj) {
+static int fwriteBulkObject(FILE *fp, robj *obj) {
char buf[128];
int decrrc = 0;
return 0;
}
+/* Write binary-safe string into a file in the bulkformat
+ * $<count>\r\n<payload>\r\n */
+static int fwriteBulkString(FILE *fp, char *s, unsigned long len) {
+ char buf[128];
+
+ snprintf(buf,sizeof(buf),"$%ld\r\n",(unsigned long)len);
+ if (fwrite(buf,strlen(buf),1,fp) == 0) return 0;
+ if (len && fwrite(s,len,1,fp) == 0) return 0;
+ if (fwrite("\r\n",2,1,fp) == 0) return 0;
+ return 1;
+}
+
/* Write a double value in bulk format $<count>\r\n<payload>\r\n */
static int fwriteBulkDouble(FILE *fp, double d) {
char buf[128], dbuf[128];
char cmd[]="*3\r\n$3\r\nSET\r\n";
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
/* Key and value */
- if (fwriteBulk(fp,key) == 0) goto werr;
- if (fwriteBulk(fp,o) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
+ if (fwriteBulkObject(fp,o) == 0) goto werr;
} else if (o->type == REDIS_LIST) {
/* Emit the RPUSHes needed to rebuild the list */
list *list = o->ptr;
robj *eleobj = listNodeValue(ln);
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
- if (fwriteBulk(fp,key) == 0) goto werr;
- if (fwriteBulk(fp,eleobj) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
+ if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
}
} else if (o->type == REDIS_SET) {
/* Emit the SADDs needed to rebuild the set */
robj *eleobj = dictGetEntryKey(de);
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
- if (fwriteBulk(fp,key) == 0) goto werr;
- if (fwriteBulk(fp,eleobj) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
+ if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
}
dictReleaseIterator(di);
} else if (o->type == REDIS_ZSET) {
double *score = dictGetEntryVal(de);
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
- if (fwriteBulk(fp,key) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
if (fwriteBulkDouble(fp,*score) == 0) goto werr;
- if (fwriteBulk(fp,eleobj) == 0) goto werr;
+ if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
}
dictReleaseIterator(di);
+ } else if (o->type == REDIS_HASH) {
+ char cmd[]="*4\r\n$4\r\nHSET\r\n";
+
+ /* Emit the HSETs needed to rebuild the hash */
+ if (o->encoding == REDIS_ENCODING_ZIPMAP) {
+ unsigned char *p = zipmapRewind(o->ptr);
+ unsigned char *field, *val;
+ unsigned int flen, vlen;
+
+ while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) {
+ if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
+ if (fwriteBulkString(fp,(char*)field,flen) == -1)
+ return -1;
+ if (fwriteBulkString(fp,(char*)val,vlen) == -1)
+ return -1;
+ }
+ } else {
+ dictIterator *di = dictGetIterator(o->ptr);
+ dictEntry *de;
+
+ while((de = dictNext(di)) != NULL) {
+ robj *field = dictGetEntryKey(de);
+ robj *val = dictGetEntryVal(de);
+
+ if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
+ if (fwriteBulkObject(fp,field) == -1) return -1;
+ if (fwriteBulkObject(fp,val) == -1) return -1;
+ }
+ dictReleaseIterator(di);
+ }
} else {
redisAssert(0 != 0);
}
/* If this key is already expired skip it */
if (expiretime < now) continue;
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
- if (fwriteBulk(fp,key) == 0) goto werr;
+ if (fwriteBulkObject(fp,key) == 0) goto werr;
if (fwriteBulkLong(fp,expiretime) == 0) goto werr;
}
if (swapped) decrRefCount(o);
val = dictGetEntryVal(de);
if (!server.vm_enabled || (key->storage == REDIS_VM_MEMORY ||
key->storage == REDIS_VM_SWAPPING)) {
+ char *strenc;
+ char buf[128];
+
+ if (val->encoding < (sizeof(strencoding)/sizeof(char*))) {
+ strenc = strencoding[val->encoding];
+ } else {
+ snprintf(buf,64,"unknown encoding %d\n", val->encoding);
+ strenc = buf;
+ }
addReplySds(c,sdscatprintf(sdsempty(),
"+Key at:%p refcount:%d, value at:%p refcount:%d "
- "encoding:%d serializedlength:%lld\r\n",
+ "encoding:%s serializedlength:%lld\r\n",
(void*)key, key->refcount, (void*)val, val->refcount,
- val->encoding, (long long) rdbSavedObjectLen(val,NULL)));
+ strenc, (long long) rdbSavedObjectLen(val,NULL)));
} else {
addReplySds(c,sdscatprintf(sdsempty(),
"+Key at:%p refcount:%d, value swapped at: page %llu "