Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
*/
/**
* Pratt boolean operator precedence loop.
* Handles OR (prec=2), XOR (prec=3), AND/&& (prec=4).
* Handles OR (prec=2), XOR (prec=3 under MySQL, else 2), AND/&& (prec=4).
* Safe to use now that no syntactic production LOOKAHEADs scan through expressions.
*/
protected Expression prattExpressionRest(Expression left, int minPrec) throws ParseException {
Expand All @@ -698,7 +698,11 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
boolean ternary = "?".equals(t.image) && isTernaryAhead();
int prec;
if (op == K_AND || op == OP_DOUBLEAND) prec = 4;
else if (op == K_XOR || op == K_OR) prec = 2;
// MySQL puts XOR between AND and OR; other dialects keep one
// left-associative level for OR and XOR.
else if (op == K_XOR)
prec = Dialect.MYSQL.name().equals(getAsString(Feature.dialect)) ? 3 : 2;
else if (op == K_OR) prec = 2;
else if (ternary) prec = 2;
else break;
if (prec < minPrec) break;
Expand Down Expand Up @@ -765,16 +769,23 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
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 if (op == S_HASH_OPERATOR || op == OP_LSHIFT
|| op == OP_RSHIFT) prec = 4;
else if (op == OP_PIPE) {
// MySQL tiers bitwise: <<, >> above & above |. Other dialects
// (PostgreSQL, SQLite, ...) keep one left-associative level.
prec = Dialect.MYSQL.name().equals(getAsString(Feature.dialect)) ? 2 : 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)) prec = 5;
else if ("&".equals(img)) prec = 4;
else if ("&".equals(img)) {
prec = Dialect.MYSQL.name().equals(getAsString(Feature.dialect)) ? 3 : 4;
}
else break; // not an arithmetic operator
}

Expand Down Expand Up @@ -11869,7 +11880,12 @@ Expression SimpleExpression():
[ LOOKAHEAD( 5, { getToken(1).kind == S_AT_IDENTIFIER } ) user = UserVariable() ( operation = "=" | operation = ":=" ) ]
retval=PrimaryExpression()
// PostgreSQL path operators bind below arithmetic and above comparisons.
{ retval = prattArithRest(retval, 4); }
// MySQL also gives | and & their own tiers below <<, >>, so the loop's
// entry floor drops to 2 there instead of the shared bitwise level 4.
{
int arithFloor = Dialect.MYSQL.name().equals(getAsString(Feature.dialect)) ? 2 : 4;
retval = prattArithRest(retval, arithFloor);
}
{
if (user != null) {
VariableAssignment assignment = new VariableAssignment();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
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.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.conditional.XorExpression;
import net.sf.jsqlparser.expression.operators.relational.Between;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
Expand Down Expand Up @@ -309,4 +311,38 @@ public void testSpaceSeparatedConcatStillBindsLikeAddition() throws JSQLParserEx
CCJSqlParserUtil.parseExpression("a & b | | c"));
Assertions.assertInstanceOf(Concat.class, and.getRightExpression());
}

@Test
public void testMySqlBitwiseOperatorsAreTiered() throws JSQLParserException {
// MySQL tiers the bitwise operators: << and >> above &, & above |
BitwiseOr or = Assertions.assertInstanceOf(BitwiseOr.class,
CCJSqlParserUtil.parseExpression("1 | 2 & 4", false,
parser -> parser.withDialect(Dialect.MYSQL)));
Assertions.assertInstanceOf(BitwiseAnd.class, or.getRightExpression());

BitwiseAnd and = Assertions.assertInstanceOf(BitwiseAnd.class,
CCJSqlParserUtil.parseExpression("3 & 1 << 2", false,
parser -> parser.withDialect(Dialect.MYSQL)));
Assertions.assertInstanceOf(BitwiseLeftShift.class, and.getRightExpression());

// the whole chain still sits below + and -, as in the default dialect
BitwiseAnd shifted = Assertions.assertInstanceOf(BitwiseAnd.class,
CCJSqlParserUtil.parseExpression("1 & 7 - 2", false,
parser -> parser.withDialect(Dialect.MYSQL)));
Assertions.assertInstanceOf(Subtraction.class, shifted.getRightExpression());
}

@Test
public void testXorBindsBetweenAndAndOr() throws JSQLParserException {
// MySQL: XOR sits between AND and OR
OrExpression or = Assertions.assertInstanceOf(OrExpression.class,
CCJSqlParserUtil.parseExpression("1 OR 1 XOR 1", false,
parser -> parser.withDialect(Dialect.MYSQL)));
Assertions.assertInstanceOf(XorExpression.class, or.getRightExpression());

XorExpression xor = Assertions.assertInstanceOf(XorExpression.class,
CCJSqlParserUtil.parseExpression("1 XOR 1 AND 1", false,
parser -> parser.withDialect(Dialect.MYSQL)));
Assertions.assertInstanceOf(AndExpression.class, xor.getRightExpression());
}
}
Loading