Skip to content

Commit

Permalink
ENG-2792. Implement ZRANGE for Sorted Sets.
Browse files Browse the repository at this point in the history
Summary:
ZRANGE key start stop [WITHSCORES]

https://redis.io/commands/zrange

Test Plan: c++ redisserver tests.

Reviewers: amitanand, hector, pritam.damania

Reviewed By: pritam.damania

Subscribers: kannan

Differential Revision: https://phabricator.dev.yugabyte.com/D4570
  • Loading branch information
rahuldesirazu committed Apr 17, 2018
1 parent 158def7 commit 8797e39
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/yb/common/redis_protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ message RedisCollectionGetRangeRequestPB {
TSRANGEBYTIME = 1;
ZRANGEBYSCORE = 2;
ZREVRANGE = 3;
ZRANGE = 4;
UNKNOWN = 99;
}

Expand Down
21 changes: 16 additions & 5 deletions src/yb/docdb/doc_operation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ CHECKED_STATUS GetAndPopulateResponseValues(
}

// Get normalized (with respect to card) upper and lower index bounds for reverse range scans.
void GetNormalizedBounds(int64 low_idx, int64 high_idx, int64 card,
void GetNormalizedBounds(int64 low_idx, int64 high_idx, int64 card, bool reverse,
int64* low_idx_normalized, int64* high_idx_normalized) {
// Turn negative bounds positive.
if (low_idx < 0) {
Expand All @@ -460,8 +460,13 @@ void GetNormalizedBounds(int64 low_idx, int64 high_idx, int64 card,
}

// Index from lower to upper instead of upper to lower.
*low_idx_normalized = card - high_idx - 1;
*high_idx_normalized = card - low_idx - 1;
if (reverse) {
*low_idx_normalized = card - high_idx - 1;
*high_idx_normalized = card - low_idx - 1;
} else {
*low_idx_normalized = low_idx;
*high_idx_normalized = high_idx;
}

// Fit bounds to range [0, card).
if (*low_idx_normalized < 0) {
Expand Down Expand Up @@ -1282,6 +1287,7 @@ Status RedisReadOperation::ExecuteCollectionGetRange() {
}
break;
}
case RedisCollectionGetRangeRequestPB_GetRangeRequestType_ZRANGE: FALLTHROUGH_INTENDED;
case RedisCollectionGetRangeRequestPB_GetRangeRequestType_ZREVRANGE: {
if(!request_.has_index_range() || !request_.index_range().has_lower_bound() ||
!request_.index_range().has_upper_bound()) {
Expand All @@ -1307,7 +1313,12 @@ Status RedisReadOperation::ExecuteCollectionGetRange() {
int64 low_idx = low_index_bound.index();
int64 high_idx = high_index_bound.index();
// Normalize the bounds to be positive and go from low to high index.
GetNormalizedBounds(low_idx, high_idx, card, &low_idx_normalized, &high_idx_normalized);
bool reverse = false;
if (request_type == RedisCollectionGetRangeRequestPB_GetRangeRequestType_ZREVRANGE) {
reverse = true;
}
GetNormalizedBounds(
low_idx, high_idx, card, reverse, &low_idx_normalized, &high_idx_normalized);

if (high_idx_normalized < low_idx_normalized) {
// Return empty response.
Expand Down Expand Up @@ -1336,7 +1347,7 @@ Status RedisReadOperation::ExecuteCollectionGetRange() {

RETURN_NOT_OK(GetAndPopulateResponseValues(
iterator_.get(), AddResponseValuesSortedSets, data, ValueType::kObject, request_,
&response_, add_keys, /* add_values */ true, /* reverse */ true));
&response_, add_keys, /* add_values */ true, reverse));
break;
}
case RedisCollectionGetRangeRequestPB_GetRangeRequestType_UNKNOWN:
Expand Down
1 change: 1 addition & 0 deletions src/yb/yql/redis/redisserver/redis_commands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace redisserver {
((tscard, TsCard, 2, READ)) \
((zrangebyscore, ZRangeByScore, -4, READ)) \
((zrevrange, ZRevRange, -4, READ)) \
((zrange, ZRange, -4, READ)) \
((tsrem, TsRem, -3, WRITE)) \
((zrem, ZRem, -3, WRITE)) \
((zadd, ZAdd, -4, WRITE)) \
Expand Down
35 changes: 23 additions & 12 deletions src/yb/yql/redis/redisserver/redis_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -623,31 +623,42 @@ CHECKED_STATUS ParseZRangeByScore(YBRedisReadOp* op, const RedisClientCommand& a
}
}

CHECKED_STATUS ParseZRevRange(YBRedisReadOp* op, const RedisClientCommand& args) {
CHECKED_STATUS ParseIndexBasedQuery(
YBRedisReadOp* op,
const RedisClientCommand& args,
RedisCollectionGetRangeRequestPB::GetRangeRequestType request_type) {
if (args.size() <= 5) {
op->mutable_request()->set_allocated_get_collection_range_request(
new RedisCollectionGetRangeRequestPB());
op->mutable_request()->mutable_get_collection_range_request()->set_request_type(
RedisCollectionGetRangeRequestPB_GetRangeRequestType_ZREVRANGE);
op->mutable_request()->mutable_get_collection_range_request()->set_request_type(request_type);

const auto& key = args[1];
RETURN_NOT_OK(ParseIndexBound(
args[2],
op->mutable_request()->mutable_index_range()->mutable_lower_bound()));
RETURN_NOT_OK(ParseIndexBound(
args[3],
op->mutable_request()->mutable_index_range()->mutable_upper_bound()));
RETURN_NOT_OK(ParseIndexBound(
args[2],
op->mutable_request()->mutable_index_range()->mutable_lower_bound()));
RETURN_NOT_OK(ParseIndexBound(
args[3],
op->mutable_request()->mutable_index_range()->mutable_upper_bound()));
op->mutable_request()->mutable_key_value()->set_key(key.ToBuffer());
if (args.size() == 5) {
RETURN_NOT_OK(ParseWithScores(
args[4],
op->mutable_request()->mutable_get_collection_range_request()));
}
return Status::OK();
} else {
return STATUS(InvalidArgument, "Expected at most 5 arguments, found $0",
std::to_string(args.size()));
}
return STATUS(InvalidArgument, "Expected at most 5 arguments, found $0",
std::to_string(args.size()));
}

CHECKED_STATUS ParseZRange(YBRedisReadOp* op, const RedisClientCommand& args) {
return ParseIndexBasedQuery(
op, args, RedisCollectionGetRangeRequestPB_GetRangeRequestType_ZRANGE);
}

CHECKED_STATUS ParseZRevRange(YBRedisReadOp* op, const RedisClientCommand& args) {
return ParseIndexBasedQuery(
op, args, RedisCollectionGetRangeRequestPB_GetRangeRequestType_ZREVRANGE);
}

CHECKED_STATUS ParseTsGet(YBRedisReadOp* op, const RedisClientCommand& args) {
Expand Down
44 changes: 44 additions & 0 deletions src/yb/yql/redis/redisserver/redisserver-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,50 @@ TEST_F(TestRedisService, TestZRevRange) {
VerifyCallbacks();
}

TEST_F(TestRedisService, TestZRange) {
// The default value is true, but we explicitly set this here for clarity.
FLAGS_emulate_redis_responses = true;
DoRedisTestInt(__LINE__, {"ZADD", "z_multi", "0", "v0", "0", "v1", "0", "v2",
"1", "v3", "1", "v4", "1", "v5"}, 6);
SyncClient();

DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "0", "5"}, {"v0", "v1", "v2", "v3", "v4", "v5"});
DoRedisTestScoreValueArray(__LINE__, {"ZRANGE", "z_multi", "0", "-1", "WITHSCORES"},
{0, 0, 0, 1, 1, 1},
{"v0", "v1", "v2", "v3", "v4", "v5"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "0", "1"}, {"v0", "v1"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "2", "3"}, {"v2", "v3"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "6", "7"}, {});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "0", "-1"},
{"v0", "v1", "v2", "v3", "v4", "v5"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "-2", "-1"}, {"v4", "v5"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "-3", "-2"}, {"v3", "v4"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "-3", "5"}, {"v3", "v4", "v5"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "0", "100"},
{"v0", "v1", "v2", "v3", "v4", "v5"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "-100", "100"},
{"v0", "v1", "v2", "v3", "v4", "v5"});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "10", "100"}, {});
DoRedisTestArray(__LINE__, {"ZRANGE", "z_multi", "-100", "-10"}, {});

// Test empty key.
DoRedisTestArray(__LINE__, {"ZRANGE", "z_key", "0", "1"}, {});

DoRedisTestExpectError(__LINE__, {"ZRANGE", "z_multi", "0"});
DoRedisTestExpectError(__LINE__, {"ZRANGE", "z_multi", "1", "2", "3"});
DoRedisTestExpectError(__LINE__, {"ZRANGE", "z_multi", "1", "2", "WITHSCORES", "1"});
DoRedisTestExpectError(__LINE__, {"ZRANGE", "z_multi", "1.0", "2.0"});
DoRedisTestExpectError(__LINE__, {"ZRANGE", "1", "2"});

// Test key with wrong type.
DoRedisTestOk(__LINE__, {"SET", "s_key", "s_val"});
DoRedisTestExpectError(__LINE__, {"ZRANGE", "s_key", "1", "2"});

SyncClient();
VerifyCallbacks();
}


TEST_F(TestRedisService, TestTimeSeriesTTL) {
int64_t ttl_sec = 5;
TestTSTtl("EXPIRE_IN", ttl_sec, ttl_sec, "test_expire_in");
Expand Down

0 comments on commit 8797e39

Please sign in to comment.