@@ -2472,6 +2472,10 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
24722472 Expression : p .parseExpression (LOWEST ),
24732473 }
24742474 }
2475+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "PROJECTION" {
2476+ cmd .Type = ast .AlterAddProjection
2477+ p .nextToken ()
2478+ cmd .Projection = p .parseProjection ()
24752479 }
24762480 case token .DROP :
24772481 p .nextToken ()
@@ -2505,6 +2509,18 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
25052509 cmd .Type = ast .AlterDropPartition
25062510 p .nextToken ()
25072511 cmd .Partition = p .parseExpression (LOWEST )
2512+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "PROJECTION" {
2513+ cmd .Type = ast .AlterDropProjection
2514+ p .nextToken ()
2515+ if p .currentIs (token .IF ) {
2516+ p .nextToken ()
2517+ p .expect (token .EXISTS )
2518+ cmd .IfExists = true
2519+ }
2520+ if p .currentIs (token .IDENT ) {
2521+ cmd .ProjectionName = p .current .Value
2522+ p .nextToken ()
2523+ }
25082524 }
25092525 case token .IDENT :
25102526 // Handle CLEAR, MATERIALIZE
@@ -2525,6 +2541,13 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
25252541 cmd .ColumnName = p .current .Value
25262542 p .nextToken ()
25272543 }
2544+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "PROJECTION" {
2545+ cmd .Type = ast .AlterClearProjection
2546+ p .nextToken ()
2547+ if p .currentIs (token .IDENT ) {
2548+ cmd .ProjectionName = p .current .Value
2549+ p .nextToken ()
2550+ }
25282551 }
25292552 } else if upper == "MATERIALIZE" {
25302553 p .nextToken ()
@@ -2535,6 +2558,13 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
25352558 cmd .Index = p .current .Value
25362559 p .nextToken ()
25372560 }
2561+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "PROJECTION" {
2562+ cmd .Type = ast .AlterMaterializeProjection
2563+ p .nextToken ()
2564+ if p .currentIs (token .IDENT ) {
2565+ cmd .ProjectionName = p .current .Value
2566+ p .nextToken ()
2567+ }
25382568 }
25392569 } else {
25402570 return nil
@@ -3500,3 +3530,92 @@ func (p *Parser) parseExistsStatement() *ast.ExistsQuery {
35003530
35013531 return exists
35023532}
3533+
3534+ // parseProjection parses a projection definition: name (SELECT ... [ORDER BY col] [GROUP BY ...])
3535+ func (p * Parser ) parseProjection () * ast.Projection {
3536+ proj := & ast.Projection {
3537+ Position : p .current .Pos ,
3538+ }
3539+
3540+ // Parse projection name
3541+ if p .currentIs (token .IDENT ) {
3542+ proj .Name = p .current .Value
3543+ p .nextToken ()
3544+ }
3545+
3546+ // Parse (SELECT ...)
3547+ if ! p .currentIs (token .LPAREN ) {
3548+ return proj
3549+ }
3550+ p .nextToken () // skip (
3551+
3552+ proj .Select = & ast.ProjectionSelectQuery {
3553+ Position : p .current .Pos ,
3554+ }
3555+
3556+ // Parse SELECT keyword (optional in projection)
3557+ if p .currentIs (token .SELECT ) {
3558+ p .nextToken ()
3559+ }
3560+
3561+ // Parse column list
3562+ for ! p .currentIs (token .EOF ) && ! p .currentIs (token .RPAREN ) {
3563+ // Check for GROUP BY or ORDER BY
3564+ if p .currentIs (token .GROUP ) || p .currentIs (token .ORDER ) {
3565+ break
3566+ }
3567+
3568+ col := p .parseExpression (LOWEST )
3569+ if col != nil {
3570+ proj .Select .Columns = append (proj .Select .Columns , col )
3571+ }
3572+
3573+ if p .currentIs (token .COMMA ) {
3574+ p .nextToken ()
3575+ } else {
3576+ break
3577+ }
3578+ }
3579+
3580+ // Parse GROUP BY
3581+ if p .currentIs (token .GROUP ) {
3582+ p .nextToken () // GROUP
3583+ if p .currentIs (token .BY ) {
3584+ p .nextToken () // BY
3585+ }
3586+ for ! p .currentIs (token .EOF ) && ! p .currentIs (token .RPAREN ) && ! p .currentIs (token .ORDER ) {
3587+ expr := p .parseExpression (LOWEST )
3588+ if expr != nil {
3589+ proj .Select .GroupBy = append (proj .Select .GroupBy , expr )
3590+ }
3591+ if p .currentIs (token .COMMA ) {
3592+ p .nextToken ()
3593+ } else {
3594+ break
3595+ }
3596+ }
3597+ }
3598+
3599+ // Parse ORDER BY
3600+ if p .currentIs (token .ORDER ) {
3601+ p .nextToken () // ORDER
3602+ if p .currentIs (token .BY ) {
3603+ p .nextToken () // BY
3604+ }
3605+ // For projection ORDER BY, we just need the column name
3606+ if p .currentIs (token .IDENT ) {
3607+ proj .Select .OrderBy = & ast.Identifier {
3608+ Position : p .current .Pos ,
3609+ Parts : []string {p .current .Value },
3610+ }
3611+ p .nextToken ()
3612+ }
3613+ }
3614+
3615+ // Skip closing paren
3616+ if p .currentIs (token .RPAREN ) {
3617+ p .nextToken ()
3618+ }
3619+
3620+ return proj
3621+ }
0 commit comments