diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index f49b3ddbae..1bfb954506 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2177,6 +2177,28 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** + * True when an IS after a condition opens a postfix test + * (IS [NOT] TRUE/FALSE/UNKNOWN/NULL/JSON) rather than IS DISTINCT FROM or + * another comparison form, which has a right-hand operand of its own. + */ + private boolean isPostfixIsTestAhead() { + if (getToken(1).kind != K_IS) { + return false; + } + int offset = getToken(2).kind == K_NOT ? 3 : 2; + switch (getToken(offset).kind) { + case K_TRUE: + case K_FALSE: + case K_UNKNOWN: + case K_NULL: + case K_JSON: + return true; + default: + return false; + } + } + /** * True when the pending {@code ON} opens the ON CONFLICT clause of the enclosing * INSERT rather than another trailing join condition: after a join, the trailing @@ -11163,6 +11185,18 @@ Expression Condition(): | LOOKAHEAD(3) result=IsDistinctExpression(left) | result=SimilarToExpression(left) ) + // SQL:2016 6.39: a boolean test applies to any boolean primary, + // so postfix IS predicates chain onto a comparison or predicate + // result: "a > 0 IS TRUE", "a IS NULL IS NOT TRUE". + ( + LOOKAHEAD({ isPostfixIsTestAhead() }) + ( + LOOKAHEAD(3) result=IsBooleanExpression(result) + | LOOKAHEAD(3) result=IsUnknownExpression(result) + | LOOKAHEAD(3) result=IsNullExpression(result) + | LOOKAHEAD(3) result=IsJsonExpression(result) + ) + )* ] ) { diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index a09ab4be36..ba39bc65cb 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -2388,6 +2388,16 @@ public void testIsNotFalse() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(statement); } + @Test + public void testBooleanTestAfterPredicate() throws JSQLParserException { + // SQL:2016 6.39: a boolean test applies to any boolean primary, so an + // IS TRUE/FALSE test chains onto a comparison or predicate result + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a > 0 IS TRUE"); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a IN (1, 2) IS FALSE"); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a IS NULL IS NOT TRUE"); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a IS TRUE IS FALSE"); + } + @Test public void testIsUnknown() throws JSQLParserException { String statement = "SELECT col FROM tbl WHERE col IS UNKNOWN";