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 2cd5d82 GEODE-9950: Add Radish LRANGE command (#7389)
2cd5d82 is described below
commit 2cd5d8271357b76e76c584577ae0c214cc5cdc16
Author: Kris10 <[email protected]>
AuthorDate: Mon Mar 7 08:39:34 2022 -0800
GEODE-9950: Add Radish LRANGE command (#7389)
This implements a version of the Redis LRANGE command, which is used for
list data types. Associated tests were also added.
Given a key, it returns a list of elements in the range (inclusive) for the
specified start index and stop index. Both the start and stop indexes are zero
based, which starts at the head of the list. Negative indexes start at the tail
of the list.
LINDEX was modified to use a new method created in this commit. The method
converts negative indexes to the corresponding positive index.
---
.../tools_modules/geode_for_redis.html.md.erb | 8 +-
geode-for-redis/README.md | 1 +
.../list/LRangeNativeRedisAcceptanceTest.java} | 42 ++---
.../list/AbstractLRangeIntegrationTest.java | 186 +++++++++++++++++++++
.../executor/list/LRangeIntegrationTest.java} | 44 +----
.../server/AbstractHitsMissesIntegrationTest.java | 9 +-
.../redis/internal/commands/RedisCommandType.java | 2 +
.../commands/executor/list/LRangeExecutor.java | 48 ++++++
.../geode/redis/internal/data/NullRedisList.java | 6 +
.../geode/redis/internal/data/RedisList.java | 76 ++++++++-
10 files changed, 343 insertions(+), 79 deletions(-)
diff --git a/geode-docs/tools_modules/geode_for_redis.html.md.erb
b/geode-docs/tools_modules/geode_for_redis.html.md.erb
index dce2066..f75daf2 100644
--- a/geode-docs/tools_modules/geode_for_redis.html.md.erb
+++ b/geode-docs/tools_modules/geode_for_redis.html.md.erb
@@ -188,10 +188,10 @@ Could not connect to Redis at 127.0.0.1:6379: Connection
refused
| HSETNX | HSTRLEN | HVALS | INCR |
| INCRBY | INCRBYFLOAT | INFO **[4]** | KEYS |
| LINDEX | LLEN | LOLWUT | LPOP |
-| LPUSH | LPUSHX | MGET | MSET |
-| MSETNX | PERSIST | PEXPIRE | PEXPIREAT |
-| PING | PSETEX | PSUBSCRIBE | PTTL |
-| PUBLISH PUBSUB | PUNSUBSCRIBE | RENAME |
+| LPUSH | LPUSHX | LRANGE | MGET |
+| MSET | MSETNX | PERSIST | PEXPIRE |
+| PEXPIREAT | PING | PSETEX | PSUBSCRIBE |
+| PTTL | PUBLISH PUBSUB | PUNSUBSCRIBE | RENAME |
| RENAMENX | RESTORE | SADD | SCARD |
| SDIFF | SDIFFSTORE | SET | SETEX |
| SETNX | SETRANGE | SINTER | SINTERSTORE |
diff --git a/geode-for-redis/README.md b/geode-for-redis/README.md
index 2c954e9..7cd1531 100644
--- a/geode-for-redis/README.md
+++ b/geode-for-redis/README.md
@@ -183,6 +183,7 @@ Geode for Redis implements a subset of the full Redis
command set.
- INFO <sup>2</sup>
- KEYS
- LINDEX
+- LRANGE
- MGET
- MSET
- MSETNX
diff --git
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
b/geode-for-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/commands/executor/list/LRangeNativeRedisAcceptanceTest.java
old mode 100644
new mode 100755
similarity index 52%
copy from
geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
copy to
geode-for-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/commands/executor/list/LRangeNativeRedisAcceptanceTest.java
index 65006eb..6e4b27f
---
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
+++
b/geode-for-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/commands/executor/list/LRangeNativeRedisAcceptanceTest.java
@@ -11,48 +11,26 @@
* 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.commands.executor.list;
-package org.apache.geode.redis.internal.data;
-
-import java.util.List;
+import org.junit.ClassRule;
-import org.apache.geode.cache.Region;
+import org.apache.geode.redis.NativeRedisClusterTestRule;
-class NullRedisList extends RedisList {
+public class LRangeNativeRedisAcceptanceTest extends
AbstractLRangeIntegrationTest {
- NullRedisList() {
- super();
- }
+ @ClassRule
+ public static NativeRedisClusterTestRule redis = new
NativeRedisClusterTestRule();
@Override
- public boolean isNull() {
- return true;
+ public int getPort() {
+ return redis.getExposedPorts().get(0);
}
@Override
- public long lpush(List<byte[]> elementsToAdd, Region<RedisKey, RedisData>
region, RedisKey key,
- final boolean onlyIfExists) {
- if (onlyIfExists) {
- return 0;
- }
-
- RedisList newList = new RedisList();
- for (byte[] element : elementsToAdd) {
- newList.elementPush(element);
- }
- region.create(key, newList);
- return elementsToAdd.size();
+ public void flushAll() {
+ redis.flushAll();
}
- @Override
- public byte[] lpop(Region<RedisKey, RedisData> region, RedisKey key) {
- return null;
- }
-
- @Override
- public int llen() {
- return 0;
- }
}
diff --git
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLRangeIntegrationTest.java
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLRangeIntegrationTest.java
new file mode 100755
index 0000000..2b82d86
--- /dev/null
+++
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLRangeIntegrationTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.commands.executor.list;
+
+import static
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs;
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE;
+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.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+
+import org.apache.geode.redis.ConcurrentLoopingThreads;
+import org.apache.geode.redis.RedisIntegrationTest;
+
+public abstract class AbstractLRangeIntegrationTest implements
RedisIntegrationTest {
+ private static final String NON_EXISTENT_LIST_KEY = "{tag1}nonExistentKey";
+ private static final String LIST_KEY = "{tag1}listKey";
+ private static final String[] LIST_ELEMENTS =
+ {"aardvark", "bats", "chameleon", "deer", "elephant", "flamingo",
"goat"};
+ private 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 lrange_wrongNumberOfArgs_returnsError() {
+ assertExactNumberOfArgs(jedis, Protocol.Command.LRANGE, 3);
+ }
+
+ @Test
+ public void
lrange_withNonExistentList_withStartIndexLessThanStopIndex_returnsEmptyList() {
+ assertThat(jedis.lrange(NON_EXISTENT_LIST_KEY, -10, 10)).isEmpty();
+ }
+
+ @Test
+ public void
lrange_withNonExistentList_withStartIndexGreaterThanStopIndex_returnsEmptyList()
{
+ assertThat(jedis.lrange(NON_EXISTENT_LIST_KEY, 10, -10)).isEmpty();
+ }
+
+ @Test
+ public void lrange_withInvalidRange_returnsEmptyList() {
+ jedis.lpush(LIST_KEY, LIST_ELEMENTS);
+
+ assertThat(getLRangeResult(4, 0)).isEmpty();
+ assertThat(getLRangeResult(2, -10)).isEmpty();
+ assertThat(getLRangeResult(-3, 0)).isEmpty();
+ assertThat(getLRangeResult(-4, -6)).isEmpty();
+ assertThat(getLRangeResult(-5, -10)).isEmpty();
+ assertThat(getLRangeResult(10, 5)).isEmpty();
+ assertThat(getLRangeResult(10, -6)).isEmpty();
+ assertThat(getLRangeResult(8, 10)).isEmpty();
+ assertThat(getLRangeResult(10, -10)).isEmpty();
+ assertThat(getLRangeResult(-12, -10)).isEmpty();
+ }
+
+ @Test
+ public void lrange_withValidRange_returnsElementsInRange() {
+ jedis.lpush(LIST_KEY, LIST_ELEMENTS);
+
+ String[] result1 = {"goat", "flamingo", "elephant", "deer"};
+ assertThat(getLRangeResult(0, -4)).containsExactly(result1);
+ assertThat(getLRangeResult(-10, 3)).containsExactly(result1);
+
+ String[] result2 = {"chameleon", "bats", "aardvark"};
+ assertThat(getLRangeResult(4, -1)).containsExactly(result2);
+ assertThat(getLRangeResult(4, 10)).containsExactly(result2);
+ assertThat(getLRangeResult(-3, 10)).containsExactly(result2);
+
+ String[] result3 = {"goat", "flamingo", "elephant", "deer", "chameleon",
"bats"};
+ assertThat(getLRangeResult(-10, -2)).containsExactly(result3);
+
+ String[] result4 = {"goat", "flamingo", "elephant", "deer", "chameleon",
"bats", "aardvark"};
+ assertThat(getLRangeResult(0, 6)).containsExactly(result4);
+ assertThat(getLRangeResult(-10, 10)).containsExactly(result4);
+ }
+
+ @Test
+ public void lrange_withSameValueForStartIndexAndStopIndex_returnsElement() {
+ jedis.lpush(LIST_KEY, LIST_ELEMENTS);
+ assertThat(getLRangeResult(0, 0)).containsExactly(new String[] {"goat"});
+ }
+
+ @Test
+ public void
lrange_withInvalidInputs_withNonExisistentList_returnsErrorNotInteger() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(LIST_KEY, Protocol.Command.LRANGE,
NON_EXISTENT_LIST_KEY, "b", "2"))
+ .hasMessage(ERROR_NOT_INTEGER);
+
+ assertThatThrownBy(
+ () -> jedis.sendCommand(LIST_KEY, Protocol.Command.LRANGE,
NON_EXISTENT_LIST_KEY, "2", "b"))
+ .hasMessage(ERROR_NOT_INTEGER);
+ }
+
+ @Test
+ public void lrange_withInvalidInputs_returnsErrorNotInteger() {
+ jedis.lpush(LIST_KEY, LIST_ELEMENTS);
+ assertThatThrownBy(
+ () -> jedis.sendCommand(LIST_KEY, Protocol.Command.LRANGE, LIST_KEY,
"b", "2"))
+ .hasMessage(ERROR_NOT_INTEGER);
+
+ assertThatThrownBy(
+ () -> jedis.sendCommand(LIST_KEY, Protocol.Command.LRANGE, LIST_KEY,
"2", "b"))
+ .hasMessage(ERROR_NOT_INTEGER);
+ }
+
+ @Test
+ public void lrange_withWrongTypeKey_returnsErrorWrongType() {
+ String key = "{tag1}ding";
+ jedis.set(key, "dong");
+ assertThatThrownBy(() -> jedis.sendCommand(key, Protocol.Command.LRANGE,
key, "0", "2"))
+ .hasMessage(ERROR_WRONG_TYPE);
+ }
+
+ @Test
+ public void
lrange_withWrongTypeKey_withInvalidInputs_returnsErrorNotInteger() {
+ String key = "{tag1}ding";
+ jedis.set(key, "dong");
+ assertThatThrownBy(() -> jedis.sendCommand(key, Protocol.Command.LRANGE,
key, "b", "2"))
+ .hasMessage(ERROR_NOT_INTEGER);
+
+ assertThatThrownBy(() -> jedis.sendCommand(key, Protocol.Command.LRANGE,
key, "2", "b"))
+ .hasMessage(ERROR_NOT_INTEGER);
+ }
+
+ @Test
+ public void ensureListConsistency_whenRunningConcurrently() {
+ jedis.lpush(LIST_KEY, LIST_ELEMENTS);
+
+ final String[] result =
+ {"goat", "flamingo", "elephant", "deer", "chameleon", "bats",
"aardvark"};
+
+ final String[] elementsToAdd =
+ {"vainglorious", "williwaw", "xiphoid", "ypsiliform",
"zinziberaceous"};
+ final String[] resultWithElementsAdded =
+ {"zinziberaceous", "ypsiliform", "xiphoid", "williwaw",
"vainglorious", "goat", "flamingo",
+ "elephant", "deer", "chameleon", "bats", "aardvark"};
+ final AtomicReference<List<String>> lrangeResultReference = new
AtomicReference<>();
+ new ConcurrentLoopingThreads(1000,
+ i -> jedis.lpush(LIST_KEY, elementsToAdd),
+ i -> lrangeResultReference.set(jedis.lrange(LIST_KEY, -15, 15)))
+ .runWithAction(() -> {
+ assertThat(lrangeResultReference).satisfiesAnyOf(
+ lrangeResult -> assertThat(lrangeResult.get())
+ .containsExactly(resultWithElementsAdded),
+ lrangeResult -> assertThat(lrangeResult.get())
+ .containsExactly(result));
+ jedis.del(LIST_KEY);
+ jedis.lpush(LIST_KEY, LIST_ELEMENTS);
+ });
+ }
+
+ private List<String> getLRangeResult(int startIndex, int stopIndex) {
+ return jedis.lrange(LIST_KEY, startIndex, stopIndex);
+ }
+}
diff --git
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/LRangeIntegrationTest.java
old mode 100644
new mode 100755
similarity index 50%
copy from
geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
copy to
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/LRangeIntegrationTest.java
index 65006eb..f185976
---
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
+++
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/LRangeIntegrationTest.java
@@ -11,48 +11,20 @@
* 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.commands.executor.list;
-package org.apache.geode.redis.internal.data;
-
-import java.util.List;
+import org.junit.ClassRule;
-import org.apache.geode.cache.Region;
+import org.apache.geode.redis.GeodeRedisServerRule;
-class NullRedisList extends RedisList {
-
- NullRedisList() {
- super();
- }
-
- @Override
- public boolean isNull() {
- return true;
- }
+public class LRangeIntegrationTest extends AbstractLRangeIntegrationTest {
- @Override
- public long lpush(List<byte[]> elementsToAdd, Region<RedisKey, RedisData>
region, RedisKey key,
- final boolean onlyIfExists) {
- if (onlyIfExists) {
- return 0;
- }
-
- RedisList newList = new RedisList();
- for (byte[] element : elementsToAdd) {
- newList.elementPush(element);
- }
- region.create(key, newList);
- return elementsToAdd.size();
- }
-
- @Override
- public byte[] lpop(Region<RedisKey, RedisData> region, RedisKey key) {
- return null;
- }
+ @ClassRule
+ public static GeodeRedisServerRule server = new GeodeRedisServerRule();
@Override
- public int llen() {
- return 0;
+ public int getPort() {
+ return server.getPort();
}
}
diff --git
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/server/AbstractHitsMissesIntegrationTest.java
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/server/AbstractHitsMissesIntegrationTest.java
index a492de4..75a0ccd 100644
---
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/server/AbstractHitsMissesIntegrationTest.java
+++
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/server/AbstractHitsMissesIntegrationTest.java
@@ -579,6 +579,11 @@ public abstract class AbstractHitsMissesIntegrationTest
implements RedisIntegrat
}
@Test
+ public void testLlen() {
+ runCommandAndAssertHitsAndMisses(LIST_KEY, k -> jedis.llen(k));
+ }
+
+ @Test
public void testLpush() {
runCommandAndAssertNoStatUpdates(LIST_KEY, k -> jedis.lpush(k, "element"));
}
@@ -594,8 +599,8 @@ public abstract class AbstractHitsMissesIntegrationTest
implements RedisIntegrat
}
@Test
- public void testLlen() {
- runCommandAndAssertHitsAndMisses(LIST_KEY, k -> jedis.llen(k));
+ public void testLrange() {
+ runCommandAndAssertHitsAndMisses(LIST_KEY, k -> jedis.lrange(k, 0, 0));
}
/************* Helper Methods *************/
diff --git
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/RedisCommandType.java
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/RedisCommandType.java
index bc9796f..51c1f09 100755
---
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/RedisCommandType.java
+++
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/RedisCommandType.java
@@ -83,6 +83,7 @@ import
org.apache.geode.redis.internal.commands.executor.list.LLenExecutor;
import org.apache.geode.redis.internal.commands.executor.list.LPopExecutor;
import org.apache.geode.redis.internal.commands.executor.list.LPushExecutor;
import org.apache.geode.redis.internal.commands.executor.list.LPushXExecutor;
+import org.apache.geode.redis.internal.commands.executor.list.LRangeExecutor;
import
org.apache.geode.redis.internal.commands.executor.pubsub.PsubscribeExecutor;
import org.apache.geode.redis.internal.commands.executor.pubsub.PubSubExecutor;
import
org.apache.geode.redis.internal.commands.executor.pubsub.PublishExecutor;
@@ -391,6 +392,7 @@ public enum RedisCommandType {
new Parameter().min(3).flags(WRITE, DENYOOM, FAST)),
LPUSHX(new LPushXExecutor(), Category.LIST, SUPPORTED,
new Parameter().min(3).flags(WRITE, DENYOOM, FAST)),
+ LRANGE(new LRangeExecutor(), Category.LIST, SUPPORTED, new
Parameter().exact(4).flags(READONLY)),
/********** Publish Subscribe **********/
diff --git
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/list/LRangeExecutor.java
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/list/LRangeExecutor.java
new file mode 100644
index 0000000..350ca22
--- /dev/null
+++
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/list/LRangeExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * 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.commands.executor.list;
+
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
+import static org.apache.geode.redis.internal.netty.Coder.bytesToLong;
+import static org.apache.geode.redis.internal.netty.Coder.narrowLongToInt;
+
+import java.util.List;
+
+import org.apache.geode.redis.internal.commands.Command;
+import org.apache.geode.redis.internal.commands.executor.CommandExecutor;
+import org.apache.geode.redis.internal.commands.executor.RedisResponse;
+import org.apache.geode.redis.internal.data.RedisKey;
+import org.apache.geode.redis.internal.netty.ExecutionHandlerContext;
+
+public class LRangeExecutor implements CommandExecutor {
+ @Override
+ public RedisResponse executeCommand(Command command, ExecutionHandlerContext
context) {
+ List<byte[]> commandElems = command.getProcessedCommand();
+
+ int startIndex;
+ int stopIndex;
+ try {
+ startIndex = narrowLongToInt(bytesToLong(commandElems.get(2)));
+ stopIndex = narrowLongToInt(bytesToLong(commandElems.get(3)));
+ } catch (NumberFormatException e) {
+ return RedisResponse.error(ERROR_NOT_INTEGER);
+ }
+
+ RedisKey key = command.getKey();
+ List<byte[]> result = context.listLockedExecute(key, true,
+ list -> list.lrange(startIndex, stopIndex));
+ return RedisResponse.array(result, true);
+ }
+}
diff --git
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
index 65006eb..3471d6a 100644
---
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
+++
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java
@@ -16,6 +16,7 @@
package org.apache.geode.redis.internal.data;
+import java.util.Collections;
import java.util.List;
import org.apache.geode.cache.Region;
@@ -27,6 +28,11 @@ class NullRedisList extends RedisList {
}
@Override
+ public List<byte[]> lrange(int start, int stop) {
+ return Collections.emptyList();
+ }
+
+ @Override
public boolean isNull() {
return true;
}
diff --git
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java
index 32f286f..1633d75 100644
---
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java
+++
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java
@@ -22,7 +22,11 @@ import static
org.apache.geode.redis.internal.data.RedisDataType.REDIS_LIST;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.ListIterator;
import java.util.Objects;
import org.apache.geode.DataSerializer;
@@ -38,28 +42,90 @@ public class RedisList extends AbstractRedisData {
protected static final int REDIS_LIST_OVERHEAD =
memoryOverhead(RedisList.class);
private final SizeableByteArrayList elementList;
+ private static final int INVALID_INDEX = -1;
+
public RedisList() {
this.elementList = new SizeableByteArrayList();
}
/**
+ * @param start start index of desired elements
+ * @param stop stop index of desired elements
+ * @return list of elements in the range (inclusive).
+ */
+ public List<byte[]> lrange(int start, int stop) {
+ start = normalizeStartIndex(start);
+ stop = normalizeStopIndex(stop);
+
+ int elementSize = elementList.size();
+ if (start > stop || elementSize <= start) {
+ return Collections.emptyList();
+ }
+
+ int resultLength = stop - start + 1;
+
+ // Finds the shortest distance to access nodes in range
+ if (start <= elementSize - stop - 1) {
+ // Starts at head to access nodes at start index then iterates forwards
+ List<byte[]> result = new ArrayList<>(resultLength);
+ ListIterator<byte[]> iterator = elementList.listIterator(start);
+
+ for (int i = start; i <= stop; i++) {
+ byte[] element = iterator.next();
+ result.add(element);
+ }
+ return result;
+
+ } else {
+ // Starts at tail to access nodes at stop index then iterates backwards
+ byte[][] result = new byte[resultLength][];
+ ListIterator<byte[]> iterator = elementList.listIterator(stop + 1);
+
+ for (int i = resultLength - 1; i >= 0; i--) {
+ byte[] element = iterator.previous();
+ result[i] = element;
+ }
+ return Arrays.asList(result);
+ }
+ }
+
+ /**
* @param index index of desired element. Positive index starts at the head.
Negative index starts
* at the tail.
* @return element at index. Null if index is out of range.
*/
public byte[] lindex(int index) {
- if (index < 0) {
- // Changes negative index to corresponding positive index to utilize
get(int index)
- index = elementList.size() + index;
- }
+ index = getArrayIndex(index);
- if (index < 0 || elementList.size() <= index) {
+ if (index == INVALID_INDEX || elementList.size() <= index) {
return null;
} else {
return elementList.get(index);
}
}
+ private int normalizeStartIndex(int startIndex) {
+ return Math.max(0, getArrayIndex(startIndex));
+ }
+
+ private int normalizeStopIndex(int stopIndex) {
+ return Math.min(elementList.size() - 1, getArrayIndex(stopIndex));
+ }
+
+ /**
+ * Changes negative index to corresponding positive index.
+ * If there is no corresponding positive index, returns INVALID_INDEX.
+ */
+ private int getArrayIndex(int listIndex) {
+ if (listIndex < 0) {
+ listIndex = elementList.size() + listIndex;
+ if (listIndex < 0) {
+ return INVALID_INDEX;
+ }
+ }
+ return listIndex;
+ }
+
/**
* @param elementsToAdd elements to add to this set; NOTE this list may by
modified by this call
* @param region the region this instance is stored in