src/t_zset.c C 5,034 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,034.
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings