void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { int i, j, setnum; int aggregate = REDIS_AGGR_SUM; zsetopsrc *src; robj *dstobj; zset *dstzset; zskiplistNode *znode; dictIterator *di; dictEntry *de; int touched = 0; /* expect setnum input keys to be given */ setnum = atoi(c->argv[2]->ptr); if (setnum < 1) { addReplyError(c, "at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE"); return; } /* test if the expected number of keys would overflow */ if (3+setnum > c->argc) { addReply(c,shared.syntaxerr); return; } /* read keys to be used for input */ src = zmalloc(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 { zfree(src); addReply(c,shared.wrongtypeerr); return; } } /* 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 (remaining >= (setnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) { j++; remaining--; for (i = 0; i < setnum; i++, j++, remaining--) { if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight, "weight value is not a double") != REDIS_OK) { zfree(src); return; } } } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) { j++; remaining--; if (!strcasecmp(c->argv[j]->ptr,"sum")) { aggregate = REDIS_AGGR_SUM; } else if (!strcasecmp(c->argv[j]->ptr,"min")) { aggregate = REDIS_AGGR_MIN; } else if (!strcasecmp(c->argv[j]->ptr,"max")) { aggregate = REDIS_AGGR_MAX; } else { zfree(src); addReply(c,shared.syntaxerr); return; } j++; remaining--; } else { zfree(src); addReply(c,shared.syntaxerr); return; } } } /* sort sets from the smallest to largest, this will improve our * algorithm's performance */ qsort(src,setnum,sizeof(zsetopsrc),qsortCompareZsetopsrcByCardinality); 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) { double score, value; score = src[0].weight * zunionInterDictValue(de); for (j = 1; j < setnum; j++) { dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (other) { value = src[j].weight * zunionInterDictValue(other); zunionInterAggregate(&score,value,aggregate); } else { break; } } /* Only continue when present in every source dict. */ 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 */ } } dictReleaseIterator(di); } } else if (op == REDIS_OP_UNION) { for (i = 0; i < setnum; i++) { if (!src[i].dict) continue; di = dictGetIterator(src[i].dict); while((de = dictNext(di)) != NULL) { double score, value; /* skip key when already processed */ if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) continue; /* initialize score */ score = src[i].weight * zunionInterDictValue(de); /* because the zsets are sorted by size, its only possible * for sets at larger indices to hold this entry */ for (j = (i+1); j < setnum; j++) { dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (other) { value = src[j].weight * zunionInterDictValue(other); 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 */ } dictReleaseIterator(di); } } else { /* unknown operator */ redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION); } if (dbDelete(c->db,dstkey)) { touchWatchedKey(c->db,dstkey); touched = 1; server.dirty++; } if (dstzset->zsl->length) { dbAdd(c->db,dstkey,dstobj); addReplyLongLong(c, dstzset->zsl->length); if (!touched) touchWatchedKey(c->db,dstkey); server.dirty++; } else { decrRefCount(dstobj); addReply(c, shared.czero); } zfree(src); }
/* 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; zsetobj = lookupKeyWrite(c->db,key); if (zsetobj == NULL) { zsetobj = createZsetObject(); dbAdd(c->db,key,zsetobj); } else { if (zsetobj->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 (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; } } /* 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; /* New element */ incrRefCount(ele); /* added to hash */ znode = zslInsert(zs->zsl,score,ele); incrRefCount(ele); /* added to skiplist */ /* Update the score in the dict entry */ de = dictFind(zs->dict,ele); redisAssert(de != NULL); dictGetEntryVal(de) = &znode->score; touchWatchedKey(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; /* 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; touchWatchedKey(c->db,c->argv[1]); server.dirty++; } if (incr) addReplyDouble(c,score); else addReply(c,shared.czero); } }
/* Slot to Key API. This is used by Redis Cluster in order to obtain in * a fast way a key that belongs to a specified hash slot. This is useful * while rehashing the cluster. */ void SlotToKeyAdd(robj *key) { unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); zslInsert(server.cluster.slots_to_keys,hashslot,key); incrRefCount(key); }
int main(void) { int i; struct timeval start, end; int *key = malloc(N * sizeof(int)); if (key == NULL) { exit(-1); } zskiplist *zsl = zslCreate(); if (zsl == NULL) { exit(-1); } printf("Test start!\n"); printf("Add %d nodes...\n", N); /* Insert test */ srandom(time(NULL)); gettimeofday(&start, NULL); for (i = 0; i < N; i++) { key[i] = (int)random(); zslInsert(zsl, key[i]); } gettimeofday(&end, NULL); printf("time span: %ldms\n", (end.tv_sec - start.tv_sec)*1000 + (end.tv_usec - start.tv_usec)/1000); /* Search test 1 */ printf("Now search each node by key...\n"); gettimeofday(&start, NULL); for (i = 0; i < N; i++) { zrangespec range; range.min = range.max = key[i]; range.minex = range.maxex = 0; zskiplistNode *zn = zslFirstInRange(zsl, &range); if (zn != NULL) { #ifdef SKIPLIST_DEBUG printf("key:0x%08x\n", zn->score); #endif } else { printf("Not found:0x%08x\n", key[i]); } #ifdef SKIPLIST_DEBUG printf("key rank:%ld\n", zslGetRank(zsl, key[i])); #else zslGetRank(zsl, key[i]); #endif } gettimeofday(&end, NULL); printf("time span: %ldms\n", (end.tv_sec - start.tv_sec)*1000 + (end.tv_usec - start.tv_usec)/1000); /* Search test 2 */ printf("Now search each node by rank...\n"); gettimeofday(&start, NULL); for (i = 0; i < N; i++) { zskiplistNode* zn = zslGetElementByRank(zsl, i + 1); if (zn != NULL) { #ifdef SKIPLIST_DEBUG printf("key:0x%08x\n", zn->score); #endif } else { printf("Not found:%d\n", i + 1); } } gettimeofday(&end, NULL); printf("time span: %ldms\n", (end.tv_sec - start.tv_sec)*1000 + (end.tv_usec - start.tv_usec)/1000); /* Delete test */ printf("Now remove all nodes...\n"); gettimeofday(&start, NULL); for (i = 0; i < N; i++) { zslDelete(zsl, key[i]); } gettimeofday(&end, NULL); printf("time span: %ldms\n", (end.tv_sec - start.tv_sec)*1000 + (end.tv_usec - start.tv_usec)/1000); printf("End of Test.\n"); zslFree(zsl); return 0; }
/* This generic command implements both ZADD and ZINCRBY. * scoreval is the score if the operation is a ZADD (doincrement == 0) or * the increment if the operation is a ZINCRBY (doincrement == 1). */ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scoreval, int doincrement) { robj *zsetobj; zset *zs; double *score; if (isnan(scoreval)) { addReplySds(c,sdsnew("-ERR provide score is Not A Number (nan)\r\n")); return; } zsetobj = lookupKeyWrite(c->db,key); if (zsetobj == NULL) { zsetobj = createZsetObject(); dbAdd(c->db,key,zsetobj); } else { if (zsetobj->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } } zs = zsetobj->ptr; /* Ok now since we implement both ZADD and ZINCRBY here the code * needs to handle the two different conditions. It's all about setting * '*score', that is, the new score to set, to the right value. */ score = zmalloc(sizeof(double)); if (doincrement) { dictEntry *de; /* Read the old score. If the element was not present starts from 0 */ de = dictFind(zs->dict,ele); if (de) { double *oldscore = dictGetEntryVal(de); *score = *oldscore + scoreval; } else { *score = scoreval; } if (isnan(*score)) { addReplySds(c, sdsnew("-ERR resulting score is Not A Number (nan)\r\n")); zfree(score); /* 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; } } else { *score = scoreval; } /* What follows is a simple remove and re-insert operation that is common * to both ZADD and ZINCRBY... */ if (dictAdd(zs->dict,ele,score) == DICT_OK) { /* case 1: New element */ incrRefCount(ele); /* added to hash */ zslInsert(zs->zsl,*score,ele); incrRefCount(ele); /* added to skiplist */ touchWatchedKey(c->db,c->argv[1]); server.dirty++; if (doincrement) addReplyDouble(c,*score); else addReply(c,shared.cone); } else { dictEntry *de; double *oldscore; /* case 2: Score update operation */ de = dictFind(zs->dict,ele); redisAssert(de != NULL); oldscore = dictGetEntryVal(de); if (*score != *oldscore) { int deleted; /* Remove and insert the element in the skip list with new score */ deleted = zslDelete(zs->zsl,*oldscore,ele); redisAssert(deleted != 0); zslInsert(zs->zsl,*score,ele); incrRefCount(ele); /* Update the score in the hash table */ dictReplace(zs->dict,ele,score); touchWatchedKey(c->db,c->argv[1]); server.dirty++; } else { zfree(score); } if (doincrement) addReplyDouble(c,*score); else addReply(c,shared.czero); } }
/* Slot to Key API. This is used by Redis Cluster in order to obtain in * a fast way a key that belongs to a specified hash slot. This is useful * while rehashing the cluster. */ void slotToKeyAdd(robj *key) { unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); sds sdskey = sdsdup(key->ptr); zslInsert(server.cluster->slots_to_keys,hashslot,sdskey); }