Skip to content

Commit

Permalink
Change FT.PROFILE to return generic object (#4067)
Browse files Browse the repository at this point in the history
* Change FT.PROFILE to return generic object

* Handle FT.PROFILE Results for both Redis 7 and 8
  • Loading branch information
sazzad16 authored Jan 30, 2025
1 parent 8da5104 commit cd9346a
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 381 deletions.
49 changes: 34 additions & 15 deletions src/main/java/redis/clients/jedis/CommandObjects.java
Original file line number Diff line number Diff line change
Expand Up @@ -3434,7 +3434,7 @@ public final CommandObject<String> ftCursorDel(String indexName, long cursorId)
.key(indexName).add(cursorId), BuilderFactory.STRING);
}

public final CommandObject<Map.Entry<AggregationResult, Map<String, Object>>> ftProfileAggregate(
public final CommandObject<Map.Entry<AggregationResult, ProfilingInfo>> ftProfileAggregate(
String indexName, FTProfileParams profileParams, AggregationBuilder aggr) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)
.add(SearchKeyword.AGGREGATE).addParams(profileParams).add(SearchKeyword.QUERY)
Expand All @@ -3443,7 +3443,7 @@ public final CommandObject<Map.Entry<AggregationResult, Map<String, Object>>> ft
: AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR));
}

public final CommandObject<Map.Entry<SearchResult, Map<String, Object>>> ftProfileSearch(
public final CommandObject<Map.Entry<SearchResult, ProfilingInfo>> ftProfileSearch(
String indexName, FTProfileParams profileParams, Query query) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)
.add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY)
Expand All @@ -3452,7 +3452,7 @@ public final CommandObject<Map.Entry<SearchResult, Map<String, Object>>> ftProfi
() -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true))));
}

public final CommandObject<Map.Entry<SearchResult, Map<String, Object>>> ftProfileSearch(
public final CommandObject<Map.Entry<SearchResult, ProfilingInfo>> ftProfileSearch(
String indexName, FTProfileParams profileParams, String query, FTSearchParams searchParams) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)
.add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY).add(query)
Expand Down Expand Up @@ -4461,32 +4461,51 @@ public void setDefaultSearchDialect(int dialect) {
this.searchDialect.set(dialect);
}

private class SearchProfileResponseBuilder<T> extends Builder<Map.Entry<T, Map<String, Object>>> {
private class SearchProfileResponseBuilder<T> extends Builder<Map.Entry<T, ProfilingInfo>> {

private static final String PROFILE_STR = "profile";
private static final String PROFILE_STR_REDIS7 = "profile";
private static final String PROFILE_STR_REDIS8 = "Profile";
private static final String RESULTS_STR_REDIS7 = "results";
private static final String RESULTS_STR_REDIS8 = "Results";

private final Builder<T> replyBuilder;
private final Builder<T> resultsBuilder;

public SearchProfileResponseBuilder(Builder<T> replyBuilder) {
this.replyBuilder = replyBuilder;
public SearchProfileResponseBuilder(Builder<T> resultsBuilder) {
this.resultsBuilder = resultsBuilder;
}

@Override
public Map.Entry<T, Map<String, Object>> build(Object data) {
public Map.Entry<T, ProfilingInfo> build(Object data) {
List list = (List) data;
if (list == null || list.isEmpty()) return null;

if (list.get(0) instanceof KeyValue) {
if (list.get(0) instanceof KeyValue) { // RESP3
Object resultsData = null, profileData = null;

for (KeyValue keyValue : (List<KeyValue>) data) {
if (PROFILE_STR.equals(BuilderFactory.STRING.build(keyValue.getKey()))) {
return KeyValue.of(replyBuilder.build(data),
BuilderFactory.AGGRESSIVE_ENCODED_OBJECT_MAP.build(keyValue.getValue()));
String keyStr = BuilderFactory.STRING.build(keyValue.getKey());
switch (keyStr) {
case PROFILE_STR_REDIS7:
case PROFILE_STR_REDIS8:
profileData = keyValue.getValue();
break;
case RESULTS_STR_REDIS7:
resultsData = data;
break;
case RESULTS_STR_REDIS8:
resultsData = keyValue.getValue();
break;
}
}

assert resultsData != null : "Could not detect Results data.";
assert profileData != null : "Could not detect Profile data.";
return KeyValue.of(resultsBuilder.build(resultsData),
ProfilingInfo.PROFILING_INFO_BUILDER.build(profileData));
}

return KeyValue.of(replyBuilder.build(list.get(0)),
SearchBuilderFactory.SEARCH_PROFILE_PROFILE.build(list.get(1)));
return KeyValue.of(resultsBuilder.build(list.get(0)),
ProfilingInfo.PROFILING_INFO_BUILDER.build(list.get(1)));
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/redis/clients/jedis/UnifiedJedis.java
Original file line number Diff line number Diff line change
Expand Up @@ -3985,19 +3985,19 @@ public FtAggregateIteration ftAggregateIteration(String indexName, AggregationBu
}

@Override
public Map.Entry<AggregationResult, Map<String, Object>> ftProfileAggregate(String indexName,
public Map.Entry<AggregationResult, ProfilingInfo> ftProfileAggregate(String indexName,
FTProfileParams profileParams, AggregationBuilder aggr) {
return executeCommand(commandObjects.ftProfileAggregate(indexName, profileParams, aggr));
}

@Override
public Map.Entry<SearchResult, Map<String, Object>> ftProfileSearch(String indexName,
public Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,
FTProfileParams profileParams, Query query) {
return executeCommand(commandObjects.ftProfileSearch(indexName, profileParams, query));
}

@Override
public Map.Entry<SearchResult, Map<String, Object>> ftProfileSearch(String indexName,
public Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,
FTProfileParams profileParams, String query, FTSearchParams searchParams) {
return executeCommand(commandObjects.ftProfileSearch(indexName, profileParams, query, searchParams));
}
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/redis/clients/jedis/search/ProfilingInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package redis.clients.jedis.search;

import static redis.clients.jedis.BuilderFactory.AGGRESSIVE_ENCODED_OBJECT;

import redis.clients.jedis.Builder;

public class ProfilingInfo {

private final Object profilingInfo;

private ProfilingInfo(Object profilingInfo) {
this.profilingInfo = profilingInfo;
}

public Object getProfilingInfo() {
return profilingInfo;
}

@Override
public String toString() {
return String.valueOf(profilingInfo);
}

public static final Builder<ProfilingInfo> PROFILING_INFO_BUILDER
= new Builder<ProfilingInfo>() {
@Override
public ProfilingInfo build(Object data) {
return new ProfilingInfo(AGGRESSIVE_ENCODED_OBJECT.build(data));
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ default SearchResult ftSearch(String indexName) {

String ftCursorDel(String indexName, long cursorId);

Map.Entry<AggregationResult, Map<String, Object>> ftProfileAggregate(String indexName,
Map.Entry<AggregationResult, ProfilingInfo> ftProfileAggregate(String indexName,
FTProfileParams profileParams, AggregationBuilder aggr);

Map.Entry<SearchResult, Map<String, Object>> ftProfileSearch(String indexName,
Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,
FTProfileParams profileParams, Query query);

Map.Entry<SearchResult, Map<String, Object>> ftProfileSearch(String indexName,
Map.Entry<SearchResult, ProfilingInfo> ftProfileSearch(String indexName,
FTProfileParams profileParams, String query, FTSearchParams searchParams);

String ftSynUpdate(String indexName, String synonymGroupId, String... terms);
Expand Down
100 changes: 0 additions & 100 deletions src/main/java/redis/clients/jedis/search/SearchBuilderFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static redis.clients.jedis.BuilderFactory.STRING;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand All @@ -12,109 +11,10 @@

import redis.clients.jedis.Builder;
import redis.clients.jedis.BuilderFactory;
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<Map<String, Object>> SEARCH_PROFILE_PROFILE = new Builder<Map<String, Object>>() {

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<String, Object> build(Object data) {
List<Object> list = (List<Object>) SafeEncoder.encodeObject(data);
Map<String, Object> profileMap = new HashMap<>(list.size(), 1f);

for (Object listObject : list) {
List<Object> attributeList = (List<Object>) 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.parseEncodedFloatingPointNumber(value);
} else {
attributeValue = value;
}

} else if (attributeList.size() > 2) {

if (attributeName.equals(RESULT_PROCESSORS_PROFILE_STR)) {
List<Map<String, Object>> 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<String, Object> parseResultProcessors(Object data) {
List<Object> list = (List<Object>) data;
Map<String, Object> 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.parseEncodedFloatingPointNumber(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<String, Object> 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.parseEncodedFloatingPointNumber(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<Map<String, List<String>>> SEARCH_SYNONYM_GROUPS = new Builder<Map<String, List<String>>>() {
@Override
public Map<String, List<String>> build(Object data) {
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/io/redis/test/utils/RedisVersion.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.redis.test.utils;

public class RedisVersion implements Comparable<RedisVersion>{
public class RedisVersion implements Comparable<RedisVersion> {
public static final RedisVersion V6_0_0 = RedisVersion.of("6.0.0");
public static final RedisVersion V7_0_0 = RedisVersion.of("7.0.0");
public static final RedisVersion V7_2_0 = RedisVersion.of("7.2.0");
public static final RedisVersion V7_4 = RedisVersion.of("7.4");
public static final RedisVersion V8_0_0_PRE = RedisVersion.of("7.9.0");
public static final RedisVersion V8_0_0 = RedisVersion.of("8.0.0");

private final int major;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,11 @@
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.StreamEntryID;
import redis.clients.jedis.graph.ResultSet;
import redis.clients.jedis.resps.FunctionStats;
import redis.clients.jedis.resps.GeoRadiusResponse;
import redis.clients.jedis.resps.LCSMatchResult;
import redis.clients.jedis.resps.LibraryInfo;
import redis.clients.jedis.resps.ScanResult;
import redis.clients.jedis.resps.StreamConsumerInfo;
import redis.clients.jedis.resps.StreamConsumersInfo;
import redis.clients.jedis.resps.StreamEntry;
import redis.clients.jedis.resps.StreamFullInfo;
import redis.clients.jedis.resps.StreamGroupInfo;
import redis.clients.jedis.resps.StreamInfo;
import redis.clients.jedis.resps.StreamPendingEntry;
import redis.clients.jedis.resps.StreamPendingSummary;
import redis.clients.jedis.resps.Tuple;
import redis.clients.jedis.resps.*;
import redis.clients.jedis.search.ProfilingInfo;
import redis.clients.jedis.search.SearchResult;
import redis.clients.jedis.search.aggr.AggregationResult;
import redis.clients.jedis.timeseries.TSElement;
import redis.clients.jedis.timeseries.TSInfo;
import redis.clients.jedis.timeseries.TSMGetElement;
import redis.clients.jedis.timeseries.TSMRangeElements;
import redis.clients.jedis.timeseries.*;
import redis.clients.jedis.util.KeyValue;

/**
Expand Down Expand Up @@ -99,9 +84,9 @@ public final static class MyBean {
@Mock protected CommandObject<List<Tuple>> listTupleCommandObject;
@Mock protected CommandObject<List<byte[]>> listBytesCommandObject;
@Mock protected CommandObject<Long> longCommandObject;
@Mock protected CommandObject<Map.Entry<AggregationResult, Map<String, Object>>> entryAggregationResultMapStringObjectCommandObject;
@Mock protected CommandObject<Map.Entry<AggregationResult, ProfilingInfo>> entryAggregationResultMapStringObjectCommandObject;
@Mock protected CommandObject<Map.Entry<Long, byte[]>> entryLongBytesCommandObject;
@Mock protected CommandObject<Map.Entry<SearchResult, Map<String, Object>>> entrySearchResultMapStringObjectCommandObject;
@Mock protected CommandObject<Map.Entry<SearchResult, ProfilingInfo>> entrySearchResultMapStringObjectCommandObject;
@Mock protected CommandObject<Map.Entry<StreamEntryID, List<StreamEntry>>> entryStreamEntryIdListStreamEntryCommandObject;
@Mock protected CommandObject<Map.Entry<StreamEntryID, List<StreamEntryID>>> entryStreamEntryIdListStreamEntryIdCommandObject;
@Mock protected CommandObject<Map<String, List<String>>> mapStringListStringCommandObject;
Expand Down
Loading

0 comments on commit cd9346a

Please sign in to comment.