Skip to content

Commit 35c8995

Browse files
committed
fix(parser): parse vector distances before relational comparisons
Treat the existing PostgreSQL distance operators as left-associative expressions below addition and above relational comparison. Preserve their AST classes and add complete-SQL and precedence regressions. Fixes #2851 Generated-by: DeepSeek Generated-by: Codex
1 parent 220d0ed commit 35c8995

2 files changed

Lines changed: 101 additions & 3 deletions

File tree

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,8 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
10041004
// the bitwise operators bind looser than + and -, as in PostgreSQL, MySQL and SQLite
10051005
else if (op == S_HASH_OPERATOR || op == OP_LSHIFT
10061006
|| op == OP_RSHIFT) prec = 4;
1007+
// PostgreSQL distance operators share the left-associative tier below + and -.
1008+
else if (op == OP_COSINESIMILARITY) prec = 4;
10071009
else if (op == OP_PIPE) {
10081010
// MySQL tiers bitwise: <<, >> above & above |. Other dialects
10091011
// (PostgreSQL, SQLite, ...) keep one left-associative level.
@@ -1019,6 +1021,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
10191021
else if ("&".equals(img)) {
10201022
prec = Dialect.MYSQL.name().equals(getAsString(Feature.dialect)) ? 3 : 4;
10211023
}
1024+
else if ("<->".equals(img) || "<#>".equals(img)) prec = 4;
10221025
else break; // not an arithmetic operator
10231026
}
10241027

@@ -1054,13 +1057,15 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
10541057
else if (op == OP_PIPE) { BitwiseOr r = new BitwiseOr(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10551058
else if (op == OP_LSHIFT) { BitwiseLeftShift r = new BitwiseLeftShift(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10561059
else if (op == OP_RSHIFT) { BitwiseRightShift r = new BitwiseRightShift(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
1060+
else if (op == OP_COSINESIMILARITY) { CosineSimilarity r = new CosineSimilarity(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10571061
else {
10581062
String img = t.image;
10591063
if ("*".equals(img)) { Multiplication r = new Multiplication(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10601064
else if ("+".equals(img)) { Addition r = new Addition(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10611065
else if ("-".equals(img)) { Subtraction r = new Subtraction(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10621066
else if ("%".equals(img)) { Modulo r = new Modulo(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10631067
else if ("&".equals(img)) { BitwiseAnd r = new BitwiseAnd(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
1068+
else if ("<->".equals(img) || "<#>".equals(img)) { GeometryDistance r = new GeometryDistance(img); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10641069
}
10651070
}
10661071
return left;
@@ -12108,9 +12113,6 @@ Expression RegularConditionRHS(Expression leftExpression, int oracleJoinRight) #
1210812113
| <OP_CONCAT> { result = new JsonOperator("||"); }
1210912114
| "-" { result = new JsonOperator("-"); }
1211012115
| "-#" { result = new JsonOperator("-#"); }
12111-
| "<->" { result = new GeometryDistance("<->"); }
12112-
| "<#>" { result = new GeometryDistance("<#>"); }
12113-
| <OP_COSINESIMILARITY> { result = new CosineSimilarity(); }
1211412116
)
1211512117

1211612118
( LOOKAHEAD({ isPriorOperatorAhead() }) <K_PRIOR> rightExpression=ComparisonItem() { oraclePrior = EqualsTo.ORACLE_PRIOR_END; }

‎src/test/java/net/sf/jsqlparser/expression/operators/relational/ComparisonOperatorTest.java‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
package net.sf.jsqlparser.expression.operators.relational;
1111

1212
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.expression.BinaryExpression;
14+
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
15+
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
1316
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1417
import net.sf.jsqlparser.test.TestUtils;
1518
import org.junit.jupiter.api.Assertions;
1619
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.ValueSource;
1722

1823

1924
class ComparisonOperatorTest {
@@ -43,4 +48,95 @@ void testCosineSimilarity() throws JSQLParserException {
4348
Assertions.assertInstanceOf(CosineSimilarity.class,
4449
CCJSqlParserUtil.parseExpression("embedding <=> '[3,1,2]'"));
4550
}
51+
52+
@Test
53+
void testVectorDistanceOperatorsInProjection() throws JSQLParserException {
54+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT a <-> b FROM t");
55+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT a <#> b FROM t");
56+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT a <=> b FROM t");
57+
Assertions.assertInstanceOf(GeometryDistance.class,
58+
CCJSqlParserUtil.parseExpression("a <-> b"));
59+
Assertions.assertInstanceOf(GeometryDistance.class,
60+
CCJSqlParserUtil.parseExpression("a <#> b"));
61+
Assertions.assertInstanceOf(CosineSimilarity.class,
62+
CCJSqlParserUtil.parseExpression("a <=> b"));
63+
}
64+
65+
@ParameterizedTest
66+
@ValueSource(strings = {"<->", "<#>", "<=>"})
67+
void testVectorDistanceOperatorsFollowedByComparison(String distance)
68+
throws JSQLParserException {
69+
String[] comparators = {"<", ">", "="};
70+
for (String comparator : comparators) {
71+
TestUtils.assertSqlCanBeParsedAndDeparsed(
72+
"SELECT * FROM t WHERE a " + distance + " b " + comparator + " c");
73+
TestUtils.assertSqlCanBeParsedAndDeparsed(
74+
"SELECT * FROM t WHERE (a " + distance + " b) " + comparator + " c");
75+
}
76+
Assertions.assertInstanceOf(MinorThan.class,
77+
CCJSqlParserUtil.parseExpression("a " + distance + " b < c"));
78+
}
79+
80+
@ParameterizedTest
81+
@ValueSource(strings = {"<->", "<#>", "<=>"})
82+
void testVectorDistanceOperatorsInProjectionFollowedByComparison(String distance)
83+
throws JSQLParserException {
84+
String[] comparators = {"<", ">", "="};
85+
for (String comparator : comparators) {
86+
TestUtils.assertSqlCanBeParsedAndDeparsed(
87+
"SELECT a " + distance + " b " + comparator + " c FROM t");
88+
TestUtils.assertSqlCanBeParsedAndDeparsed(
89+
"SELECT (a " + distance + " b) " + comparator + " c FROM t");
90+
}
91+
}
92+
93+
@ParameterizedTest
94+
@ValueSource(strings = {"<->", "<#>", "<=>"})
95+
void testVectorDistanceOperatorsShareBitwisePrecedence(String distance)
96+
throws JSQLParserException {
97+
Class<?> distanceType =
98+
"<=>".equals(distance) ? CosineSimilarity.class : GeometryDistance.class;
99+
100+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT a " + distance + " b & c FROM t");
101+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT a & b " + distance + " c FROM t");
102+
103+
BinaryExpression bitwiseRoot =
104+
(BinaryExpression) CCJSqlParserUtil.parseExpression("a " + distance + " b & c");
105+
Assertions.assertInstanceOf(BitwiseAnd.class, bitwiseRoot);
106+
Assertions.assertInstanceOf(distanceType, bitwiseRoot.getLeftExpression());
107+
108+
BinaryExpression distanceRoot =
109+
(BinaryExpression) CCJSqlParserUtil.parseExpression("a & b " + distance + " c");
110+
Assertions.assertInstanceOf(distanceType, distanceRoot);
111+
Assertions.assertInstanceOf(BitwiseAnd.class, distanceRoot.getLeftExpression());
112+
}
113+
114+
@Test
115+
void testGeometryDistanceOrderByControl() throws JSQLParserException {
116+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t ORDER BY a <-> b");
117+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t ORDER BY a <#> b");
118+
}
119+
120+
@Test
121+
void testVectorDistancePrecedenceAboveComparisonBelowArithmetic() throws JSQLParserException {
122+
BinaryExpression geometryComparison = Assertions.assertInstanceOf(MinorThan.class,
123+
CCJSqlParserUtil.parseExpression("a <-> b + c < 5"));
124+
GeometryDistance geometryDistance = Assertions.assertInstanceOf(GeometryDistance.class,
125+
geometryComparison.getLeftExpression());
126+
Assertions.assertInstanceOf(Addition.class, geometryDistance.getRightExpression());
127+
128+
BinaryExpression cosineComparison = Assertions.assertInstanceOf(MinorThan.class,
129+
CCJSqlParserUtil.parseExpression("a <=> b + c < 5"));
130+
CosineSimilarity cosineSimilarity = Assertions.assertInstanceOf(CosineSimilarity.class,
131+
cosineComparison.getLeftExpression());
132+
Assertions.assertInstanceOf(Addition.class, cosineSimilarity.getRightExpression());
133+
}
134+
135+
@Test
136+
void testComparisonChainsRemainInvalid() {
137+
Assertions.assertThrows(JSQLParserException.class,
138+
() -> CCJSqlParserUtil.parse("SELECT * FROM t WHERE a < b < c"));
139+
Assertions.assertThrows(JSQLParserException.class,
140+
() -> CCJSqlParserUtil.parse("SELECT * FROM t WHERE a <-> b < c < d"));
141+
}
46142
}

0 commit comments

Comments
 (0)