Skip to content

Commit 2204170

Browse files
committed
feat(query): add sortBy and inOrder parameters to vector queries
Add query enhancements for better result control and term ordering: **sortBy parameter:** - Add sortBy/sortDescending to VectorQuery and VectorRangeQuery - Support ascending and descending sort order - Integrate with FTSearchParams for Redis sorting - Port Python test_sort_vector_query and test_sort_range_query **inOrder parameter:** - Add inOrder to VectorQuery and VectorRangeQuery - FilterQuery already had inOrder support - Requires terms in field to match query order - Uses FTSearchParams.inOrder() for Redis enforcement **Implementation details:** - Update SearchIndex to handle sorting via searchWithSort() - Support both sortBy and inOrder simultaneously - Fix VectorRangeQuery distance field name (vector_distance vs distance) - Add 4 comprehensive integration tests All 312+ tests passing with no regressions.
1 parent d9baf56 commit 2204170

File tree

4 files changed

+449
-4
lines changed

4 files changed

+449
-4
lines changed

core/src/main/java/com/redis/vl/index/SearchIndex.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,13 @@ public SearchResult search(VectorQuery query) {
11041104
// Convert VectorQuery to search string
11051105
String queryString = query.toQueryString();
11061106
Map<String, Object> params = query.toParams();
1107+
1108+
// Handle sorting or inOrder if specified
1109+
if ((query.getSortBy() != null && !query.getSortBy().isEmpty()) || query.isInOrder()) {
1110+
return searchWithSort(
1111+
queryString, params, query.getSortBy(), query.isSortDescending(), query.isInOrder());
1112+
}
1113+
11071114
return search(queryString, params);
11081115
}
11091116

@@ -1138,6 +1145,57 @@ public SearchResult search(String query, Map<String, Object> params) {
11381145
}
11391146
}
11401147

1148+
/**
1149+
* Search with sorting and/or inOrder support.
1150+
*
1151+
* @param query The query string
1152+
* @param params Query parameters
1153+
* @param sortBy Field to sort by (can be null)
1154+
* @param descending Whether to sort in descending order
1155+
* @param inOrder Whether to require terms in field to have same order as in query
1156+
* @return Search results
1157+
*/
1158+
private SearchResult searchWithSort(
1159+
String query, Map<String, Object> params, String sortBy, boolean descending, boolean inOrder) {
1160+
if (!exists()) {
1161+
throw new RedisVLException("Index " + getName() + " does not exist");
1162+
}
1163+
1164+
UnifiedJedis jedis = getUnifiedJedis();
1165+
try {
1166+
// Convert params to FTSearchParams
1167+
redis.clients.jedis.search.FTSearchParams searchParams =
1168+
new redis.clients.jedis.search.FTSearchParams();
1169+
1170+
// Set dialect to 2 for KNN queries
1171+
searchParams.dialect(2);
1172+
1173+
// Add vector parameters if present
1174+
if (params != null && !params.isEmpty()) {
1175+
addParamsToSearchParams(searchParams, params);
1176+
}
1177+
1178+
// Add sorting if specified
1179+
if (sortBy != null && !sortBy.isEmpty()) {
1180+
redis.clients.jedis.args.SortingOrder order =
1181+
descending
1182+
? redis.clients.jedis.args.SortingOrder.DESC
1183+
: redis.clients.jedis.args.SortingOrder.ASC;
1184+
searchParams.sortBy(sortBy, order);
1185+
}
1186+
1187+
// Add inOrder if specified
1188+
if (inOrder) {
1189+
searchParams.inOrder();
1190+
}
1191+
1192+
return jedis.ftSearch(schema.getName(), query, searchParams);
1193+
} catch (Exception e) {
1194+
throw new RuntimeException(
1195+
"Failed to search index with sorting/inOrder: " + e.getMessage(), e);
1196+
}
1197+
}
1198+
11411199
/**
11421200
* List all search indexes in Redis
11431201
*
@@ -1227,6 +1285,9 @@ public List<Map<String, Object>> query(Object query) {
12271285
.hybridQuery(vq.getHybridQuery())
12281286
.efRuntime(vq.getEfRuntime())
12291287
.returnFields(vq.getReturnFields())
1288+
.sortBy(vq.getSortBy())
1289+
.sortDescending(vq.isSortDescending())
1290+
.inOrder(vq.isInOrder())
12301291
.build();
12311292
}
12321293

@@ -1241,14 +1302,19 @@ public List<Map<String, Object>> query(Object query) {
12411302
.field(vrq.getField())
12421303
.numResults(vrq.getNumResults())
12431304
.returnDistance(true)
1305+
.sortBy(vrq.getSortBy())
1306+
.sortDescending(vrq.isSortDescending())
1307+
.inOrder(vrq.isInOrder())
12441308
.build();
12451309
SearchResult result = search(vq);
12461310
// Filter results by distance threshold
12471311
List<Map<String, Object>> allResults = processSearchResult(result);
12481312
List<Map<String, Object>> filtered = new ArrayList<>();
12491313
for (Map<String, Object> doc : allResults) {
1250-
if (doc.containsKey("distance")) {
1251-
double distance = Double.parseDouble(doc.get("distance").toString());
1314+
// Check both "distance" and "vector_distance" for compatibility
1315+
Object distanceObj = doc.getOrDefault("distance", doc.get("vector_distance"));
1316+
if (distanceObj != null) {
1317+
double distance = Double.parseDouble(distanceObj.toString());
12521318
if (distance <= vrq.getDistanceThreshold()) {
12531319
filtered.add(doc);
12541320
}

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

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ public class VectorQuery {
5252
/** Batch size for hybrid search */
5353
private Integer batchSize;
5454

55+
/** Field to sort results by */
56+
private String sortBy;
57+
58+
/** Whether to sort in descending order (default: ascending) */
59+
private boolean sortDescending;
60+
61+
/** Whether to require terms in field to have same order as in query */
62+
private boolean inOrder;
63+
5564
/** Private constructor */
5665
private VectorQuery(
5766
String field,
@@ -65,7 +74,10 @@ private VectorQuery(
6574
String hybridQuery,
6675
Integer efRuntime,
6776
List<String> returnFields,
68-
boolean normalizeVectorDistance) {
77+
boolean normalizeVectorDistance,
78+
String sortBy,
79+
boolean sortDescending,
80+
boolean inOrder) {
6981
this.field = field;
7082
this.vector = vector != null ? vector.clone() : null; // Defensive copy
7183
this.numResults = numResults;
@@ -78,6 +90,9 @@ private VectorQuery(
7890
this.efRuntime = efRuntime;
7991
this.returnFields = returnFields;
8092
this.normalizeVectorDistance = normalizeVectorDistance;
93+
this.sortBy = sortBy;
94+
this.sortDescending = sortDescending;
95+
this.inOrder = inOrder;
8196
}
8297

8398
/** Create a builder */
@@ -330,6 +345,18 @@ public boolean isNormalizeVectorDistance() {
330345
return normalizeVectorDistance;
331346
}
332347

348+
public String getSortBy() {
349+
return sortBy;
350+
}
351+
352+
public boolean isSortDescending() {
353+
return sortDescending;
354+
}
355+
356+
public boolean isInOrder() {
357+
return inOrder;
358+
}
359+
333360
@Override
334361
public String toString() {
335362
StringBuilder sb = new StringBuilder();
@@ -366,6 +393,9 @@ public static class Builder {
366393
private Integer efRuntime;
367394
private List<String> returnFields;
368395
private boolean normalizeVectorDistance = false;
396+
private String sortBy;
397+
private boolean sortDescending = false;
398+
private boolean inOrder = false;
369399

370400
private static float[] toFloatArray(double[] doubles) {
371401
if (doubles == null) return null;
@@ -495,6 +525,21 @@ public Builder normalizeVectorDistance(boolean normalize) {
495525
return this;
496526
}
497527

528+
public Builder sortBy(String sortBy) {
529+
this.sortBy = sortBy;
530+
return this;
531+
}
532+
533+
public Builder sortDescending(boolean descending) {
534+
this.sortDescending = descending;
535+
return this;
536+
}
537+
538+
public Builder inOrder(boolean inOrder) {
539+
this.inOrder = inOrder;
540+
return this;
541+
}
542+
498543
public VectorQuery build() {
499544
// Validate required fields
500545
if (field == null || field.trim().isEmpty()) {
@@ -523,7 +568,10 @@ public VectorQuery build() {
523568
hybridQuery,
524569
efRuntime,
525570
returnFields,
526-
normalizeVectorDistance);
571+
normalizeVectorDistance,
572+
sortBy,
573+
sortDescending,
574+
inOrder);
527575
}
528576
}
529577
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public class VectorRangeQuery {
1414
private final boolean normalizeVectorDistance;
1515
private double distanceThreshold;
1616
private Double epsilon;
17+
private final String sortBy;
18+
private final boolean sortDescending;
19+
private final boolean inOrder;
1720

1821
private VectorRangeQuery(Builder builder) {
1922
// Validate before modifying state to avoid partial initialization
@@ -33,6 +36,9 @@ private VectorRangeQuery(Builder builder) {
3336
this.returnScore = builder.returnScore;
3437
this.normalizeVectorDistance = builder.normalizeVectorDistance;
3538
this.epsilon = builder.epsilon;
39+
this.sortBy = builder.sortBy;
40+
this.sortDescending = builder.sortDescending;
41+
this.inOrder = builder.inOrder;
3642
}
3743

3844
public static Builder builder() {
@@ -91,6 +97,18 @@ public void setEpsilon(double epsilon) {
9197
this.epsilon = epsilon;
9298
}
9399

100+
public String getSortBy() {
101+
return sortBy;
102+
}
103+
104+
public boolean isSortDescending() {
105+
return sortDescending;
106+
}
107+
108+
public boolean isInOrder() {
109+
return inOrder;
110+
}
111+
94112
/**
95113
* Build the query string for Redis range query
96114
*
@@ -140,6 +158,9 @@ public static class Builder {
140158
private boolean returnScore = false;
141159
private boolean normalizeVectorDistance = false;
142160
private Double epsilon;
161+
private String sortBy;
162+
private boolean sortDescending = false;
163+
private boolean inOrder = false;
143164

144165
public Builder vector(float[] vector) {
145166
this.vector = vector != null ? vector.clone() : null;
@@ -195,6 +216,21 @@ public Builder epsilon(double epsilon) {
195216
return this;
196217
}
197218

219+
public Builder sortBy(String sortBy) {
220+
this.sortBy = sortBy;
221+
return this;
222+
}
223+
224+
public Builder sortDescending(boolean descending) {
225+
this.sortDescending = descending;
226+
return this;
227+
}
228+
229+
public Builder inOrder(boolean inOrder) {
230+
this.inOrder = inOrder;
231+
return this;
232+
}
233+
198234
public VectorRangeQuery build() {
199235
return new VectorRangeQuery(this);
200236
}

0 commit comments

Comments
 (0)