Skip to content

Commit cf5a179

Browse files
committed
Refactor PgVercorStore filter template to use JSONB field access
Changed the filter template in PgVercorStore to replace JSONPath expressions with JSONB field access, using standard SQL operators such as '=' instead of '==', 'AND' instead of '&&', and 'OR' instead of '||'. This adjustment addresses compatibility issues with the 'IN' operator, which previously returned parse errors. Also updated PgVectorFilterExpressionConverter and corresponding tests to align with these changes. - Replaced `metadata::jsonb @@ '$.key == "value"'::jsonpath` with `metadata::jsonb->>'$.key' = 'value'` - Fixed parsing issues with `metadata::jsonb @@ '$.key in ["value"]'::jsonpath` by using JSONB field access. - Updated logical operators IN filter expressions to standard SQL syntax. These changes improve the clarity, execution reliability, and compatibility of filter expressions in the database.
1 parent 5888fae commit cf5a179

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

spring-ai-core/src/main/java/org/springframework/ai/vectorstore/filter/converter/PgVectorFilterExpressionConverter.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.ai.vectorstore.filter.converter;
1717

18+
import org.springframework.ai.vectorstore.filter.Filter;
1819
import org.springframework.ai.vectorstore.filter.Filter.Expression;
1920
import org.springframework.ai.vectorstore.filter.Filter.Group;
2021
import org.springframework.ai.vectorstore.filter.Filter.Key;
@@ -37,11 +38,11 @@ protected void doExpression(Expression expression, StringBuilder context) {
3738
private String getOperationSymbol(Expression exp) {
3839
switch (exp.type()) {
3940
case AND:
40-
return " && ";
41+
return " AND ";
4142
case OR:
42-
return " || ";
43+
return " OR ";
4344
case EQ:
44-
return " == ";
45+
return " = ";
4546
case NE:
4647
return " != ";
4748
case LT:
@@ -53,17 +54,34 @@ private String getOperationSymbol(Expression exp) {
5354
case GTE:
5455
return " >= ";
5556
case IN:
56-
return " in ";
57+
return " IN ";
5758
case NIN:
58-
return " nin ";
59+
return " NOT IN ";
5960
default:
6061
throw new RuntimeException("Not supported expression type: " + exp.type());
6162
}
6263
}
6364

6465
@Override
6566
protected void doKey(Key key, StringBuilder context) {
66-
context.append("$." + key.key());
67+
context.append("metadata::jsonb->>'");
68+
if (hasOuterQuotes(key.key())) {
69+
context.append(removeOuterQuotes(key.key()));
70+
}
71+
else {
72+
context.append(key.key());
73+
}
74+
context.append('\'');
75+
}
76+
77+
@Override
78+
protected void doSingleValue(Object value, StringBuilder context) {
79+
if (value instanceof String) {
80+
context.append(String.format("\'%s\'", value));
81+
}
82+
else {
83+
context.append(value);
84+
}
6785
}
6886

6987
@Override
@@ -76,4 +94,14 @@ protected void doEndGroup(Group group, StringBuilder context) {
7694
context.append(")");
7795
}
7896

97+
@Override
98+
protected void doStartValueRange(Filter.Value listValue, StringBuilder context) {
99+
context.append("(");
100+
}
101+
102+
@Override
103+
protected void doEndValueRange(Filter.Value listValue, StringBuilder context) {
104+
context.append(")");
105+
}
106+
79107
}

spring-ai-core/src/test/java/org/springframework/ai/vectorstore/filter/converter/PgVectorFilterExpressionConverterTests.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class PgVectorFilterExpressionConverterTests {
4646
public void testEQ() {
4747
// country == "BG"
4848
String vectorExpr = converter.convertExpression(new Expression(EQ, new Key("country"), new Value("BG")));
49-
assertThat(vectorExpr).isEqualTo("$.country == \"BG\"");
49+
assertThat(vectorExpr).isEqualTo("metadata::jsonb->>'country' = 'BG'");
5050
}
5151

5252
@Test
@@ -55,15 +55,15 @@ public void tesEqAndGte() {
5555
String vectorExpr = converter
5656
.convertExpression(new Expression(AND, new Expression(EQ, new Key("genre"), new Value("drama")),
5757
new Expression(GTE, new Key("year"), new Value(2020))));
58-
assertThat(vectorExpr).isEqualTo("$.genre == \"drama\" && $.year >= 2020");
58+
assertThat(vectorExpr).isEqualTo("metadata::jsonb->>'genre' = 'drama' AND metadata::jsonb->>'year' >= 2020");
5959
}
6060

6161
@Test
6262
public void tesIn() {
6363
// genre in ["comedy", "documentary", "drama"]
6464
String vectorExpr = converter.convertExpression(
6565
new Expression(IN, new Key("genre"), new Value(List.of("comedy", "documentary", "drama"))));
66-
assertThat(vectorExpr).isEqualTo("$.genre in [\"comedy\",\"documentary\",\"drama\"]");
66+
assertThat(vectorExpr).isEqualTo("metadata::jsonb->>'genre' IN ('comedy','documentary','drama')");
6767
}
6868

6969
@Test
@@ -73,7 +73,8 @@ public void testNe() {
7373
.convertExpression(new Expression(OR, new Expression(GTE, new Key("year"), new Value(2020)),
7474
new Expression(AND, new Expression(EQ, new Key("country"), new Value("BG")),
7575
new Expression(NE, new Key("city"), new Value("Sofia")))));
76-
assertThat(vectorExpr).isEqualTo("$.year >= 2020 || $.country == \"BG\" && $.city != \"Sofia\"");
76+
assertThat(vectorExpr).isEqualTo(
77+
"metadata::jsonb->>'year' >= 2020 OR metadata::jsonb->>'country' = 'BG' AND metadata::jsonb->>'city' != 'Sofia'");
7778
}
7879

7980
@Test
@@ -83,8 +84,8 @@ public void testGroup() {
8384
new Group(new Expression(OR, new Expression(GTE, new Key("year"), new Value(2020)),
8485
new Expression(EQ, new Key("country"), new Value("BG")))),
8586
new Expression(NIN, new Key("city"), new Value(List.of("Sofia", "Plovdiv")))));
86-
assertThat(vectorExpr)
87-
.isEqualTo("($.year >= 2020 || $.country == \"BG\") && $.city nin [\"Sofia\",\"Plovdiv\"]");
87+
assertThat(vectorExpr).isEqualTo(
88+
"(metadata::jsonb->>'year' >= 2020 OR metadata::jsonb->>'country' = 'BG') AND metadata::jsonb->>'city' NOT IN ('Sofia','Plovdiv')");
8889
}
8990

9091
@Test
@@ -95,7 +96,8 @@ public void tesBoolean() {
9596
new Expression(GTE, new Key("year"), new Value(2020))),
9697
new Expression(IN, new Key("country"), new Value(List.of("BG", "NL", "US")))));
9798

98-
assertThat(vectorExpr).isEqualTo("$.isOpen == true && $.year >= 2020 && $.country in [\"BG\",\"NL\",\"US\"]");
99+
assertThat(vectorExpr).isEqualTo(
100+
"metadata::jsonb->>'isOpen' = true AND metadata::jsonb->>'year' >= 2020 AND metadata::jsonb->>'country' IN ('BG','NL','US')");
99101
}
100102

101103
@Test
@@ -105,14 +107,15 @@ public void testDecimal() {
105107
.convertExpression(new Expression(AND, new Expression(GTE, new Key("temperature"), new Value(-15.6)),
106108
new Expression(LTE, new Key("temperature"), new Value(20.13))));
107109

108-
assertThat(vectorExpr).isEqualTo("$.temperature >= -15.6 && $.temperature <= 20.13");
110+
assertThat(vectorExpr)
111+
.isEqualTo("metadata::jsonb->>'temperature' >= -15.6 AND metadata::jsonb->>'temperature' <= 20.13");
109112
}
110113

111114
@Test
112115
public void testComplexIdentifiers() {
113116
String vectorExpr = converter
114117
.convertExpression(new Expression(EQ, new Key("\"country 1 2 3\""), new Value("BG")));
115-
assertThat(vectorExpr).isEqualTo("$.\"country 1 2 3\" == \"BG\"");
118+
assertThat(vectorExpr).isEqualTo("metadata::jsonb->>'country 1 2 3' = 'BG'");
116119
}
117120

118121
}

vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/PgVectorStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ public List<Document> similaritySearch(SearchRequest request) {
292292
String jsonPathFilter = "";
293293

294294
if (StringUtils.hasText(nativeFilterExpression)) {
295-
jsonPathFilter = " AND metadata::jsonb @@ '" + nativeFilterExpression + "'::jsonpath ";
295+
jsonPathFilter = " AND (" + nativeFilterExpression + ") ";
296296
}
297297

298298
double distance = 1 - request.getSimilarityThreshold();

vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorStoreIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ public void searchWithFilters(String distanceType) {
157157
assertThat(results).hasSize(1);
158158
assertThat(results.get(0).getId()).isEqualTo(nlDocument.getId());
159159

160+
results = vectorStore.similaritySearch(searchRequest.withFilterExpression("country in ['NL', 'SP']"));
161+
162+
assertThat(results).hasSize(1);
163+
assertThat(results.get(0).getId()).isEqualTo(nlDocument.getId());
164+
160165
results = vectorStore.similaritySearch(searchRequest.withFilterExpression("country == 'BG'"));
161166

162167
assertThat(results).hasSize(2);

0 commit comments

Comments
 (0)