1/* t_zset.c -- zset data type implementation.2 *3 * Copyright (c) 2009-Present, Redis Ltd.4 * All rights reserved.5 *6 * Copyright (c) 2024-present, Valkey contributors.7 * All rights reserved.8 *9 * Licensed under your choice of (a) the Redis Source Available License 2.010 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the11 * GNU Affero General Public License v3 (AGPLv3).12 *13 * Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.14 */1516/*-----------------------------------------------------------------------------17 * Sorted set API18 *----------------------------------------------------------------------------*/1920/* ZSETs are ordered sets using two data structures to hold the same elements21 * in order to get O(log(N)) INSERT and REMOVE operations into a sorted22 * data structure.23 *24 * The elements are added to a hash table mapping Redis objects to scores.25 * At the same time the elements are added to a skip list mapping scores26 * to Redis objects (so objects are sorted by scores in this "view").27 *28 * Note that the SDS string representing the element is the same in both29 * the hash table and skiplist in order to save memory. What we do in order30 * to manage the shared SDS string more easily is to free the SDS string31 * only in zslFreeNode(). The dictionary has no value free method set.32 * So we should always remove an element from the dictionary, and later from33 * the skiplist.34 *35 * This skiplist implementation is almost a C translation of the original36 * algorithm described by William Pugh in "Skip Lists: A Probabilistic37 * Alternative to Balanced Trees", modified in three ways:38 * a) this implementation allows for repeated scores.39 * b) the comparison is not just by key (our 'score') but by satellite data.40 * c) there is a back pointer, so it's a doubly linked list with the back41 * pointers being only at "level 1". This allows to traverse the list42 * from tail to head, useful for ZREVRANGE. */43#include "fast_float_strtod.h"44#include "server.h"45#include "intset.h" /* Compact integer set structure */46#include <math.h>4748#define ZSL_OFFSET_MAX_ELE UINT16_MAX49#define ZSL_OFFSET_NO_ELE UINT16_MAX5051const void *zslGetNodeElementForDict(const void *node);5253/* dictType for zset's dict (maps sds to zskiplistNode*) */54dictType zsetDictType = {55 dictSdsHash, /* hash function */56 NULL, /* key dup */57 NULL, /* val dup */58 dictSdsKeyCompare, /* compares embedded sds by keyFromStoredKey */59 NULL, /* key destructor - skiplist owns the node memory */60 NULL, /* val destructor */61 NULL, /* allow to expand */62 .no_value = 1, /* no values stored (only nodes) */63 .keyFromStoredKey = zslGetNodeElementForDict, /* extract embedded sds from node */64};6566/*-----------------------------------------------------------------------------67 * Skiplist implementation of the low level API68 *----------------------------------------------------------------------------*/6970int zslLexValueGteMin(sds value, zlexrangespec *spec);71int zslLexValueLteMax(sds value, zlexrangespec *spec);72void zsetConvertAndExpand(robj *zobj, int encoding, unsigned long cap);73static zskiplistNode *zslGetElementByRankFromNode(zskiplistNode *start_node, int start_level, unsigned long rank);7475static inline unsigned long zslGetNodeSpanAtLevel(zskiplistNode *x, int level) {76 /* At level 0, span stores node level instead of distance, so return the actual span value:77 * 1 for all nodes except the last node (which has span 0). */78 if (level > 0) return x->level[level].span;79 /* For level 0, if regular node, span is 1. If tail node, span is 0. */80 return x->level[0].forward ? 1 : 0;81}8283static inline void zslSetNodeSpanAtLevel(zskiplistNode *x, int level, unsigned long span) {84 /* Skip level 0 since it stores node level, not span. */85 if (level > 0)86 x->level[level].span = span;87}8889static inline void zslIncrNodeSpanAtLevel(zskiplistNode *x, int level, unsigned long incr) {90 /* Skip level 0 since it stores node level, not span. */91 if (level > 0)92 x->level[level].span += incr;93}9495static inline void zslDecrNodeSpanAtLevel(zskiplistNode *x, int level, unsigned long decr) {96 /* Skip level 0 since it stores node level, not span. */97 if (level > 0)98 x->level[level].span -= decr;99}100101/* Get zskiplistNodeInfo from node (stored in level[0].span). */102static_assert(sizeof(zskiplistNodeInfo) <= sizeof(((zskiplistNode *)0)->level[0].span), "Must fit in level[0].span");103static inline zskiplistNodeInfo *zslGetNodeInfo(const zskiplistNode *node) {104 return (zskiplistNodeInfo *)&node->level[0].span;105}106107/* Set zskiplistNodeInfo in node (stored in level[0].span) */108static inline void zslSetNodeInfo(zskiplistNode *node, uint8_t levels, uint16_t sdsoffset) {109 union {110 zskiplistNodeInfo info;111 unsigned long span;112 } u = { .info = { .levels = levels, .sdsoffset = sdsoffset } };113 node->level[0].span = u.span;114}115116/* Compare {score, ele} with node. Returns: 1=bigger 0=equal -1=smaller117 *118 * Ordering is by score first, then lexicographically by element.119 * NULL is treated as +infinity (comes after any real node). */120int zslCompareWithNode(double score, sds ele, const zskiplistNode *n) {121 if (/*score < */ n == NULL) return -1; /* NULL is +infinity, comes after any real node */122 if (score < n->score) return -1;123 if (score > n->score) return 1;124 /* Scores are equal, compare elements lexicographically */125 return sdscmp(ele, zslGetNodeElement(n));126}127128/* Get embedded sds from node. Uses the stored offset to directly access the sds data */129sds zslGetNodeElement(const zskiplistNode *node) {130 zskiplistNodeInfo *info = zslGetNodeInfo(node);131 debugServerAssert(info->sdsoffset != ZSL_OFFSET_NO_ELE);132 return (char*)node + info->sdsoffset;133}134135/* Wrapper for dict getKeyId callback - extracts sds from node pointer.136 * This allows the dict to store zskiplistNode* but look them up using sds. */137const void *zslGetNodeElementForDict(const void *node) {138 return zslGetNodeElement((zskiplistNode*)node);139}140141/* Create a skiplist header node with ZSKIPLIST_MAXLEVEL levels */142static zskiplistNode *zslCreateHeaderNode(zskiplist *zsl) {143 size_t usable;144 zskiplistNode *zn = zmalloc_usable(sizeof(*zn) + ZSKIPLIST_MAXLEVEL * sizeof(struct zskiplistLevel), &usable);145146 /* Initialize all fields */147 zn->score = 0;148 zn->backward = NULL;149150 /* Initialize all level pointers and spans */151 for (int j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {152 zn->level[j].forward = NULL;153 zn->level[j].span = 0; /* Will be overwritten for level[0] below */154 }155156 /* Use ZSL_OFFSET_NO_ELE as sentinel to indicate no embedded sds (header node) */157 zslSetNodeInfo(zn, ZSKIPLIST_MAXLEVEL, ZSL_OFFSET_NO_ELE);158159 /* Track allocation size */160 zsl->alloc_size += usable;161162 return zn;163}164165/* Create a skiplist node with the specified number of levels.166 * The SDS string 'ele' is COPIED into an embedded sds within the node allocation.167 * This creates a single allocation containing: node + level[] + embedded sds.168 * The caller is responsible for freeing 'ele' if it's no longer needed. */169static zskiplistNode *zslCreateNode(zskiplist *zsl, int level, double score, sds ele) {170 size_t usable;171 size_t ele_len = sdslen(ele);172 char sds_type = sdsReqType(ele_len);173 size_t sds_hdr_len = sdsHdrSize(sds_type);174175 /* Calculate total size: node fixed part + level[] + sds buffer space */176 size_t node_size = sizeof(zskiplistNode) + level * sizeof(struct zskiplistLevel);177 size_t sds_buf_size = sds_hdr_len + ele_len + 1; /* header + data + null terminator */178 size_t total_size = node_size + sds_buf_size;179180 /* Allocate single block for everything */181 zskiplistNode *zn = zmalloc_usable(total_size, &usable);182183 /* Initialize node fields */184 zn->score = score;185 zn->backward = NULL;186187 /* Calculate offset from node start to sds data (after sds header) */188 size_t sds_offset = node_size + sds_hdr_len;189 debugServerAssert(sds_offset < ZSL_OFFSET_MAX_ELE);190191 /* Initialize embedded sds using sdsnewplacement */192 char *sds_buf = (char*)zn + node_size;193 sds embedded_sds = sdsnewplacement(sds_buf, sds_buf_size, sds_type, ele, ele_len);194195 /* Store node info in level[0].span */196 zslSetNodeInfo(zn, level, sds_offset);197198 /* Verify that embedded_sds matches our calculated offset */199 serverAssert(embedded_sds == (sds)((char*)zn + sds_offset));200201 /* Update allocation size tracking */202 zsl->alloc_size += usable;203204 return zn;205}206207/* Create a new skiplist. */208zskiplist *zslCreate(void) {209 zskiplist *zsl;210 size_t zsl_size;211212 zsl = zmalloc_usable(sizeof(*zsl), &zsl_size);213 zsl->level = 1;214 zsl->length = 0;215 zsl->alloc_size = zsl_size;216 zsl->header = zslCreateHeaderNode(zsl);217 zsl->header->backward = NULL;218 zsl->tail = NULL;219 return zsl;220}221222/* Free the specified skiplist node. The embedded SDS is freed as part of223 * the single allocation (node + level[] + embedded sds). */224static void zslFreeNode(zskiplist *zsl, zskiplistNode *node) {225 size_t usable;226 /* No separate sdsfree() needed - embedded sds is part of node allocation */227 zfree_usable(node, &usable);228 zsl->alloc_size -= usable;229}230231/* Free a whole skiplist. */232void zslFree(zskiplist *zsl) {233 zskiplistNode *node = zsl->header->level[0].forward, *next;234 size_t usable;235236 zfree_usable(zsl->header, &usable);237 zsl->alloc_size -= usable;238 while(node) {239 next = node->level[0].forward;240 zslFreeNode(zsl, node);241 node = next;242 }243 debugServerAssert(zsl->alloc_size == zmalloc_usable_size(zsl));244 zfree(zsl);245}246247/* Return cached total memory used (in bytes) */248size_t zslAllocSize(const zskiplist *zsl) { return zsl->alloc_size; }249250/* Returns a random level for the new skiplist node we are going to create.251 * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL252 * (both inclusive), with a powerlaw-alike distribution where higher253 * levels are less likely to be returned. */254static int zslRandomLevel(void) {255 static const int threshold = ZSKIPLIST_P*RAND_MAX;256 int level = 1;257 while (random() < threshold)258 level += 1;259 return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;260}261262/* Insert an already-created node, with its score, element set into the skiplist 263 * at the correct position. Updates all forward/backward pointers and spans.264 * The node's level must already be set via zslSetNodeInfo(). */265static void zslInsertNode(zskiplist *zsl, zskiplistNode *node) {266 zskiplistNode *update[ZSKIPLIST_MAXLEVEL]; /* Nodes that will point to the new node at each level */267 unsigned long rank[ZSKIPLIST_MAXLEVEL]; /* Rank (0-based) at each level during traversal */268 zskiplistNode *x;269 int i, level;270 double score = node->score;271 sds ele = zslGetNodeElement(node);272 level = zslGetNodeInfo(node)->levels;273 serverAssert(!isnan(score));274275 /* Find the position where this node should be inserted */276 x = zsl->header;277 for (i = zsl->level-1; i >= 0; i--) {278 /* store rank that is crossed to reach the insert position */279 rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];280 while (zslCompareWithNode(score, ele, x->level[i].forward) > 0) {281 rank[i] += zslGetNodeSpanAtLevel(x, i);282 x = x->level[i].forward;283 }284 update[i] = x;285 }286287 /* Update skiplist level if needed */288 if (level > zsl->level) {289 for (i = zsl->level; i < level; i++) {290 rank[i] = 0;291 update[i] = zsl->header;292 zslSetNodeSpanAtLevel(update[i], i, zsl->length);293 }294 zsl->level = level;295 zslGetNodeInfo(zsl->header)->levels = level;296 }297298 /* Insert the node at the found position */299 for (i = 0; i < level; i++) {300 node->level[i].forward = update[i]->level[i].forward;301 update[i]->level[i].forward = node;302303 /* update span covered by update[i] as node is inserted here */304 zslSetNodeSpanAtLevel(node, i, zslGetNodeSpanAtLevel(update[i], i) - (rank[0] - rank[i]));305 zslSetNodeSpanAtLevel(update[i], i, (rank[0] - rank[i]) + 1);306 }307308 /* increment span for untouched levels */309 for (i = level; i < zsl->level; i++) {310 zslIncrNodeSpanAtLevel(update[i], i, 1);311 }312313 /* Update backward pointers */314 node->backward = (update[0] == zsl->header) ? NULL : update[0];315 if (node->level[0].forward)316 node->level[0].forward->backward = node;317 else318 zsl->tail = node;319320 zsl->length++;321}322323/* Insert a new node in the skiplist. Assumes the element does not already324 * exist (up to the caller to enforce that). The element 'ele' is COPIED325 * into the new node, so the caller retains ownership and can free it. */326zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {327 int level;328329 serverAssert(!isnan(score));330331 /* we assume the element is not already inside, since we allow duplicated332 * scores, reinserting the same element should never happen since the333 * caller of zslInsert() should test in the hash table if the element is334 * already inside or not. */335 level = zslRandomLevel();336 zskiplistNode *node = zslCreateNode(zsl, level, score, ele);337 zslInsertNode(zsl, node);338 return node;339}340341/* Internal function used by zslDelete, zslDeleteRangeByScore and342 * zslDeleteRangeByRank.343 * This function only unlinks the node from the skiplist structure but does NOT free it.344 * The caller is responsible for freeing the node with zslFreeNode(). */345static void zslUnlinkNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {346 int i;347 for (i = 0; i < zsl->level; i++) {348 if (update[i]->level[i].forward == x) {349 zslIncrNodeSpanAtLevel(update[i], i, zslGetNodeSpanAtLevel(x, i) - 1);350 update[i]->level[i].forward = x->level[i].forward;351 } else {352 zslDecrNodeSpanAtLevel(update[i], i, 1);353 }354 }355 if (x->level[0].forward) {356 x->level[0].forward->backward = x->backward;357 } else {358 zsl->tail = x->backward;359 }360 /* Decrease skiplist level if top levels are empty, and clear their spans */361 while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL) {362 zsl->header->level[zsl->level-1].span = 0;363 zsl->level--;364 }365 zsl->length--;366}367368/* Delete the specified node from the skiplist.369 * The node is unlinked from all levels and then freed by zslFreeNode(),370 * which also frees the embedded SDS string. */371static void zslDelete(zskiplist *zsl, zskiplistNode *node) {372 zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;373 int i;374 double score = node->score;375 sds ele = zslGetNodeElement(node);376377 x = zsl->header;378 for (i = zsl->level-1; i >= 0; i--) {379 while (zslCompareWithNode(score, ele, x->level[i].forward) > 0) {380 x = x->level[i].forward;381 }382 update[i] = x;383 }384385 /* Verify we truly found the node */386 serverAssert(x->level[0].forward == node);387388 zslUnlinkNode(zsl, node, update);389 zslFreeNode(zsl, node);390}391392/* Update the score of an element inside the sorted set skiplist.393 * If the new score would keep the node in its current position, updates in-place and returns NULL.394 * Otherwise, unlinks the node, updates score, reinserts at correct position, and returns node.395 * Anyway, the node pointer stays the same (no dict update needed). */396static void zslUpdateScore(zskiplist *zsl, zskiplistNode *node, double newscore) {397 /* Fast path: if the node, after the score update, would be still exactly398 * at the same position, we can just update the score without399 * actually removing and re-inserting the element in the skiplist. */400 if ((node->backward == NULL || node->backward->score < newscore) &&401 (node->level[0].forward == NULL || node->level[0].forward->score > newscore))402 {403 node->score = newscore;404 return;405 }406407 /* Slow path: need to reposition the node.408 * Find the update[] array for unlinking. */409 zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;410 int i;411 double curscore = node->score;412 sds ele = zslGetNodeElement(node);413414 x = zsl->header;415 for (i = zsl->level-1; i >= 0; i--) {416 while (zslCompareWithNode(curscore, ele, x->level[i].forward) > 0) {417 x = x->level[i].forward;418 }419 update[i] = x;420 }421422 /* Verify we found the right node */423 serverAssert(x->level[0].forward == node);424425 /* Unlink, update score, and reinsert at new position.426 * We reuse the same node to avoid dict updates. */427 zslUnlinkNode(zsl, node, update);428 node->score = newscore;429 zslInsertNode(zsl, node);430}431432int zslValueGteMin(double value, zrangespec *spec) {433 return spec->minex ? (value > spec->min) : (value >= spec->min);434}435436int zslValueLteMax(double value, zrangespec *spec) {437 return spec->maxex ? (value < spec->max) : (value <= spec->max);438}439440/* Returns if there is a part of the zset is in range. */441static int zslIsInRange(zskiplist *zsl, zrangespec *range) {442 zskiplistNode *x;443444 /* Test for ranges that will always be empty. */445 if (range->min > range->max ||446 (range->min == range->max && (range->minex || range->maxex)))447 return 0;448 x = zsl->tail;449 if (x == NULL || !zslValueGteMin(x->score,range))450 return 0;451 x = zsl->header->level[0].forward;452 if (x == NULL || !zslValueLteMax(x->score,range))453 return 0;454 return 1;455}456457/* Find the Nth element within the specified score range.458 *459 * Parameters:460 * - N is 0-based for forward direction (0 = first element in range)461 * - N can be negative for reverse direction (-1 = last element in range)462 *463 * Returns:464 * - The skiplist node at position N within the range, or NULL if:465 * * N is out of bounds for the range466 * * The range contains no elements467 * - If out_rank!=NULL, it receives the 1-based absolute rank of the returned node 468 */469zskiplistNode *zslNthInRange(zskiplist *zsl, zrangespec *range, long n, unsigned long *out_rank) {470 zskiplistNode *x;471 int i;472 long edge_rank = 0; /* 0-based rank of the last element smaller than the range. */473 long last_highest_level_rank = 0;474 zskiplistNode *last_highest_level_node = NULL;475 unsigned long rank_diff;476477 /* If everything is out of range, return early. */478 if (!zslIsInRange(zsl,range)) return NULL;479480 /* Go forward while *OUT* of range at level of zsl->level-1. */481 x = zsl->header;482 i = zsl->level - 1;483 while (x->level[i].forward && !zslValueGteMin(x->level[i].forward->score, range)) {484 edge_rank += zslGetNodeSpanAtLevel(x, i);485 x = x->level[i].forward;486 }487 /* Remember the last node which has zsl->level-1 levels and its rank. */488 last_highest_level_node = x;489 last_highest_level_rank = edge_rank;490491 if (n >= 0) {492 for (i = zsl->level - 2; i >= 0; i--) {493 /* Go forward while *OUT* of range. */494 while (x->level[i].forward && !zslValueGteMin(x->level[i].forward->score, range)) {495 /* Count the rank of the last element smaller than the range. */496 edge_rank += zslGetNodeSpanAtLevel(x, i);497 x = x->level[i].forward;498 }499 }500 /* Check if zsl is long enough. */501 if ((unsigned long)(edge_rank + n) >= zsl->length) return NULL;502 if (n < ZSKIPLIST_MAX_SEARCH) {503 /* If offset is small, we can just jump node by node */504 /* rank+1 is the first element in range, so we need n+1 steps to reach target. */505 for (i = 0; i < n + 1; i++) { 506 x = x->level[0].forward;507 }508 } else {509 /* If offset is big, we can jump from the last zsl->level-1 node. */510 rank_diff = edge_rank + 1 + n - last_highest_level_rank;511 x = zslGetElementByRankFromNode(last_highest_level_node, zsl->level - 1, rank_diff);512 }513 /* Check if score <= max. */514 if (x && !zslValueLteMax(x->score,range)) return NULL;515 /* Store rank if requested. For n >= 0, the returned node is at rank edge_rank + n + 1. */516 if (x && out_rank) *out_rank = edge_rank + n + 1;517 } else {518 for (i = zsl->level - 1; i >= 0; i--) {519 /* Go forward while *IN* range. */520 while (x->level[i].forward && zslValueLteMax(x->level[i].forward->score, range)) {521 /* Count the rank of the last element in range. */522 edge_rank += zslGetNodeSpanAtLevel(x, i);523 x = x->level[i].forward;524 }525 }526 /* Check if the range is big enough. */527 if (edge_rank < -n) return NULL;528 if (n + 1 > -ZSKIPLIST_MAX_SEARCH) {529 /* If offset is small, we can just jump node by node */530 /* rank is the -1th element in range, so we need -n-1 steps to reach target. */531 for (i = 0; i < -n - 1; i++) {532 x = x->backward;533 }534 } else {535 /* If offset is big, we can jump from the last zsl->level-1 node. */536 /* rank is the last element in range, n is -1-based, so we need n+1 to count backwards. */537 rank_diff = edge_rank + 1 + n - last_highest_level_rank;538 x = zslGetElementByRankFromNode(last_highest_level_node, zsl->level - 1, rank_diff);539 }540 /* Check if score >= min. */541 if (x && !zslValueGteMin(x->score, range)) return NULL;542 /* Store rank if requested. For n < 0, the returned node is at rank edge_rank + n + 1. */543 if (x && out_rank) *out_rank = edge_rank + n + 1;544 }545546 return x;547}548549/* Delete all the elements with score between min and max from the skiplist.550 * Both min and max can be inclusive or exclusive (see range->minex and551 * range->maxex). When inclusive a score >= min && score <= max is deleted.552 * Note that this function takes the reference to the hash table view of the553 * sorted set, in order to remove the elements from the hash table too. */554static unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec *range, dict *dict) {555 zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;556 unsigned long removed = 0;557 int i;558559 x = zsl->header;560 for (i = zsl->level-1; i >= 0; i--) {561 while (x->level[i].forward &&562 !zslValueGteMin(x->level[i].forward->score, range))563 x = x->level[i].forward;564 update[i] = x;565 }566567 /* Current node is the last with score < or <= min. */568 x = x->level[0].forward;569570 /* Delete nodes while in range. */571 while (x && zslValueLteMax(x->score, range)) {572 zskiplistNode *next = x->level[0].forward;573 zslUnlinkNode(zsl,x,update);574 dictDelete(dict,zslGetNodeElement(x));575 zslFreeNode(zsl, x); /* Here is where x->ele is actually released. */576 removed++;577 x = next;578 }579 return removed;580}581582static unsigned long zslDeleteRangeByLex(zskiplist *zsl, zlexrangespec *range, dict *dict) {583 zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;584 unsigned long removed = 0;585 int i;586587588 x = zsl->header;589 for (i = zsl->level-1; i >= 0; i--) {590 while (x->level[i].forward &&591 !zslLexValueGteMin(zslGetNodeElement(x->level[i].forward),range))592 x = x->level[i].forward;593 update[i] = x;594 }595596 /* Current node is the last with score < or <= min. */597 x = x->level[0].forward;598599 /* Delete nodes while in range. */600 while (x && zslLexValueLteMax(zslGetNodeElement(x),range)) {601 zskiplistNode *next = x->level[0].forward;602 zslUnlinkNode(zsl,x,update);603 dictDelete(dict,zslGetNodeElement(x));604 zslFreeNode(zsl, x); /* Here is where x->ele is actually released. */605 removed++;606 x = next;607 }608 return removed;609}610611/* Delete all the elements with rank between start and end from the skiplist.612 * Start and end are inclusive. Note that start and end need to be 1-based */613static unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned int end, dict *dict) {614 zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;615 unsigned long traversed = 0, removed = 0;616 int i;617618 x = zsl->header;619 for (i = zsl->level-1; i >= 0; i--) {620 while (x->level[i].forward && (traversed + zslGetNodeSpanAtLevel(x, i)) < start) {621 traversed += zslGetNodeSpanAtLevel(x, i);622 x = x->level[i].forward;623 }624 update[i] = x;625 }626627 traversed++;628 x = x->level[0].forward;629 while (x && traversed <= end) {630 zskiplistNode *next = x->level[0].forward;631 zslUnlinkNode(zsl,x,update);632 dictDelete(dict,zslGetNodeElement(x));633 zslFreeNode(zsl, x);634 removed++;635 traversed++;636 x = next;637 }638 return removed;639}640641/* Find the rank for an element by both score and key.642 * Returns 0 when the element cannot be found, rank otherwise.643 * Note that the rank is 1-based due to the span of zsl->header to the644 * first element. */645unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {646 zskiplistNode *x;647 unsigned long rank = 0;648 int i;649650 x = zsl->header;651 for (i = zsl->level-1; i >= 0; i--) {652 while (zslCompareWithNode(score, ele, x->level[i].forward) >= 0) {653 rank += zslGetNodeSpanAtLevel(x, i);654 x = x->level[i].forward;655 }656657 if (x != zsl->header && zslCompareWithNode(score, ele, x) == 0) {658 return rank;659 }660 }661 return 0;662}663664/* Find the rank for a skiplist node by walking forward from the node to the end.665 * This avoids expensive string comparisons during traversal. The algorithm:666 * 1. Start at the given node's top level667 * 2. Walk forward to the tail, jumping at each node's top level668 * 3. Sum the spans to get distance from node to end669 * 4. Calculate rank as (list_length - distance_to_end)670 * Time complexity: O(log N) on average, same as traditional approach but faster671 * due to avoiding string comparisons. */672unsigned long zslGetRankByNode(zskiplist *zsl, zskiplistNode *x) {673 unsigned long distance_to_end = 0;674 int level;675 676 /* Walk forward from x to the end, using top level of each node for fast jumps */677 while (x) {678 level = zslGetNodeInfo(x)->levels - 1;679 distance_to_end += zslGetNodeSpanAtLevel(x, level);680 x = x->level[level].forward;681 }682 683 /* Rank = total nodes - nodes after this one */684 return zsl->length - distance_to_end;685}686687/* Finds an element by its rank from start node. The rank argument needs to be 1-based. */688static zskiplistNode *zslGetElementByRankFromNode(zskiplistNode *start_node, int start_level, unsigned long rank) {689 zskiplistNode *x;690 unsigned long traversed = 0;691 int i;692693 x = start_node;694 for (i = start_level; i >= 0; i--) {695 while (x->level[i].forward && (traversed + zslGetNodeSpanAtLevel(x, i)) <= rank)696 {697 traversed += zslGetNodeSpanAtLevel(x, i);698 x = x->level[i].forward;699 }700 if (traversed == rank) {701 return x;702 }703 }704 return NULL;705}706707/* Finds an element by its rank. The rank argument needs to be 1-based. */708zskiplistNode *zslGetElementByRank(zskiplist *zsl, unsigned long rank) {709 return zslGetElementByRankFromNode(zsl->header, zsl->level - 1, rank);710}711712/* Populate the rangespec according to the objects min and max. */713static int zslParseRange(robj *min, robj *max, zrangespec *spec) {714 char *eptr;715 spec->minex = spec->maxex = 0;716717 /* Parse the min-max interval. If one of the values is prefixed718 * by the "(" character, it's considered "open". For instance719 * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max720 * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */721 if (min->encoding == OBJ_ENCODING_INT) {722 spec->min = (long)min->ptr;723 } else {724 size_t len = sdslen(min->ptr);725 if (((char*)min->ptr)[0] == '(') {726 spec->min = fast_float_strtod((char*)min->ptr+1,len-1,&eptr);727 if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;728 spec->minex = 1;729 } else {730 spec->min = fast_float_strtod((char*)min->ptr,len,&eptr);731 if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;732 }733 }734 if (max->encoding == OBJ_ENCODING_INT) {735 spec->max = (long)max->ptr;736 } else {737 size_t len = sdslen(max->ptr);738 if (((char*)max->ptr)[0] == '(') {739 spec->max = fast_float_strtod((char*)max->ptr+1,len-1,&eptr);740 if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;741 spec->maxex = 1;742 } else {743 spec->max = fast_float_strtod((char*)max->ptr,len,&eptr);744 if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;745 }746 }747748 return C_OK;749}750751/* ------------------------ Lexicographic ranges ---------------------------- */752753/* Parse max or min argument of ZRANGEBYLEX.754 * (foo means foo (open interval)755 * [foo means foo (closed interval)756 * - means the min string possible757 * + means the max string possible758 *759 * If the string is valid the *dest pointer is set to the redis object760 * that will be used for the comparison, and ex will be set to 0 or 1761 * respectively if the item is exclusive or inclusive. C_OK will be762 * returned.763 *764 * If the string is not a valid range C_ERR is returned, and the value765 * of *dest and *ex is undefined. */766static int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {767 char *c = item->ptr;768769 switch(c[0]) {770 case '+':771 if (c[1] != '\0') return C_ERR;772 *ex = 1;773 *dest = shared.maxstring;774 return C_OK;775 case '-':776 if (c[1] != '\0') return C_ERR;777 *ex = 1;778 *dest = shared.minstring;779 return C_OK;780 case '(':781 *ex = 1;782 *dest = sdsnewlen(c+1,sdslen(c)-1);783 return C_OK;784 case '[':785 *ex = 0;786 *dest = sdsnewlen(c+1,sdslen(c)-1);787 return C_OK;788 default:789 return C_ERR;790 }791}792793/* Free a lex range structure, must be called only after zslParseLexRange()794 * populated the structure with success (C_OK returned). */795void zslFreeLexRange(zlexrangespec *spec) {796 if (spec->min != shared.minstring &&797 spec->min != shared.maxstring) sdsfree(spec->min);798 if (spec->max != shared.minstring &&799 spec->max != shared.maxstring) sdsfree(spec->max);800}801802/* Populate the lex rangespec according to the objects min and max.803 *804 * Return C_OK on success. On error C_ERR is returned.805 * When OK is returned the structure must be freed with zslFreeLexRange(),806 * otherwise no release is needed. */807int zslParseLexRange(robj *min, robj *max, zlexrangespec *spec) {808 /* The range can't be valid if objects are integer encoded.809 * Every item must start with ( or [. */810 if (min->encoding == OBJ_ENCODING_INT ||811 max->encoding == OBJ_ENCODING_INT) return C_ERR;812813 spec->min = spec->max = NULL;814 if (zslParseLexRangeItem(min, &spec->min, &spec->minex) == C_ERR ||815 zslParseLexRangeItem(max, &spec->max, &spec->maxex) == C_ERR) {816 zslFreeLexRange(spec);817 return C_ERR;818 } else {819 return C_OK;820 }821}822823/* This is just a wrapper to sdscmp() that is able to824 * handle shared.minstring and shared.maxstring as the equivalent of825 * -inf and +inf for strings */826static int sdscmplex(sds a, sds b) {827 if (a == b) return 0;828 if (a == shared.minstring || b == shared.maxstring) return -1;829 if (a == shared.maxstring || b == shared.minstring) return 1;830 return sdscmp(a,b);831}832833int zslLexValueGteMin(sds value, zlexrangespec *spec) {834 return spec->minex ?835 (sdscmplex(value,spec->min) > 0) :836 (sdscmplex(value,spec->min) >= 0);837}838839int zslLexValueLteMax(sds value, zlexrangespec *spec) {840 return spec->maxex ?841 (sdscmplex(value,spec->max) < 0) :842 (sdscmplex(value,spec->max) <= 0);843}844845/* Returns if there is a part of the zset is in the lex range. */846static int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {847 zskiplistNode *x;848849 /* Test for ranges that will always be empty. */850 int cmp = sdscmplex(range->min,range->max);851 if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))852 return 0;853 x = zsl->tail;854 if ((x == NULL) || (!zslLexValueGteMin(zslGetNodeElement(x),range)))855 return 0;856 x = zsl->header->level[0].forward;857 if ((x == NULL) || (!zslLexValueLteMax(zslGetNodeElement(x),range)))858 return 0;859 return 1;860}861862/* Find the Nth node that is contained in the specified range. N should be 0-based.863 * Negative N works for reversed order (-1 represents the last element). Returns864 * NULL when no element is contained in the range.865 * If out_rank is not NULL, stores the 1-based rank of the returned node. */866zskiplistNode *zslNthInLexRange(zskiplist *zsl, zlexrangespec *range, long n, unsigned long *out_rank) {867 zskiplistNode *x;868 int i;869 long edge_rank = 0;870 long last_highest_level_rank = 0;871 zskiplistNode *last_highest_level_node = NULL;872 unsigned long rank_diff;873874 /* If everything is out of range, return early. */875 if (!zslIsInLexRange(zsl,range)) return NULL;876877 /* Go forward while *OUT* of range at level of zsl->level-1. */878 x = zsl->header;879 i = zsl->level - 1;880 while (x->level[i].forward && !zslLexValueGteMin(zslGetNodeElement(x->level[i].forward), range)) {881 edge_rank += zslGetNodeSpanAtLevel(x, i);882 x = x->level[i].forward;883 }884 /* Remember the last node which has zsl->level-1 levels and its rank. */885 last_highest_level_node = x;886 last_highest_level_rank = edge_rank;887888 if (n >= 0) {889 for (i = zsl->level - 2; i >= 0; i--) {890 /* Go forward while *OUT* of range. */891 while (x->level[i].forward && !zslLexValueGteMin(zslGetNodeElement(x->level[i].forward), range)) {892 /* Count the rank of the last element smaller than the range. */893 edge_rank += zslGetNodeSpanAtLevel(x, i);894 x = x->level[i].forward;895 }896 }897 /* Check if zsl is long enough. */898 if ((unsigned long)(edge_rank + n) >= zsl->length) return NULL; 899 if (n < ZSKIPLIST_MAX_SEARCH) {900 /* If offset is small, we can just jump node by node */901 /* rank+1 is the first element in range, so we need n+1 steps to reach target. */902 for (i = 0; i < n + 1; i++) { 903 x = x->level[0].forward;904 }905 } else {906 /* If offset is big, we can jump from the last zsl->level-1 node. */907 rank_diff = edge_rank + 1 + n - last_highest_level_rank;908 x = zslGetElementByRankFromNode(last_highest_level_node, zsl->level - 1, rank_diff);909 }910 /* Check if score <= max. */911 if (x && !zslLexValueLteMax(zslGetNodeElement(x),range)) return NULL;912 /* Store rank if requested. For n >= 0, the returned node is at rank edge_rank + n + 1. */913 if (x && out_rank) *out_rank = edge_rank + n + 1;914 } else {915 for (i = zsl->level - 1; i >= 0; i--) {916 /* Go forward while *IN* range. */917 while (x->level[i].forward && zslLexValueLteMax(zslGetNodeElement(x->level[i].forward), range)) {918 /* Count the rank of the last element in range. */919 edge_rank += zslGetNodeSpanAtLevel(x, i);920 x = x->level[i].forward;921 }922 }923 /* Check if the range is big enough. */924 if (edge_rank < -n) return NULL;925 if (n + 1 > -ZSKIPLIST_MAX_SEARCH) {926 /* If offset is small, we can just jump node by node */927 for (i = 0; i < -n - 1; i++) {928 x = x->backward;929 }930 } else {931 /* If offset is big, we can jump from the last zsl->level-1 node. */932 /* rank is the last element in range, n is -1-based, so we need n+1 to count backwards. */933 rank_diff = edge_rank + 1 + n - last_highest_level_rank;934 x = zslGetElementByRankFromNode(last_highest_level_node, zsl->level - 1, rank_diff);935 }936 /* Check if score >= min. */937 if (x && !zslLexValueGteMin(zslGetNodeElement(x), range)) return NULL;938 /* Store rank if requested. For n < 0, the returned node is at rank edge_rank + n + 1. */939 if (x && out_rank) *out_rank = edge_rank + n + 1;940 }941942 return x;943}944945/*-----------------------------------------------------------------------------946 * Listpack-backed sorted set API947 *----------------------------------------------------------------------------*/948949static double zzlStrtod(unsigned char *vstr, unsigned int vlen) {950 return fast_float_strtod((char*)vstr, vlen, NULL);951}952953double zzlGetScore(unsigned char *sptr) {954 unsigned char *vstr;955 unsigned int vlen;956 long long vlong;957 double score;958959 serverAssert(sptr != NULL);960 vstr = lpGetValue(sptr,&vlen,&vlong);961962 if (vstr) {963 score = zzlStrtod(vstr,vlen);964 } else {965 score = vlong;966 }967968 return score;969}970971/* Return a listpack element as an SDS string. */972sds lpGetObject(unsigned char *sptr) {973 unsigned char *vstr;974 unsigned int vlen;975 long long vlong;976977 serverAssert(sptr != NULL);978 vstr = lpGetValue(sptr,&vlen,&vlong);979980 if (vstr) {981 return sdsnewlen((char*)vstr,vlen);982 } else {983 return sdsfromlonglong(vlong);984 }985}986987/* Compare element in sorted set with given element. */988static int zzlCompareElements(unsigned char *eptr, unsigned char *cstr, unsigned int clen) {989 unsigned char *vstr;990 unsigned int vlen;991 long long vlong;992 unsigned char vbuf[32];993 int minlen, cmp;994995 vstr = lpGetValue(eptr,&vlen,&vlong);996 if (vstr == NULL) {997 /* Store string representation of long long in buf. */998 vlen = ll2string((char*)vbuf,sizeof(vbuf),vlong);999 vstr = vbuf;1000 }10011002 minlen = (vlen < clen) ? vlen : clen;1003 cmp = memcmp(vstr,cstr,minlen);1004 if (cmp == 0) return vlen-clen;1005 return cmp;1006}10071008static unsigned int zzlLength(unsigned char *zl) {1009 return lpLength(zl)/2;1010}10111012/* Move to next entry based on the values in eptr and sptr. Both are set to1013 * NULL when there is no next entry. */1014void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr) {1015 unsigned char *_eptr, *_sptr;1016 serverAssert(*eptr != NULL && *sptr != NULL);10171018 _eptr = lpNext(zl,*sptr);1019 if (_eptr != NULL) {1020 _sptr = lpNext(zl,_eptr);1021 serverAssert(_sptr != NULL);1022 } else {1023 /* No next entry. */1024 _sptr = NULL;1025 }10261027 *eptr = _eptr;1028 *sptr = _sptr;1029}10301031/* Move to the previous entry based on the values in eptr and sptr. Both are1032 * set to NULL when there is no prev entry. */1033void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr) {1034 unsigned char *_eptr, *_sptr;1035 serverAssert(*eptr != NULL && *sptr != NULL);10361037 _sptr = lpPrev(zl,*eptr);1038 if (_sptr != NULL) {1039 _eptr = lpPrev(zl,_sptr);1040 serverAssert(_eptr != NULL);1041 } else {1042 /* No previous entry. */1043 _eptr = NULL;1044 }10451046 *eptr = _eptr;1047 *sptr = _sptr;1048}10491050/* Returns if there is a part of the zset is in range. Should only be used1051 * internally by zzlFirstInRange and zzlLastInRange. */1052static int zzlIsInRange(unsigned char *zl, zrangespec *range) {1053 unsigned char *p;1054 double score;10551056 /* Test for ranges that will always be empty. */1057 if (range->min > range->max ||1058 (range->min == range->max && (range->minex || range->maxex)))1059 return 0;10601061 p = lpSeek(zl,-1); /* Last score. */1062 if (p == NULL) return 0; /* Empty sorted set */1063 score = zzlGetScore(p);1064 if (!zslValueGteMin(score,range))1065 return 0;10661067 p = lpSeek(zl,1); /* First score. */1068 serverAssert(p != NULL);1069 score = zzlGetScore(p);1070 if (!zslValueLteMax(score,range))1071 return 0;10721073 return 1;1074}10751076/* Find pointer to the first element contained in the specified range.1077 * Returns NULL when no element is contained in the range. */1078unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range) {1079 unsigned char *eptr = lpSeek(zl,0), *sptr;1080 double score;10811082 /* If everything is out of range, return early. */1083 if (!zzlIsInRange(zl,range)) return NULL;10841085 while (eptr != NULL) {1086 sptr = lpNext(zl,eptr);1087 serverAssert(sptr != NULL);10881089 score = zzlGetScore(sptr);1090 if (zslValueGteMin(score,range)) {1091 /* Check if score <= max. */1092 if (zslValueLteMax(score,range))1093 return eptr;1094 return NULL;1095 }10961097 /* Move to next element. */1098 eptr = lpNext(zl,sptr);1099 }11001101 return NULL;1102}11031104/* Find pointer to the last element contained in the specified range.1105 * Returns NULL when no element is contained in the range. */1106unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range) {1107 unsigned char *eptr = lpSeek(zl,-2), *sptr;1108 double score;11091110 /* If everything is out of range, return early. */1111 if (!zzlIsInRange(zl,range)) return NULL;11121113 while (eptr != NULL) {1114 sptr = lpNext(zl,eptr);1115 serverAssert(sptr != NULL);11161117 score = zzlGetScore(sptr);1118 if (zslValueLteMax(score,range)) {1119 /* Check if score >= min. */1120 if (zslValueGteMin(score,range))1121 return eptr;1122 return NULL;1123 }11241125 /* Move to previous element by moving to the score of previous element.1126 * When this returns NULL, we know there also is no element. */1127 sptr = lpPrev(zl,eptr);1128 if (sptr != NULL)1129 serverAssert((eptr = lpPrev(zl,sptr)) != NULL);1130 else1131 eptr = NULL;1132 }11331134 return NULL;1135}11361137int zzlLexValueGteMin(unsigned char *p, zlexrangespec *spec) {1138 sds value = lpGetObject(p);1139 int res = zslLexValueGteMin(value,spec);1140 sdsfree(value);1141 return res;1142}11431144int zzlLexValueLteMax(unsigned char *p, zlexrangespec *spec) {1145 sds value = lpGetObject(p);1146 int res = zslLexValueLteMax(value,spec);1147 sdsfree(value);1148 return res;1149}11501151/* Returns if there is a part of the zset is in range. Should only be used1152 * internally by zzlFirstInLexRange and zzlLastInLexRange. */1153static int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {1154 unsigned char *p;11551156 /* Test for ranges that will always be empty. */1157 int cmp = sdscmplex(range->min,range->max);1158 if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))1159 return 0;11601161 p = lpSeek(zl,-2); /* Last element. */1162 if (p == NULL) return 0;1163 if (!zzlLexValueGteMin(p,range))1164 return 0;11651166 p = lpSeek(zl,0); /* First element. */1167 serverAssert(p != NULL);1168 if (!zzlLexValueLteMax(p,range))1169 return 0;11701171 return 1;1172}11731174/* Find pointer to the first element contained in the specified lex range.1175 * Returns NULL when no element is contained in the range. */1176unsigned char *zzlFirstInLexRange(unsigned char *zl, zlexrangespec *range) {1177 unsigned char *eptr = lpSeek(zl,0), *sptr;11781179 /* If everything is out of range, return early. */1180 if (!zzlIsInLexRange(zl,range)) return NULL;11811182 while (eptr != NULL) {1183 if (zzlLexValueGteMin(eptr,range)) {1184 /* Check if score <= max. */1185 if (zzlLexValueLteMax(eptr,range))1186 return eptr;1187 return NULL;1188 }11891190 /* Move to next element. */1191 sptr = lpNext(zl,eptr); /* This element score. Skip it. */1192 serverAssert(sptr != NULL);1193 eptr = lpNext(zl,sptr); /* Next element. */1194 }11951196 return NULL;1197}11981199/* Find pointer to the last element contained in the specified lex range.1200 * Returns NULL when no element is contained in the range. */1201unsigned char *zzlLastInLexRange(unsigned char *zl, zlexrangespec *range) {1202 unsigned char *eptr = lpSeek(zl,-2), *sptr;12031204 /* If everything is out of range, return early. */1205 if (!zzlIsInLexRange(zl,range)) return NULL;12061207 while (eptr != NULL) {1208 if (zzlLexValueLteMax(eptr,range)) {1209 /* Check if score >= min. */1210 if (zzlLexValueGteMin(eptr,range))1211 return eptr;1212 return NULL;1213 }12141215 /* Move to previous element by moving to the score of previous element.1216 * When this returns NULL, we know there also is no element. */1217 sptr = lpPrev(zl,eptr);1218 if (sptr != NULL)1219 serverAssert((eptr = lpPrev(zl,sptr)) != NULL);1220 else1221 eptr = NULL;1222 }12231224 return NULL;1225}12261227static unsigned char *zzlFind(unsigned char *lp, sds ele, double *score) {1228 unsigned char *eptr, *sptr;12291230 if ((eptr = lpFirst(lp)) == NULL) return NULL;1231 eptr = lpFind(lp, eptr, (unsigned char*)ele, sdslen(ele), 1);1232 if (eptr) {1233 sptr = lpNext(lp,eptr);1234 serverAssert(sptr != NULL);12351236 /* Matching element, pull out score. */1237 if (score != NULL) *score = zzlGetScore(sptr);1238 return eptr;1239 }12401241 return NULL;1242}12431244/* Delete (element,score) pair from listpack. Use local copy of eptr because we1245 * don't want to modify the one given as argument. */1246static unsigned char *zzlDelete(unsigned char *zl, unsigned char *eptr) {1247 return lpDeleteRangeWithEntry(zl,&eptr,2);1248}12491250static unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, sds ele, double score) {1251 char scorebuf[MAX_D2STRING_CHARS];1252 int scorelen = 0;1253 long long lscore;1254 int score_is_long = double2ll(score, &lscore);1255 if (!score_is_long)1256 scorelen = d2string(scorebuf,sizeof(scorebuf),score);12571258 listpackEntry entries[2];1259 entries[0].sval = (unsigned char*)ele;1260 entries[0].slen = sdslen(ele);1261 if (score_is_long) {1262 entries[1].sval = NULL;1263 entries[1].lval = lscore;1264 } else {1265 entries[1].sval = (unsigned char*)scorebuf;1266 entries[1].slen = scorelen;1267 }12681269 if (eptr == NULL)1270 zl = lpBatchAppend(zl, entries, 2);1271 else1272 zl = lpBatchInsert(zl, eptr, LP_BEFORE, entries, 2, NULL);12731274 return zl;1275}12761277/* Insert (element,score) pair in listpack. This function assumes the element is1278 * not yet present in the list. */1279unsigned char *zzlInsert(unsigned char *zl, sds ele, double score) {1280 unsigned char *eptr = lpSeek(zl,0), *sptr;1281 double s;12821283 while (eptr != NULL) {1284 sptr = lpNext(zl,eptr);1285 serverAssert(sptr != NULL);1286 s = zzlGetScore(sptr);12871288 if (s > score) {1289 /* First element with score larger than score for element to be1290 * inserted. This means we should take its spot in the list to1291 * maintain ordering. */1292 zl = zzlInsertAt(zl,eptr,ele,score);1293 break;1294 } else if (s == score) {1295 /* Ensure lexicographical ordering for elements. */1296 if (zzlCompareElements(eptr,(unsigned char*)ele,sdslen(ele)) > 0) {1297 zl = zzlInsertAt(zl,eptr,ele,score);1298 break;1299 }1300 }13011302 /* Move to next element. */1303 eptr = lpNext(zl,sptr);1304 }13051306 /* Push on tail of list when it was not yet inserted. */1307 if (eptr == NULL)1308 zl = zzlInsertAt(zl,NULL,ele,score);1309 return zl;1310}13111312static unsigned char *zzlDeleteRangeByScore(unsigned char *zl, zrangespec *range, unsigned long *deleted) {1313 unsigned char *eptr, *sptr;1314 double score;1315 unsigned long num = 0;13161317 if (deleted != NULL) *deleted = 0;13181319 eptr = zzlFirstInRange(zl,range);1320 if (eptr == NULL) return zl;13211322 /* When the tail of the listpack is deleted, eptr will be NULL. */1323 while (eptr && (sptr = lpNext(zl,eptr)) != NULL) {1324 score = zzlGetScore(sptr);1325 if (zslValueLteMax(score,range)) {1326 /* Delete both the element and the score. */1327 zl = lpDeleteRangeWithEntry(zl,&eptr,2);1328 num++;1329 } else {1330 /* No longer in range. */1331 break;1332 }1333 }13341335 if (deleted != NULL) *deleted = num;1336 return zl;1337}13381339static unsigned char *zzlDeleteRangeByLex(unsigned char *zl, zlexrangespec *range, unsigned long *deleted) {1340 unsigned char *eptr, *sptr;1341 unsigned long num = 0;13421343 if (deleted != NULL) *deleted = 0;13441345 eptr = zzlFirstInLexRange(zl,range);1346 if (eptr == NULL) return zl;13471348 /* When the tail of the listpack is deleted, eptr will be NULL. */1349 while (eptr && (sptr = lpNext(zl,eptr)) != NULL) {1350 if (zzlLexValueLteMax(eptr,range)) {1351 /* Delete both the element and the score. */1352 zl = lpDeleteRangeWithEntry(zl,&eptr,2);1353 num++;1354 } else {1355 /* No longer in range. */1356 break;1357 }1358 }13591360 if (deleted != NULL) *deleted = num;1361 return zl;1362}13631364/* Delete all the elements with rank between start and end from the skiplist.1365 * Start and end are inclusive. Note that start and end need to be 1-based */1366static unsigned char *zzlDeleteRangeByRank(unsigned char *zl, unsigned int start, unsigned int end, unsigned long *deleted) {1367 unsigned int num = (end-start)+1;1368 if (deleted) *deleted = num;1369 zl = lpDeleteRange(zl,2*(start-1),2*num);1370 return zl;1371}13721373/*-----------------------------------------------------------------------------1374 * Common sorted set API1375 *----------------------------------------------------------------------------*/13761377unsigned long zsetLength(const robj *zobj) {1378 unsigned long length = 0;1379 if (zobj->encoding == OBJ_ENCODING_LISTPACK) {1380 length = zzlLength(zobj->ptr);1381 } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {1382 length = ((const zset*)zobj->ptr)->zsl->length;1383 } else {1384 serverPanic("Unknown sorted set encoding");1385 }1386 return length;1387}13881389size_t zsetAllocSize(const robj *o) {1390 serverAssertWithInfo(NULL,o,o->type == OBJ_ZSET);1391 size_t size = 0;1392 if (o->encoding == OBJ_ENCODING_LISTPACK) {1393 size = lpBytes(o->ptr);1394 } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {1395 dict *d = ((zset*)o->ptr)->dict;1396 zskiplist *zsl = ((zset*)o->ptr)->zsl;1397 size = sizeof(zset) + zslAllocSize(zsl) +1398 sizeof(dict) + dictMemUsage(d);1399 } else {1400 serverPanic("Unknown sorted set encoding");1401 }1402 return size;1403}14041405/* Factory method to return a zset.1406 *1407 * The size hint indicates approximately how many items will be added,1408 * and the value len hint indicates the approximate individual size of the added elements,1409 * they are used to determine the initial representation.1410 *1411 * If the hints are not known, and underestimation or 0 is suitable. 1412 * We should never pass a negative value because it will convert to a very large unsigned number. */1413robj *zsetTypeCreate(size_t size_hint, size_t val_len_hint) {1414 if (size_hint <= server.zset_max_listpack_entries &&1415 val_len_hint <= server.zset_max_listpack_value)1416 {1417 return createZsetListpackObject();1418 }14191420 robj *zobj = createZsetObject();1421 zset *zs = zobj->ptr;1422 dictExpand(zs->dict, size_hint);1423 return zobj;1424}14251426/* Check if the existing zset should be converted to another encoding based off the1427 * the size hint. */1428void zsetTypeMaybeConvert(robj *zobj, size_t size_hint) {1429 if (zobj->encoding == OBJ_ENCODING_LISTPACK &&1430 size_hint > server.zset_max_listpack_entries)1431 {1432 zsetConvertAndExpand(zobj, OBJ_ENCODING_SKIPLIST, size_hint);1433 }1434}14351436/* Convert the zset to specified encoding. The zset dict (when converting1437 * to a skiplist) is presized to hold the number of elements in the original1438 * zset. */1439void zsetConvert(robj *zobj, int encoding) {1440 zsetConvertAndExpand(zobj, encoding, zsetLength(zobj));1441}14421443/* Converts a zset to the specified encoding, pre-sizing it for 'cap' elements. */1444void zsetConvertAndExpand(robj *zobj, int encoding, unsigned long cap) {1445 zset *zs;1446 zskiplistNode *node, *next;1447 sds ele;1448 double score;14491450 if (zobj->encoding == encoding) return;1451 if (zobj->encoding == OBJ_ENCODING_LISTPACK) {1452 unsigned char *zl = zobj->ptr;1453 unsigned char *eptr, *sptr;1454 unsigned char *vstr;1455 unsigned int vlen;1456 long long vlong;14571458 if (encoding != OBJ_ENCODING_SKIPLIST)1459 serverPanic("Unknown target encoding");14601461 zs = zmalloc(sizeof(*zs));1462 zs->dict = dictCreate(&zsetDictType);1463 zs->zsl = zslCreate();14641465 /* Presize the dict to avoid rehashing */1466 dictExpand(zs->dict, cap);14671468 eptr = lpSeek(zl,0);1469 if (eptr != NULL) {1470 sptr = lpNext(zl,eptr);1471 serverAssertWithInfo(NULL,zobj,sptr != NULL);1472 }14731474 while (eptr != NULL) {1475 score = zzlGetScore(sptr);1476 vstr = lpGetValue(eptr,&vlen,&vlong);1477 if (vstr == NULL)1478 ele = sdsfromlonglong(vlong);1479 else1480 ele = sdsnewlen((char*)vstr,vlen);14811482 node = zslInsert(zs->zsl,score,ele);1483 serverAssert(dictAdd(zs->dict, node, NULL) == DICT_OK);1484 sdsfree(ele); /* zslInsert copied it, we can free our copy */1485 zzlNext(zl,&eptr,&sptr);1486 }14871488 zfree(zobj->ptr);1489 zobj->ptr = zs;1490 zobj->encoding = OBJ_ENCODING_SKIPLIST;1491 } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {1492 unsigned char *zl = lpNew(0);14931494 if (encoding != OBJ_ENCODING_LISTPACK)1495 serverPanic("Unknown target encoding");14961497 /* Approach similar to zslFree(), since we want to free the skiplist at1498 * the same time as creating the listpack. */1499 zs = zobj->ptr;1500 dictRelease(zs->dict);1501 node = zs->zsl->header->level[0].forward;1502 zfree(zs->zsl->header);15031504 while (node) {1505 zl = zzlInsertAt(zl,NULL,zslGetNodeElement(node),node->score);1506 next = node->level[0].forward;1507 zslFreeNode(zs->zsl, node);1508 node = next;1509 }15101511 zfree(zs->zsl);1512 zfree(zs);1513 zobj->ptr = zl;1514 zobj->encoding = OBJ_ENCODING_LISTPACK;1515 } else {1516 serverPanic("Unknown sorted set encoding");1517 }1518}15191520/* Convert the sorted set object into a listpack if it is not already a listpack1521 * and if the number of elements and the maximum element size and total elements size1522 * are within the expected ranges. */1523void zsetConvertToListpackIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {1524 if (zobj->encoding == OBJ_ENCODING_LISTPACK) return;1525 zset *zset = zobj->ptr;15261527 if (zset->zsl->length <= server.zset_max_listpack_entries &&1528 maxelelen <= server.zset_max_listpack_value &&1529 lpSafeToAdd(NULL, totelelen))1530 {1531 zsetConvert(zobj,OBJ_ENCODING_LISTPACK);1532 }1533}15341535/* Return (by reference) the score of the specified member of the sorted set1536 * storing it into *score. If the element does not exist C_ERR is returned1537 * otherwise C_OK is returned and *score is correctly populated.1538 * If 'zobj' or 'member' is NULL, C_ERR is returned. */1539int zsetScore(robj *zobj, sds member, double *score) {1540 if (!zobj || !member) return C_ERR;15411542 if (zobj->encoding == OBJ_ENCODING_LISTPACK) {1543 if (zzlFind(zobj->ptr, member, score) == NULL) return C_ERR;1544 } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {1545 zset *zs = zobj->ptr;1546 dictEntry *de = dictFind(zs->dict, member);1547 if (de == NULL) return C_ERR;1548 zskiplistNode *znode = dictGetKey(de);1549 *score = znode->score;1550 } else {1551 serverPanic("Unknown sorted set encoding");1552 }1553 return C_OK;1554}15551556/* Add a new element or update the score of an existing element in a sorted1557 * set, regardless of its encoding.1558 *1559 * The set of flags change the command behavior. 1560 *1561 * The input flags are the following:1562 *1563 * ZADD_INCR: Increment the current element score by 'score' instead of updating1564 * the current element score. If the element does not exist, we1565 * assume 0 as previous score.1566 * ZADD_NX: Perform the operation only if the element does not exist.1567 * ZADD_XX: Perform the operation only if the element already exist.1568 * ZADD_GT: Perform the operation on existing elements only if the new score is 1569 * greater than the current score.1570 * ZADD_LT: Perform the operation on existing elements only if the new score is 1571 * less than the current score.1572 *1573 * When ZADD_INCR is used, the new score of the element is stored in1574 * '*newscore' if 'newscore' is not NULL.1575 *1576 * The returned flags are the following:1577 *1578 * ZADD_NAN: The resulting score is not a number.1579 * ZADD_ADDED: The element was added (not present before the call).1580 * ZADD_UPDATED: The element score was updated.1581 * ZADD_NOP: No operation was performed because of NX or XX.1582 *1583 * Return value:1584 *1585 * The function returns 1 on success, and sets the appropriate flags1586 * ADDED or UPDATED to signal what happened during the operation (note that1587 * none could be set if we re-added an element using the same score it used1588 * to have, or in the case a zero increment is used).1589 *1590 * The function returns 0 on error, currently only when the increment1591 * produces a NAN condition, or when the 'score' value is NAN since the1592 * start.1593 *1594 * The command as a side effect of adding a new element may convert the sorted1595 * set internal encoding from listpack to hashtable+skiplist.1596 *1597 * Memory management of 'ele':1598 *1599 * The function does not take ownership of the 'ele' SDS string, but copies1600 * it if needed. */1601int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore) {1602 /* Turn options into simple to check vars. */1603 int incr = (in_flags & ZADD_IN_INCR) != 0;1604 int nx = (in_flags & ZADD_IN_NX) != 0;1605 int xx = (in_flags & ZADD_IN_XX) != 0;1606 int gt = (in_flags & ZADD_IN_GT) != 0;1607 int lt = (in_flags & ZADD_IN_LT) != 0;1608 *out_flags = 0; /* We'll return our response flags. */1609 double curscore;16101611 /* NaN as input is an error regardless of all the other parameters. */1612 if (isnan(score)) {1613 *out_flags = ZADD_OUT_NAN;1614 return 0;1615 }16161617 /* Update the sorted set according to its encoding. */1618 if (zobj->encoding == OBJ_ENCODING_LISTPACK) {1619 unsigned char *eptr;16201621 if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {1622 /* NX? Return, same element already exists. */1623 if (nx) {1624 *out_flags |= ZADD_OUT_NOP;1625 return 1;1626 }16271628 /* Prepare the score for the increment if needed. */1629 if (incr) {1630 score += curscore;1631 if (isnan(score)) {1632 *out_flags |= ZADD_OUT_NAN;1633 return 0;1634 }1635 }16361637 /* GT/LT? Only update if score is greater/less than current. */1638 if ((lt && score >= curscore) || (gt && score <= curscore)) {1639 *out_flags |= ZADD_OUT_NOP;1640 return 1;1641 }16421643 if (newscore) *newscore = score;16441645 /* Remove and re-insert when score changed. */1646 if (score != curscore) {1647 zobj->ptr = zzlDelete(zobj->ptr,eptr);1648 zobj->ptr = zzlInsert(zobj->ptr,ele,score);1649 *out_flags |= ZADD_OUT_UPDATED;1650 }1651 return 1;1652 } else if (!xx) {1653 /* check if the element is too large or the list1654 * becomes too long *before* executing zzlInsert. */1655 if (zzlLength(zobj->ptr)+1 > server.zset_max_listpack_entries ||1656 sdslen(ele) > server.zset_max_listpack_value ||1657 !lpSafeToAdd(zobj->ptr, sdslen(ele)))1658 {1659 zsetConvertAndExpand(zobj, OBJ_ENCODING_SKIPLIST, zsetLength(zobj) + 1);1660 } else {1661 zobj->ptr = zzlInsert(zobj->ptr,ele,score);1662 if (newscore) *newscore = score;1663 *out_flags |= ZADD_OUT_ADDED;1664 return 1;1665 }1666 } else {1667 *out_flags |= ZADD_OUT_NOP;1668 return 1;1669 }1670 }16711672 /* Note that the above block handling listpack would have either returned or1673 * converted the key to skiplist. */1674 if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {1675 zset *zs = zobj->ptr;1676 zskiplistNode *znode;1677 dictEntry *de;1678 dictEntryLink bucket, link;16791680 /* Use dictFindLink to find the element and get the bucket for potential insertion.1681 * This avoids a second lookup in dictAdd() if the element doesn't exist. */1682 link = dictFindLink(zs->dict, ele, &bucket);16831684 if (link != NULL) {1685 /* Element exists - get the dictEntry from the link */1686 de = *link;16871688 /* NX? Return, same element already exists. */1689 if (nx) {1690 *out_flags |= ZADD_OUT_NOP;1691 return 1;1692 }16931694 /* Get the node pointer from dict entry */1695 znode = dictGetKey(de);1696 curscore = znode->score;16971698 /* Prepare the score for the increment if needed. */1699 if (incr) {1700 score += curscore;1701 if (isnan(score)) {1702 *out_flags |= ZADD_OUT_NAN;1703 return 0;1704 }1705 }17061707 /* GT/LT? Only update if score is greater/less than current. */1708 if ((lt && score >= curscore) || (gt && score <= curscore)) {1709 *out_flags |= ZADD_OUT_NOP;1710 return 1;1711 }17121713 if (newscore) *newscore = score;17141715 /* Remove and re-insert when score changes. */1716 if (score != curscore) {1717 zslUpdateScore(zs->zsl, znode, score);1718 /* Note that we did not remove the original element from1719 * the hash table representing the sorted set, so we don't1720 * need to update the dict - the node pointer stays the same. */1721 *out_flags |= ZADD_OUT_UPDATED;1722 }1723 return 1;1724 } else if (!xx) {1725 /* Element doesn't exist - create node with embedded sds and add to skiplist */1726 znode = zslInsert(zs->zsl, score, ele);17271728 /* Add node pointer to dict using the bucket we already found */1729 dictSetKeyAtLink(zs->dict, znode, &bucket, 1);17301731 *out_flags |= ZADD_OUT_ADDED;1732 if (newscore) *newscore = score;1733 return 1;1734 } else {1735 *out_flags |= ZADD_OUT_NOP;1736 return 1;1737 }1738 } else {1739 serverPanic("Unknown sorted set encoding");1740 }1741 return 0; /* Never reached. */1742}17431744/* Deletes the element 'ele' from the sorted set encoded as a skiplist+dict,1745 * returning 1 if the element existed and was deleted, 0 otherwise (the1746 * element was not there). It does not resize the dict after deleting the1747 * element. */1748static int zsetRemoveFromSkiplist(zset *zs, sds ele) {1749 dictEntry *de;17501751 de = dictUnlink(zs->dict,ele);1752 if (de != NULL) {1753 /* Get the node and score in order to delete from the skiplist later. */1754 zskiplistNode *znode = dictGetKey(de);17551756 /* Delete from the hash table and later from the skiplist.1757 * Note that the order is important: deleting from the skiplist1758 * actually releases the SDS string representing the element,1759 * which is shared between the skiplist and the hash table, so1760 * we need to delete from the skiplist as the final step. */1761 dictFreeUnlinkedEntry(zs->dict,de);17621763 /* Delete from skiplist. */1764 zslDelete(zs->zsl, znode);17651766 return 1;1767 }17681769 return 0;1770}17711772/* Delete the element 'ele' from the sorted set, returning 1 if the element1773 * existed and was deleted, 0 otherwise (the element was not there). */1774int zsetDel(robj *zobj, sds ele) {1775 if (zobj->encoding == OBJ_ENCODING_LISTPACK) {1776 unsigned char *eptr;17771778 if ((eptr = zzlFind(zobj->ptr,ele,NULL)) != NULL) {1779 zobj->ptr = zzlDelete(zobj->ptr,eptr);1780 return 1;1781 }1782 } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {1783 zset *zs = zobj->ptr;1784 if (zsetRemoveFromSkiplist(zs, ele)) {1785 return 1;1786 }1787 } else {1788 serverPanic("Unknown sorted set encoding");1789 }1790 return 0; /* No such element found. */1791}17921793/* Given a sorted set object returns the 0-based rank of the object or1794 * -1 if the object does not exist.1795 *1796 * For rank we mean the position of the element in the sorted collection1797 * of elements. So the first element has rank 0, the second rank 1, and so1798 * forth up to length-1 elements.1799 *1800 * If 'reverse' is false, the rank is returned considering as first element1801 * the one with the lowest score. Otherwise if 'reverse' is non-zero1802 * the rank is computed considering as element with rank 0 the one with1803 * the highest score. */1804long zsetRank(robj *zobj, sds ele, int reverse, double *output_score) {1805 unsigned long llen;1806 unsigned long rank;18071808 llen = zsetLength(zobj);18091810 if (zobj->encoding == OBJ_ENCODING_LISTPACK) {1811 unsigned char *zl = zobj->ptr;1812 unsigned char *eptr, *sptr;18131814 eptr = lpSeek(zl,0);1815 serverAssert(eptr != NULL);1816 sptr = lpNext(zl,eptr);1817 serverAssert(sptr != NULL);1818 const size_t ele_len = sdslen(ele);1819 long long cached_val = 0;1820 int cached_valid = 0;1821 rank = 1;1822 while(eptr != NULL) {1823 if (lpCompare(eptr,(unsigned char*)ele,ele_len,&cached_val,&cached_valid))1824 break;1825 rank++;1826 zzlNext(zl,&eptr,&sptr);1827 }18281829 if (eptr != NULL) {1830 if (output_score) 1831 *output_score = zzlGetScore(sptr);1832 if (reverse)1833 return llen-rank;1834 else1835 return rank-1;1836 } else {1837 return -1;1838 }1839 } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {1840 zset *zs = zobj->ptr;1841 zskiplist *zsl = zs->zsl;1842 dictEntry *de;18431844 de = dictFind(zs->dict,ele);1845 if (de != NULL) {1846 zskiplistNode *n = dictGetKey(de);1847 rank = zslGetRankByNode(zsl, n);1848 /* Existing elements always have a rank. */1849 serverAssert(rank != 0);1850 if (output_score)1851 *output_score = n->score;1852 if (reverse)1853 return llen-rank;1854 else1855 return rank-1;1856 } else {1857 return -1;1858 }1859 } else {1860 serverPanic("Unknown sorted set encoding");1861 }1862}18631864/* This is a helper function for the COPY command.1865 * Duplicate a sorted set object, with the guarantee that the returned object1866 * has the same encoding as the original one.1867 *1868 * The resulting object always has refcount set to 1 */1869robj *zsetDup(robj *o) {1870 robj *zobj;1871 zset *zs;1872 zset *new_zs;18731874 serverAssert(o->type == OBJ_ZSET);18751876 /* Create a new sorted set object that have the same encoding as the original object's encoding */1877 if (o->encoding == OBJ_ENCODING_LISTPACK) {1878 unsigned char *zl = o->ptr;1879 size_t sz = lpBytes(zl);1880 unsigned char *new_zl = zmalloc(sz);1881 memcpy(new_zl, zl, sz);1882 zobj = createObject(OBJ_ZSET, new_zl);1883 zobj->encoding = OBJ_ENCODING_LISTPACK;1884 } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {1885 zobj = createZsetObject();1886 zs = o->ptr;1887 new_zs = zobj->ptr;1888 dictExpand(new_zs->dict,dictSize(zs->dict));1889 zskiplist *zsl = zs->zsl;1890 zskiplistNode *ln;1891 sds ele;1892 long llen = zsetLength(o);18931894 /* We copy the skiplist elements from the greatest to the1895 * smallest (that's trivial since the elements are already ordered in1896 * the skiplist): this improves the load process, since the next loaded1897 * element will always be the smaller, so adding to the skiplist1898 * will always immediately stop at the head, making the insertion1899 * O(1) instead of O(log(N)). */1900 ln = zsl->tail;1901 while (llen--) {1902 ele = zslGetNodeElement(ln);1903 zskiplistNode *znode = zslInsert(new_zs->zsl,ln->score,ele);1904 dictAdd(new_zs->dict, znode, NULL);1905 ln = ln->backward;1906 }1907 } else {1908 serverPanic("Unknown sorted set encoding");1909 }1910 return zobj;1911}19121913/* Create a new sds string from the listpack entry. */1914sds zsetSdsFromListpackEntry(listpackEntry *e) {1915 return e->sval ? sdsnewlen(e->sval, e->slen) : sdsfromlonglong(e->lval);1916}19171918/* Reply with bulk string from the listpack entry. */1919void zsetReplyFromListpackEntry(client *c, listpackEntry *e) {1920 if (e->sval)1921 addReplyBulkCBuffer(c, e->sval, e->slen);1922 else1923 addReplyBulkLongLong(c, e->lval);1924}192519261927/* Return random element from a non empty zset.1928 * 'key' and 'val' will be set to hold the element.1929 * The memory in `key` is not to be freed or modified by the caller.1930 * 'score' can be NULL in which case it's not extracted. */1931void zsetTypeRandomElement(robj *zsetobj, unsigned long zsetsize, listpackEntry *key, double *score) {1932 if (zsetobj->encoding == OBJ_ENCODING_SKIPLIST) {1933 zset *zs = zsetobj->ptr;1934 dictEntry *de = dictGetFairRandomKey(zs->dict);1935 zskiplistNode *znode = dictGetKey(de);1936 sds s = zslGetNodeElement(znode);1937 key->sval = (unsigned char*)s;1938 key->slen = sdslen(s);1939 if (score) {1940 *score = znode->score;1941 }1942 } else if (zsetobj->encoding == OBJ_ENCODING_LISTPACK) {1943 listpackEntry val;1944 lpRandomPair(zsetobj->ptr, zsetsize, key, &val, 2);1945 if (score) {1946 if (val.sval) {1947 *score = zzlStrtod(val.sval,val.slen);1948 } else {1949 *score = (double)val.lval;1950 }1951 }1952 } else {1953 serverPanic("Unknown zset encoding");1954 }1955}19561957/*-----------------------------------------------------------------------------1958 * Sorted set commands1959 *----------------------------------------------------------------------------*/19601961/* This generic command implements both ZADD and ZINCRBY. */1962void zaddGenericCommand(client *c, int flags) {1963 static char *nanerr = "resulting score is not a number (NaN)";1964 robj *key = c->argv[1];1965 robj *zobj;1966 sds ele;1967 size_t oldsize = 0;1968 double score = 0, *scores = NULL;1969 int j, elements, ch = 0;1970 int scoreidx = 0;1971 /* The following vars are used in order to track what the command actually1972 * did during the execution, to reply to the client and to trigger the1973 * notification of keyspace change. */1974 int added = 0; /* Number of new elements added. */1975 int updated = 0; /* Number of elements with updated score. */1976 int processed = 0; /* Number of elements processed, may remain zero with1977 options like XX. */19781979 /* Parse options. At the end 'scoreidx' is set to the argument position1980 * of the score of the first score-element pair. */1981 scoreidx = 2;1982 while(scoreidx < c->argc) {1983 char *opt = c->argv[scoreidx]->ptr;1984 if (!strcasecmp(opt,"nx")) flags |= ZADD_IN_NX;1985 else if (!strcasecmp(opt,"xx")) flags |= ZADD_IN_XX;1986 else if (!strcasecmp(opt,"ch")) ch = 1; /* Return num of elements added or updated. */1987 else if (!strcasecmp(opt,"incr")) flags |= ZADD_IN_INCR;1988 else if (!strcasecmp(opt,"gt")) flags |= ZADD_IN_GT;1989 else if (!strcasecmp(opt,"lt")) flags |= ZADD_IN_LT;1990 else break;1991 scoreidx++;1992 }19931994 /* Turn options into simple to check vars. */1995 int incr = (flags & ZADD_IN_INCR) != 0;1996 int nx = (flags & ZADD_IN_NX) != 0;1997 int xx = (flags & ZADD_IN_XX) != 0;1998 int gt = (flags & ZADD_IN_GT) != 0;1999 int lt = (flags & ZADD_IN_LT) != 0;
Findings
✓ No findings reported for this file.