Skip to content

Commit 5080d19

Browse files
authored
fix: do not treat a named parameter ':' as the ternary else separator (#2476)
Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 7b64825 commit 5080d19

2 files changed

Lines changed: 74 additions & 3 deletions

File tree

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,14 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
227227
* {@code cond ? then : else} rather than the PostgreSQL JSON operator:
228228
* a standalone ":" closes the then-branch at the same nesting depth before
229229
* any expression boundary (",", ";", EOF, an unbalanced closing bracket or a
230-
* clause keyword such as FROM/WHERE).
230+
* clause keyword such as FROM/WHERE). A ":" in operand position (directly
231+
* after an operator) starts a JDBC named parameter instead and cannot close
232+
* the then-branch.
231233
*/
232234
protected boolean isTernaryAhead() {
233235
try {
234236
int depth = 0;
237+
Token prev = null;
235238
for (int i = 2; ; i++) {
236239
Token t = getToken(i);
237240
if (t == null || t.kind == EOF) {
@@ -246,7 +249,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
246249
}
247250
depth--;
248251
} else if (depth == 0) {
249-
if (":".equals(image)) {
252+
if (":".equals(image) && canEndExpression(prev)) {
250253
return true;
251254
}
252255
if (",".equals(image) || ";".equals(image)) {
@@ -262,12 +265,39 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
262265
break;
263266
}
264267
}
268+
prev = t;
265269
}
266270
} catch (Exception e) {
267271
return false;
268272
}
269273
}
270274

275+
/**
276+
* True when the token can be the LAST token of an expression: an
277+
* identifier, a literal, a JDBC parameter, or a balanced closing bracket.
278+
* Only such a token may directly precede the ":" closing the ternary
279+
* then-branch, since the then-branch must be a complete expression. A ":"
280+
* in operand position instead (directly after an operator, e.g. `x = :name`
281+
* or directly after the leading "?") starts a JDBC named parameter.
282+
* DATA_TYPE tokens end expressions as well, because a cast's target type
283+
* (`b :: int`) or a bare type keyword closes the then-branch.
284+
*/
285+
private boolean canEndExpression(Token t) {
286+
if (t == null) {
287+
return false;
288+
}
289+
switch (t.kind) {
290+
case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case S_PARAMETER:
291+
case S_LONG: case S_DOUBLE: case S_HEX: case S_CHAR_LITERAL:
292+
case K_DATE_LITERAL: case K_DATETIMELITERAL: case DATA_TYPE:
293+
case K_NULL: case K_TRUE: case K_FALSE:
294+
return true;
295+
default:
296+
return (t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD)
297+
|| ")".equals(t.image) || "]".equals(t.image);
298+
}
299+
}
300+
271301

272302
/**
273303
* Tokens that have dedicated branches in PrimaryExpression AFTER the Function branch.

src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
1515
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
1616
import net.sf.jsqlparser.expression.operators.relational.ComparisonOperator;
17+
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
1718
import net.sf.jsqlparser.schema.Column;
1819
import net.sf.jsqlparser.statement.select.PlainSelect;
1920
import net.sf.jsqlparser.statement.select.Select;
@@ -67,7 +68,11 @@ void testIssue2436() throws JSQLParserException {
6768
"SELECT a ? b : ? FROM t",
6869
"SELECT * FROM t WHERE x = ? AND y ? z : w",
6970
// case expression inside a branch
70-
"SELECT a ? CASE WHEN b THEN c ELSE d END : e FROM t"
71+
"SELECT a ? CASE WHEN b THEN c ELSE d END : e FROM t",
72+
// cast target type ends the then-branch (a bare ":" after a type
73+
// keyword is the ternary separator, not a named parameter)
74+
"SELECT a ? b :: int : c FROM t",
75+
"SELECT x > 0 ? CAST(b AS int) : c FROM t"
7176
})
7277
void testTernaryInVariousContexts(String sqlStr) throws JSQLParserException {
7378
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
@@ -129,4 +134,40 @@ void testRightAssociativeNesting() throws JSQLParserException {
129134
void testUnrelatedSyntaxUnaffected(String sqlStr) throws JSQLParserException {
130135
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
131136
}
137+
138+
@Test
139+
void testJsonbOperatorWithNamedParameter() throws JSQLParserException {
140+
// regression guard (ternary support, #2466): a ":" in operand position
141+
// starts a JDBC named parameter and must not close a ternary then-branch
142+
Select select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE j ? :key");
143+
Expression where = ((PlainSelect) select).getWhere();
144+
Assertions.assertTrue(where instanceof JsonOperator);
145+
Assertions.assertTrue(
146+
((JsonOperator) where).getRightExpression() instanceof JdbcNamedParameter);
147+
}
148+
149+
@Test
150+
void testTernaryWithCastThenBranch() throws JSQLParserException {
151+
// regression guard: a DATA_TYPE token can end the then-branch, so the
152+
// ":" after the cast target type must close the ternary
153+
Select select = (Select) CCJSqlParserUtil.parse("SELECT a ? b :: int : c FROM t");
154+
TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0)
155+
.getExpression();
156+
Assertions.assertTrue(ternary.getThenExpression() instanceof CastExpression);
157+
}
158+
159+
@ParameterizedTest
160+
@ValueSource(strings = {
161+
// PostgreSQL jsonb operator combined with JDBC named parameters
162+
"SELECT * FROM t WHERE j ? :key",
163+
"SELECT * FROM t WHERE j ? 'k' AND x = :p",
164+
"SELECT * FROM t WHERE j ? 'k' OR x = :p",
165+
"SELECT * FROM t WHERE j ? :key AND x = 1",
166+
// named parameters keep working inside ternary branches, too
167+
"SELECT a ? :t : :e FROM t",
168+
"SELECT * FROM t WHERE c = :c AND a ? :t : :e"
169+
})
170+
void testTernaryAndJsonbWithJdbcNamedParameters(String sqlStr) throws JSQLParserException {
171+
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
172+
}
132173
}

0 commit comments

Comments
 (0)