@@ -711,6 +711,119 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
711711 }
712712 }
713713
714+ /** The literal tokens that may complete a typed literal in {@link #isImplicitCastAhead()}. */
715+ private boolean isTypedLiteralFollower(int kind) {
716+ return kind == S_CHAR_LITERAL || kind == S_LONG || kind == S_DOUBLE;
717+ }
718+
719+ /**
720+ * Whether the token could start a {@link #RelObjectName()}, mirroring the token
721+ * alternatives accepted there (base identifiers, DATA_TYPE, non-reserved keywords
722+ * and the reserved-keyword-as-identifier list).
723+ */
724+ private static boolean isIdentifierishNameToken(Token t) {
725+ switch (t.kind) {
726+ case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case DATA_TYPE:
727+ case K_DATETIMELITERAL: case K_DATE_LITERAL:
728+ case K_ALL: case K_ANY: case K_CASEWHEN: case K_CONNECT: case K_CREATE:
729+ case K_DEFAULT: case K_GLOBAL: case K_GROUP: case K_GROUPING: case K_IF:
730+ case K_IIF: case K_IGNORE: case K_IN: case K_INTERVAL: case K_LEFT:
731+ case K_LIMIT: case K_NEXTVAL: case K_OFFSET: case K_ON: case K_OPTIMIZE:
732+ case K_ORDER: case K_PROCEDURE: case K_PUBLIC: case K_QUALIFY: case K_RIGHT:
733+ case K_FILE: case K_SET: case K_SOME: case K_START: case K_TABLES:
734+ case K_TOP: case K_VALUE: case K_VALUES:
735+ return true;
736+ default:
737+ return t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD;
738+ }
739+ }
740+
741+ /**
742+ * Skips "." name continuations starting at token position {@code i}; returns the
743+ * position of the first token after the qualified name.
744+ */
745+ private int skipQualifiedObjectName(int i) {
746+ int guard = 0;
747+ while (guard++ < 64 && ".".equals(getToken(i).image)) {
748+ i += 2;
749+ }
750+ return i;
751+ }
752+
753+ /**
754+ * Skips the balanced round-bracket group opening at token position {@code i};
755+ * returns the position after the matching closing bracket, or -1 when the
756+ * group is unbalanced or the scan would run past the token stream.
757+ */
758+ private int skipBalancedBracketGroup(int i) {
759+ int depth = 0;
760+ int guard = 0;
761+ while (guard++ < 4096) {
762+ int kind = getToken(i).kind;
763+ if (kind == 0) {
764+ return -1;
765+ }
766+ if (kind == OPENING_BRACKET) {
767+ depth++;
768+ } else if (kind == CLOSING_BRACKET) {
769+ depth--;
770+ if (depth == 0) {
771+ return i + 1;
772+ }
773+ }
774+ i++;
775+ }
776+ return -1;
777+ }
778+
779+ /**
780+ * True when the upcoming WITH item is ClickHouse's expression alias form
781+ * {@code WITH <expression> AS <identifier>} (including lambda aliases such as
782+ * {@code x -> x * 2} or {@code (x -> x * 2)}), and false for the classic CTE
783+ * shape {@code name [(cols)] AS [NOT] MATERIALIZED (statement)} or for the
784+ * other WithItem alternatives (FUNCTION, RECURSIVE).
785+ *
786+ * <p>Everything that cannot start a CTE name (literals, brackets, operators,
787+ * function calls) can only be an expression. A name-shaped head followed by
788+ * anything but an AS decision point also continues as an expression
789+ * ({@code total * 2}, {@code case when .. end}); the CTE shape is exactly
790+ * the name-shaped head with AS before a parenthesized statement.</p>
791+ */
792+ private boolean isWithExpressionAliasAhead() {
793+ try {
794+ Token t1 = getToken(1);
795+ if (t1.kind == K_FUNCTION || t1.kind == K_RECURSIVE) {
796+ return false;
797+ }
798+ if (!isIdentifierishNameToken(t1)) {
799+ return true;
800+ }
801+ int i = skipQualifiedObjectName(2);
802+ if (getToken(i).kind == OPENING_BRACKET) {
803+ // a column list (CTE) or call arguments (expression); the decision
804+ // point sits behind the balanced group either way
805+ i = skipBalancedBracketGroup(i);
806+ if (i < 0) {
807+ return false;
808+ }
809+ }
810+ if (getToken(i).kind != K_AS) {
811+ return true;
812+ }
813+ i++;
814+ if (getToken(i).kind == K_NOT) {
815+ i++;
816+ }
817+ if (getToken(i).kind == K_MATERIALIZED) {
818+ i++;
819+ }
820+ // the CTE shape requires a parenthesized statement here
821+ return getToken(i).kind != OPENING_BRACKET;
822+ } catch (TokenMgrException e) {
823+ return false;
824+ }
825+ }
826+
714827 protected boolean isImplicitCastAhead() {
715828 try {
716829 int k1 = getToken(1).kind;
@@ -721,18 +834,33 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
721834 if (k1 == K_JSON) return getToken(2).kind == S_CHAR_LITERAL;
722835 if (k1 != DATA_TYPE) return false;
723836 int k2 = getToken(2).kind;
724- if (k2 != OPENING_BRACKET) return true; // DATA_TYPE literal - simple cast
725- // DATA_TYPE( ... ) - precision cast if content is only S_LONG literals
726- // function call otherwise (e.g. UUID(), VARCHAR(col))
837+ if (k2 != OPENING_BRACKET) {
838+ // a bare DATA_TYPE is a typed literal (INT '5') only when a trailing
839+ // literal completes it, possibly after further type-name tokens
840+ // (DOUBLE PRECISION '1'); otherwise it is an identifier, e.g. a
841+ // column named number or a keyword-named lambda alias
842+ if (isTypedLiteralFollower(k2)) {
843+ return true;
844+ }
845+ return k2 == DATA_TYPE && isTypedLiteralFollower(getToken(3).kind);
846+ }
847+ // DATA_TYPE( ... ) - precision cast only when a trailing literal completes
848+ // the typed literal (DATA_TYPE(N) 'lit'); a function call otherwise,
849+ // e.g. double(5), int(5), VARCHAR(col), UUID()
727850 int k3 = getToken(3).kind;
728851 if (k3 == CLOSING_BRACKET) return false; // DATA_TYPE() - empty call
729852 if (k3 != S_LONG) return false; // DATA_TYPE(expr) - function call
730853 int k4 = getToken(4).kind;
731- if (k4 == CLOSING_BRACKET) return true; // DATA_TYPE(N) - precision cast
732- if (k4 != K_COMMA) return false; // DATA_TYPE(N expr) - function call
733- int k5 = getToken(5).kind;
734- if (k5 != S_LONG) return false; // DATA_TYPE(N, expr) - function call
735- return getToken(6).kind == CLOSING_BRACKET; // DATA_TYPE(N,M) - precision cast
854+ if (k4 == K_COMMA) {
855+ int k5 = getToken(5).kind;
856+ if (k5 != S_LONG) return false; // DATA_TYPE(N, expr) - function call
857+ if (getToken(6).kind != CLOSING_BRACKET) {
858+ return false; // DATA_TYPE(N,M expr) - function call
859+ }
860+ return isTypedLiteralFollower(getToken(7).kind); // DATA_TYPE(N,M) 'lit'
861+ }
862+ if (k4 != CLOSING_BRACKET) return false; // DATA_TYPE(N expr) - function call
863+ return isTypedLiteralFollower(getToken(5).kind); // DATA_TYPE(N) 'lit'
736864 } catch (TokenMgrException e) {
737865 return false;
738866 }
@@ -7011,6 +7139,7 @@ WithItem<?> WithItem() #WithItem:
70117139 List<SelectItem<?>> selectItems = null;
70127140 WithFunctionDeclaration withFunctionDeclaration = null;
70137141 ParenthesedStatement statement = null;
7142+ Expression expr = null;
70147143 WithSearchClause withSearchClause = null;
70157144 WithCycleClause withCycleClause = null;
70167145 WithItem<?> withItem;
@@ -7023,6 +7152,7 @@ WithItem<?> WithItem() #WithItem:
70237152 withItem = new WithItem().withWithFunctionDeclaration(withFunctionDeclaration);
70247153 }
70257154 |
7155+ LOOKAHEAD({ !isWithExpressionAliasAhead() })
70267156 (
70277157 [ LOOKAHEAD(2) <K_RECURSIVE> { recursive = true; } ]
70287158 name=RelObjectName()
@@ -7044,6 +7174,16 @@ WithItem<?> WithItem() #WithItem:
70447174 .withWithItemList(selectItems);
70457175 }
70467176 )
7177+ |
7178+ (
7179+ // ClickHouse style expression alias: WITH <expression> AS <identifier>
7180+ expr = WithItemExpression()
7181+ <K_AS>
7182+ name = RelObjectName()
7183+ {
7184+ withItem = new WithItem().withExpression(expr).withAlias(new Alias(name, false));
7185+ }
7186+ )
70477187 )
70487188 [ withSearchClause = WithSearchClause() { withItem.setSearchClause(withSearchClause); } ]
70497189 [ withCycleClause = WithCycleClause() { withItem.setCycleClause(withCycleClause); } ]
@@ -7052,6 +7192,31 @@ WithItem<?> WithItem() #WithItem:
70527192 }
70537193}
70547194
7195+ Expression WithItemExpression():
7196+ {
7197+ LambdaExpression lambdaExpression;
7198+ Expression expr;
7199+ }
7200+ {
7201+ (
7202+ // unparenthesized parameter, e.g. x -> x * 2
7203+ LOOKAHEAD( RelObjectName() "->" )
7204+ expr = LambdaExpression()
7205+ |
7206+ // parenthesized parameter list, e.g. (value) -> value + 1
7207+ LOOKAHEAD( ParenthesedColumnList() "->" )
7208+ expr = LambdaExpression()
7209+ |
7210+ // whole lambda wrapped in parentheses, e.g. (x -> x * 2)
7211+ LOOKAHEAD( "(" ( RelObjectName() | ParenthesedColumnList() ) "->" )
7212+ "(" lambdaExpression = LambdaExpression() ")"
7213+ { expr = new ParenthesedExpressionList<Expression>(lambdaExpression); }
7214+ |
7215+ expr = Expression()
7216+ )
7217+ { return expr; }
7218+ }
7219+
70557220WithSearchClause WithSearchClause() #WithSearchClause:
70567221{
70577222 Token orderingToken;
0 commit comments