Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decoding FT.SEARCH reply can be disabled at field level #3926

Merged
merged 3 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions src/main/java/redis/clients/jedis/CommandObjects.java
Original file line number Diff line number Diff line change
Expand Up @@ -3376,19 +3376,20 @@ public final CommandObject<String> ftDropIndexDD(String indexName) {

public final CommandObject<SearchResult> ftSearch(String indexName, String query) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName).add(query),
getSearchResultBuilder(() -> new SearchResultBuilder(true, false, true)));
getSearchResultBuilder(null, () -> new SearchResultBuilder(true, false, true)));
}

public final CommandObject<SearchResult> ftSearch(String indexName, String query, FTSearchParams params) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName)
.add(query).addParams(params.dialectOptional(searchDialect.get())),
getSearchResultBuilder(() -> new SearchResultBuilder(!params.getNoContent(), params.getWithScores(), true)));
getSearchResultBuilder(params.getReturnFieldDecodeMap(), () -> new SearchResultBuilder(
!params.getNoContent(), params.getWithScores(), true, params.getReturnFieldDecodeMap())));
}

public final CommandObject<SearchResult> ftSearch(String indexName, Query query) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.SEARCH, indexName)
.addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(() ->
new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)));
.addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(null,
() -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true)));
}

@Deprecated
Expand All @@ -3397,8 +3398,8 @@ public final CommandObject<SearchResult> ftSearch(byte[] indexName, Query query)
throw new UnsupportedOperationException("binary ft.search is not implemented with resp3.");
}
return new CommandObject<>(checkAndRoundRobinSearchCommand(commandArguments(SearchCommand.SEARCH), indexName)
.addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(() ->
new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), false)));
.addParams(query.dialectOptional(searchDialect.get())), getSearchResultBuilder(null,
() -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), false)));
}

public final CommandObject<String> ftExplain(String indexName, Query query) {
Expand Down Expand Up @@ -3441,20 +3442,27 @@ public final CommandObject<Map.Entry<SearchResult, Map<String, Object>>> ftProfi
String indexName, FTProfileParams profileParams, Query query) {
return new CommandObject<>(checkAndRoundRobinSearchCommand(SearchCommand.PROFILE, indexName)
.add(SearchKeyword.SEARCH).addParams(profileParams).add(SearchKeyword.QUERY)
.addParams(query.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>(
getSearchResultBuilder(() -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true))));
.addParams(query.dialectOptional(searchDialect.get())),
new SearchProfileResponseBuilder<>(getSearchResultBuilder(null,
() -> new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), true))));
}

public final CommandObject<Map.Entry<SearchResult, Map<String, Object>>> 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)
.addParams(searchParams.dialectOptional(searchDialect.get())), new SearchProfileResponseBuilder<>(
getSearchResultBuilder(() -> new SearchResultBuilder(!searchParams.getNoContent(), searchParams.getWithScores(), true))));
.addParams(searchParams.dialectOptional(searchDialect.get())),
new SearchProfileResponseBuilder<>(getSearchResultBuilder(searchParams.getReturnFieldDecodeMap(),
() -> new SearchResultBuilder(!searchParams.getNoContent(), searchParams.getWithScores(), true,
searchParams.getReturnFieldDecodeMap()))));
}

private Builder<SearchResult> getSearchResultBuilder(Supplier<Builder<SearchResult>> resp2) {
if (protocol == RedisProtocol.RESP3) return SearchResult.SEARCH_RESULT_BUILDER;
private Builder<SearchResult> getSearchResultBuilder(
Map<String, Boolean> isReturnFieldDecode, Supplier<Builder<SearchResult>> resp2) {
if (protocol == RedisProtocol.RESP3) {
return isReturnFieldDecode == null ? SearchResult.SEARCH_RESULT_BUILDER
: new SearchResult.PerFieldDecoderSearchResultBuilder(isReturnFieldDecode);
}
return resp2.get();
}

Expand Down
101 changes: 76 additions & 25 deletions src/main/java/redis/clients/jedis/search/Document.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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;
Expand Down Expand Up @@ -50,24 +51,6 @@ public Iterable<Map.Entry<String, Object>> getProperties() {
return fields.entrySet();
}

public static Document load(String id, double score, byte[] payload, List<byte[]> fields) {
return Document.load(id, score, fields, true);
}

public static Document load(String id, double score, List<byte[]> fields, boolean decode) {
Document ret = new Document(id, score);
if (fields != null) {
for (int i = 0; i < fields.size(); i += 2) {
byte[] rawKey = fields.get(i);
byte[] rawValue = fields.get(i + 1);
String key = SafeEncoder.encode(rawKey);
Object value = rawValue == null ? null : decode ? SafeEncoder.encode(rawValue) : rawValue;
ret.set(key, value);
}
}
return ret;
}

/**
* @return the document's id
*/
Expand Down Expand Up @@ -102,16 +85,22 @@ public Object get(String key) {
*/
public String getString(String key) {
Object value = fields.get(key);
if (value instanceof String) {
if (value == null) {
return null;
} else if (value instanceof String) {
return (String) value;
} else if (value instanceof byte[]) {
return SafeEncoder.encode((byte[]) value);
} else {
return String.valueOf(value);
}
return value instanceof byte[] ? SafeEncoder.encode((byte[]) value) : value.toString();
}

public boolean hasProperty(String key) {
return fields.containsKey(key);
}

// TODO: private ??
public Document set(String key, Object value) {
fields.put(key, value);
return this;
Expand All @@ -122,7 +111,9 @@ public Document set(String key, Object value) {
*
* @param score new score to set
* @return the document itself
* @deprecated
*/
@Deprecated
public Document setScore(float score) {
this.score = (double) score;
return this;
Expand All @@ -134,13 +125,58 @@ public String toString() {
", properties:" + this.getProperties();
}

static Builder<Document> SEARCH_DOCUMENT = new Builder<Document>() {
/// RESP2 -->
public static Document load(String id, double score, byte[] payload, List<byte[]> fields) {
return Document.load(id, score, fields, true);
}

public static Document load(String id, double score, List<byte[]> fields, boolean decode) {
return load(id, score, fields, decode, null);
}

/**
* Parse document object from FT.SEARCH reply.
* @param id
* @param score
* @param fields
* @param decode
* @param isFieldDecode checked only if {@code decode=true}
* @return document
*/
public static Document load(String id, double score, List<byte[]> fields, boolean decode,
Map<String, Boolean> isFieldDecode) {
Document ret = new Document(id, score);
if (fields != null) {
for (int i = 0; i < fields.size(); i += 2) {
byte[] rawKey = fields.get(i);
byte[] rawValue = fields.get(i + 1);
String key = SafeEncoder.encode(rawKey);
Object value = rawValue == null ? null
: (decode && (isFieldDecode == null || !Boolean.FALSE.equals(isFieldDecode.get(key))))
? SafeEncoder.encode(rawValue) : rawValue;
ret.set(key, value);
}
}
return ret;
}
/// <-- RESP2

/// RESP3 -->
// TODO: final
static Builder<Document> SEARCH_DOCUMENT = new PerFieldDecoderDocumentBuilder((Map) null);

static final class PerFieldDecoderDocumentBuilder extends Builder<Document> {

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";

private final Map<String, Boolean> isFieldDecode;

public PerFieldDecoderDocumentBuilder(Map<String, Boolean> isFieldDecode) {
this.isFieldDecode = isFieldDecode != null ? isFieldDecode : Collections.emptyMap();
}

@Override
public Document build(Object data) {
List<KeyValue> list = (List<KeyValue>) data;
Expand All @@ -157,13 +193,28 @@ public Document build(Object data) {
score = BuilderFactory.DOUBLE.build(kv.getValue());
break;
case FIELDS_STR:
fields = BuilderFactory.ENCODED_OBJECT_MAP.build(kv.getValue());
fields = makeFieldsMap(isFieldDecode, kv.getValue());
break;
}
}
// assert id != null;
// if (fields == null) fields = Collections.emptyMap();
return new Document(id, score, fields);
}
};

private static Map<String, Object> makeFieldsMap(Map<String, Boolean> isDecode, Object data) {
if (data == null) return null;

final List<KeyValue> list = (List) data;

Map<String, Object> map = new HashMap<>(list.size(), 1f);
list.stream().filter((kv) -> (kv != null && kv.getKey() != null && kv.getValue() != null))
.forEach((kv) -> {
String key = BuilderFactory.STRING.build(kv.getKey());
map.put(key,
(Boolean.FALSE.equals(isDecode.get(key)) ? BuilderFactory.RAW_OBJECT
: BuilderFactory.AGGRESSIVE_ENCODED_OBJECT).build(kv.getValue()));
});
return map;
}
/// <-- RESP3
}
62 changes: 37 additions & 25 deletions src/main/java/redis/clients/jedis/search/FTSearchParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public class FTSearchParams implements IParams {
private final List<IParams> filters = new LinkedList<>();
private Collection<String> inKeys;
private Collection<String> inFields;
private Collection<String> returnFields;
private Collection<FieldName> returnFieldNames;
private Collection<FieldName> returnFieldsNames;
private boolean summarize;
private SummarizeParams summarizeParams;
private boolean highlight;
Expand All @@ -43,6 +42,9 @@ public class FTSearchParams implements IParams {
private Map<String, Object> params;
private Integer dialect;

/// non command parameters
private Map<String, Boolean> returnFieldDecodeMap = null;

public FTSearchParams() {
}

Expand Down Expand Up @@ -78,17 +80,15 @@ public void addParams(CommandArguments args) {
args.add(INFIELDS).add(inFields.size()).addObjects(inFields);
}

if (returnFieldNames != null && !returnFieldNames.isEmpty()) {
if (returnFieldsNames != null && !returnFieldsNames.isEmpty()) {
args.add(RETURN);
LazyRawable returnCountObject = new LazyRawable();
args.add(returnCountObject); // holding a place for setting the total count later.
int returnCount = 0;
for (FieldName fn : returnFieldNames) {
for (FieldName fn : returnFieldsNames) {
returnCount += fn.addCommandArguments(args);
}
returnCountObject.setRaw(Protocol.toByteArray(returnCount));
} else if (returnFields != null && !returnFields.isEmpty()) {
args.add(RETURN).add(returnFields.size()).addObjects(returnFields);
}

if (summarizeParams != null) {
Expand Down Expand Up @@ -256,41 +256,46 @@ public FTSearchParams inFields(Collection<String> fields) {
* @return the query object itself
*/
public FTSearchParams returnFields(String... fields) {
if (returnFieldNames != null) {
Arrays.stream(fields).forEach(f -> returnFieldNames.add(FieldName.of(f)));
} else {
if (returnFields == null) {
returnFields = new ArrayList<>();
}
Arrays.stream(fields).forEach(f -> returnFields.add(f));
if (returnFieldsNames == null) {
returnFieldsNames = new ArrayList<>();
}
Arrays.stream(fields).forEach(f -> returnFieldsNames.add(FieldName.of(f)));
return this;
}

public FTSearchParams returnField(FieldName field) {
initReturnFieldNames();
returnFieldNames.add(field);
return this;
return returnFields(Collections.singleton(field));
}

public FTSearchParams returnFields(FieldName... fields) {
return returnFields(Arrays.asList(fields));
}

public FTSearchParams returnFields(Collection<FieldName> fields) {
initReturnFieldNames();
returnFieldNames.addAll(fields);
if (returnFieldsNames == null) {
returnFieldsNames = new ArrayList<>();
}
returnFieldsNames.addAll(fields);
return this;
}

private void initReturnFieldNames() {
if (returnFieldNames == null) {
returnFieldNames = new ArrayList<>();
}
if (returnFields != null) {
returnFields.forEach(f -> returnFieldNames.add(FieldName.of(f)));
returnFields = null;
public FTSearchParams returnField(String field, boolean decode) {
returnFields(field);
addReturnFieldDecode(field, decode);
return this;
}

public FTSearchParams returnField(FieldName field, boolean decode) {
returnFields(field);
addReturnFieldDecode(field.getAttribute() != null ? field.getAttribute() : field.getName(), decode);
return this;
}

private void addReturnFieldDecode(String returnName, boolean decode) {
if (returnFieldDecodeMap == null) {
returnFieldDecodeMap = new HashMap<>();
}
returnFieldDecodeMap.put(returnName, decode);
}

public FTSearchParams summarize() {
Expand Down Expand Up @@ -436,14 +441,21 @@ public FTSearchParams dialectOptional(int dialect) {
return this;
}

@Internal
public boolean getNoContent() {
return noContent;
}

@Internal
public boolean getWithScores() {
return withScores;
}

@Internal
public Map<String, Boolean> getReturnFieldDecodeMap() {
return returnFieldDecodeMap;
}

/**
* NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive
*/
Expand Down
Loading
Loading