From 7a806a13c315bcaa2dda5ad196ef67eb1f9a9dfb Mon Sep 17 00:00:00 2001 From: R-J Lim Date: Tue, 12 Mar 2024 23:50:31 -0700 Subject: [PATCH 01/12] Proof-of-concept for automatic key prefixing --- ...usterCommandArgumentsWithPrefixedKeys.java | 46 ++++++++++++++ ...ClusterCommandObjectsWithPrefixedKeys.java | 16 +++++ .../jedis/JedisClusterWithPrefixedKeys.java | 61 +++++++++++++++++++ .../redis/clients/jedis/UnifiedJedis.java | 6 +- .../JedisClusterWithPrefixedKeysTest.java | 38 ++++++++++++ 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java create mode 100644 src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java create mode 100644 src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java create mode 100644 src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java diff --git a/src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java new file mode 100644 index 0000000000..63a7905585 --- /dev/null +++ b/src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java @@ -0,0 +1,46 @@ +package redis.clients.jedis; + +import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.args.RawableFactory; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.SafeEncoder; + +public class ClusterCommandArgumentsWithPrefixedKeys extends ClusterCommandArguments { + private final byte[] prefix; + private final String prefixString; + + public ClusterCommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString) { + super(command); + this.prefixString = prefixString; + prefix = SafeEncoder.encode(prefixString); + } + + public CommandArguments key(Object key) { + return super.key(namespacedKey(key)); + } + + private Object namespacedKey(Object key) { + if (key instanceof Rawable) { + byte[] raw = ((Rawable) key).getRaw(); + return RawableFactory.from(namespacedKeyBytes(raw)); + } + + if (key instanceof byte[]) { + return namespacedKeyBytes((byte[]) key); + } + + if (key instanceof String) { + String raw = (String) key; + return prefixString + raw; + } + + throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); + } + + private byte[] namespacedKeyBytes(byte[] key) { + byte[] namespaced = new byte[prefix.length + key.length]; + System.arraycopy(prefix, 0, namespaced, 0, prefix.length); + System.arraycopy(key, 0, namespaced, prefix.length, key.length); + return namespaced; + } +} diff --git a/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java new file mode 100644 index 0000000000..01e103c4f4 --- /dev/null +++ b/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java @@ -0,0 +1,16 @@ +package redis.clients.jedis; + +import redis.clients.jedis.commands.ProtocolCommand; + +public class ClusterCommandObjectsWithPrefixedKeys extends ClusterCommandObjects { + // For the purposes of this demonstration, the prefix is assigned statically. + // Additional changes are required to prevent the parent class CommandObjects + // from calling commandArguments in its constructor, which would be a prerequisite + // to making this field into an instance field. + public static String PREFIX_STRING; + + @Override + protected ClusterCommandArguments commandArguments(ProtocolCommand command) { + return new ClusterCommandArgumentsWithPrefixedKeys(command, PREFIX_STRING); + } +} diff --git a/src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java new file mode 100644 index 0000000000..a23ca5574b --- /dev/null +++ b/src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java @@ -0,0 +1,61 @@ +package redis.clients.jedis; + +import redis.clients.jedis.executors.ClusterCommandExecutor; +import redis.clients.jedis.providers.ClusterConnectionProvider; +import redis.clients.jedis.util.JedisClusterCRC16; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; + +public class JedisClusterWithPrefixedKeys extends UnifiedJedis { + + public JedisClusterWithPrefixedKeys(HostAndPort node, JedisClientConfig clientConfig) { + this(new ClusterConnectionProvider(Collections.singleton(node), clientConfig), clientConfig); + } + + public JedisClusterWithPrefixedKeys(ClusterConnectionProvider provider, JedisClientConfig clientConfig) { + super(new ClusterCommandExecutor(provider, 5, Duration.ofSeconds(100)), provider, new ClusterCommandObjectsWithPrefixedKeys(), clientConfig.getRedisProtocol()); + + } + + public Map getClusterNodes() { + return ((ClusterConnectionProvider) provider).getNodes(); + } + + public Connection getConnectionFromSlot(int slot) { + return ((ClusterConnectionProvider) provider).getConnectionFromSlot(slot); + } + + // commands + public long spublish(String channel, String message) { + return executeCommand(commandObjects.spublish(channel, message)); + } + + public long spublish(byte[] channel, byte[] message) { + return executeCommand(commandObjects.spublish(channel, message)); + } + + public void ssubscribe(final JedisShardedPubSub jedisPubSub, final String... channels) { + try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) { + jedisPubSub.proceed(connection, channels); + } + } + + public void ssubscribe(BinaryJedisShardedPubSub jedisPubSub, final byte[]... channels) { + try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) { + jedisPubSub.proceed(connection, channels); + } + } + // commands + + @Override + public ClusterPipeline pipelined() { + return new ClusterPipeline((ClusterConnectionProvider) provider, (ClusterCommandObjects) commandObjects); + } + + @Override + public Transaction multi() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 14e67a0fa1..0f5c7676d6 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -206,12 +206,12 @@ public UnifiedJedis(CommandExecutor executor) { this(executor, (ConnectionProvider) null); } - private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider) { + public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider) { this(executor, provider, new CommandObjects()); } // Uses a fetched connection to process protocol. Should be avoided if possible. - private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects) { + public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects) { this(executor, provider, commandObjects, null); if (this.provider != null) { try (Connection conn = this.provider.getConnection()) { @@ -223,7 +223,7 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm } } - private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, + public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, RedisProtocol protocol) { this.provider = provider; this.executor = executor; diff --git a/src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java new file mode 100644 index 0000000000..557422cb2d --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java @@ -0,0 +1,38 @@ +package redis.clients.jedis; + +import org.junit.Test; +import redis.clients.jedis.resps.Tuple; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import static junit.framework.TestCase.assertEquals; + +public class JedisClusterWithPrefixedKeysTest extends JedisClusterTestBase { + private static final DefaultJedisClientConfig DEFAULT_CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("cluster").build(); + private static final int DEFAULT_TIMEOUT = 2000; + private static final int DEFAULT_REDIRECTIONS = 5; + private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig(); + + @Test + public void hasPrefixedKeys() { + HostAndPort hp = new HostAndPort("127.0.0.1", 7379); + ClusterCommandObjectsWithPrefixedKeys.PREFIX_STRING = "test-prefix:"; + + try (JedisClusterWithPrefixedKeys cluster = new JedisClusterWithPrefixedKeys(hp, DEFAULT_CLIENT_CONFIG)) { + cluster.set("foo1", "bar1"); + cluster.set("foo2".getBytes(StandardCharsets.UTF_8), "bar2".getBytes(StandardCharsets.UTF_8)); + ClusterPipeline pipeline = cluster.pipelined(); + pipeline.incr("foo3"); + pipeline.zadd("foo4", 1234, "bar4"); + pipeline.sync(); + } + + try (JedisCluster cluster = new JedisCluster(hp, DEFAULT_CLIENT_CONFIG, DEFAULT_REDIRECTIONS, DEFAULT_POOL_CONFIG)) { + assertEquals("bar1", cluster.get("test-prefix:foo1")); + assertEquals("bar2", cluster.get("test-prefix:foo2")); + assertEquals("1", cluster.get("test-prefix:foo3")); + assertEquals(new Tuple("bar4", 1234d), cluster.zpopmax("test-prefix:foo4")); + } + } +} From 90a50b42fbc9b3456f925eb1999bdfd0091bc312 Mon Sep 17 00:00:00 2001 From: R-J Lim Date: Wed, 13 Mar 2024 23:16:53 -0700 Subject: [PATCH 02/12] Iteration on key-prefixing POC - Demonstrated automatic key-prefixing for all subclasses of UnifiedJedis: JedisCluster, JedisPooled, and JedisSentineled - Key-prefixing is possible as long as the underlying CommandObjects can be customized. - CommandObjects cannot use commandArguments in its constructor since in the specific case of key-prefixing, commandArguments depends on the child constructor running first. So we lose caching of argument-less CommandObjects. - Based on this POC, the minimum changes required to jedis would be: - public constructors that allow UnifiedJedis and its subclasses to take a custom CommandObjects. - Consistent use of supplied CommandObjects throughout code (e.g. in Pipeline, Transaction, etc). - Removal of caching of argument-less CommandObjects in the constructor of CommandObjects. - Applications can then supply CommandObjects with custom behavior as necessary. Sample classes that implement the behavior of prefixed keys, etc are provided but these can be supplied by the application as long as required constructors are available. --- ...ClusterCommandObjectsWithPrefixedKeys.java | 12 ++-- .../CommandArgumentsWithPrefixedKeys.java | 46 ++++++++++++++ .../redis/clients/jedis/CommandObjects.java | 25 ++------ .../jedis/CommandObjectsWithPrefixedKeys.java | 16 +++++ .../redis/clients/jedis/JedisCluster.java | 5 ++ .../jedis/JedisClusterWithPrefixedKeys.java | 61 ------------------- .../java/redis/clients/jedis/JedisPooled.java | 6 ++ .../redis/clients/jedis/JedisSentineled.java | 5 ++ .../java/redis/clients/jedis/Pipeline.java | 6 +- .../redis/clients/jedis/UnifiedJedis.java | 2 +- .../jedis/JedisClusterPrefixedKeysTest.java | 56 +++++++++++++++++ .../JedisClusterWithPrefixedKeysTest.java | 38 ------------ .../jedis/JedisPooledPrefixedKeysTest.java | 20 ++++++ .../JedisSentineledPrefixedKeysTest.java | 25 ++++++++ .../redis/clients/jedis/PrefixedKeysTest.java | 36 +++++++++++ 15 files changed, 233 insertions(+), 126 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java create mode 100644 src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java delete mode 100644 src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java create mode 100644 src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java delete mode 100644 src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java create mode 100644 src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java create mode 100644 src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java create mode 100644 src/test/java/redis/clients/jedis/PrefixedKeysTest.java diff --git a/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java index 01e103c4f4..a11286163e 100644 --- a/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java +++ b/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java @@ -3,14 +3,14 @@ import redis.clients.jedis.commands.ProtocolCommand; public class ClusterCommandObjectsWithPrefixedKeys extends ClusterCommandObjects { - // For the purposes of this demonstration, the prefix is assigned statically. - // Additional changes are required to prevent the parent class CommandObjects - // from calling commandArguments in its constructor, which would be a prerequisite - // to making this field into an instance field. - public static String PREFIX_STRING; + private final String prefixString; + + public ClusterCommandObjectsWithPrefixedKeys(String prefixString) { + this.prefixString = prefixString; + } @Override protected ClusterCommandArguments commandArguments(ProtocolCommand command) { - return new ClusterCommandArgumentsWithPrefixedKeys(command, PREFIX_STRING); + return new ClusterCommandArgumentsWithPrefixedKeys(command, prefixString); } } diff --git a/src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java new file mode 100644 index 0000000000..0e4d5821ba --- /dev/null +++ b/src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java @@ -0,0 +1,46 @@ +package redis.clients.jedis; + +import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.args.RawableFactory; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.SafeEncoder; + +public class CommandArgumentsWithPrefixedKeys extends CommandArguments { + private final byte[] prefix; + private final String prefixString; + + public CommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString) { + super(command); + this.prefixString = prefixString; + prefix = SafeEncoder.encode(prefixString); + } + + public CommandArguments key(Object key) { + return super.key(namespacedKey(key)); + } + + private Object namespacedKey(Object key) { + if (key instanceof Rawable) { + byte[] raw = ((Rawable) key).getRaw(); + return RawableFactory.from(namespacedKeyBytes(raw)); + } + + if (key instanceof byte[]) { + return namespacedKeyBytes((byte[]) key); + } + + if (key instanceof String) { + String raw = (String) key; + return prefixString + raw; + } + + throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); + } + + private byte[] namespacedKeyBytes(byte[] key) { + byte[] namespaced = new byte[prefix.length + key.length]; + System.arraycopy(prefix, 0, namespaced, 0, prefix.length); + System.arraycopy(key, 0, namespaced, prefix.length, key.length); + return namespaced; + } +} diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 1de7affa6a..403f479df4 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -63,22 +63,16 @@ protected CommandArguments commandArguments(ProtocolCommand command) { return new CommandArguments(command); } - private final CommandObject PING_COMMAND_OBJECT = new CommandObject<>(commandArguments(PING), BuilderFactory.STRING); - public final CommandObject ping() { - return PING_COMMAND_OBJECT; + return new CommandObject<>(commandArguments(PING), BuilderFactory.STRING); } - private final CommandObject FLUSHALL_COMMAND_OBJECT = new CommandObject<>(commandArguments(FLUSHALL), BuilderFactory.STRING); - public final CommandObject flushAll() { - return FLUSHALL_COMMAND_OBJECT; + return new CommandObject<>(commandArguments(FLUSHALL), BuilderFactory.STRING); } - private final CommandObject FLUSHDB_COMMAND_OBJECT = new CommandObject<>(commandArguments(FLUSHDB), BuilderFactory.STRING); - public final CommandObject flushDB() { - return FLUSHDB_COMMAND_OBJECT; + return new CommandObject<>(commandArguments(FLUSHDB), BuilderFactory.STRING); } public final CommandObject configSet(String parameter, String value) { @@ -2818,10 +2812,8 @@ public final CommandObject scriptLoad(String script, String sampleKey) { return new CommandObject<>(commandArguments(SCRIPT).add(LOAD).add(script).processKey(sampleKey), BuilderFactory.STRING); } - private final CommandObject SCRIPT_FLUSH_COMMAND_OBJECT = new CommandObject<>(commandArguments(SCRIPT).add(FLUSH), BuilderFactory.STRING); - public final CommandObject scriptFlush() { - return SCRIPT_FLUSH_COMMAND_OBJECT; + return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH), BuilderFactory.STRING); } public final CommandObject scriptFlush(String sampleKey) { @@ -2832,10 +2824,8 @@ public final CommandObject scriptFlush(String sampleKey, FlushMode flush return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH).add(flushMode).processKey(sampleKey), BuilderFactory.STRING); } - private final CommandObject SCRIPT_KILL_COMMAND_OBJECT = new CommandObject<>(commandArguments(SCRIPT).add(KILL), BuilderFactory.STRING); - public final CommandObject scriptKill() { - return SCRIPT_KILL_COMMAND_OBJECT; + return new CommandObject<>(commandArguments(SCRIPT).add(KILL), BuilderFactory.STRING); } public final CommandObject scriptKill(String sampleKey) { @@ -2863,11 +2853,8 @@ public final CommandObject scriptKill(byte[] sampleKey) { return new CommandObject<>(commandArguments(SCRIPT).add(KILL).processKey(sampleKey), BuilderFactory.STRING); } - private final CommandObject SLOWLOG_RESET_COMMAND_OBJECT - = new CommandObject<>(commandArguments(SLOWLOG).add(Keyword.RESET), BuilderFactory.STRING); - public final CommandObject slowlogReset() { - return SLOWLOG_RESET_COMMAND_OBJECT; + return new CommandObject<>(commandArguments(SLOWLOG).add(Keyword.RESET), BuilderFactory.STRING); } public final CommandObject fcall(String name, List keys, List args) { diff --git a/src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java new file mode 100644 index 0000000000..5af7aecbc3 --- /dev/null +++ b/src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java @@ -0,0 +1,16 @@ +package redis.clients.jedis; + +import redis.clients.jedis.commands.ProtocolCommand; + +public class CommandObjectsWithPrefixedKeys extends CommandObjects { + private final String prefixString; + + public CommandObjectsWithPrefixedKeys(String prefixString) { + this.prefixString = prefixString; + } + + @Override + protected CommandArguments commandArguments(ProtocolCommand command) { + return new CommandArgumentsWithPrefixedKeys(command, prefixString); + } +} diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 55495e6513..ff582c38b0 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -7,6 +7,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.executors.ClusterCommandExecutor; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.util.JedisClusterCRC16; @@ -222,6 +223,10 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati super(provider, maxAttempts, maxTotalRetriesDuration, protocol); } + public JedisCluster(ClusterCommandExecutor executor, ClusterConnectionProvider provider, ClusterCommandObjects commandObjects, RedisProtocol protocol) { + super(executor, provider, commandObjects, protocol); + } + public Map getClusterNodes() { return ((ClusterConnectionProvider) provider).getNodes(); } diff --git a/src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java deleted file mode 100644 index a23ca5574b..0000000000 --- a/src/main/java/redis/clients/jedis/JedisClusterWithPrefixedKeys.java +++ /dev/null @@ -1,61 +0,0 @@ -package redis.clients.jedis; - -import redis.clients.jedis.executors.ClusterCommandExecutor; -import redis.clients.jedis.providers.ClusterConnectionProvider; -import redis.clients.jedis.util.JedisClusterCRC16; - -import java.time.Duration; -import java.util.Collections; -import java.util.Map; - -public class JedisClusterWithPrefixedKeys extends UnifiedJedis { - - public JedisClusterWithPrefixedKeys(HostAndPort node, JedisClientConfig clientConfig) { - this(new ClusterConnectionProvider(Collections.singleton(node), clientConfig), clientConfig); - } - - public JedisClusterWithPrefixedKeys(ClusterConnectionProvider provider, JedisClientConfig clientConfig) { - super(new ClusterCommandExecutor(provider, 5, Duration.ofSeconds(100)), provider, new ClusterCommandObjectsWithPrefixedKeys(), clientConfig.getRedisProtocol()); - - } - - public Map getClusterNodes() { - return ((ClusterConnectionProvider) provider).getNodes(); - } - - public Connection getConnectionFromSlot(int slot) { - return ((ClusterConnectionProvider) provider).getConnectionFromSlot(slot); - } - - // commands - public long spublish(String channel, String message) { - return executeCommand(commandObjects.spublish(channel, message)); - } - - public long spublish(byte[] channel, byte[] message) { - return executeCommand(commandObjects.spublish(channel, message)); - } - - public void ssubscribe(final JedisShardedPubSub jedisPubSub, final String... channels) { - try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) { - jedisPubSub.proceed(connection, channels); - } - } - - public void ssubscribe(BinaryJedisShardedPubSub jedisPubSub, final byte[]... channels) { - try (Connection connection = getConnectionFromSlot(JedisClusterCRC16.getSlot(channels[0]))) { - jedisPubSub.proceed(connection, channels); - } - } - // commands - - @Override - public ClusterPipeline pipelined() { - return new ClusterPipeline((ClusterConnectionProvider) provider, (ClusterCommandObjects) commandObjects); - } - - @Override - public Transaction multi() { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index c6d022e094..75503ddcb2 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -8,6 +8,7 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.executors.CommandExecutor; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -394,6 +395,11 @@ public JedisPooled(PooledConnectionProvider provider) { super(provider); } + public JedisPooled(CommandExecutor executor, PooledConnectionProvider provider, CommandObjects commandObjects, + RedisProtocol redisProtocol) { + super(executor, provider, commandObjects, redisProtocol); + } + public final Pool getPool() { return ((PooledConnectionProvider) provider).getPool(); } diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index 0ea0221c1a..ff7c7a9787 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -2,6 +2,7 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.executors.CommandExecutor; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { @@ -23,6 +24,10 @@ public JedisSentineled(SentineledConnectionProvider sentineledConnectionProvider super(sentineledConnectionProvider); } + public JedisSentineled(CommandExecutor executor, SentineledConnectionProvider sentineledConnectionProvider, CommandObjects commandObjects, RedisProtocol redisProtocol) { + super(executor, sentineledConnectionProvider, commandObjects, redisProtocol); + } + public HostAndPort getCurrentMaster() { return ((SentineledConnectionProvider) provider).getCurrentMaster(); } diff --git a/src/main/java/redis/clients/jedis/Pipeline.java b/src/main/java/redis/clients/jedis/Pipeline.java index 36fab65602..dab9346747 100644 --- a/src/main/java/redis/clients/jedis/Pipeline.java +++ b/src/main/java/redis/clients/jedis/Pipeline.java @@ -28,7 +28,11 @@ public Pipeline(Connection connection) { } public Pipeline(Connection connection, boolean closeConnection) { - super(new CommandObjects()); + this(connection, new CommandObjects(), closeConnection); + } + + public Pipeline(Connection connection, CommandObjects commandObjects, boolean closeConnection) { + super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; RedisProtocol proto = this.connection.getRedisProtocol(); diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 0f5c7676d6..1468324ecc 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -4859,7 +4859,7 @@ public PipelineBase pipelined() { } else if (provider instanceof MultiClusterPooledConnectionProvider) { return new MultiClusterPipeline((MultiClusterPooledConnectionProvider) provider, commandObjects); } else { - return new Pipeline(provider.getConnection(), true); + return new Pipeline(provider.getConnection(), commandObjects, true); } } diff --git a/src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java new file mode 100644 index 0000000000..8338c96a12 --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java @@ -0,0 +1,56 @@ +package redis.clients.jedis; + +import org.junit.Before; +import redis.clients.jedis.args.ClusterResetType; +import redis.clients.jedis.executors.ClusterCommandExecutor; +import redis.clients.jedis.providers.ClusterConnectionProvider; +import redis.clients.jedis.util.JedisClusterTestUtil; + +import java.time.Duration; +import java.util.Collections; + +public class JedisClusterPrefixedKeysTest extends PrefixedKeysTest { + private static final DefaultJedisClientConfig DEFAULT_CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("cluster").build(); + private static final int DEFAULT_TIMEOUT = 2000; + private static final int DEFAULT_REDIRECTIONS = 5; + private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig(); + private static final HostAndPort HOST_AND_PORT = new HostAndPort("127.0.0.1", 7379); + + @Before + public void setUp() throws InterruptedException { + Jedis jedis = new Jedis(HOST_AND_PORT); + jedis.auth("cluster"); + jedis.clusterReset(ClusterResetType.HARD); + jedis.flushAll(); + + int[] slots = new int[Protocol.CLUSTER_HASHSLOTS]; + + for (int i = 0; i < Protocol.CLUSTER_HASHSLOTS; ++i) { + slots[i] = i; + } + + jedis.clusterAddSlots(slots); + JedisClusterTestUtil.waitForClusterReady(jedis); + } + + @Before + public void tearDown() throws InterruptedException { + Jedis jedis = new Jedis(HOST_AND_PORT); + jedis.auth("cluster"); + jedis.clusterReset(ClusterResetType.HARD); + jedis.flushAll(); + } + + @Override + public UnifiedJedis prefixingJedis() { + ClusterConnectionProvider connectionProvider = new ClusterConnectionProvider(Collections.singleton(HOST_AND_PORT), DEFAULT_CLIENT_CONFIG); + ClusterCommandExecutor executor = new ClusterCommandExecutor(connectionProvider, 5, Duration.ofSeconds(5 * DEFAULT_TIMEOUT)); + ClusterCommandObjectsWithPrefixedKeys commandObjects = new ClusterCommandObjectsWithPrefixedKeys("test-prefix:"); + return new JedisCluster(executor, connectionProvider, commandObjects, DEFAULT_CLIENT_CONFIG.getRedisProtocol()); + } + + @Override + public UnifiedJedis nonPrefixingJedis() { + return new JedisCluster(HOST_AND_PORT, DEFAULT_CLIENT_CONFIG, DEFAULT_REDIRECTIONS, DEFAULT_POOL_CONFIG); + } +} diff --git a/src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java deleted file mode 100644 index 557422cb2d..0000000000 --- a/src/test/java/redis/clients/jedis/JedisClusterWithPrefixedKeysTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package redis.clients.jedis; - -import org.junit.Test; -import redis.clients.jedis.resps.Tuple; - -import java.nio.charset.StandardCharsets; -import java.util.Collections; - -import static junit.framework.TestCase.assertEquals; - -public class JedisClusterWithPrefixedKeysTest extends JedisClusterTestBase { - private static final DefaultJedisClientConfig DEFAULT_CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("cluster").build(); - private static final int DEFAULT_TIMEOUT = 2000; - private static final int DEFAULT_REDIRECTIONS = 5; - private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig(); - - @Test - public void hasPrefixedKeys() { - HostAndPort hp = new HostAndPort("127.0.0.1", 7379); - ClusterCommandObjectsWithPrefixedKeys.PREFIX_STRING = "test-prefix:"; - - try (JedisClusterWithPrefixedKeys cluster = new JedisClusterWithPrefixedKeys(hp, DEFAULT_CLIENT_CONFIG)) { - cluster.set("foo1", "bar1"); - cluster.set("foo2".getBytes(StandardCharsets.UTF_8), "bar2".getBytes(StandardCharsets.UTF_8)); - ClusterPipeline pipeline = cluster.pipelined(); - pipeline.incr("foo3"); - pipeline.zadd("foo4", 1234, "bar4"); - pipeline.sync(); - } - - try (JedisCluster cluster = new JedisCluster(hp, DEFAULT_CLIENT_CONFIG, DEFAULT_REDIRECTIONS, DEFAULT_POOL_CONFIG)) { - assertEquals("bar1", cluster.get("test-prefix:foo1")); - assertEquals("bar2", cluster.get("test-prefix:foo2")); - assertEquals("1", cluster.get("test-prefix:foo3")); - assertEquals(new Tuple("bar4", 1234d), cluster.zpopmax("test-prefix:foo4")); - } - } -} diff --git a/src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java new file mode 100644 index 0000000000..55843b9fc6 --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java @@ -0,0 +1,20 @@ +package redis.clients.jedis; + +import redis.clients.jedis.executors.DefaultCommandExecutor; +import redis.clients.jedis.providers.PooledConnectionProvider; + +public class JedisPooledPrefixedKeysTest extends PrefixedKeysTest { + private final HostAndPort hostAndPort = HostAndPorts.getRedisServers().get(7); + + @Override + UnifiedJedis prefixingJedis() { + PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); + return new JedisPooled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); + } + + @Override + UnifiedJedis nonPrefixingJedis() { + PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); + return new JedisPooled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjects(), RedisProtocol.RESP3); + } +} diff --git a/src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java new file mode 100644 index 0000000000..658a20f10e --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java @@ -0,0 +1,25 @@ +package redis.clients.jedis; + +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.executors.DefaultCommandExecutor; +import redis.clients.jedis.providers.SentineledConnectionProvider; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class JedisSentineledPrefixedKeysTest extends PrefixedKeysTest { + private final Set sentinels = new HashSet<>(Arrays.asList( HostAndPorts.getSentinelServers().get(1), HostAndPorts.getSentinelServers().get(3))); + + @Override + UnifiedJedis prefixingJedis() { + SentineledConnectionProvider connectionProvider = new SentineledConnectionProvider("mymaster", DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(), new GenericObjectPoolConfig<>(), sentinels, DefaultJedisClientConfig.builder().build()); + return new JedisSentineled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); + } + + @Override + UnifiedJedis nonPrefixingJedis() { + SentineledConnectionProvider connectionProvider = new SentineledConnectionProvider("mymaster", DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(), new GenericObjectPoolConfig<>(), sentinels, DefaultJedisClientConfig.builder().build()); + return new JedisSentineled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjects(), RedisProtocol.RESP3); + } +} diff --git a/src/test/java/redis/clients/jedis/PrefixedKeysTest.java b/src/test/java/redis/clients/jedis/PrefixedKeysTest.java new file mode 100644 index 0000000000..ca2c56897a --- /dev/null +++ b/src/test/java/redis/clients/jedis/PrefixedKeysTest.java @@ -0,0 +1,36 @@ +package redis.clients.jedis; + +import org.junit.Test; +import redis.clients.jedis.resps.Tuple; + +import java.nio.charset.StandardCharsets; + +import static junit.framework.TestCase.assertEquals; + +public abstract class PrefixedKeysTest { + + abstract UnifiedJedis prefixingJedis(); + + abstract UnifiedJedis nonPrefixingJedis(); + + @Test + public void hasPrefixedKeys() { + HostAndPort hp = new HostAndPort("127.0.0.1", 7379); + + try (UnifiedJedis jedis = prefixingJedis()) { + jedis.set("foo1", "bar1"); + jedis.set("foo2".getBytes(StandardCharsets.UTF_8), "bar2".getBytes(StandardCharsets.UTF_8)); + AbstractPipeline pipeline = jedis.pipelined(); + pipeline.incr("foo3"); + pipeline.zadd("foo4", 1234, "bar4"); + pipeline.sync(); + } + + try (UnifiedJedis jedis = nonPrefixingJedis()) { + assertEquals("bar1", jedis.get("test-prefix:foo1")); + assertEquals("bar2", jedis.get("test-prefix:foo2")); + assertEquals("1", jedis.get("test-prefix:foo3")); + assertEquals(new Tuple("bar4", 1234d), jedis.zpopmax("test-prefix:foo4")); + } + } +} From 7098377295c7b52d564d50c69a0619a4553de835 Mon Sep 17 00:00:00 2001 From: R-J Lim Date: Fri, 15 Mar 2024 18:03:01 -0700 Subject: [PATCH 03/12] Second iteration on key-prefixing POC - Restore cached key-less commands in CommandObjects - Support Transactions - New constructors do not take CommandExecutor - Requested JavaDoc regarding new constructors specifying RedisProtocol - New classes moved into 'prefix' packages - De-duplicate prefixing code --- ...usterCommandArgumentsWithPrefixedKeys.java | 46 ------------ .../CommandArgumentsWithPrefixedKeys.java | 46 ------------ .../redis/clients/jedis/CommandObjects.java | 35 ++++++++-- .../redis/clients/jedis/JedisCluster.java | 16 ++++- .../java/redis/clients/jedis/JedisPooled.java | 13 +++- .../redis/clients/jedis/JedisSentineled.java | 13 +++- .../java/redis/clients/jedis/Pipeline.java | 15 ++-- .../java/redis/clients/jedis/Transaction.java | 13 ++-- .../redis/clients/jedis/UnifiedJedis.java | 2 +- .../jedis/mcf/MultiClusterTransaction.java | 4 +- ...usterCommandArgumentsWithPrefixedKeys.java | 20 ++++++ ...ClusterCommandObjectsWithPrefixedKeys.java | 9 ++- .../CommandArgumentsWithPrefixedKeys.java | 19 +++++ .../CommandObjectsWithPrefixedKeys.java | 9 ++- .../clients/jedis/util/prefix/Prefixer.java | 34 +++++++++ .../jedis/JedisPooledPrefixedKeysTest.java | 20 ------ .../redis/clients/jedis/PrefixedKeysTest.java | 36 ---------- .../JedisClusterPrefixedKeysTest.java | 41 +++++++---- .../prefix/JedisPooledPrefixedKeysTest.java | 26 +++++++ .../JedisSentineledPrefixedKeysTest.java | 14 +++- .../jedis/prefix/PrefixedKeysTest.java | 70 +++++++++++++++++++ 21 files changed, 307 insertions(+), 194 deletions(-) delete mode 100644 src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java delete mode 100644 src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java create mode 100644 src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java rename src/main/java/redis/clients/jedis/{ => util/prefix}/ClusterCommandObjectsWithPrefixedKeys.java (59%) create mode 100644 src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java rename src/main/java/redis/clients/jedis/{ => util/prefix}/CommandObjectsWithPrefixedKeys.java (59%) create mode 100644 src/main/java/redis/clients/jedis/util/prefix/Prefixer.java delete mode 100644 src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java delete mode 100644 src/test/java/redis/clients/jedis/PrefixedKeysTest.java rename src/test/java/redis/clients/jedis/{ => prefix}/JedisClusterPrefixedKeysTest.java (55%) create mode 100644 src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java rename src/test/java/redis/clients/jedis/{ => prefix}/JedisSentineledPrefixedKeysTest.java (65%) create mode 100644 src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java diff --git a/src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java deleted file mode 100644 index 63a7905585..0000000000 --- a/src/main/java/redis/clients/jedis/ClusterCommandArgumentsWithPrefixedKeys.java +++ /dev/null @@ -1,46 +0,0 @@ -package redis.clients.jedis; - -import redis.clients.jedis.args.Rawable; -import redis.clients.jedis.args.RawableFactory; -import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.util.SafeEncoder; - -public class ClusterCommandArgumentsWithPrefixedKeys extends ClusterCommandArguments { - private final byte[] prefix; - private final String prefixString; - - public ClusterCommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString) { - super(command); - this.prefixString = prefixString; - prefix = SafeEncoder.encode(prefixString); - } - - public CommandArguments key(Object key) { - return super.key(namespacedKey(key)); - } - - private Object namespacedKey(Object key) { - if (key instanceof Rawable) { - byte[] raw = ((Rawable) key).getRaw(); - return RawableFactory.from(namespacedKeyBytes(raw)); - } - - if (key instanceof byte[]) { - return namespacedKeyBytes((byte[]) key); - } - - if (key instanceof String) { - String raw = (String) key; - return prefixString + raw; - } - - throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); - } - - private byte[] namespacedKeyBytes(byte[] key) { - byte[] namespaced = new byte[prefix.length + key.length]; - System.arraycopy(prefix, 0, namespaced, 0, prefix.length); - System.arraycopy(key, 0, namespaced, prefix.length, key.length); - return namespaced; - } -} diff --git a/src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java deleted file mode 100644 index 0e4d5821ba..0000000000 --- a/src/main/java/redis/clients/jedis/CommandArgumentsWithPrefixedKeys.java +++ /dev/null @@ -1,46 +0,0 @@ -package redis.clients.jedis; - -import redis.clients.jedis.args.Rawable; -import redis.clients.jedis.args.RawableFactory; -import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.util.SafeEncoder; - -public class CommandArgumentsWithPrefixedKeys extends CommandArguments { - private final byte[] prefix; - private final String prefixString; - - public CommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString) { - super(command); - this.prefixString = prefixString; - prefix = SafeEncoder.encode(prefixString); - } - - public CommandArguments key(Object key) { - return super.key(namespacedKey(key)); - } - - private Object namespacedKey(Object key) { - if (key instanceof Rawable) { - byte[] raw = ((Rawable) key).getRaw(); - return RawableFactory.from(namespacedKeyBytes(raw)); - } - - if (key instanceof byte[]) { - return namespacedKeyBytes((byte[]) key); - } - - if (key instanceof String) { - String raw = (String) key; - return prefixString + raw; - } - - throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); - } - - private byte[] namespacedKeyBytes(byte[] key) { - byte[] namespaced = new byte[prefix.length + key.length]; - System.arraycopy(prefix, 0, namespaced, 0, prefix.length); - System.arraycopy(key, 0, namespaced, prefix.length, key.length); - return namespaced; - } -} diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 403f479df4..c2fb1d72d8 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -63,16 +63,22 @@ protected CommandArguments commandArguments(ProtocolCommand command) { return new CommandArguments(command); } + private final CommandObject PING_COMMAND_OBJECT = new CommandObject<>(commandArguments(PING), BuilderFactory.STRING); + public final CommandObject ping() { - return new CommandObject<>(commandArguments(PING), BuilderFactory.STRING); + return PING_COMMAND_OBJECT; } + private final CommandObject FLUSHALL_COMMAND_OBJECT = new CommandObject<>(commandArguments(FLUSHALL), BuilderFactory.STRING); + public final CommandObject flushAll() { - return new CommandObject<>(commandArguments(FLUSHALL), BuilderFactory.STRING); + return FLUSHALL_COMMAND_OBJECT; } + private final CommandObject FLUSHDB_COMMAND_OBJECT = new CommandObject<>(commandArguments(FLUSHDB), BuilderFactory.STRING); + public final CommandObject flushDB() { - return new CommandObject<>(commandArguments(FLUSHDB), BuilderFactory.STRING); + return FLUSHDB_COMMAND_OBJECT; } public final CommandObject configSet(String parameter, String value) { @@ -2812,8 +2818,10 @@ public final CommandObject scriptLoad(String script, String sampleKey) { return new CommandObject<>(commandArguments(SCRIPT).add(LOAD).add(script).processKey(sampleKey), BuilderFactory.STRING); } + private final CommandObject SCRIPT_FLUSH_COMMAND_OBJECT = new CommandObject<>(commandArguments(SCRIPT).add(FLUSH), BuilderFactory.STRING); + public final CommandObject scriptFlush() { - return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH), BuilderFactory.STRING); + return SCRIPT_FLUSH_COMMAND_OBJECT; } public final CommandObject scriptFlush(String sampleKey) { @@ -2824,8 +2832,10 @@ public final CommandObject scriptFlush(String sampleKey, FlushMode flush return new CommandObject<>(commandArguments(SCRIPT).add(FLUSH).add(flushMode).processKey(sampleKey), BuilderFactory.STRING); } + private final CommandObject SCRIPT_KILL_COMMAND_OBJECT = new CommandObject<>(commandArguments(SCRIPT).add(KILL), BuilderFactory.STRING); + public final CommandObject scriptKill() { - return new CommandObject<>(commandArguments(SCRIPT).add(KILL), BuilderFactory.STRING); + return SCRIPT_KILL_COMMAND_OBJECT; } public final CommandObject scriptKill(String sampleKey) { @@ -2853,8 +2863,11 @@ public final CommandObject scriptKill(byte[] sampleKey) { return new CommandObject<>(commandArguments(SCRIPT).add(KILL).processKey(sampleKey), BuilderFactory.STRING); } + private final CommandObject SLOWLOG_RESET_COMMAND_OBJECT + = new CommandObject<>(commandArguments(SLOWLOG).add(Keyword.RESET), BuilderFactory.STRING); + public final CommandObject slowlogReset() { - return new CommandObject<>(commandArguments(SLOWLOG).add(Keyword.RESET), BuilderFactory.STRING); + return SLOWLOG_RESET_COMMAND_OBJECT; } public final CommandObject fcall(String name, List keys, List args) { @@ -4245,6 +4258,16 @@ public final CommandObject tFunctionCallAsync(String library, String fun } // RedisGears commands + // Transaction commands + public final CommandObject watch(String... keys) { + return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.RAW_OBJECT); + } + + public final CommandObject watch(byte[]... keys) { + return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.RAW_OBJECT); + } + // Transaction commands + /** * Get the instance for JsonObjectMapper if not null, otherwise a new instance reference with * default implementation will be created and returned. diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index ff582c38b0..f57c4b46f4 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -223,8 +223,20 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati super(provider, maxAttempts, maxTotalRetriesDuration, protocol); } - public JedisCluster(ClusterCommandExecutor executor, ClusterConnectionProvider provider, ClusterCommandObjects commandObjects, RedisProtocol protocol) { - super(executor, provider, commandObjects, protocol); + /** + * Constructor that allows CommandObjects to be customized. The RedisProtocol specified will be written into the given + * CommandObjects. + * + * @param provider The ClusterConnectionProvider. + * @param maxAttempts Max number of attempts execute a command. + * @param maxTotalRetriesDuration Max amount of time to execute a command. + * @param commandObjects The CommandObjects. + * @param protocol The RedisProtocol that will be written into the given CommandObjects. + */ + public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, + ClusterCommandObjects commandObjects, RedisProtocol protocol) { + super(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, commandObjects, + protocol); } public Map getClusterNodes() { diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 75503ddcb2..49476acee7 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -9,6 +9,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.executors.CommandExecutor; +import redis.clients.jedis.executors.DefaultCommandExecutor; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -395,9 +396,17 @@ public JedisPooled(PooledConnectionProvider provider) { super(provider); } - public JedisPooled(CommandExecutor executor, PooledConnectionProvider provider, CommandObjects commandObjects, + /** + * Constructor that allows CommandObjects to be customized. The RedisProtocol specified will be written into the given + * CommandObjects. + * + * @param provider The PooledConnectionProvider. + * @param commandObjects The CommandObjects. + * @param redisProtocol The RedisProtocol that will be written into the given CommandObjects. + */ + public JedisPooled(PooledConnectionProvider provider, CommandObjects commandObjects, RedisProtocol redisProtocol) { - super(executor, provider, commandObjects, redisProtocol); + super(new DefaultCommandExecutor(provider), provider, commandObjects, redisProtocol); } public final Pool getPool() { diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index ff7c7a9787..4b17cece9e 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -3,6 +3,7 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.executors.CommandExecutor; +import redis.clients.jedis.executors.DefaultCommandExecutor; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { @@ -24,8 +25,16 @@ public JedisSentineled(SentineledConnectionProvider sentineledConnectionProvider super(sentineledConnectionProvider); } - public JedisSentineled(CommandExecutor executor, SentineledConnectionProvider sentineledConnectionProvider, CommandObjects commandObjects, RedisProtocol redisProtocol) { - super(executor, sentineledConnectionProvider, commandObjects, redisProtocol); + /** + * Constructor that allows CommandObjects to be customized. The RedisProtocol specified will be written into the given + * CommandObjects. + * + * @param provider The SentineledConnectionProvider. + * @param commandObjects The CommandObjects. + * @param redisProtocol The RedisProtocol that will be written into the given CommandObjects. + */ + public JedisSentineled(SentineledConnectionProvider provider, CommandObjects commandObjects, RedisProtocol redisProtocol) { + super(new DefaultCommandExecutor(provider), provider, commandObjects, redisProtocol); } public HostAndPort getCurrentMaster() { diff --git a/src/main/java/redis/clients/jedis/Pipeline.java b/src/main/java/redis/clients/jedis/Pipeline.java index dab9346747..5b195c2425 100644 --- a/src/main/java/redis/clients/jedis/Pipeline.java +++ b/src/main/java/redis/clients/jedis/Pipeline.java @@ -28,16 +28,23 @@ public Pipeline(Connection connection) { } public Pipeline(Connection connection, boolean closeConnection) { - this(connection, new CommandObjects(), closeConnection); + this(connection, commandObjects(connection), closeConnection); + } + + private static CommandObjects commandObjects(Connection connection) { + RedisProtocol proto = connection.getRedisProtocol(); + CommandObjects commandObjects = new CommandObjects(); + if (proto != null) commandObjects.setProtocol(proto); + return commandObjects; } public Pipeline(Connection connection, CommandObjects commandObjects, boolean closeConnection) { super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; - RedisProtocol proto = this.connection.getRedisProtocol(); - if (proto != null) this.commandObjects.setProtocol(proto); - setGraphCommands(new GraphCommandObjects(this.connection)); + GraphCommandObjects graphCommandObjects = new GraphCommandObjects(this.connection); + graphCommandObjects.setBaseCommandArgumentsCreator(protocolCommand -> commandObjects.commandArguments(protocolCommand)); + setGraphCommands(graphCommandObjects); } @Override diff --git a/src/main/java/redis/clients/jedis/Transaction.java b/src/main/java/redis/clients/jedis/Transaction.java index 0dccd655a0..b77a4f57f0 100644 --- a/src/main/java/redis/clients/jedis/Transaction.java +++ b/src/main/java/redis/clients/jedis/Transaction.java @@ -59,7 +59,7 @@ public Transaction(Connection connection) { * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI */ public Transaction(Connection connection, boolean doMulti) { - this(connection, doMulti, false); + this(connection, new CommandObjects(), doMulti, false); } /** @@ -72,10 +72,13 @@ public Transaction(Connection connection, boolean doMulti) { * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI * @param closeConnection should the 'connection' be closed when 'close()' is called? */ - public Transaction(Connection connection, boolean doMulti, boolean closeConnection) { + public Transaction(Connection connection, CommandObjects commandObjects, boolean doMulti, boolean closeConnection) { + super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; - setGraphCommands(new GraphCommandObjects(this.connection)); + GraphCommandObjects graphCommandObjects = new GraphCommandObjects(this.connection); + graphCommandObjects.setBaseCommandArgumentsCreator(protocolCommand -> commandObjects.commandArguments(protocolCommand)); + setGraphCommands(graphCommandObjects); if (doMulti) multi(); } @@ -88,7 +91,7 @@ public final void multi() { @Override public String watch(final String... keys) { - connection.sendCommand(WATCH, keys); + connection.sendCommand(commandObjects.watch(keys).getArguments()); String status = connection.getStatusCodeReply(); inWatch = true; return status; @@ -96,7 +99,7 @@ public String watch(final String... keys) { @Override public String watch(final byte[]... keys) { - connection.sendCommand(WATCH, keys); + connection.sendCommand(commandObjects.watch(keys).getArguments()); String status = connection.getStatusCodeReply(); inWatch = true; return status; diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 1468324ecc..c9c990e3ef 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -4869,7 +4869,7 @@ public AbstractTransaction multi() { } else if (provider instanceof MultiClusterPooledConnectionProvider) { return new MultiClusterTransaction((MultiClusterPooledConnectionProvider) provider, true, commandObjects); } else { - return new Transaction(provider.getConnection(), true, true); + return new Transaction(provider.getConnection(), commandObjects, true, true); } } diff --git a/src/main/java/redis/clients/jedis/mcf/MultiClusterTransaction.java b/src/main/java/redis/clients/jedis/mcf/MultiClusterTransaction.java index d759ce1da0..d73dfd7c86 100644 --- a/src/main/java/redis/clients/jedis/mcf/MultiClusterTransaction.java +++ b/src/main/java/redis/clients/jedis/mcf/MultiClusterTransaction.java @@ -90,7 +90,7 @@ public final void multi() { */ @Override public final String watch(String... keys) { - appendCommand(new CommandObject<>(new CommandArguments(WATCH).addObjects((Object[]) keys), NO_OP_BUILDER)); + appendCommand(commandObjects.watch(keys)); extraCommandCount.incrementAndGet(); inWatch = true; return null; @@ -102,7 +102,7 @@ public final String watch(String... keys) { */ @Override public final String watch(byte[]... keys) { - appendCommand(new CommandObject<>(new CommandArguments(WATCH).addObjects((Object[]) keys), NO_OP_BUILDER)); + appendCommand(commandObjects.watch(keys)); extraCommandCount.incrementAndGet(); inWatch = true; return null; diff --git a/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java new file mode 100644 index 0000000000..619eff2094 --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java @@ -0,0 +1,20 @@ +package redis.clients.jedis.util.prefix; + +import redis.clients.jedis.ClusterCommandArguments; +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.commands.ProtocolCommand; + +public class ClusterCommandArgumentsWithPrefixedKeys extends ClusterCommandArguments { + private final byte[] prefixBytes; + private final String prefixString; + + public ClusterCommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString, byte[] prefixBytes) { + super(command); + this.prefixString = prefixString; + this.prefixBytes = prefixBytes; + } + + public CommandArguments key(Object key) { + return super.key(Prefixer.prefixKey(key, prefixString, prefixBytes)); + } +} diff --git a/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java similarity index 59% rename from src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java rename to src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java index a11286163e..3f8e2bb8a4 100644 --- a/src/main/java/redis/clients/jedis/ClusterCommandObjectsWithPrefixedKeys.java +++ b/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java @@ -1,16 +1,21 @@ -package redis.clients.jedis; +package redis.clients.jedis.util.prefix; +import redis.clients.jedis.ClusterCommandArguments; +import redis.clients.jedis.ClusterCommandObjects; import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.SafeEncoder; public class ClusterCommandObjectsWithPrefixedKeys extends ClusterCommandObjects { private final String prefixString; + private final byte[] prefixBytes; public ClusterCommandObjectsWithPrefixedKeys(String prefixString) { this.prefixString = prefixString; + prefixBytes = SafeEncoder.encode(prefixString); } @Override protected ClusterCommandArguments commandArguments(ProtocolCommand command) { - return new ClusterCommandArgumentsWithPrefixedKeys(command, prefixString); + return new ClusterCommandArgumentsWithPrefixedKeys(command, prefixString, prefixBytes); } } diff --git a/src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java new file mode 100644 index 0000000000..5353c9d6d9 --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java @@ -0,0 +1,19 @@ +package redis.clients.jedis.util.prefix; + +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.commands.ProtocolCommand; + +public class CommandArgumentsWithPrefixedKeys extends CommandArguments { + private final byte[] prefixBytes; + private final String prefixString; + + public CommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString, byte[] prefixBytes) { + super(command); + this.prefixString = prefixString; + this.prefixBytes = prefixBytes; + } + + public CommandArguments key(Object key) { + return super.key(Prefixer.prefixKey(key, prefixString, prefixBytes)); + } +} diff --git a/src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java similarity index 59% rename from src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java rename to src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java index 5af7aecbc3..8a3aef40b4 100644 --- a/src/main/java/redis/clients/jedis/CommandObjectsWithPrefixedKeys.java +++ b/src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java @@ -1,16 +1,21 @@ -package redis.clients.jedis; +package redis.clients.jedis.util.prefix; +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.CommandObjects; import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.SafeEncoder; public class CommandObjectsWithPrefixedKeys extends CommandObjects { private final String prefixString; + private final byte[] prefixBytes; public CommandObjectsWithPrefixedKeys(String prefixString) { this.prefixString = prefixString; + prefixBytes = SafeEncoder.encode(prefixString); } @Override protected CommandArguments commandArguments(ProtocolCommand command) { - return new CommandArgumentsWithPrefixedKeys(command, prefixString); + return new CommandArgumentsWithPrefixedKeys(command, prefixString, prefixBytes); } } diff --git a/src/main/java/redis/clients/jedis/util/prefix/Prefixer.java b/src/main/java/redis/clients/jedis/util/prefix/Prefixer.java new file mode 100644 index 0000000000..cf9b66522b --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/prefix/Prefixer.java @@ -0,0 +1,34 @@ +package redis.clients.jedis.util.prefix; + +import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.args.RawableFactory; + +final class Prefixer { + private Prefixer() { + } + + static Object prefixKey(Object key, String prefixString, byte[] prefixBytes) { + if (key instanceof Rawable) { + byte[] raw = ((Rawable) key).getRaw(); + return RawableFactory.from(prefixKeyWithBytes(raw, prefixBytes)); + } + + if (key instanceof byte[]) { + return prefixKeyWithBytes((byte[]) key, prefixBytes); + } + + if (key instanceof String) { + String raw = (String) key; + return prefixString + raw; + } + + throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); + } + + private static byte[] prefixKeyWithBytes(byte[] key, byte[] prefixBytes) { + byte[] namespaced = new byte[prefixBytes.length + key.length]; + System.arraycopy(prefixBytes, 0, namespaced, 0, prefixBytes.length); + System.arraycopy(key, 0, namespaced, prefixBytes.length, key.length); + return namespaced; + } +} diff --git a/src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java deleted file mode 100644 index 55843b9fc6..0000000000 --- a/src/test/java/redis/clients/jedis/JedisPooledPrefixedKeysTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package redis.clients.jedis; - -import redis.clients.jedis.executors.DefaultCommandExecutor; -import redis.clients.jedis.providers.PooledConnectionProvider; - -public class JedisPooledPrefixedKeysTest extends PrefixedKeysTest { - private final HostAndPort hostAndPort = HostAndPorts.getRedisServers().get(7); - - @Override - UnifiedJedis prefixingJedis() { - PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); - return new JedisPooled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); - } - - @Override - UnifiedJedis nonPrefixingJedis() { - PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); - return new JedisPooled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjects(), RedisProtocol.RESP3); - } -} diff --git a/src/test/java/redis/clients/jedis/PrefixedKeysTest.java b/src/test/java/redis/clients/jedis/PrefixedKeysTest.java deleted file mode 100644 index ca2c56897a..0000000000 --- a/src/test/java/redis/clients/jedis/PrefixedKeysTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package redis.clients.jedis; - -import org.junit.Test; -import redis.clients.jedis.resps.Tuple; - -import java.nio.charset.StandardCharsets; - -import static junit.framework.TestCase.assertEquals; - -public abstract class PrefixedKeysTest { - - abstract UnifiedJedis prefixingJedis(); - - abstract UnifiedJedis nonPrefixingJedis(); - - @Test - public void hasPrefixedKeys() { - HostAndPort hp = new HostAndPort("127.0.0.1", 7379); - - try (UnifiedJedis jedis = prefixingJedis()) { - jedis.set("foo1", "bar1"); - jedis.set("foo2".getBytes(StandardCharsets.UTF_8), "bar2".getBytes(StandardCharsets.UTF_8)); - AbstractPipeline pipeline = jedis.pipelined(); - pipeline.incr("foo3"); - pipeline.zadd("foo4", 1234, "bar4"); - pipeline.sync(); - } - - try (UnifiedJedis jedis = nonPrefixingJedis()) { - assertEquals("bar1", jedis.get("test-prefix:foo1")); - assertEquals("bar2", jedis.get("test-prefix:foo2")); - assertEquals("1", jedis.get("test-prefix:foo3")); - assertEquals(new Tuple("bar4", 1234d), jedis.zpopmax("test-prefix:foo4")); - } - } -} diff --git a/src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java similarity index 55% rename from src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java rename to src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java index 8338c96a12..7713f5606e 100644 --- a/src/test/java/redis/clients/jedis/JedisClusterPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java @@ -1,8 +1,16 @@ -package redis.clients.jedis; +package redis.clients.jedis.prefix; -import org.junit.Before; +import org.junit.BeforeClass; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.util.prefix.ClusterCommandObjectsWithPrefixedKeys; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisCluster; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.args.ClusterResetType; -import redis.clients.jedis.executors.ClusterCommandExecutor; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.util.JedisClusterTestUtil; @@ -14,10 +22,10 @@ public class JedisClusterPrefixedKeysTest extends PrefixedKeysTest { private static final int DEFAULT_TIMEOUT = 2000; private static final int DEFAULT_REDIRECTIONS = 5; private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig(); - private static final HostAndPort HOST_AND_PORT = new HostAndPort("127.0.0.1", 7379); + private static final HostAndPort HOST_AND_PORT = HostAndPorts.getClusterServers().get(0); - @Before - public void setUp() throws InterruptedException { + @BeforeClass + public static void setUpClass() { Jedis jedis = new Jedis(HOST_AND_PORT); jedis.auth("cluster"); jedis.clusterReset(ClusterResetType.HARD); @@ -30,23 +38,26 @@ public void setUp() throws InterruptedException { } jedis.clusterAddSlots(slots); - JedisClusterTestUtil.waitForClusterReady(jedis); + + try { + JedisClusterTestUtil.waitForClusterReady(jedis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } - @Before - public void tearDown() throws InterruptedException { - Jedis jedis = new Jedis(HOST_AND_PORT); - jedis.auth("cluster"); - jedis.clusterReset(ClusterResetType.HARD); - jedis.flushAll(); + @Override + public void prefixesKeysInTransaction() { + // Transactions are not supported by JedisCluster, so override this test to no-op } @Override public UnifiedJedis prefixingJedis() { ClusterConnectionProvider connectionProvider = new ClusterConnectionProvider(Collections.singleton(HOST_AND_PORT), DEFAULT_CLIENT_CONFIG); - ClusterCommandExecutor executor = new ClusterCommandExecutor(connectionProvider, 5, Duration.ofSeconds(5 * DEFAULT_TIMEOUT)); + int maxAttempts = 5; + Duration maxTotalRetriesDuration = Duration.ofSeconds(5 * DEFAULT_TIMEOUT); ClusterCommandObjectsWithPrefixedKeys commandObjects = new ClusterCommandObjectsWithPrefixedKeys("test-prefix:"); - return new JedisCluster(executor, connectionProvider, commandObjects, DEFAULT_CLIENT_CONFIG.getRedisProtocol()); + return new JedisCluster(connectionProvider, maxAttempts, maxTotalRetriesDuration, commandObjects, DEFAULT_CLIENT_CONFIG.getRedisProtocol()); } @Override diff --git a/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java new file mode 100644 index 0000000000..74422e45b9 --- /dev/null +++ b/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java @@ -0,0 +1,26 @@ +package redis.clients.jedis.prefix; + +import redis.clients.jedis.CommandObjects; +import redis.clients.jedis.util.prefix.CommandObjectsWithPrefixedKeys; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.providers.PooledConnectionProvider; + +public class JedisPooledPrefixedKeysTest extends PrefixedKeysTest { + private final HostAndPort hostAndPort = HostAndPorts.getRedisServers().get(7); + + @Override + UnifiedJedis prefixingJedis() { + PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); + return new JedisPooled(connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); + } + + @Override + UnifiedJedis nonPrefixingJedis() { + PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); + return new JedisPooled(connectionProvider, new CommandObjects(), RedisProtocol.RESP3); + } +} diff --git a/src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java similarity index 65% rename from src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java rename to src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java index 658a20f10e..33db85514f 100644 --- a/src/test/java/redis/clients/jedis/JedisSentineledPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java @@ -1,6 +1,14 @@ -package redis.clients.jedis; +package redis.clients.jedis.prefix; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.CommandObjects; +import redis.clients.jedis.util.prefix.CommandObjectsWithPrefixedKeys; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.JedisSentineled; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.executors.DefaultCommandExecutor; import redis.clients.jedis.providers.SentineledConnectionProvider; @@ -14,12 +22,12 @@ public class JedisSentineledPrefixedKeysTest extends PrefixedKeysTest { @Override UnifiedJedis prefixingJedis() { SentineledConnectionProvider connectionProvider = new SentineledConnectionProvider("mymaster", DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(), new GenericObjectPoolConfig<>(), sentinels, DefaultJedisClientConfig.builder().build()); - return new JedisSentineled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); + return new JedisSentineled(connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); } @Override UnifiedJedis nonPrefixingJedis() { SentineledConnectionProvider connectionProvider = new SentineledConnectionProvider("mymaster", DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(), new GenericObjectPoolConfig<>(), sentinels, DefaultJedisClientConfig.builder().build()); - return new JedisSentineled(new DefaultCommandExecutor(connectionProvider), connectionProvider, new CommandObjects(), RedisProtocol.RESP3); + return new JedisSentineled(connectionProvider, new CommandObjects(), RedisProtocol.RESP3); } } diff --git a/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java new file mode 100644 index 0000000000..8e21a0ab7b --- /dev/null +++ b/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java @@ -0,0 +1,70 @@ +package redis.clients.jedis.prefix; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import redis.clients.jedis.AbstractPipeline; +import redis.clients.jedis.AbstractTransaction; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.resps.Tuple; + +import java.nio.charset.StandardCharsets; + +import static junit.framework.TestCase.assertEquals; + +public abstract class PrefixedKeysTest { + + abstract UnifiedJedis prefixingJedis(); + + abstract UnifiedJedis nonPrefixingJedis(); + + private void flush() { + try (UnifiedJedis jedis = prefixingJedis()) { + jedis.flushAll(); + } + } + + @Before + public void setUp() { + flush(); + } + + @After + public void tearDown() { + flush(); + } + + @Test + public void prefixesKeys() { + try (UnifiedJedis jedis = prefixingJedis()) { + jedis.set("foo1", "bar1"); + jedis.set("foo2".getBytes(StandardCharsets.UTF_8), "bar2".getBytes(StandardCharsets.UTF_8)); + AbstractPipeline pipeline = jedis.pipelined(); + pipeline.incr("foo3"); + pipeline.zadd("foo4", 1234, "bar4"); + pipeline.sync(); + } + + try (UnifiedJedis jedis = nonPrefixingJedis()) { + assertEquals("bar1", jedis.get("test-prefix:foo1")); + assertEquals("bar2", jedis.get("test-prefix:foo2")); + assertEquals("1", jedis.get("test-prefix:foo3")); + assertEquals(new Tuple("bar4", 1234d), jedis.zpopmax("test-prefix:foo4")); + } + } + + @Test + public void prefixesKeysInTransaction() { + try (UnifiedJedis jedis = prefixingJedis()) { + AbstractTransaction transaction = jedis.multi(); + transaction.set("foo1", "bar1-from-transaction"); + transaction.hset("foo2", "bar2-key", "bar2-value"); + transaction.exec(); + } + + try (UnifiedJedis jedis = nonPrefixingJedis()) { + assertEquals("bar1-from-transaction", jedis.get("test-prefix:foo1")); + assertEquals("bar2-value", jedis.hget("test-prefix:foo2", "bar2-key")); + } + } +} From 02b8ba97e801578683552413cccbd33dcb5e882e Mon Sep 17 00:00:00 2001 From: R-J Lim Date: Sun, 17 Mar 2024 15:38:17 -0700 Subject: [PATCH 04/12] Address code review comments - Restore public Transaction constructor that was removed - Use Connection.executeCommand instead of Connection.sendCommand --- .../redis/clients/jedis/CommandObjects.java | 8 +++---- .../java/redis/clients/jedis/Transaction.java | 22 +++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index c2fb1d72d8..5a2957bc2d 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -4259,12 +4259,12 @@ public final CommandObject tFunctionCallAsync(String library, String fun // RedisGears commands // Transaction commands - public final CommandObject watch(String... keys) { - return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.RAW_OBJECT); + public final CommandObject watch(String... keys) { + return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.STRING); } - public final CommandObject watch(byte[]... keys) { - return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.RAW_OBJECT); + public final CommandObject watch(byte[]... keys) { + return new CommandObject<>(commandArguments(WATCH).keys((Object[]) keys), BuilderFactory.STRING); } // Transaction commands diff --git a/src/main/java/redis/clients/jedis/Transaction.java b/src/main/java/redis/clients/jedis/Transaction.java index b77a4f57f0..ab67f3dc0d 100644 --- a/src/main/java/redis/clients/jedis/Transaction.java +++ b/src/main/java/redis/clients/jedis/Transaction.java @@ -62,6 +62,7 @@ public Transaction(Connection connection, boolean doMulti) { this(connection, new CommandObjects(), doMulti, false); } + /** * Creates a new transaction. * @@ -72,6 +73,21 @@ public Transaction(Connection connection, boolean doMulti) { * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI * @param closeConnection should the 'connection' be closed when 'close()' is called? */ + public Transaction(Connection connection, boolean doMulti, boolean closeConnection) { + this(connection, new CommandObjects(), doMulti, closeConnection); + } + + /** + * Creates a new transaction. + * + * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should + * be {@code doMulti=false}. + * + * @param connection connection + * @param commandObjects commandObjects + * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI + * @param closeConnection should the 'connection' be closed when 'close()' is called? + */ public Transaction(Connection connection, CommandObjects commandObjects, boolean doMulti, boolean closeConnection) { super(commandObjects); this.connection = connection; @@ -91,16 +107,14 @@ public final void multi() { @Override public String watch(final String... keys) { - connection.sendCommand(commandObjects.watch(keys).getArguments()); - String status = connection.getStatusCodeReply(); + String status = connection.executeCommand(commandObjects.watch(keys)); inWatch = true; return status; } @Override public String watch(final byte[]... keys) { - connection.sendCommand(commandObjects.watch(keys).getArguments()); - String status = connection.getStatusCodeReply(); + String status = connection.executeCommand(commandObjects.watch(keys)); inWatch = true; return status; } From 66f63364fa7842b068b7f9312faaae6fd3ef6933 Mon Sep 17 00:00:00 2001 From: R-J Lim Date: Mon, 18 Mar 2024 14:48:47 -0700 Subject: [PATCH 05/12] Attempt to fix cluster cleanup before/after prefix test --- .../prefix/JedisClusterPrefixedKeysTest.java | 30 ++++--------------- .../jedis/prefix/PrefixedKeysTest.java | 2 +- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java index 7713f5606e..4b81f9b6e2 100644 --- a/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java @@ -1,6 +1,5 @@ package redis.clients.jedis.prefix; -import org.junit.BeforeClass; import redis.clients.jedis.HostAndPorts; import redis.clients.jedis.util.prefix.ClusterCommandObjectsWithPrefixedKeys; import redis.clients.jedis.ConnectionPoolConfig; @@ -8,11 +7,8 @@ import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.Protocol; import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.args.ClusterResetType; import redis.clients.jedis.providers.ClusterConnectionProvider; -import redis.clients.jedis.util.JedisClusterTestUtil; import java.time.Duration; import java.util.Collections; @@ -22,27 +18,13 @@ public class JedisClusterPrefixedKeysTest extends PrefixedKeysTest { private static final int DEFAULT_TIMEOUT = 2000; private static final int DEFAULT_REDIRECTIONS = 5; private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig(); - private static final HostAndPort HOST_AND_PORT = HostAndPorts.getClusterServers().get(0); + private static final HostAndPort HOST_AND_PORT = HostAndPorts.getStableClusterServers().get(0); - @BeforeClass - public static void setUpClass() { - Jedis jedis = new Jedis(HOST_AND_PORT); - jedis.auth("cluster"); - jedis.clusterReset(ClusterResetType.HARD); - jedis.flushAll(); - - int[] slots = new int[Protocol.CLUSTER_HASHSLOTS]; - - for (int i = 0; i < Protocol.CLUSTER_HASHSLOTS; ++i) { - slots[i] = i; - } - - jedis.clusterAddSlots(slots); - - try { - JedisClusterTestUtil.waitForClusterReady(jedis); - } catch (InterruptedException e) { - throw new RuntimeException(e); + @Override + protected void flush() { + try (Jedis jedis = new Jedis(HOST_AND_PORT)) { + jedis.auth("cluster"); + jedis.flushAll(); } } diff --git a/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java index 8e21a0ab7b..1e06bf978f 100644 --- a/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java @@ -18,7 +18,7 @@ public abstract class PrefixedKeysTest { abstract UnifiedJedis nonPrefixingJedis(); - private void flush() { + protected void flush() { try (UnifiedJedis jedis = prefixingJedis()) { jedis.flushAll(); } From d0be6bf03c2d1b66c8190f2920d7214822f33a64 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:34:58 +0600 Subject: [PATCH 06/12] Support automatic key prefixing by handler interface --- .../clients/jedis/AbstractTransaction.java | 1 + .../clients/jedis/ClusterCommandObjects.java | 4 +- .../redis/clients/jedis/CommandArguments.java | 10 ++++ .../jedis/CommandKeyArgumentPreProcessor.java | 10 ++++ .../redis/clients/jedis/CommandObjects.java | 12 +++- .../redis/clients/jedis/JedisCluster.java | 17 ------ .../java/redis/clients/jedis/JedisPooled.java | 15 ----- .../redis/clients/jedis/JedisSentineled.java | 14 ----- .../java/redis/clients/jedis/Pipeline.java | 8 +-- .../clients/jedis/ReliableTransaction.java | 23 ++++++++ .../clients/jedis/ShardedCommandObjects.java | 4 +- .../java/redis/clients/jedis/Transaction.java | 16 ++++-- .../redis/clients/jedis/TransactionBase.java | 1 + .../redis/clients/jedis/UnifiedJedis.java | 14 +++-- .../util/PrefixedKeyArgumentPreProcessor.java | 45 +++++++++++++++ ...usterCommandArgumentsWithPrefixedKeys.java | 20 ------- ...ClusterCommandObjectsWithPrefixedKeys.java | 21 ------- .../CommandArgumentsWithPrefixedKeys.java | 19 ------- .../CommandObjectsWithPrefixedKeys.java | 21 ------- .../clients/jedis/util/prefix/Prefixer.java | 34 ------------ .../prefix/JedisClusterPrefixedKeysTest.java | 55 ++++++------------- .../prefix/JedisPooledPrefixedKeysTest.java | 26 +++------ .../JedisSentineledPrefixedKeysTest.java | 38 +++++-------- .../jedis/prefix/PrefixedKeysTest.java | 35 +++++------- 24 files changed, 185 insertions(+), 278 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java create mode 100644 src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java delete mode 100644 src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java delete mode 100644 src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java delete mode 100644 src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java delete mode 100644 src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java delete mode 100644 src/main/java/redis/clients/jedis/util/prefix/Prefixer.java diff --git a/src/main/java/redis/clients/jedis/AbstractTransaction.java b/src/main/java/redis/clients/jedis/AbstractTransaction.java index 2a551224fa..7c84532930 100644 --- a/src/main/java/redis/clients/jedis/AbstractTransaction.java +++ b/src/main/java/redis/clients/jedis/AbstractTransaction.java @@ -5,6 +5,7 @@ public abstract class AbstractTransaction extends PipeliningBase implements Closeable { + @Deprecated protected AbstractTransaction() { super(new CommandObjects()); } diff --git a/src/main/java/redis/clients/jedis/ClusterCommandObjects.java b/src/main/java/redis/clients/jedis/ClusterCommandObjects.java index 02cec4c3fd..466e9d37f9 100644 --- a/src/main/java/redis/clients/jedis/ClusterCommandObjects.java +++ b/src/main/java/redis/clients/jedis/ClusterCommandObjects.java @@ -16,7 +16,9 @@ public class ClusterCommandObjects extends CommandObjects { @Override protected ClusterCommandArguments commandArguments(ProtocolCommand command) { - return new ClusterCommandArguments(command); + ClusterCommandArguments comArgs = new ClusterCommandArguments(command); + if (keyPreProcessor != null) comArgs.setKeyArgumentPreProcessor(keyPreProcessor); + return comArgs; } private static final String CLUSTER_UNSUPPORTED_MESSAGE = "Not supported in cluster mode."; diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index b9190245ce..330ac2ee4e 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -12,6 +12,7 @@ public class CommandArguments implements Iterable { + private CommandKeyArgumentPreProcessor keyPreProc = null; private final ArrayList args; private boolean blocking; @@ -29,6 +30,10 @@ public ProtocolCommand getCommand() { return (ProtocolCommand) args.get(0); } + void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { + this.keyPreProc = keyPreProcessor; + } + public CommandArguments add(Object arg) { if (arg == null) { throw new IllegalArgumentException("null is not a valid argument."); @@ -68,6 +73,10 @@ public CommandArguments addObjects(Collection args) { } public CommandArguments key(Object key) { + if (keyPreProc != null) { + key = keyPreProc.actualKey(key); + } + if (key instanceof Rawable) { Rawable raw = (Rawable) key; processKey(raw.getRaw()); @@ -83,6 +92,7 @@ public CommandArguments key(Object key) { } else { throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); } + return this; } diff --git a/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java b/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java new file mode 100644 index 0000000000..e26f25ee3f --- /dev/null +++ b/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java @@ -0,0 +1,10 @@ +package redis.clients.jedis; + +public interface CommandKeyArgumentPreProcessor { + + /** + * @param paramKey key name in application + * @return key name in Redis server + */ + Object actualKey(Object paramKey); +} diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 5a2957bc2d..5b43318bac 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -50,17 +50,23 @@ protected RedisProtocol getProtocol() { return protocol; } + private JedisBroadcastAndRoundRobinConfig broadcastAndRoundRobinConfig = null; + protected volatile CommandKeyArgumentPreProcessor keyPreProcessor = null; private volatile JsonObjectMapper jsonObjectMapper; private final AtomicInteger searchDialect = new AtomicInteger(0); - private JedisBroadcastAndRoundRobinConfig broadcastAndRoundRobinConfig = null; - void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig config) { this.broadcastAndRoundRobinConfig = config; } + void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { + this.keyPreProcessor = keyPreProcessor; + } + protected CommandArguments commandArguments(ProtocolCommand command) { - return new CommandArguments(command); + CommandArguments comArgs = new CommandArguments(command); + if (keyPreProcessor != null) comArgs.setKeyArgumentPreProcessor(keyPreProcessor); + return comArgs; } private final CommandObject PING_COMMAND_OBJECT = new CommandObject<>(commandArguments(PING), BuilderFactory.STRING); diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index f57c4b46f4..55495e6513 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -7,7 +7,6 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.executors.ClusterCommandExecutor; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.util.JedisClusterCRC16; @@ -223,22 +222,6 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati super(provider, maxAttempts, maxTotalRetriesDuration, protocol); } - /** - * Constructor that allows CommandObjects to be customized. The RedisProtocol specified will be written into the given - * CommandObjects. - * - * @param provider The ClusterConnectionProvider. - * @param maxAttempts Max number of attempts execute a command. - * @param maxTotalRetriesDuration Max amount of time to execute a command. - * @param commandObjects The CommandObjects. - * @param protocol The RedisProtocol that will be written into the given CommandObjects. - */ - public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, - ClusterCommandObjects commandObjects, RedisProtocol protocol) { - super(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, commandObjects, - protocol); - } - public Map getClusterNodes() { return ((ClusterConnectionProvider) provider).getNodes(); } diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 49476acee7..c6d022e094 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -8,8 +8,6 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.executors.CommandExecutor; -import redis.clients.jedis.executors.DefaultCommandExecutor; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -396,19 +394,6 @@ public JedisPooled(PooledConnectionProvider provider) { super(provider); } - /** - * Constructor that allows CommandObjects to be customized. The RedisProtocol specified will be written into the given - * CommandObjects. - * - * @param provider The PooledConnectionProvider. - * @param commandObjects The CommandObjects. - * @param redisProtocol The RedisProtocol that will be written into the given CommandObjects. - */ - public JedisPooled(PooledConnectionProvider provider, CommandObjects commandObjects, - RedisProtocol redisProtocol) { - super(new DefaultCommandExecutor(provider), provider, commandObjects, redisProtocol); - } - public final Pool getPool() { return ((PooledConnectionProvider) provider).getPool(); } diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index 4b17cece9e..0ea0221c1a 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -2,8 +2,6 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.executors.CommandExecutor; -import redis.clients.jedis.executors.DefaultCommandExecutor; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { @@ -25,18 +23,6 @@ public JedisSentineled(SentineledConnectionProvider sentineledConnectionProvider super(sentineledConnectionProvider); } - /** - * Constructor that allows CommandObjects to be customized. The RedisProtocol specified will be written into the given - * CommandObjects. - * - * @param provider The SentineledConnectionProvider. - * @param commandObjects The CommandObjects. - * @param redisProtocol The RedisProtocol that will be written into the given CommandObjects. - */ - public JedisSentineled(SentineledConnectionProvider provider, CommandObjects commandObjects, RedisProtocol redisProtocol) { - super(new DefaultCommandExecutor(provider), provider, commandObjects, redisProtocol); - } - public HostAndPort getCurrentMaster() { return ((SentineledConnectionProvider) provider).getCurrentMaster(); } diff --git a/src/main/java/redis/clients/jedis/Pipeline.java b/src/main/java/redis/clients/jedis/Pipeline.java index 5b195c2425..b9217b53a4 100644 --- a/src/main/java/redis/clients/jedis/Pipeline.java +++ b/src/main/java/redis/clients/jedis/Pipeline.java @@ -28,17 +28,17 @@ public Pipeline(Connection connection) { } public Pipeline(Connection connection, boolean closeConnection) { - this(connection, commandObjects(connection), closeConnection); + this(connection, closeConnection, createCommandObjects(connection)); } - private static CommandObjects commandObjects(Connection connection) { - RedisProtocol proto = connection.getRedisProtocol(); + private static CommandObjects createCommandObjects(Connection connection) { CommandObjects commandObjects = new CommandObjects(); + RedisProtocol proto = connection.getRedisProtocol(); if (proto != null) commandObjects.setProtocol(proto); return commandObjects; } - public Pipeline(Connection connection, CommandObjects commandObjects, boolean closeConnection) { + Pipeline(Connection connection, boolean closeConnection, CommandObjects commandObjects) { super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; diff --git a/src/main/java/redis/clients/jedis/ReliableTransaction.java b/src/main/java/redis/clients/jedis/ReliableTransaction.java index c750bdb9d9..5140544b80 100644 --- a/src/main/java/redis/clients/jedis/ReliableTransaction.java +++ b/src/main/java/redis/clients/jedis/ReliableTransaction.java @@ -66,6 +66,29 @@ public ReliableTransaction(Connection connection, boolean doMulti) { * @param closeConnection should the 'connection' be closed when 'close()' is called? */ public ReliableTransaction(Connection connection, boolean doMulti, boolean closeConnection) { + this(connection, doMulti, closeConnection, createCommandObjects(connection)); + } + + private static CommandObjects createCommandObjects(Connection connection) { + CommandObjects commandObjects = new CommandObjects(); + RedisProtocol proto = connection.getRedisProtocol(); + if (proto != null) commandObjects.setProtocol(proto); + return commandObjects; + } + + /** + * Creates a new transaction. + * + * A user wanting to WATCH/UNWATCH keys followed by a call to MULTI ({@link #multi()}) it should + * be {@code doMulti=false}. + * + * @param connection connection + * @param commandObjects command objects + * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI + * @param closeConnection should the 'connection' be closed when 'close()' is called? + */ + ReliableTransaction(Connection connection, boolean doMulti, boolean closeConnection, CommandObjects commandObjects) { + super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; setGraphCommands(new GraphCommandObjects(this.connection)); diff --git a/src/main/java/redis/clients/jedis/ShardedCommandObjects.java b/src/main/java/redis/clients/jedis/ShardedCommandObjects.java index 44604c00eb..c0f2cc1fd1 100644 --- a/src/main/java/redis/clients/jedis/ShardedCommandObjects.java +++ b/src/main/java/redis/clients/jedis/ShardedCommandObjects.java @@ -34,7 +34,9 @@ public ShardedCommandObjects(Hashing algo, Pattern tagPattern) { @Override protected ShardedCommandArguments commandArguments(ProtocolCommand command) { - return new ShardedCommandArguments(algo, tagPattern, command); + ShardedCommandArguments comArgs = new ShardedCommandArguments(algo, tagPattern, command); + if (keyPreProcessor != null) comArgs.setKeyArgumentPreProcessor(keyPreProcessor); + return comArgs; } @Override diff --git a/src/main/java/redis/clients/jedis/Transaction.java b/src/main/java/redis/clients/jedis/Transaction.java index ab67f3dc0d..40009d6f2c 100644 --- a/src/main/java/redis/clients/jedis/Transaction.java +++ b/src/main/java/redis/clients/jedis/Transaction.java @@ -59,10 +59,9 @@ public Transaction(Connection connection) { * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI */ public Transaction(Connection connection, boolean doMulti) { - this(connection, new CommandObjects(), doMulti, false); + this(connection, doMulti, false, createCommandObjects(connection)); } - /** * Creates a new transaction. * @@ -74,7 +73,14 @@ public Transaction(Connection connection, boolean doMulti) { * @param closeConnection should the 'connection' be closed when 'close()' is called? */ public Transaction(Connection connection, boolean doMulti, boolean closeConnection) { - this(connection, new CommandObjects(), doMulti, closeConnection); + this(connection, doMulti, closeConnection, createCommandObjects(connection)); + } + + private static CommandObjects createCommandObjects(Connection connection) { + CommandObjects commandObjects = new CommandObjects(); + RedisProtocol proto = connection.getRedisProtocol(); + if (proto != null) commandObjects.setProtocol(proto); + return commandObjects; } /** @@ -84,11 +90,11 @@ public Transaction(Connection connection, boolean doMulti, boolean closeConnecti * be {@code doMulti=false}. * * @param connection connection - * @param commandObjects commandObjects + * @param commandObjects command objects * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI * @param closeConnection should the 'connection' be closed when 'close()' is called? */ - public Transaction(Connection connection, CommandObjects commandObjects, boolean doMulti, boolean closeConnection) { + Transaction(Connection connection, boolean doMulti, boolean closeConnection, CommandObjects commandObjects) { super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; diff --git a/src/main/java/redis/clients/jedis/TransactionBase.java b/src/main/java/redis/clients/jedis/TransactionBase.java index efdf332700..e2c4b9080d 100644 --- a/src/main/java/redis/clients/jedis/TransactionBase.java +++ b/src/main/java/redis/clients/jedis/TransactionBase.java @@ -6,6 +6,7 @@ @Deprecated public abstract class TransactionBase extends AbstractTransaction { + @Deprecated protected TransactionBase() { super(); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index c9c990e3ef..caff75517d 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -206,12 +206,12 @@ public UnifiedJedis(CommandExecutor executor) { this(executor, (ConnectionProvider) null); } - public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider) { + private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider) { this(executor, provider, new CommandObjects()); } // Uses a fetched connection to process protocol. Should be avoided if possible. - public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects) { + private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects) { this(executor, provider, commandObjects, null); if (this.provider != null) { try (Connection conn = this.provider.getConnection()) { @@ -223,7 +223,7 @@ public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comma } } - public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, + private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, RedisProtocol protocol) { this.provider = provider; this.executor = executor; @@ -4859,7 +4859,7 @@ public PipelineBase pipelined() { } else if (provider instanceof MultiClusterPooledConnectionProvider) { return new MultiClusterPipeline((MultiClusterPooledConnectionProvider) provider, commandObjects); } else { - return new Pipeline(provider.getConnection(), commandObjects, true); + return new Pipeline(provider.getConnection(), true, commandObjects); } } @@ -4869,7 +4869,7 @@ public AbstractTransaction multi() { } else if (provider instanceof MultiClusterPooledConnectionProvider) { return new MultiClusterTransaction((MultiClusterPooledConnectionProvider) provider, true, commandObjects); } else { - return new Transaction(provider.getConnection(), commandObjects, true, true); + return new Transaction(provider.getConnection(), true, true, commandObjects); } } @@ -4913,6 +4913,10 @@ public Object executeCommand(CommandArguments args) { return executeCommand(new CommandObject<>(args, BuilderFactory.RAW_OBJECT)); } + public void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { + this.commandObjects.setKeyArgumentPreProcessor(keyPreProcessor); + } + public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) { this.commandObjects.setJsonObjectMapper(jsonObjectMapper); } diff --git a/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java b/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java new file mode 100644 index 0000000000..14479c7c77 --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java @@ -0,0 +1,45 @@ +package redis.clients.jedis.util; + +import redis.clients.jedis.CommandKeyArgumentPreProcessor; +import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.args.RawableFactory; + +public class PrefixedKeyArgumentPreProcessor implements CommandKeyArgumentPreProcessor { + + private final byte[] prefixBytes; + private final String prefixString; + + public PrefixedKeyArgumentPreProcessor(String prefix) { + this(prefix, SafeEncoder.encode(prefix)); + } + + public PrefixedKeyArgumentPreProcessor(String prefixString, byte[] prefixBytes) { + this.prefixString = prefixString; + this.prefixBytes = prefixBytes; + } + + @Override + public Object actualKey(Object paramKey) { + return prefixKey(paramKey, prefixString, prefixBytes); + } + + private static Object prefixKey(Object key, String prefixString, byte[] prefixBytes) { + if (key instanceof Rawable) { + byte[] raw = ((Rawable) key).getRaw(); + return RawableFactory.from(prefixKeyWithBytes(raw, prefixBytes)); + } else if (key instanceof byte[]) { + return prefixKeyWithBytes((byte[]) key, prefixBytes); + } else if (key instanceof String) { + String raw = (String) key; + return prefixString + raw; + } + throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); + } + + private static byte[] prefixKeyWithBytes(byte[] key, byte[] prefixBytes) { + byte[] namespaced = new byte[prefixBytes.length + key.length]; + System.arraycopy(prefixBytes, 0, namespaced, 0, prefixBytes.length); + System.arraycopy(key, 0, namespaced, prefixBytes.length, key.length); + return namespaced; + } +} diff --git a/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java deleted file mode 100644 index 619eff2094..0000000000 --- a/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandArgumentsWithPrefixedKeys.java +++ /dev/null @@ -1,20 +0,0 @@ -package redis.clients.jedis.util.prefix; - -import redis.clients.jedis.ClusterCommandArguments; -import redis.clients.jedis.CommandArguments; -import redis.clients.jedis.commands.ProtocolCommand; - -public class ClusterCommandArgumentsWithPrefixedKeys extends ClusterCommandArguments { - private final byte[] prefixBytes; - private final String prefixString; - - public ClusterCommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString, byte[] prefixBytes) { - super(command); - this.prefixString = prefixString; - this.prefixBytes = prefixBytes; - } - - public CommandArguments key(Object key) { - return super.key(Prefixer.prefixKey(key, prefixString, prefixBytes)); - } -} diff --git a/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java deleted file mode 100644 index 3f8e2bb8a4..0000000000 --- a/src/main/java/redis/clients/jedis/util/prefix/ClusterCommandObjectsWithPrefixedKeys.java +++ /dev/null @@ -1,21 +0,0 @@ -package redis.clients.jedis.util.prefix; - -import redis.clients.jedis.ClusterCommandArguments; -import redis.clients.jedis.ClusterCommandObjects; -import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.util.SafeEncoder; - -public class ClusterCommandObjectsWithPrefixedKeys extends ClusterCommandObjects { - private final String prefixString; - private final byte[] prefixBytes; - - public ClusterCommandObjectsWithPrefixedKeys(String prefixString) { - this.prefixString = prefixString; - prefixBytes = SafeEncoder.encode(prefixString); - } - - @Override - protected ClusterCommandArguments commandArguments(ProtocolCommand command) { - return new ClusterCommandArgumentsWithPrefixedKeys(command, prefixString, prefixBytes); - } -} diff --git a/src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java deleted file mode 100644 index 5353c9d6d9..0000000000 --- a/src/main/java/redis/clients/jedis/util/prefix/CommandArgumentsWithPrefixedKeys.java +++ /dev/null @@ -1,19 +0,0 @@ -package redis.clients.jedis.util.prefix; - -import redis.clients.jedis.CommandArguments; -import redis.clients.jedis.commands.ProtocolCommand; - -public class CommandArgumentsWithPrefixedKeys extends CommandArguments { - private final byte[] prefixBytes; - private final String prefixString; - - public CommandArgumentsWithPrefixedKeys(ProtocolCommand command, String prefixString, byte[] prefixBytes) { - super(command); - this.prefixString = prefixString; - this.prefixBytes = prefixBytes; - } - - public CommandArguments key(Object key) { - return super.key(Prefixer.prefixKey(key, prefixString, prefixBytes)); - } -} diff --git a/src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java b/src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java deleted file mode 100644 index 8a3aef40b4..0000000000 --- a/src/main/java/redis/clients/jedis/util/prefix/CommandObjectsWithPrefixedKeys.java +++ /dev/null @@ -1,21 +0,0 @@ -package redis.clients.jedis.util.prefix; - -import redis.clients.jedis.CommandArguments; -import redis.clients.jedis.CommandObjects; -import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.util.SafeEncoder; - -public class CommandObjectsWithPrefixedKeys extends CommandObjects { - private final String prefixString; - private final byte[] prefixBytes; - - public CommandObjectsWithPrefixedKeys(String prefixString) { - this.prefixString = prefixString; - prefixBytes = SafeEncoder.encode(prefixString); - } - - @Override - protected CommandArguments commandArguments(ProtocolCommand command) { - return new CommandArgumentsWithPrefixedKeys(command, prefixString, prefixBytes); - } -} diff --git a/src/main/java/redis/clients/jedis/util/prefix/Prefixer.java b/src/main/java/redis/clients/jedis/util/prefix/Prefixer.java deleted file mode 100644 index cf9b66522b..0000000000 --- a/src/main/java/redis/clients/jedis/util/prefix/Prefixer.java +++ /dev/null @@ -1,34 +0,0 @@ -package redis.clients.jedis.util.prefix; - -import redis.clients.jedis.args.Rawable; -import redis.clients.jedis.args.RawableFactory; - -final class Prefixer { - private Prefixer() { - } - - static Object prefixKey(Object key, String prefixString, byte[] prefixBytes) { - if (key instanceof Rawable) { - byte[] raw = ((Rawable) key).getRaw(); - return RawableFactory.from(prefixKeyWithBytes(raw, prefixBytes)); - } - - if (key instanceof byte[]) { - return prefixKeyWithBytes((byte[]) key, prefixBytes); - } - - if (key instanceof String) { - String raw = (String) key; - return prefixString + raw; - } - - throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); - } - - private static byte[] prefixKeyWithBytes(byte[] key, byte[] prefixBytes) { - byte[] namespaced = new byte[prefixBytes.length + key.length]; - System.arraycopy(prefixBytes, 0, namespaced, 0, prefixBytes.length); - System.arraycopy(key, 0, namespaced, prefixBytes.length, key.length); - return namespaced; - } -} diff --git a/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java index 4b81f9b6e2..0c4040de30 100644 --- a/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisClusterPrefixedKeysTest.java @@ -1,49 +1,28 @@ package redis.clients.jedis.prefix; +import java.util.stream.Collectors; +import java.util.Set; +import org.junit.Test; + import redis.clients.jedis.HostAndPorts; -import redis.clients.jedis.util.prefix.ClusterCommandObjectsWithPrefixedKeys; -import redis.clients.jedis.ConnectionPoolConfig; import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; -import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.providers.ClusterConnectionProvider; - -import java.time.Duration; -import java.util.Collections; - -public class JedisClusterPrefixedKeysTest extends PrefixedKeysTest { - private static final DefaultJedisClientConfig DEFAULT_CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("cluster").build(); - private static final int DEFAULT_TIMEOUT = 2000; - private static final int DEFAULT_REDIRECTIONS = 5; - private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig(); - private static final HostAndPort HOST_AND_PORT = HostAndPorts.getStableClusterServers().get(0); - @Override - protected void flush() { - try (Jedis jedis = new Jedis(HOST_AND_PORT)) { - jedis.auth("cluster"); - jedis.flushAll(); - } - } +public class JedisClusterPrefixedKeysTest extends PrefixedKeysTest { - @Override - public void prefixesKeysInTransaction() { - // Transactions are not supported by JedisCluster, so override this test to no-op - } + private static final JedisClientConfig CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("cluster").build(); + private static final Set NODES = HostAndPorts.getStableClusterServers().stream().collect(Collectors.toSet()); - @Override - public UnifiedJedis prefixingJedis() { - ClusterConnectionProvider connectionProvider = new ClusterConnectionProvider(Collections.singleton(HOST_AND_PORT), DEFAULT_CLIENT_CONFIG); - int maxAttempts = 5; - Duration maxTotalRetriesDuration = Duration.ofSeconds(5 * DEFAULT_TIMEOUT); - ClusterCommandObjectsWithPrefixedKeys commandObjects = new ClusterCommandObjectsWithPrefixedKeys("test-prefix:"); - return new JedisCluster(connectionProvider, maxAttempts, maxTotalRetriesDuration, commandObjects, DEFAULT_CLIENT_CONFIG.getRedisProtocol()); - } + @Override + JedisCluster nonPrefixingJedis() { + return new JedisCluster(NODES, CLIENT_CONFIG); + } - @Override - public UnifiedJedis nonPrefixingJedis() { - return new JedisCluster(HOST_AND_PORT, DEFAULT_CLIENT_CONFIG, DEFAULT_REDIRECTIONS, DEFAULT_POOL_CONFIG); - } + @Override + @Test(expected = UnsupportedOperationException.class) + public void prefixesKeysInTransaction() { + super.prefixesKeysInTransaction(); + } } diff --git a/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java index 74422e45b9..aee4f5b76d 100644 --- a/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java @@ -1,26 +1,18 @@ package redis.clients.jedis.prefix; -import redis.clients.jedis.CommandObjects; -import redis.clients.jedis.util.prefix.CommandObjectsWithPrefixedKeys; +import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.RedisProtocol; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.providers.PooledConnectionProvider; -public class JedisPooledPrefixedKeysTest extends PrefixedKeysTest { - private final HostAndPort hostAndPort = HostAndPorts.getRedisServers().get(7); +public class JedisPooledPrefixedKeysTest extends PrefixedKeysTest { - @Override - UnifiedJedis prefixingJedis() { - PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); - return new JedisPooled(connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); - } + private static final HostAndPort ADDRESS = HostAndPorts.getRedisServers().get(1); + private static final JedisClientConfig CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("foobared").build(); - @Override - UnifiedJedis nonPrefixingJedis() { - PooledConnectionProvider connectionProvider = new PooledConnectionProvider(hostAndPort); - return new JedisPooled(connectionProvider, new CommandObjects(), RedisProtocol.RESP3); - } + @Override + JedisPooled nonPrefixingJedis() { + return new JedisPooled(ADDRESS, CLIENT_CONFIG); + } } diff --git a/src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java index 33db85514f..94c53982a2 100644 --- a/src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisSentineledPrefixedKeysTest.java @@ -1,33 +1,25 @@ package redis.clients.jedis.prefix; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.CommandObjects; -import redis.clients.jedis.util.prefix.CommandObjectsWithPrefixedKeys; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisSentineled; -import redis.clients.jedis.RedisProtocol; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.executors.DefaultCommandExecutor; -import redis.clients.jedis.providers.SentineledConnectionProvider; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -public class JedisSentineledPrefixedKeysTest extends PrefixedKeysTest { - private final Set sentinels = new HashSet<>(Arrays.asList( HostAndPorts.getSentinelServers().get(1), HostAndPorts.getSentinelServers().get(3))); +public class JedisSentineledPrefixedKeysTest extends PrefixedKeysTest { - @Override - UnifiedJedis prefixingJedis() { - SentineledConnectionProvider connectionProvider = new SentineledConnectionProvider("mymaster", DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(), new GenericObjectPoolConfig<>(), sentinels, DefaultJedisClientConfig.builder().build()); - return new JedisSentineled(connectionProvider, new CommandObjectsWithPrefixedKeys("test-prefix:"), RedisProtocol.RESP3); - } + private static final String MASTER_NAME = "mymaster"; + private static final JedisClientConfig MASTER_CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("foobared").build(); + private static final Set SENTINEL_NODES = new HashSet<>( + Arrays.asList(HostAndPorts.getSentinelServers().get(1), HostAndPorts.getSentinelServers().get(3))); + private static final JedisClientConfig SENTINEL_CLIENT_CONFIG = DefaultJedisClientConfig.builder().build(); - @Override - UnifiedJedis nonPrefixingJedis() { - SentineledConnectionProvider connectionProvider = new SentineledConnectionProvider("mymaster", DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(), new GenericObjectPoolConfig<>(), sentinels, DefaultJedisClientConfig.builder().build()); - return new JedisSentineled(connectionProvider, new CommandObjects(), RedisProtocol.RESP3); - } + @Override + JedisSentineled nonPrefixingJedis() { + return new JedisSentineled(MASTER_NAME, MASTER_CLIENT_CONFIG, SENTINEL_NODES, SENTINEL_CLIENT_CONFIG); + } } diff --git a/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java index 1e06bf978f..66fc2fec02 100644 --- a/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/PrefixedKeysTest.java @@ -1,44 +1,39 @@ package redis.clients.jedis.prefix; +import static org.junit.Assert.assertEquals; + import org.junit.After; -import org.junit.Before; import org.junit.Test; + import redis.clients.jedis.AbstractPipeline; import redis.clients.jedis.AbstractTransaction; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.resps.Tuple; +import redis.clients.jedis.util.PrefixedKeyArgumentPreProcessor; +import redis.clients.jedis.util.SafeEncoder; -import java.nio.charset.StandardCharsets; - -import static junit.framework.TestCase.assertEquals; +public abstract class PrefixedKeysTest { -public abstract class PrefixedKeysTest { + abstract T nonPrefixingJedis(); - abstract UnifiedJedis prefixingJedis(); - - abstract UnifiedJedis nonPrefixingJedis(); + T prefixingJedis() { + T jedis = nonPrefixingJedis(); + jedis.setKeyArgumentPreProcessor(new PrefixedKeyArgumentPreProcessor("test-prefix:")); + return jedis; + } - protected void flush() { + @After + public void cleanUp() { try (UnifiedJedis jedis = prefixingJedis()) { jedis.flushAll(); } } - @Before - public void setUp() { - flush(); - } - - @After - public void tearDown() { - flush(); - } - @Test public void prefixesKeys() { try (UnifiedJedis jedis = prefixingJedis()) { jedis.set("foo1", "bar1"); - jedis.set("foo2".getBytes(StandardCharsets.UTF_8), "bar2".getBytes(StandardCharsets.UTF_8)); + jedis.set(SafeEncoder.encode("foo2"), SafeEncoder.encode("bar2")); AbstractPipeline pipeline = jedis.pipelined(); pipeline.incr("foo3"); pipeline.zadd("foo4", 1234, "bar4"); From dd4a7352db8ddc25041848b53ed125df73071b9a Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 21 Mar 2024 12:51:23 +0600 Subject: [PATCH 07/12] missing changes --- .../java/redis/clients/jedis/ReliableTransaction.java | 11 +++++------ src/main/java/redis/clients/jedis/Transaction.java | 1 - 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/redis/clients/jedis/ReliableTransaction.java b/src/main/java/redis/clients/jedis/ReliableTransaction.java index 5140544b80..dfc25cd791 100644 --- a/src/main/java/redis/clients/jedis/ReliableTransaction.java +++ b/src/main/java/redis/clients/jedis/ReliableTransaction.java @@ -4,7 +4,6 @@ import static redis.clients.jedis.Protocol.Command.EXEC; import static redis.clients.jedis.Protocol.Command.MULTI; import static redis.clients.jedis.Protocol.Command.UNWATCH; -import static redis.clients.jedis.Protocol.Command.WATCH; import java.util.ArrayList; import java.util.LinkedList; @@ -91,7 +90,9 @@ private static CommandObjects createCommandObjects(Connection connection) { super(commandObjects); this.connection = connection; this.closeConnection = closeConnection; - setGraphCommands(new GraphCommandObjects(this.connection)); + GraphCommandObjects graphCommandObjects = new GraphCommandObjects(this.connection); + graphCommandObjects.setBaseCommandArgumentsCreator(protocolCommand -> commandObjects.commandArguments(protocolCommand)); + setGraphCommands(graphCommandObjects); if (doMulti) multi(); } @@ -107,16 +108,14 @@ public final void multi() { @Override public String watch(final String... keys) { - connection.sendCommand(WATCH, keys); - String status = connection.getStatusCodeReply(); + String status = connection.executeCommand(commandObjects.watch(keys)); inWatch = true; return status; } @Override public String watch(final byte[]... keys) { - connection.sendCommand(WATCH, keys); - String status = connection.getStatusCodeReply(); + String status = connection.executeCommand(commandObjects.watch(keys)); inWatch = true; return status; } diff --git a/src/main/java/redis/clients/jedis/Transaction.java b/src/main/java/redis/clients/jedis/Transaction.java index 40009d6f2c..a7cd46e4fc 100644 --- a/src/main/java/redis/clients/jedis/Transaction.java +++ b/src/main/java/redis/clients/jedis/Transaction.java @@ -4,7 +4,6 @@ import static redis.clients.jedis.Protocol.Command.EXEC; import static redis.clients.jedis.Protocol.Command.MULTI; import static redis.clients.jedis.Protocol.Command.UNWATCH; -import static redis.clients.jedis.Protocol.Command.WATCH; import java.util.ArrayList; import java.util.LinkedList; From d9770a934e2523e78dbd2a18f47cd02c672122c4 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:29:31 +0600 Subject: [PATCH 08/12] [unrelated] javadoc --- src/main/java/redis/clients/jedis/ReliableTransaction.java | 3 +-- src/main/java/redis/clients/jedis/Transaction.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/redis/clients/jedis/ReliableTransaction.java b/src/main/java/redis/clients/jedis/ReliableTransaction.java index dfc25cd791..c73f47a13d 100644 --- a/src/main/java/redis/clients/jedis/ReliableTransaction.java +++ b/src/main/java/redis/clients/jedis/ReliableTransaction.java @@ -16,8 +16,7 @@ import redis.clients.jedis.graph.GraphCommandObjects; /** - * ReliableTransaction is a transaction where commands are immediately sent to Redis server and the - * 'QUEUED' reply checked. + * A transaction where commands are immediately sent to Redis server and the {@code QUEUED} reply checked. */ public class ReliableTransaction extends TransactionBase { diff --git a/src/main/java/redis/clients/jedis/Transaction.java b/src/main/java/redis/clients/jedis/Transaction.java index a7cd46e4fc..78d2353a2d 100644 --- a/src/main/java/redis/clients/jedis/Transaction.java +++ b/src/main/java/redis/clients/jedis/Transaction.java @@ -15,7 +15,7 @@ import redis.clients.jedis.graph.GraphCommandObjects; /** - * A pipeline based transaction. + * A transaction based on pipelining. */ public class Transaction extends TransactionBase { From b58462ca297de45b901c079241fe869665c5c25a Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:01:50 +0600 Subject: [PATCH 09/12] Use Experimental annotation --- src/main/java/redis/clients/jedis/CommandArguments.java | 2 ++ .../redis/clients/jedis/CommandKeyArgumentPreProcessor.java | 3 +++ src/main/java/redis/clients/jedis/CommandObjects.java | 2 ++ src/main/java/redis/clients/jedis/UnifiedJedis.java | 1 + .../clients/jedis/util/PrefixedKeyArgumentPreProcessor.java | 2 ++ 5 files changed, 10 insertions(+) diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index 330ac2ee4e..19bfb70de4 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Iterator; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.args.RawableFactory; import redis.clients.jedis.commands.ProtocolCommand; @@ -30,6 +31,7 @@ public ProtocolCommand getCommand() { return (ProtocolCommand) args.get(0); } + @Experimental void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { this.keyPreProc = keyPreProcessor; } diff --git a/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java b/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java index e26f25ee3f..e1b66c8dde 100644 --- a/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java +++ b/src/main/java/redis/clients/jedis/CommandKeyArgumentPreProcessor.java @@ -1,5 +1,8 @@ package redis.clients.jedis; +import redis.clients.jedis.annots.Experimental; + +@Experimental public interface CommandKeyArgumentPreProcessor { /** diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index a29b558f46..1ce9c95412 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -12,6 +12,7 @@ import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.Protocol.Keyword; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.args.*; import redis.clients.jedis.bloom.*; import redis.clients.jedis.bloom.RedisBloomProtocol.*; @@ -59,6 +60,7 @@ void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig config) { this.broadcastAndRoundRobinConfig = config; } + @Experimental void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { this.keyPreProcessor = keyPreProcessor; } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index a1e0535132..896e4a3f26 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -4926,6 +4926,7 @@ public Object executeCommand(CommandArguments args) { return executeCommand(new CommandObject<>(args, BuilderFactory.RAW_OBJECT)); } + @Experimental public void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { this.commandObjects.setKeyArgumentPreProcessor(keyPreProcessor); } diff --git a/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java b/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java index 14479c7c77..ab6072873a 100644 --- a/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java +++ b/src/main/java/redis/clients/jedis/util/PrefixedKeyArgumentPreProcessor.java @@ -1,9 +1,11 @@ package redis.clients.jedis.util; import redis.clients.jedis.CommandKeyArgumentPreProcessor; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.args.RawableFactory; +@Experimental public class PrefixedKeyArgumentPreProcessor implements CommandKeyArgumentPreProcessor { private final byte[] prefixBytes; From 7175a5ac624376b4ec6c1db0d37bf29b462db0fd Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Tue, 11 Jun 2024 17:27:28 +0600 Subject: [PATCH 10/12] Merge fix: after introducing EndpointConfig in #3836 --- .../jedis/prefix/JedisPooledPrefixedKeysTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java b/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java index aee4f5b76d..e1c99bfb0b 100644 --- a/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java +++ b/src/test/java/redis/clients/jedis/prefix/JedisPooledPrefixedKeysTest.java @@ -1,18 +1,15 @@ package redis.clients.jedis.prefix; -import redis.clients.jedis.DefaultJedisClientConfig; -import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.EndpointConfig; import redis.clients.jedis.HostAndPorts; -import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; public class JedisPooledPrefixedKeysTest extends PrefixedKeysTest { - private static final HostAndPort ADDRESS = HostAndPorts.getRedisServers().get(1); - private static final JedisClientConfig CLIENT_CONFIG = DefaultJedisClientConfig.builder().password("foobared").build(); + private static final EndpointConfig ENDPOINT = HostAndPorts.getRedisEndpoint("standalone1"); @Override JedisPooled nonPrefixingJedis() { - return new JedisPooled(ADDRESS, CLIENT_CONFIG); + return new JedisPooled(ENDPOINT.getHostAndPort(), ENDPOINT.getClientConfigBuilder().timeoutMillis(500).build()); } } From 2fd276aa7da4558a54dbcc67b0567ef80f02fd80 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:09:19 +0600 Subject: [PATCH 11/12] Update CommandObjects.java --- src/main/java/redis/clients/jedis/CommandObjects.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index ba555f83e6..b89f06f62e 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -59,15 +59,15 @@ protected RedisProtocol getProtocol() { private volatile JsonObjectMapper jsonObjectMapper; private final AtomicInteger searchDialect = new AtomicInteger(0); - void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig config) { - this.broadcastAndRoundRobinConfig = config; - } - @Experimental void setKeyArgumentPreProcessor(CommandKeyArgumentPreProcessor keyPreProcessor) { this.keyPreProcessor = keyPreProcessor; } + void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig config) { + this.broadcastAndRoundRobinConfig = config; + } + protected CommandArguments commandArguments(ProtocolCommand command) { CommandArguments comArgs = new CommandArguments(command); if (keyPreProcessor != null) comArgs.setKeyArgumentPreProcessor(keyPreProcessor); From be0c15d3dcc27dc0503eac9ce88812a99600f9aa Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Tue, 16 Jul 2024 22:42:36 +0600 Subject: [PATCH 12/12] Reorder method in ReliableTransaction --- .../redis/clients/jedis/ReliableTransaction.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/redis/clients/jedis/ReliableTransaction.java b/src/main/java/redis/clients/jedis/ReliableTransaction.java index c73f47a13d..6fe41570b6 100644 --- a/src/main/java/redis/clients/jedis/ReliableTransaction.java +++ b/src/main/java/redis/clients/jedis/ReliableTransaction.java @@ -67,13 +67,6 @@ public ReliableTransaction(Connection connection, boolean doMulti, boolean close this(connection, doMulti, closeConnection, createCommandObjects(connection)); } - private static CommandObjects createCommandObjects(Connection connection) { - CommandObjects commandObjects = new CommandObjects(); - RedisProtocol proto = connection.getRedisProtocol(); - if (proto != null) commandObjects.setProtocol(proto); - return commandObjects; - } - /** * Creates a new transaction. * @@ -95,6 +88,13 @@ private static CommandObjects createCommandObjects(Connection connection) { if (doMulti) multi(); } + private static CommandObjects createCommandObjects(Connection connection) { + CommandObjects commandObjects = new CommandObjects(); + RedisProtocol proto = connection.getRedisProtocol(); + if (proto != null) commandObjects.setProtocol(proto); + return commandObjects; + } + @Override public final void multi() { connection.sendCommand(MULTI);