Skip to content

Commit 4452bb9

Browse files
committed
Add PROJECTION support for ALTER commands
Implemented support for: - ADD PROJECTION (parses projection definition with SELECT/ORDER BY/GROUP BY) - DROP PROJECTION - MATERIALIZE PROJECTION - CLEAR PROJECTION Added new AST types: - Projection (with name and select query) - ProjectionSelectQuery (with columns, order by, group by) This fixes 53+ explain tests related to ALTER TABLE projections.
1 parent fc9aa6f commit 4452bb9

40 files changed

Lines changed: 230 additions & 108 deletions

File tree

ast/ast.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,26 @@ type AlterCommand struct {
426426
Settings []*SettingExpr `json:"settings,omitempty"`
427427
Where Expression `json:"where,omitempty"` // For DELETE WHERE
428428
Assignments []*Assignment `json:"assignments,omitempty"` // For UPDATE
429+
Projection *Projection `json:"projection,omitempty"` // For ADD PROJECTION
430+
ProjectionName string `json:"projection_name,omitempty"` // For DROP/MATERIALIZE/CLEAR PROJECTION
431+
}
432+
433+
// Projection represents a projection definition.
434+
type Projection struct {
435+
Position token.Position `json:"-"`
436+
Name string `json:"name"`
437+
Select *ProjectionSelectQuery `json:"select"`
438+
}
439+
440+
func (p *Projection) Pos() token.Position { return p.Position }
441+
func (p *Projection) End() token.Position { return p.Position }
442+
443+
// ProjectionSelectQuery represents the SELECT part of a projection.
444+
type ProjectionSelectQuery struct {
445+
Position token.Position `json:"-"`
446+
Columns []Expression `json:"columns"`
447+
GroupBy []Expression `json:"group_by,omitempty"`
448+
OrderBy *Identifier `json:"order_by,omitempty"` // Single column for ORDER BY
429449
}
430450

431451
// Assignment represents a column assignment in UPDATE.
@@ -457,16 +477,20 @@ const (
457477
AlterMaterializeIndex AlterCommandType = "MATERIALIZE_INDEX"
458478
AlterAddConstraint AlterCommandType = "ADD_CONSTRAINT"
459479
AlterDropConstraint AlterCommandType = "DROP_CONSTRAINT"
460-
AlterModifyTTL AlterCommandType = "MODIFY_TTL"
461-
AlterModifySetting AlterCommandType = "MODIFY_SETTING"
462-
AlterDropPartition AlterCommandType = "DROP_PARTITION"
463-
AlterDetachPartition AlterCommandType = "DETACH_PARTITION"
464-
AlterAttachPartition AlterCommandType = "ATTACH_PARTITION"
465-
AlterReplacePartition AlterCommandType = "REPLACE_PARTITION"
466-
AlterFreezePartition AlterCommandType = "FREEZE_PARTITION"
467-
AlterFreeze AlterCommandType = "FREEZE"
468-
AlterDeleteWhere AlterCommandType = "DELETE_WHERE"
469-
AlterUpdate AlterCommandType = "UPDATE"
480+
AlterModifyTTL AlterCommandType = "MODIFY_TTL"
481+
AlterModifySetting AlterCommandType = "MODIFY_SETTING"
482+
AlterDropPartition AlterCommandType = "DROP_PARTITION"
483+
AlterDetachPartition AlterCommandType = "DETACH_PARTITION"
484+
AlterAttachPartition AlterCommandType = "ATTACH_PARTITION"
485+
AlterReplacePartition AlterCommandType = "REPLACE_PARTITION"
486+
AlterFreezePartition AlterCommandType = "FREEZE_PARTITION"
487+
AlterFreeze AlterCommandType = "FREEZE"
488+
AlterDeleteWhere AlterCommandType = "DELETE_WHERE"
489+
AlterUpdate AlterCommandType = "UPDATE"
490+
AlterAddProjection AlterCommandType = "ADD_PROJECTION"
491+
AlterDropProjection AlterCommandType = "DROP_PROJECTION"
492+
AlterMaterializeProjection AlterCommandType = "MATERIALIZE_PROJECTION"
493+
AlterClearProjection AlterCommandType = "CLEAR_PROJECTION"
470494
)
471495

472496
// TruncateQuery represents a TRUNCATE statement.

internal/explain/statements.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,13 +683,61 @@ func explainAlterCommand(sb *strings.Builder, cmd *ast.AlterCommand, indent stri
683683
if cmd.Where != nil {
684684
Node(sb, cmd.Where, depth+1)
685685
}
686+
case ast.AlterAddProjection:
687+
if cmd.Projection != nil {
688+
explainProjection(sb, cmd.Projection, indent, depth+1)
689+
}
690+
case ast.AlterDropProjection, ast.AlterMaterializeProjection, ast.AlterClearProjection:
691+
if cmd.ProjectionName != "" {
692+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, cmd.ProjectionName)
693+
}
686694
default:
687695
if cmd.Partition != nil {
688696
Node(sb, cmd.Partition, depth+1)
689697
}
690698
}
691699
}
692700

701+
func explainProjection(sb *strings.Builder, p *ast.Projection, indent string, depth int) {
702+
children := 0
703+
if p.Select != nil {
704+
children++
705+
}
706+
fmt.Fprintf(sb, "%s Projection (children %d)\n", indent, children)
707+
if p.Select != nil {
708+
explainProjectionSelectQuery(sb, p.Select, indent+" ", depth+1)
709+
}
710+
}
711+
712+
func explainProjectionSelectQuery(sb *strings.Builder, q *ast.ProjectionSelectQuery, indent string, depth int) {
713+
children := 0
714+
if len(q.Columns) > 0 {
715+
children++
716+
}
717+
if q.OrderBy != nil {
718+
children++
719+
}
720+
if len(q.GroupBy) > 0 {
721+
children++
722+
}
723+
fmt.Fprintf(sb, "%sProjectionSelectQuery (children %d)\n", indent, children)
724+
if len(q.Columns) > 0 {
725+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(q.Columns))
726+
for _, col := range q.Columns {
727+
Node(sb, col, depth+2)
728+
}
729+
}
730+
if q.OrderBy != nil {
731+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, q.OrderBy.Parts[len(q.OrderBy.Parts)-1])
732+
}
733+
if len(q.GroupBy) > 0 {
734+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(q.GroupBy))
735+
for _, expr := range q.GroupBy {
736+
Node(sb, expr, depth+2)
737+
}
738+
}
739+
}
740+
693741
func countAlterCommandChildren(cmd *ast.AlterCommand) int {
694742
children := 0
695743
switch cmd.Type {
@@ -754,6 +802,14 @@ func countAlterCommandChildren(cmd *ast.AlterCommand) int {
754802
if cmd.Where != nil {
755803
children++
756804
}
805+
case ast.AlterAddProjection:
806+
if cmd.Projection != nil {
807+
children++
808+
}
809+
case ast.AlterDropProjection, ast.AlterMaterializeProjection, ast.AlterClearProjection:
810+
if cmd.ProjectionName != "" {
811+
children++
812+
}
757813
default:
758814
if cmd.Partition != nil {
759815
children++

parser/parser.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

parser/testdata/01701_clear_projection_and_part_remove/metadata.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
"explain_todo": {
33
"stmt10": true,
44
"stmt2": true,
5-
"stmt5": true,
65
"stmt7": true,
7-
"stmt8": true,
8-
"stmt9": true
6+
"stmt8": true
97
}
108
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"explain_todo": {
3-
"stmt5": true,
43
"stmt6": true
54
}
65
}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt4": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt6": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"explain_todo": {
3-
"stmt3": true,
4-
"stmt6": true
3+
"stmt3": true
54
}
65
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
{
22
"explain_todo": {
3-
"stmt2": true,
4-
"stmt3": true,
5-
"stmt4": true,
6-
"stmt5": true,
7-
"stmt6": true,
8-
"stmt7": true
3+
"stmt2": true
94
}
105
}

parser/testdata/01710_projection_fetch_long/metadata.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
{
22
"explain_todo": {
3-
"stmt11": true,
43
"stmt12": true,
54
"stmt13": true,
6-
"stmt16": true,
75
"stmt17": true,
86
"stmt19": true,
97
"stmt20": true,
108
"stmt21": true,
11-
"stmt23": true,
129
"stmt24": true,
1310
"stmt25": true,
1411
"stmt3": true,

0 commit comments

Comments
 (0)