From 21ac7c4e4fe3bbcbf572f54af50ae2f0582732db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=98=E5=85=B8?= Date: Sun, 16 Aug 2026 14:45:00 +0800 Subject: [PATCH] fix: treat a positional parameter '?' as a valid end of the ternary then-branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare '?' (anonymous token, no kind constant) was missing from canEndExpression(), so a ternary whose then-branch ends with a positional parameter was silently misread as a jsonb operator: SELECT a ? ? : c FROM t parsed as JsonOperator(a, JsonExpression(?:c)) instead of TernaryExpression(a, JdbcParameter, c). Recognize it by image, like the closing brackets, and pin the AST shape with a regression test. Follow-up to #2476. Signed-off-by: 付典 --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 7 +++++-- .../expression/TernaryExpressionTest.java | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 973004a6e..d86e227d7 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -280,7 +280,9 @@ public class CCJSqlParser extends AbstractJSqlParser { * 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. + * (`b :: int`) or a bare type keyword closes the then-branch. The bare "?" + * positional parameter has no token-kind constant (anonymous token), so it + * is recognized by its image, like the closing brackets. */ private boolean canEndExpression(Token t) { if (t == null) { @@ -294,7 +296,8 @@ public class CCJSqlParser extends AbstractJSqlParser { return true; default: return (t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD) - || ")".equals(t.image) || "]".equals(t.image); + || ")".equals(t.image) || "]".equals(t.image) + || "?".equals(t.image); } } diff --git a/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java index 03f535db3..38f20111d 100644 --- a/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.expression; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; @@ -65,6 +66,8 @@ void testIssue2436() throws JSQLParserException { "SELECT a IS NULL ? 'x' : y FROM t", // jdbc parameters as branches "SELECT a ? ? : c FROM t", + "SELECT a ? b + ? : c FROM t", + "SELECT a ? ? : ? FROM t", "SELECT a ? b : ? FROM t", "SELECT * FROM t WHERE x = ? AND y ? z : w", // case expression inside a branch @@ -156,6 +159,23 @@ void testTernaryWithCastThenBranch() throws JSQLParserException { Assertions.assertTrue(ternary.getThenExpression() instanceof CastExpression); } + @Test + void testTernaryWithPositionalParameterThenBranch() throws JSQLParserException { + // regression guard: a bare "?" positional parameter is a complete + // expression, so the ":" after it closes the ternary instead of being + // skipped in favor of the jsonb reading + Select select = (Select) CCJSqlParserUtil.parse("SELECT a ? ? : c FROM t"); + TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0) + .getExpression(); + Assertions.assertTrue(ternary.getThenExpression() instanceof JdbcParameter); + Assertions.assertTrue(ternary.getElseExpression() instanceof Column); + + // a positional parameter as the LAST token of the then-branch, too + select = (Select) CCJSqlParserUtil.parse("SELECT a ? b + ? : c FROM t"); + ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0).getExpression(); + Assertions.assertTrue(ternary.getThenExpression() instanceof Addition); + } + @ParameterizedTest @ValueSource(strings = { // PostgreSQL jsonb operator combined with JDBC named parameters