mirror of https://github.com/redis/redis.git
				
				
				
			Add ZRANGESTORE command, and improve ZSTORE command (#7844)
Add ZRANGESTORE command, and improve ZSTORE command to deprecated Z[REV]RANGE[BYSCORE|BYLEX]. Syntax for the new ZRANGESTORE command: ZRANGESTORE [BYSCORE | BYLEX] [REV] [LIMIT offset count] New syntax for ZRANGE: ZRANGE [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count] Old syntax for ZRANGE: ZRANGE [WITHSCORES] Other ZRANGE commands remain unchanged. The implementation uses common code for all of these, by utilizing a consumer interface that in one command response to the client, and in the other command stores a zset key. Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
		
							parent
							
								
									cfcd0fa6f7
								
							
						
					
					
						commit
						b5029dfdad
					
				| 
						 | 
				
			
			@ -464,6 +464,10 @@ struct redisCommand redisCommandTable[] = {
 | 
			
		|||
     "read-only @sortedset",
 | 
			
		||||
     0,NULL,1,1,1,0,0,0},
 | 
			
		||||
 | 
			
		||||
    {"zrangestore",zrangestoreCommand,-5,
 | 
			
		||||
     "write use-memory @sortedset",
 | 
			
		||||
     0,NULL,1,2,1,0,0,0},
 | 
			
		||||
 | 
			
		||||
    {"zrangebyscore",zrangebyscoreCommand,-4,
 | 
			
		||||
     "read-only @sortedset",
 | 
			
		||||
     0,NULL,1,1,1,0,0,0},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2517,6 +2517,7 @@ void zinterstoreCommand(client *c);
 | 
			
		|||
void zdiffstoreCommand(client *c);
 | 
			
		||||
void zunionCommand(client *c);
 | 
			
		||||
void zinterCommand(client *c);
 | 
			
		||||
void zrangestoreCommand(client *c);
 | 
			
		||||
void zdiffCommand(client *c);
 | 
			
		||||
void zscanCommand(client *c);
 | 
			
		||||
void hkeysCommand(client *c);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										667
									
								
								src/t_zset.c
								
								
								
								
							
							
						
						
									
										667
									
								
								src/t_zset.c
								
								
								
								
							| 
						 | 
				
			
			@ -1822,11 +1822,15 @@ void zremCommand(client *c) {
 | 
			
		|||
    addReplyLongLong(c,deleted);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    ZRANGE_AUTO = 0,
 | 
			
		||||
    ZRANGE_RANK,
 | 
			
		||||
    ZRANGE_SCORE,
 | 
			
		||||
    ZRANGE_LEX,
 | 
			
		||||
} zrange_type;
 | 
			
		||||
 | 
			
		||||
/* Implements ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZREMRANGEBYLEX commands. */
 | 
			
		||||
#define ZRANGE_RANK 0
 | 
			
		||||
#define ZRANGE_SCORE 1
 | 
			
		||||
#define ZRANGE_LEX 2
 | 
			
		||||
void zremrangeGenericCommand(client *c, int rangetype) {
 | 
			
		||||
void zremrangeGenericCommand(client *c, zrange_type rangetype) {
 | 
			
		||||
    robj *key = c->argv[1];
 | 
			
		||||
    robj *zobj;
 | 
			
		||||
    int keyremoved = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -1834,22 +1838,28 @@ void zremrangeGenericCommand(client *c, int rangetype) {
 | 
			
		|||
    zrangespec range;
 | 
			
		||||
    zlexrangespec lexrange;
 | 
			
		||||
    long start, end, llen;
 | 
			
		||||
    char *notify_type = NULL;
 | 
			
		||||
 | 
			
		||||
    /* Step 1: Parse the range. */
 | 
			
		||||
    if (rangetype == ZRANGE_RANK) {
 | 
			
		||||
        notify_type = "zremrangebyrank";
 | 
			
		||||
        if ((getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) ||
 | 
			
		||||
            (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK))
 | 
			
		||||
            return;
 | 
			
		||||
    } else if (rangetype == ZRANGE_SCORE) {
 | 
			
		||||
        notify_type = "zremrangebyscore";
 | 
			
		||||
        if (zslParseRange(c->argv[2],c->argv[3],&range) != C_OK) {
 | 
			
		||||
            addReplyError(c,"min or max is not a float");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (rangetype == ZRANGE_LEX) {
 | 
			
		||||
        notify_type = "zremrangebylex";
 | 
			
		||||
        if (zslParseLexRange(c->argv[2],c->argv[3],&lexrange) != C_OK) {
 | 
			
		||||
            addReplyError(c,"min or max not valid string range item");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        serverPanic("unknown rangetype %d", (int)rangetype);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Step 2: Lookup & range sanity checks if needed. */
 | 
			
		||||
| 
						 | 
				
			
			@ -1875,6 +1885,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
 | 
			
		|||
    /* Step 3: Perform the range deletion operation. */
 | 
			
		||||
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
 | 
			
		||||
        switch(rangetype) {
 | 
			
		||||
        case ZRANGE_AUTO:
 | 
			
		||||
        case ZRANGE_RANK:
 | 
			
		||||
            zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
 | 
			
		||||
            break;
 | 
			
		||||
| 
						 | 
				
			
			@ -1892,6 +1903,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
 | 
			
		|||
    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
 | 
			
		||||
        zset *zs = zobj->ptr;
 | 
			
		||||
        switch(rangetype) {
 | 
			
		||||
        case ZRANGE_AUTO:
 | 
			
		||||
        case ZRANGE_RANK:
 | 
			
		||||
            deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
 | 
			
		||||
            break;
 | 
			
		||||
| 
						 | 
				
			
			@ -1913,9 +1925,8 @@ void zremrangeGenericCommand(client *c, int rangetype) {
 | 
			
		|||
 | 
			
		||||
    /* Step 4: Notifications and reply. */
 | 
			
		||||
    if (deleted) {
 | 
			
		||||
        char *event[3] = {"zremrangebyrank","zremrangebyscore","zremrangebylex"};
 | 
			
		||||
        signalModifiedKey(c,c->db,key);
 | 
			
		||||
        notifyKeyspaceEvent(NOTIFY_ZSET,event[rangetype],key,c->db->id);
 | 
			
		||||
        notifyKeyspaceEvent(NOTIFY_ZSET,notify_type,key,c->db->id);
 | 
			
		||||
        if (keyremoved)
 | 
			
		||||
            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2778,27 +2789,180 @@ void zdiffCommand(client *c) {
 | 
			
		|||
    zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void zrangeGenericCommand(client *c, int reverse) {
 | 
			
		||||
    robj *key = c->argv[1];
 | 
			
		||||
    robj *zobj;
 | 
			
		||||
    int withscores = 0;
 | 
			
		||||
    long start;
 | 
			
		||||
    long end;
 | 
			
		||||
    long llen;
 | 
			
		||||
    long rangelen;
 | 
			
		||||
typedef enum {
 | 
			
		||||
    ZRANGE_DIRECTION_AUTO = 0,
 | 
			
		||||
    ZRANGE_DIRECTION_FORWARD,
 | 
			
		||||
    ZRANGE_DIRECTION_REVERSE
 | 
			
		||||
} zrange_direction;
 | 
			
		||||
 | 
			
		||||
    if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
 | 
			
		||||
        (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
 | 
			
		||||
typedef enum {
 | 
			
		||||
    ZRANGE_CONSUMER_TYPE_CLIENT = 0,
 | 
			
		||||
    ZRANGE_CONSUMER_TYPE_INTERNAL
 | 
			
		||||
} zrange_consumer_type;
 | 
			
		||||
 | 
			
		||||
    if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
 | 
			
		||||
        withscores = 1;
 | 
			
		||||
    } else if (c->argc >= 5) {
 | 
			
		||||
        addReplyErrorObject(c,shared.syntaxerr);
 | 
			
		||||
        return;
 | 
			
		||||
typedef struct zrange_result_handler zrange_result_handler;
 | 
			
		||||
 | 
			
		||||
typedef void (*zrangeResultBeginFunction)(zrange_result_handler *c);
 | 
			
		||||
typedef void (*zrangeResultFinalizeFunction)(
 | 
			
		||||
    zrange_result_handler *c, size_t result_count);
 | 
			
		||||
typedef void (*zrangeResultEmitCBufferFunction)(
 | 
			
		||||
    zrange_result_handler *c, const void *p, size_t len, double score);
 | 
			
		||||
typedef void (*zrangeResultEmitLongLongFunction)(
 | 
			
		||||
    zrange_result_handler *c, long long ll, double score);
 | 
			
		||||
 | 
			
		||||
void zrangeGenericCommand (zrange_result_handler *handler, int argc_start, int store,
 | 
			
		||||
                           zrange_type rangetype, zrange_direction direction);
 | 
			
		||||
 | 
			
		||||
/* Interface struct for ZRANGE/ZRANGESTORE generic implementation.
 | 
			
		||||
 * There is one implementation of this interface that sends a RESP reply to clients.
 | 
			
		||||
 * and one implementation that stores the range result into a zset object. */
 | 
			
		||||
struct zrange_result_handler {
 | 
			
		||||
    zrange_consumer_type                 type;
 | 
			
		||||
    client                              *client;
 | 
			
		||||
    robj                                *dstkey;
 | 
			
		||||
    robj                                *dstobj;
 | 
			
		||||
    void                                *userdata;
 | 
			
		||||
    int                                  withscores;
 | 
			
		||||
    int                                  should_emit_array_length;
 | 
			
		||||
    zrangeResultBeginFunction            beginResultEmission;
 | 
			
		||||
    zrangeResultFinalizeFunction         finalizeResultEmission;
 | 
			
		||||
    zrangeResultEmitCBufferFunction      emitResultFromCBuffer;
 | 
			
		||||
    zrangeResultEmitLongLongFunction     emitResultFromLongLong;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Result handler methods for responding the ZRANGE to clients. */
 | 
			
		||||
static void zrangeResultBeginClient(zrange_result_handler *handler) {
 | 
			
		||||
    handler->userdata = addReplyDeferredLen(handler->client);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultEmitCBufferToClient(zrange_result_handler *handler,
 | 
			
		||||
    const void *value, size_t value_length_in_bytes, double score)
 | 
			
		||||
{
 | 
			
		||||
    if (handler->should_emit_array_length) {
 | 
			
		||||
        addReplyArrayLen(handler->client, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL
 | 
			
		||||
         || checkType(c,zobj,OBJ_ZSET)) return;
 | 
			
		||||
    addReplyBulkCBuffer(handler->client, value, value_length_in_bytes);
 | 
			
		||||
 | 
			
		||||
    if (handler->withscores) {
 | 
			
		||||
        addReplyDouble(handler->client, score);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultEmitLongLongToClient(zrange_result_handler *handler,
 | 
			
		||||
    long long value, double score)
 | 
			
		||||
{
 | 
			
		||||
    if (handler->should_emit_array_length) {
 | 
			
		||||
        addReplyArrayLen(handler->client, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addReplyBulkLongLong(handler->client, value);
 | 
			
		||||
 | 
			
		||||
    if (handler->withscores) {
 | 
			
		||||
        addReplyDouble(handler->client, score);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultFinalizeClient(zrange_result_handler *handler,
 | 
			
		||||
    size_t result_count)
 | 
			
		||||
{
 | 
			
		||||
    if (handler->withscores && (handler->client->resp == 2)) {
 | 
			
		||||
        result_count *= 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setDeferredArrayLen(handler->client, handler->userdata, result_count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Result handler methods for storing the ZRANGESTORE to a zset. */
 | 
			
		||||
static void zrangeResultBeginStore(zrange_result_handler *handler)
 | 
			
		||||
{
 | 
			
		||||
    handler->dstobj = createZsetZiplistObject();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultEmitCBufferForStore(zrange_result_handler *handler,
 | 
			
		||||
    const void *value, size_t value_length_in_bytes, double score)
 | 
			
		||||
{
 | 
			
		||||
    double newscore;
 | 
			
		||||
    int retflags = 0;
 | 
			
		||||
    sds ele = sdsnewlen(value, value_length_in_bytes);
 | 
			
		||||
    int retval = zsetAdd(handler->dstobj, score, ele, &retflags, &newscore);
 | 
			
		||||
    sdsfree(ele);
 | 
			
		||||
    serverAssert(retval);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultEmitLongLongForStore(zrange_result_handler *handler,
 | 
			
		||||
    long long value, double score)
 | 
			
		||||
{
 | 
			
		||||
    double newscore;
 | 
			
		||||
    int retflags = 0;
 | 
			
		||||
    sds ele = sdsfromlonglong(value);
 | 
			
		||||
    int retval = zsetAdd(handler->dstobj, score, ele, &retflags, &newscore);
 | 
			
		||||
    sdsfree(ele);
 | 
			
		||||
    serverAssert(retval);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultFinalizeStore(zrange_result_handler *handler, size_t result_count)
 | 
			
		||||
{
 | 
			
		||||
    if (result_count) {
 | 
			
		||||
        setKey(handler->client, handler->client->db, handler->dstkey, handler->dstobj);
 | 
			
		||||
        addReplyLongLong(handler->client, result_count);
 | 
			
		||||
        notifyKeyspaceEvent(NOTIFY_ZSET, "zrangestore", handler->dstkey, handler->client->db->id);
 | 
			
		||||
        server.dirty++;
 | 
			
		||||
    } else {
 | 
			
		||||
        addReply(handler->client, shared.czero);
 | 
			
		||||
        if (dbDelete(handler->client->db, handler->dstkey)) {
 | 
			
		||||
            signalModifiedKey(handler->client, handler->client->db, handler->dstkey);
 | 
			
		||||
            notifyKeyspaceEvent(NOTIFY_GENERIC, "del", handler->dstkey, handler->client->db->id);
 | 
			
		||||
            server.dirty++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    decrRefCount(handler->dstobj);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Initialize the consumer interface type with the requested type. */
 | 
			
		||||
static void zrangeResultHandlerInit(zrange_result_handler *handler,
 | 
			
		||||
    client *client, zrange_consumer_type type)
 | 
			
		||||
{
 | 
			
		||||
    memset(handler, 0, sizeof(*handler));
 | 
			
		||||
 | 
			
		||||
    handler->client = client;
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
    case ZRANGE_CONSUMER_TYPE_CLIENT:
 | 
			
		||||
        handler->beginResultEmission = zrangeResultBeginClient;
 | 
			
		||||
        handler->finalizeResultEmission = zrangeResultFinalizeClient;
 | 
			
		||||
        handler->emitResultFromCBuffer = zrangeResultEmitCBufferToClient;
 | 
			
		||||
        handler->emitResultFromLongLong = zrangeResultEmitLongLongToClient;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case ZRANGE_CONSUMER_TYPE_INTERNAL:
 | 
			
		||||
        handler->beginResultEmission = zrangeResultBeginStore;
 | 
			
		||||
        handler->finalizeResultEmission = zrangeResultFinalizeStore;
 | 
			
		||||
        handler->emitResultFromCBuffer = zrangeResultEmitCBufferForStore;
 | 
			
		||||
        handler->emitResultFromLongLong = zrangeResultEmitLongLongForStore;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultHandlerScoreEmissionEnable(zrange_result_handler *handler) {
 | 
			
		||||
    handler->withscores = 1;
 | 
			
		||||
    handler->should_emit_array_length = (handler->client->resp > 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void zrangeResultHandlerDestinationKeySet (zrange_result_handler *handler,
 | 
			
		||||
    robj *dstkey)
 | 
			
		||||
{
 | 
			
		||||
    handler->dstkey = dstkey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* This command implements ZRANGE, ZREVRANGE. */
 | 
			
		||||
void genericZrangebyrankCommand(zrange_result_handler *handler,
 | 
			
		||||
    robj *zobj, long start, long end, int withscores, int reverse) {
 | 
			
		||||
 | 
			
		||||
    client *c = handler->client;
 | 
			
		||||
    long llen;
 | 
			
		||||
    long rangelen;
 | 
			
		||||
    size_t result_cardinality;
 | 
			
		||||
 | 
			
		||||
    /* Sanitize indexes. */
 | 
			
		||||
    llen = zsetLength(zobj);
 | 
			
		||||
| 
						 | 
				
			
			@ -2806,22 +2970,17 @@ void zrangeGenericCommand(client *c, int reverse) {
 | 
			
		|||
    if (end < 0) end = llen+end;
 | 
			
		||||
    if (start < 0) start = 0;
 | 
			
		||||
 | 
			
		||||
    handler->beginResultEmission(handler);
 | 
			
		||||
 | 
			
		||||
    /* Invariant: start >= 0, so this test will be true when end < 0.
 | 
			
		||||
     * The range is empty when start > end or start >= length. */
 | 
			
		||||
    if (start > end || start >= llen) {
 | 
			
		||||
        addReply(c,shared.emptyarray);
 | 
			
		||||
        handler->finalizeResultEmission(handler, 0);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (end >= llen) end = llen-1;
 | 
			
		||||
    rangelen = (end-start)+1;
 | 
			
		||||
 | 
			
		||||
    /* Return the result in form of a multi-bulk reply. RESP3 clients
 | 
			
		||||
     * will receive sub arrays with score->element, while RESP2 returned
 | 
			
		||||
     * a flat array. */
 | 
			
		||||
    if (withscores && c->resp == 2)
 | 
			
		||||
        addReplyArrayLen(c, rangelen*2);
 | 
			
		||||
    else
 | 
			
		||||
        addReplyArrayLen(c, rangelen);
 | 
			
		||||
    result_cardinality = rangelen;
 | 
			
		||||
 | 
			
		||||
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
 | 
			
		||||
        unsigned char *zl = zobj->ptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -2829,6 +2988,7 @@ void zrangeGenericCommand(client *c, int reverse) {
 | 
			
		|||
        unsigned char *vstr;
 | 
			
		||||
        unsigned int vlen;
 | 
			
		||||
        long long vlong;
 | 
			
		||||
        double score = 0.0;
 | 
			
		||||
 | 
			
		||||
        if (reverse)
 | 
			
		||||
            eptr = ziplistIndex(zl,-2-(2*start));
 | 
			
		||||
| 
						 | 
				
			
			@ -2842,12 +3002,14 @@ void zrangeGenericCommand(client *c, int reverse) {
 | 
			
		|||
            serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
 | 
			
		||||
            serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
 | 
			
		||||
 | 
			
		||||
            if (withscores && c->resp > 2) addReplyArrayLen(c,2);
 | 
			
		||||
            if (vstr == NULL)
 | 
			
		||||
                addReplyBulkLongLong(c,vlong);
 | 
			
		||||
            else
 | 
			
		||||
                addReplyBulkCBuffer(c,vstr,vlen);
 | 
			
		||||
            if (withscores) addReplyDouble(c,zzlGetScore(sptr));
 | 
			
		||||
            if (withscores) /* don't bother to extract the score if it's gonna be ignored. */
 | 
			
		||||
                score = zzlGetScore(sptr);
 | 
			
		||||
 | 
			
		||||
            if (vstr == NULL) {
 | 
			
		||||
                handler->emitResultFromLongLong(handler, vlong, score);
 | 
			
		||||
            } else {
 | 
			
		||||
                handler->emitResultFromCBuffer(handler, vstr, vlen, score);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (reverse)
 | 
			
		||||
                zzlPrev(zl,&eptr,&sptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -2859,7 +3021,6 @@ void zrangeGenericCommand(client *c, int reverse) {
 | 
			
		|||
        zset *zs = zobj->ptr;
 | 
			
		||||
        zskiplist *zsl = zs->zsl;
 | 
			
		||||
        zskiplistNode *ln;
 | 
			
		||||
        sds ele;
 | 
			
		||||
 | 
			
		||||
        /* Check if starting point is trivial, before doing log(N) lookup. */
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2874,83 +3035,53 @@ void zrangeGenericCommand(client *c, int reverse) {
 | 
			
		|||
 | 
			
		||||
        while(rangelen--) {
 | 
			
		||||
            serverAssertWithInfo(c,zobj,ln != NULL);
 | 
			
		||||
            ele = ln->ele;
 | 
			
		||||
            if (withscores && c->resp > 2) addReplyArrayLen(c,2);
 | 
			
		||||
            addReplyBulkCBuffer(c,ele,sdslen(ele));
 | 
			
		||||
            if (withscores) addReplyDouble(c,ln->score);
 | 
			
		||||
            sds ele = ln->ele;
 | 
			
		||||
            handler->emitResultFromCBuffer(handler, ele, sdslen(ele), ln->score);
 | 
			
		||||
            ln = reverse ? ln->backward : ln->level[0].forward;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        serverPanic("Unknown sorted set encoding");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handler->finalizeResultEmission(handler, result_cardinality);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZRANGESTORE <dst> <src> <min> <max> [BYSCORE | BYLEX] [REV] [LIMIT offset count] */
 | 
			
		||||
void zrangestoreCommand (client *c) {
 | 
			
		||||
    robj *dstkey = c->argv[1];
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_INTERNAL);
 | 
			
		||||
    zrangeResultHandlerDestinationKeySet(&handler, dstkey);
 | 
			
		||||
    zrangeGenericCommand(&handler, 2, 1, ZRANGE_AUTO, ZRANGE_DIRECTION_AUTO);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZRANGE <key> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count] */
 | 
			
		||||
void zrangeCommand(client *c) {
 | 
			
		||||
    zrangeGenericCommand(c,0);
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
 | 
			
		||||
    zrangeGenericCommand(&handler, 1, 0, ZRANGE_AUTO, ZRANGE_DIRECTION_AUTO);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZREVRANGE <key> <min> <max> [WITHSCORES] */
 | 
			
		||||
void zrevrangeCommand(client *c) {
 | 
			
		||||
    zrangeGenericCommand(c,1);
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
 | 
			
		||||
    zrangeGenericCommand(&handler, 1, 0, ZRANGE_RANK, ZRANGE_DIRECTION_REVERSE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE. */
 | 
			
		||||
void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		||||
    zrangespec range;
 | 
			
		||||
    robj *key = c->argv[1];
 | 
			
		||||
    robj *zobj;
 | 
			
		||||
    long offset = 0, limit = -1;
 | 
			
		||||
    int withscores = 0;
 | 
			
		||||
void genericZrangebyscoreCommand(zrange_result_handler *handler,
 | 
			
		||||
    zrangespec *range, robj *zobj, int withscores, long offset,
 | 
			
		||||
    long limit, int reverse) {
 | 
			
		||||
 | 
			
		||||
    client *c = handler->client;
 | 
			
		||||
    unsigned long rangelen = 0;
 | 
			
		||||
    void *replylen = NULL;
 | 
			
		||||
    int minidx, maxidx;
 | 
			
		||||
 | 
			
		||||
    /* Parse the range arguments. */
 | 
			
		||||
    if (reverse) {
 | 
			
		||||
        /* Range is given as [max,min] */
 | 
			
		||||
        maxidx = 2; minidx = 3;
 | 
			
		||||
    } else {
 | 
			
		||||
        /* Range is given as [min,max] */
 | 
			
		||||
        minidx = 2; maxidx = 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (zslParseRange(c->argv[minidx],c->argv[maxidx],&range) != C_OK) {
 | 
			
		||||
        addReplyError(c,"min or max is not a float");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Parse optional extra arguments. Note that ZCOUNT will exactly have
 | 
			
		||||
     * 4 arguments, so we'll never enter the following code path. */
 | 
			
		||||
    if (c->argc > 4) {
 | 
			
		||||
        int remaining = c->argc - 4;
 | 
			
		||||
        int pos = 4;
 | 
			
		||||
 | 
			
		||||
        while (remaining) {
 | 
			
		||||
            if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
 | 
			
		||||
                pos++; remaining--;
 | 
			
		||||
                withscores = 1;
 | 
			
		||||
            } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
 | 
			
		||||
                if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL)
 | 
			
		||||
                        != C_OK) ||
 | 
			
		||||
                    (getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL)
 | 
			
		||||
                        != C_OK))
 | 
			
		||||
                {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                pos += 3; remaining -= 3;
 | 
			
		||||
            } else {
 | 
			
		||||
                addReplyErrorObject(c,shared.syntaxerr);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Ok, lookup the key and get the range */
 | 
			
		||||
    if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
 | 
			
		||||
        checkType(c,zobj,OBJ_ZSET)) return;
 | 
			
		||||
    handler->beginResultEmission(handler);
 | 
			
		||||
 | 
			
		||||
    /* For invalid offset, return directly. */
 | 
			
		||||
    if (offset > 0 && offset >= (long)zsetLength(zobj)) {
 | 
			
		||||
        addReply(c,shared.emptyarray);
 | 
			
		||||
        handler->finalizeResultEmission(handler, 0);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2960,29 +3091,17 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		|||
        unsigned char *vstr;
 | 
			
		||||
        unsigned int vlen;
 | 
			
		||||
        long long vlong;
 | 
			
		||||
        double score;
 | 
			
		||||
 | 
			
		||||
        /* If reversed, get the last node in range as starting point. */
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
            eptr = zzlLastInRange(zl,&range);
 | 
			
		||||
            eptr = zzlLastInRange(zl,range);
 | 
			
		||||
        } else {
 | 
			
		||||
            eptr = zzlFirstInRange(zl,&range);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* No "first" element in the specified interval. */
 | 
			
		||||
        if (eptr == NULL) {
 | 
			
		||||
            addReply(c,shared.emptyarray);
 | 
			
		||||
            return;
 | 
			
		||||
            eptr = zzlFirstInRange(zl,range);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Get score pointer for the first element. */
 | 
			
		||||
        serverAssertWithInfo(c,zobj,eptr != NULL);
 | 
			
		||||
        sptr = ziplistNext(zl,eptr);
 | 
			
		||||
 | 
			
		||||
        /* We don't know in advance how many matching elements there are in the
 | 
			
		||||
         * list, so we push this object that will represent the multi-bulk
 | 
			
		||||
         * length in the output buffer, and will "fix" it later */
 | 
			
		||||
        replylen = addReplyDeferredLen(c);
 | 
			
		||||
        if (eptr)
 | 
			
		||||
            sptr = ziplistNext(zl,eptr);
 | 
			
		||||
 | 
			
		||||
        /* If there is an offset, just traverse the number of elements without
 | 
			
		||||
         * checking the score because that is done in the next loop. */
 | 
			
		||||
| 
						 | 
				
			
			@ -2995,13 +3114,13 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        while (eptr && limit--) {
 | 
			
		||||
            score = zzlGetScore(sptr);
 | 
			
		||||
            double score = zzlGetScore(sptr);
 | 
			
		||||
 | 
			
		||||
            /* Abort when the node is no longer in range. */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
                if (!zslValueGteMin(score,&range)) break;
 | 
			
		||||
                if (!zslValueGteMin(score,range)) break;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!zslValueLteMax(score,&range)) break;
 | 
			
		||||
                if (!zslValueLteMax(score,range)) break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* We know the element exists, so ziplistGet should always
 | 
			
		||||
| 
						 | 
				
			
			@ -3009,13 +3128,11 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		|||
            serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
 | 
			
		||||
 | 
			
		||||
            rangelen++;
 | 
			
		||||
            if (withscores && c->resp > 2) addReplyArrayLen(c,2);
 | 
			
		||||
            if (vstr == NULL) {
 | 
			
		||||
                addReplyBulkLongLong(c,vlong);
 | 
			
		||||
                handler->emitResultFromLongLong(handler, vlong, score);
 | 
			
		||||
            } else {
 | 
			
		||||
                addReplyBulkCBuffer(c,vstr,vlen);
 | 
			
		||||
                handler->emitResultFromCBuffer(handler, vstr, vlen, score);
 | 
			
		||||
            }
 | 
			
		||||
            if (withscores) addReplyDouble(c,score);
 | 
			
		||||
 | 
			
		||||
            /* Move to next node */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3031,22 +3148,11 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		|||
 | 
			
		||||
        /* If reversed, get the last node in range as starting point. */
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
            ln = zslLastInRange(zsl,&range);
 | 
			
		||||
            ln = zslLastInRange(zsl,range);
 | 
			
		||||
        } else {
 | 
			
		||||
            ln = zslFirstInRange(zsl,&range);
 | 
			
		||||
            ln = zslFirstInRange(zsl,range);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* No "first" element in the specified interval. */
 | 
			
		||||
        if (ln == NULL) {
 | 
			
		||||
            addReply(c,shared.emptyarray);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* We don't know in advance how many matching elements there are in the
 | 
			
		||||
         * list, so we push this object that will represent the multi-bulk
 | 
			
		||||
         * length in the output buffer, and will "fix" it later */
 | 
			
		||||
        replylen = addReplyDeferredLen(c);
 | 
			
		||||
 | 
			
		||||
        /* If there is an offset, just traverse the number of elements without
 | 
			
		||||
         * checking the score because that is done in the next loop. */
 | 
			
		||||
        while (ln && offset--) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3060,15 +3166,14 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		|||
        while (ln && limit--) {
 | 
			
		||||
            /* Abort when the node is no longer in range. */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
                if (!zslValueGteMin(ln->score,&range)) break;
 | 
			
		||||
                if (!zslValueGteMin(ln->score,range)) break;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!zslValueLteMax(ln->score,&range)) break;
 | 
			
		||||
                if (!zslValueLteMax(ln->score,range)) break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            rangelen++;
 | 
			
		||||
            if (withscores && c->resp > 2) addReplyArrayLen(c,2);
 | 
			
		||||
            addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));
 | 
			
		||||
            if (withscores) addReplyDouble(c,ln->score);
 | 
			
		||||
            handler->emitResultFromCBuffer(handler, ln->ele, sdslen(ln->ele),
 | 
			
		||||
              ((withscores) ? ln->score : ln->score));
 | 
			
		||||
 | 
			
		||||
            /* Move to next node */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3081,16 +3186,21 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
 | 
			
		|||
        serverPanic("Unknown sorted set encoding");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (withscores && c->resp == 2) rangelen *= 2;
 | 
			
		||||
    setDeferredArrayLen(c, replylen, rangelen);
 | 
			
		||||
    handler->finalizeResultEmission(handler, rangelen);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count] */
 | 
			
		||||
void zrangebyscoreCommand(client *c) {
 | 
			
		||||
    genericZrangebyscoreCommand(c,0);
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
 | 
			
		||||
    zrangeGenericCommand(&handler, 1, 0, ZRANGE_SCORE, ZRANGE_DIRECTION_FORWARD);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZREVRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count] */
 | 
			
		||||
void zrevrangebyscoreCommand(client *c) {
 | 
			
		||||
    genericZrangebyscoreCommand(c,1);
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
 | 
			
		||||
    zrangeGenericCommand(&handler, 1, 0, ZRANGE_SCORE, ZRANGE_DIRECTION_REVERSE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void zcountCommand(client *c) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3250,58 +3360,14 @@ void zlexcountCommand(client *c) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/* This command implements ZRANGEBYLEX, ZREVRANGEBYLEX. */
 | 
			
		||||
void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		||||
    zlexrangespec range;
 | 
			
		||||
    robj *key = c->argv[1];
 | 
			
		||||
    robj *zobj;
 | 
			
		||||
    long offset = 0, limit = -1;
 | 
			
		||||
void genericZrangebylexCommand(zrange_result_handler *handler,
 | 
			
		||||
    zlexrangespec *range, robj *zobj, int withscores, long offset, long limit,
 | 
			
		||||
    int reverse)
 | 
			
		||||
{
 | 
			
		||||
    client *c = handler->client;
 | 
			
		||||
    unsigned long rangelen = 0;
 | 
			
		||||
    void *replylen = NULL;
 | 
			
		||||
    int minidx, maxidx;
 | 
			
		||||
 | 
			
		||||
    /* Parse the range arguments. */
 | 
			
		||||
    if (reverse) {
 | 
			
		||||
        /* Range is given as [max,min] */
 | 
			
		||||
        maxidx = 2; minidx = 3;
 | 
			
		||||
    } else {
 | 
			
		||||
        /* Range is given as [min,max] */
 | 
			
		||||
        minidx = 2; maxidx = 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (zslParseLexRange(c->argv[minidx],c->argv[maxidx],&range) != C_OK) {
 | 
			
		||||
        addReplyError(c,"min or max not valid string range item");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Parse optional extra arguments. Note that ZCOUNT will exactly have
 | 
			
		||||
     * 4 arguments, so we'll never enter the following code path. */
 | 
			
		||||
    if (c->argc > 4) {
 | 
			
		||||
        int remaining = c->argc - 4;
 | 
			
		||||
        int pos = 4;
 | 
			
		||||
 | 
			
		||||
        while (remaining) {
 | 
			
		||||
            if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
 | 
			
		||||
                if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
 | 
			
		||||
                    (getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
 | 
			
		||||
                    zslFreeLexRange(&range);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                pos += 3; remaining -= 3;
 | 
			
		||||
            } else {
 | 
			
		||||
                zslFreeLexRange(&range);
 | 
			
		||||
                addReplyErrorObject(c,shared.syntaxerr);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Ok, lookup the key and get the range */
 | 
			
		||||
    if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
 | 
			
		||||
        checkType(c,zobj,OBJ_ZSET))
 | 
			
		||||
    {
 | 
			
		||||
        zslFreeLexRange(&range);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    handler->beginResultEmission(handler);
 | 
			
		||||
 | 
			
		||||
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
 | 
			
		||||
        unsigned char *zl = zobj->ptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -3312,26 +3378,14 @@ void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		|||
 | 
			
		||||
        /* If reversed, get the last node in range as starting point. */
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
            eptr = zzlLastInLexRange(zl,&range);
 | 
			
		||||
            eptr = zzlLastInLexRange(zl,range);
 | 
			
		||||
        } else {
 | 
			
		||||
            eptr = zzlFirstInLexRange(zl,&range);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* No "first" element in the specified interval. */
 | 
			
		||||
        if (eptr == NULL) {
 | 
			
		||||
            addReply(c,shared.emptyarray);
 | 
			
		||||
            zslFreeLexRange(&range);
 | 
			
		||||
            return;
 | 
			
		||||
            eptr = zzlFirstInLexRange(zl,range);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Get score pointer for the first element. */
 | 
			
		||||
        serverAssertWithInfo(c,zobj,eptr != NULL);
 | 
			
		||||
        sptr = ziplistNext(zl,eptr);
 | 
			
		||||
 | 
			
		||||
        /* We don't know in advance how many matching elements there are in the
 | 
			
		||||
         * list, so we push this object that will represent the multi-bulk
 | 
			
		||||
         * length in the output buffer, and will "fix" it later */
 | 
			
		||||
        replylen = addReplyDeferredLen(c);
 | 
			
		||||
        if (eptr)
 | 
			
		||||
            sptr = ziplistNext(zl,eptr);
 | 
			
		||||
 | 
			
		||||
        /* If there is an offset, just traverse the number of elements without
 | 
			
		||||
         * checking the score because that is done in the next loop. */
 | 
			
		||||
| 
						 | 
				
			
			@ -3344,11 +3398,15 @@ void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        while (eptr && limit--) {
 | 
			
		||||
            double score = 0;
 | 
			
		||||
            if (withscores) /* don't bother to extract the score if it's gonna be ignored. */
 | 
			
		||||
                score = zzlGetScore(sptr);
 | 
			
		||||
 | 
			
		||||
            /* Abort when the node is no longer in range. */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
                if (!zzlLexValueGteMin(eptr,&range)) break;
 | 
			
		||||
                if (!zzlLexValueGteMin(eptr,range)) break;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!zzlLexValueLteMax(eptr,&range)) break;
 | 
			
		||||
                if (!zzlLexValueLteMax(eptr,range)) break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* We know the element exists, so ziplistGet should always
 | 
			
		||||
| 
						 | 
				
			
			@ -3357,9 +3415,9 @@ void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		|||
 | 
			
		||||
            rangelen++;
 | 
			
		||||
            if (vstr == NULL) {
 | 
			
		||||
                addReplyBulkLongLong(c,vlong);
 | 
			
		||||
                handler->emitResultFromLongLong(handler, vlong, score);
 | 
			
		||||
            } else {
 | 
			
		||||
                addReplyBulkCBuffer(c,vstr,vlen);
 | 
			
		||||
                handler->emitResultFromCBuffer(handler, vstr, vlen, score);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* Move to next node */
 | 
			
		||||
| 
						 | 
				
			
			@ -3376,23 +3434,11 @@ void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		|||
 | 
			
		||||
        /* If reversed, get the last node in range as starting point. */
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
            ln = zslLastInLexRange(zsl,&range);
 | 
			
		||||
            ln = zslLastInLexRange(zsl,range);
 | 
			
		||||
        } else {
 | 
			
		||||
            ln = zslFirstInLexRange(zsl,&range);
 | 
			
		||||
            ln = zslFirstInLexRange(zsl,range);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* No "first" element in the specified interval. */
 | 
			
		||||
        if (ln == NULL) {
 | 
			
		||||
            addReply(c,shared.emptyarray);
 | 
			
		||||
            zslFreeLexRange(&range);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* We don't know in advance how many matching elements there are in the
 | 
			
		||||
         * list, so we push this object that will represent the multi-bulk
 | 
			
		||||
         * length in the output buffer, and will "fix" it later */
 | 
			
		||||
        replylen = addReplyDeferredLen(c);
 | 
			
		||||
 | 
			
		||||
        /* If there is an offset, just traverse the number of elements without
 | 
			
		||||
         * checking the score because that is done in the next loop. */
 | 
			
		||||
        while (ln && offset--) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3406,13 +3452,13 @@ void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		|||
        while (ln && limit--) {
 | 
			
		||||
            /* Abort when the node is no longer in range. */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
                if (!zslLexValueGteMin(ln->ele,&range)) break;
 | 
			
		||||
                if (!zslLexValueGteMin(ln->ele,range)) break;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!zslLexValueLteMax(ln->ele,&range)) break;
 | 
			
		||||
                if (!zslLexValueLteMax(ln->ele,range)) break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            rangelen++;
 | 
			
		||||
            addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));
 | 
			
		||||
            handler->emitResultFromCBuffer(handler, ln->ele, sdslen(ln->ele), ln->score);
 | 
			
		||||
 | 
			
		||||
            /* Move to next node */
 | 
			
		||||
            if (reverse) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3425,16 +3471,171 @@ void genericZrangebylexCommand(client *c, int reverse) {
 | 
			
		|||
        serverPanic("Unknown sorted set encoding");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    zslFreeLexRange(&range);
 | 
			
		||||
    setDeferredArrayLen(c, replylen, rangelen);
 | 
			
		||||
    handler->finalizeResultEmission(handler, rangelen);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZRANGEBYLEX <key> <min> <max> [LIMIT offset count] */
 | 
			
		||||
void zrangebylexCommand(client *c) {
 | 
			
		||||
    genericZrangebylexCommand(c,0);
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
 | 
			
		||||
    zrangeGenericCommand(&handler, 1, 0, ZRANGE_LEX, ZRANGE_DIRECTION_FORWARD);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ZREVRANGEBYLEX <key> <min> <max> [LIMIT offset count] */
 | 
			
		||||
void zrevrangebylexCommand(client *c) {
 | 
			
		||||
    genericZrangebylexCommand(c,1);
 | 
			
		||||
    zrange_result_handler handler;
 | 
			
		||||
    zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
 | 
			
		||||
    zrangeGenericCommand(&handler, 1, 0, ZRANGE_LEX, ZRANGE_DIRECTION_REVERSE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This function handles ZRANGE and ZRANGESTORE, and also the deprecated
 | 
			
		||||
 * Z[REV]RANGE[BYPOS|BYLEX] commands.
 | 
			
		||||
 *
 | 
			
		||||
 * The simple ZRANGE and ZRANGESTORE can take _AUTO in rangetype and direction,
 | 
			
		||||
 * other command pass explicit value.
 | 
			
		||||
 *
 | 
			
		||||
 * The argc_start points to the src key argument, so following syntax is like:
 | 
			
		||||
 * <src> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count]
 | 
			
		||||
 */
 | 
			
		||||
void zrangeGenericCommand(zrange_result_handler *handler, int argc_start, int store,
 | 
			
		||||
                          zrange_type rangetype, zrange_direction direction)
 | 
			
		||||
{
 | 
			
		||||
    client *c = handler->client;
 | 
			
		||||
    robj *key = c->argv[argc_start];
 | 
			
		||||
    robj *zobj;
 | 
			
		||||
    zrangespec range;
 | 
			
		||||
    zlexrangespec lexrange;
 | 
			
		||||
    int minidx = argc_start + 1;
 | 
			
		||||
    int maxidx = argc_start + 2;
 | 
			
		||||
 | 
			
		||||
    /* Options common to all */
 | 
			
		||||
    long opt_start = 0;
 | 
			
		||||
    long opt_end = 0;
 | 
			
		||||
    int opt_withscores = 0;
 | 
			
		||||
    long opt_offset = 0;
 | 
			
		||||
    long opt_limit = -1;
 | 
			
		||||
 | 
			
		||||
    /* Step 1: Skip the <src> <min> <max> args and parse remaining optional arguments. */
 | 
			
		||||
    for (int j=argc_start + 3; j < c->argc; j++) {
 | 
			
		||||
        int leftargs = c->argc-j-1;
 | 
			
		||||
        if (!store && !strcasecmp(c->argv[j]->ptr,"withscores")) {
 | 
			
		||||
            opt_withscores = 1;
 | 
			
		||||
        } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
 | 
			
		||||
            if ((getLongFromObjectOrReply(c, c->argv[j+1], &opt_offset, NULL) != C_OK) ||
 | 
			
		||||
                (getLongFromObjectOrReply(c, c->argv[j+2], &opt_limit, NULL) != C_OK))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            j += 2;
 | 
			
		||||
        } else if (direction == ZRANGE_DIRECTION_AUTO &&
 | 
			
		||||
                   !strcasecmp(c->argv[j]->ptr,"rev"))
 | 
			
		||||
        {
 | 
			
		||||
            direction = ZRANGE_DIRECTION_REVERSE;
 | 
			
		||||
        } else if (rangetype == ZRANGE_AUTO &&
 | 
			
		||||
                   !strcasecmp(c->argv[j]->ptr,"bylex"))
 | 
			
		||||
        {
 | 
			
		||||
            rangetype = ZRANGE_LEX;
 | 
			
		||||
        } else if (rangetype == ZRANGE_AUTO &&
 | 
			
		||||
                   !strcasecmp(c->argv[j]->ptr,"byscore"))
 | 
			
		||||
        {
 | 
			
		||||
            rangetype = ZRANGE_SCORE;
 | 
			
		||||
        } else {
 | 
			
		||||
            addReplyErrorObject(c,shared.syntaxerr);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Use defaults if not overriden by arguments. */
 | 
			
		||||
    if (direction == ZRANGE_DIRECTION_AUTO)
 | 
			
		||||
        direction = ZRANGE_DIRECTION_FORWARD;
 | 
			
		||||
    if (rangetype == ZRANGE_AUTO)
 | 
			
		||||
        rangetype = ZRANGE_RANK;
 | 
			
		||||
 | 
			
		||||
    /* Check for conflicting arguments. */
 | 
			
		||||
    if (opt_limit != -1 && rangetype == ZRANGE_RANK) {
 | 
			
		||||
        addReplyError(c,"syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (opt_withscores && rangetype == ZRANGE_LEX) {
 | 
			
		||||
        addReplyError(c,"syntax error, WITHSCORES not supported in combination with BYLEX");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (direction == ZRANGE_DIRECTION_REVERSE &&
 | 
			
		||||
        ((ZRANGE_SCORE == rangetype) || (ZRANGE_LEX == rangetype)))
 | 
			
		||||
    {
 | 
			
		||||
        /* Range is given as [max,min] */
 | 
			
		||||
        int tmp = maxidx;
 | 
			
		||||
        maxidx = minidx;
 | 
			
		||||
        minidx = tmp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Step 2: Parse the range. */
 | 
			
		||||
    switch (rangetype) {
 | 
			
		||||
    case ZRANGE_AUTO:
 | 
			
		||||
    case ZRANGE_RANK:
 | 
			
		||||
        /* Z[REV]RANGE, ZRANGESTORE [REV]RANGE */
 | 
			
		||||
        if ((getLongFromObjectOrReply(c, c->argv[minidx], &opt_start,NULL) != C_OK) ||
 | 
			
		||||
            (getLongFromObjectOrReply(c, c->argv[maxidx], &opt_end,NULL) != C_OK))
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case ZRANGE_SCORE:
 | 
			
		||||
        /* Z[REV]RANGEBYSCORE, ZRANGESTORE [REV]RANGEBYSCORE */
 | 
			
		||||
        if (zslParseRange(c->argv[minidx], c->argv[maxidx], &range) != C_OK) {
 | 
			
		||||
            addReplyError(c, "min or max is not a float");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case ZRANGE_LEX:
 | 
			
		||||
        /* Z[REV]RANGEBYLEX, ZRANGESTORE [REV]RANGEBYLEX */
 | 
			
		||||
        if (zslParseLexRange(c->argv[minidx], c->argv[maxidx], &lexrange) != C_OK) {
 | 
			
		||||
            addReplyError(c, "min or max not valid string range item");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opt_withscores || store) {
 | 
			
		||||
        zrangeResultHandlerScoreEmissionEnable(handler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Step 3: Lookup the key and get the range. */
 | 
			
		||||
    if (((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL)
 | 
			
		||||
        || checkType(c, zobj, OBJ_ZSET)) {
 | 
			
		||||
        goto cleanup;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Step 4: Pass this to the command-specific handler. */
 | 
			
		||||
    switch (rangetype) {
 | 
			
		||||
    case ZRANGE_AUTO:
 | 
			
		||||
    case ZRANGE_RANK:
 | 
			
		||||
        genericZrangebyrankCommand(handler, zobj, opt_start, opt_end,
 | 
			
		||||
            opt_withscores || store, direction == ZRANGE_DIRECTION_REVERSE);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case ZRANGE_SCORE:
 | 
			
		||||
        genericZrangebyscoreCommand(handler, &range, zobj, opt_withscores || store,
 | 
			
		||||
            opt_offset, opt_limit, direction == ZRANGE_DIRECTION_REVERSE);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case ZRANGE_LEX:
 | 
			
		||||
        genericZrangebylexCommand(handler, &lexrange, zobj, opt_withscores || store,
 | 
			
		||||
            opt_offset, opt_limit, direction == ZRANGE_DIRECTION_REVERSE);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Instead of returning here, we'll just fall-through the clean-up. */
 | 
			
		||||
 | 
			
		||||
cleanup:
 | 
			
		||||
 | 
			
		||||
    if (rangetype == ZRANGE_LEX) {
 | 
			
		||||
        zslFreeLexRange(&lexrange);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void zcardCommand(client *c) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1472,4 +1472,86 @@ start_server {tags {"zset"}} {
 | 
			
		|||
        }
 | 
			
		||||
        r config set zset-max-ziplist-entries $original_max
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE basic} {
 | 
			
		||||
        r flushall
 | 
			
		||||
        r zadd z1 1 a 2 b 3 c 4 d
 | 
			
		||||
        set res [r zrangestore z2 z1 0 -1]
 | 
			
		||||
        assert_equal $res 4
 | 
			
		||||
        r zrange z2 0 -1 withscores
 | 
			
		||||
    } {a 1 b 2 c 3 d 4}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE range} {
 | 
			
		||||
        set res [r zrangestore z2 z1 1 2]
 | 
			
		||||
        assert_equal $res 2
 | 
			
		||||
        r zrange z2 0 -1 withscores
 | 
			
		||||
    } {b 2 c 3}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE BYLEX} {
 | 
			
		||||
        set res [r zrangestore z2 z1 \[b \[c BYLEX]
 | 
			
		||||
        assert_equal $res 2
 | 
			
		||||
        r zrange z2 0 -1 withscores
 | 
			
		||||
    } {b 2 c 3}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE BYSCORE} {
 | 
			
		||||
        set res [r zrangestore z2 z1 1 2 BYSCORE]
 | 
			
		||||
        assert_equal $res 2
 | 
			
		||||
        r zrange z2 0 -1 withscores
 | 
			
		||||
    } {a 1 b 2}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE BYSCORE LIMIT} {
 | 
			
		||||
        set res [r zrangestore z2 z1 0 5 BYSCORE LIMIT 0 2]
 | 
			
		||||
        assert_equal $res 2
 | 
			
		||||
        r zrange z2 0 -1 withscores
 | 
			
		||||
    } {a 1 b 2}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE BYSCORE REV LIMIT} {
 | 
			
		||||
        set res [r zrangestore z2 z1 5 0 BYSCORE REV LIMIT 0 2]
 | 
			
		||||
        assert_equal $res 2
 | 
			
		||||
        r zrange z2 0 -1 withscores
 | 
			
		||||
    } {c 3 d 4}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGE BYSCORE REV LIMIT} {
 | 
			
		||||
        r zrange z1 5 0 BYSCORE REV LIMIT 0 2 WITHSCORES
 | 
			
		||||
    } {d 4 c 3}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE - empty range} {
 | 
			
		||||
        set res [r zrangestore z2 z1 5 6]
 | 
			
		||||
        assert_equal $res 0
 | 
			
		||||
        r exists z2
 | 
			
		||||
    } {0}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE BYLEX - empty range} {
 | 
			
		||||
        set res [r zrangestore z2 z1 \[f \[g BYLEX]
 | 
			
		||||
        assert_equal $res 0
 | 
			
		||||
        r exists z2
 | 
			
		||||
    } {0}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE BYSCORE - empty range} {
 | 
			
		||||
        set res [r zrangestore z2 z1 5 6 BYSCORE]
 | 
			
		||||
        assert_equal $res 0
 | 
			
		||||
        r exists z2
 | 
			
		||||
    } {0}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGE BYLEX} {
 | 
			
		||||
        r zrange z1 \[b \[c BYLEX
 | 
			
		||||
    } {b c}
 | 
			
		||||
 | 
			
		||||
    test {ZRANGESTORE invalid syntax} {
 | 
			
		||||
        catch {r zrangestore z2 z1 0 -1 limit 1 2} err
 | 
			
		||||
        assert_match "*syntax*" $err
 | 
			
		||||
        catch {r zrangestore z2 z1 0 -1 WITHSCORES} err
 | 
			
		||||
        assert_match "*syntax*" $err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test {ZRANGE invalid syntax} {
 | 
			
		||||
        catch {r zrange z1 0 -1 limit 1 2} err
 | 
			
		||||
        assert_match "*syntax*" $err
 | 
			
		||||
        catch {r zrange z1 0 -1 BYLEX WITHSCORES} err
 | 
			
		||||
        assert_match "*syntax*" $err
 | 
			
		||||
        catch {r zrevrange z1 0 -1 BYSCORE} err
 | 
			
		||||
        assert_match "*syntax*" $err
 | 
			
		||||
        catch {r zrangebyscore z1 0 -1 REV} err
 | 
			
		||||
        assert_match "*syntax*" $err
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue