diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 902ece9a7..f49b3ddba 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -681,7 +681,7 @@ public class CCJSqlParser extends AbstractJSqlParser { /** * Pratt arithmetic operator precedence loop. - * Handles: ^, ** (prec=7), *, /, %, DIV (prec=6) and +, -, ||, |, &, <<, >> (prec=5) + * Handles: ^, ** (prec=7), *, /, %, DIV (prec=6), +, -, || (prec=5) and |, &, #, <<, >> (prec=4) * Called only in real-parse mode (not syntactic-LOOKAHEAD mode) so action * blocks execute correctly. Safe now that no syntactic production LOOKAHEADs * scan through arithmetic expressions. @@ -762,16 +762,19 @@ public class CCJSqlParser extends AbstractJSqlParser { else if (op == OP_SLASH || op == K_DIV) prec = 6; // ^ binds tighter than * / %, both as PostgreSQL's power and as MySQL's XOR else if (op == OP_CARET || op == OP_POWER) prec = 7; - else if (op == S_HASH_OPERATOR) prec = 5; - else if (op == OP_CONCAT || op == OP_PIPE - || op == OP_LSHIFT || op == OP_RSHIFT) prec = 5; + else if (op == OP_CONCAT + || (op == OP_PIPE && getToken(2).kind == OP_PIPE)) prec = 5; + // the bitwise operators bind looser than + and -, as in PostgreSQL, MySQL and SQLite + else if (op == S_HASH_OPERATOR || op == OP_PIPE + || op == OP_LSHIFT || op == OP_RSHIFT) prec = 4; else { // Handle unnamed tokens by image String img = t.image; // DuckDB's LIMIT 10%: the % ends the row count instead of starting a modulo if (t == limitPercentToken) break; if ("*".equals(img) || "%".equals(img)) prec = 6; - else if ("+".equals(img) || "-".equals(img) || "&".equals(img)) prec = 5; + else if ("+".equals(img) || "-".equals(img)) prec = 5; + else if ("&".equals(img)) prec = 4; else break; // not an arithmetic operator } diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java index 12e15e877..980ffbe1d 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java @@ -12,19 +12,25 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; +import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; +import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseOr; +import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseRightShift; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor; import net.sf.jsqlparser.expression.operators.arithmetic.Concat; import net.sf.jsqlparser.expression.operators.arithmetic.Division; import net.sf.jsqlparser.expression.operators.arithmetic.Modulo; import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication; +import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.Between; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.Intersects; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; import net.sf.jsqlparser.expression.operators.relational.LikeExpression; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.test.TestUtils; @@ -40,7 +46,15 @@ public class ExpressionPrecedenceTest { @Test public void testGetSign() throws JSQLParserException { + // || binds like +, so tighter than & Expression expr = CCJSqlParserUtil.parseExpression("1&2||3"); + Assertions.assertInstanceOf(BitwiseAnd.class, expr); + Assertions.assertInstanceOf(LongValue.class, ((BitwiseAnd) expr).getLeftExpression()); + Assertions.assertInstanceOf(Concat.class, ((BitwiseAnd) expr).getRightExpression()); + + // PostgreSQL puts || and & on one level + expr = CCJSqlParserUtil.parseExpression("1&2||3", false, + parser -> parser.withDialect(Dialect.POSTGRESQL)); Assertions.assertInstanceOf(Concat.class, expr); Assertions.assertInstanceOf(BitwiseAnd.class, ((Concat) expr).getLeftExpression()); Assertions.assertInstanceOf(LongValue.class, ((Concat) expr).getRightExpression()); @@ -252,4 +266,47 @@ public void testCaretIsLeftAssociative() throws JSQLParserException { Assertions.assertInstanceOf(BitwiseXor.class, outer.getLeftExpression()); Assertions.assertInstanceOf(LongValue.class, outer.getRightExpression()); } + + @Test + public void testBitwiseOperatorsBindLooserThanAddition() throws JSQLParserException { + // PostgreSQL, MySQL and SQLite all evaluate 1 & 7 - 2 as 1 & (7 - 2) + BitwiseAnd and = Assertions.assertInstanceOf(BitwiseAnd.class, + CCJSqlParserUtil.parseExpression("1 & 7 - 2")); + Assertions.assertInstanceOf(LongValue.class, and.getLeftExpression()); + Assertions.assertInstanceOf(Subtraction.class, and.getRightExpression()); + + BitwiseOr or = Assertions.assertInstanceOf(BitwiseOr.class, + CCJSqlParserUtil.parseExpression("a | b + c")); + Assertions.assertInstanceOf(Addition.class, or.getRightExpression()); + + BitwiseLeftShift leftShift = Assertions.assertInstanceOf(BitwiseLeftShift.class, + CCJSqlParserUtil.parseExpression("1 << n - 1")); + Assertions.assertInstanceOf(Subtraction.class, leftShift.getRightExpression()); + + BitwiseRightShift rightShift = Assertions.assertInstanceOf(BitwiseRightShift.class, + CCJSqlParserUtil.parseExpression("a + b >> 2")); + Assertions.assertInstanceOf(Addition.class, rightShift.getLeftExpression()); + + Intersects xor = Assertions.assertInstanceOf(Intersects.class, + CCJSqlParserUtil.parseExpression("a # b + c", false, + parser -> parser.withDialect(Dialect.POSTGRESQL))); + Assertions.assertInstanceOf(Addition.class, xor.getRightExpression()); + + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT flags & 1 << bit - 1 FROM t"); + } + + @Test + public void testBitwiseOperatorsShareOneLeftAssociativeLevel() throws JSQLParserException { + // as in PostgreSQL and SQLite: 4 | 1 & 0 is (4 | 1) & 0 + BitwiseAnd and = Assertions.assertInstanceOf(BitwiseAnd.class, + CCJSqlParserUtil.parseExpression("4 | 1 & 0")); + Assertions.assertInstanceOf(BitwiseOr.class, and.getLeftExpression()); + } + + @Test + public void testSpaceSeparatedConcatStillBindsLikeAddition() throws JSQLParserException { + BitwiseAnd and = Assertions.assertInstanceOf(BitwiseAnd.class, + CCJSqlParserUtil.parseExpression("a & b | | c")); + Assertions.assertInstanceOf(Concat.class, and.getRightExpression()); + } }