This is an automated email from the ASF dual-hosted git repository.

jensdeppe pushed a commit to branch support/1.15
in repository https://gitbox.apache.org/repos/asf/geode.git

commit d4563383d0e8dbbd8f67f28d3367664c5c8f518b
Author: Kris10 <kod...@vmware.com>
AuthorDate: Fri Jan 21 10:14:07 2022 -0800

    GEODE-9834: SRANDMEMBER Command Support (#7228)
    
    (cherry picked from commit a53c6da8dad75c8953de7e7ceb4bbfa545e5f405)
---
 .../tools_modules/geode_for_redis.html.md.erb      |   1 +
 geode-for-redis/README.md                          |   1 +
 .../set/SRandMemberNativeRedisAcceptanceTest.java  |  34 +++++
 .../server/AbstractHitsMissesIntegrationTest.java  |  10 +-
 .../set/AbstractSRandMemberIntegrationTest.java    | 165 +++++++++++++++++++++
 .../executor/set/AbstractSetsIntegrationTest.java  |  49 ------
 .../executor/set/SRandMemberIntegrationTest.java   |  29 ++++
 .../redis/internal/commands/RedisCommandType.java  |   4 +-
 .../commands/executor/set/SRandMemberExecutor.java |  50 +------
 ...dMemberExecutor.java => SetRandomExecutor.java} |  52 ++++---
 .../geode/redis/internal/data/NullRedisSet.java    |   2 +-
 .../apache/geode/redis/internal/data/RedisSet.java |  85 ++++++-----
 .../SizeableObjectOpenCustomHashSet.java           |   8 +
 13 files changed, 330 insertions(+), 160 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 748ddf8..475fc7d 100644
--- a/geode-docs/tools_modules/geode_for_redis.html.md.erb
+++ b/geode-docs/tools_modules/geode_for_redis.html.md.erb
@@ -121,6 +121,7 @@ If the server is functioning properly, you should see a 
response of `PONG`.
  - SLOWLOG **[3]**  <br/>
  - SMEMBERS <br/>
  - SMOVE <br/>
+ - SRANDMEMBER <br/>
  - SREM <br/>
  - STRLEN <br/>
  - SUBSCRIBE <br/>
diff --git a/geode-for-redis/README.md b/geode-for-redis/README.md
index fa392d9..592b129 100644
--- a/geode-for-redis/README.md
+++ b/geode-for-redis/README.md
@@ -211,6 +211,7 @@ Geode for Redis implements a subset of the full Redis 
command set.
 - SLOWLOG <sup>3</sup>  
 - SMEMBERS  
 - SMOVE
+- SRANDMEMBER
 - SREM  
 - STRLEN  
 - SUBSCRIBE  
diff --git 
a/geode-for-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberNativeRedisAcceptanceTest.java
 
b/geode-for-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberNativeRedisAcceptanceTest.java
new file mode 100644
index 0000000..dcab8c0
--- /dev/null
+++ 
b/geode-for-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberNativeRedisAcceptanceTest.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.commands.executor.set;
+
+import org.junit.ClassRule;
+
+import org.apache.geode.redis.NativeRedisClusterTestRule;
+
+public class SRandMemberNativeRedisAcceptanceTest extends 
AbstractSRandMemberIntegrationTest {
+  @ClassRule
+  public static NativeRedisClusterTestRule redis = new 
NativeRedisClusterTestRule();
+
+  @Override
+  public int getPort() {
+    return redis.getExposedPorts().get(0);
+  }
+
+  @Override
+  public void flushAll() {
+    redis.flushAll();
+  }
+}
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 5923e07..23735d9 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
@@ -410,6 +410,11 @@ public abstract class AbstractHitsMissesIntegrationTest 
implements RedisIntegrat
   }
 
   @Test
+  public void testSrandmember() {
+    runCommandAndAssertHitsAndMisses(SET_KEY, k -> jedis.srandmember(k));
+  }
+
+  @Test
   public void testSrem() {
     runCommandAndAssertNoStatUpdates(SET_KEY, k -> jedis.srem(k, "member"));
   }
@@ -556,11 +561,6 @@ public abstract class AbstractHitsMissesIntegrationTest 
implements RedisIntegrat
   }
 
   @Test
-  public void testSrandmember() {
-    runCommandAndAssertHitsAndMisses(SET_KEY, k -> jedis.srandmember(k));
-  }
-
-  @Test
   public void testSscan() {
     runCommandAndAssertHitsAndMisses(SET_KEY, k -> jedis.sscan(k, "0"));
   }
diff --git 
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSRandMemberIntegrationTest.java
 
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSRandMemberIntegrationTest.java
new file mode 100644
index 0000000..821e68d
--- /dev/null
+++ 
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSRandMemberIntegrationTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.set;
+
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs;
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_SYNTAX;
+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.Arrays;
+import java.util.List;
+
+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.RedisIntegrationTest;
+
+public abstract class AbstractSRandMemberIntegrationTest implements 
RedisIntegrationTest {
+  private JedisCluster jedis;
+  private static final String NON_EXISTENT_SET_KEY = "{tag1}nonExistentSet";
+  private static final String SET_KEY = "{tag1}setKey";
+  private static final String[] SET_MEMBERS =
+      {"one", "two", "three", "four", "five", "six", "seven", "eight"};
+
+  @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 srandmemberTooFewArgs_returnsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SRANDMEMBER, 1);
+  }
+
+  @Test
+  public void srandmemberTooManyArgs_returnsError() {
+    assertThatThrownBy(
+        () -> jedis.sendCommand(SET_KEY, Protocol.Command.SRANDMEMBER, 
SET_KEY, "5", "5"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
+
+  @Test
+  public void srandmember_withInvalidCount_returnsError() {
+    assertThatThrownBy(() -> jedis.sendCommand(SET_KEY, 
Protocol.Command.SRANDMEMBER, SET_KEY, "b"))
+        .hasMessageContaining(ERROR_NOT_INTEGER);
+  }
+
+  @Test
+  public void srandmember_withoutCount_withNonExistentSet_returnsNull() {
+    assertThat(jedis.srandmember(NON_EXISTENT_SET_KEY)).isNull();
+    assertThat(jedis.exists(NON_EXISTENT_SET_KEY)).isFalse();
+  }
+
+  @Test
+  public void srandmember_withoutCount_withExistentSet_returnsOneMember() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    String result = jedis.srandmember(SET_KEY);
+    assertThat(result).isIn(Arrays.asList(SET_MEMBERS));
+  }
+
+  @Test
+  public void srandmember_withCountAsZero_withExistentSet_returnsEmptyList() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    assertThat(jedis.srandmember(SET_KEY, 0)).isEmpty();
+  }
+
+  @Test
+  public void 
srandmember_withNegativeCount_withExistentSet_returnsSubsetOfSet() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    int count = -20;
+
+    List<String> result = jedis.srandmember(SET_KEY, count);
+    assertThat(result.size()).isEqualTo(-count);
+    assertThat(result).isSubsetOf(SET_MEMBERS);
+  }
+
+  @Test
+  public void srandmember_withSmallCount_withNonExistentSet_returnsEmptyList() 
{
+    assertThat(jedis.srandmember(NON_EXISTENT_SET_KEY, 1)).isEmpty();
+    assertThat(jedis.exists(NON_EXISTENT_SET_KEY)).isFalse();
+  }
+
+  @Test
+  public void 
srandmember_withSmallCount_withExistentSet_returnsCorrectNumberOfMembers() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    int count = 2; // 2*3 < 8 Calls srandomUniqueListWithSmallCount
+
+    List<String> result = jedis.srandmember(SET_KEY, count);
+    assertThat(result.size()).isEqualTo(2);
+    assertThat(result).isSubsetOf(SET_MEMBERS);
+    assertThat(result).doesNotHaveDuplicates();
+  }
+
+  @Test
+  public void 
srandmember_withLargeCount_withExistentSet_returnsCorrectNumberOfMembers() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    int count = 6; // 6*3 > 8 Calls srandomUniqueListWithLargeCount
+
+    List<String> result = jedis.srandmember(SET_KEY, count);
+    assertThat(result.size()).isEqualTo(count);
+    assertThat(result).isSubsetOf(SET_MEMBERS);
+    assertThat(result).doesNotHaveDuplicates();
+  }
+
+  @Test
+  public void 
srandmember_withCountAsSetSize_withExistentSet_returnsAllMembers() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    int count = SET_MEMBERS.length;
+
+    assertThat(jedis.srandmember(SET_KEY, 
count)).containsExactlyInAnyOrder(SET_MEMBERS);
+  }
+
+  @Test
+  public void 
srandmember_withCountGreaterThanSetSize_withExistentSet_returnsAllMembers() {
+    jedis.sadd(SET_KEY, SET_MEMBERS);
+    assertThat(jedis.srandmember(SET_KEY, 
20)).containsExactlyInAnyOrder(SET_MEMBERS);
+  }
+
+  @Test
+  public void 
srandmember_withoutCount_withWrongKeyType_returnsWrongTypeError() {
+    String key = "ding";
+    jedis.set(key, "dong");
+    assertThatThrownBy(() -> 
jedis.srandmember(key)).hasMessageContaining(ERROR_WRONG_TYPE);
+  }
+
+  @Test
+  public void srandmember_withCount_withWrongKeyType_returnsWrongTypeError() {
+    String key = "ding";
+    jedis.set(key, "dong");
+    assertThatThrownBy(() -> jedis.srandmember(key, 
5)).hasMessageContaining(ERROR_WRONG_TYPE);
+  }
+
+  @Test
+  public void 
srandmember_withCountAsZero_withWrongKeyType_returnsWrongTypeError() {
+    String key = "ding";
+    jedis.set(key, "dong");
+    assertThatThrownBy(() -> jedis.srandmember(key, 
0)).hasMessageContaining(ERROR_WRONG_TYPE);
+  }
+}
diff --git 
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSetsIntegrationTest.java
 
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSetsIntegrationTest.java
index 99c05c3..e40b5cb 100755
--- 
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSetsIntegrationTest.java
+++ 
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSetsIntegrationTest.java
@@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 import org.junit.After;
@@ -99,54 +98,6 @@ public abstract class AbstractSetsIntegrationTest implements 
RedisIntegrationTes
   }
 
   @Test
-  public void srandmember_withStringFails() {
-    jedis.set("string", "value");
-    assertThatThrownBy(() -> 
jedis.srandmember("string")).hasMessageContaining("WRONGTYPE");
-  }
-
-  @Test
-  public void srandmember_withNonExistentKeyReturnsNull() {
-    assertThat(jedis.srandmember("non existent")).isNull();
-  }
-
-  @Test
-  public void srandmemberCount_withNonExistentKeyReturnsEmptyArray() {
-    assertThat(jedis.srandmember("non existent", 3)).isEmpty();
-  }
-
-  @Test
-  public void srandmember_returnsOneMember() {
-    jedis.sadd("key", "m1", "m2");
-    String result = jedis.srandmember("key");
-    assertThat(result).isIn("m1", "m2");
-  }
-
-  @Test
-  public void srandmemberCount_returnsTwoUniqueMembers() {
-    jedis.sadd("key", "m1", "m2", "m3");
-    List<String> results = jedis.srandmember("key", 2);
-    assertThat(results).hasSize(2);
-    assertThat(results).containsAnyOf("m1", "m2", "m3");
-    assertThat(results.get(0)).isNotEqualTo(results.get(1));
-  }
-
-  @Test
-  public void srandmemberNegativeCount_returnsThreeMembers() {
-    jedis.sadd("key", "m1", "m2", "m3");
-    List<String> results = jedis.srandmember("key", -3);
-    assertThat(results).hasSize(3);
-    assertThat(results).containsAnyOf("m1", "m2", "m3");
-  }
-
-  @Test
-  public void srandmemberNegativeCount_givenSmallSet_returnsThreeMembers() {
-    jedis.sadd("key", "m1");
-    List<String> results = jedis.srandmember("key", -3);
-    assertThat(results).hasSize(3);
-    assertThat(results).containsAnyOf("m1");
-  }
-
-  @Test
   public void 
smembers_givenKeyNotProvided_returnsWrongNumberOfArgumentsError() {
     assertThatThrownBy(() -> jedis.sendCommand("key", 
Protocol.Command.SMEMBERS))
         .hasMessageContaining("ERR wrong number of arguments for 'smembers' 
command");
diff --git 
a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberIntegrationTest.java
 
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberIntegrationTest.java
new file mode 100644
index 0000000..26766b6
--- /dev/null
+++ 
b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberIntegrationTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.set;
+
+import org.junit.ClassRule;
+
+import org.apache.geode.redis.GeodeRedisServerRule;
+
+public class SRandMemberIntegrationTest extends 
AbstractSRandMemberIntegrationTest {
+  @ClassRule
+  public static GeodeRedisServerRule server = new GeodeRedisServerRule();
+
+  @Override
+  public int getPort() {
+    return server.getPort();
+  }
+}
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 ef1d703..7887281 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
@@ -250,6 +250,8 @@ public enum RedisCommandType {
   SMEMBERS(new SMembersExecutor(), SUPPORTED,
       new Parameter().exact(2).flags(READONLY, SORT_FOR_SCRIPT)),
   SMOVE(new SMoveExecutor(), SUPPORTED, new 
Parameter().exact(4).lastKey(2).flags(WRITE, FAST)),
+  SRANDMEMBER(new SRandMemberExecutor(), SUPPORTED,
+      new Parameter().min(2).max(3, ERROR_SYNTAX).flags(READONLY, RANDOM)),
   SREM(new SRemExecutor(), SUPPORTED, new Parameter().min(3).flags(WRITE, 
FAST)),
   SUNION(new SUnionExecutor(), SUPPORTED,
       new Parameter().min(2).lastKey(-1).flags(READONLY, SORT_FOR_SCRIPT)),
@@ -355,8 +357,6 @@ public enum RedisCommandType {
       new Parameter().min(3).lastKey(-1).flags(WRITE, DENYOOM)),
   SPOP(new SPopExecutor(), UNSUPPORTED,
       new Parameter().min(2).max(3, ERROR_SYNTAX).flags(WRITE, RANDOM, FAST)),
-  SRANDMEMBER(new SRandMemberExecutor(), UNSUPPORTED,
-      new Parameter().min(2).flags(READONLY, RANDOM)),
   SSCAN(new SScanExecutor(), UNSUPPORTED, new 
Parameter().min(3).flags(READONLY, RANDOM),
       new Parameter().odd(ERROR_SYNTAX).firstKey(0)),
 
diff --git 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
index 86f60cf..a60d1c5 100755
--- 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
+++ 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
@@ -14,55 +14,13 @@
  */
 package org.apache.geode.redis.internal.commands.executor.set;
 
-import static org.apache.geode.redis.internal.netty.Coder.bytesToLong;
-import static org.apache.geode.redis.internal.netty.Coder.narrowLongToInt;
-
-import java.util.Collection;
 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 SRandMemberExecutor implements CommandExecutor {
-
-  private static final String ERROR_NOT_NUMERIC = "The count provided must be 
numeric";
+import org.apache.geode.redis.internal.data.RedisSet;
 
+public class SRandMemberExecutor extends SetRandomExecutor {
   @Override
-  public RedisResponse executeCommand(Command command, ExecutionHandlerContext 
context) {
-    List<byte[]> commandElems = command.getProcessedCommand();
-
-    RedisKey key = command.getKey();
-
-    boolean countSpecified = false;
-    int count;
-
-    if (commandElems.size() > 2) {
-      try {
-        count = narrowLongToInt(bytesToLong(commandElems.get(2)));
-        countSpecified = true;
-      } catch (NumberFormatException e) {
-        return RedisResponse.error(ERROR_NOT_NUMERIC);
-      }
-    } else {
-      count = 1;
-    }
-
-    if (count == 0) {
-      return RedisResponse.emptyArray();
-    }
-
-    Collection<byte[]> results = context.setLockedExecute(key, true,
-        set -> set.srandmember(count));
-
-    if (countSpecified) {
-      return RedisResponse.array(results, true);
-    } else if (results.isEmpty()) {
-      return RedisResponse.nil();
-    } else {
-      return RedisResponse.bulkString(results.iterator().next());
-    }
+  protected List<byte[]> performCommand(RedisSet set, int count) {
+    return set.srandmember(count);
   }
 }
diff --git 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetRandomExecutor.java
old mode 100755
new mode 100644
similarity index 59%
copy from 
geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
copy to 
geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetRandomExecutor.java
index 86f60cf..56e4254
--- 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SRandMemberExecutor.java
+++ 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetRandomExecutor.java
@@ -14,55 +14,63 @@
  */
 package org.apache.geode.redis.internal.commands.executor.set;
 
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
+import static 
org.apache.geode.redis.internal.data.NullRedisDataStructures.NULL_REDIS_SET;
+import static org.apache.geode.redis.internal.data.RedisDataType.REDIS_SET;
 import static org.apache.geode.redis.internal.netty.Coder.bytesToLong;
 import static org.apache.geode.redis.internal.netty.Coder.narrowLongToInt;
 
-import java.util.Collection;
+import java.util.Collections;
 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.data.RedisSet;
 import org.apache.geode.redis.internal.netty.ExecutionHandlerContext;
+import org.apache.geode.redis.internal.services.RegionProvider;
 
-public class SRandMemberExecutor implements CommandExecutor {
-
-  private static final String ERROR_NOT_NUMERIC = "The count provided must be 
numeric";
-
+public abstract class SetRandomExecutor implements CommandExecutor {
   @Override
   public RedisResponse executeCommand(Command command, ExecutionHandlerContext 
context) {
     List<byte[]> commandElems = command.getProcessedCommand();
-
     RedisKey key = command.getKey();
+    boolean hasCount = commandElems.size() == 3;
 
-    boolean countSpecified = false;
     int count;
-
-    if (commandElems.size() > 2) {
+    if (hasCount) {
       try {
         count = narrowLongToInt(bytesToLong(commandElems.get(2)));
-        countSpecified = true;
       } catch (NumberFormatException e) {
-        return RedisResponse.error(ERROR_NOT_NUMERIC);
+        return RedisResponse.error(ERROR_NOT_INTEGER);
       }
     } else {
       count = 1;
     }
 
-    if (count == 0) {
-      return RedisResponse.emptyArray();
-    }
-
-    Collection<byte[]> results = context.setLockedExecute(key, true,
-        set -> set.srandmember(count));
-
-    if (countSpecified) {
+    List<byte[]> results =
+        context.lockedExecute(key, () -> getResult(count, 
context.getRegionProvider(), key));
+    if (hasCount) {
       return RedisResponse.array(results, true);
-    } else if (results.isEmpty()) {
-      return RedisResponse.nil();
     } else {
-      return RedisResponse.bulkString(results.iterator().next());
+      if (results.isEmpty()) {
+        return RedisResponse.nil();
+      } else {
+        return RedisResponse.bulkString(results.get(0));
+      }
+    }
+  }
+
+  private List<byte[]> getResult(int count, RegionProvider regionProvider, 
RedisKey key) {
+    RedisSet set =
+        regionProvider.getTypedRedisData(REDIS_SET, key, true);
+    if (count == 0 || set == NULL_REDIS_SET) {
+      return Collections.emptyList();
     }
+
+    return performCommand(set, count);
   }
+
+  protected abstract List<byte[]> performCommand(RedisSet set, int count);
 }
diff --git 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSet.java
 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSet.java
index 1ef7d6f..13dbed8 100644
--- 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSet.java
+++ 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisSet.java
@@ -43,7 +43,7 @@ class NullRedisSet extends RedisSet {
   }
 
   @Override
-  public Collection<byte[]> srandmember(int count) {
+  public List<byte[]> srandmember(int count) {
     return emptyList();
   }
 
diff --git 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSet.java
 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSet.java
index f17d909..f9a2e1a 100644
--- 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSet.java
+++ 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisSet.java
@@ -28,6 +28,7 @@ import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
@@ -52,7 +53,6 @@ import 
org.apache.geode.redis.internal.services.RegionProvider;
 
 public class RedisSet extends AbstractRedisData {
   protected static final int REDIS_SET_OVERHEAD = 
memoryOverhead(RedisSet.class);
-
   private MemberSet members;
 
   public RedisSet(Collection<byte[]> members) {
@@ -275,49 +275,64 @@ public class RedisSet extends AbstractRedisData {
     return popped;
   }
 
-  public Collection<byte[]> srandmember(int count) {
-    int membersSize = members.size();
-    boolean duplicatesAllowed = count < 0;
-    if (duplicatesAllowed) {
-      count = -count;
+  public List<byte[]> srandmember(int count) {
+    List<byte[]> result = new ArrayList<>();
+    int randMethodRatio = 3;
+    if (count < 0) {
+      srandomDuplicateList(-count, result);
+    } else if (count * randMethodRatio < members.size()) {
+      // Count is small enough to add random elements to result
+      srandomUniqueListWithSmallCount(count, result);
+    } else {
+      // Count either equal or greater to member size or close to the member 
size
+      srandomUniqueListWithLargeCount(count, result);
     }
+    return result;
+  }
 
-    if (!duplicatesAllowed && membersSize <= count && count != 1) {
-      return new ArrayList<>(members);
+  private void srandomDuplicateList(int count, List<byte[]> result) {
+    Random rand = new Random();
+    while (result.size() != count) {
+      int randIndex = rand.nextInt(members.getBackingArrayLength());
+      byte[] member = members.getFromBackingArray(randIndex);
+      if (member != null) {
+        result.add(member);
+      }
     }
+  }
 
+  private void srandomUniqueListWithSmallCount(int count, List<byte[]> result) 
{
     Random rand = new Random();
+    Set<Integer> indexesUsed = new HashSet<>();
 
-    // TODO: this could be optimized to take advantage of MemberSet
-    // storing its data in an array. We probably don't need to copy it
-    // into another array here.
-    byte[][] entries = members.toArray(new byte[membersSize][]);
-
-    if (count == 1) {
-      byte[] randEntry = entries[rand.nextInt(entries.length)];
-      // TODO: Now that the result is no longer serialized this could use 
singleton.
-      // Note using ArrayList because Collections.singleton has serialization 
issues.
-      List<byte[]> result = new ArrayList<>(1);
-      result.add(randEntry);
-      return result;
-    }
-    if (duplicatesAllowed) {
-      List<byte[]> result = new ArrayList<>(count);
-      while (count > 0) {
-        result.add(entries[rand.nextInt(entries.length)]);
-        count--;
+    while (result.size() != count) {
+      int randIndex = rand.nextInt(members.getBackingArrayLength());
+      byte[] member = members.getFromBackingArray(randIndex);
+      if (member != null && !indexesUsed.contains(randIndex)) {
+        result.add(member);
+        indexesUsed.add(randIndex);
       }
-      return result;
-    } else {
-      Set<byte[]> result = new MemberSet(count);
-      // Note that rand.nextInt can return duplicates when "count" is high
-      // so we need to use a Set to collect the results.
-      while (result.size() < count) {
-        byte[] s = entries[rand.nextInt(entries.length)];
-        result.add(s);
+    }
+  }
+
+  private void srandomUniqueListWithLargeCount(int count, List<byte[]> result) 
{
+    if (count >= members.size()) {
+      result.addAll(members);
+      return;
+    }
+
+    Random rand = new Random();
+    MemberSet duplicateSet = new MemberSet(members);
+
+    while (duplicateSet.size() != count) {
+      int randIndex = rand.nextInt(duplicateSet.getBackingArrayLength());
+      byte[] member = duplicateSet.getFromBackingArray(randIndex);
+      if (member != null) {
+        duplicateSet.remove(member);
       }
-      return result;
     }
+
+    result.addAll(duplicateSet);
   }
 
   public boolean sismember(byte[] member) {
diff --git 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/collections/SizeableObjectOpenCustomHashSet.java
 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/collections/SizeableObjectOpenCustomHashSet.java
index d441b0b..1d57d8f 100644
--- 
a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/collections/SizeableObjectOpenCustomHashSet.java
+++ 
b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/collections/SizeableObjectOpenCustomHashSet.java
@@ -62,6 +62,14 @@ public abstract class SizeableObjectOpenCustomHashSet<K> 
extends ObjectOpenCusto
     return removed;
   }
 
+  public K getFromBackingArray(final int pos) {
+    return key[pos];
+  }
+
+  public int getBackingArrayLength() {
+    return key.length;
+  }
+
   @Override
   public int getSizeInBytes() {
     // The object referenced by the "strategy" field is not sized

Reply via email to