From 4f699963ffd1f65ab4bc7581c0bc5e436b20037f Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Mon, 3 Feb 2025 00:34:08 +0600 Subject: [PATCH 1/2] Support INFO command in UnifiedJedis --- .../redis/clients/jedis/UnifiedJedis.java | 15 +++++++ .../executors/ClusterCommandExecutor.java | 19 +++++++++ .../jedis/executors/CommandExecutor.java | 5 +++ .../jedis/util/JedisBroadcastReplies.java | 40 +++++++++++++++++++ .../pooled/PooledMiscellaneousTest.java | 16 ++++++++ 5 files changed, 95 insertions(+) create mode 100644 src/main/java/redis/clients/jedis/util/JedisBroadcastReplies.java diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 4f2c97eac7..b4ae20801c 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -42,6 +42,7 @@ import redis.clients.jedis.search.schemafields.SchemaField; import redis.clients.jedis.timeseries.*; import redis.clients.jedis.util.IOUtils; +import redis.clients.jedis.util.JedisBroadcastReplies; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.KeyValue; @@ -333,6 +334,10 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co this.commandObjects.setBroadcastAndRoundRobinConfig(this.broadcastAndRoundRobinConfig); } + public final JedisBroadcastReplies broadcastCommandDifferingReplies(CommandObject commandObject) { + return executor.broadcastCommandDifferingReplies(commandObject); + } + public Cache getCache() { return cache; } @@ -353,6 +358,16 @@ public String configSet(String parameter, String value) { return checkAndBroadcastCommand(commandObjects.configSet(parameter, value)); } + public final JedisBroadcastReplies info() { + return executor.broadcastCommandDifferingReplies(new CommandObject( + new CommandArguments(Protocol.Command.INFO), BuilderFactory.STRING)); + } + + public final JedisBroadcastReplies info(String section) { + return executor.broadcastCommandDifferingReplies(new CommandObject( + new CommandArguments(Protocol.Command.INFO).add(section), BuilderFactory.STRING)); + } + // Key commands @Override public boolean exists(String key) { diff --git a/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java b/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java index e5049672b8..85bba16077 100644 --- a/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java +++ b/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java @@ -17,6 +17,7 @@ import redis.clients.jedis.exceptions.*; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.util.IOUtils; +import redis.clients.jedis.util.JedisBroadcastReplies; public class ClusterCommandExecutor implements CommandExecutor { @@ -71,6 +72,24 @@ public final T broadcastCommand(CommandObject commandObject) { return reply; } + @Override + public final JedisBroadcastReplies broadcastCommandDifferingReplies(CommandObject commandObject) { + Map connectionMap = provider.getConnectionMap(); + + JedisBroadcastReplies bcastReplies = new JedisBroadcastReplies(connectionMap.size()); + for (Map.Entry entry : connectionMap.entrySet()) { + HostAndPort node = HostAndPort.from(entry.getKey()); + ConnectionPool pool = entry.getValue(); + try (Connection connection = pool.getResource()) { + Object aReply = execute(connection, commandObject); + bcastReplies.addReply(node, aReply); + } catch (Exception anError) { + bcastReplies.addReply(node, anError); + } + } + return bcastReplies; + } + @Override public final T executeCommand(CommandObject commandObject) { return doExecuteCommand(commandObject, false); diff --git a/src/main/java/redis/clients/jedis/executors/CommandExecutor.java b/src/main/java/redis/clients/jedis/executors/CommandExecutor.java index 85ec034b02..2d35c0d2fd 100644 --- a/src/main/java/redis/clients/jedis/executors/CommandExecutor.java +++ b/src/main/java/redis/clients/jedis/executors/CommandExecutor.java @@ -1,6 +1,7 @@ package redis.clients.jedis.executors; import redis.clients.jedis.CommandObject; +import redis.clients.jedis.util.JedisBroadcastReplies; public interface CommandExecutor extends AutoCloseable { @@ -9,4 +10,8 @@ public interface CommandExecutor extends AutoCloseable { default T broadcastCommand(CommandObject commandObject) { return executeCommand(commandObject); } + + default JedisBroadcastReplies broadcastCommandDifferingReplies(CommandObject commandObject) { + return JedisBroadcastReplies.singleton(executeCommand(commandObject)); + } } diff --git a/src/main/java/redis/clients/jedis/util/JedisBroadcastReplies.java b/src/main/java/redis/clients/jedis/util/JedisBroadcastReplies.java new file mode 100644 index 0000000000..c98c134bde --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/JedisBroadcastReplies.java @@ -0,0 +1,40 @@ +package redis.clients.jedis.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The collection of replies where a command is broadcasted to multiple nodes, but the replies are expected to differ + * even in ideal situation. + */ +public class JedisBroadcastReplies { + + private static final Object DUMMY_OBJECT = new Object(); + + private final Map replies; + + public JedisBroadcastReplies() { + this(new HashMap<>()); + } + + public JedisBroadcastReplies(int size) { + this(new HashMap<>(size)); + } + + private JedisBroadcastReplies(Map replies) { + this.replies = replies; + } + + public void addReply(Object node, Object reply) { + replies.put(node, reply); + } + + public Map getReplies() { + return Collections.unmodifiableMap(replies); + } + + public static JedisBroadcastReplies singleton(Object reply) { + return new JedisBroadcastReplies(Collections.singletonMap(DUMMY_OBJECT, reply)); + } +} diff --git a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledMiscellaneousTest.java b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledMiscellaneousTest.java index c1e3857bb6..326d1c3e3c 100644 --- a/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledMiscellaneousTest.java +++ b/src/test/java/redis/clients/jedis/commands/unified/pooled/PooledMiscellaneousTest.java @@ -1,5 +1,6 @@ package redis.clients.jedis.commands.unified.pooled; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; @@ -9,6 +10,7 @@ import java.util.List; import io.redis.test.annotations.SinceRedisVersion; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -23,6 +25,7 @@ import redis.clients.jedis.commands.unified.UnifiedJedisCommandsTestBase; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.util.EnabledOnCommandRule; +import redis.clients.jedis.util.JedisBroadcastReplies; import redis.clients.jedis.util.RedisVersionRule; @RunWith(Parameterized.class) @@ -164,4 +167,17 @@ public void broadcastWithError() { () -> jedis.functionDelete("xyz")); assertEquals("ERR Library not found", error.getMessage()); } + + @Test + public void broadcastDifferentReplies() { + JedisBroadcastReplies infoReplies = jedis.info(); + assertThat(infoReplies.getReplies(), Matchers.aMapWithSize(1)); + Object infoValue = infoReplies.getReplies().values().stream().findFirst().get(); + assertThat((String) infoValue, Matchers.notNullValue()); + + infoReplies = jedis.info("server"); + assertThat(infoReplies.getReplies(), Matchers.aMapWithSize(1)); + infoValue = infoReplies.getReplies().values().stream().findFirst().get(); + assertThat((String) infoValue, Matchers.notNullValue()); + } } From 0b9833d35eed698425585d2649bf48f6edc3a812 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Mon, 3 Feb 2025 01:02:12 +0600 Subject: [PATCH 2/2] test in cluster --- .../jedis/ClusterValuesCommandsTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/redis/clients/jedis/commands/jedis/ClusterValuesCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/ClusterValuesCommandsTest.java index 7168a186d1..9efd3ce9e4 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/ClusterValuesCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/ClusterValuesCommandsTest.java @@ -1,5 +1,6 @@ package redis.clients.jedis.commands.jedis; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; @@ -10,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.hamcrest.Matchers; import org.junit.Test; import redis.clients.jedis.BuilderFactory; @@ -23,6 +25,7 @@ import redis.clients.jedis.params.GeoRadiusParam; import redis.clients.jedis.params.GeoRadiusStoreParam; import redis.clients.jedis.resps.ScanResult; +import redis.clients.jedis.util.JedisBroadcastReplies; public class ClusterValuesCommandsTest extends ClusterJedisCommandsTestBase { @@ -128,6 +131,21 @@ public void pingBroadcast() { assertEquals("PONG", cluster.ping()); } + @Test + public void broadcastDifferentReplies() { + JedisBroadcastReplies infoReplies = cluster.info(); + assertThat(infoReplies.getReplies(), Matchers.aMapWithSize(3)); + infoReplies.getReplies().values().forEach(infoValue -> { + assertThat((String) infoValue, Matchers.notNullValue()); + }); + + infoReplies = cluster.info("server"); + assertThat(infoReplies.getReplies(), Matchers.aMapWithSize(3)); + infoReplies.getReplies().values().forEach(infoValue -> { + assertThat((String) infoValue, Matchers.notNullValue()); + }); + } + @Test public void flushAllBroadcast() { assertNull(cluster.get("foo"));