Skip to content

Commit c767150

Browse files
committed
feat: implement skip_decode parameter for binary fields (port of Python PR #389)
Add skipDecodeFields parameter to query builders to control binary field decoding: - FilterQuery, VectorQuery, VectorRangeQuery, TextQuery - Accepts List<String> or varargs of field names - Returns immutable copy via getSkipDecodeFields() Implementation details: - Field names that should not be decoded from binary format - Useful for binary data like embeddings and image data - Validates field names cannot be null - Maintains backward compatibility (defaults to empty list) Test coverage: - 10 unit tests in SkipDecodeFieldsTest - Tests for all query types - Validation tests for null handling - Varargs convenience method tests Python reference: - skip_decode parameter in return_fields() method - Prevents automatic decoding of specified binary fields - Allows retrieval of raw bytes for embeddings/images Refs: redis/redis-vl-python#389 Fixes: #252
1 parent 3d92bad commit c767150

File tree

5 files changed

+412
-2
lines changed

5 files changed

+412
-2
lines changed

core/src/main/java/com/redis/vl/query/FilterQuery.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ private FilterQuery(FilterQueryBuilder builder) {
4444
this.sortAscending = builder.sortAscending;
4545
this.inOrder = builder.inOrder;
4646
this.params = builder.params != null ? Map.copyOf(builder.params) : Map.of();
47+
this.skipDecodeFields =
48+
builder.skipDecodeFields != null ? List.copyOf(builder.skipDecodeFields) : List.of();
4749
}
4850

4951
/**
@@ -92,6 +94,11 @@ public static FilterQueryBuilder builder() {
9294
/** Additional parameters for the query. Python: params (Optional[Dict[str, Any]]) */
9395
private final Map<String, Object> params;
9496

97+
/**
98+
* Fields that should not be decoded from binary format. Python: skip_decode parameter (PR #389)
99+
*/
100+
private final List<String> skipDecodeFields;
101+
95102
/**
96103
* Build Redis Query object from FilterQuery.
97104
*
@@ -194,6 +201,17 @@ public Map<String, Object> getParams() {
194201
return params;
195202
}
196203

204+
/**
205+
* Get the list of fields that should not be decoded from binary format.
206+
*
207+
* <p>Python equivalent: _skip_decode_fields attribute (PR #389)
208+
*
209+
* @return List of field names to skip decoding
210+
*/
211+
public List<String> getSkipDecodeFields() {
212+
return skipDecodeFields;
213+
}
214+
197215
/** Builder for FilterQuery with defensive copying. */
198216
public static class FilterQueryBuilder {
199217
private Filter filterExpression;
@@ -204,6 +222,7 @@ public static class FilterQueryBuilder {
204222
private boolean sortAscending = true; // Default to ascending
205223
private boolean inOrder = false;
206224
private Map<String, Object> params = Map.of();
225+
private List<String> skipDecodeFields = List.of();
207226

208227
FilterQueryBuilder() {}
209228

@@ -359,6 +378,52 @@ public FilterQueryBuilder params(Map<String, Object> params) {
359378
return this;
360379
}
361380

381+
/**
382+
* Set fields that should not be decoded from binary format.
383+
*
384+
* <p>Python equivalent: skip_decode parameter in return_fields() (PR #389)
385+
*
386+
* @param skipDecodeFields List of field names
387+
* @return this builder
388+
* @throws IllegalArgumentException if list contains null values
389+
*/
390+
public FilterQueryBuilder skipDecodeFields(List<String> skipDecodeFields) {
391+
if (skipDecodeFields == null) {
392+
this.skipDecodeFields = List.of();
393+
return this;
394+
}
395+
// Validate no null values
396+
for (String field : skipDecodeFields) {
397+
if (field == null) {
398+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
399+
}
400+
}
401+
this.skipDecodeFields = List.copyOf(skipDecodeFields);
402+
return this;
403+
}
404+
405+
/**
406+
* Set fields that should not be decoded from binary format (varargs).
407+
*
408+
* @param fields Field names
409+
* @return this builder
410+
* @throws IllegalArgumentException if any field is null
411+
*/
412+
public FilterQueryBuilder skipDecodeFields(String... fields) {
413+
if (fields == null || fields.length == 0) {
414+
this.skipDecodeFields = List.of();
415+
return this;
416+
}
417+
// Validate no null values
418+
for (String field : fields) {
419+
if (field == null) {
420+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
421+
}
422+
}
423+
this.skipDecodeFields = List.of(fields);
424+
return this;
425+
}
426+
362427
/**
363428
* Build the FilterQuery instance.
364429
*

core/src/main/java/com/redis/vl/query/TextQuery.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class TextQuery {
5252
/** Whether to sort in descending order */
5353
private final boolean sortDescending;
5454

55+
/** Fields that should not be decoded from binary format */
56+
private final List<String> skipDecodeFields;
57+
5558
private TextQuery(Builder builder) {
5659
this.text = builder.text;
5760
this.scorer = builder.scorer;
@@ -60,6 +63,8 @@ private TextQuery(Builder builder) {
6063
this.fieldWeights = new HashMap<>(builder.fieldWeights);
6164
this.sortBy = builder.sortBy;
6265
this.sortDescending = builder.sortDescending;
66+
this.skipDecodeFields =
67+
builder.skipDecodeFields != null ? List.copyOf(builder.skipDecodeFields) : List.of();
6368
}
6469

6570
/**
@@ -81,6 +86,15 @@ public Map<String, Double> getFieldWeights() {
8186
return new HashMap<>(fieldWeights);
8287
}
8388

89+
/**
90+
* Get the list of fields that should not be decoded from binary format.
91+
*
92+
* @return List of field names to skip decoding
93+
*/
94+
public List<String> getSkipDecodeFields() {
95+
return skipDecodeFields;
96+
}
97+
8498
/**
8599
* Build the Redis query string for text search with weighted fields.
86100
*
@@ -174,6 +188,7 @@ public static class Builder {
174188
private Map<String, Double> fieldWeights = new HashMap<>();
175189
private String sortBy;
176190
private boolean sortDescending = false;
191+
private List<String> skipDecodeFields = List.of();
177192

178193
/**
179194
* Set the text to search for.
@@ -327,6 +342,49 @@ public Builder sortDescending(boolean descending) {
327342
return this;
328343
}
329344

345+
/**
346+
* Set fields that should not be decoded from binary format.
347+
*
348+
* @param skipDecodeFields List of field names
349+
* @return This builder
350+
* @throws IllegalArgumentException if list contains null values
351+
*/
352+
public Builder skipDecodeFields(List<String> skipDecodeFields) {
353+
if (skipDecodeFields == null) {
354+
this.skipDecodeFields = List.of();
355+
return this;
356+
}
357+
// Validate no null values
358+
for (String field : skipDecodeFields) {
359+
if (field == null) {
360+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
361+
}
362+
}
363+
this.skipDecodeFields = List.copyOf(skipDecodeFields);
364+
return this;
365+
}
366+
367+
/**
368+
* Set fields that should not be decoded from binary format (varargs).
369+
*
370+
* @param fields Field names
371+
* @return This builder
372+
* @throws IllegalArgumentException if any field is null
373+
*/
374+
public Builder skipDecodeFields(String... fields) {
375+
if (fields == null || fields.length == 0) {
376+
this.skipDecodeFields = List.of();
377+
return this;
378+
}
379+
for (String field : fields) {
380+
if (field == null) {
381+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
382+
}
383+
}
384+
this.skipDecodeFields = List.of(fields);
385+
return this;
386+
}
387+
330388
/**
331389
* Build the TextQuery instance.
332390
*

core/src/main/java/com/redis/vl/query/VectorQuery.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ public class VectorQuery {
6161
/** Whether to require terms in field to have same order as in query */
6262
private boolean inOrder;
6363

64+
/** Fields that should not be decoded from binary format */
65+
private List<String> skipDecodeFields;
66+
6467
/** Private constructor */
6568
private VectorQuery(
6669
String field,
@@ -77,7 +80,8 @@ private VectorQuery(
7780
boolean normalizeVectorDistance,
7881
String sortBy,
7982
boolean sortDescending,
80-
boolean inOrder) {
83+
boolean inOrder,
84+
List<String> skipDecodeFields) {
8185
this.field = field;
8286
this.vector = vector != null ? vector.clone() : null; // Defensive copy
8387
this.numResults = numResults;
@@ -93,6 +97,8 @@ private VectorQuery(
9397
this.sortBy = sortBy;
9498
this.sortDescending = sortDescending;
9599
this.inOrder = inOrder;
100+
this.skipDecodeFields =
101+
skipDecodeFields != null ? new ArrayList<>(skipDecodeFields) : new ArrayList<>();
96102
}
97103

98104
/**
@@ -505,6 +511,15 @@ public boolean isInOrder() {
505511
return inOrder;
506512
}
507513

514+
/**
515+
* Get the list of fields that should not be decoded from binary format.
516+
*
517+
* @return List of field names to skip decoding
518+
*/
519+
public List<String> getSkipDecodeFields() {
520+
return skipDecodeFields != null ? new ArrayList<>(skipDecodeFields) : new ArrayList<>();
521+
}
522+
508523
@Override
509524
public String toString() {
510525
StringBuilder sb = new StringBuilder();
@@ -550,6 +565,7 @@ public Builder() {
550565
private String sortBy;
551566
private boolean sortDescending = false;
552567
private boolean inOrder = false;
568+
private List<String> skipDecodeFields = new ArrayList<>();
553569

554570
private static float[] toFloatArray(double[] doubles) {
555571
if (doubles == null) return null;
@@ -904,6 +920,49 @@ public Builder inOrder(boolean inOrder) {
904920
return this;
905921
}
906922

923+
/**
924+
* Set fields that should not be decoded from binary format.
925+
*
926+
* @param skipDecodeFields List of field names
927+
* @return This builder
928+
* @throws IllegalArgumentException if list contains null values
929+
*/
930+
public Builder skipDecodeFields(List<String> skipDecodeFields) {
931+
if (skipDecodeFields == null) {
932+
this.skipDecodeFields = new ArrayList<>();
933+
return this;
934+
}
935+
// Validate no null values
936+
for (String field : skipDecodeFields) {
937+
if (field == null) {
938+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
939+
}
940+
}
941+
this.skipDecodeFields = new ArrayList<>(skipDecodeFields);
942+
return this;
943+
}
944+
945+
/**
946+
* Set fields that should not be decoded from binary format (varargs).
947+
*
948+
* @param fields Field names
949+
* @return This builder
950+
* @throws IllegalArgumentException if any field is null
951+
*/
952+
public Builder skipDecodeFields(String... fields) {
953+
if (fields == null || fields.length == 0) {
954+
this.skipDecodeFields = new ArrayList<>();
955+
return this;
956+
}
957+
for (String field : fields) {
958+
if (field == null) {
959+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
960+
}
961+
}
962+
this.skipDecodeFields = new ArrayList<>(Arrays.asList(fields));
963+
return this;
964+
}
965+
907966
/**
908967
* Build the VectorQuery
909968
*
@@ -940,7 +999,8 @@ public VectorQuery build() {
940999
normalizeVectorDistance,
9411000
sortBy,
9421001
sortDescending,
943-
inOrder);
1002+
inOrder,
1003+
skipDecodeFields);
9441004
}
9451005
}
9461006
}

core/src/main/java/com/redis/vl/query/VectorRangeQuery.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public final class VectorRangeQuery {
2222
private final String sortBy;
2323
private final boolean sortDescending;
2424
private final boolean inOrder;
25+
private final List<String> skipDecodeFields;
2526

2627
private VectorRangeQuery(Builder builder) {
2728
// Validate before modifying state to avoid partial initialization
@@ -44,6 +45,8 @@ private VectorRangeQuery(Builder builder) {
4445
this.sortBy = builder.sortBy;
4546
this.sortDescending = builder.sortDescending;
4647
this.inOrder = builder.inOrder;
48+
this.skipDecodeFields =
49+
builder.skipDecodeFields != null ? List.copyOf(builder.skipDecodeFields) : List.of();
4750
}
4851

4952
/**
@@ -188,6 +191,15 @@ public boolean isInOrder() {
188191
return inOrder;
189192
}
190193

194+
/**
195+
* Get the list of fields that should not be decoded from binary format.
196+
*
197+
* @return List of field names to skip decoding
198+
*/
199+
public List<String> getSkipDecodeFields() {
200+
return skipDecodeFields;
201+
}
202+
191203
/**
192204
* Build the query string for Redis range query
193205
*
@@ -242,6 +254,7 @@ public static class Builder {
242254
private String sortBy;
243255
private boolean sortDescending = false;
244256
private boolean inOrder = false;
257+
private List<String> skipDecodeFields = List.of();
245258

246259
/** Package-private constructor used by builder() method. */
247260
Builder() {}
@@ -454,6 +467,49 @@ public Builder inOrder(boolean inOrder) {
454467
return this;
455468
}
456469

470+
/**
471+
* Set fields that should not be decoded from binary format.
472+
*
473+
* @param skipDecodeFields List of field names
474+
* @return This builder
475+
* @throws IllegalArgumentException if list contains null values
476+
*/
477+
public Builder skipDecodeFields(List<String> skipDecodeFields) {
478+
if (skipDecodeFields == null) {
479+
this.skipDecodeFields = List.of();
480+
return this;
481+
}
482+
// Validate no null values
483+
for (String field : skipDecodeFields) {
484+
if (field == null) {
485+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
486+
}
487+
}
488+
this.skipDecodeFields = List.copyOf(skipDecodeFields);
489+
return this;
490+
}
491+
492+
/**
493+
* Set fields that should not be decoded from binary format (varargs).
494+
*
495+
* @param fields Field names
496+
* @return This builder
497+
* @throws IllegalArgumentException if any field is null
498+
*/
499+
public Builder skipDecodeFields(String... fields) {
500+
if (fields == null || fields.length == 0) {
501+
this.skipDecodeFields = List.of();
502+
return this;
503+
}
504+
for (String field : fields) {
505+
if (field == null) {
506+
throw new IllegalArgumentException("skipDecodeFields cannot contain null values");
507+
}
508+
}
509+
this.skipDecodeFields = List.of(fields);
510+
return this;
511+
}
512+
457513
/**
458514
* Build the VectorRangeQuery instance.
459515
*

0 commit comments

Comments
 (0)