Skip to content

Commit ea892ea

Browse files
authored
fix: bind ^ tighter than *, / and % (#2831)
Since the Pratt rewrite, ^ shares the precedence of the multiplicative operators, so 2 * 3 ^ 2 parses as (2 * 3) ^ 2. PostgreSQL (power) and MySQL (XOR) both evaluate it as 2 * (3 ^ 2), as did JSqlParser 5.3, where BitwiseXor was an operand of MultiplicativeExpression. Move ^ to the level of **, above * / % DIV and still left-associative. Co-authored-by: ybai08 <271969568+ybai08@users.noreply.github.com>
1 parent e9ec7ba commit ea892ea

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
681681

682682
/**
683683
* Pratt arithmetic operator precedence loop.
684-
* Handles: *, /, %, ^, DIV (prec=6) and +, -, ||, |, &, <<, >> (prec=5)
684+
* Handles: ^, ** (prec=7), *, /, %, DIV (prec=6) and +, -, ||, |, &, <<, >> (prec=5)
685685
* Called only in real-parse mode (not syntactic-LOOKAHEAD mode) so action
686686
* blocks execute correctly. Safe now that no syntactic production LOOKAHEADs
687687
* scan through arithmetic expressions.
@@ -759,8 +759,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
759759
if (jsonAccess || op == OP_JSON_PATH_DELETE || op == OP_JSON_PATH_EXISTS
760760
|| (op == OP_CONCAT && Dialect.POSTGRESQL.name()
761761
.equals(getAsString(Feature.dialect)))) prec = 4;
762-
else if (op == OP_SLASH || op == OP_CARET || op == K_DIV) prec = 6;
763-
else if (op == OP_POWER) prec = 7;
762+
else if (op == OP_SLASH || op == K_DIV) prec = 6;
763+
// ^ binds tighter than * / %, both as PostgreSQL's power and as MySQL's XOR
764+
else if (op == OP_CARET || op == OP_POWER) prec = 7;
764765
else if (op == S_HASH_OPERATOR) prec = 5;
765766
else if (op == OP_CONCAT || op == OP_PIPE
766767
|| op == OP_LSHIFT || op == OP_RSHIFT) prec = 5;

‎src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
import net.sf.jsqlparser.JSQLParserException;
1313
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
1414
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
15+
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor;
1516
import net.sf.jsqlparser.expression.operators.arithmetic.Concat;
17+
import net.sf.jsqlparser.expression.operators.arithmetic.Division;
18+
import net.sf.jsqlparser.expression.operators.arithmetic.Modulo;
19+
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
1620
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
1721
import net.sf.jsqlparser.expression.operators.relational.Between;
1822
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
@@ -217,4 +221,35 @@ public void testValuePositionNotStaysOnOperand() throws JSQLParserException {
217221
Addition add = Assertions.assertInstanceOf(Addition.class, addition);
218222
Assertions.assertInstanceOf(NotExpression.class, add.getRightExpression());
219223
}
224+
225+
@Test
226+
public void testCaretBindsTighterThanMultiplication() throws JSQLParserException {
227+
// PostgreSQL's power and MySQL's XOR both evaluate 2 * 3 ^ 2 as 2 * (3 ^ 2)
228+
Multiplication multiplication = Assertions.assertInstanceOf(Multiplication.class,
229+
CCJSqlParserUtil.parseExpression("2 * 3 ^ 2"));
230+
Assertions.assertInstanceOf(LongValue.class, multiplication.getLeftExpression());
231+
Assertions.assertInstanceOf(BitwiseXor.class, multiplication.getRightExpression());
232+
233+
Division division = Assertions.assertInstanceOf(Division.class,
234+
CCJSqlParserUtil.parseExpression("10 / 2 ^ 2"));
235+
Assertions.assertInstanceOf(BitwiseXor.class, division.getRightExpression());
236+
237+
Modulo modulo = Assertions.assertInstanceOf(Modulo.class,
238+
CCJSqlParserUtil.parseExpression("18 % 4 ^ 2"));
239+
Assertions.assertInstanceOf(BitwiseXor.class, modulo.getRightExpression());
240+
241+
multiplication = Assertions.assertInstanceOf(Multiplication.class,
242+
CCJSqlParserUtil.parseExpression("2 ^ 3 * 2"));
243+
Assertions.assertInstanceOf(BitwiseXor.class, multiplication.getLeftExpression());
244+
245+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT pi() * r ^ 2 FROM circles");
246+
}
247+
248+
@Test
249+
public void testCaretIsLeftAssociative() throws JSQLParserException {
250+
BitwiseXor outer = Assertions.assertInstanceOf(BitwiseXor.class,
251+
CCJSqlParserUtil.parseExpression("2 ^ 3 ^ 2"));
252+
Assertions.assertInstanceOf(BitwiseXor.class, outer.getLeftExpression());
253+
Assertions.assertInstanceOf(LongValue.class, outer.getRightExpression());
254+
}
220255
}

0 commit comments

Comments
 (0)