void precisionCommand(client *c) { counter *cntr; double precision; cntr = counterLookup(c->argv[1]->ptr); if (cntr == NULL && c->argc == 2) { /* Counter doesn't exist, return the default precision. */ addReplyDouble(c, server.default_precision); return; } if (c->argc == 2) { addReplyDouble(c, cntr->precision); return; } if (getDoubleFromObjectOrReply(c, c->argv[2], &precision, NULL) != C_OK) return; if (cntr == NULL) { cntr = counterCreate(c->argv[1]->ptr); } cntr->precision = precision; server.dirty++; addReplyDouble(c, cntr->precision); }
void zscoreCommand(redisClient *c) { robj *o; zset *zs; dictEntry *de; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk); } else { double *score = dictGetEntryVal(de); addReplyDouble(c,*score); } }
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE and ZCOUNT. * If "justcount", only the number of elements in the range is returned. */ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) { zrangespec range; robj *o, *emptyreply; zset *zsetobj; zskiplist *zsl; zskiplistNode *ln; int offset = 0, limit = -1; int withscores = 0; unsigned long rangelen = 0; void *replylen = NULL; /* Parse the range arguments. */ if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { addReplyError(c,"min or max is not a double"); return; } /* Parse optional extra arguments. Note that ZCOUNT will exactly have * 4 arguments, so we'll never enter the following code path. */ if (c->argc > 4) { int remaining = c->argc - 4; int pos = 4; while (remaining) { if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) { pos++; remaining--; withscores = 1; } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) { offset = atoi(c->argv[pos+1]->ptr); limit = atoi(c->argv[pos+2]->ptr); pos += 3; remaining -= 3; } else { addReply(c,shared.syntaxerr); return; } } } /* Ok, lookup the key and get the range */ emptyreply = justcount ? shared.czero : shared.emptymultibulk; if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL || checkType(c,o,REDIS_ZSET)) return; zsetobj = o->ptr; zsl = zsetobj->zsl; /* If reversed, assume the elements are sorted from high to low score. */ ln = zslFirstWithScore(zsl,range.min); if (reverse) { /* If range.min is out of range, ln will be NULL and we need to use * the tail of the skiplist as first node of the range. */ if (ln == NULL) ln = zsl->tail; /* zslFirstWithScore returns the first element with where with * score >= range.min, so backtrack to make sure the element we use * here has score <= range.min. */ while (ln && ln->score > range.min) ln = ln->backward; /* Move to the right element according to the range spec. */ if (range.minex) { /* Find last element with score < range.min */ while (ln && ln->score == range.min) ln = ln->backward; } else { /* Find last element with score <= range.min */ while (ln && ln->level[0].forward && ln->level[0].forward->score == range.min) ln = ln->level[0].forward; } } else { if (range.minex) { /* Find first element with score > range.min */ while (ln && ln->score == range.min) ln = ln->level[0].forward; } } /* No "first" element in the specified interval. */ if (ln == NULL) { addReply(c,emptyreply); return; } /* We don't know in advance how many matching elements there * are in the list, so we push this object that will represent * the multi-bulk length in the output buffer, and will "fix" * it later */ if (!justcount) replylen = addDeferredMultiBulkLength(c); /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ while(ln && offset--) { if (reverse) ln = ln->backward; else ln = ln->level[0].forward; } while (ln && limit--) { /* Check if this this element is in range. */ if (reverse) { if (range.maxex) { /* Element should have score > range.max */ if (ln->score <= range.max) break; } else { /* Element should have score >= range.max */ if (ln->score < range.max) break; } } else { if (range.maxex) { /* Element should have score < range.max */ if (ln->score >= range.max) break; } else { /* Element should have score <= range.max */ if (ln->score > range.max) break; } } /* Do our magic */ rangelen++; if (!justcount) { addReplyBulk(c,ln->obj); if (withscores) addReplyDouble(c,ln->score); } if (reverse) ln = ln->backward; else ln = ln->level[0].forward; } if (justcount) { addReplyLongLong(c,(long)rangelen); } else { setDeferredMultiBulkLength(c,replylen, withscores ? (rangelen*2) : rangelen); } }
void zrangeGenericCommand(redisClient *c, int reverse) { robj *o; long start; long end; int withscores = 0; int llen; int rangelen, j; zset *zsetobj; zskiplist *zsl; zskiplistNode *ln; robj *ele; if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) { withscores = 1; } else if (c->argc >= 5) { addReply(c,shared.syntaxerr); 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; /* convert negative indexes */ if (start < 0) start = llen+start; if (end < 0) end = llen+end; if (start < 0) start = 0; /* Invariant: start >= 0, so this test will be true when end < 0. * The range is empty when start > end or start >= length. */ if (start > end || start >= llen) { addReply(c,shared.emptymultibulk); return; } if (end >= llen) end = llen-1; rangelen = (end-start)+1; /* check if starting point is trivial, before searching * the element in log(N) time */ if (reverse) { ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start); } else { ln = start == 0 ? zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1); } /* Return the result in form of a multi-bulk reply */ addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen); for (j = 0; j < rangelen; j++) { ele = ln->obj; addReplyBulk(c,ele); if (withscores) addReplyDouble(c,ln->score); ln = reverse ? ln->backward : ln->level[0].forward; } }
/* 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); } }
/* This command implements both ZRANGEBYSCORE and ZCOUNT. * If justcount is non-zero, just the count is returned. */ void genericZrangebyscoreCommand(redisClient *c, int justcount) { robj *o; double min, max; int minex = 0, maxex = 0; /* are min or max exclusive? */ int offset = 0, limit = -1; int withscores = 0; int badsyntax = 0; /* Parse the min-max interval. If one of the values is prefixed * by the "(" character, it's considered "open". For instance * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */ if (((char*)c->argv[2]->ptr)[0] == '(') { min = strtod((char*)c->argv[2]->ptr+1,NULL); minex = 1; } else { min = strtod(c->argv[2]->ptr,NULL); } if (((char*)c->argv[3]->ptr)[0] == '(') { max = strtod((char*)c->argv[3]->ptr+1,NULL); maxex = 1; } else { max = strtod(c->argv[3]->ptr,NULL); } /* Parse "WITHSCORES": note that if the command was called with * the name ZCOUNT then we are sure that c->argc == 4, so we'll never * enter the following paths to parse WITHSCORES and LIMIT. */ if (c->argc == 5 || c->argc == 8) { if (strcasecmp(c->argv[c->argc-1]->ptr,"withscores") == 0) withscores = 1; else badsyntax = 1; } if (c->argc != (4 + withscores) && c->argc != (7 + withscores)) badsyntax = 1; if (badsyntax) { addReplySds(c, sdsnew("-ERR wrong number of arguments for ZRANGEBYSCORE\r\n")); return; } /* Parse "LIMIT" */ if (c->argc == (7 + withscores) && strcasecmp(c->argv[4]->ptr,"limit")) { addReply(c,shared.syntaxerr); return; } else if (c->argc == (7 + withscores)) { offset = atoi(c->argv[5]->ptr); limit = atoi(c->argv[6]->ptr); if (offset < 0) offset = 0; } /* Ok, lookup the key and get the range */ o = lookupKeyRead(c->db,c->argv[1]); if (o == NULL) { addReply(c,justcount ? shared.czero : shared.emptymultibulk); } else { if (o->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); } else { zset *zsetobj = o->ptr; zskiplist *zsl = zsetobj->zsl; zskiplistNode *ln; robj *ele, *lenobj = NULL; unsigned long rangelen = 0; /* Get the first node with the score >= min, or with * score > min if 'minex' is true. */ ln = zslFirstWithScore(zsl,min); while (minex && ln && ln->score == min) ln = ln->forward[0]; if (ln == NULL) { /* No element matching the speciifed interval */ addReply(c,justcount ? shared.czero : shared.emptymultibulk); return; } /* We don't know in advance how many matching elements there * are in the list, so we push this object that will represent * the multi-bulk length in the output buffer, and will "fix" * it later */ if (!justcount) { lenobj = createObject(REDIS_STRING,NULL); addReply(c,lenobj); decrRefCount(lenobj); } while(ln && (maxex ? (ln->score < max) : (ln->score <= max))) { if (offset) { offset--; ln = ln->forward[0]; continue; } if (limit == 0) break; if (!justcount) { ele = ln->obj; addReplyBulk(c,ele); if (withscores) addReplyDouble(c,ln->score); } ln = ln->forward[0]; rangelen++; if (limit > 0) limit--; } if (justcount) { addReplyLongLong(c,(long)rangelen); } else { lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n", withscores ? (rangelen*2) : rangelen); } } } }
/* 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); } }