@@ -2656,54 +2656,25 @@ func (p *Parser) parseTableOptions(create *ast.CreateQuery) {
26562656 case p .currentIs (token .TTL ):
26572657 p .nextToken ()
26582658 create .TTL = & ast.TTLClause {
2659- Position : p .current .Pos ,
2660- Expression : p .parseExpression (ALIAS_PREC ), // Use ALIAS_PREC for AS SELECT
2661- }
2662- // Skip RECOMPRESS CODEC(...) if present
2663- p .skipTTLModifiers ()
2664- // Parse additional TTL elements (comma-separated)
2665- for p .currentIs (token .COMMA ) {
2666- p .nextToken () // skip comma
2667- expr := p .parseExpression (ALIAS_PREC )
2668- create .TTL .Expressions = append (create .TTL .Expressions , expr )
2669- // Skip RECOMPRESS CODEC(...) if present
2670- p .skipTTLModifiers ()
2659+ Position : p .current .Pos ,
26712660 }
2672- // Handle TTL GROUP BY x SET y = max(y) syntax
2673- if p .currentIs (token .GROUP ) {
2674- p .nextToken ()
2675- if p .currentIs (token .BY ) {
2661+ // Parse TTL elements (comma-separated)
2662+ for {
2663+ elem := p .parseTTLElement ()
2664+ create .TTL .Elements = append (create .TTL .Elements , elem )
2665+ if p .currentIs (token .COMMA ) {
26762666 p .nextToken ()
2677- // Parse GROUP BY expressions (can have multiple, comma separated)
2678- for {
2679- p .parseExpression (ALIAS_PREC )
2680- if p .currentIs (token .COMMA ) {
2681- p .nextToken ()
2682- } else {
2683- break
2684- }
2685- }
2667+ } else {
2668+ break
26862669 }
26872670 }
2688- // Handle SET clause in TTL (aggregation expressions for TTL GROUP BY)
2689- if p .currentIs (token .SET ) {
2690- p .nextToken ()
2691- // Parse SET expressions until we hit a keyword or end
2692- for ! p .currentIs (token .SETTINGS ) && ! p .currentIs (token .AS ) && ! p .currentIs (token .WHERE ) && ! p .currentIs (token .SEMICOLON ) && ! p .currentIs (token .EOF ) {
2693- p .parseExpression (ALIAS_PREC )
2694- if p .currentIs (token .COMMA ) {
2695- p .nextToken ()
2696- } else {
2697- break
2698- }
2671+ // Keep backward compatibility with Expression/Expressions fields
2672+ if len (create .TTL .Elements ) > 0 {
2673+ create .TTL .Expression = create .TTL .Elements [0 ].Expr
2674+ for i := 1 ; i < len (create .TTL .Elements ); i ++ {
2675+ create .TTL .Expressions = append (create .TTL .Expressions , create .TTL .Elements [i ].Expr )
26992676 }
27002677 }
2701- // Handle WHERE clause in TTL (conditional deletion)
2702- if p .currentIs (token .WHERE ) {
2703- p .nextToken ()
2704- // Parse WHERE condition
2705- p .parseExpression (ALIAS_PREC )
2706- }
27072678 case p .currentIs (token .SETTINGS ):
27082679 p .nextToken ()
27092680 create .Settings = p .parseSettingsList ()
@@ -8068,6 +8039,119 @@ func (p *Parser) parseTransactionControl() *ast.TransactionControlQuery {
80688039 return query
80698040}
80708041
8042+ // parseTTLElement parses a single TTL element: expression [DELETE] [WHERE condition] [GROUP BY ...] [SET ...]
8043+ func (p * Parser ) parseTTLElement () * ast.TTLElement {
8044+ elem := & ast.TTLElement {
8045+ Position : p .current .Pos ,
8046+ Expr : p .parseExpression (ALIAS_PREC ),
8047+ }
8048+ // Skip RECOMPRESS CODEC(...), DELETE, TO DISK, TO VOLUME (but not WHERE)
8049+ p .skipTTLModifiersExceptWhere ()
8050+ // Handle WHERE clause for this TTL element (conditional deletion)
8051+ if p .currentIs (token .WHERE ) {
8052+ p .nextToken ()
8053+ elem .Where = p .parseExpression (ALIAS_PREC )
8054+ }
8055+ // Handle GROUP BY x SET y = max(y) syntax (skip for now, already parsed in Where or just skip)
8056+ if p .currentIs (token .GROUP ) {
8057+ p .nextToken ()
8058+ if p .currentIs (token .BY ) {
8059+ p .nextToken ()
8060+ for {
8061+ p .parseExpression (ALIAS_PREC )
8062+ if p .currentIs (token .COMMA ) {
8063+ p .nextToken ()
8064+ } else {
8065+ break
8066+ }
8067+ }
8068+ }
8069+ }
8070+ // Handle SET clause - assignments are comma separated (id = expr, id = expr, ...)
8071+ // We need to distinguish between:
8072+ // - Comma continuing SET: followed by IDENT = pattern
8073+ // - Comma starting new TTL: followed by expression (like d + toIntervalYear(...))
8074+ if p .currentIs (token .SET ) {
8075+ p .nextToken ()
8076+ for {
8077+ // Parse assignment expression: id = expr
8078+ p .parseExpression (ALIAS_PREC )
8079+ // Check for comma
8080+ if p .currentIs (token .COMMA ) {
8081+ // Look ahead to check pattern. We need to see: COMMA IDENT EQ
8082+ // Save state to peek ahead
8083+ savedCurrent := p .current
8084+ savedPeek := p .peek
8085+ p .nextToken () // skip comma to see what follows
8086+ isSetContinuation := false
8087+ if p .currentIs (token .IDENT ) || p .current .Token .IsKeyword () {
8088+ if p .peekIs (token .EQ ) {
8089+ // It's another SET assignment (id = expr)
8090+ isSetContinuation = true
8091+ }
8092+ }
8093+ if isSetContinuation {
8094+ // Continue parsing SET assignments (already consumed comma)
8095+ continue
8096+ }
8097+ // Not a SET assignment - restore state so caller sees the comma
8098+ p .current = savedCurrent
8099+ p .peek = savedPeek
8100+ break
8101+ }
8102+ // No comma, end of SET clause
8103+ break
8104+ }
8105+ }
8106+ return elem
8107+ }
8108+
8109+ // skipTTLModifiersExceptWhere skips TTL modifiers but stops at WHERE
8110+ func (p * Parser ) skipTTLModifiersExceptWhere () {
8111+ for {
8112+ // Skip RECOMPRESS CODEC(...)
8113+ if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "RECOMPRESS" {
8114+ p .nextToken ()
8115+ if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "CODEC" {
8116+ p .nextToken ()
8117+ if p .currentIs (token .LPAREN ) {
8118+ depth := 1
8119+ p .nextToken ()
8120+ for depth > 0 && ! p .currentIs (token .EOF ) {
8121+ if p .currentIs (token .LPAREN ) {
8122+ depth ++
8123+ } else if p .currentIs (token .RPAREN ) {
8124+ depth --
8125+ }
8126+ p .nextToken ()
8127+ }
8128+ }
8129+ }
8130+ continue
8131+ }
8132+ // Skip DELETE (TTL ... DELETE)
8133+ if p .currentIs (token .DELETE ) {
8134+ p .nextToken ()
8135+ continue
8136+ }
8137+ // Skip TO DISK 'name' or TO VOLUME 'name'
8138+ if p .currentIs (token .TO ) {
8139+ p .nextToken ()
8140+ if p .currentIs (token .IDENT ) {
8141+ upper := strings .ToUpper (p .current .Value )
8142+ if upper == "DISK" || upper == "VOLUME" {
8143+ p .nextToken ()
8144+ if p .currentIs (token .STRING ) {
8145+ p .nextToken ()
8146+ }
8147+ continue
8148+ }
8149+ }
8150+ }
8151+ break
8152+ }
8153+ }
8154+
80718155// skipTTLModifiers skips TTL modifiers like RECOMPRESS CODEC(...), DELETE, TO DISK, TO VOLUME
80728156func (p * Parser ) skipTTLModifiers () {
80738157 for {
0 commit comments