From 54bf2cd6ead8e9568f0d9170d899fa6e38dfdfd1 Mon Sep 17 00:00:00 2001 From: "bodong.ybd" Date: Sun, 5 Oct 2025 17:47:41 +0800 Subject: [PATCH] Support hash filed expiration commands Signed-off-by: bodong.ybd --- src/main/java/io/valkey/CommandObjects.java | 162 +++++++++++ src/main/java/io/valkey/Jedis.java | 194 +++++++++++++ src/main/java/io/valkey/PipeliningBase.java | 162 +++++++++++ src/main/java/io/valkey/Protocol.java | 5 +- src/main/java/io/valkey/UnifiedJedis.java | 162 +++++++++++ .../valkey/commands/HashBinaryCommands.java | 35 +++ .../java/io/valkey/commands/HashCommands.java | 35 +++ .../commands/HashPipelineBinaryCommands.java | 34 +++ .../valkey/commands/HashPipelineCommands.java | 35 +++ .../java/io/valkey/params/HGetExParams.java | 140 +++++++++ .../java/io/valkey/params/HSetExParams.java | 176 ++++++++++++ .../CommandObjectsHashCommandsTest.java | 245 ++++++++++++++++ .../commands/jedis/HashesCommandsTest.java | 265 +++++++++++++++++- .../unified/HashesCommandsTestBase.java | 265 +++++++++++++++++- 14 files changed, 1911 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/valkey/params/HGetExParams.java create mode 100644 src/main/java/io/valkey/params/HSetExParams.java diff --git a/src/main/java/io/valkey/CommandObjects.java b/src/main/java/io/valkey/CommandObjects.java index 6bbbc875..0331e08c 100644 --- a/src/main/java/io/valkey/CommandObjects.java +++ b/src/main/java/io/valkey/CommandObjects.java @@ -33,6 +33,8 @@ import io.valkey.params.GeoRadiusStoreParam; import io.valkey.params.GeoSearchParam; import io.valkey.params.GetExParams; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.LCSParams; import io.valkey.params.LPosParams; import io.valkey.params.MigrateParams; @@ -1242,6 +1244,166 @@ public final CommandObject> hscanNoValues(byte[] key, byte[] public final CommandObject hstrlen(byte[] key, byte[] field) { return new CommandObject<>(commandArguments(Command.HSTRLEN).key(key).add(field), BuilderFactory.LONG); } + + public final CommandObject hsetex(String key, HSetExParams params, String field, String value) { + return new CommandObject<>(commandArguments(Command.HSETEX).key(key) + .addParams(params).add(Keyword.FIELDS).add(1).add(field).add(value), BuilderFactory.LONG); + } + + public final CommandObject hsetex(String key, HSetExParams params, Map hash) { + return new CommandObject<>(addFlatMapArgs(commandArguments(Command.HSETEX).key(key) + .addParams(params).add(Keyword.FIELDS).add(hash.size()), hash), BuilderFactory.LONG); + } + + public final CommandObject> hgetex(String key, HGetExParams params, String... fields) { + return new CommandObject<>(commandArguments(Command.HGETEX).key(key) + .addParams(params).add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.STRING_LIST); + } + + public final CommandObject hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) { + return new CommandObject<>(commandArguments(Command.HSETEX).key(key) + .addParams(params).add(Keyword.FIELDS).add(1).add(field).add(value), BuilderFactory.LONG); + } + + public final CommandObject hsetex(byte[] key, HSetExParams params, Map hash) { + return new CommandObject<>(addFlatMapArgs(commandArguments(Command.HSETEX).key(key) + .addParams(params).add(Keyword.FIELDS).add(hash.size()), hash), BuilderFactory.LONG); + } + + public final CommandObject> hgetex(byte[] key, HGetExParams params, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HGETEX).key(key) + .addParams(params).add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.BINARY_LIST); + } + + public final CommandObject> hexpire(String key, long seconds, String... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIRE).key(key).add(seconds) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpire(String key, long seconds, ExpiryOption condition, String... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIRE).key(key).add(seconds).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpire(String key, long milliseconds, String... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIRE).key(key).add(milliseconds) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIRE).key(key).add(milliseconds).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpireAt(String key, long unixTimeSeconds, String... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIREAT).key(key).add(unixTimeSeconds) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIREAT).key(key).add(unixTimeSeconds).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpireAt(String key, long unixTimeMillis, String... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIREAT).key(key).add(unixTimeMillis) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIREAT).key(key).add(unixTimeMillis).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpire(byte[] key, long seconds, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIRE).key(key).add(seconds) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIRE).key(key).add(seconds).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpire(byte[] key, long milliseconds, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIRE).key(key).add(milliseconds) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIRE).key(key).add(milliseconds).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIREAT).key(key).add(unixTimeSeconds) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIREAT).key(key).add(unixTimeSeconds).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIREAT).key(key).add(unixTimeMillis) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIREAT).key(key).add(unixTimeMillis).add(condition) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpireTime(String key, String... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIRETIME).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpireTime(String key, String... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIRETIME).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> httl(String key, String... fields) { + return new CommandObject<>(commandArguments(Command.HTTL).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpttl(String key, String... fields) { + return new CommandObject<>(commandArguments(Command.HPTTL).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hexpireTime(byte[] key, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HEXPIRETIME).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpexpireTime(byte[] key, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPEXPIRETIME).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> httl(byte[] key, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HTTL).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpttl(byte[] key, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPTTL).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpersist(String key, String... fields) { + return new CommandObject<>(commandArguments(Command.HPERSIST).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } + + public final CommandObject> hpersist(byte[] key, byte[]... fields) { + return new CommandObject<>(commandArguments(Command.HPERSIST).key(key) + .add(Keyword.FIELDS).add(fields.length).addObjects((Object[]) fields), BuilderFactory.LONG_LIST); + } // Hash commands // Set commands diff --git a/src/main/java/io/valkey/Jedis.java b/src/main/java/io/valkey/Jedis.java index 5280c7ce..92cc1680 100644 --- a/src/main/java/io/valkey/Jedis.java +++ b/src/main/java/io/valkey/Jedis.java @@ -58,6 +58,8 @@ import io.valkey.params.GeoRadiusStoreParam; import io.valkey.params.GeoSearchParam; import io.valkey.params.GetExParams; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.LCSParams; import io.valkey.params.LPosParams; import io.valkey.params.LolwutParams; @@ -4752,6 +4754,102 @@ public long hstrlen(final byte[] key, final byte[] field) { return connection.executeCommand(commandObjects.hstrlen(key, field)); } + @Override + public List hgetex(byte[] key, HGetExParams params, byte[]... fields){ + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hgetex(key, params, fields)); + } + + @Override + public long hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hsetex(key, params, field, value)); + } + + @Override + public long hsetex(byte[] key, HSetExParams params, Map hash){ + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hsetex(key, params, hash)); + } + + @Override + public List hexpire(byte[] key, long seconds, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpire(key, seconds, fields)); + } + + @Override + public List hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpire(key, seconds, condition, fields)); + } + + @Override + public List hpexpire(byte[] key, long milliseconds, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, fields)); + } + + @Override + public List hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields)); + } + + @Override + public List hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields)); + } + + @Override + public List hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields)); + } + + @Override + public List hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields)); + } + + @Override + public List hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields)); + } + + @Override + public List hexpireTime(byte[] key, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpireTime(key, fields)); + } + + @Override + public List hpexpireTime(byte[] key, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpireTime(key, fields)); + } + + @Override + public List httl(byte[] key, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.httl(key, fields)); + } + + @Override + public List hpttl(byte[] key, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpttl(key, fields)); + } + + @Override + public List hpersist(byte[] key, byte[]... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpersist(key, fields)); + } + @Override public List xread(XReadParams xReadParams, Entry... streams) { checkIsInMultiOrPipeline(); @@ -9347,6 +9445,102 @@ public long hstrlen(final String key, final String field) { return connection.executeCommand(commandObjects.hstrlen(key, field)); } + @Override + public long hsetex(String key, HSetExParams params, String field, String value) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hsetex(key, params, field, value)); + } + + @Override + public long hsetex(String key, HSetExParams params, Map hash) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hsetex(key, params, hash)); + } + + @Override + public List hgetex(String key, HGetExParams params, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hgetex(key, params, fields)); + } + + @Override + public List hexpire(String key, long seconds, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpire(key, seconds, fields)); + } + + @Override + public List hexpire(String key, long seconds, ExpiryOption condition, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpire(key, seconds, condition, fields)); + } + + @Override + public List hpexpire(String key, long milliseconds, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, fields)); + } + + @Override + public List hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields)); + } + + @Override + public List hexpireAt(String key, long unixTimeSeconds, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields)); + } + + @Override + public List hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields)); + } + + @Override + public List hpexpireAt(String key, long unixTimeMillis, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields)); + } + + @Override + public List hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields)); + } + + @Override + public List hexpireTime(String key, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hexpireTime(key, fields)); + } + + @Override + public List hpexpireTime(String key, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpexpireTime(key, fields)); + } + + @Override + public List httl(String key, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.httl(key, fields)); + } + + @Override + public List hpttl(String key, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpttl(key, fields)); + } + + @Override + public List hpersist(String key, String... fields) { + checkIsInMultiOrPipeline(); + return connection.executeCommand(commandObjects.hpersist(key, fields)); + } + @Override public String memoryDoctor() { checkIsInMultiOrPipeline(); diff --git a/src/main/java/io/valkey/PipeliningBase.java b/src/main/java/io/valkey/PipeliningBase.java index 1f990e47..ebcaaa71 100644 --- a/src/main/java/io/valkey/PipeliningBase.java +++ b/src/main/java/io/valkey/PipeliningBase.java @@ -24,6 +24,8 @@ import io.valkey.params.GeoRadiusStoreParam; import io.valkey.params.GeoSearchParam; import io.valkey.params.GetExParams; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.LCSParams; import io.valkey.params.LPosParams; import io.valkey.params.MigrateParams; @@ -800,6 +802,86 @@ public Response hstrlen(String key, String field) { return appendCommand(commandObjects.hstrlen(key, field)); } + @Override + public Response hsetex(String key, HSetExParams params, String field, String value) { + return appendCommand(commandObjects.hsetex(key, params, field, value)); + } + + @Override + public Response hsetex(String key, HSetExParams params, Map hash) { + return appendCommand(commandObjects.hsetex(key, params, hash)); + } + + @Override + public Response> hgetex(String key, HGetExParams params, String... fields) { + return appendCommand(commandObjects.hgetex(key, params, fields)); + } + + @Override + public Response> hexpire(String key, long seconds, String... fields) { + return appendCommand(commandObjects.hexpire(key, seconds, fields)); + } + + @Override + public Response> hexpire(String key, long seconds, ExpiryOption condition, String... fields) { + return appendCommand(commandObjects.hexpire(key, seconds, condition, fields)); + } + + @Override + public Response> hpexpire(String key, long milliseconds, String... fields) { + return appendCommand(commandObjects.hpexpire(key, milliseconds, fields)); + } + + @Override + public Response> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) { + return appendCommand(commandObjects.hpexpire(key, milliseconds, condition, fields)); + } + + @Override + public Response> hexpireAt(String key, long unixTimeSeconds, String... fields) { + return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields)); + } + + @Override + public Response> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) { + return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields)); + } + + @Override + public Response> hpexpireAt(String key, long unixTimeMillis, String... fields) { + return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields)); + } + + @Override + public Response> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) { + return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields)); + } + + @Override + public Response> hexpireTime(String key, String... fields) { + return appendCommand(commandObjects.hexpireTime(key, fields)); + } + + @Override + public Response> hpexpireTime(String key, String... fields) { + return appendCommand(commandObjects.hpexpireTime(key, fields)); + } + + @Override + public Response> httl(String key, String... fields) { + return appendCommand(commandObjects.httl(key, fields)); + } + + @Override + public Response> hpttl(String key, String... fields) { + return appendCommand(commandObjects.hpttl(key, fields)); + } + + @Override + public Response> hpersist(String key, String... fields) { + return appendCommand(commandObjects.hpersist(key, fields)); + } + @Override public Response sadd(String key, String... members) { return appendCommand(commandObjects.sadd(key, members)); @@ -2081,6 +2163,86 @@ public Response hstrlen(byte[] key, byte[] field) { return appendCommand(commandObjects.hstrlen(key, field)); } + @Override + public Response hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) { + return appendCommand(commandObjects.hsetex(key, params, field, value)); + } + + @Override + public Response hsetex(byte[] key, HSetExParams params, Map hash) { + return appendCommand(commandObjects.hsetex(key, params, hash)); + } + + @Override + public Response> hgetex(byte[] key, HGetExParams params, byte[]... fields) { + return appendCommand(commandObjects.hgetex(key, params, fields)); + } + + @Override + public Response> hexpire(byte[] key, long seconds, byte[]... fields) { + return appendCommand(commandObjects.hexpire(key, seconds, fields)); + } + + @Override + public Response> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) { + return appendCommand(commandObjects.hexpire(key, seconds, condition, fields)); + } + + @Override + public Response> hpexpire(byte[] key, long milliseconds, byte[]... fields) { + return appendCommand(commandObjects.hpexpire(key, milliseconds, fields)); + } + + @Override + public Response> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) { + return appendCommand(commandObjects.hpexpire(key, milliseconds, condition, fields)); + } + + @Override + public Response> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) { + return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields)); + } + + @Override + public Response> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) { + return appendCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields)); + } + + @Override + public Response> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) { + return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields)); + } + + @Override + public Response> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) { + return appendCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields)); + } + + @Override + public Response> hexpireTime(byte[] key, byte[]... fields) { + return appendCommand(commandObjects.hexpireTime(key, fields)); + } + + @Override + public Response> hpexpireTime(byte[] key, byte[]... fields) { + return appendCommand(commandObjects.hpexpireTime(key, fields)); + } + + @Override + public Response> httl(byte[] key, byte[]... fields) { + return appendCommand(commandObjects.httl(key, fields)); + } + + @Override + public Response> hpttl(byte[] key, byte[]... fields) { + return appendCommand(commandObjects.hpttl(key, fields)); + } + + @Override + public Response> hpersist(byte[] key, byte[]... fields) { + return appendCommand(commandObjects.hpersist(key, fields)); + } + @Override public Response pfadd(byte[] key, byte[]... elements) { return appendCommand(commandObjects.pfadd(key, elements)); diff --git a/src/main/java/io/valkey/Protocol.java b/src/main/java/io/valkey/Protocol.java index 393993e7..908def5f 100644 --- a/src/main/java/io/valkey/Protocol.java +++ b/src/main/java/io/valkey/Protocol.java @@ -268,10 +268,11 @@ public static enum Command implements ProtocolCommand { EXPIRE, EXPIREAT, EXPIRETIME, PEXPIRE, PEXPIREAT, PEXPIRETIME, TTL, PTTL, // <-- key expiration MULTI, DISCARD, EXEC, WATCH, UNWATCH, SORT, SORT_RO, INFO, SHUTDOWN, MONITOR, CONFIG, LCS, // GETSET, MGET, SETNX, SETEX, PSETEX, MSET, MSETNX, DECR, DECRBY, INCR, INCRBY, INCRBYFLOAT, + HEXPIRE, HPEXPIRE, HEXPIREAT, HPEXPIREAT, HTTL, HPTTL, HEXPIRETIME, HPEXPIRETIME, HPERSIST, + HRANDFIELD, HINCRBYFLOAT, HSETEX, HGETEX, // <-- hash STRLEN, APPEND, SUBSTR, DELIFEQ,// <-- string SETBIT, GETBIT, BITPOS, SETRANGE, GETRANGE, BITCOUNT, BITOP, BITFIELD, BITFIELD_RO, // <-- bit (string) HSET, HGET, HSETNX, HMSET, HMGET, HINCRBY, HEXISTS, HDEL, HLEN, HKEYS, HVALS, HGETALL, HSTRLEN, - HRANDFIELD, HINCRBYFLOAT, // <-- hash RPUSH, LPUSH, LLEN, LRANGE, LTRIM, LINDEX, LSET, LREM, LPOP, RPOP, BLPOP, BRPOP, LINSERT, LPOS, RPOPLPUSH, BRPOPLPUSH, BLMOVE, LMOVE, LMPOP, BLMPOP, LPUSHX, RPUSHX, // <-- list SADD, SMEMBERS, SREM, SPOP, SMOVE, SCARD, SRANDMEMBER, SINTER, SINTERSTORE, SUNION, SUNIONSTORE, @@ -317,7 +318,7 @@ public static enum Keyword implements Rawable { STOREDIST, TO, FORCE, TIMEOUT, DB, UNLOAD, ABORT, IDX, MINMATCHLEN, WITHMATCHLEN, FULL, DELETE, LIBRARYNAME, WITHCODE, DESCRIPTION, GETKEYS, GETKEYSANDFLAGS, DOCS, FILTERBY, DUMP, MODULE, ACLCAT, PATTERN, DOCTOR, LATEST, HISTORY, USAGE, SAMPLES, PURGE, STATS, LOADEX, CONFIG, ARGS, RANK, - NOW, VERSION, ADDR, SKIPME, USER, LADDR, + NOW, VERSION, ADDR, SKIPME, USER, LADDR, FIELDS, FXX, FNX, CHANNELS, NUMPAT, NUMSUB, SHARDCHANNELS, SHARDNUMSUB, NOVALUES, MAXAGE, CAPA, IFEQ; private final byte[] raw; diff --git a/src/main/java/io/valkey/UnifiedJedis.java b/src/main/java/io/valkey/UnifiedJedis.java index 7aceb325..a5da7922 100644 --- a/src/main/java/io/valkey/UnifiedJedis.java +++ b/src/main/java/io/valkey/UnifiedJedis.java @@ -32,6 +32,8 @@ import io.valkey.params.GeoRadiusStoreParam; import io.valkey.params.GeoSearchParam; import io.valkey.params.GetExParams; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.LCSParams; import io.valkey.params.LPosParams; import io.valkey.params.MigrateParams; @@ -1729,6 +1731,166 @@ public ScanResult hscanNoValues(byte[] key, byte[] cursor, ScanParams pa public long hstrlen(byte[] key, byte[] field) { return executeCommand(commandObjects.hstrlen(key, field)); } + + @Override + public long hsetex(String key, HSetExParams params, String field, String value) { + return executeCommand(commandObjects.hsetex(key, params, field, value)); + } + + @Override + public long hsetex(String key, HSetExParams params, Map hash) { + return executeCommand(commandObjects.hsetex(key, params, hash)); + } + + @Override + public long hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value) { + return executeCommand(commandObjects.hsetex(key, params, field, value)); + } + + @Override + public long hsetex(byte[] key, HSetExParams params, Map hash) { + return executeCommand(commandObjects.hsetex(key, params, hash)); + } + + @Override + public List hgetex(String key, HGetExParams params, String... fields) { + return executeCommand(commandObjects.hgetex(key, params, fields)); + } + + @Override + public List hgetex(byte[] key, HGetExParams params, byte[]... fields) { + return executeCommand(commandObjects.hgetex(key, params, fields)); + } + + @Override + public List hexpire(String key, long seconds, String... fields) { + return executeCommand(commandObjects.hexpire(key, seconds, fields)); + } + + @Override + public List hexpire(String key, long seconds, ExpiryOption condition, String... fields) { + return executeCommand(commandObjects.hexpire(key, seconds, condition, fields)); + } + + @Override + public List hpexpire(String key, long milliseconds, String... fields) { + return executeCommand(commandObjects.hpexpire(key, milliseconds, fields)); + } + + @Override + public List hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields) { + return executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields)); + } + + @Override + public List hexpireAt(String key, long unixTimeSeconds, String... fields) { + return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields)); + } + + @Override + public List hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields) { + return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields)); + } + + @Override + public List hpexpireAt(String key, long unixTimeMillis, String... fields) { + return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields)); + } + + @Override + public List hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields) { + return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields)); + } + + @Override + public List hexpire(byte[] key, long seconds, byte[]... fields) { + return executeCommand(commandObjects.hexpire(key, seconds, fields)); + } + + @Override + public List hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields) { + return executeCommand(commandObjects.hexpire(key, seconds, condition, fields)); + } + + @Override + public List hpexpire(byte[] key, long milliseconds, byte[]... fields) { + return executeCommand(commandObjects.hpexpire(key, milliseconds, fields)); + } + + @Override + public List hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields) { + return executeCommand(commandObjects.hpexpire(key, milliseconds, condition, fields)); + } + + @Override + public List hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields) { + return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, fields)); + } + + @Override + public List hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields) { + return executeCommand(commandObjects.hexpireAt(key, unixTimeSeconds, condition, fields)); + } + + @Override + public List hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields) { + return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, fields)); + } + + @Override + public List hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields) { + return executeCommand(commandObjects.hpexpireAt(key, unixTimeMillis, condition, fields)); + } + + @Override + public List hexpireTime(String key, String... fields) { + return executeCommand(commandObjects.hexpireTime(key, fields)); + } + + @Override + public List hpexpireTime(String key, String... fields) { + return executeCommand(commandObjects.hpexpireTime(key, fields)); + } + + @Override + public List httl(String key, String... fields) { + return executeCommand(commandObjects.httl(key, fields)); + } + + @Override + public List hpttl(String key, String... fields) { + return executeCommand(commandObjects.hpttl(key, fields)); + } + + @Override + public List hexpireTime(byte[] key, byte[]... fields) { + return executeCommand(commandObjects.hexpireTime(key, fields)); + } + + @Override + public List hpexpireTime(byte[] key, byte[]... fields) { + return executeCommand(commandObjects.hpexpireTime(key, fields)); + } + + @Override + public List httl(byte[] key, byte[]... fields) { + return executeCommand(commandObjects.httl(key, fields)); + } + + @Override + public List hpttl(byte[] key, byte[]... fields) { + return executeCommand(commandObjects.hpttl(key, fields)); + } + + @Override + public List hpersist(String key, String... fields) { + return executeCommand(commandObjects.hpersist(key, fields)); + } + + @Override + public List hpersist(byte[] key, byte[]... fields) { + return executeCommand(commandObjects.hpersist(key, fields)); + } // Hash commands // Set commands diff --git a/src/main/java/io/valkey/commands/HashBinaryCommands.java b/src/main/java/io/valkey/commands/HashBinaryCommands.java index 3ab02839..b8a049df 100644 --- a/src/main/java/io/valkey/commands/HashBinaryCommands.java +++ b/src/main/java/io/valkey/commands/HashBinaryCommands.java @@ -5,6 +5,9 @@ import java.util.Map.Entry; import java.util.Set; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.ScanParams; import io.valkey.resps.ScanResult; @@ -58,4 +61,36 @@ default ScanResult hscanNoValues(byte[] key, byte[] cursor) { long hstrlen(byte[] key, byte[] field); + long hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value); + + long hsetex(byte[] key, HSetExParams params, Map hash); + + List hgetex(byte[] key, HGetExParams params, byte[]... fields); + + List hexpire(byte[] key, long seconds, byte[]... fields); + + List hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields); + + List hpexpire(byte[] key, long milliseconds, byte[]... fields); + + List hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields); + + List hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields); + + List hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields); + + List hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields); + + List hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields); + + List hexpireTime(byte[] key, byte[]... fields); + + List hpexpireTime(byte[] key, byte[]... fields); + + List httl(byte[] key, byte[]... fields); + + List hpttl(byte[] key, byte[]... fields); + + List hpersist(byte[] key, byte[]... fields); + } diff --git a/src/main/java/io/valkey/commands/HashCommands.java b/src/main/java/io/valkey/commands/HashCommands.java index 763330fa..991e4711 100644 --- a/src/main/java/io/valkey/commands/HashCommands.java +++ b/src/main/java/io/valkey/commands/HashCommands.java @@ -5,6 +5,9 @@ import java.util.Map.Entry; import java.util.Set; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.ScanParams; import io.valkey.resps.ScanResult; @@ -57,4 +60,36 @@ default ScanResult hscanNoValues(String key, String cursor) { ScanResult hscanNoValues(String key, String cursor, ScanParams params); long hstrlen(String key, String field); + + long hsetex(String key, HSetExParams params, String field, String value); + + long hsetex(String key, HSetExParams params, Map hash); + + List hgetex(String key, HGetExParams params, String... fields); + + List hexpire(String key, long seconds, String... fields); + + List hexpire(String key, long seconds, ExpiryOption condition, String... fields); + + List hpexpire(String key, long milliseconds, String... fields); + + List hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields); + + List hexpireAt(String key, long unixTimeSeconds, String... fields); + + List hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields); + + List hpexpireAt(String key, long unixTimeMillis, String... fields); + + List hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields); + + List hexpireTime(String key, String... fields); + + List hpexpireTime(String key, String... fields); + + List httl(String key, String... fields); + + List hpttl(String key, String... fields); + + List hpersist(String key, String... fields); } diff --git a/src/main/java/io/valkey/commands/HashPipelineBinaryCommands.java b/src/main/java/io/valkey/commands/HashPipelineBinaryCommands.java index 0719f0b0..ca09bb88 100644 --- a/src/main/java/io/valkey/commands/HashPipelineBinaryCommands.java +++ b/src/main/java/io/valkey/commands/HashPipelineBinaryCommands.java @@ -6,6 +6,9 @@ import java.util.Set; import io.valkey.Response; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.ScanParams; import io.valkey.resps.ScanResult; @@ -59,4 +62,35 @@ default Response> hscanNoValues(byte[] key, byte[] cursor) { Response hstrlen(byte[] key, byte[] field); + Response hsetex(byte[] key, HSetExParams params, byte[] field, byte[] value); + + Response hsetex(byte[] key, HSetExParams params, Map hash); + + Response> hgetex(byte[] key, HGetExParams params, byte[]... fields); + + Response> hexpire(byte[] key, long seconds, byte[]... fields); + + Response> hexpire(byte[] key, long seconds, ExpiryOption condition, byte[]... fields); + + Response> hpexpire(byte[] key, long milliseconds, byte[]... fields); + + Response> hpexpire(byte[] key, long milliseconds, ExpiryOption condition, byte[]... fields); + + Response> hexpireAt(byte[] key, long unixTimeSeconds, byte[]... fields); + + Response> hexpireAt(byte[] key, long unixTimeSeconds, ExpiryOption condition, byte[]... fields); + + Response> hpexpireAt(byte[] key, long unixTimeMillis, byte[]... fields); + + Response> hpexpireAt(byte[] key, long unixTimeMillis, ExpiryOption condition, byte[]... fields); + + Response> hexpireTime(byte[] key, byte[]... fields); + + Response> hpexpireTime(byte[] key, byte[]... fields); + + Response> httl(byte[] key, byte[]... fields); + + Response> hpttl(byte[] key, byte[]... fields); + + Response> hpersist(byte[] key, byte[]... fields); } diff --git a/src/main/java/io/valkey/commands/HashPipelineCommands.java b/src/main/java/io/valkey/commands/HashPipelineCommands.java index 19f0aac5..6dd999b7 100644 --- a/src/main/java/io/valkey/commands/HashPipelineCommands.java +++ b/src/main/java/io/valkey/commands/HashPipelineCommands.java @@ -6,6 +6,9 @@ import java.util.Set; import io.valkey.Response; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.ScanParams; import io.valkey.resps.ScanResult; @@ -58,4 +61,36 @@ default Response> hscanNoValues(String key, String cursor) { Response> hscanNoValues(String key, String cursor, ScanParams params); Response hstrlen(String key, String field); + + Response hsetex(String key, HSetExParams params, String field, String value); + + Response hsetex(String key, HSetExParams params, Map hash); + + Response> hgetex(String key, HGetExParams params, String... fields); + + Response> hexpire(String key, long seconds, String... fields); + + Response> hexpire(String key, long seconds, ExpiryOption condition, String... fields); + + Response> hpexpire(String key, long milliseconds, String... fields); + + Response> hpexpire(String key, long milliseconds, ExpiryOption condition, String... fields); + + Response> hexpireAt(String key, long unixTimeSeconds, String... fields); + + Response> hexpireAt(String key, long unixTimeSeconds, ExpiryOption condition, String... fields); + + Response> hpexpireAt(String key, long unixTimeMillis, String... fields); + + Response> hpexpireAt(String key, long unixTimeMillis, ExpiryOption condition, String... fields); + + Response> hexpireTime(String key, String... fields); + + Response> hpexpireTime(String key, String... fields); + + Response> httl(String key, String... fields); + + Response> hpttl(String key, String... fields); + + Response> hpersist(String key, String... fields); } diff --git a/src/main/java/io/valkey/params/HGetExParams.java b/src/main/java/io/valkey/params/HGetExParams.java new file mode 100644 index 00000000..c669a86f --- /dev/null +++ b/src/main/java/io/valkey/params/HGetExParams.java @@ -0,0 +1,140 @@ +package io.valkey.params; + +import io.valkey.CommandArguments; +import io.valkey.Protocol.Keyword; + +/** + * Parameters for HGETEX command. + * Supports various expiration options and PERSIST. + */ +public class HGetExParams implements IParams { + + private Long ex = null; + private Long px = null; + private Long exAt = null; + private Long pxAt = null; + private boolean persist = false; + + public HGetExParams() { + } + + /** + * Set the specified expiration time in seconds. + * @param seconds expiration time in seconds + * @return this + */ + public HGetExParams ex(long seconds) { + this.ex = seconds; + this.px = null; + this.exAt = null; + this.pxAt = null; + this.persist = false; + return this; + } + + /** + * Set the specified expiration time in milliseconds. + * @param milliseconds expiration time in milliseconds + * @return this + */ + public HGetExParams px(long milliseconds) { + this.px = milliseconds; + this.ex = null; + this.exAt = null; + this.pxAt = null; + this.persist = false; + return this; + } + + /** + * Set the specified Unix time in seconds at which the fields will expire. + * @param unixTimeSeconds Unix time in seconds + * @return this + */ + public HGetExParams exAt(long unixTimeSeconds) { + this.exAt = unixTimeSeconds; + this.ex = null; + this.px = null; + this.pxAt = null; + this.persist = false; + return this; + } + + /** + * Set the specified Unix time in milliseconds at which the fields will expire. + * @param unixTimeMillis Unix time in milliseconds + * @return this + */ + public HGetExParams pxAt(long unixTimeMillis) { + this.pxAt = unixTimeMillis; + this.ex = null; + this.px = null; + this.exAt = null; + this.persist = false; + return this; + } + + /** + * Remove the TTL associated with the fields. + * @return this + */ + public HGetExParams persist() { + this.persist = true; + this.ex = null; + this.px = null; + this.exAt = null; + this.pxAt = null; + return this; + } + + /** + * Add parameters to the command arguments. + * @param args the command arguments builder + */ + public void addParams(CommandArguments args) { + if (ex != null) { + args.add(Keyword.EX).add(ex); + } + if (px != null) { + args.add(Keyword.PX).add(px); + } + if (exAt != null) { + args.add(Keyword.EXAT).add(exAt); + } + if (pxAt != null) { + args.add(Keyword.PXAT).add(pxAt); + } + if (persist) { + args.add(Keyword.PERSIST); + } + } + + /** + * Create a new HGetExParams instance. + * @return new HGetExParams instance + */ + public static HGetExParams hGetExParams() { + return new HGetExParams(); + } + + // Getters for testing purposes + public Long getEx() { + return ex; + } + + public Long getPx() { + return px; + } + + public Long getExAt() { + return exAt; + } + + public Long getPxAt() { + return pxAt; + } + + public boolean isPersist() { + return persist; + } +} diff --git a/src/main/java/io/valkey/params/HSetExParams.java b/src/main/java/io/valkey/params/HSetExParams.java new file mode 100644 index 00000000..3ef247f7 --- /dev/null +++ b/src/main/java/io/valkey/params/HSetExParams.java @@ -0,0 +1,176 @@ +package io.valkey.params; + +import io.valkey.CommandArguments; +import io.valkey.Protocol.Keyword; + +/** + * Parameters for HSETEX command. + * Supports FNX/FXX conditions and various expiration options. + */ +public class HSetExParams implements IParams { + + private boolean fnx = false; + private boolean fxx = false; + private Long ex = null; + private Long px = null; + private Long exAt = null; + private Long pxAt = null; + private boolean keepTtl = false; + + public HSetExParams() { + } + + /** + * Only set the fields if none of them already exist. + * @return this + */ + public HSetExParams fnx() { + this.fnx = true; + this.fxx = false; + return this; + } + + /** + * Only set the fields if all of them already exist. + * @return this + */ + public HSetExParams fxx() { + this.fxx = true; + this.fnx = false; + return this; + } + + /** + * Set the specified expiration time in seconds. + * @param seconds expiration time in seconds + * @return this + */ + public HSetExParams ex(long seconds) { + this.ex = seconds; + this.px = null; + this.exAt = null; + this.pxAt = null; + this.keepTtl = false; + return this; + } + + /** + * Set the specified expiration time in milliseconds. + * @param milliseconds expiration time in milliseconds + * @return this + */ + public HSetExParams px(long milliseconds) { + this.px = milliseconds; + this.ex = null; + this.exAt = null; + this.pxAt = null; + this.keepTtl = false; + return this; + } + + /** + * Set the specified Unix time in seconds at which the fields will expire. + * @param unixTimeSeconds Unix time in seconds + * @return this + */ + public HSetExParams exAt(long unixTimeSeconds) { + this.exAt = unixTimeSeconds; + this.ex = null; + this.px = null; + this.pxAt = null; + this.keepTtl = false; + return this; + } + + /** + * Set the specified Unix time in milliseconds at which the fields will expire. + * @param unixTimeMillis Unix time in milliseconds + * @return this + */ + public HSetExParams pxAt(long unixTimeMillis) { + this.pxAt = unixTimeMillis; + this.ex = null; + this.px = null; + this.exAt = null; + this.keepTtl = false; + return this; + } + + /** + * Retain the TTL associated with the fields. + * @return this + */ + public HSetExParams keepTtl() { + this.keepTtl = true; + this.ex = null; + this.px = null; + this.exAt = null; + this.pxAt = null; + return this; + } + + /** + * Add parameters to the command arguments. + * @param args the command arguments builder + */ + public void addParams(CommandArguments args) { + if (fnx) { + args.add(Keyword.FNX); + } + if (fxx) { + args.add(Keyword.FXX); + } + if (ex != null) { + args.add(Keyword.EX).add(ex); + } + if (px != null) { + args.add(Keyword.PX).add(px); + } + if (exAt != null) { + args.add(Keyword.EXAT).add(exAt); + } + if (pxAt != null) { + args.add(Keyword.PXAT).add(pxAt); + } + if (keepTtl) { + args.add(Keyword.KEEPTTL); + } + } + + /** + * Create a new HSetExParams instance. + * @return new HSetExParams instance + */ + public static HSetExParams hSetExParams() { + return new HSetExParams(); + } + + // Getters for testing purposes + public boolean isFnx() { + return fnx; + } + + public boolean isFxx() { + return fxx; + } + + public Long getEx() { + return ex; + } + + public Long getPx() { + return px; + } + + public Long getExAt() { + return exAt; + } + + public Long getPxAt() { + return pxAt; + } + + public boolean isKeepTtl() { + return keepTtl; + } +} diff --git a/src/test/java/io/valkey/commands/commandobjects/CommandObjectsHashCommandsTest.java b/src/test/java/io/valkey/commands/commandobjects/CommandObjectsHashCommandsTest.java index 7b40dafb..5457ab73 100644 --- a/src/test/java/io/valkey/commands/commandobjects/CommandObjectsHashCommandsTest.java +++ b/src/test/java/io/valkey/commands/commandobjects/CommandObjectsHashCommandsTest.java @@ -1,17 +1,24 @@ package io.valkey.commands.commandobjects; +import static io.valkey.util.AssertUtil.assertByteArrayListEquals; +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.Matchers.nullValue; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,6 +26,9 @@ import java.util.Set; import io.valkey.RedisProtocol; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.resps.ScanResult; import org.junit.Test; import io.valkey.params.ScanParams; @@ -28,6 +38,13 @@ */ public class CommandObjectsHashCommandsTest extends CommandObjectsStandaloneTestBase { + private final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 }; + private final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C }; + + private final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A }; + private final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B }; + private final byte[] bbar3 = { 0x05, 0x06, 0x07, 0x08, 0x0C }; + public CommandObjectsHashCommandsTest(RedisProtocol protocol) { super(protocol); } @@ -403,4 +420,232 @@ public void testHashStrlen() { Long strlenNonExistingFieldBinary = exec(commandObjects.hstrlen(bkey, "nonExistingField".getBytes())); assertThat(strlenNonExistingFieldBinary, equalTo(0L)); } + + @Test + public void testHgetex() { + String key = "hashKey"; + String field = "name"; + String value = "John"; + long seconds = 20; + + exec(commandObjects.hset(key, field, value)); + List fieldValues = exec( + commandObjects.hgetex(key, new HGetExParams().ex(seconds), field)); + assertThat(fieldValues, is(Collections.singletonList(value))); + + List ttlList = exec(commandObjects.httl(key, field)); + Long ttl = ttlList.get(0); + assertThat(ttl, greaterThanOrEqualTo(seconds - 1)); + } + + @Test + public void testHgetexBinary() { + byte[] key = "hashKeyBytes".getBytes(); + byte[] field = "name".getBytes(); + byte[] value = "John".getBytes(); + long seconds = 20; + + exec(commandObjects.hset(key, field, value)); + List get = exec(commandObjects.hgetex(key, new HGetExParams().ex(seconds), field)); + assertByteArrayListEquals(get, Collections.singletonList(value)); + + List ttlList = exec(commandObjects.httl(key, field)); + Long ttl = ttlList.get(0); + assertThat(ttl, greaterThanOrEqualTo(seconds - 1)); + assertThat(ttl, greaterThanOrEqualTo(seconds - 1)); + } + + @Test + public void testHsetex() { + String key = "hashKey"; + String field = "name"; + String value = "John"; + long seconds = 20; + + Long set = exec(commandObjects.hsetex(key, new HSetExParams().ex(seconds), field, value)); + assertThat(set, equalTo(1L)); + + String get = exec(commandObjects.hget(key, field)); + assertThat(get, equalTo(value)); + + List ttlList = exec(commandObjects.httl(key, field)); + Long ttl = ttlList.get(0); + assertThat(ttl, greaterThanOrEqualTo(seconds - 1)); + } + + @Test + public void testHsetexBinary() { + byte[] key = "hashKeyBytes".getBytes(); + byte[] field = "name".getBytes(); + byte[] value = "John".getBytes(); + long seconds = 20; + Long set = exec(commandObjects.hsetex(key, new HSetExParams().ex(seconds), field, value)); + assertThat(set, equalTo(1L)); + + byte[] get = exec(commandObjects.hget(key, field)); + assertThat(get, equalTo(value)); + + List ttlList = exec(commandObjects.httl(key, field)); + Long ttl = ttlList.get(0); + assertThat(ttl, greaterThanOrEqualTo(seconds - 1)); + assertThat(ttl, greaterThanOrEqualTo(seconds - 1)); + } + + @Test + public void hexpireAndHttl() { + long seconds1 = 20; + long seconds2 = 10; + + exec(commandObjects.hset("foo", "bar", "car")); + exec(commandObjects.hset("foo", "bare", "care")); + assertThat(exec(commandObjects.hexpire("foo", seconds1, "bar", "bared")), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset("foo", "bared", "cared")); + assertThat(exec(commandObjects.hexpire("foo", seconds2, ExpiryOption.NX, "bar", "bared")), equalTo(asList(0L, 1L))); + + assertThat(exec(commandObjects.httl("foo", "bar", "bare", "bared")), + contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hexpireAndHttlBinary() { + long seconds1 = 20; + long seconds2 = 10; + + exec(commandObjects.hset(bfoo, bbar1, bcar)); + exec(commandObjects.hset(bfoo, bbar2, bcar)); + assertThat(exec(commandObjects.hexpire(bfoo, seconds1, bbar1, bbar3)), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset(bfoo, bbar3, bcar)); + assertThat(exec(commandObjects.hexpire(bfoo, seconds2, ExpiryOption.NX, bbar1, bbar3)), equalTo(asList(0L, 1L))); + + assertThat(exec(commandObjects.httl(bfoo, bbar1, bbar2, bbar3)), + contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hpexpireAndHpttl() { + long millis1 = 20_000; + long millis2 = 10_000; + + exec(commandObjects.hset("foo", "bar", "car")); + assertThat(exec(commandObjects.hpexpire("foo", millis1, "bar", "bared")), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset("foo", "bared", "cared")); + assertThat(exec(commandObjects.hpexpire("foo", millis2, ExpiryOption.XX, "bar", "bared")), equalTo(asList(1L, 0L))); + + assertThat(exec(commandObjects.hpttl("foo", "bar", "bare", "bared")), + contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 10)), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpexpireAndHpttlBinary() { + long millis1 = 20_000; + long millis2 = 10_000; + + exec(commandObjects.hset(bfoo, bbar1, bcar)); + assertThat(exec(commandObjects.hpexpire(bfoo, millis1, bbar1, bbar3)), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset(bfoo, bbar3, bcar)); + assertThat(exec(commandObjects.hpexpire(bfoo, millis2, ExpiryOption.XX, bbar1, bbar3)), equalTo(asList(1L, 0L))); + + assertThat(exec(commandObjects.hpttl(bfoo, bbar1, bbar2, bbar3)), + contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 10)), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hexpireAtAndExpireTime() { + long currSeconds = System.currentTimeMillis() / 1000; + long seconds1 = currSeconds + 20; + long seconds2 = currSeconds + 10; + + exec(commandObjects.hset("foo", "bar", "car")); + exec(commandObjects.hset("foo", "bare", "care")); + assertThat(exec(commandObjects.hexpireAt("foo", seconds1, "bar", "bared")), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset("foo", "bared", "cared")); + assertThat(exec(commandObjects.hexpireAt("foo", seconds2, ExpiryOption.LT, "bar", "bared")), equalTo(asList(1L, 1L))); + + assertThat(exec(commandObjects.hexpireTime("foo", "bar", "bare", "bared")), + contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hexpireAtAndExpireTimeBinary() { + long currSeconds = System.currentTimeMillis() / 1000; + long seconds1 = currSeconds + 20; + long seconds2 = currSeconds + 10; + + exec(commandObjects.hset(bfoo, bbar1, bcar)); + exec(commandObjects.hset(bfoo, bbar2, bcar)); + assertThat(exec(commandObjects.hexpireAt(bfoo, seconds1, bbar1, bbar3)), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset(bfoo, bbar3, bcar)); + assertThat(exec(commandObjects.hexpireAt(bfoo, seconds2, ExpiryOption.LT, bbar1, bbar3)), equalTo(asList(1L, 1L))); + + assertThat(exec(commandObjects.hexpireTime(bfoo, bbar1, bbar2, bbar3)), + contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hpexpireAtAndPexpireTime() { + long currMillis = System.currentTimeMillis(); + long unixMillis = currMillis + 20_000; + + exec(commandObjects.hset("foo", "bar", "car")); + assertThat(exec(commandObjects.hpexpireAt("foo", unixMillis - 100, "bar", "bared")), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset("foo", "bared", "cared")); + assertThat(exec(commandObjects.hpexpireAt("foo", unixMillis, ExpiryOption.GT, "bar", "bared")), equalTo(asList(1L, 0L))); + + assertThat(exec(commandObjects.hpexpireTime("foo", "bar", "bare", "bared")), + contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpexpireAtAndPexpireTimeBinary() { + long currMillis = System.currentTimeMillis(); + long unixMillis = currMillis + 20_000; + + exec(commandObjects.hset(bfoo, bbar1, bcar)); + assertThat(exec(commandObjects.hpexpireAt(bfoo, unixMillis - 100, bbar1, bbar3)), equalTo(asList(1L, -2L))); + + exec(commandObjects.hset(bfoo, bbar3, bcar)); + assertThat(exec(commandObjects.hpexpireAt(bfoo, unixMillis, ExpiryOption.GT, bbar1, bbar3)), equalTo(asList(1L, 0L))); + + assertThat(exec(commandObjects.hpexpireTime(bfoo, bbar1, bbar2, bbar3)), + contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpersist() { + long seconds = 20; + + exec(commandObjects.hset("foo", "bar", "car")); + exec(commandObjects.hset("foo", "bare", "care")); + assertThat(exec(commandObjects.hexpire("foo", seconds, "bar", "bared")), equalTo(asList(1L, -2L))); + + assertThat(exec(commandObjects.hpersist("foo", "bar", "bare", "bared")), equalTo(asList(1L, -1L, -2L))); + + assertThat(exec(commandObjects.httl("foo", "bar", "bare", "bared")), + contains(equalTo(-1L), equalTo(-1L), equalTo(-2L))); + } + + @Test + public void hpersistBinary() { + long seconds = 20; + + exec(commandObjects.hset(bfoo, bbar1, bcar)); + exec(commandObjects.hset(bfoo, bbar2, bcar)); + assertThat(exec(commandObjects.hexpire(bfoo, seconds, bbar1, bbar3)), equalTo(asList(1L, -2L))); + + assertThat(exec(commandObjects.hpersist(bfoo, bbar1, bbar2, bbar3)), equalTo(asList(1L, -1L, -2L))); + + assertThat(exec(commandObjects.httl(bfoo, bbar1, bbar2, bbar3)), + contains(equalTo(-1L), equalTo(-1L), equalTo(-2L))); + } } diff --git a/src/test/java/io/valkey/commands/jedis/HashesCommandsTest.java b/src/test/java/io/valkey/commands/jedis/HashesCommandsTest.java index 81233723..248ddf01 100644 --- a/src/test/java/io/valkey/commands/jedis/HashesCommandsTest.java +++ b/src/test/java/io/valkey/commands/jedis/HashesCommandsTest.java @@ -1,7 +1,15 @@ package io.valkey.commands.jedis; +import static io.valkey.util.AssertUtil.assertByteArrayListEquals; +import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -23,6 +31,9 @@ import io.valkey.Pipeline; import io.valkey.RedisProtocol; import io.valkey.Response; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.ScanParams; import io.valkey.resps.ScanResult; import io.valkey.util.AssertUtil; @@ -37,6 +48,8 @@ public class HashesCommandsTest extends JedisCommandsTestBase { final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 }; final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 }; final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C }; + final byte[] bbare = { 0x05, 0x06, 0x07, 0x08, 0x09 }; + final byte[] bcare = { 0x09, 0x0A, 0x0B, 0x0C, 0x0D }; final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A }; final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B }; @@ -156,7 +169,7 @@ public void hmget() { bexpected.add(bbar); bexpected.add(null); - AssertUtil.assertByteArrayListEquals(bexpected, bvalues); + assertByteArrayListEquals(bexpected, bvalues); } @Test @@ -639,4 +652,254 @@ public void hrandfield() { assertEquals(5, bactual.size()); bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue())); } + + @Test + public void hgetex() { + long seconds = 20; + jedis.hset("foo", "bar", "car"); + assertEquals(asList("car"), jedis.hgetex("foo", HGetExParams.hGetExParams().ex(seconds), "bar")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + + jedis.hset("foo", "bar", "car"); + assertEquals(asList("car"), jedis.hgetex("foo", HGetExParams.hGetExParams().persist(), "bar")); + assertEquals(jedis.httl("foo", "bar").get(0), Long.valueOf(-1)); + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList("car", "care"), jedis.hgetex("foo", HGetExParams.hGetExParams().ex(seconds), "bar", "bare")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl("foo", "bare").get(0), greaterThanOrEqualTo(seconds - 1)); + + // Binary + jedis.hset(bfoo, bbar, bcar); + assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + + jedis.hset(bfoo, bbar, bcar); + assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().persist(), bbar)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0)); + + jedis.hset(bfoo, bbar, bcar); + jedis.hset(bfoo, bbare, bcare); + assertByteArrayListEquals(asList(bcar, bcare), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar, bbare)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1)); + } + + @Test + public void hsetex() { + long seconds = 20; + jedis.del("foo"); + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().ex(seconds).fnx(), "bar", "car")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().keepTtl(), "bar", "car")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().fxx(), "bar", "car")); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bar").get(0)); + + HashMap hash = new HashMap(); + hash.put("bar", "car"); + hash.put("bare", "care"); + jedis.del("foo"); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().ex(seconds).fnx(), hash)); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl("foo", "bare").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().keepTtl(), hash)); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl("foo", "bare").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().fxx(), hash)); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bar").get(0)); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bare").get(0)); + + // Binary + jedis.del(bfoo); + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bbar, bcar)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bbar, bcar)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bbar, bcar)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0)); + + HashMap bhash = new HashMap(); + bhash.put(bbar, bcar); + bhash.put(bbare, bcare); + + jedis.del(bfoo); + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bhash)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bhash)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bhash)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbare).get(0)); + } + + @Test + public void hexpireAndHttl() { + long seconds1 = 20; + long seconds2 = 10; + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList(1L, -2L), jedis.hexpire("foo", seconds1, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(0L, 1L), jedis.hexpire("foo", seconds2, ExpiryOption.NX, "bar", "bared")); + + assertThat(jedis.httl("foo", "bar", "bare", "bared"), + contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hexpireAndHttlBinary() { + long seconds1 = 20; + long seconds2 = 10; + + jedis.hset(bfoo, bbar1, bcar); + jedis.hset(bfoo, bbar2, bcar); + assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds1, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(0L, 1L), jedis.hexpire(bfoo, seconds2, ExpiryOption.NX, bbar1, bbar3)); + + assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3), + contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hpexpireAndHpttl() { + long millis1 = 20_000; + long millis2 = 10_000; + + jedis.hset("foo", "bar", "car"); + assertEquals(asList(1L, -2L), jedis.hpexpire("foo", millis1, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(1L, 0L), jedis.hpexpire("foo", millis2, ExpiryOption.XX, "bar", "bared")); + + assertThat(jedis.hpttl("foo", "bar", "bare", "bared"), + contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 10)), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpexpireAndHpttlBinary() { + long millis1 = 20_000; + long millis2 = 10_000; + + jedis.hset(bfoo, bbar1, bcar); + assertEquals(asList(1L, -2L), jedis.hpexpire(bfoo, millis1, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(1L, 0L), jedis.hpexpire(bfoo, millis2, ExpiryOption.XX, bbar1, bbar3)); + + assertThat(jedis.hpttl(bfoo, bbar1, bbar2, bbar3), + contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 10)), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hexpireAtAndExpireTime() { + long currSeconds = System.currentTimeMillis() / 1000; + long seconds1 = currSeconds + 20; + long seconds2 = currSeconds + 10; + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList(1L, -2L), jedis.hexpireAt("foo", seconds1, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(1L, 1L), jedis.hexpireAt("foo", seconds2, ExpiryOption.LT, "bar", "bared")); + + assertThat(jedis.hexpireTime("foo", "bar", "bare", "bared"), + contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hexpireAtAndExpireTimeBinary() { + long currSeconds = System.currentTimeMillis() / 1000; + long seconds1 = currSeconds + 20; + long seconds2 = currSeconds + 10; + + jedis.hset(bfoo, bbar1, bcar); + jedis.hset(bfoo, bbar2, bcar); + assertEquals(asList(1L, -2L), jedis.hexpireAt(bfoo, seconds1, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(1L, 1L), jedis.hexpireAt(bfoo, seconds2, ExpiryOption.LT, bbar1, bbar3)); + + assertThat(jedis.hexpireTime(bfoo, bbar1, bbar2, bbar3), + contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hpexpireAtAndPexpireTime() { + long currMillis = System.currentTimeMillis(); + long unixMillis = currMillis + 20_000; + + jedis.hset("foo", "bar", "car"); + assertEquals(asList(1L, -2L), jedis.hpexpireAt("foo", unixMillis - 100, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(1L, 0L), jedis.hpexpireAt("foo", unixMillis, ExpiryOption.GT, "bar", "bared")); + + assertThat(jedis.hpexpireTime("foo", "bar", "bare", "bared"), + contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpexpireAtAndPexpireTimeBinary() { + long currMillis = System.currentTimeMillis(); + long unixMillis = currMillis + 20_000; + + jedis.hset(bfoo, bbar1, bcar); + assertEquals(asList(1L, -2L), jedis.hpexpireAt(bfoo, unixMillis - 100, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(1L, 0L), jedis.hpexpireAt(bfoo, unixMillis, ExpiryOption.GT, bbar1, bbar3)); + + assertThat(jedis.hpexpireTime(bfoo, bbar1, bbar2, bbar3), + contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpersist() { + long seconds = 20; + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList(1L, -2L), jedis.hexpire("foo", seconds, "bar", "bared")); + + assertEquals(asList(1L, -1L, -2L), jedis.hpersist("foo", "bar", "bare", "bared")); + + assertThat(jedis.httl("foo", "bar", "bare", "bared"), + contains(equalTo(-1L), equalTo(-1L), equalTo(-2L))); + } + + @Test + public void hpersistBinary() { + long seconds = 20; + + jedis.hset(bfoo, bbar1, bcar); + jedis.hset(bfoo, bbar2, bcar); + assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds, bbar1, bbar3)); + + assertEquals(asList(1L, -1L, -2L), jedis.hpersist(bfoo, bbar1, bbar2, bbar3)); + + assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3), + contains(equalTo(-1L), equalTo(-1L), equalTo(-2L))); + } } diff --git a/src/test/java/io/valkey/commands/unified/HashesCommandsTestBase.java b/src/test/java/io/valkey/commands/unified/HashesCommandsTestBase.java index 8673dd01..f93a8eff 100644 --- a/src/test/java/io/valkey/commands/unified/HashesCommandsTestBase.java +++ b/src/test/java/io/valkey/commands/unified/HashesCommandsTestBase.java @@ -1,7 +1,15 @@ package io.valkey.commands.unified; +import static io.valkey.util.AssertUtil.assertByteArrayListEquals; +import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -21,6 +29,9 @@ import java.util.stream.Collectors; import io.valkey.RedisProtocol; +import io.valkey.args.ExpiryOption; +import io.valkey.params.HGetExParams; +import io.valkey.params.HSetExParams; import io.valkey.params.ScanParams; import io.valkey.resps.ScanResult; import io.valkey.util.AssertUtil; @@ -33,6 +44,8 @@ public abstract class HashesCommandsTestBase extends UnifiedJedisCommandsTestBas final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 }; final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 }; final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C }; + final byte[] bbare = { 0x05, 0x06, 0x07, 0x08, 0x09 }; + final byte[] bcare = { 0x09, 0x0A, 0x0B, 0x0C, 0x0D }; final byte[] bbar1 = { 0x05, 0x06, 0x07, 0x08, 0x0A }; final byte[] bbar2 = { 0x05, 0x06, 0x07, 0x08, 0x0B }; @@ -152,7 +165,7 @@ public void hmget() { bexpected.add(bbar); bexpected.add(null); - AssertUtil.assertByteArrayListEquals(bexpected, bvalues); + assertByteArrayListEquals(bexpected, bvalues); } @Test @@ -611,4 +624,254 @@ public void hrandfield() { assertEquals(5, bactual.size()); bactual.forEach(e -> assertArrayEquals(bhash.get(e.getKey()), e.getValue())); } + + @Test + public void hgetex() { + long seconds = 20; + jedis.hset("foo", "bar", "car"); + assertEquals(asList("car"), jedis.hgetex("foo", HGetExParams.hGetExParams().ex(seconds), "bar")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + + jedis.hset("foo", "bar", "car"); + assertEquals(asList("car"), jedis.hgetex("foo", HGetExParams.hGetExParams().persist(), "bar")); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bar").get(0)); + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList("car", "care"), jedis.hgetex("foo", HGetExParams.hGetExParams().ex(seconds), "bar", "bare")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl("foo", "bare").get(0), greaterThanOrEqualTo(seconds - 1)); + + // Binary + jedis.hset(bfoo, bbar, bcar); + assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + + jedis.hset(bfoo, bbar, bcar); + assertByteArrayListEquals(asList(bcar), jedis.hgetex(bfoo, HGetExParams.hGetExParams().persist(), bbar)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0)); + + jedis.hset(bfoo, bbar, bcar); + jedis.hset(bfoo, bbare, bcare); + assertByteArrayListEquals(asList(bcar, bcare), jedis.hgetex(bfoo, HGetExParams.hGetExParams().ex(seconds), bbar, bbare)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1)); + } + + @Test + public void hsetex() { + long seconds = 20; + jedis.del("foo"); + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().ex(seconds).fnx(), "bar", "car")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().keepTtl(), "bar", "car")); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().fxx(), "bar", "car")); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bar").get(0)); + + HashMap hash = new HashMap(); + hash.put("bar", "car"); + hash.put("bare", "care"); + jedis.del("foo"); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().ex(seconds).fnx(), hash)); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl("foo", "bare").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().keepTtl(), hash)); + assertThat(jedis.httl("foo", "bar").get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl("foo", "bare").get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex("foo", HSetExParams.hSetExParams().fxx(), hash)); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bar").get(0)); + assertEquals(Long.valueOf(-1), jedis.httl("foo", "bare").get(0)); + + // Binary + jedis.del(bfoo); + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bbar, bcar)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bbar, bcar)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bbar, bcar)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0)); + + HashMap bhash = new HashMap(); + bhash.put(bbar, bcar); + bhash.put(bbare, bcare); + + jedis.del(bfoo); + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().ex(seconds).fnx(), bhash)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().keepTtl(), bhash)); + assertThat(jedis.httl(bfoo, bbar).get(0), greaterThanOrEqualTo(seconds - 1)); + assertThat(jedis.httl(bfoo, bbare).get(0), greaterThanOrEqualTo(seconds - 1)); + + assertEquals(1, jedis.hsetex(bfoo, HSetExParams.hSetExParams().fxx(), bhash)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbar).get(0)); + assertEquals(Long.valueOf(-1), jedis.httl(bfoo, bbare).get(0)); + } + + @Test + public void hexpireAndHttl() { + long seconds1 = 20; + long seconds2 = 10; + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList(1L, -2L), jedis.hexpire("foo", seconds1, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(0L, 1L), jedis.hexpire("foo", seconds2, ExpiryOption.NX, "bar", "bared")); + + assertThat(jedis.httl("foo", "bar", "bare", "bared"), + contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hexpireAndHttlBinary() { + long seconds1 = 20; + long seconds2 = 10; + + jedis.hset(bfoo, bbar1, bcar); + jedis.hset(bfoo, bbar2, bcar); + assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds1, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(0L, 1L), jedis.hexpire(bfoo, seconds2, ExpiryOption.NX, bbar1, bbar3)); + + assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3), + contains(greaterThanOrEqualTo(seconds1 - 1), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hpexpireAndHpttl() { + long millis1 = 20_000; + long millis2 = 10_000; + + jedis.hset("foo", "bar", "car"); + assertEquals(asList(1L, -2L), jedis.hpexpire("foo", millis1, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(1L, 0L), jedis.hpexpire("foo", millis2, ExpiryOption.XX, "bar", "bared")); + + assertThat(jedis.hpttl("foo", "bar", "bare", "bared"), + contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 10)), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpexpireAndHpttlBinary() { + long millis1 = 20_000; + long millis2 = 10_000; + + jedis.hset(bfoo, bbar1, bcar); + assertEquals(asList(1L, -2L), jedis.hpexpire(bfoo, millis1, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(1L, 0L), jedis.hpexpire(bfoo, millis2, ExpiryOption.XX, bbar1, bbar3)); + + assertThat(jedis.hpttl(bfoo, bbar1, bbar2, bbar3), + contains(both(lessThanOrEqualTo(millis2)).and(greaterThan(millis2 - 10)), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hexpireAtAndExpireTime() { + long currSeconds = System.currentTimeMillis() / 1000; + long seconds1 = currSeconds + 20; + long seconds2 = currSeconds + 10; + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList(1L, -2L), jedis.hexpireAt("foo", seconds1, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(1L, 1L), jedis.hexpireAt("foo", seconds2, ExpiryOption.LT, "bar", "bared")); + + assertThat(jedis.hexpireTime("foo", "bar", "bare", "bared"), + contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hexpireAtAndExpireTimeBinary() { + long currSeconds = System.currentTimeMillis() / 1000; + long seconds1 = currSeconds + 20; + long seconds2 = currSeconds + 10; + + jedis.hset(bfoo, bbar1, bcar); + jedis.hset(bfoo, bbar2, bcar); + assertEquals(asList(1L, -2L), jedis.hexpireAt(bfoo, seconds1, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(1L, 1L), jedis.hexpireAt(bfoo, seconds2, ExpiryOption.LT, bbar1, bbar3)); + + assertThat(jedis.hexpireTime(bfoo, bbar1, bbar2, bbar3), + contains(both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)), equalTo(-1L), + both(lessThanOrEqualTo(seconds2)).and(greaterThanOrEqualTo(seconds2 - 1)))); + } + + @Test + public void hpexpireAtAndPexpireTime() { + long currMillis = System.currentTimeMillis(); + long unixMillis = currMillis + 20_000; + + jedis.hset("foo", "bar", "car"); + assertEquals(asList(1L, -2L), jedis.hpexpireAt("foo", unixMillis - 100, "bar", "bared")); + + jedis.hset("foo", "bared", "cared"); + assertEquals(asList(1L, 0L), jedis.hpexpireAt("foo", unixMillis, ExpiryOption.GT, "bar", "bared")); + + assertThat(jedis.hpexpireTime("foo", "bar", "bare", "bared"), + contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpexpireAtAndPexpireTimeBinary() { + long currMillis = System.currentTimeMillis(); + long unixMillis = currMillis + 20_000; + + jedis.hset(bfoo, bbar1, bcar); + assertEquals(asList(1L, -2L), jedis.hpexpireAt(bfoo, unixMillis - 100, bbar1, bbar3)); + + jedis.hset(bfoo, bbar3, bcar); + assertEquals(asList(1L, 0L), jedis.hpexpireAt(bfoo, unixMillis, ExpiryOption.GT, bbar1, bbar3)); + + assertThat(jedis.hpexpireTime(bfoo, bbar1, bbar2, bbar3), + contains(equalTo(unixMillis), equalTo(-2L), equalTo(-1L))); + } + + @Test + public void hpersist() { + long seconds = 20; + + jedis.hset("foo", "bar", "car"); + jedis.hset("foo", "bare", "care"); + assertEquals(asList(1L, -2L), jedis.hexpire("foo", seconds, "bar", "bared")); + + assertEquals(asList(1L, -1L, -2L), jedis.hpersist("foo", "bar", "bare", "bared")); + + assertThat(jedis.httl("foo", "bar", "bare", "bared"), + contains(equalTo(-1L), equalTo(-1L), equalTo(-2L))); + } + + @Test + public void hpersistBinary() { + long seconds = 20; + + jedis.hset(bfoo, bbar1, bcar); + jedis.hset(bfoo, bbar2, bcar); + assertEquals(asList(1L, -2L), jedis.hexpire(bfoo, seconds, bbar1, bbar3)); + + assertEquals(asList(1L, -1L, -2L), jedis.hpersist(bfoo, bbar1, bbar2, bbar3)); + + assertThat(jedis.httl(bfoo, bbar1, bbar2, bbar3), + contains(equalTo(-1L), equalTo(-1L), equalTo(-2L))); + } }