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 f7cc081  GEODE-9185: Add ZPOPMAX Radish command (#6726)
f7cc081 is described below

commit f7cc081745ff3c0c162b5073bf7eddd3da474573
Author: Jens Deppe <jde...@vmware.com>
AuthorDate: Wed Aug 4 06:23:40 2021 -0700

    GEODE-9185: Add ZPOPMAX Radish command (#6726)
---
 .../ZPopMaxNativeRedisAcceptanceTest.java          |  36 +++++
 .../server/AbstractHitsMissesIntegrationTest.java  |   5 +
 .../sortedset/AbstractZPopMaxIntegrationTest.java  | 157 +++++++++++++++++++++
 .../executor/sortedset/ZPopMaxIntegrationTest.java |  31 ++++
 .../geode/redis/internal/RedisCommandType.java     |   3 +
 .../redis/internal/data/NullRedisSortedSet.java    |   5 +
 .../geode/redis/internal/data/RedisSortedSet.java  |  32 ++++-
 .../RedisSortedSetCommandsFunctionExecutor.java    |   6 +
 .../executor/sortedset/RedisSortedSetCommands.java |   2 +
 .../executor/sortedset/ZPopMaxExecutor.java        |  51 +++++++
 .../redis/internal/SupportedCommandsJUnitTest.java |   1 +
 .../redis/internal/data/RedisSortedSetTest.java    | 125 +++++++++++++++-
 12 files changed, 446 insertions(+), 8 deletions(-)

diff --git 
a/geode-apis-compatible-with-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxNativeRedisAcceptanceTest.java
 
b/geode-apis-compatible-with-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxNativeRedisAcceptanceTest.java
new file mode 100755
index 0000000..12fb5b6
--- /dev/null
+++ 
b/geode-apis-compatible-with-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxNativeRedisAcceptanceTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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 ZPopMaxNativeRedisAcceptanceTest extends 
AbstractZPopMaxIntegrationTest {
+
+  @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 ceb0425..53d6a5f 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
@@ -253,6 +253,11 @@ public abstract class AbstractHitsMissesIntegrationTest 
implements RedisIntegrat
   }
 
   @Test
+  public void testZPopMax() {
+    runCommandAndAssertNoStatUpdates(SORTED_SET_KEY, k -> jedis.zpopmax(k, 1));
+  }
+
+  @Test
   public void testZrange() {
     runCommandAndAssertHitsAndMisses(SORTED_SET_KEY, k -> jedis.zrange(k, 0, 
1));
   }
diff --git 
a/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/AbstractZPopMaxIntegrationTest.java
 
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/AbstractZPopMaxIntegrationTest.java
new file mode 100755
index 0000000..4de663a
--- /dev/null
+++ 
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/AbstractZPopMaxIntegrationTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.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.Collections;
+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 redis.clients.jedis.Tuple;
+
+import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.RedisConstants;
+
+public abstract class AbstractZPopMaxIntegrationTest implements 
RedisIntegrationTest {
+  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 shouldError_givenWrongNumberOfArguments() {
+    assertThatThrownBy(
+        () -> jedis.sendCommand("key", Protocol.Command.ZPOPMAX, "key", "1", 
"2"))
+            .hasMessageContaining(RedisConstants.ERROR_SYNTAX);
+  }
+
+  @Test
+  public void shouldError_givenWrongNumberFormat() {
+    assertThatThrownBy(
+        () -> jedis.sendCommand("key", Protocol.Command.ZPOPMAX, "key", "wat"))
+            .hasMessageContaining(RedisConstants.ERROR_NOT_INTEGER);
+  }
+
+  @Test
+  public void shouldReturnEmpty_givenNonExistentSortedSet() {
+    assertThat(jedis.zpopmax("unknown", 1)).isEmpty();
+  }
+
+  @Test
+  public void shouldReturnEmpty_givenNegativeCount() {
+    jedis.zadd("key", 1, "player1");
+
+    List<?> result = (List<?>) jedis.sendCommand("key", 
Protocol.Command.ZPOPMAX, "key", "-1");
+    assertThat(result).isEmpty();
+    assertThat(jedis.zrange("key", 0, 10)).containsExactly("player1");
+  }
+
+  @Test
+  public void shouldReturn_highestLex_whenScoresAreEqual() {
+    jedis.zadd("key", 1, "player1");
+    jedis.zadd("key", 1, "player2");
+    jedis.zadd("key", 1, "player3");
+
+    assertThat(jedis.zpopmax("key").getElement()).isEqualTo("player3");
+    assertThat(jedis.zrange("key", 0, 10))
+        .containsExactlyInAnyOrder("player1", "player2");
+  }
+
+  @Test
+  public void shouldReturn_highestScore() {
+    jedis.zadd("key", 3, "player1");
+    jedis.zadd("key", 2, "player2");
+    jedis.zadd("key", 1, "player3");
+
+    assertThat(jedis.zpopmax("key").getElement()).isEqualTo("player1");
+    assertThat(jedis.zrange("key", 0, 10))
+        .containsExactlyInAnyOrder("player2", "player3");
+  }
+
+  @Test
+  public void 
withCountShouldReturn_membersInScoreOrder_whenScoresAreDifferent() {
+    List<Tuple> tuples = new ArrayList<>();
+    int count = 10;
+    // Make sure that the results are not somehow dependent on the order of 
insertion
+    List<Integer> shuffles = makeShuffledList(count);
+    for (int i = 0; i < count; i++) {
+      jedis.zadd("key", count - shuffles.get(i), "player" + shuffles.get(i));
+      tuples.add(new Tuple("player" + i, (double) (count - i)));
+    }
+
+    assertThat(jedis.zpopmax("key", count)).containsExactlyElementsOf(tuples);
+  }
+
+  @Test
+  public void 
withCountShouldReturn_membersInReverseLexicalOrder_whenScoresAreTheSame() {
+    List<Tuple> tuples = new ArrayList<>();
+    int count = 10;
+    // Make sure that the results are not somehow dependent on the order of 
insertion
+    List<Integer> shuffles = makeShuffledList(count);
+    for (int i = 0; i < count; i++) {
+      jedis.zadd("key", 1D, "player" + shuffles.get(i));
+      tuples.add(new Tuple("player" + (count - i - 1), 1D));
+    }
+
+    assertThat(jedis.zpopmax("key", count)).containsExactlyElementsOf(tuples);
+  }
+
+  @Test
+  public void shouldReturn_countHighestScores() {
+    for (int i = 0; i < 5; i++) {
+      jedis.zadd("key", i, "player" + i);
+    }
+
+    assertThat(jedis.zpopmax("key", 3))
+        .containsExactlyInAnyOrder(
+            new Tuple("player2", 2D),
+            new Tuple("player3", 3D),
+            new Tuple("player4", 4D));
+
+    assertThat(jedis.zrange("key", 0, 10))
+        .containsExactly("player0", "player1");
+  }
+
+  /**
+   * Create a shuffled list of a range of integers from 0..count (exclusive).
+   */
+  private List<Integer> makeShuffledList(int count) {
+    List<Integer> result = new ArrayList<>(count);
+    for (int i = 0; i < count; i++) {
+      result.add(i);
+    }
+
+    Collections.shuffle(result);
+    return result;
+  }
+}
diff --git 
a/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxIntegrationTest.java
 
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxIntegrationTest.java
new file mode 100755
index 0000000..2e79e6f
--- /dev/null
+++ 
b/geode-apis-compatible-with-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxIntegrationTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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 ZPopMaxIntegrationTest extends AbstractZPopMaxIntegrationTest {
+
+  @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 60f7a30..10fe5a7 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.ZPopMaxExecutor;
 import org.apache.geode.redis.internal.executor.sortedset.ZRangeByLexExecutor;
 import 
org.apache.geode.redis.internal.executor.sortedset.ZRangeByScoreExecutor;
 import org.apache.geode.redis.internal.executor.sortedset.ZRangeExecutor;
@@ -213,6 +214,8 @@ public enum RedisCommandType {
   ZCARD(new ZCardExecutor(), SUPPORTED, new ExactParameterRequirements(2)),
   ZCOUNT(new ZCountExecutor(), SUPPORTED, new ExactParameterRequirements(4)),
   ZINCRBY(new ZIncrByExecutor(), SUPPORTED, new ExactParameterRequirements(4)),
+  ZPOPMAX(new ZPopMaxExecutor(), SUPPORTED, new MinimumParameterRequirements(2)
+      .and(new MaximumParameterRequirements(3, ERROR_SYNTAX))),
   ZRANGE(new ZRangeExecutor(), SUPPORTED, new MinimumParameterRequirements(4)
       .and(new MaximumParameterRequirements(5, ERROR_SYNTAX))),
   ZRANGEBYLEX(new ZRangeByLexExecutor(), SUPPORTED, new 
MinimumParameterRequirements(4)),
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 94ea183..f58c75c 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
@@ -87,6 +87,11 @@ class NullRedisSortedSet extends RedisSortedSet {
   }
 
   @Override
+  List<byte[]> zpopmax(Region<RedisKey, RedisData> region, RedisKey key, int 
count) {
+    return Collections.emptyList();
+  }
+
+  @Override
   List<byte[]> zrange(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 f92ebae..ebfeb76 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
@@ -205,12 +205,16 @@ public class RedisSortedSet extends AbstractRedisData {
       scoreSet.remove(orderedSetEntry);
       oldValue = orderedSetEntry.getScoreBytes();
       // Adjust for removal.
-      sizeInBytesAdjustment -= orderedSetEntry.getSizeInBytes() + 
calculateByteArraySize(member);
+      adjustSizeForRemoval(orderedSetEntry);
     }
 
     return oldValue;
   }
 
+  private void adjustSizeForRemoval(AbstractOrderedSetEntry entry) {
+    sizeInBytesAdjustment -= entry.getSizeInBytes() + 
calculateByteArraySize(entry.getMember());
+  }
+
   private synchronized void membersAddAll(AddsDeltaInfo addsDeltaInfo) {
     Iterator<byte[]> iterator = addsDeltaInfo.getAdds().iterator();
     while (iterator.hasNext()) {
@@ -397,6 +401,32 @@ public class RedisSortedSet extends AbstractRedisData {
     return null;
   }
 
+  List<byte[]> zpopmax(Region<RedisKey, RedisData> region, RedisKey key, int 
count) {
+    Iterator<AbstractOrderedSetEntry> scoresIterator =
+        scoreSet.getIndexRange(scoreSet.size() - 1, count, true);
+
+    if (!scoresIterator.hasNext()) {
+      return Collections.emptyList();
+    }
+
+    List<byte[]> result = new ArrayList<>();
+    RemsDeltaInfo deltaInfo = new RemsDeltaInfo();
+    while (scoresIterator.hasNext()) {
+      AbstractOrderedSetEntry entry = scoresIterator.next();
+      scoresIterator.remove();
+      members.remove(entry.member);
+      adjustSizeForRemoval(entry);
+
+      result.add(entry.member);
+      result.add(entry.scoreBytes);
+      deltaInfo.add(entry.member);
+    }
+
+    storeChanges(region, key, deltaInfo);
+
+    return result;
+  }
+
   private byte[] zaddIncr(Region<RedisKey, RedisData> region, RedisKey key,
       List<byte[]> membersToAdd, ZAddOptions options) {
     // for zadd incr option, only one incrementing element pair is allowed to 
get here.
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 f2405a0..910afa0 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
@@ -102,4 +102,10 @@ public class RedisSortedSetCommandsFunctionExecutor 
extends RedisDataCommandsFun
   public byte[] zscore(RedisKey key, byte[] member) {
     return stripedExecute(key, () -> getRedisSortedSet(key, 
true).zscore(member));
   }
+
+  @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 444d4e0..6bcd46a 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
@@ -45,4 +45,6 @@ public interface RedisSortedSetCommands {
   long zrevrank(RedisKey key, byte[] member);
 
   byte[] zscore(RedisKey key, byte[] member);
+
+  List<byte[]> zpopmax(RedisKey key, int count);
 }
diff --git 
a/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxExecutor.java
 
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxExecutor.java
new file mode 100644
index 0000000..9ccbc90
--- /dev/null
+++ 
b/geode-apis-compatible-with-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZPopMaxExecutor.java
@@ -0,0 +1,51 @@
+/*
+ * 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 java.util.List;
+
+import org.apache.geode.redis.internal.RedisConstants;
+import org.apache.geode.redis.internal.executor.AbstractExecutor;
+import org.apache.geode.redis.internal.executor.RedisResponse;
+import org.apache.geode.redis.internal.netty.Coder;
+import org.apache.geode.redis.internal.netty.Command;
+import org.apache.geode.redis.internal.netty.ExecutionHandlerContext;
+
+public class ZPopMaxExecutor extends AbstractExecutor {
+  @Override
+  public RedisResponse executeCommand(Command command, ExecutionHandlerContext 
context) {
+    RedisSortedSetCommands redisSortedSetCommands = 
context.getSortedSetCommands();
+
+    List<byte[]> commandElements = command.getProcessedCommand();
+
+    int count = 1;
+    if (commandElements.size() > 2) {
+      try {
+        count = 
Coder.narrowLongToInt(Coder.bytesToLong(commandElements.get(2)));
+      } catch (NumberFormatException nex) {
+        return RedisResponse.error(RedisConstants.ERROR_NOT_INTEGER);
+      }
+    }
+
+    if (count < 1) {
+      return RedisResponse.emptyArray();
+    }
+
+    List<byte[]> result = redisSortedSetCommands.zpopmax(command.getKey(), 
count);
+
+    return RedisResponse.array(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 3ba9e19..fb29e59 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",
+      "ZPOPMAX",
       "ZRANGE",
       "ZRANGEBYLEX",
       "ZRANGEBYSCORE",
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 176db29..0e3bc3e 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
@@ -45,6 +45,7 @@ import java.util.stream.Collectors;
 import it.unimi.dsi.fastutil.bytes.ByteArrays;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
 import org.apache.geode.DataSerializer;
 import org.apache.geode.cache.Region;
@@ -475,6 +476,70 @@ public class RedisSortedSetTest {
     assertThat(inclusiveMax.compareTo(realEntry)).isEqualTo(1);
   }
 
+  @Test
+  public void zpopmaxRemovesMemberWithHighestScore() {
+    int originalSize = rangeSortedSet.getSortedSetSize();
+    RedisSortedSet sortedSet = spy(rangeSortedSet);
+    Region<RedisKey, RedisData> region = uncheckedCast(mock(Region.class));
+    RedisKey key = new RedisKey();
+    int count = 1;
+
+    List<byte[]> result = sortedSet.zpopmax(region, key, count);
+    assertThat(result).containsExactly("member12".getBytes(), 
"2.1".getBytes());
+
+    ArgumentCaptor<RemsDeltaInfo> argumentCaptor = 
ArgumentCaptor.forClass(RemsDeltaInfo.class);
+    verify(sortedSet).storeChanges(eq(region), eq(key), 
argumentCaptor.capture());
+    
assertThat(argumentCaptor.getValue().getRemoves()).containsExactly("member12".getBytes());
+    assertThat(rangeSortedSet.getSortedSetSize()).isEqualTo(originalSize - 
count);
+  }
+
+  @Test
+  public void 
zpopmaxRemovesMembersWithHighestScores_whenCountIsGreaterThanOne() {
+    int originalSize = rangeSortedSet.getSortedSetSize();
+    RedisSortedSet sortedSet = spy(rangeSortedSet);
+    Region<RedisKey, RedisData> region = uncheckedCast(mock(Region.class));
+    RedisKey key = new RedisKey();
+    int count = 3;
+
+    List<byte[]> result = sortedSet.zpopmax(region, key, count);
+    assertThat(result).containsExactlyInAnyOrder("member10".getBytes(), 
"1.9".getBytes(),
+        "member11".getBytes(), "2".getBytes(), "member12".getBytes(), 
"2.1".getBytes());
+
+    ArgumentCaptor<RemsDeltaInfo> argumentCaptor = 
ArgumentCaptor.forClass(RemsDeltaInfo.class);
+    verify(sortedSet).storeChanges(eq(region), eq(key), 
argumentCaptor.capture());
+    
assertThat(argumentCaptor.getValue().getRemoves()).containsExactlyInAnyOrder(
+        "member10".getBytes(), "member11".getBytes(), "member12".getBytes());
+    assertThat(rangeSortedSet.getSortedSetSize()).isEqualTo(originalSize - 
count);
+  }
+
+  @Test
+  public void zpopmaxRemovesRegionEntryWhenSetBecomesEmpty() {
+    RedisSortedSet sortedSet = spy(createRedisSortedSet(score1, member1));
+    Region<RedisKey, RedisData> region = uncheckedCast(mock(Region.class));
+    RedisKey key = new RedisKey();
+
+    List<byte[]> result = sortedSet.zpopmax(region, key, 1);
+    assertThat(result).containsExactly(member1.getBytes(), score1.getBytes());
+
+    verify(sortedSet).storeChanges(eq(region), eq(key), 
any(RemsDeltaInfo.class));
+    verify(region).remove(key);
+  }
+
+  @Test
+  public void zpopmaxRemovesHighestLexWhenScoresAreEqual() {
+    RedisSortedSet sortedSet = spy(createRedisSortedSet(
+        "1.1", "member5",
+        "1.1", "member4",
+        "1.1", "member3",
+        "1.1", "member2",
+        "1.1", "member1"));
+    Region<RedisKey, RedisData> region = uncheckedCast(mock(Region.class));
+    RedisKey key = new RedisKey();
+
+    List<byte[]> result = sortedSet.zpopmax(region, key, 1);
+    assertThat(result).containsExactly("member5".getBytes(), "1.1".getBytes());
+  }
+
   /******** constants *******/
   // These tests contain the math that is used to derive the constants in 
RedisSortedSet and
   // OrderedSetEntry. If these tests start failing, it is because the 
overheads of RedisSortedSet or
@@ -507,7 +572,7 @@ public class RedisSortedSetTest {
   /****************** Size ******************/
 
   @Test
-  public void 
redisSortedSetGetSizeInBytes_isAccurateForAddsUpdatesAndRemoves() {
+  public void redisSortedSetGetSizeInBytes_isAccurateForAdds() {
     Region<RedisKey, RedisData> mockRegion = uncheckedCast(mock(Region.class));
     RedisKey mockKey = mock(RedisKey.class);
     ZAddOptions options = new ZAddOptions(ZAddOptions.Exists.NONE, false, 
false);
@@ -527,15 +592,41 @@ public class RedisSortedSetTest {
       calculatedSize = sortedSet.getSizeInBytes();
       assertThat(calculatedSize).isEqualTo(actualSize);
     }
+  }
+
+  @Test
+  public void redisSortedSetGetSizeInBytes_isAccurateForUpdates() {
+    Region<RedisKey, RedisData> mockRegion = uncheckedCast(mock(Region.class));
+    RedisKey mockKey = mock(RedisKey.class);
+    ZAddOptions options = new ZAddOptions(ZAddOptions.Exists.NONE, false, 
false);
+    RedisSortedSet sortedSet = new RedisSortedSet(Collections.emptyList());
+
+    int numberOfEntries = 100;
+    for (int i = 0; i < numberOfEntries; ++i) {
+      List<byte[]> scoreAndMember = Arrays.asList(doubleToBytes(i), new 
byte[i]);
+      sortedSet.zadd(mockRegion, mockKey, scoreAndMember, options);
+    }
 
     // Update half the scores and confirm that the calculated size is accurate 
after each operation
     for (int i = 0; i < numberOfEntries / 2; ++i) {
       List<byte[]> scoreAndMember = Arrays.asList(doubleToBytes(i * 2), new 
byte[i]);
       sortedSet.zadd(mockRegion, mockKey, scoreAndMember, options);
       sortedSet.clearDelta();
-      actualSize = sizer.sizeof(sortedSet);
-      calculatedSize = sortedSet.getSizeInBytes();
-      assertThat(calculatedSize).isEqualTo(actualSize);
+      
assertThat(sizer.sizeof(sortedSet)).isEqualTo(sortedSet.getSizeInBytes());
+    }
+  }
+
+  @Test
+  public void redisSortedSetGetSizeInBytes_isAccurateForRemoves() {
+    Region<RedisKey, RedisData> mockRegion = uncheckedCast(mock(Region.class));
+    RedisKey mockKey = mock(RedisKey.class);
+    ZAddOptions options = new ZAddOptions(ZAddOptions.Exists.NONE, false, 
false);
+    RedisSortedSet sortedSet = new RedisSortedSet(Collections.emptyList());
+
+    int numberOfEntries = 100;
+    for (int i = 0; i < numberOfEntries; ++i) {
+      List<byte[]> scoreAndMember = Arrays.asList(doubleToBytes(i), new 
byte[i]);
+      sortedSet.zadd(mockRegion, mockKey, scoreAndMember, options);
     }
 
     // Remove all members and confirm that the calculated size is accurate 
after each operation
@@ -543,9 +634,29 @@ public class RedisSortedSetTest {
       List<byte[]> memberToRemove = Collections.singletonList(new byte[i]);
       sortedSet.zrem(mockRegion, mockKey, memberToRemove);
       sortedSet.clearDelta();
-      actualSize = sizer.sizeof(sortedSet);
-      calculatedSize = sortedSet.getSizeInBytes();
-      assertThat(calculatedSize).isEqualTo(actualSize);
+      
assertThat(sizer.sizeof(sortedSet)).isEqualTo(sortedSet.getSizeInBytes());
+    }
+  }
+
+
+  @Test
+  public void redisSortedSetGetSizeInBytes_isAccurateForZpopmax() {
+    Region<RedisKey, RedisData> mockRegion = uncheckedCast(mock(Region.class));
+    RedisKey mockKey = mock(RedisKey.class);
+    ZAddOptions options = new ZAddOptions(ZAddOptions.Exists.NONE, false, 
false);
+    RedisSortedSet sortedSet = new RedisSortedSet(Collections.emptyList());
+
+    int numberOfEntries = 100;
+    for (int i = 0; i < numberOfEntries; ++i) {
+      List<byte[]> scoreAndMember = Arrays.asList(doubleToBytes(i), new 
byte[i]);
+      sortedSet.zadd(mockRegion, mockKey, scoreAndMember, options);
+    }
+
+    // Remove all members by zpopmax and ensure size is correct
+    for (int i = 0; i < numberOfEntries; ++i) {
+      sortedSet.zpopmax(mockRegion, mockKey, 1);
+      sortedSet.clearDelta();
+      
assertThat(sizer.sizeof(sortedSet)).isEqualTo(sortedSet.getSizeInBytes());
     }
   }
 

Reply via email to