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 @@ -874,6 +874,9 @@ public <S> Void visit(WithItem<?> withItem, S context) {
if (body instanceof Statement) {
((Statement) body).accept(analysis.statements, context);
}
if (withItem.getExpression() != null) {
withItem.getExpression().accept(new FeatureExpressionVisitor(analysis), context);
}
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,19 @@ public <S> T visit(WithItem<?> withItem, S context) {
return ((Statement) body).accept(statementVisitor, context);
}

// a ClickHouse expression alias (WITH <expression> AS <identifier>): a
// parenthesized subquery stays on the select path, any other expression
// goes to the expression visitor
Expression expression = withItem.getExpression();
if (expression != null) {
if (expression instanceof Select) {
return ((Select) expression).accept(this, context);
}
if (expressionVisitor != null) {
return expression.accept(expressionVisitor, context);
}
}

// no statement visitor available: skip the body rather than fail
return null;
}
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/select/WithItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class WithItem<K extends ParenthesedStatement> implements Serializable {
private Alias alias;
private List<SelectItem<?>> withItemList;
private WithFunctionDeclaration withFunctionDeclaration;
private Expression expression;
private WithSearchClause searchClause;
private WithCycleClause cycleClause;
private boolean recursive = false;
Expand All @@ -45,6 +46,25 @@ public WithItem() {
this(null, (Alias) null);
}

/**
* The aliased expression of a WITH item like ClickHouse's
* {@code WITH <expression> AS <identifier>}, where the item does not hold a statement.
*
* @return the expression of this WITH item, or null for statement-based (CTE) items
*/
public Expression getExpression() {
return expression;
}

public void setExpression(Expression expression) {
this.expression = expression;
}

public WithItem<K> withExpression(Expression expression) {
this.setExpression(expression);
return this;
}

public K getParenthesedStatement() {
return statement;
}
Expand Down Expand Up @@ -184,6 +204,12 @@ public String toString() {
StringBuilder builder = new StringBuilder();
if (withFunctionDeclaration != null) {
builder.append(withFunctionDeclaration);
} else if (expression != null) {
builder.append(expression);
if (alias != null) {
builder.append(" AS ").append(alias.getName());
}
appendRecursiveClausesTo(builder, expr -> builder.append(expr));
} else {
builder.append(recursive ? "RECURSIVE " : "");
if (alias != null) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ public <S> Void visit(WithItem<?> withItem, S context) {
}
// dispatch any ParenthesedStatement payload (Select, Delete, Update, Insert)
withItem.accept((StatementVisitor<?>) this, context);
// an expression alias (WITH expr AS name) may read tables on its own
if (withItem.getExpression() != null) {
withItem.getExpression().accept(this, context);
}
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ <S> StringBuilder deparseWithItem(WithItem<?> item, S context) {
if (item.getWithFunctionDeclaration() != null) {
return builder.append(item.getWithFunctionDeclaration());
}
if (item.getExpression() != null) {
item.getExpression().accept(expressions, context);
if (item.getAlias() != null) {
builder.append(" AS ").append(item.getAlias().getName());
}
return item.appendRecursiveClausesTo(builder,
expression -> expression.accept(expressions, context));
}
if (item.isRecursive()) {
builder.append("RECURSIVE ");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ public <S> Void visit(WithItem<?> withItem, S context) {
if (withItem.getCycleClause() != null) {
withItem.getCycleClause().accept(getValidator(ExpressionValidator.class), context);
}
if (withItem.getExpression() != null) {
withItem.getExpression().accept(getValidator(ExpressionValidator.class), context);
}
withItem.accept(getValidator(StatementValidator.class), context);
return null;
}
Expand Down
153 changes: 145 additions & 8 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import net.sf.jsqlparser.statement.export.*;
import net.sf.jsqlparser.statement.lock.*;
import java.util.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.concurrent.CancellationException;
import net.sf.jsqlparser.statement.select.SetOperationList.SetOperationType;

import java.util.logging.Level;
Expand Down Expand Up @@ -711,6 +712,90 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}
}

/** The literal tokens that may complete a typed literal in {@link #isImplicitCastAhead()}. */
private boolean isTypedLiteralFollower(int kind) {
return kind == S_CHAR_LITERAL || kind == S_LONG || kind == S_DOUBLE;
}

/**
* Whether the token could start a {@link #RelObjectName()}, mirroring the token
* alternatives accepted there (base identifiers, DATA_TYPE, non-reserved keywords
* and the reserved-keyword-as-identifier list).
*/
private static boolean isIdentifierishNameToken(Token t) {
switch (t.kind) {
case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case DATA_TYPE:
case K_DATETIMELITERAL: case K_DATE_LITERAL:
case K_ALL: case K_ANY: case K_CASEWHEN: case K_CONNECT: case K_CREATE:
case K_DEFAULT: case K_GLOBAL: case K_GROUP: case K_GROUPING: case K_IF:
case K_IIF: case K_IGNORE: case K_IN: case K_INTERVAL: case K_LEFT:
case K_LIMIT: case K_NEXTVAL: case K_OFFSET: case K_ON: case K_OPTIMIZE:
case K_ORDER: case K_PROCEDURE: case K_PUBLIC: case K_QUALIFY: case K_RIGHT:
case K_FILE: case K_SET: case K_SOME: case K_START: case K_TABLES:
case K_TOP: case K_VALUE: case K_VALUES:
return true;
default:
return t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD;
}
}

private Token nextWithItemToken(Token current) {
if (current.next == null) {
current.next = token_source.getNextToken();
}
return current.next;
}

private Token skipBalancedBracketGroup(Token current) {
int depth = 0;
while (!interrupted && current.kind != EOF) {
if (current.kind == OPENING_BRACKET) {
depth++;
} else if (current.kind == CLOSING_BRACKET) {
if (--depth == 0) {
return nextWithItemToken(current);
}
}
current = nextWithItemToken(current);
}
if (interrupted) {
throw new CancellationException("Parsing interrupted");
}
return null;
}

private boolean isWithExpressionAliasAhead() {
try {
Token t1 = getToken(1);
if (t1.kind == K_FUNCTION || t1.kind == K_RECURSIVE) {
return false;
}
if (!isIdentifierishNameToken(t1)) {
return true;
}
Token next = getToken(2);
if (next.kind == OPENING_BRACKET) {
next = skipBalancedBracketGroup(next);
if (next == null) {
return false;
}
}
if (next.kind != K_AS) {
return true;
}
next = nextWithItemToken(next);
if (next.kind == K_NOT) {
next = nextWithItemToken(next);
}
if (next.kind == K_MATERIALIZED) {
next = nextWithItemToken(next);
}
return next.kind != OPENING_BRACKET;
} catch (TokenMgrException e) {
return false;
}
}

protected boolean isImplicitCastAhead() {
try {
int k1 = getToken(1).kind;
Expand All @@ -721,18 +806,33 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
if (k1 == K_JSON) return getToken(2).kind == S_CHAR_LITERAL;
if (k1 != DATA_TYPE) return false;
int k2 = getToken(2).kind;
if (k2 != OPENING_BRACKET) return true; // DATA_TYPE literal - simple cast
// DATA_TYPE( ... ) - precision cast if content is only S_LONG literals
// function call otherwise (e.g. UUID(), VARCHAR(col))
if (k2 != OPENING_BRACKET) {
// a bare DATA_TYPE is a typed literal (INT '5') only when a trailing
// literal completes it, possibly after further type-name tokens
// (DOUBLE PRECISION '1'); otherwise it is an identifier, e.g. a
// column named number or a keyword-named lambda alias
if (isTypedLiteralFollower(k2)) {
return true;
}
return k2 == DATA_TYPE && isTypedLiteralFollower(getToken(3).kind);
}
// DATA_TYPE( ... ) - precision cast only when a trailing literal completes
// the typed literal (DATA_TYPE(N) 'lit'); a function call otherwise,
// e.g. double(5), int(5), VARCHAR(col), UUID()
int k3 = getToken(3).kind;
if (k3 == CLOSING_BRACKET) return false; // DATA_TYPE() - empty call
if (k3 != S_LONG) return false; // DATA_TYPE(expr) - function call
int k4 = getToken(4).kind;
if (k4 == CLOSING_BRACKET) return true; // DATA_TYPE(N) - precision cast
if (k4 != K_COMMA) return false; // DATA_TYPE(N expr) - function call
int k5 = getToken(5).kind;
if (k5 != S_LONG) return false; // DATA_TYPE(N, expr) - function call
return getToken(6).kind == CLOSING_BRACKET; // DATA_TYPE(N,M) - precision cast
if (k4 == K_COMMA) {
int k5 = getToken(5).kind;
if (k5 != S_LONG) return false; // DATA_TYPE(N, expr) - function call
if (getToken(6).kind != CLOSING_BRACKET) {
return false; // DATA_TYPE(N,M expr) - function call
}
return isTypedLiteralFollower(getToken(7).kind); // DATA_TYPE(N,M) 'lit'
}
if (k4 != CLOSING_BRACKET) return false; // DATA_TYPE(N expr) - function call
return isTypedLiteralFollower(getToken(5).kind); // DATA_TYPE(N) 'lit'
} catch (TokenMgrException e) {
return false;
}
Expand Down Expand Up @@ -7011,6 +7111,7 @@ WithItem<?> WithItem() #WithItem:
List<SelectItem<?>> selectItems = null;
WithFunctionDeclaration withFunctionDeclaration = null;
ParenthesedStatement statement = null;
Expression expr = null;
WithSearchClause withSearchClause = null;
WithCycleClause withCycleClause = null;
WithItem<?> withItem;
Expand All @@ -7023,6 +7124,7 @@ WithItem<?> WithItem() #WithItem:
withItem = new WithItem().withWithFunctionDeclaration(withFunctionDeclaration);
}
|
LOOKAHEAD({ !isWithExpressionAliasAhead() })
(
[ LOOKAHEAD(2) <K_RECURSIVE> { recursive = true; } ]
name=RelObjectName()
Expand All @@ -7044,6 +7146,16 @@ WithItem<?> WithItem() #WithItem:
.withWithItemList(selectItems);
}
)
|
(
// ClickHouse style expression alias: WITH <expression> AS <identifier>
expr = WithItemExpression()
<K_AS>
name = RelObjectName()
{
withItem = new WithItem().withExpression(expr).withAlias(new Alias(name, false));
}
)
)
[ withSearchClause = WithSearchClause() { withItem.setSearchClause(withSearchClause); } ]
[ withCycleClause = WithCycleClause() { withItem.setCycleClause(withCycleClause); } ]
Expand All @@ -7052,6 +7164,31 @@ WithItem<?> WithItem() #WithItem:
}
}

Expression WithItemExpression():
{
LambdaExpression lambdaExpression;
Expression expr;
}
{
(
// unparenthesized parameter, e.g. x -> x * 2
LOOKAHEAD( RelObjectName() "->" )
expr = LambdaExpression()
|
// parenthesized parameter list, e.g. (value) -> value + 1
LOOKAHEAD( ParenthesedColumnList() "->" )
expr = LambdaExpression()
|
// whole lambda wrapped in parentheses, e.g. (x -> x * 2)
LOOKAHEAD( "(" ( RelObjectName() | ParenthesedColumnList() ) "->" )
"(" lambdaExpression = LambdaExpression() ")"
{ expr = new ParenthesedExpressionList<Expression>(lambdaExpression); }
|
expr = Expression()
)
{ return expr; }
}

WithSearchClause WithSearchClause() #WithSearchClause:
{
Token orderingToken;
Expand Down
Loading
Loading