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
34 changes: 32 additions & 2 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,14 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
* {@code cond ? then : else} rather than the PostgreSQL JSON operator:
* a standalone ":" closes the then-branch at the same nesting depth before
* any expression boundary (",", ";", EOF, an unbalanced closing bracket or a
* clause keyword such as FROM/WHERE).
* clause keyword such as FROM/WHERE). A ":" in operand position (directly
* after an operator) starts a JDBC named parameter instead and cannot close
* the then-branch.
*/
protected boolean isTernaryAhead() {
try {
int depth = 0;
Token prev = null;
for (int i = 2; ; i++) {
Token t = getToken(i);
if (t == null || t.kind == EOF) {
Expand All @@ -246,7 +249,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}
depth--;
} else if (depth == 0) {
if (":".equals(image)) {
if (":".equals(image) && canEndExpression(prev)) {
return true;
}
if (",".equals(image) || ";".equals(image)) {
Expand All @@ -262,12 +265,39 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
break;
}
}
prev = t;
}
} catch (Exception e) {
return false;
}
}

/**
* True when the token can be the LAST token of an expression: an
* identifier, a literal, a JDBC parameter, or a balanced closing bracket.
* Only such a token may directly precede the ":" closing the ternary
* then-branch, since the then-branch must be a complete expression. A ":"
* in operand position instead (directly after an operator, e.g. `x = :name`
* or directly after the leading "?") starts a JDBC named parameter.
* DATA_TYPE tokens end expressions as well, because a cast's target type
* (`b :: int`) or a bare type keyword closes the then-branch.
*/
private boolean canEndExpression(Token t) {
if (t == null) {
return false;
}
switch (t.kind) {
case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case S_PARAMETER:
case S_LONG: case S_DOUBLE: case S_HEX: case S_CHAR_LITERAL:
case K_DATE_LITERAL: case K_DATETIMELITERAL: case DATA_TYPE:
case K_NULL: case K_TRUE: case K_FALSE:
return true;
default:
return (t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD)
|| ")".equals(t.image) || "]".equals(t.image);
}
}


/**
* Tokens that have dedicated branches in PrimaryExpression AFTER the Function branch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ComparisonOperator;
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
Expand Down Expand Up @@ -67,7 +68,11 @@ void testIssue2436() throws JSQLParserException {
"SELECT a ? b : ? FROM t",
"SELECT * FROM t WHERE x = ? AND y ? z : w",
// case expression inside a branch
"SELECT a ? CASE WHEN b THEN c ELSE d END : e FROM t"
"SELECT a ? CASE WHEN b THEN c ELSE d END : e FROM t",
// cast target type ends the then-branch (a bare ":" after a type
// keyword is the ternary separator, not a named parameter)
"SELECT a ? b :: int : c FROM t",
"SELECT x > 0 ? CAST(b AS int) : c FROM t"
})
void testTernaryInVariousContexts(String sqlStr) throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
Expand Down Expand Up @@ -129,4 +134,40 @@ void testRightAssociativeNesting() throws JSQLParserException {
void testUnrelatedSyntaxUnaffected(String sqlStr) throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}

@Test
void testJsonbOperatorWithNamedParameter() throws JSQLParserException {
// regression guard (ternary support, #2466): a ":" in operand position
// starts a JDBC named parameter and must not close a ternary then-branch
Select select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE j ? :key");
Expression where = ((PlainSelect) select).getWhere();
Assertions.assertTrue(where instanceof JsonOperator);
Assertions.assertTrue(
((JsonOperator) where).getRightExpression() instanceof JdbcNamedParameter);
}

@Test
void testTernaryWithCastThenBranch() throws JSQLParserException {
// regression guard: a DATA_TYPE token can end the then-branch, so the
// ":" after the cast target type must close the ternary
Select select = (Select) CCJSqlParserUtil.parse("SELECT a ? b :: int : c FROM t");
TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0)
.getExpression();
Assertions.assertTrue(ternary.getThenExpression() instanceof CastExpression);
}

@ParameterizedTest
@ValueSource(strings = {
// PostgreSQL jsonb operator combined with JDBC named parameters
"SELECT * FROM t WHERE j ? :key",
"SELECT * FROM t WHERE j ? 'k' AND x = :p",
"SELECT * FROM t WHERE j ? 'k' OR x = :p",
"SELECT * FROM t WHERE j ? :key AND x = 1",
// named parameters keep working inside ternary branches, too
"SELECT a ? :t : :e FROM t",
"SELECT * FROM t WHERE c = :c AND a ? :t : :e"
})
void testTernaryAndJsonbWithJdbcNamedParameters(String sqlStr) throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}
}
Loading