This is an automated email from the ASF dual-hosted git repository.
jensdeppe pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push:
new 7835dc6 GEODE-9184: Add Radish ZLEXCOUNT command (#6735)
7835dc6 is described below
commit 7835dc65c4901af38955ca466d098e3ebaed2463
Author: Jens Deppe <[email protected]>
AuthorDate: Thu Aug 19 09:17:01 2021 -0700
GEODE-9184: Add Radish ZLEXCOUNT command (#6735)
---
.../ZLexCountNativeRedisAcceptanceTest.java | 34 +++
.../server/AbstractHitsMissesIntegrationTest.java | 7 +-
.../AbstractZLexCountIntegrationTest.java | 247 +++++++++++++++++++++
.../sortedset/ZLexCountIntegrationTest.java | 30 +++
.../geode/redis/internal/RedisCommandType.java | 2 +
.../redis/internal/data/NullRedisSortedSet.java | 25 ++-
.../geode/redis/internal/data/RedisSortedSet.java | 24 ++
.../RedisSortedSetCommandsFunctionExecutor.java | 5 +
.../executor/sortedset/RedisSortedSetCommands.java | 2 +
.../sortedset/SortedSetLexRangeOptions.java | 2 +-
.../executor/sortedset/ZLexCountExecutor.java | 50 +++++
.../redis/internal/SupportedCommandsJUnitTest.java | 1 +
.../redis/internal/data/RedisSortedSetTest.java | 79 +++++++
13 files changed, 496 insertions(+), 12 deletions(-)
diff --git
a/geode-apis-compatible-with-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountNativeRedisAcceptanceTest.java
b/geode-apis-compatible-with-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountNativeRedisAcceptanceTest.java
new file mode 100644
index 0000000..1a1caf5
--- /dev/null
+++
b/geode-apis-compatible-with-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountNativeRedisAcceptanceTest.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ * or implied. See the License for the specific language governing permissions
and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal.executor.sortedset;
+
+import org.junit.ClassRule;
+
+import org.apache.geode.redis.NativeRedisClusterTestRule;
+
+public class ZLexCountNativeRedisAcceptanceTest extends
AbstractZLexCountIntegrationTest {
+ @ClassRule
+ public static NativeRedisClusterTestRule server = new
NativeRedisClusterTestRule();
+
+ @Override
+ public int getPort() {
+ return server.getExposedPorts().get(0);
+ }
+
+ @Override
+ public void flushAll() {
+ server.flushAll();
+ }
+}
diff --git
a/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractHitsMissesIntegrationTest.java
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractHitsMissesIntegrationTest.java
index 0cf091e..c184712 100644
---
a/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractHitsMissesIntegrationTest.java
+++
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractHitsMissesIntegrationTest.java
@@ -249,7 +249,12 @@ public abstract class AbstractHitsMissesIntegrationTest
implements RedisIntegrat
@Test
public void testZIncrBy() {
- runCommandAndAssertNoStatUpdates("key", (k, m) -> jedis.zincrby(k, 100.0,
m));
+ runCommandAndAssertNoStatUpdates(SORTED_SET_KEY, (k, m) ->
jedis.zincrby(k, 100.0, m));
+ }
+
+ @Test
+ public void testZLexCount() {
+ runCommandAndAssertHitsAndMisses(SORTED_SET_KEY, k -> jedis.zlexcount(k,
"-", "+"));
}
@Test
diff --git
a/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/AbstractZLexCountIntegrationTest.java
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/AbstractZLexCountIntegrationTest.java
new file mode 100644
index 0000000..b8128b1
--- /dev/null
+++
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/AbstractZLexCountIntegrationTest.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ * or implied. See the License for the specific language governing permissions
and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal.executor.sortedset;
+
+import static
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs;
+import static
org.apache.geode.redis.internal.RedisConstants.ERROR_MIN_MAX_NOT_A_VALID_STRING;
+import static
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+
+import org.apache.geode.redis.RedisIntegrationTest;
+
+@RunWith(JUnitParamsRunner.class)
+public abstract class AbstractZLexCountIntegrationTest implements
RedisIntegrationTest {
+ public static final String KEY = "key";
+ public static final int SCORE = 1;
+ public static final String BASE_MEMBER_NAME = "v";
+
+ JedisCluster jedis;
+
+ @Before
+ public void setUp() {
+ jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, getPort()),
REDIS_CLIENT_TIMEOUT);
+ }
+
+ @After
+ public void tearDown() {
+ flushAll();
+ jedis.close();
+ }
+
+ @Test
+ public void shouldError_givenWrongNumberOfArguments() {
+ assertAtLeastNArgs(jedis, Protocol.Command.ZLEXCOUNT, 3);
+ }
+
+ @Test
+ @Parameters({"a", "--", "++"})
+ public void shouldError_givenInvalidMinOrMax(String invalidArgument) {
+ assertThatThrownBy(() -> jedis.zlexcount("fakeKey", invalidArgument, "+"))
+ .hasMessageContaining(ERROR_MIN_MAX_NOT_A_VALID_STRING);
+ assertThatThrownBy(() -> jedis.zlexcount("fakeKey", "-", invalidArgument))
+ .hasMessageContaining(ERROR_MIN_MAX_NOT_A_VALID_STRING);
+ assertThatThrownBy(() -> jedis.zlexcount("fakeKey", invalidArgument,
invalidArgument))
+ .hasMessageContaining(ERROR_MIN_MAX_NOT_A_VALID_STRING);
+ }
+
+ @Test
+ public void shouldReturnZero_givenNonExistentKey() {
+ assertThat(jedis.zlexcount("fakeKey", "-", "+")).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnZero_givenMinGreaterThanMax() {
+ jedis.zadd(KEY, SCORE, "member");
+
+ // Range + <= result <= -
+ assertThat(jedis.zlexcount(KEY, "+", "-")).isEqualTo(0);
+ // Range z <= result <= a
+ assertThat(jedis.zlexcount(KEY, "[z", "[a")).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnOne_givenMemberNameInRange() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ // Range m <= result <= n
+ assertThat(jedis.zlexcount(KEY, "[m", "[n")).isEqualTo(1);
+ // Range -infinity <= result <= n
+ assertThat(jedis.zlexcount(KEY, "-", "[n")).isEqualTo(1);
+ // Range m <= result <= +infinity
+ assertThat(jedis.zlexcount(KEY, "[m", "+")).isEqualTo(1);
+ }
+
+ @Test
+ public void shouldReturnOne_givenMinEqualToMemberNameAndMinInclusive() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ // Range member <= result <= n
+ assertThat(jedis.zlexcount(KEY, "[" + memberName, "[n")).isEqualTo(1);
+ }
+
+ @Test
+ public void shouldReturnOne_givenMaxEqualToMemberNameAndMaxInclusive() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ // Range a <= result <= member
+ assertThat(jedis.zlexcount(KEY, "[a", "[" + memberName)).isEqualTo(1);
+ }
+
+ @Test
+ public void
shouldReturnOne_givenMinAndMaxEqualToMemberNameAndMinAndMaxInclusive() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ assertThat(jedis.zlexcount(KEY, "[" + memberName, "[" +
memberName)).isEqualTo(1);
+ }
+
+ @Test
+ @Parameters({"[", "(", "", "-", "+"})
+ public void shouldReturnOne_givenMemberNameIsSpecialCharacter(String
memberName) {
+ jedis.zadd(KEY, SCORE, memberName);
+
+ assertThat(jedis.zlexcount(KEY, "[" + memberName, "[" +
memberName)).isEqualTo(1);
+ }
+
+ @Test
+ public void shouldReturnZero_givenMinEqualToMemberNameAndMinExclusive() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ // Range member < result <= n
+ assertThat(jedis.zlexcount(KEY, "(" + memberName, "[n")).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnZero_givenMaxEqualToMemberNameAndMaxExclusive() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ // Range a <= result < member
+ assertThat(jedis.zlexcount(KEY, "[a", "(" + memberName)).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnZero_givenRangeExcludingMember() {
+ String memberName = "member";
+ jedis.zadd(KEY, SCORE, memberName);
+
+ // Range n <= result <= o
+ assertThat(jedis.zlexcount(KEY, "[n", "[o")).isEqualTo(0);
+ }
+
+ @Test
+ public void
shouldReturnCount_givenMultipleMembersInRange_withInclusiveMinAndMax() {
+ populateSortedSet();
+
+ int minLength = 3;
+ int maxLength = 6;
+ String min = StringUtils.repeat(BASE_MEMBER_NAME, minLength);
+ String max = StringUtils.repeat(BASE_MEMBER_NAME, maxLength);
+
+ // Range (v * 3) <= result <= (v * 6)
+ assertThat(jedis.zlexcount(KEY, "[" + min, "[" + max)).isEqualTo(4);
+ }
+
+ @Test
+ public void
shouldReturnCount_givenMultipleMembersInRange_withExclusiveMinAndMax() {
+ populateSortedSet();
+
+ int minLength = 1;
+ int maxLength = 7;
+ String min = StringUtils.repeat(BASE_MEMBER_NAME, minLength);
+ String max = StringUtils.repeat(BASE_MEMBER_NAME, maxLength);
+
+ // Range (v * 1) < result < (v * 7)
+ assertThat(jedis.zlexcount(KEY, "(" + min, "(" + max)).isEqualTo(5);
+ }
+
+ @Test
+ public void
shouldReturnCount_givenMultipleMembersInRange_withInclusiveMinAndExclusiveMax()
{
+ populateSortedSet();
+
+ int minLength = 5;
+ int maxLength = 8;
+ String min = StringUtils.repeat(BASE_MEMBER_NAME, minLength);
+ String max = StringUtils.repeat(BASE_MEMBER_NAME, maxLength);
+
+ // Range (v * 5) <= result < (v * 8)
+ assertThat(jedis.zlexcount(KEY, "[" + min, "(" + max)).isEqualTo(3);
+ }
+
+ @Test
+ public void
shouldReturnCount_givenMultipleMembersInRange_withExclusiveMinAndInclusiveMax()
{
+ populateSortedSet();
+
+ int minLength = 2;
+ int maxLength = 5;
+ String min = StringUtils.repeat(BASE_MEMBER_NAME, minLength);
+ String max = StringUtils.repeat(BASE_MEMBER_NAME, maxLength);
+
+ // Range (v * 2) < result <= (v * 5)
+ assertThat(jedis.zlexcount(KEY, "(" + min, "[" + max)).isEqualTo(3);
+ }
+
+ @Test
+ public void
shouldReturnCount_givenMultipleMembersInRangeUsingMinusAndPlusArguments() {
+ populateSortedSet();
+
+ int minLength = 4;
+ int maxLength = 8;
+ String min = StringUtils.repeat(BASE_MEMBER_NAME, minLength);
+ String max = StringUtils.repeat(BASE_MEMBER_NAME, maxLength);
+
+ // Range -infinity <= result <= (v * 8)
+ assertThat(jedis.zlexcount(KEY, "-", "[" + max)).isEqualTo(8);
+
+ // Range (v * 4) <= result < +infinity
+ assertThat(jedis.zlexcount(KEY, "[" + min, "+")).isEqualTo(7);
+
+ // Range -infinity <= result < +infinity
+ assertThat(jedis.zlexcount(KEY, "-", "+")).isEqualTo(10);
+ }
+
+ // Add 10 members with the same score and member names consisting of 'v'
repeated an increasing
+ // number of times
+ private List<String> populateSortedSet() {
+ List<String> members = new ArrayList<>();
+ String memberName = BASE_MEMBER_NAME;
+ for (int i = 0; i < 10; ++i) {
+ jedis.zadd(KEY, SCORE, memberName);
+ members.add(memberName);
+ memberName += BASE_MEMBER_NAME;
+ }
+ return members;
+ }
+}
diff --git
a/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountIntegrationTest.java
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountIntegrationTest.java
new file mode 100644
index 0000000..235750a
--- /dev/null
+++
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountIntegrationTest.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ * or implied. See the License for the specific language governing permissions
and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal.executor.sortedset;
+
+import org.junit.ClassRule;
+
+import org.apache.geode.redis.GeodeRedisServerRule;
+
+public class ZLexCountIntegrationTest extends AbstractZLexCountIntegrationTest
{
+
+ @ClassRule
+ public static GeodeRedisServerRule server = new GeodeRedisServerRule();
+
+ @Override
+ public int getPort() {
+ return server.getPort();
+ }
+}
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
index b5c0b43..43d7748 100755
---
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
@@ -100,6 +100,7 @@ import
org.apache.geode.redis.internal.executor.sortedset.ZAddExecutor;
import org.apache.geode.redis.internal.executor.sortedset.ZCardExecutor;
import org.apache.geode.redis.internal.executor.sortedset.ZCountExecutor;
import org.apache.geode.redis.internal.executor.sortedset.ZIncrByExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZLexCountExecutor;
import org.apache.geode.redis.internal.executor.sortedset.ZPopMaxExecutor;
import org.apache.geode.redis.internal.executor.sortedset.ZPopMinExecutor;
import org.apache.geode.redis.internal.executor.sortedset.ZRangeByLexExecutor;
@@ -216,6 +217,7 @@ public enum RedisCommandType {
ZCARD(new ZCardExecutor(), SUPPORTED, new ExactParameterRequirements(2)),
ZCOUNT(new ZCountExecutor(), SUPPORTED, new ExactParameterRequirements(4)),
ZINCRBY(new ZIncrByExecutor(), SUPPORTED, new ExactParameterRequirements(4)),
+ ZLEXCOUNT(new ZLexCountExecutor(), SUPPORTED, new
ExactParameterRequirements(4)),
ZPOPMAX(new ZPopMaxExecutor(), SUPPORTED, new MinimumParameterRequirements(2)
.and(new MaximumParameterRequirements(3, ERROR_SYNTAX))),
ZPOPMIN(new ZPopMinExecutor(), SUPPORTED, new MinimumParameterRequirements(2)
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSortedSet.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSortedSet.java
index 71a2907..9d2d7ef 100644
---
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSortedSet.java
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSortedSet.java
@@ -64,16 +64,6 @@ class NullRedisSortedSet extends RedisSortedSet {
}
@Override
- List<byte[]> zrangebylex(SortedSetLexRangeOptions rangeOptions) {
- return Collections.emptyList();
- }
-
- @Override
- List<byte[]> zrangebyscore(SortedSetScoreRangeOptions rangeOptions, boolean
withScores) {
- return Collections.emptyList();
- }
-
- @Override
byte[] zincrby(Region<RedisKey, RedisData> region, RedisKey key, byte[]
increment,
byte[] member) {
List<byte[]> valuesToAdd = new ArrayList<>();
@@ -87,6 +77,11 @@ class NullRedisSortedSet extends RedisSortedSet {
}
@Override
+ long zlexcount(SortedSetLexRangeOptions rangeOptions) {
+ return 0;
+ }
+
+ @Override
List<byte[]> zpopmax(Region<RedisKey, RedisData> region, RedisKey key, int
count) {
return Collections.emptyList();
}
@@ -102,6 +97,16 @@ class NullRedisSortedSet extends RedisSortedSet {
}
@Override
+ List<byte[]> zrangebylex(SortedSetLexRangeOptions rangeOptions) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ List<byte[]> zrangebyscore(SortedSetScoreRangeOptions rangeOptions, boolean
withScores) {
+ return Collections.emptyList();
+ }
+
+ @Override
List<byte[]> zrevrange(int min, int max, boolean withScores) {
return Collections.emptyList();
}
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSet.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSet.java
index 2fa70d7..fcd6aad 100644
---
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSet.java
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSet.java
@@ -340,6 +340,30 @@ public class RedisSortedSet extends AbstractRedisData {
return addLimitToRange(rangeOptions, withScores, false, minIndex,
maxIndex);
}
+ long zlexcount(SortedSetLexRangeOptions lexOptions) {
+ if (scoreSet.isEmpty()) {
+ return 0;
+ }
+
+ // Assume that all members have the same score. Behaviour is unspecified
otherwise.
+ double score = scoreSet.get(0).score;
+
+ int minIndex =
+ getIndexByLex(score, lexOptions.getStartRange(),
lexOptions.isStartExclusive(), true);
+ if (minIndex >= scoreSet.size()) {
+ return 0;
+ }
+
+ AbstractOrderedSetEntry maxEntry = new
MemberDummyOrderedSetEntry(lexOptions.getEndRange(),
+ score, lexOptions.isEndExclusive(), false);
+ int maxIndex = scoreSet.indexOf(maxEntry);
+ if (minIndex >= maxIndex) {
+ return 0;
+ }
+
+ return maxIndex - minIndex;
+ }
+
long zrank(byte[] member) {
OrderedSetEntry orderedSetEntry = members.get(member);
if (orderedSetEntry == null) {
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSetCommandsFunctionExecutor.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSetCommandsFunctionExecutor.java
index 5653586..a6d094b 100644
---
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSetCommandsFunctionExecutor.java
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSortedSetCommandsFunctionExecutor.java
@@ -58,6 +58,11 @@ public class RedisSortedSetCommandsFunctionExecutor extends
RedisDataCommandsFun
}
@Override
+ public long zlexcount(RedisKey key, SortedSetLexRangeOptions lexOptions) {
+ return stripedExecute(key, () -> getRedisSortedSet(key,
true).zlexcount(lexOptions));
+ }
+
+ @Override
public List<byte[]> zpopmax(RedisKey key, int count) {
return stripedExecute(key,
() -> getRedisSortedSet(key, false).zpopmax(getRegion(), key, count));
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/RedisSortedSetCommands.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/RedisSortedSetCommands.java
index 1283c54..1585d97 100644
---
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/RedisSortedSetCommands.java
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/RedisSortedSetCommands.java
@@ -29,6 +29,8 @@ public interface RedisSortedSetCommands {
byte[] zincrby(RedisKey key, byte[] increment, byte[] member);
+ long zlexcount(RedisKey key, SortedSetLexRangeOptions rangeOptions);
+
List<byte[]> zpopmax(RedisKey key, int count);
List<byte[]> zpopmin(RedisKey key, int count);
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetLexRangeOptions.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetLexRangeOptions.java
index 5bedba0..2512d4b 100644
---
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetLexRangeOptions.java
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetLexRangeOptions.java
@@ -27,7 +27,7 @@ import java.util.Arrays;
public class SortedSetLexRangeOptions extends
AbstractSortedSetRangeOptions<byte[]> {
- SortedSetLexRangeOptions(byte[] minimumBytes, byte[] maximumBytes) {
+ public SortedSetLexRangeOptions(byte[] minimumBytes, byte[] maximumBytes) {
super(minimumBytes, maximumBytes);
}
diff --git
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java
new file mode 100644
index 0000000..6740637
--- /dev/null
+++
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ * or implied. See the License for the specific language governing permissions
and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal.executor.sortedset;
+
+import static
org.apache.geode.redis.internal.RedisConstants.ERROR_MIN_MAX_NOT_A_VALID_STRING;
+
+import java.util.List;
+
+import org.apache.geode.redis.internal.executor.AbstractExecutor;
+import org.apache.geode.redis.internal.executor.RedisResponse;
+import org.apache.geode.redis.internal.netty.Command;
+import org.apache.geode.redis.internal.netty.ExecutionHandlerContext;
+
+public class ZLexCountExecutor extends AbstractExecutor {
+ @Override
+ public RedisResponse executeCommand(Command command, ExecutionHandlerContext
context) {
+ List<byte[]> commandElements = command.getProcessedCommand();
+
+ SortedSetLexRangeOptions rangeOptions;
+
+ try {
+ byte[] minBytes = commandElements.get(2);
+ byte[] maxBytes = commandElements.get(3);
+ rangeOptions = new SortedSetLexRangeOptions(minBytes, maxBytes);
+ } catch (IllegalArgumentException ex) {
+ return RedisResponse.error(ERROR_MIN_MAX_NOT_A_VALID_STRING);
+ }
+
+ // If the range is empty, return early
+ if (rangeOptions.isEmptyRange(false)) {
+ return RedisResponse.integer(0);
+ }
+
+ long result = context.getSortedSetCommands().zlexcount(command.getKey(),
rangeOptions);
+
+ return RedisResponse.integer(result);
+ }
+}
diff --git
a/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
b/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
index 976be3b..65e2653 100644
---
a/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
+++
b/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
@@ -91,6 +91,7 @@ public class SupportedCommandsJUnitTest {
"ZINCRBY",
"ZCARD",
"ZCOUNT",
+ "ZLEXCOUNT",
"ZPOPMAX",
"ZPOPMIN",
"ZRANGE",
diff --git
a/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/data/RedisSortedSetTest.java
b/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/data/RedisSortedSetTest.java
index 718136f..901bd05 100644
---
a/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/data/RedisSortedSetTest.java
+++
b/geode-apis-compatible-with-redis/src/test/java/org/apache/geode/redis/internal/data/RedisSortedSetTest.java
@@ -58,6 +58,7 @@ import
org.apache.geode.internal.serialization.DataSerializableFixedID;
import org.apache.geode.internal.serialization.SerializationContext;
import org.apache.geode.internal.size.ReflectionObjectSizer;
import org.apache.geode.redis.internal.delta.RemsDeltaInfo;
+import
org.apache.geode.redis.internal.executor.sortedset.SortedSetLexRangeOptions;
import org.apache.geode.redis.internal.executor.sortedset.ZAddOptions;
import org.apache.geode.redis.internal.netty.Coder;
@@ -275,6 +276,84 @@ public class RedisSortedSetTest {
}
@Test
+ public void zlexcount_shouldBeInclusiveWhenSpecified() {
+ RedisSortedSet sortedSet = createRedisSortedSet(
+ score1, "member1",
+ score1, "member2",
+ score1, "member3",
+ score1, "member4",
+ score1, "member5");
+ SortedSetLexRangeOptions lexOptions =
+ new SortedSetLexRangeOptions("[member1".getBytes(),
"[member3".getBytes());
+ assertThat(sortedSet.zlexcount(lexOptions)).isEqualTo(3);
+ }
+
+ @Test
+ public void zlexcount_shouldBeExclusiveWhenSpecified() {
+ RedisSortedSet sortedSet = createRedisSortedSet(
+ score1, "member1",
+ score1, "member2",
+ score1, "member3",
+ score1, "member4",
+ score1, "member5");
+ SortedSetLexRangeOptions lexOptions =
+ new SortedSetLexRangeOptions("(member1".getBytes(),
"(member3".getBytes());
+ assertThat(sortedSet.zlexcount(lexOptions)).isEqualTo(1);
+ }
+
+ @Test
+ public void zlexcount_shouldBeZero_whenMinIsTooGreat() {
+ RedisSortedSet sortedSet = createRedisSortedSet(
+ score1, "member1",
+ score1, "member2",
+ score1, "member3",
+ score1, "member4",
+ score1, "member5");
+ SortedSetLexRangeOptions lexOptions = new
SortedSetLexRangeOptions("[member6".getBytes(),
+ "(member8".getBytes());
+ assertThat(sortedSet.zlexcount(lexOptions)).isEqualTo(0);
+ }
+
+ @Test
+ public void zlexcount_shouldBeZero_whenMaxIsTooSmall() {
+ RedisSortedSet sortedSet = createRedisSortedSet(
+ score1, "member1",
+ score1, "member2",
+ score1, "member3",
+ score1, "member4",
+ score1, "member5");
+ SortedSetLexRangeOptions lexOptions = new
SortedSetLexRangeOptions("[membeq0".getBytes(),
+ "[member0".getBytes());
+ assertThat(sortedSet.zlexcount(lexOptions)).isEqualTo(0);
+ }
+
+ @Test
+ public void zlexcount_shouldBeZero_whenMinAndMaxAreReversed() {
+ RedisSortedSet sortedSet = createRedisSortedSet(
+ score1, "member1",
+ score1, "member2",
+ score1, "member3",
+ score1, "member4",
+ score1, "member5");
+ SortedSetLexRangeOptions lexOptions =
+ new SortedSetLexRangeOptions("[member5".getBytes(),
"[member0".getBytes());
+ assertThat(sortedSet.zlexcount(lexOptions)).isEqualTo(0);
+ }
+
+ @Test
+ public void zlexcount_shouldBeAbleToCountAllEntries() {
+ RedisSortedSet sortedSet = createRedisSortedSet(
+ score1, "member1",
+ score1, "member2",
+ score1, "member3",
+ score1, "member4",
+ score1, "member5");
+ SortedSetLexRangeOptions lexOptions =
+ new SortedSetLexRangeOptions("-".getBytes(), "+".getBytes());
+ assertThat(sortedSet.zlexcount(lexOptions)).isEqualTo(5);
+ }
+
+ @Test
public void scoreSet_shouldNotRetainOldEntries_whenEntriesUpdated() {
Collection<byte[]> rangeList = rangeSortedSet.zrange(0, 100, false);
assertThat(rangeList).hasSize(12);