diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index 94fe55f622..422590a4fd 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -1667,6 +1667,103 @@ public AggregationResult build(Object data) { } }; + public static final Builder> SEARCH_PROFILE_PROFILE = new Builder>() { + + private final String ITERATORS_PROFILE_STR = "Iterators profile"; + private final String CHILD_ITERATORS_STR = "Child iterators"; + private final String RESULT_PROCESSORS_PROFILE_STR = "Result processors profile"; + + @Override + public Map build(Object data) { + List list = (List) SafeEncoder.encodeObject(data); + Map profileMap = new HashMap<>(list.size(), 1f); + + for (Object listObject : list) { + List attributeList = (List) listObject; + String attributeName = (String) attributeList.get(0); + Object attributeValue; + + if (attributeList.size() == 2) { + + Object value = attributeList.get(1); + if (attributeName.equals(ITERATORS_PROFILE_STR)) { + attributeValue = parseIterators(value); + } else if (attributeName.endsWith(" time")) { + attributeValue = DoublePrecision.parseFloatingPointNumber((String) value); + } else { + attributeValue = value; + } + + } else if (attributeList.size() > 2) { + + if (attributeName.equals(RESULT_PROCESSORS_PROFILE_STR)) { + List> resultProcessorsProfileList = new ArrayList<>(attributeList.size() - 1); + for (int i = 1; i < attributeList.size(); i++) { + resultProcessorsProfileList.add(parseResultProcessors(attributeList.get(i))); + } + attributeValue = resultProcessorsProfileList; + } else { + attributeValue = attributeList.subList(1, attributeList.size()); + } + + } else { + attributeValue = null; + } + + profileMap.put(attributeName, attributeValue); + } + return profileMap; + } + + private Map parseResultProcessors(Object data) { + List list = (List) data; + Map map = new HashMap<>(list.size() / 2, 1f); + for (int i = 0; i < list.size(); i += 2) { + String key = (String) list.get(i); + Object value = list.get(i + 1); + if (key.equals("Time")) { + value = DoublePrecision.parseFloatingPointNumber((String) value); + } + map.put(key, value); + } + return map; + } + + private Object parseIterators(Object data) { + if (!(data instanceof List)) return data; + List iteratorsAttributeList = (List) data; + int childIteratorsIndex = iteratorsAttributeList.indexOf(CHILD_ITERATORS_STR); + // https://github.com/RediSearch/RediSearch/issues/3205 patch. TODO: Undo if resolved in RediSearch. + if (childIteratorsIndex < 0) childIteratorsIndex = iteratorsAttributeList.indexOf("Child iterator"); + + Map iteratorsProfile; + if (childIteratorsIndex < 0) { + childIteratorsIndex = iteratorsAttributeList.size(); + iteratorsProfile = new HashMap<>(childIteratorsIndex / 2, 1f); + } else { + iteratorsProfile = new HashMap<>(1 + childIteratorsIndex / 2, 1f); + } + + for (int i = 0; i < childIteratorsIndex; i += 2) { + String key = (String) iteratorsAttributeList.get(i); + Object value = iteratorsAttributeList.get(i + 1); + if (key.equals("Time")) { + value = DoublePrecision.parseFloatingPointNumber((String) value); + } + iteratorsProfile.put(key, value); + } + + if (childIteratorsIndex + 1 < iteratorsAttributeList.size()) { + List childIteratorsList = new ArrayList(iteratorsAttributeList.size() - childIteratorsIndex - 1); + for (int i = childIteratorsIndex + 1; i < iteratorsAttributeList.size(); i++) { + childIteratorsList.add(parseIterators(iteratorsAttributeList.get(i))); + } + iteratorsProfile.put(CHILD_ITERATORS_STR, childIteratorsList); + } + return iteratorsProfile; + } + }; + public static final Builder>> SEARCH_SYNONYM_GROUPS = new Builder>>() { @Override public Map> build(Object data) { diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 933555df3a..c7a9a40f33 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -3132,6 +3132,31 @@ public CommandObject ftCursorDel(String indexName, long cursorId) { .add(indexName).add(cursorId), BuilderFactory.STRING); } + public CommandObject>> ftProfileAggregate( + String indexName, FTProfileParams profileParams, AggregationBuilder aggr) { + return new CommandObject<>(commandArguments(SearchCommand.PROFILE).add(indexName) + .add(SearchKeyword.AGGREGATE).addParams(profileParams).add(SearchKeyword.QUERY) + .addObjects(aggr.getArgs()), new SearchProfileResponseBuilder<>(!aggr.isWithCursor() + ? BuilderFactory.SEARCH_AGGREGATION_RESULT + : BuilderFactory.SEARCH_AGGREGATION_RESULT_WITH_CURSOR)); + } + + public CommandObject>> ftProfileSearch( + String indexName, FTProfileParams profileParams, Query query) { + return new CommandObject<>(commandArguments(SearchCommand.PROFILE).add(indexName) + .add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY) + .addParams(query), new SearchProfileResponseBuilder<>(new SearchResultBuilder( + !query.getNoContent(), query.getWithScores(), query.getWithPayloads(), true))); + } + + public CommandObject>> ftProfileSearch( + String indexName, FTProfileParams profileParams, String query, FTSearchParams searchParams) { + return new CommandObject<>(commandArguments(SearchCommand.PROFILE).add(indexName) + .add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY).add(query) + .addParams(searchParams), new SearchProfileResponseBuilder<>(new SearchResultBuilder( + !searchParams.getNoContent(), searchParams.getWithScores(), false, true))); + } + public CommandObject ftDropIndex(String indexName) { return new CommandObject<>(commandArguments(SearchCommand.DROPINDEX).add(indexName), BuilderFactory.STRING); } @@ -3992,6 +4017,22 @@ public final CommandObject> graphConfigGet(String configName } // RedisGraph commands + private class SearchProfileResponseBuilder extends Builder>> { + + private final Builder replyBuilder; + + public SearchProfileResponseBuilder(Builder replyBuilder) { + this.replyBuilder = replyBuilder; + } + + @Override + public Map.Entry> build(Object data) { + List list = (List) data; + return KeyValue.of(replyBuilder.build(list.get(0)), + BuilderFactory.SEARCH_PROFILE_PROFILE.build(list.get(1))); + } + } + private static final Gson GSON = new Gson(); private class GsonObjectBuilder extends Builder { diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 6e9ecfb97c..609bd6e977 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -3511,6 +3511,24 @@ public String ftCursorDel(String indexName, long cursorId) { return executeCommand(commandObjects.ftCursorDel(indexName, cursorId)); } + @Override + public Map.Entry> ftProfileAggregate(String indexName, + FTProfileParams profileParams, AggregationBuilder aggr) { + return executeCommand(commandObjects.ftProfileAggregate(indexName, profileParams, aggr)); + } + + @Override + public Map.Entry> ftProfileSearch(String indexName, + FTProfileParams profileParams, Query query) { + return executeCommand(commandObjects.ftProfileSearch(indexName, profileParams, query)); + } + + @Override + public Map.Entry> ftProfileSearch(String indexName, + FTProfileParams profileParams, String query, FTSearchParams searchParams) { + return executeCommand(commandObjects.ftProfileSearch(indexName, profileParams, query, searchParams)); + } + @Override public String ftDropIndex(String indexName) { return executeCommand(commandObjects.ftDropIndex(indexName)); diff --git a/src/main/java/redis/clients/jedis/search/FTProfileParams.java b/src/main/java/redis/clients/jedis/search/FTProfileParams.java new file mode 100644 index 0000000000..77bd43fc51 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/FTProfileParams.java @@ -0,0 +1,34 @@ +package redis.clients.jedis.search; + +import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.LIMITED; + +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.params.IParams; + +public class FTProfileParams implements IParams { + + private boolean limited; + + public FTProfileParams() { + } + + public static FTProfileParams profileParams() { + return new FTProfileParams(); + } + + /** + * Removes details of {@code reader} iterator. + */ + public FTProfileParams limited() { + this.limited = true; + return this; + } + + @Override + public void addParams(CommandArguments args) { + + if (limited) { + args.add(LIMITED); + } + } +} diff --git a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java index 42cb166dc9..1b0a29b4bc 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java @@ -62,6 +62,15 @@ default SearchResult ftSearch(String indexName) { String ftCursorDel(String indexName, long cursorId); + Map.Entry> ftProfileAggregate(String indexName, + FTProfileParams profileParams, AggregationBuilder aggr); + + Map.Entry> ftProfileSearch(String indexName, + FTProfileParams profileParams, Query query); + + Map.Entry> ftProfileSearch(String indexName, + FTProfileParams profileParams, String query, FTSearchParams searchParams); + String ftDropIndex(String indexName); String ftDropIndexDD(String indexName); @@ -84,7 +93,8 @@ default SearchResult ftSearch(String indexName) { Map> ftSpellCheck(String index, String query); - Map> ftSpellCheck(String index, String query, FTSpellCheckParams spellCheckParams); + Map> ftSpellCheck(String index, String query, + FTSpellCheckParams spellCheckParams); Map ftInfo(String indexName); diff --git a/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java b/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java index 46d0789946..5598a10dc1 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchPipelineCommands.java @@ -85,7 +85,8 @@ default Response ftSearch(String indexName) { Response>> ftSpellCheck(String index, String query); - Response>> ftSpellCheck(String index, String query, FTSpellCheckParams spellCheckParams); + Response>> ftSpellCheck(String index, String query, + FTSpellCheckParams spellCheckParams); Response> ftInfo(String indexName); diff --git a/src/main/java/redis/clients/jedis/search/SearchProtocol.java b/src/main/java/redis/clients/jedis/search/SearchProtocol.java index d2bac45261..17acc9bb87 100644 --- a/src/main/java/redis/clients/jedis/search/SearchProtocol.java +++ b/src/main/java/redis/clients/jedis/search/SearchProtocol.java @@ -31,7 +31,8 @@ public enum SearchCommand implements ProtocolCommand { DICTDEL("FT.DICTDEL"), DICTDUMP("FT.DICTDUMP"), SPELLCHECK("FT.SPELLCHECK"), - TAGVALS("FT.TAGVALS"); + TAGVALS("FT.TAGVALS"), + PROFILE("FT.PROFILE"); private final byte[] raw; @@ -54,6 +55,7 @@ public enum SearchKeyword implements Rawable { SCORE_FIELD, SCORER, PARAMS, AS, DIALECT, SLOP, TIMEOUT, INORDER, EXPANDER, MAXTEXTFIELDS, SKIPINITIALSCAN, WITHSUFFIXTRIE, NOSTEM, NOINDEX, PHONETIC, WEIGHT, CASESENSITIVE, /*EXPLAINSCORE,*/ LOAD, APPLY, GROUPBY, MAXIDLE, WITHCURSOR, DISTANCE, TERMS, INCLUDE, EXCLUDE, + SEARCH, AGGREGATE, QUERY, LIMITED, @Deprecated ASYNC, @Deprecated PAYLOAD_FIELD, @Deprecated WITHPAYLOADS, @Deprecated PAYLOAD, @Deprecated COUNT; 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 ebb3e312eb..5805b6255a 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java @@ -993,10 +993,10 @@ public void timeout() { Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0); assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); - Map doc = new HashMap<>(); - doc.put("field1", "value"); - doc.put("field2", "not"); - addDocument("doc1", doc); + Map map = new HashMap<>(); + map.put("field1", "value"); + map.put("field2", "not"); + client.hset("doc1", map); SearchResult res = client.ftSearch(index, new Query("value").timeout(1000)); assertEquals(1, res.getTotalResults()); @@ -1010,11 +1010,11 @@ public void inOrder() { Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0); assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); - Map doc = new HashMap<>(); - doc.put("field1", "value"); - doc.put("field2", "not"); - addDocument("doc2", doc); - addDocument("doc1", doc); + Map map = new HashMap<>(); + map.put("field1", "value"); + map.put("field2", "not"); + client.hset("doc2", map); + client.hset("doc1", map); SearchResult res = client.ftSearch(index, new Query("value").setInOrder()); assertEquals(2, res.getTotalResults()); @@ -1144,6 +1144,15 @@ public void testHNSWVVectorSimilarity() { Document doc1 = client.ftSearch(index, query).getDocuments().get(0); assertEquals("a", doc1.getId()); assertEquals("0", doc1.get("__v_score")); + + // profile + Map.Entry> profile + = client.ftProfileSearch(index, FTProfileParams.profileParams(), query); + doc1 = profile.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 @@ -1168,5 +1177,35 @@ public void testFlatVectorSimilarity() { Document doc1 = client.ftSearch(index, query).getDocuments().get(0); assertEquals("a", doc1.getId()); assertEquals("0", doc1.get("__v_score")); + + // profile + Map.Entry> profile + = client.ftProfileSearch(index, FTProfileParams.profileParams(), query); + doc1 = profile.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()); } } 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 635586e86b..6e982a0d60 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java @@ -4,6 +4,7 @@ import static redis.clients.jedis.util.AssertUtil.assertOK; import java.util.*; +import java.util.stream.Collectors; import org.junit.BeforeClass; import org.junit.Test; @@ -14,6 +15,7 @@ import redis.clients.jedis.json.Path; import redis.clients.jedis.search.*; import redis.clients.jedis.search.schemafields.*; +import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; public class SearchWithParamsTest extends RedisModuleCommandsTestBase { @@ -859,10 +861,10 @@ public void slop() { public void timeout() { assertOK(client.ftCreate(index, TextField.of("field1"), TextField.of("field2"))); - Map doc = new HashMap<>(); - doc.put("field1", "value"); - doc.put("field2", "not"); - addDocument("doc1", doc); + Map map = new HashMap<>(); + map.put("field1", "value"); + map.put("field2", "not"); + client.hset("doc1", map); SearchResult res = client.ftSearch(index, "value", FTSearchParams.searchParams().timeout(1000)); assertEquals(1, res.getTotalResults()); @@ -875,11 +877,11 @@ public void timeout() { public void inOrder() { assertOK(client.ftCreate(index, TextField.of("field1"), TextField.of("field2"))); - Map doc = new HashMap<>(); - doc.put("field1", "value"); - doc.put("field2", "not"); - addDocument("doc2", doc); - addDocument("doc1", doc); + Map map = new HashMap<>(); + map.put("field1", "value"); + map.put("field2", "not"); + client.hset("doc2", map); + client.hset("doc1", map); SearchResult res = client.ftSearch(index, "value", FTSearchParams.searchParams().inOrder()); assertEquals(2, res.getTotalResults()); @@ -937,4 +939,156 @@ public void testFlatVectorSimilarity() { assertEquals("a", doc1.getId()); assertEquals("0", doc1.get("__v_score")); } + + @Test + 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.Entry> profile = 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"); + 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()); + } + + @Test + public void vectorSearchProfile() { + assertOK(client.ftCreate(index, VectorField.builder().fieldName("v") + .algorithm(VectorAlgorithm.FLAT).addAttribute("TYPE", "FLOAT32") + .addAttribute("DIM", 2).addAttribute("DISTANCE_METRIC", "L2").build(), + TextField.of("t"))); + + client.hset("1", toMap("v", "bababaca", "t", "hello")); + client.hset("2", toMap("v", "babababa", "t", "hello")); + client.hset("3", toMap("v", "aabbaabb", "t", "hello")); + client.hset("4", toMap("v", "bbaabbaa", "t", "hello world")); + client.hset("5", toMap("v", "aaaabbbb", "t", "hello world")); + + FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", "aaaaaaaa") + .sortBy("__v_score", SortingOrder.ASC).noContent().dialect(2); + Map.Entry> profile = client.ftProfileSearch(index, + FTProfileParams.profileParams(), "*=>[KNN 3 @v $vec]", searchParams); + assertEquals(3, profile.getKey().getTotalResults()); + + assertEquals(Arrays.asList(4, 2, 1).toString(), profile.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")); + + List> resultProcessorsProfile = (List>) profile.getValue().get("Result processors profile"); + assertEquals("Vector Similarity Scores Loader", resultProcessorsProfile.get(1).get("Type")); + assertEquals(3l, resultProcessorsProfile.get(1).get("Counter")); + } + + @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); + } + + @Test + public void notIteratorSearchProfile() { + assertOK(client.ftCreate(index, TextField.of("t"))); + client.hset("1", Collections.singletonMap("t", "foo")); + client.hset("2", Collections.singletonMap("t", "bar")); + + Map.Entry> profile = client.ftProfileSearch(index, + FTProfileParams.profileParams(), "foo -@t:baz", FTSearchParams.searchParams().noContent()); + + Map depth0 = (Map) profile.getValue().get("Iterators profile"); + 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")); + } + + @Test + public void deepReplySearchProfile() { + assertOK(client.ftCreate(index, TextField.of("t"))); + client.hset("1", Collections.singletonMap("t", "hello")); + client.hset("2", Collections.singletonMap("t", "world")); + + Map.Entry> profile + = client.ftProfileSearch(index, FTProfileParams.profileParams(), + "hello(hello(hello(hello(hello(hello)))))", FTSearchParams.searchParams().noContent()); + + Map depth0 = (Map) profile.getValue().get("Iterators profile"); + 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("INTERSECT", depth1.get("Type")); + List> depth1_children = (List>) depth1.get("Child iterators"); + assertEquals("TEXT", depth1_children.get(0).get("Type")); + Map depth2 = depth1_children.get(1); + assertEquals("INTERSECT", depth2.get("Type")); + List> depth2_children = (List>) depth2.get("Child iterators"); + assertEquals("TEXT", depth2_children.get(0).get("Type")); + Map depth3 = depth2_children.get(1); + assertEquals("INTERSECT", depth3.get("Type")); + List> depth3_children = (List>) depth3.get("Child iterators"); + assertEquals("TEXT", depth3_children.get(0).get("Type")); + Map depth4 = depth3_children.get(1); + assertEquals("INTERSECT", depth4.get("Type")); + List> depth4_children = (List>) depth4.get("Child iterators"); + assertEquals("TEXT", depth4_children.get(0).get("Type")); + Map depth5 = depth4_children.get(1); + assertEquals("TEXT", depth5.get("Type")); + assertNull(depth5.get("Child iterators")); + } + + @Test + public void limitedSearchProfile() { + assertOK(client.ftCreate(index, TextField.of("t"))); + client.hset("1", Collections.singletonMap("t", "hello")); + client.hset("2", Collections.singletonMap("t", "hell")); + client.hset("3", Collections.singletonMap("t", "help")); + client.hset("4", Collections.singletonMap("t", "helowa")); + + Map.Entry> profile = client.ftProfileSearch(index, + FTProfileParams.profileParams().limited(), "%hell% hel*", FTSearchParams.searchParams().noContent()); + + Map depth0 = (Map) profile.getValue().get("Iterators profile"); + assertEquals("INTERSECT", depth0.get("Type")); + assertEquals(3L, depth0.get("Counter")); + + List> depth0_children = (List>) depth0.get("Child iterators"); + assertFalse(depth0_children.isEmpty()); + 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()); + } + } }