diff --git a/docs/jedis5-breaking.md b/docs/jedis5-breaking.md index f9d5df7ad4..7b373b2e77 100644 --- a/docs/jedis5-breaking.md +++ b/docs/jedis5-breaking.md @@ -42,6 +42,10 @@ - `getAgeSeconds()` in `AccessControlLogEntry` now returns `Double` instead of `String`. +- Both `ftConfigGet(String option)` and `ftConfigGet(String indexName, String option)` methods now return `Map` instead of `Map`. + +- `ftList()` method now returns `Set` instead of `List`. + - `graphSlowlog(String graphName)` now returns `List>` (instead of `List>`). - All _payload_ related parameters are removed from _search_ related classes; namely `Document`, `IndexDefinition`, `Query`. @@ -74,6 +78,10 @@ - `getParams()` method is removed from `SortingParams` class. +- Both `SEARCH_AGGREGATION_RESULT` and `SEARCH_AGGREGATION_RESULT_WITH_CURSOR` implementations from `SearchBuilderFactory` class have been moved to `AggregationResult` class. + +- All `AggregationResult` constructors have been made `private`. + - `addCommandEncodedArguments` and `addCommandBinaryArguments` methods have been removed from `FieldName` class. - `getArgs` method is removed from `AggregationBuilder` class. diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index 5e5d03a830..20a061d6b9 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -290,52 +290,57 @@ public String toString() { } }; - public static final Builder> ENCODED_OBJECT_MAP = new Builder>() { + public static final Builder> BINARY_MAP = new Builder>() { @Override - public Map build(Object data) { - if (data == null) return null; + @SuppressWarnings("unchecked") + public Map build(Object data) { final List list = (List) data; if (list.isEmpty()) return Collections.emptyMap(); if (list.get(0) instanceof KeyValue) { - final Map map = new HashMap<>(list.size(), 1f); + final Map map = new JedisByteHashMap(); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { KeyValue kv = (KeyValue) iterator.next(); - map.put(STRING.build(kv.getKey()), ENCODED_OBJECT.build(kv.getValue())); + map.put(BINARY.build(kv.getKey()), BINARY.build(kv.getValue())); } return map; } else { - final Map map = new HashMap<>(list.size() / 2, 1f); + final Map map = new JedisByteHashMap(); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { - map.put(STRING.build(iterator.next()), ENCODED_OBJECT.build(iterator.next())); + map.put(BINARY.build(iterator.next()), BINARY.build(iterator.next())); } return map; } } + + @Override + public String toString() { + return "Map"; + } }; - public static final Builder> BINARY_MAP = new Builder>() { + public static final Builder> STRING_MAP = new Builder>() { @Override @SuppressWarnings("unchecked") - public Map build(Object data) { + public Map build(Object data) { final List list = (List) data; if (list.isEmpty()) return Collections.emptyMap(); if (list.get(0) instanceof KeyValue) { - final Map map = new JedisByteHashMap(); + final Map map = new HashMap<>(list.size(), 1f); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { KeyValue kv = (KeyValue) iterator.next(); - map.put(BINARY.build(kv.getKey()), BINARY.build(kv.getValue())); + map.put(STRING.build(kv.getKey()), STRING.build(kv.getValue())); } return map; } else { - final Map map = new JedisByteHashMap(); + final Map map = new HashMap<>(list.size() / 2, 1f); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { - map.put(BINARY.build(iterator.next()), BINARY.build(iterator.next())); + map.put(STRING.build(iterator.next()), STRING.build(iterator.next())); } return map; } @@ -343,38 +348,64 @@ public Map build(Object data) { @Override public String toString() { - return "Map"; + return "Map"; } }; - public static final Builder> STRING_MAP = new Builder>() { + public static final Builder> ENCODED_OBJECT_MAP = new Builder>() { @Override - @SuppressWarnings("unchecked") - public Map build(Object data) { + public Map build(Object data) { + if (data == null) return null; final List list = (List) data; if (list.isEmpty()) return Collections.emptyMap(); if (list.get(0) instanceof KeyValue) { - final Map map = new HashMap<>(list.size(), 1f); + final Map map = new HashMap<>(list.size(), 1f); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { KeyValue kv = (KeyValue) iterator.next(); - map.put(STRING.build(kv.getKey()), STRING.build(kv.getValue())); + map.put(STRING.build(kv.getKey()), ENCODED_OBJECT.build(kv.getValue())); } return map; } else { - final Map map = new HashMap<>(list.size() / 2, 1f); + final Map map = new HashMap<>(list.size() / 2, 1f); final Iterator iterator = list.iterator(); while (iterator.hasNext()) { - map.put(STRING.build(iterator.next()), STRING.build(iterator.next())); + map.put(STRING.build(iterator.next()), ENCODED_OBJECT.build(iterator.next())); } return map; } } + }; + public static final Builder AGGRESSIVE_ENCODED_OBJECT = new Builder() { @Override - public String toString() { - return "Map"; + public Object build(Object data) { + if (data == null) return null; + + if (data instanceof List) { + final List list = (List) data; + if (list.isEmpty()) return Collections.emptyMap(); + + if (list.get(0) instanceof KeyValue) { + return ((List) data).stream() + .filter(kv -> kv != null && kv.getKey() != null && kv.getValue() != null) + .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()), + kv -> this.build(kv.getValue()))); + } else { + return list.stream().map(this::build).collect(Collectors.toList()); + } + } else if (data instanceof byte[]) { + return STRING.build(data); + } + return data; + } + }; + + public static final Builder> AGGRESSIVE_ENCODED_OBJECT_MAP = new Builder>() { + @Override + public Map build(Object data) { + return (Map) AGGRESSIVE_ENCODED_OBJECT.build(data); } }; @@ -1736,7 +1767,15 @@ private void addMatchedPosition(List matchedPositions, Object o @Override @SuppressWarnings("unchecked") public Map build(Object data) { - final List list = (List) data; + final List list = (List) data; + if (list.isEmpty()) return Collections.emptyMap(); + + if (list.get(0) instanceof KeyValue) { + return ((List) list).stream() + .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()), + kv -> STRING.build(kv.getValue()))); + } + final Map map = new HashMap<>(list.size()); for (Object object : list) { if (object == null) continue; @@ -1753,6 +1792,35 @@ public String toString() { } }; + public static final Builder> ENCODED_OBJECT_MAP_FROM_PAIRS = new Builder>() { + @Override + @SuppressWarnings("unchecked") + public Map build(Object data) { + final List list = (List) data; + if (list.isEmpty()) return Collections.emptyMap(); + + if (list.get(0) instanceof KeyValue) { + return ((List) list).stream() + .collect(Collectors.toMap(kv -> STRING.build(kv.getKey()), + kv -> ENCODED_OBJECT.build(kv.getValue()))); + } + + final Map map = new HashMap<>(list.size()); + for (Object object : list) { + if (object == null) continue; + final List flat = (List) object; + if (flat.isEmpty()) continue; + map.put(STRING.build(flat.get(0)), STRING.build(flat.get(1))); + } + return map; + } + + @Override + public String toString() { + return "Map"; + } + }; + public static final Builder> LIBRARY_LIST = new Builder>() { @Override public List build(Object data) { diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 60031520a2..b22cf94d2a 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -5,6 +5,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.json.JSONArray; import org.json.JSONObject; @@ -34,10 +35,14 @@ public class CommandObjects { - private RedisProtocol proto; + private RedisProtocol protocol; protected void setProtocol(RedisProtocol proto) { - this.proto = proto; + this.protocol = proto; + } + + protected RedisProtocol getProtocol() { + return protocol; } private volatile JsonObjectMapper jsonObjectMapper; @@ -1094,7 +1099,7 @@ public final CommandObject> hrandfield(String key, long count) { public final CommandObject>> hrandfieldWithValues(String key, long count) { return new CommandObject<>(commandArguments(HRANDFIELD).key(key).add(count).add(WITHVALUES), - proto != RedisProtocol.RESP3 ? BuilderFactory.STRING_PAIR_LIST : BuilderFactory.STRING_PAIR_LIST_FROM_PAIRS); + protocol != RedisProtocol.RESP3 ? BuilderFactory.STRING_PAIR_LIST : BuilderFactory.STRING_PAIR_LIST_FROM_PAIRS); } public final CommandObject> hgetAll(byte[] key) { @@ -1111,7 +1116,7 @@ public final CommandObject> hrandfield(byte[] key, long count) { public final CommandObject>> hrandfieldWithValues(byte[] key, long count) { return new CommandObject<>(commandArguments(HRANDFIELD).key(key).add(count).add(WITHVALUES), - proto != RedisProtocol.RESP3 ? BuilderFactory.BINARY_PAIR_LIST : BuilderFactory.BINARY_PAIR_LIST_FROM_PAIRS); + protocol != RedisProtocol.RESP3 ? BuilderFactory.BINARY_PAIR_LIST : BuilderFactory.BINARY_PAIR_LIST_FROM_PAIRS); } public final CommandObject>> hscan(String key, String cursor, ScanParams params) { @@ -1992,11 +1997,11 @@ public final CommandObject>> bzmpop(double timeout, } private Builder> getTupleListBuilder() { - return proto == RedisProtocol.RESP3 ? BuilderFactory.TUPLE_LIST_RESP3 : BuilderFactory.TUPLE_LIST; + return protocol == RedisProtocol.RESP3 ? BuilderFactory.TUPLE_LIST_RESP3 : BuilderFactory.TUPLE_LIST; } private Builder> getTupleSetBuilder() { - return proto == RedisProtocol.RESP3 ? BuilderFactory.TUPLE_ZSET_RESP3 : BuilderFactory.TUPLE_ZSET; + return protocol == RedisProtocol.RESP3 ? BuilderFactory.TUPLE_ZSET_RESP3 : BuilderFactory.TUPLE_ZSET; } // Sorted Set commands @@ -3190,24 +3195,29 @@ public final CommandObject ftAlter(String indexName, Iterable ftSearch(String indexName, String query) { return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.SEARCH), indexName).add(query), - new SearchResultBuilder(true, false, true)); + getSearchResultBuilder(() -> new SearchResultBuilder(true, false, true))); } public final CommandObject ftSearch(String indexName, String query, FTSearchParams params) { return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.SEARCH), indexName) - .add(query).addParams(params.dialectOptional(searchDialect.get())), new SearchResultBuilder(!params.getNoContent(), params.getWithScores(), true)); + .add(query).addParams(params.dialectOptional(searchDialect.get())), + getSearchResultBuilder(() -> new SearchResultBuilder(!params.getNoContent(), params.getWithScores(), true))); } public final CommandObject ftSearch(String indexName, Query query) { return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.SEARCH), indexName) - .addParams(query.dialectOptional(searchDialect.get())), - new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)); + .addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(() -> + new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true))); } + @Deprecated public final CommandObject ftSearch(byte[] indexName, Query query) { + if (protocol == RedisProtocol.RESP3) { + throw new UnsupportedOperationException("binary ft.search is not implemented with resp3."); + } return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.SEARCH), indexName) - .addParams(query.dialectOptional(searchDialect.get())), - new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), false)); + .addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(() -> + new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), false))); } public final CommandObject ftExplain(String indexName, Query query) { @@ -3222,14 +3232,14 @@ public final CommandObject> ftExplainCLI(String indexName, Query qu public final CommandObject ftAggregate(String indexName, AggregationBuilder aggr) { return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.AGGREGATE), indexName) - .addParams(aggr.dialectOptional(searchDialect.get())), !aggr.isWithCursor() ? SearchBuilderFactory.SEARCH_AGGREGATION_RESULT - : SearchBuilderFactory.SEARCH_AGGREGATION_RESULT_WITH_CURSOR); + .addParams(aggr.dialectOptional(searchDialect.get())), !aggr.isWithCursor() ? AggregationResult.SEARCH_AGGREGATION_RESULT + : AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR); } public final CommandObject ftCursorRead(String indexName, long cursorId, int count) { return new CommandObject<>(commandArguments(SearchCommand.CURSOR).add(SearchKeyword.READ) .add(indexName).add(cursorId).add(SearchKeyword.COUNT).add(count), - SearchBuilderFactory.SEARCH_AGGREGATION_RESULT_WITH_CURSOR); + AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR); } public final CommandObject ftCursorDel(String indexName, long cursorId) { @@ -3241,9 +3251,9 @@ public final CommandObject>> ft String indexName, FTProfileParams profileParams, AggregationBuilder aggr) { return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.PROFILE), indexName) .add(SearchKeyword.AGGREGATE).addParams(profileParams).add(SearchKeyword.QUERY) - .addParams(aggr.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>(!aggr.isWithCursor() - ? SearchBuilderFactory.SEARCH_AGGREGATION_RESULT - : SearchBuilderFactory.SEARCH_AGGREGATION_RESULT_WITH_CURSOR)); + .addParams(aggr.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>( + !aggr.isWithCursor() ? AggregationResult.SEARCH_AGGREGATION_RESULT + : AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR)); } public final CommandObject>> ftProfileSearch( @@ -3251,7 +3261,7 @@ public final CommandObject>> ftProfi return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.PROFILE), indexName) .add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY) .addParams(query.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>( - new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true))); + getSearchResultBuilder(() -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)))); } public final CommandObject>> ftProfileSearch( @@ -3259,7 +3269,12 @@ public final CommandObject>> ftProfi return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.PROFILE), indexName) .add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY).add(query) .addParams(searchParams.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>( - new SearchResultBuilder(!searchParams.getNoContent(), searchParams.getWithScores(), true))); + getSearchResultBuilder(() -> new SearchResultBuilder(!searchParams.getNoContent(), searchParams.getWithScores(), true)))); + } + + private Builder getSearchResultBuilder(Supplier> resp2) { + if (protocol == RedisProtocol.RESP3) return SearchResult.SEARCH_RESULT_BUILDER; + return resp2.get(); } public final CommandObject ftDropIndex(String indexName) { @@ -3320,7 +3335,7 @@ public final CommandObject>> ftSpellCheck(String public final CommandObject> ftInfo(String indexName) { return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.INFO), indexName), - BuilderFactory.ENCODED_OBJECT_MAP); + protocol == RedisProtocol.RESP3 ? BuilderFactory.AGGRESSIVE_ENCODED_OBJECT_MAP : BuilderFactory.ENCODED_OBJECT_MAP); } public final CommandObject> ftTagVals(String indexName, String fieldName) { @@ -3340,11 +3355,12 @@ public final CommandObject ftAliasDel(String aliasName) { return new CommandObject<>(commandArguments(SearchCommand.ALIASDEL).add(aliasName), BuilderFactory.STRING); } - public final CommandObject> ftConfigGet(String option) { - return new CommandObject<>(commandArguments(SearchCommand.CONFIG).add(SearchKeyword.GET).add(option), BuilderFactory.STRING_MAP_FROM_PAIRS); + public final CommandObject> ftConfigGet(String option) { + return new CommandObject<>(commandArguments(SearchCommand.CONFIG).add(SearchKeyword.GET).add(option), + protocol == RedisProtocol.RESP3 ? BuilderFactory.AGGRESSIVE_ENCODED_OBJECT_MAP : BuilderFactory.ENCODED_OBJECT_MAP_FROM_PAIRS); } - public final CommandObject> ftConfigGet(String indexName, String option) { + public final CommandObject> ftConfigGet(String indexName, String option) { return directSearchCommand(ftConfigGet(option), indexName); } @@ -3396,8 +3412,8 @@ public final CommandObject ftSugLen(String key) { return new CommandObject<>(commandArguments(SearchCommand.SUGLEN).key(key), BuilderFactory.LONG); } - public final CommandObject> ftList() { - return new CommandObject<>(commandArguments(SearchCommand._LIST), BuilderFactory.STRING_LIST); + public final CommandObject> ftList() { + return new CommandObject<>(commandArguments(SearchCommand._LIST), BuilderFactory.STRING_SET); } // RediSearch commands @@ -3829,7 +3845,7 @@ public final CommandObject tsGet(String key, TSGetParams getParams) { public final CommandObject> tsMGet(TSMGetParams multiGetParams, String... filters) { return new CommandObject<>(commandArguments(TimeSeriesCommand.MGET).addParams(multiGetParams) .add(TimeSeriesKeyword.FILTER).addObjects((Object[]) filters), - proto == RedisProtocol.RESP3 ? TimeSeriesBuilderFactory.TIMESERIES_MGET_RESPONSE_RESP3 + protocol == RedisProtocol.RESP3 ? TimeSeriesBuilderFactory.TIMESERIES_MGET_RESPONSE_RESP3 : TimeSeriesBuilderFactory.TIMESERIES_MGET_RESPONSE); } @@ -3864,12 +3880,12 @@ public final CommandObject tsInfoDebug(String key) { } private Builder> getTimeseriesMultiRangeResponseBuilder() { - return proto == RedisProtocol.RESP3 ? TimeSeriesBuilderFactory.TIMESERIES_MRANGE_RESPONSE_RESP3 + return protocol == RedisProtocol.RESP3 ? TimeSeriesBuilderFactory.TIMESERIES_MRANGE_RESPONSE_RESP3 : TimeSeriesBuilderFactory.TIMESERIES_MRANGE_RESPONSE; } private Builder getTimeseriesInfoBuilder() { - return proto == RedisProtocol.RESP3 ? TSInfo.TIMESERIES_INFO_RESP3 : TSInfo.TIMESERIES_INFO; + return protocol == RedisProtocol.RESP3 ? TSInfo.TIMESERIES_INFO_RESP3 : TSInfo.TIMESERIES_INFO; } // RedisTimeSeries commands @@ -4203,6 +4219,8 @@ public void setDefaultSearchDialect(int dialect) { private class SearchProfileResponseBuilder extends Builder>> { + private static final String PROFILE_STR = "profile"; + private final Builder replyBuilder; public SearchProfileResponseBuilder(Builder replyBuilder) { @@ -4211,7 +4229,18 @@ public SearchProfileResponseBuilder(Builder replyBuilder) { @Override public Map.Entry> build(Object data) { - List list = (List) data; + List list = (List) data; + if (list == null || list.isEmpty()) return null; + + if (list.get(0) instanceof KeyValue) { + for (KeyValue keyValue : (List) data) { + if (PROFILE_STR.equals(BuilderFactory.STRING.build(keyValue.getKey()))) { + return KeyValue.of(replyBuilder.build(data), + BuilderFactory.AGGRESSIVE_ENCODED_OBJECT_MAP.build(keyValue.getValue())); + } + } + } + return KeyValue.of(replyBuilder.build(list.get(0)), SearchBuilderFactory.SEARCH_PROFILE_PROFILE.build(list.get(1))); } diff --git a/src/main/java/redis/clients/jedis/PipelineBase.java b/src/main/java/redis/clients/jedis/PipelineBase.java index 4511bfa8a5..b4b077a899 100644 --- a/src/main/java/redis/clients/jedis/PipelineBase.java +++ b/src/main/java/redis/clients/jedis/PipelineBase.java @@ -3299,6 +3299,7 @@ public Response ftSearch(String indexName, Query query) { } @Override + @Deprecated public Response ftSearch(byte[] indexName, Query query) { return appendCommand(commandObjects.ftSearch(indexName, query)); } @@ -3379,12 +3380,12 @@ public Response> ftTagVals(String indexName, String fieldName) { } @Override - public Response> ftConfigGet(String option) { + public Response> ftConfigGet(String option) { return appendCommand(commandObjects.ftConfigGet(option)); } @Override - public Response> ftConfigGet(String indexName, String option) { + public Response> ftConfigGet(String indexName, String option) { return appendCommand(commandObjects.ftConfigGet(indexName, option)); } diff --git a/src/main/java/redis/clients/jedis/TransactionBase.java b/src/main/java/redis/clients/jedis/TransactionBase.java index 2baf02f5c1..03513c2e40 100644 --- a/src/main/java/redis/clients/jedis/TransactionBase.java +++ b/src/main/java/redis/clients/jedis/TransactionBase.java @@ -3466,6 +3466,7 @@ public Response ftSearch(String indexName, Query query) { } @Override + @Deprecated public Response ftSearch(byte[] indexName, Query query) { return appendCommand(commandObjects.ftSearch(indexName, query)); } @@ -3546,12 +3547,12 @@ public Response> ftTagVals(String indexName, String fieldName) { } @Override - public Response> ftConfigGet(String option) { + public Response> ftConfigGet(String option) { return appendCommand(commandObjects.ftConfigGet(option)); } @Override - public Response> ftConfigGet(String indexName, String option) { + public Response> ftConfigGet(String indexName, String option) { return appendCommand(commandObjects.ftConfigGet(indexName, option)); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index bf2481f712..2063194290 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -3655,7 +3655,7 @@ public SearchResult ftSearch(String indexName, String query, FTSearchParams para * @return search iteration */ public FtSearchIteration ftSearchIteration(int batchSize, String indexName, String query, FTSearchParams params) { - return new FtSearchIteration(provider, batchSize, indexName, query, params); + return new FtSearchIteration(provider, commandObjects.getProtocol(), batchSize, indexName, query, params); } @Override @@ -3671,10 +3671,11 @@ public SearchResult ftSearch(String indexName, Query query) { * @return search iteration */ public FtSearchIteration ftSearchIteration(int batchSize, String indexName, Query query) { - return new FtSearchIteration(provider, batchSize, indexName, query); + return new FtSearchIteration(provider, commandObjects.getProtocol(), batchSize, indexName, query); } @Override + @Deprecated public SearchResult ftSearch(byte[] indexName, Query query) { return executeCommand(commandObjects.ftSearch(indexName, query)); } @@ -3818,12 +3819,12 @@ public String ftAliasDel(String aliasName) { } @Override - public Map ftConfigGet(String option) { + public Map ftConfigGet(String option) { return executeCommand(commandObjects.ftConfigGet(option)); } @Override - public Map ftConfigGet(String indexName, String option) { + public Map ftConfigGet(String indexName, String option) { return executeCommand(commandObjects.ftConfigGet(indexName, option)); } @@ -3878,7 +3879,7 @@ public long ftSugLen(String key) { } @Override - public List ftList() { + public Set ftList() { return executeCommand(commandObjects.ftList()); } // RediSearch commands diff --git a/src/main/java/redis/clients/jedis/search/Document.java b/src/main/java/redis/clients/jedis/search/Document.java index 9e126b948b..20149e581a 100644 --- a/src/main/java/redis/clients/jedis/search/Document.java +++ b/src/main/java/redis/clients/jedis/search/Document.java @@ -3,9 +3,13 @@ import redis.clients.jedis.util.SafeEncoder; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import redis.clients.jedis.Builder; +import redis.clients.jedis.BuilderFactory; +import redis.clients.jedis.util.KeyValue; /** * Document represents a single indexed document or entity in the engine @@ -15,29 +19,35 @@ public class Document implements Serializable { private static final long serialVersionUID = 4884173545291367373L; private final String id; - private double score; - private final Map properties; - - public Document(String id, double score) { - this(id, new HashMap<>(), score); - } + private Double score; + private final Map fields; public Document(String id) { this(id, 1.0); } + public Document(String id, double score) { + this(id, new HashMap<>(), score); + } + public Document(String id, Map fields) { this(id, fields, 1.0f); } public Document(String id, Map fields, double score) { this.id = id; - this.properties = fields; + this.fields = fields; + this.score = score; + } + + private Document(String id, Double score, Map fields) { + this.id = id; this.score = score; + this.fields = fields; } public Iterable> getProperties() { - return properties.entrySet(); + return fields.entrySet(); } public static Document load(String id, double score, byte[] payload, List fields) { @@ -58,9 +68,18 @@ public static Document load(String id, double score, List fields, boolea return ret; } - public Document set(String key, Object value) { - properties.put(key, value); - return this; + /** + * @return the document's id + */ + public String getId() { + return id; + } + + /** + * @return the document's score + */ + public Double getScore() { + return score; } /** @@ -71,7 +90,7 @@ public Document set(String key, Object value) { * @return the property value */ public Object get(String key) { - return properties.get(key); + return fields.get(key); } /** @@ -82,18 +101,20 @@ public Object get(String key) { * @return the property value */ public String getString(String key) { - Object value = properties.get(key); + Object value = fields.get(key); if (value instanceof String) { return (String) value; } return value instanceof byte[] ? SafeEncoder.encode((byte[]) value) : value.toString(); } - /** - * @return the document's score - */ - public double getScore() { - return score; + public boolean hasProperty(String key) { + return fields.containsKey(key); + } + + public Document set(String key, Object value) { + fields.put(key, value); + return this; } /** @@ -103,24 +124,46 @@ public double getScore() { * @return the document itself */ public Document setScore(float score) { - this.score = score; + this.score = (double) score; return this; } - /** - * @return the document's id - */ - public String getId() { - return id; - } - - public boolean hasProperty(String key) { - return properties.containsKey(key); - } - @Override public String toString() { return "id:" + this.getId() + ", score: " + this.getScore() + ", properties:" + this.getProperties(); } + + static Builder SEARCH_DOCUMENT = new Builder() { + + private static final String ID_STR = "id"; + private static final String SCORE_STR = "score"; + // private static final String FIELDS_STR = "fields"; + private static final String FIELDS_STR = "extra_attributes"; + + @Override + public Document build(Object data) { + List list = (List) data; + String id = null; + Double score = null; + Map fields = null; + for (KeyValue kv : list) { + String key = BuilderFactory.STRING.build(kv.getKey()); + switch (key) { + case ID_STR: + id = BuilderFactory.STRING.build(kv.getValue()); + break; + case SCORE_STR: + score = BuilderFactory.DOUBLE.build(kv.getValue()); + break; + case FIELDS_STR: + fields = BuilderFactory.ENCODED_OBJECT_MAP.build(kv.getValue()); + break; + } + } +// assert id != null; +// if (fields == null) fields = Collections.emptyMap(); + return new Document(id, score, fields); + } + }; } diff --git a/src/main/java/redis/clients/jedis/search/FtSearchIteration.java b/src/main/java/redis/clients/jedis/search/FtSearchIteration.java index d8686e2186..c856e5d780 100644 --- a/src/main/java/redis/clients/jedis/search/FtSearchIteration.java +++ b/src/main/java/redis/clients/jedis/search/FtSearchIteration.java @@ -4,6 +4,7 @@ import java.util.function.IntFunction; import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.providers.ConnectionProvider; import redis.clients.jedis.search.SearchResult.SearchResultBuilder; import redis.clients.jedis.util.JedisCommandIterationBase; @@ -18,7 +19,22 @@ public class FtSearchIteration extends JedisCommandIterationBase new CommandArguments(SearchProtocol.SearchCommand.SEARCH) .add(indexName).add(query).addParams(params.limit(limitFirst, this.batchSize)); @@ -27,8 +43,9 @@ public FtSearchIteration(ConnectionProvider connectionProvider, int batchSize, S /** * {@link Query#limit(java.lang.Integer, java.lang.Integer)} will be ignored. */ - public FtSearchIteration(ConnectionProvider connectionProvider, int batchSize, String indexName, Query query) { - super(connectionProvider, new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)); + public FtSearchIteration(ConnectionProvider connectionProvider, RedisProtocol protocol, int batchSize, String indexName, Query query) { + super(connectionProvider, protocol == RedisProtocol.RESP3 ? SearchResult.SEARCH_RESULT_BUILDER + : new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)); this.batchSize = batchSize; this.args = (limitFirst) -> new CommandArguments(SearchProtocol.SearchCommand.SEARCH) .add(indexName).addParams(query.limit(limitFirst, this.batchSize)); diff --git a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java index d128babf83..1e09266ad7 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java @@ -50,6 +50,7 @@ default SearchResult ftSearch(String indexName) { SearchResult ftSearch(String indexName, Query query); + @Deprecated SearchResult ftSearch(byte[] indexName, Query query); String ftExplain(String indexName, Query query); @@ -106,9 +107,9 @@ Map> ftSpellCheck(String index, String query, String ftAliasDel(String aliasName); - Map ftConfigGet(String option); + Map ftConfigGet(String option); - Map ftConfigGet(String indexName, String option); + Map ftConfigGet(String indexName, String option); String ftConfigSet(String option, String value); @@ -130,5 +131,5 @@ Map> ftSpellCheck(String index, String query, long ftSugLen(String key); - List ftList(); + Set ftList(); } diff --git a/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java b/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java index 87a4954a4f..ed633ea35a 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java @@ -51,6 +51,7 @@ default Response ftSearch(String indexName) { Response ftSearch(String indexName, Query query); + @Deprecated Response ftSearch(byte[] indexName, Query query); Response ftExplain(String indexName, Query query); @@ -84,9 +85,9 @@ Response>> ftSpellCheck(String index, String que Response> ftTagVals(String indexName, String fieldName); - Response> ftConfigGet(String option); + Response> ftConfigGet(String option); - Response> ftConfigGet(String indexName, String option); + Response> ftConfigGet(String indexName, String option); Response ftConfigSet(String option, String value); diff --git a/src/main/java/redis/clients/jedis/search/SearchBuilderFactory.java b/src/main/java/redis/clients/jedis/search/SearchBuilderFactory.java index e4490efdfb..8702b4a307 100644 --- a/src/main/java/redis/clients/jedis/search/SearchBuilderFactory.java +++ b/src/main/java/redis/clients/jedis/search/SearchBuilderFactory.java @@ -3,34 +3,21 @@ import static redis.clients.jedis.BuilderFactory.STRING; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import redis.clients.jedis.Builder; import redis.clients.jedis.BuilderFactory; -import redis.clients.jedis.search.aggr.AggregationResult; import redis.clients.jedis.util.DoublePrecision; +import redis.clients.jedis.util.KeyValue; import redis.clients.jedis.util.SafeEncoder; public final class SearchBuilderFactory { - public static final Builder SEARCH_AGGREGATION_RESULT = new Builder() { - @Override - public AggregationResult build(Object data) { - return new AggregationResult(data); - } - }; - - public static final Builder SEARCH_AGGREGATION_RESULT_WITH_CURSOR = new Builder() { - @Override - public AggregationResult build(Object data) { - List list = (List) data; - return new AggregationResult(list.get(0), (long) list.get(1)); - } - }; - public static final Builder> SEARCH_PROFILE_PROFILE = new Builder>() { private final String ITERATORS_PROFILE_STR = "Iterators profile"; @@ -131,7 +118,14 @@ private Object parseIterators(Object data) { public static final Builder>> SEARCH_SYNONYM_GROUPS = new Builder>>() { @Override public Map> build(Object data) { - List list = (List) data; + List list = (List) data; + if (list.isEmpty()) return Collections.emptyMap(); + + if (list.get(0) instanceof KeyValue) { + return ((List) data).stream().collect(Collectors.toMap( + kv -> STRING.build(kv.getKey()), kv -> BuilderFactory.STRING_LIST.build(kv.getValue()))); + } + Map> dump = new HashMap<>(list.size() / 2, 1f); for (int i = 0; i < list.size(); i += 2) { dump.put(STRING.build(list.get(i)), BuilderFactory.STRING_LIST.build(list.get(i + 1))); @@ -143,15 +137,33 @@ public Map> build(Object data) { public static final Builder>> SEARCH_SPELLCHECK_RESPONSE = new Builder>>() { - private final String TERM = "TERM"; + private static final String TERM = "TERM"; + private static final String RESULTS = "results"; @Override public Map> build(Object data) { - List rawTerms = (List) data; - Map> returnTerms = new LinkedHashMap<>(rawTerms.size()); + List rawDataList = (List) data; + if (rawDataList.isEmpty()) return Collections.emptyMap(); + + if (rawDataList.get(0) instanceof KeyValue) { + KeyValue rawData = (KeyValue) rawDataList.get(0); + String header = STRING.build(rawData.getKey()); + if (!RESULTS.equals(header)) { + throw new IllegalStateException("Unrecognized header: " + header); + } + + return ((List) rawData.getValue()).stream().collect(Collectors.toMap( + rawTerm -> STRING.build(rawTerm.getKey()), + rawTerm -> ((List>) rawTerm.getValue()).stream() + .collect(Collectors.toMap(entry -> STRING.build(entry.get(0).getKey()), + entry -> BuilderFactory.DOUBLE.build(entry.get(0).getValue()))), + (x, y) -> x, LinkedHashMap::new)); + } + + Map> returnTerms = new LinkedHashMap<>(rawDataList.size()); - for (Object rawTerm : rawTerms) { - List rawElements = (List) rawTerm; + for (Object rawData : rawDataList) { + List rawElements = (List) rawData; String header = STRING.build(rawElements.get(0)); if (!TERM.equals(header)) { diff --git a/src/main/java/redis/clients/jedis/search/SearchResult.java b/src/main/java/redis/clients/jedis/search/SearchResult.java index e756a54e6a..cc28cc942a 100644 --- a/src/main/java/redis/clients/jedis/search/SearchResult.java +++ b/src/main/java/redis/clients/jedis/search/SearchResult.java @@ -1,9 +1,12 @@ package redis.clients.jedis.search; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import redis.clients.jedis.Builder; import redis.clients.jedis.BuilderFactory; +import redis.clients.jedis.util.KeyValue; /** * SearchResult encapsulates the returned result from a search query. It contains publicly @@ -25,7 +28,13 @@ public long getTotalResults() { } public List getDocuments() { - return documents; + return Collections.unmodifiableList(documents); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{Total results:" + totalResults + + ", Documents:" + documents + "}"; } public static class SearchResultBuilder extends Builder { @@ -72,4 +81,31 @@ public SearchResult build(Object data) { return new SearchResult(totalResults, documents); } } + + public static Builder SEARCH_RESULT_BUILDER = new Builder() { + + private static final String TOTAL_RESULTS_STR = "total_results"; + private static final String RESULTS_STR = "results"; + + @Override + public SearchResult build(Object data) { + List list = (List) data; + long totalResults = -1; + List results = null; + for (KeyValue kv : list) { + String key = BuilderFactory.STRING.build(kv.getKey()); + switch (key) { + case TOTAL_RESULTS_STR: + totalResults = BuilderFactory.LONG.build(kv.getValue()); + break; + case RESULTS_STR: + results = ((List) kv.getValue()).stream() + .map(Document.SEARCH_DOCUMENT::build) + .collect(Collectors.toList()); + break; + } + } + return new SearchResult(totalResults, results); + } + }; } diff --git a/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java b/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java index 67133c5f18..cec65f9cd9 100644 --- a/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java +++ b/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java @@ -1,12 +1,16 @@ package redis.clients.jedis.search.aggr; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import redis.clients.jedis.Builder; +import redis.clients.jedis.BuilderFactory; import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.util.KeyValue; import redis.clients.jedis.util.SafeEncoder; public class AggregationResult { @@ -15,14 +19,14 @@ public class AggregationResult { private final List> results; - private long cursorId = -1; + private Long cursorId = -1L; - public AggregationResult(Object resp, long cursorId) { + private AggregationResult(Object resp, long cursorId) { this(resp); this.cursorId = cursorId; } - public AggregationResult(Object resp) { + private AggregationResult(Object resp) { List list = (List) SafeEncoder.encodeObject(resp); // the first element is always the number of results @@ -43,12 +47,25 @@ public AggregationResult(Object resp) { } } + private AggregationResult(long totalResults, List> results) { + this.totalResults = totalResults; + this.results = results; + } + + private void setCursorId(Long cursorId) { + this.cursorId = cursorId; + } + + public Long getCursorId() { + return cursorId; + } + public long getTotalResults() { return totalResults; } public List> getResults() { - return results; + return Collections.unmodifiableList(results); } /** @@ -63,7 +80,75 @@ public Row getRow(int index) { return new Row(results.get(index)); } - public long getCursorId() { - return cursorId; - } + public static final Builder SEARCH_AGGREGATION_RESULT = new Builder() { + + private static final String TOTAL_RESULTS_STR = "total_results"; + private static final String RESULTS_STR = "results"; + // private static final String FIELDS_STR = "fields"; + private static final String FIELDS_STR = "extra_attributes"; + + @Override + public AggregationResult build(Object data) { + // return new AggregationResult(data); + List list = (List) data; + + if (list.get(0) instanceof KeyValue) { + List kvList = (List) data; + long totalResults = -1; + List> results = null; + for (KeyValue kv : kvList) { + String key = BuilderFactory.STRING.build(kv.getKey()); + switch (key) { + case TOTAL_RESULTS_STR: + totalResults = BuilderFactory.LONG.build(kv.getValue()); + break; + case RESULTS_STR: + List> resList = (List>) kv.getValue(); + results = new ArrayList<>(resList.size()); + for (List rikv : resList) { + for (KeyValue ikv : rikv) { + if (FIELDS_STR.equals(BuilderFactory.STRING.build(ikv.getKey()))) { + results.add(BuilderFactory.ENCODED_OBJECT_MAP.build(ikv.getValue())); + break; + } + } + } + break; + } + } + return new AggregationResult(totalResults, results); + } + + list = (List) SafeEncoder.encodeObject(data); + + // the first element is always the number of results + long totalResults = (Long) list.get(0); + List> results = new ArrayList<>(list.size() - 1); + + for (int i = 1; i < list.size(); i++) { + List mapList = (List) list.get(i); + Map map = new HashMap<>(mapList.size() / 2, 1f); + for (int j = 0; j < mapList.size(); j += 2) { + Object r = mapList.get(j); + if (r instanceof JedisDataException) { + throw (JedisDataException) r; + } + map.put((String) r, mapList.get(j + 1)); + } + results.add(map); + } + return new AggregationResult(totalResults, results); + } + }; + + public static final Builder SEARCH_AGGREGATION_RESULT_WITH_CURSOR = new Builder() { + @Override + public AggregationResult build(Object data) { + List list = (List) data; + // return new AggregationResult(list.get(0), (long) list.get(1)); + AggregationResult r = SEARCH_AGGREGATION_RESULT.build(list.get(0)); + r.setCursorId((Long) list.get(1)); + return r; + } + }; } diff --git a/src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java b/src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java index f35100af09..931834ed49 100644 --- a/src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java +++ b/src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java @@ -4,7 +4,6 @@ import redis.clients.jedis.CommandArguments; import redis.clients.jedis.providers.ConnectionProvider; -import redis.clients.jedis.search.SearchBuilderFactory; import redis.clients.jedis.search.SearchProtocol; import redis.clients.jedis.util.JedisCommandIterationBase; @@ -20,7 +19,7 @@ public class FtAggregateIteration extends JedisCommandIterationBase searchResult = p.ftSearch(index, new Query("hello world")); - Response searchBytesResult = p.ftSearch(index.getBytes(), new Query("hello world")); +// Response searchBytesResult = p.ftSearch(index.getBytes(), new Query("hello world")); // not RESP3 supported Response aggregateResult = p.ftAggregate(index, new AggregationBuilder().groupBy("@title")); Response explain = p.ftExplain(index, new Query("@title:title_val")); Response> explainCLI = p.ftExplainCLI(index, new Query("@title:title_val")); Response> info = p.ftInfo(index); Response configSet = p.ftConfigSet("timeout", "100"); - Response> configGet = p.ftConfigGet("*"); + Response> configGet = p.ftConfigGet("*"); Response configSetIndex = p.ftConfigSet(index, "timeout", "100"); - Response> configGetIndex = p.ftConfigGet(index, "*"); + Response> configGetIndex = p.ftConfigGet(index, "*"); Response synUpdate = p.ftSynUpdate(index, "foo", "bar"); Response>> synDump = p.ftSynDump(index); @@ -71,7 +71,7 @@ public void search() { assertEquals("OK", alter.get()); assertEquals("OK", alter.get()); assertEquals(2, searchResult.get().getTotalResults()); - assertEquals(2, searchBytesResult.get().getTotalResults()); +// assertEquals(2, searchBytesResult.get().getTotalResults()); assertEquals(1, aggregateResult.get().getTotalResults()); assertNotNull(explain.get()); assertNotNull(explainCLI.get().get(0)); diff --git a/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java b/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java index f605ca973f..f67cfe8637 100644 --- a/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java +++ b/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java @@ -2,7 +2,6 @@ import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.Objects; public class JsonObjects { diff --git a/src/test/java/redis/clients/jedis/modules/search/AggregationTest.java b/src/test/java/redis/clients/jedis/modules/search/AggregationTest.java index 14f210ae8a..b594f28dd1 100644 --- a/src/test/java/redis/clients/jedis/modules/search/AggregationTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/AggregationTest.java @@ -11,11 +11,14 @@ import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.search.Document; import redis.clients.jedis.search.FieldName; @@ -27,6 +30,7 @@ import redis.clients.jedis.search.aggr.Row; import redis.clients.jedis.search.aggr.SortedField; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; +import redis.clients.jedis.search.FTProfileParams; import redis.clients.jedis.search.aggr.FtAggregateIteration; import redis.clients.jedis.search.schemafields.NumericField; import redis.clients.jedis.search.schemafields.TextField; @@ -123,6 +127,52 @@ public void testAggregations2() { assertEquals("10", rows.get(1).get("sum")); } + @Test + public void testAggregations2Profile() { + Schema sc = new Schema(); + sc.addSortableTextField("name", 1.0); + sc.addSortableNumericField("count"); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); + + addDocument(new Document("data1").set("name", "abc").set("count", 10)); + addDocument(new Document("data2").set("name", "def").set("count", 5)); + addDocument(new Document("data3").set("name", "def").set("count", 25)); + + AggregationBuilder aggr = new AggregationBuilder() + .groupBy("@name", Reducers.sum("@count").as("sum")) + .sortBy(10, SortedField.desc("@sum")); + + Map.Entry> reply + = client.ftProfileAggregate(index, FTProfileParams.profileParams(), aggr); + + // actual search + AggregationResult result = reply.getKey(); + assertEquals(2, result.getTotalResults()); + + List rows = result.getRows(); + assertEquals("def", rows.get(0).get("name")); + assertEquals("30", rows.get(0).get("sum")); + assertNull(rows.get(0).get("nosuchcol")); + + assertEquals("abc", rows.get(1).get("name")); + assertEquals("10", rows.get(1).get("sum")); + + // profile + Map profile = reply.getValue(); + + assertEquals(Arrays.asList("Index", "Grouper", "Sorter"), + ((List>) profile.get("Result processors profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); + + if (protocol != RedisProtocol.RESP3) { + assertEquals("WILDCARD", ((Map) profile.get("Iterators profile")).get("Type")); + } else { + assertEquals(Arrays.asList("WILDCARD"), + ((List>) profile.get("Iterators profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); + } + } + @Test public void testAggregationBuilderVerbatim() { Schema sc = new Schema(); diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchConfigTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchConfigTest.java index b180711cc7..bda3cbe4d1 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchConfigTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchConfigTest.java @@ -5,10 +5,8 @@ import java.util.Collections; import java.util.Map; - import org.junit.BeforeClass; import org.junit.Test; - import redis.clients.jedis.modules.RedisModuleCommandsTestBase; public class SearchConfigTest extends RedisModuleCommandsTestBase { @@ -25,12 +23,14 @@ public static void prepare() { @Test public void config() { - Map map = client.ftConfigGet("TIMEOUT"); + Map map = client.ftConfigGet("TIMEOUT"); assertEquals(1, map.size()); - String value = map.get("TIMEOUT"); - assertNotNull(value); - - assertEquals("OK", client.ftConfigSet("timeout", value)); + String value = (String) map.get("TIMEOUT"); + try { + assertNotNull(value); + } finally { + assertEquals("OK", client.ftConfigSet("timeout", value)); + } } @Test @@ -38,11 +38,14 @@ public void configOnTimeout() { // confirm default assertEquals(Collections.singletonMap("ON_TIMEOUT", "return"), client.ftConfigGet("ON_TIMEOUT")); - assertEquals("OK", client.ftConfigSet("ON_TIMEOUT", "fail")); - assertEquals(Collections.singletonMap("ON_TIMEOUT", "fail"), client.ftConfigGet("ON_TIMEOUT")); + try { + assertEquals("OK", client.ftConfigSet("ON_TIMEOUT", "fail")); + assertEquals(Collections.singletonMap("ON_TIMEOUT", "fail"), client.ftConfigGet("ON_TIMEOUT")); - // restore to default - assertEquals("OK", client.ftConfigSet("ON_TIMEOUT", "return")); + } finally { + // restore to default + assertEquals("OK", client.ftConfigSet("ON_TIMEOUT", "return")); + } } @Test @@ -50,10 +53,13 @@ public void dialectConfig() { // confirm default assertEquals(Collections.singletonMap("DEFAULT_DIALECT", "1"), client.ftConfigGet("DEFAULT_DIALECT")); - assertEquals("OK", client.ftConfigSet("DEFAULT_DIALECT", "2")); - assertEquals(Collections.singletonMap("DEFAULT_DIALECT", "2"), client.ftConfigGet("DEFAULT_DIALECT")); + try { + assertEquals("OK", client.ftConfigSet("DEFAULT_DIALECT", "2")); + assertEquals(Collections.singletonMap("DEFAULT_DIALECT", "2"), client.ftConfigGet("DEFAULT_DIALECT")); - // restore to default - assertEquals("OK", client.ftConfigSet("DEFAULT_DIALECT", "1")); + } finally { + // restore to default + assertEquals("OK", client.ftConfigSet("DEFAULT_DIALECT", "1")); + } } } diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java index c36c3db2ca..b8656ff344 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java @@ -3,10 +3,13 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.stream.Collectors; import org.hamcrest.Matchers; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; +import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.json.Path; import redis.clients.jedis.search.*; @@ -155,11 +158,6 @@ public void alterAdd() throws Exception { } SearchResult res2 = client.ftSearch(index, new Query("@tags:{tagA}")); assertEquals(100, res2.getTotalResults()); - - Map info = client.ftInfo(index); - assertEquals(index, info.get("index_name")); - assertEquals("identifier", ((List) ((List) info.get("attributes")).get(1)).get(0)); - assertEquals("attribute", ((List) ((List) info.get("attributes")).get(1)).get(2)); } @Test @@ -373,8 +371,13 @@ public void testQueryFlags() throws Exception { res = client.ftSearch(index, q); for (Document d : res.getDocuments()) { assertTrue(d.getId().startsWith("doc")); - assertEquals(1.0, d.getScore(), 0); - assertNull(d.get("title")); + if (protocol != RedisProtocol.RESP3) { + assertEquals(1.0, d.getScore(), 0); + assertNull(d.get("title")); + } else { + assertNull(d.getScore()); + assertThrows(NullPointerException.class, () -> d.get("title")); + } } // test verbatim vs. stemming @@ -644,8 +647,13 @@ public void info() throws Exception { Map info = client.ftInfo(index); assertEquals(index, info.get("index_name")); assertEquals(6, ((List) info.get("attributes")).size()); - assertEquals("global_idle", ((List) info.get("cursor_stats")).get(0)); - assertEquals(0L, ((List) info.get("cursor_stats")).get(1)); + if (protocol != RedisProtocol.RESP3) { + assertEquals("global_idle", ((List) info.get("cursor_stats")).get(0)); + assertEquals(0L, ((List) info.get("cursor_stats")).get(1)); + } else { + assertEquals(0L, ((Map) info.get("cursor_stats")).get("global_idle")); + assertEquals(128L, ((Map) info.get("cursor_stats")).get("index_capacity")); + } } @Test @@ -892,6 +900,8 @@ public void inKeys() throws Exception { @Test public void blobField() throws Exception { + Assume.assumeFalse(protocol == RedisProtocol.RESP3); // not supporting + Schema sc = new Schema().addTextField("field1", 1.0); assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); @@ -1123,6 +1133,43 @@ public void testDialectsWithFTExplain() throws Exception { assertTrue("Should contain '{K=10 nearest vector'", client.ftExplain(index, query).contains("{K=10 nearest vector")); } + @Test + public void searchProfile() { + Schema sc = new Schema().addTextField("t1", 1.0).addTextField("t2", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map hash = new HashMap<>(); + hash.put("t1", "foo"); + hash.put("t2", "bar"); + client.hset("doc1", hash); + + Map.Entry> reply = client.ftProfileSearch(index, + FTProfileParams.profileParams(), new Query("foo")); + + SearchResult result = reply.getKey(); + assertEquals(1, result.getTotalResults()); + assertEquals(Collections.singletonList("doc1"), result.getDocuments().stream().map(Document::getId).collect(Collectors.toList())); + + Map profile = reply.getValue(); + Map iteratorsProfile; + if (protocol != RedisProtocol.RESP3) { + iteratorsProfile = (Map) profile.get("Iterators profile"); + } else { + List iteratorsProfileList = (List) profile.get("Iterators profile"); + assertEquals(1, iteratorsProfileList.size()); + iteratorsProfile = (Map) iteratorsProfileList.get(0); + } + assertEquals("TEXT", iteratorsProfile.get("Type")); + assertEquals("foo", iteratorsProfile.get("Term")); + assertEquals(1L, iteratorsProfile.get("Counter")); + assertEquals(1L, iteratorsProfile.get("Size")); + assertSame(Double.class, iteratorsProfile.get("Time").getClass()); + + assertEquals(Arrays.asList("Index", "Scorer", "Sorter", "Loader"), + ((List>) profile.get("Result processors profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); + } + @Test public void testHNSWVVectorSimilarity() { Map attr = new HashMap<>(); @@ -1147,13 +1194,18 @@ public void testHNSWVVectorSimilarity() { assertEquals("0", doc1.get("__v_score")); // profile - Map.Entry> profile + Map.Entry> reply = client.ftProfileSearch(index, FTProfileParams.profileParams(), query); - doc1 = profile.getKey().getDocuments().get(0); + doc1 = reply.getKey().getDocuments().get(0); assertEquals("a", doc1.getId()); assertEquals("0", doc1.get("__v_score")); - Map iteratorsProfile = (Map) profile.getValue().get("Iterators profile"); - assertEquals("VECTOR", iteratorsProfile.get("Type")); + if (protocol != RedisProtocol.RESP3) { + assertEquals("VECTOR", ((Map) reply.getValue().get("Iterators profile")).get("Type")); + } else { + assertEquals(Arrays.asList("VECTOR"), + ((List>) reply.getValue().get("Iterators profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); + } } @Test @@ -1180,34 +1232,18 @@ public void testFlatVectorSimilarity() { assertEquals("0", doc1.get("__v_score")); // profile - Map.Entry> profile + Map.Entry> reply = client.ftProfileSearch(index, FTProfileParams.profileParams(), query); - doc1 = profile.getKey().getDocuments().get(0); + doc1 = reply.getKey().getDocuments().get(0); assertEquals("a", doc1.getId()); assertEquals("0", doc1.get("__v_score")); - Map iteratorsProfile = (Map) profile.getValue().get("Iterators profile"); - assertEquals("VECTOR", iteratorsProfile.get("Type")); - } - - @Test - public void searchProfile() { - Schema sc = new Schema().addTextField("t1", 1.0).addTextField("t2", 1.0); - assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); - - Map map = new HashMap<>(); - map.put("t1", "foo"); - map.put("t2", "bar"); - client.hset("doc1", map); - - Map.Entry> profile = client.ftProfileSearch(index, - FTProfileParams.profileParams(), new Query("foo")); - // Iterators profile={Type=TEXT, Time=0.0, Term=foo, Counter=1, Size=1} - Map iteratorsProfile = (Map) profile.getValue().get("Iterators profile"); - assertEquals("TEXT", iteratorsProfile.get("Type")); - assertEquals("foo", iteratorsProfile.get("Term")); - assertEquals(1L, iteratorsProfile.get("Counter")); - assertEquals(1L, iteratorsProfile.get("Size")); - assertSame(Double.class, iteratorsProfile.get("Time").getClass()); + if (protocol != RedisProtocol.RESP3) { + assertEquals("VECTOR", ((Map) reply.getValue().get("Iterators profile")).get("Type")); + } else { + assertEquals(Arrays.asList("VECTOR"), + ((List>) reply.getValue().get("Iterators profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); + } } @Test diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java index bcdee83679..98bc5968e8 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import redis.clients.jedis.GeoCoordinate; +import redis.clients.jedis.RedisProtocol; import redis.clients.jedis.args.GeoUnit; import redis.clients.jedis.args.SortingOrder; import redis.clients.jedis.exceptions.JedisDataException; @@ -163,11 +164,6 @@ public void alterAdd() { } SearchResult res2 = client.ftSearch(index, "@tags:{tagA}"); assertEquals(100, res2.getTotalResults()); - - Map info = client.ftInfo(index); - assertEquals(index, info.get("index_name")); - assertEquals("identifier", ((List) ((List) info.get("attributes")).get(1)).get(0)); - assertEquals("attribute", ((List) ((List) info.get("attributes")).get(1)).get(2)); } @Test @@ -371,8 +367,13 @@ public void testQueryFlags() { FTSearchParams.searchParams().noContent()); for (Document d : res.getDocuments()) { assertTrue(d.getId().startsWith("doc")); - assertEquals(1.0, d.getScore(), 0); - assertNull(d.get("title")); + if (protocol != RedisProtocol.RESP3) { + assertEquals(1.0, d.getScore(), 0); + assertNull(d.get("title")); + } else { + assertNull(d.getScore()); + assertThrows(NullPointerException.class, () -> d.get("title")); + } } // test verbatim vs. stemming @@ -566,8 +567,12 @@ public void info() { Map info = client.ftInfo(index); assertEquals(index, info.get("index_name")); assertEquals(6, ((List) info.get("attributes")).size()); - assertEquals("global_idle", ((List) info.get("cursor_stats")).get(0)); - assertEquals(0L, ((List) info.get("cursor_stats")).get(1)); + if (protocol != RedisProtocol.RESP3) { + assertEquals("global_idle", ((List) info.get("cursor_stats")).get(0)); + assertEquals(0L, ((List) info.get("cursor_stats")).get(1)); + } else { + assertEquals(0L, ((Map) info.get("cursor_stats")).get("global_idle")); + } } @Test @@ -945,20 +950,36 @@ public void testFlatVectorSimilarity() { public void searchProfile() { assertOK(client.ftCreate(index, TextField.of("t1"), TextField.of("t2"))); - Map map = new HashMap<>(); - map.put("t1", "foo"); - map.put("t2", "bar"); - client.hset("doc1", map); + Map hash = new HashMap<>(); + hash.put("t1", "foo"); + hash.put("t2", "bar"); + client.hset("doc1", hash); - Map.Entry> profile = client.ftProfileSearch(index, + Map.Entry> reply = client.ftProfileSearch(index, FTProfileParams.profileParams(), "foo", FTSearchParams.searchParams()); - // Iterators profile={Type=TEXT, Time=0.0, Term=foo, Counter=1, Size=1} - Map iteratorsProfile = (Map) profile.getValue().get("Iterators profile"); + + SearchResult result = reply.getKey(); + assertEquals(1, result.getTotalResults()); + assertEquals(Collections.singletonList("doc1"), result.getDocuments().stream().map(Document::getId).collect(Collectors.toList())); + + Map profile = reply.getValue(); + Map iteratorsProfile; + if (protocol != RedisProtocol.RESP3) { + iteratorsProfile = (Map) profile.get("Iterators profile"); + } else { + List iteratorsProfileList = (List) profile.get("Iterators profile"); + assertEquals(1, iteratorsProfileList.size()); + iteratorsProfile = (Map) iteratorsProfileList.get(0); + } assertEquals("TEXT", iteratorsProfile.get("Type")); assertEquals("foo", iteratorsProfile.get("Term")); assertEquals(1L, iteratorsProfile.get("Counter")); assertEquals(1L, iteratorsProfile.get("Size")); assertSame(Double.class, iteratorsProfile.get("Time").getClass()); + + assertEquals(Arrays.asList("Index", "Scorer", "Sorter", "Loader"), + ((List>) profile.get("Result processors profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); } @Test @@ -976,44 +997,59 @@ public void vectorSearchProfile() { FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", "aaaaaaaa") .sortBy("__v_score", SortingOrder.ASC).noContent().dialect(2); - Map.Entry> profile = client.ftProfileSearch(index, + Map.Entry> reply = client.ftProfileSearch(index, FTProfileParams.profileParams(), "*=>[KNN 3 @v $vec]", searchParams); - assertEquals(3, profile.getKey().getTotalResults()); + assertEquals(3, reply.getKey().getTotalResults()); - assertEquals(Arrays.asList(4, 2, 1).toString(), profile.getKey().getDocuments() + assertEquals(Arrays.asList(4, 2, 1).toString(), reply.getKey().getDocuments() .stream().map(Document::getId).collect(Collectors.toList()).toString()); - Map iteratorsProfile = (Map) profile.getValue().get("Iterators profile"); - assertEquals("VECTOR", iteratorsProfile.get("Type")); - assertEquals(3L, iteratorsProfile.get("Counter")); + Map profile = reply.getValue(); - List> resultProcessorsProfile = (List>) profile.getValue().get("Result processors profile"); - // assertEquals("Vector Similarity Scores Loader", resultProcessorsProfile.get(1).get("Type")); // Changed to "Metrics Applier" - assertEquals(3l, resultProcessorsProfile.get(1).get("Counter")); + if (protocol != RedisProtocol.RESP3) { + assertEquals("VECTOR", ((Map) profile.get("Iterators profile")).get("Type")); + } else { + assertEquals(Arrays.asList("VECTOR"), + ((List>) profile.get("Iterators profile")).stream() + .map(map -> map.get("Type")).collect(Collectors.toList())); + } + + List> resultProcessorsProfile + = (List>) reply.getValue().get("Result processors profile"); + assertEquals(3, resultProcessorsProfile.size()); + assertEquals("Index", resultProcessorsProfile.get(0).get("Type")); + assertEquals("Sorter", resultProcessorsProfile.get(2).get("Type")); } @Test public void maxPrefixExpansionSearchProfile() { final String configParam = "MAXPREFIXEXPANSIONS"; - String configValue = client.ftConfigGet(configParam).get(configParam); - client.ftConfigSet(configParam, "2"); - - assertOK(client.ftCreate(index, TextField.of("t"))); - client.hset("1", Collections.singletonMap("t", "foo1")); - client.hset("2", Collections.singletonMap("t", "foo2")); - client.hset("3", Collections.singletonMap("t", "foo3")); - - Map.Entry> profile = client.ftProfileSearch(index, - FTProfileParams.profileParams(), "foo*", FTSearchParams.searchParams().limit(0, 0)); - // Warning=Max prefix expansion reached - Map iteratorsProfile = (Map) profile.getValue().get("Iterators profile"); - assertEquals("Max prefix expansion reached", iteratorsProfile.get("Warning")); - - client.ftConfigSet(configParam, configValue); + String configValue = (String) client.ftConfigGet(configParam).get(configParam); + try { + client.ftConfigSet(configParam, "2"); + + assertOK(client.ftCreate(index, TextField.of("t"))); + client.hset("1", Collections.singletonMap("t", "foo1")); + client.hset("2", Collections.singletonMap("t", "foo2")); + client.hset("3", Collections.singletonMap("t", "foo3")); + + Map.Entry> reply = client.ftProfileSearch(index, + FTProfileParams.profileParams(), "foo*", FTSearchParams.searchParams().limit(0, 0)); + // Warning=Max prefix expansion reached + if (protocol != RedisProtocol.RESP3) { + assertEquals("Max prefix expansion reached", + ((Map) reply.getValue().get("Iterators profile")).get("Warning")); + } else { + assertEquals("Max prefix expansion reached", + ((Map) ((List) reply.getValue().get("Iterators profile")).get(0)).get("Warning")); + } + } finally { + client.ftConfigSet(configParam, configValue); + } } @Test - public void notIteratorSearchProfile() { + public void noContentSearchProfile() { assertOK(client.ftCreate(index, TextField.of("t"))); client.hset("1", Collections.singletonMap("t", "foo")); client.hset("2", Collections.singletonMap("t", "bar")); @@ -1021,15 +1057,22 @@ public void notIteratorSearchProfile() { Map.Entry> profile = client.ftProfileSearch(index, FTProfileParams.profileParams(), "foo -@t:baz", FTSearchParams.searchParams().noContent()); - Map depth0 = (Map) profile.getValue().get("Iterators profile"); + Map depth0 = protocol != RedisProtocol.RESP3 + ? (Map) profile.getValue().get("Iterators profile") + : ((List>) profile.getValue().get("Iterators profile")).get(0); + assertEquals("INTERSECT", depth0.get("Type")); List> depth0_children = (List>) depth0.get("Child iterators"); assertEquals("TEXT", depth0_children.get(0).get("Type")); Map depth1 = depth0_children.get(1); assertEquals("NOT", depth1.get("Type")); - List> depth1_children = (List>) depth1.get("Child iterators"); - assertEquals(1, depth1_children.size()); - assertEquals("EMPTY", depth1_children.get(0).get("Type")); + if (protocol != RedisProtocol.RESP3) { + List> depth1_children = (List>) depth1.get("Child iterators"); + assertEquals(1, depth1_children.size()); + assertEquals("EMPTY", depth1_children.get(0).get("Type")); + } else { + assertEquals("EMPTY", ((Map) depth1.get("Child iterator")).get("Type")); + } } @Test @@ -1042,7 +1085,10 @@ public void deepReplySearchProfile() { = client.ftProfileSearch(index, FTProfileParams.profileParams(), "hello(hello(hello(hello(hello(hello)))))", FTSearchParams.searchParams().noContent()); - Map depth0 = (Map) profile.getValue().get("Iterators profile"); + Map depth0 = protocol != RedisProtocol.RESP3 + ? (Map) profile.getValue().get("Iterators profile") + : ((List>) profile.getValue().get("Iterators profile")).get(0); + assertEquals("INTERSECT", depth0.get("Type")); List> depth0_children = (List>) depth0.get("Child iterators"); assertEquals("TEXT", depth0_children.get(0).get("Type")); @@ -1078,7 +1124,10 @@ public void limitedSearchProfile() { Map.Entry> profile = client.ftProfileSearch(index, FTProfileParams.profileParams().limited(), "%hell% hel*", FTSearchParams.searchParams().noContent()); - Map depth0 = (Map) profile.getValue().get("Iterators profile"); + Map depth0 = protocol != RedisProtocol.RESP3 + ? (Map) profile.getValue().get("Iterators profile") + : ((List>) profile.getValue().get("Iterators profile")).get(0); + assertEquals("INTERSECT", depth0.get("Type")); assertEquals(3L, depth0.get("Counter")); @@ -1087,15 +1136,19 @@ public void limitedSearchProfile() { for (Map depth1 : depth0_children) { assertEquals("UNION", depth1.get("Type")); assertNotNull(depth1.get("Query type")); - List depth1_children = (List) depth1.get("Child iterators"); - assertEquals(1, depth1_children.size()); - assertSame(String.class, depth1_children.get(0).getClass()); + if (protocol != RedisProtocol.RESP3) { + List depth1_children = (List) depth1.get("Child iterators"); + assertEquals(1, depth1_children.size()); + assertSame(String.class, depth1_children.get(0).getClass()); + } else { + assertSame(String.class, depth1.get("Child iterators").getClass()); + } } } @Test public void list() { - assertEquals(Collections.emptyList(), client.ftList()); + assertEquals(Collections.emptySet(), client.ftList()); final int count = 20; Set names = new HashSet<>(); @@ -1104,7 +1157,7 @@ public void list() { assertOK(client.ftCreate(name, TextField.of("t" + i))); names.add(name); } - assertEquals(names, new HashSet<>(client.ftList())); + assertEquals(names, client.ftList()); } @Test diff --git a/src/test/java/redis/clients/jedis/modules/search/SpellCheckTest.java b/src/test/java/redis/clients/jedis/modules/search/SpellCheckTest.java index c551b59d39..508ff4441a 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SpellCheckTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SpellCheckTest.java @@ -52,7 +52,8 @@ public void dictionary() { @Test public void dictionaryBySampleKey() { assertEquals(3L, client.ftDictAddBySampleKey(index, "dict", "foo", "bar", "hello world")); - assertEquals(new HashSet<>(Arrays.asList("foo", "bar", "hello world")), client.ftDictDumpBySampleKey(index, "dict")); + assertEquals(new HashSet<>(Arrays.asList("foo", "bar", "hello world")), + client.ftDictDumpBySampleKey(index, "dict")); assertEquals(3L, client.ftDictDelBySampleKey(index, "dict", "foo", "bar", "hello world")); assertEquals(Collections.emptySet(), client.ftDictDumpBySampleKey(index, "dict")); } @@ -61,8 +62,8 @@ public void dictionaryBySampleKey() { public void basicSpellCheck() { client.ftCreate(index, TextField.of("name"), TextField.of("body")); client.hset("doc1", toMap("name", "name1", "body", "body1")); - client.hset("doc1", toMap("name", "name2", "body", "body2")); - client.hset("doc1", toMap("name", "name2", "body", "name2")); + client.hset("doc2", toMap("name", "name2", "body", "body2")); + client.hset("doc3", toMap("name", "name2", "body", "name2")); Map> reply = client.ftSpellCheck(index, "name"); assertEquals(Collections.singleton("name"), reply.keySet()); @@ -74,7 +75,8 @@ public void crossTermDictionary() { client.ftCreate(index, TextField.of("report")); client.ftDictAdd("slang", "timmies", "toque", "toonie", "serviette", "kerfuffle", "chesterfield"); - Map> expected = Collections.singletonMap("tooni", Collections.singletonMap("toonie", 0d)); + Map> expected = Collections.singletonMap("tooni", + Collections.singletonMap("toonie", 0d)); assertEquals(expected, client.ftSpellCheck(index, "Tooni toque kerfuffle", FTSpellCheckParams.spellCheckParams().includeTerm("slang").excludeTerm("slang"))); } @@ -92,6 +94,7 @@ public void dialectBound() { JedisDataException error = Assert.assertThrows(JedisDataException.class, () -> client.ftSpellCheck(index, "Tooni toque kerfuffle", FTSpellCheckParams.spellCheckParams().dialect(0))); - MatcherAssert.assertThat(error.getMessage(), Matchers.containsString("DIALECT requires a non negative integer")); + MatcherAssert.assertThat(error.getMessage(), + Matchers.containsString("DIALECT requires a non negative integer")); } }