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
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
}

default <S> T visitLimit(Limit limit, S context) {
if (limit != null && !limit.isLimitNull() && !limit.isLimitAll()) {

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] isLimitAll() in Limit has been deprecated

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] isLimitNull() in Limit has been deprecated

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] isLimitAll() in Limit has been deprecated

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] isLimitNull() in Limit has been deprecated
if (limit.getOffset() != null) {
limit.getOffset().accept(this, context);
}
Expand Down Expand Up @@ -737,6 +737,12 @@
this.visit(rangeExpression, null);
}

<S> T visit(TernaryExpression ternaryExpression, S context);

default void visit(TernaryExpression ternaryExpression) {
this.visit(ternaryExpression, null);
}

<S> T visit(TSQLLeftJoin tsqlLeftJoin, S context);

default void visit(TSQLLeftJoin tsqlLeftJoin) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -765,12 +765,12 @@

@Override
public <S> T visit(ConnectByRootOperator connectByRootOperator, S context) {
return connectByRootOperator.getColumn().accept(this, context);

Check warning on line 768 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] getColumn() in ConnectByRootOperator has been deprecated

Check warning on line 768 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] getColumn() in ConnectByRootOperator has been deprecated
}

@Override
public <S> T visit(ConnectByPriorOperator connectByPriorOperator, S context) {
return connectByPriorOperator.getColumn().accept(this, context);

Check warning on line 773 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] getColumn() in ConnectByPriorOperator has been deprecated

Check warning on line 773 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] getColumn() in ConnectByPriorOperator has been deprecated
}

@Override
Expand Down Expand Up @@ -798,7 +798,7 @@
if (selectVisitor != null) {
if (select.getWithItemsList() != null) {
for (WithItem<?> item : select.getWithItemsList()) {
item.accept(selectVisitor, context);

Check warning on line 801 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] <T,S>accept(SelectVisitor<T>,S) in WithItem has been deprecated

Check warning on line 801 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] <T,S>accept(SelectVisitor<T>,S) in WithItem has been deprecated
}
}
select.accept(selectVisitor, context);
Expand All @@ -822,6 +822,12 @@
rangeExpression.getEndExpression());
}

@Override
public <S> T visit(TernaryExpression ternaryExpression, S context) {
return visitExpressions(ternaryExpression, context, ternaryExpression.getCondition(),
ternaryExpression.getThenExpression(), ternaryExpression.getElseExpression());
}

@Override
public <S> T visit(TSQLLeftJoin tsqlLeftJoin, S context) {
return visitBinaryExpression(tsqlLeftJoin, context);
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2025 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.expression;

import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
* The C-style ternary conditional operator {@code condition ? thenExpression : elseExpression},
* supported for instance by ClickHouse as an alias for {@code if(condition, then, else)}.
*/
public class TernaryExpression extends ASTNodeAccessImpl implements Expression {
private Expression condition;
private Expression thenExpression;
private Expression elseExpression;

public TernaryExpression() {}

public TernaryExpression(Expression condition, Expression thenExpression,
Expression elseExpression) {
this.condition = condition;
this.thenExpression = thenExpression;
this.elseExpression = elseExpression;
}

public Expression getCondition() {
return condition;
}

public TernaryExpression setCondition(Expression condition) {
this.condition = condition;
return this;
}

public Expression getThenExpression() {
return thenExpression;
}

public TernaryExpression setThenExpression(Expression thenExpression) {
this.thenExpression = thenExpression;
return this;
}

public Expression getElseExpression() {
return elseExpression;
}

public TernaryExpression setElseExpression(Expression elseExpression) {
this.elseExpression = elseExpression;
return this;
}

@Override
public String toString() {
return condition + " ? " + thenExpression + " : " + elseExpression;
}

@Override
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
return expressionVisitor.visit(this, context);
}
}
8 changes: 8 additions & 0 deletions src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ public <S> Void visit(RangeExpression rangeExpression, S context) {
return null;
}

@Override
public <S> Void visit(TernaryExpression ternaryExpression, S context) {
ternaryExpression.getCondition().accept(this, context);
ternaryExpression.getThenExpression().accept(this, context);
ternaryExpression.getElseExpression().accept(this, context);
return null;
}

/**
* Main entry for this Tool class. A list of found tables is returned.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import net.sf.jsqlparser.expression.OverlapsCondition;
import net.sf.jsqlparser.expression.PostgresNamedFunctionParameter;
import net.sf.jsqlparser.expression.RangeExpression;
import net.sf.jsqlparser.expression.TernaryExpression;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.RowGetExpression;
import net.sf.jsqlparser.expression.SignedExpression;
Expand Down Expand Up @@ -818,6 +819,16 @@ public <S> StringBuilder visit(RangeExpression rangeExpression, S context) {
return builder;
}

@Override
public <S> StringBuilder visit(TernaryExpression ternaryExpression, S context) {
ternaryExpression.getCondition().accept(this, context);
builder.append(" ? ");
ternaryExpression.getThenExpression().accept(this, context);
builder.append(" : ");
ternaryExpression.getElseExpression().accept(this, context);
return builder;
}

@Override
public <S> StringBuilder visit(Column tableColumn, S context) {
final Table table = tableColumn.getTable();
Expand Down Expand Up @@ -1045,6 +1056,10 @@ public void visit(RangeExpression rangeExpression) {
visit(rangeExpression, null);
}

public void visit(TernaryExpression ternaryExpression) {
visit(ternaryExpression, null);
}

public void visit(Column tableColumn) {
visit(tableColumn, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import net.sf.jsqlparser.expression.OverlapsCondition;
import net.sf.jsqlparser.expression.PostgresNamedFunctionParameter;
import net.sf.jsqlparser.expression.RangeExpression;
import net.sf.jsqlparser.expression.TernaryExpression;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.RowGetExpression;
import net.sf.jsqlparser.expression.SignedExpression;
Expand Down Expand Up @@ -1153,6 +1154,14 @@ public <S> Void visit(RangeExpression rangeExpression, S context) {
return null;
}

@Override
public <S> Void visit(TernaryExpression ternaryExpression, S context) {
ternaryExpression.getCondition().accept(this, context);
ternaryExpression.getThenExpression().accept(this, context);
ternaryExpression.getElseExpression().accept(this, context);
return null;
}

@Override
public <S> Void visit(TSQLLeftJoin tsqlLeftJoin, S context) {
tsqlLeftJoin.getLeftExpression().accept(this, context);
Expand Down Expand Up @@ -1310,6 +1319,10 @@ public void visit(RangeExpression rangeExpression) {
visit(rangeExpression, null);
}

public void visit(TernaryExpression ternaryExpression) {
visit(ternaryExpression, null);
}

public void visit(TSQLLeftJoin tsqlLeftJoin) {
visit(tsqlLeftJoin, null);
}
Expand Down
87 changes: 84 additions & 3 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
return this;
}

// depth of open ClickHouse-style ternary then-branches: a ":" directly after the
// then-branch closes the ternary and must not be taken as the JSON path operator
private int ternaryThenBranchDepth = 0;

private void linkAST(ASTNodeAccess access, Node node) {
access.setASTNode(node);
node.jjtSetValue(access);
Expand Down Expand Up @@ -182,6 +186,11 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
// comparison operator follows after "(" "+" ")"
return isComparisonOperator(getToken(4));
}
if ("?".equals(token.image) && isTernaryAhead()) {
// ClickHouse ternary `cond ? then : else` — the `?` must not be taken
// as the PostgreSQL JSON operator here, it is consumed by prattExpressionRest
return false;
}
return isComparisonOperator(token);
} catch (Exception e) {
return false;
Expand Down Expand Up @@ -212,6 +221,52 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}
}

/**
* True when the pending "?" starts a ClickHouse-style ternary conditional
* {@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).
*/
protected boolean isTernaryAhead() {
try {
int depth = 0;
for (int i = 2; ; i++) {
Token t = getToken(i);
if (t == null || t.kind == EOF) {
return false;
}
String image = t.image;
if ("(".equals(image) || "[".equals(image)) {
depth++;
} else if (")".equals(image) || "]".equals(image)) {
if (depth == 0) {
return false;
}
depth--;
} else if (depth == 0) {
if (":".equals(image)) {
return true;
}
if (",".equals(image) || ";".equals(image)) {
return false;
}
switch (t.kind) {
case K_SELECT: case K_FROM: case K_WHERE: case K_GROUP:
case K_HAVING: case K_ORDER: case K_LIMIT: case K_UNION:
case K_INTERSECT: case K_EXCEPT: case K_MINUS: case K_INTO:
case K_VALUES: case K_FETCH: case K_OFFSET:
return false;
default:
break;
}
}
}
} catch (Exception e) {
return false;
}
}


/**
* Tokens that have dedicated branches in PrimaryExpression AFTER the Function branch.
Expand Down Expand Up @@ -328,12 +383,35 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
*/
protected Expression prattExpressionRest(Expression left, int minPrec) throws ParseException {
while (!interrupted) {
int op = getToken(1).kind;
Token t = getToken(1);
int op = t.kind;
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;
else if (ternary) prec = 2;
else break;
if (prec < minPrec) break;
if (ternary) {
// ClickHouse-style ternary conditional: cond ? then : else
// Right-associative: both branches parse at the full expression
// level (prec 2), nested ternaries are absorbed by the else-branch.
jj_consume_token(op, t.image);
Expression thenExpression;
ternaryThenBranchDepth++;
try {
thenExpression = prattExpressionRest(Condition(), 2);
} finally {
ternaryThenBranchDepth--;
}
if (!":".equals(getToken(1).image)) {
throw new ParseException("Expected ':' closing the ternary conditional operator");
}
jj_consume_token(getToken(1).kind, getToken(1).image);
Expression elseExpression = prattExpressionRest(Condition(), 2);
left = new TernaryExpression(left, thenExpression, elseExpression);
continue;
}
jj_consume_token(op, getToken(1).image);
// +1 makes OR/AND/XOR left-associative
Expression right = prattExpressionRest(Condition(), prec + 1);
Expand Down Expand Up @@ -920,6 +998,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
if (t.kind == OPENING_BRACKET && getToken(2).image.equals("+")) {
return isComparisonOperator(getToken(4));
}
// ClickHouse ternary `cond ? then : else` — the `?` must not be taken as the
// PostgreSQL JSON operator, it is consumed by prattExpressionRest instead
if ("?".equals(t.image) && isTernaryAhead()) return false;
if (isComparisonOperator(t)) return true;
switch (t.kind) {
// Each suffix's start token:
Expand Down Expand Up @@ -7980,7 +8061,7 @@ Expression PrimaryExpression() #PrimaryExpression:

// Check for JSON operands
[
LOOKAHEAD(2) (
LOOKAHEAD(2, { ternaryThenBranchDepth == 0 || !":".equals(getToken(1).image) }) (
LOOKAHEAD(2) (
token="->"
|
Expand Down Expand Up @@ -8310,7 +8391,7 @@ JsonExpression JsonExpression(Expression expr, List<Map.Entry<Expression, String
}

(
LOOKAHEAD(2) (
LOOKAHEAD(2, { ternaryThenBranchDepth == 0 || !":".equals(getToken(1).image) }) (
token="->"
|
token=":"
Expand Down
Loading
Loading