From 567a31eae9991055f5cdcdbacef42441f2dbbf55 Mon Sep 17 00:00:00 2001 From: Dltmd202 Date: Thu, 31 Oct 2024 22:58:38 +0900 Subject: [PATCH] Deprecate the STRALGO command and implement the LCS in its place --- .../core/AbstractRedisAsyncCommands.java | 5 + .../core/AbstractRedisReactiveCommands.java | 5 + src/main/java/io/lettuce/core/LcsArgs.java | 141 ++++++++++++++++++ .../io/lettuce/core/RedisCommandBuilder.java | 8 + .../api/async/RedisStringAsyncCommands.java | 19 +++ .../reactive/RedisStringReactiveCommands.java | 19 +++ .../core/api/sync/RedisStringCommands.java | 19 +++ .../io/lettuce/core/protocol/CommandType.java | 2 +- .../StringCommandIntegrationTests.java | 92 ++++-------- 9 files changed, 247 insertions(+), 63 deletions(-) create mode 100644 src/main/java/io/lettuce/core/LcsArgs.java diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java index 9c8b52849b..c9ba85d2de 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java @@ -2414,6 +2414,11 @@ public RedisFuture stralgoLcs(StrAlgoArgs args) { return dispatch(commandBuilder.stralgoLcs(args)); } + @Override + public RedisFuture lcs(LcsArgs args) { + return dispatch(commandBuilder.lcs(args)); + } + @Override public RedisFuture> sunion(K... keys) { return dispatch(commandBuilder.sunion(keys)); diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java index eb7e911ca4..4e4f1a4d20 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java @@ -2499,6 +2499,11 @@ public Mono stralgoLcs(StrAlgoArgs strAlgoArgs) { return createMono(() -> commandBuilder.stralgoLcs(strAlgoArgs)); } + @Override + public Mono lcs(LcsArgs lcsArgs) { + return createMono(() -> commandBuilder.lcs(lcsArgs)); + } + @Override public Flux sunion(K... keys) { return createDissolvingFlux(() -> commandBuilder.sunion(keys)); diff --git a/src/main/java/io/lettuce/core/LcsArgs.java b/src/main/java/io/lettuce/core/LcsArgs.java new file mode 100644 index 0000000000..8d9ad86ddd --- /dev/null +++ b/src/main/java/io/lettuce/core/LcsArgs.java @@ -0,0 +1,141 @@ +/* + * Copyright 2011-Present, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + * + * This file contains contributions from third-party contributors + * licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; + +/** + * Argument list builder for the Redis LCS command. Static import the methods from + * {@link LcsArgs.Builder} and call the methods: {@code keys(…)} . + *

+ * {@link LcsArgs} is a mutable object and instances should be used only once to avoid shared mutable state. + * + * @author Dltmd202 + * @since 7.0 + */ +public class LcsArgs implements CompositeArgument { + + private boolean justLen; + + private int minMatchLen; + + private boolean withMatchLen; + + private boolean withIdx; + + private String[] keys; + + /** + * Builder entry points for {@link LcsArgs}. + */ + public static class Builder { + + /** + * Utility constructor. + */ + private Builder() { + } + + /** + * Creates new {@link LcsArgs} by keys. + * + * @return new {@link LcsArgs} with {@literal By KEYS} set. + */ + public static LcsArgs keys(String... keys) { + return new LcsArgs().by(keys); + } + + } + + /** + * restrict the list of matches to the ones of a given minimal length. + * + * @return {@code this} {@link LcsArgs}. + */ + public LcsArgs minMatchLen(int minMatchLen) { + this.minMatchLen = minMatchLen; + return this; + } + + /** + * Request just the length of the match for results. + * + * @return {@code this} {@link LcsArgs}. + */ + public LcsArgs justLen() { + justLen = true; + return this; + } + + /** + * Request match len for results. + * + * @return {@code this} {@link LcsArgs}. + */ + public LcsArgs withMatchLen() { + withMatchLen = true; + return this; + } + + /** + * Request match position in each strings for results. + * + * @return {@code this} {@link LcsArgs}. + */ + public LcsArgs withIdx() { + withIdx = true; + return this; + } + + public LcsArgs by(String... keys) { + LettuceAssert.notEmpty(keys, "Keys must not be empty"); + + this.keys = keys; + return this; + } + + public boolean isWithIdx() { + return withIdx; + } + + @Override + public void build(CommandArgs args) { + for (String key : keys) { + args.add(key); + } + if (justLen) { + args.add("LEN"); + } + if (withIdx) { + args.add("IDX"); + } + + if (minMatchLen > 0) { + args.add("MINMATCHLEN"); + args.add(minMatchLen); + } + + if (withMatchLen) { + args.add("WITHMATCHLEN"); + } + } + +} diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java index ff403c6dda..8a9dc3da1b 100644 --- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java @@ -2912,6 +2912,14 @@ Command stralgoLcs(StrAlgoArgs strAlgoArgs) { return createCommand(STRALGO, new StringMatchResultOutput<>(codec), args); } + Command lcs(LcsArgs lcsArgs) { + LettuceAssert.notNull(lcsArgs, "lcsArgs" + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec); + lcsArgs.build(args); + return createCommand(LCS, new StringMatchResultOutput<>(codec), args); + } + Command> sunion(K... keys) { notEmpty(keys); diff --git a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java index 1e5b1d3d05..e514db52ce 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java @@ -27,6 +27,7 @@ import io.lettuce.core.KeyValue; import io.lettuce.core.RedisFuture; import io.lettuce.core.SetArgs; +import io.lettuce.core.LcsArgs; import io.lettuce.core.StrAlgoArgs; import io.lettuce.core.StringMatchResult; import io.lettuce.core.output.KeyValueStreamingChannel; @@ -424,8 +425,26 @@ public interface RedisStringAsyncCommands { * @return StringMatchResult. * @since 6.0 */ + @Deprecated RedisFuture stralgoLcs(StrAlgoArgs strAlgoArgs); + /** + * The LCS command implements the longest common subsequence algorithm. + * + *

    + *
  • Without modifiers the string representing the longest common substring is returned.
  • + *
  • When {@link LcsArgs#justLen() LEN} is given the command returns the length of the longest common substring.
  • + *
  • When {@link LcsArgs#withIdx() IDX} is given the command returns an array with the LCS length and all the ranges in + * both the strings, start and end offset for each string, where there are matches. When {@link LcsArgs#withMatchLen() + * WITHMATCHLEN} is given each array representing a match will also have the length of the match.
  • + *
+ * + * @param lcsArgs command arguments. + * @return StringMatchResult. + * @since 7.0 + */ + RedisFuture lcs(LcsArgs lcsArgs); + /** * Get the length of the value stored in a key. * diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java index 817c235375..454677df40 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java @@ -28,6 +28,7 @@ import io.lettuce.core.KeyValue; import io.lettuce.core.SetArgs; import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.LcsArgs; import io.lettuce.core.StringMatchResult; import io.lettuce.core.Value; import io.lettuce.core.output.KeyValueStreamingChannel; @@ -428,8 +429,26 @@ public interface RedisStringReactiveCommands { * @return StringMatchResult. * @since 6.0 */ + @Deprecated Mono stralgoLcs(StrAlgoArgs strAlgoArgs); + /** + * The LCS command implements the longest common subsequence algorithm. + * + *
    + *
  • Without modifiers the string representing the longest common substring is returned.
  • + *
  • When {@link LcsArgs#justLen() LEN} is given the command returns the length of the longest common substring.
  • + *
  • When {@link LcsArgs#withIdx() IDX} is given the command returns an array with the LCS length and all the ranges in + * both the strings, start and end offset for each string, where there are matches. When {@link LcsArgs#withMatchLen() + * WITHMATCHLEN} is given each array representing a match will also have the length of the match.
  • + *
+ * + * @param lcsArgs command arguments. + * @return StringMatchResult. + * @since 7.0 + */ + Mono lcs(LcsArgs lcsArgs); + /** * Get the length of the value stored in a key. * diff --git a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java index 03280bf924..2f552ed5bf 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java @@ -27,6 +27,7 @@ import io.lettuce.core.KeyValue; import io.lettuce.core.SetArgs; import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.LcsArgs; import io.lettuce.core.StringMatchResult; import io.lettuce.core.output.KeyValueStreamingChannel; @@ -423,8 +424,26 @@ public interface RedisStringCommands { * @return StringMatchResult. * @since 6.0 */ + @Deprecated StringMatchResult stralgoLcs(StrAlgoArgs strAlgoArgs); + /** + * The LCS command implements the longest common subsequence algorithm. + * + *
    + *
  • Without modifiers the string representing the longest common substring is returned.
  • + *
  • When {@link LcsArgs#justLen() LEN} is given the command returns the length of the longest common substring.
  • + *
  • When {@link LcsArgs#withIdx() IDX} is given the command returns an array with the LCS length and all the ranges in + * both the strings, start and end offset for each string, where there are matches. When {@link LcsArgs#withMatchLen() + * WITHMATCHLEN} is given each array representing a match will also have the length of the match.
  • + *
+ * + * @param lcsArgs command arguments. + * @return StringMatchResult. + * @since 7.0 + */ + StringMatchResult lcs(LcsArgs lcsArgs); + /** * Get the length of the value stored in a key. * diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java index 7eb949e5d3..9a2fcf83f6 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandType.java +++ b/src/main/java/io/lettuce/core/protocol/CommandType.java @@ -50,7 +50,7 @@ public enum CommandType implements ProtocolKeyword { // String - APPEND, GET, GETDEL, GETEX, GETRANGE, GETSET, MGET, MSET, MSETNX, SET, SETEX, PSETEX, SETNX, SETRANGE, STRLEN, STRALGO, + APPEND, GET, GETDEL, GETEX, GETRANGE, GETSET, MGET, MSET, MSETNX, SET, SETEX, PSETEX, SETNX, SETRANGE, STRLEN, STRALGO, LCS, // Numeric diff --git a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java index b32a9beb86..cdfed4ec3d 100644 --- a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java @@ -382,48 +382,36 @@ void strAlgoWithIdx() { @Test @EnabledOnCommand("LCS") void lcs() { - redis.set("key1", "ohmytext"); - redis.set("key2", "mynewtext"); + redis.set("key1{k}", "ohmytext"); + redis.set("key2{k}", "mynewtext"); - // LCS key1 key2 - CustomStringCommands commands = CustomStringCommands.instance(getConnection()); - StringMatchResult matchResult = commands.lcs("key1", "key2"); - assertThat(matchResult.getMatchString()).isEqualTo("mytext"); + // LCS key1{k} key2{k} IDX MINMATCHLEN 4 WITHMATCHLEN + StringMatchResult matchResult = redis.lcs(LcsArgs.Builder.keys("key1{k}", "key2{k}")); + System.out.println("StringCommandIntegrationTests.lcs: " + matchResult.getMatchString()); - // LCS a b IDX MINMATCHLEN 4 WITHMATCHLEN - // Keys don't exist. - matchResult = commands.lcsMinMatchLenWithMatchLen("a", "b", 4); - assertThat(matchResult.getMatchString()).isNullOrEmpty(); - assertThat(matchResult.getLen()).isEqualTo(0); + assertThat(matchResult.getMatchString()).isEqualTo("mytext"); } @Test @EnabledOnCommand("LCS") void lcsUsingKeys() { - redis.set("key1{k}", "ohmytext"); redis.set("key2{k}", "mynewtext"); - CustomStringCommands commands = CustomStringCommands.instance(getConnection()); + StringMatchResult matchResult = redis.lcs(LcsArgs.Builder.keys("key1{k}", "key2{k}")); + System.out.println("StringCommandIntegrationTests.lcsUsingKeys: " + matchResult.getMatchString()); - StringMatchResult matchResult = commands.lcs("key1{k}", "key2{k}"); assertThat(matchResult.getMatchString()).isEqualTo("mytext"); - - // STRALGO LCS STRINGS a b - matchResult = commands.lcsMinMatchLenWithMatchLen("a", "b", 4); - assertThat(matchResult.getMatchString()).isNullOrEmpty(); - assertThat(matchResult.getLen()).isEqualTo(0); } @Test @EnabledOnCommand("LCS") void lcsJustLen() { - redis.set("one", "ohmytext"); - redis.set("two", "mynewtext"); - - CustomStringCommands commands = CustomStringCommands.instance(getConnection()); + redis.set("one{k}", "ohmytext"); + redis.set("two{k}", "mynewtext"); - StringMatchResult matchResult = commands.lcsLen("one", "two"); + StringMatchResult matchResult = redis.lcs(LcsArgs.Builder.keys("one{k}", "two{k}").justLen()); + System.out.println("StringCommandIntegrationTests.lcsJustLen: " + matchResult.getMatchString()); assertThat(matchResult.getLen()).isEqualTo(6); } @@ -431,12 +419,11 @@ void lcsJustLen() { @Test @EnabledOnCommand("LCS") void lcsWithMinMatchLen() { - redis.set("key1", "ohmytext"); - redis.set("key2", "mynewtext"); - - CustomStringCommands commands = CustomStringCommands.instance(getConnection()); + redis.set("key1{k}", "ohmytext"); + redis.set("key2{k}", "mynewtext"); - StringMatchResult matchResult = commands.lcsMinMatchLen("key1", "key2", 4); + StringMatchResult matchResult = redis.lcs(LcsArgs.Builder.keys("key1{k}", "key2{k}").minMatchLen(4)); + System.out.println("StringCommandIntegrationTests.lcsWithMinMatchLen: " + matchResult.getMatchString()); assertThat(matchResult.getMatchString()).isEqualTo("mytext"); } @@ -444,13 +431,14 @@ void lcsWithMinMatchLen() { @Test @EnabledOnCommand("LCS") void lcsMinMatchLenIdxMatchLen() { - redis.set("key1", "ohmytext"); - redis.set("key2", "mynewtext"); - - CustomStringCommands commands = CustomStringCommands.instance(getConnection()); + redis.set("key1{k}", "ohmytext"); + redis.set("key2{k}", "mynewtext"); - // LCS key1 key2 IDX MINMATCHLEN 4 WITHMATCHLEN - StringMatchResult matchResult = commands.lcsMinMatchLenWithMatchLen("key1", "key2", 4); + StringMatchResult matchResult = redis.lcs(LcsArgs.Builder.keys("key1{k}", "key2{k}").minMatchLen(4).withMatchLen()); + System.out.println( + "StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen, getMatchString: " + matchResult.getMatchString()); + System.out.println("StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen, getMatches().hasSize " + + matchResult.getMatches().size()); assertThat(matchResult.getMatches()).hasSize(1); assertThat(matchResult.getMatches().get(0).getMatchLen()).isEqualTo(4); @@ -458,6 +446,13 @@ void lcsMinMatchLenIdxMatchLen() { Position a = matchResult.getMatches().get(0).getA(); Position b = matchResult.getMatches().get(0).getB(); + System.out.println("StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen.a.start: " + a.getStart()); + System.out.println("StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen.a.end: " + a.getEnd()); + System.out.println("StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen.b.start: " + b.getStart()); + System.out.println("StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen.b.end: " + b.getEnd()); + System.out.println( + "StringCommandIntegrationTests.lcsMinMatchLenIdxMatchLen.matchResult.getLen(): " + matchResult.getLen()); + assertThat(a.getStart()).isEqualTo(4); assertThat(a.getEnd()).isEqualTo(7); assertThat(b.getStart()).isEqualTo(5); @@ -465,31 +460,4 @@ void lcsMinMatchLenIdxMatchLen() { assertThat(matchResult.getLen()).isEqualTo(6); } - protected StatefulConnection getConnection() { - StatefulRedisConnection src = redis.getStatefulConnection(); - Assumptions.assumeFalse(Proxy.isProxyClass(src.getClass()), "Redis connection is proxy, skipping."); - return src; - } - - private interface CustomStringCommands extends Commands { - - @Command("LCS :k1 :k2") - StringMatchResult lcs(@Param("k1") String k1, @Param("k2") String k2); - - @Command("LCS :k1 :k2 LEN") - StringMatchResult lcsLen(@Param("k1") String k1, @Param("k2") String k2); - - @Command("LCS :k1 :k2 MINMATCHLEN :mml") - StringMatchResult lcsMinMatchLen(@Param("k1") String k1, @Param("k2") String k2, @Param("mml") int mml); - - @Command("LCS :k1 :k2 IDX MINMATCHLEN :mml WITHMATCHLEN") - StringMatchResult lcsMinMatchLenWithMatchLen(@Param("k1") String k1, @Param("k2") String k2, @Param("mml") int mml); - - static CustomStringCommands instance(StatefulConnection conn) { - RedisCommandFactory factory = new RedisCommandFactory(conn); - return factory.getCommands(CustomStringCommands.class); - } - - } - }