All files / src/resp/command/sset zrangebyscore-command.ts

14.81% Statements 12/81
0% Branches 0/56
50% Functions 1/2
15% Lines 12/80

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 2041x   1x   1x 1x 1x 1x                                                                                                                         1x 4x   4x   4x   4x   4x                                                                                                                                                                                                                                                            
import { Logger } from "../../../logger";
import { IRequest } from "../../../server/request";
import { DataType } from "../../data/data-type";
import { Database } from "../../data/database";
import { DatabaseValue } from "../../data/database-value";
import { SortedSet } from "../../data/sorted-set";
import { RedisToken } from "../../protocol/redis-token";
import { IRespCommand } from "../resp-command";
 
/**
 * ### Available since 1.0.5.
 * ### ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
 * Returns all the elements in the sorted set at key with a score between min and max
 * (including elements with score equal to min or max). The elements are considered to be
 * ordered from low to high scores.
 *
 * The elements having the same score are returned in lexicographical order (this follows
 * from a property of the sorted set implementation in Redis and does not involve further
 * computation).
 *
 * The optional LIMIT argument can be used to only get a range of the matching elements
 * (similar to SELECT LIMIT offset, count in SQL). A negative count returns all elements from
 * the offset. Keep in mind that if offset is large, the sorted set needs to be traversed for
 * offset elements before getting to the elements to return, which can add up to O(N) time
 * complexity.
 *
 * The optional WITHSCORES argument makes the command return both the element and its score,
 * instead of the element alone. This option is available since Redis 2.0.
 * ### Exclusive intervals and infinity
 *
 * min and max can be -inf and +inf, so that you are not required to know the highest or
 * lowest score in the sorted set to get all elements from or up to a certain score.
 *
 * By default, the interval specified by min and max is closed (inclusive). It is possible
 * to specify an open interval (exclusive) by prefixing the score with the character **(**.
 * For example:
 * ```
 * ZRANGEBYSCORE zset (1 5
 * ```
 * Will return all elements with 1 < score <= 5 while:
 * ```
 * ZRANGEBYSCORE zset (5 (10
 * ```
 * Will return all the elements with 5 < score < 10 (5 and 10 excluded).
 * ### Return value
 * Array reply: list of elements in the specified score range (optionally with their scores).
 * ### Examples
 * ```
 * redis> ZADD myzset 1 "one"
 * (integer) 1
 * redis> ZADD myzset 2 "two"
 * (integer) 1
 * redis> ZADD myzset 3 "three"
 * (integer) 1
 * redis> ZRANGEBYSCORE myzset -inf +inf
 * 1) "one"
 * 2) "two"
 * 3) "three"
 * redis> ZRANGEBYSCORE myzset 1 2
 * 1) "one"
 * 2) "two"
 * redis> ZRANGEBYSCORE myzset (1 2
 * 1) "two"
 * redis> ZRANGEBYSCORE myzset (1 (2
 * (empty list or set)
 * redis>
 * ```
 */
export class ZRangeByScoreCommand extends IRespCommand {
    public DbDataType = DataType.ZSET
 
    public maxParams = 7
 
    public minParams = 3
 
    public name = "zrangebyscore"
 
    private logger: Logger = new Logger(module.id);
 
    public execSync(request: IRequest, db: Database): RedisToken {
        this.logger.debug(
            `${request.getCommand()}.execute(%s)`,
            ...request.getParams()
        );
        const zkey: string = request.getParam(0);
        this.logger.debug(`Zkey is ${zkey}`);
        let min: any = String(request.getParam(1)).toLowerCase();
        this.logger.debug(`min is ${min}`);
        let max: any = String(request.getParam(2)).toLowerCase();
        this.logger.debug(`max is ${max}`);
        let maxExclusive: boolean = false,
            minExclusive: boolean = false,
            withScores: boolean = false;
        if (min.startsWith("(")) {
            minExclusive = true;
            min = min.substring(1);
            this.logger.debug("Setting minExclusive.");
        }
        if (max.startsWith("(")) {
            maxExclusive = true;
            max = max.substring(1);
            this.logger.debug("Setting maxExclusive.");
        }
        if (min === "-inf") {
            min = -Infinity;
        } else if (min === "+inf" || min === "inf") {
            min = Number(Infinity);
        } else {
            min = Number(min);
        }
        this.logger.debug(`min is now ${min}`);
        if (isNaN(min)) {
            return RedisToken.error("ERR min or max is not a float");
        }
        if (max === "-inf") {
            max = -Infinity;
        } else if (max === "+inf" || max === "inf") {
            max = Number(Infinity);
        } else {
            max = Number(max);
        }
        this.logger.debug(`max is now ${max}`);
        if (isNaN(max)) {
            return RedisToken.error("ERR min or max is not a float");
        }
        let count: any = "",
            offset: number = 0;
        if (request.getParams().length >= 4) {
            if (request.getParam(3).toLowerCase() === "withscores") {
                this.logger.debug("Set WITHSCORES");
                withScores = true;
            }
            if (!withScores || request.getParams().length >= 5) {
                if (request.getParam(withScores
                    ? 4
                    : 3).toLowerCase() !== "limit") {
                    this.logger.debug(`Param ${withScores
                        ? 4
                        : 3} is ${request.getParam(withScores
                        ? 4
                        : 3)}`);
                    return RedisToken.error("ERR syntax error");
                }
                if (request.getParams().length - (withScores
                    ? 4
                    : 3) > 0) {
                    if (request.getParams().length - (withScores
                        ? 4
                        : 3) !== 3) {
                        this.logger.debug(`Param count is ${request.getParams().length}`);
                        return RedisToken.error("ERR syntax error");
                    }
                    offset = Number(request.getParam(request.getParams().length - 2));
                    count = Number(request.getParam(request.getParams().length - 1));
                    if (isNaN(offset) || isNaN(count)) {
                        this.logger.debug(`offset is ${offset}, count is ${count}`);
                        return RedisToken.error("ERR value is not an integer or out of range");
                    }
                }
            }
        }
 
        const dbKey: DatabaseValue = db.getOrDefault(
            zkey,
            new DatabaseValue(
                DataType.ZSET,
                new SortedSet()
            )
        );
        if (count === "") {
            count = dbKey.getSortedSet().length;
        }
        const scores: any[] = [],
            options: any = {
                maxExclusive,
                minExclusive,
                withScores
            },
            set: any[] = dbKey.getSortedSet().rangeByScore(
                Number(min),
                Number(max),
                options
            );
        for (let index = offset; index < (count < 0
            ? set.length
            : offset + count); index++) {
            if (index >= set.length) {
                break;
            }
            if (set[index].constructor.name === "Array") {
                this.logger.debug(`pushing ${set[index][0]}, ${set[index][1]}}`);
                scores.push(
                    RedisToken.string(set[index][0]),
                    RedisToken.string(set[index][1])
                );
            } else {
                scores.push(RedisToken.string(set[index]));
            }
        }
        const finalValues = RedisToken.array(scores);
        return finalValues;
    }
}