Skip to content

Commit ce830b5

Browse files
authored
fix(parser): parse vector distances before relational comparisons (#2854)
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 97c61b1 commit ce830b5

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
@@ -1030,6 +1030,8 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
10301030
// the bitwise operators bind looser than + and -, as in PostgreSQL, MySQL and SQLite
10311031
else if (op == S_HASH_OPERATOR || op == OP_LSHIFT
10321032
|| op == OP_RSHIFT) prec = 4;
1033+
// PostgreSQL distance operators share the left-associative tier below + and -.
1034+
else if (op == OP_COSINESIMILARITY) prec = 4;
10331035
else if (op == OP_PIPE) {
10341036
// MySQL tiers bitwise: <<, >> above & above |. Other dialects
10351037
// (PostgreSQL, SQLite, ...) keep one left-associative level.
@@ -1045,6 +1047,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
10451047
else if ("&".equals(img)) {
10461048
prec = Dialect.MYSQL.name().equals(getAsString(Feature.dialect)) ? 3 : 4;
10471049
}
1050+
else if ("<->".equals(img) || "<#>".equals(img)) prec = 4;
10481051
else break; // not an arithmetic operator
10491052
}
10501053

@@ -1080,13 +1083,15 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
10801083
else if (op == OP_PIPE) { BitwiseOr r = new BitwiseOr(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10811084
else if (op == OP_LSHIFT) { BitwiseLeftShift r = new BitwiseLeftShift(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10821085
else if (op == OP_RSHIFT) { BitwiseRightShift r = new BitwiseRightShift(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
1086+
else if (op == OP_COSINESIMILARITY) { CosineSimilarity r = new CosineSimilarity(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10831087
else {
10841088
String img = t.image;
10851089
if ("*".equals(img)) { Multiplication r = new Multiplication(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10861090
else if ("+".equals(img)) { Addition r = new Addition(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10871091
else if ("-".equals(img)) { Subtraction r = new Subtraction(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10881092
else if ("%".equals(img)) { Modulo r = new Modulo(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10891093
else if ("&".equals(img)) { BitwiseAnd r = new BitwiseAnd(); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
1094+
else if ("<->".equals(img) || "<#>".equals(img)) { GeometryDistance r = new GeometryDistance(img); r.setLeftExpression(left); r.setRightExpression(right); left = r; }
10901095
}
10911096
}
10921097
return left;
@@ -12215,9 +12220,6 @@ Expression RegularConditionRHS(Expression leftExpression, int oracleJoinRight) #
1221512220
| <OP_CONCAT> { result = new JsonOperator("||"); }
1221612221
| "-" { result = new JsonOperator("-"); }
1221712222
| "-#" { result = new JsonOperator("-#"); }
12218-
| "<->" { result = new GeometryDistance("<->"); }
12219-
| "<#>" { result = new GeometryDistance("<#>"); }
12220-
| <OP_COSINESIMILARITY> { result = new CosineSimilarity(); }
1222112223
)
1222212224

1222312225
( 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)