Repository: AfterShip/clickhouse-sql-parser
Pull request #62 (2024-04-03 06:35:02)

Add support of the CLEAR PROJECTION statement
This PR is part of #59 


Patch:
diff --git a/parser/ast.go b/parser/ast.go
index 05e2c32..8b7b4b5 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -868,6 +868,57 @@ func (a *AlterTableClearIndex) Accept(visitor ASTVisitor) error {
 	return visitor.VisitAlterTableClearIndex(a)
 }
 
+type AlterTableClearProjection struct {
+	ClearPos     Pos
+	StatementEnd Pos
+
+	IfExists       bool
+	ProjectionName *NestedIdentifier
+	PartitionExpr  *PartitionExpr
+}
+
+func (a *AlterTableClearProjection) Pos() Pos {
+	return a.ClearPos
+}
+
+func (a *AlterTableClearProjection) End() Pos {
+	return a.StatementEnd
+}
+
+func (a *AlterTableClearProjection) AlterType() string {
+	return "CLEAR_PROJECTION"
+}
+
+func (a *AlterTableClearProjection) String(level int) string {
+	var builder strings.Builder
+	builder.WriteString("CLEAR PROJECTION ")
+	if a.IfExists {
+		builder.WriteString("IF EXISTS ")
+	}
+	builder.WriteString(a.ProjectionName.String(level + 1))
+	if a.PartitionExpr != nil {
+		builder.WriteString(NewLine(level))
+		builder.WriteString("IN ")
+		builder.WriteString(a.PartitionExpr.String(level))
+	}
+
+	return builder.String()
+}
+
+func (a *AlterTableClearProjection) Accept(visitor ASTVisitor) error {
+	visitor.enter(a)
+	defer visitor.leave(a)
+	if err := a.ProjectionName.Accept(visitor); err != nil {
+		return err
+	}
+	if a.PartitionExpr != nil {
+		if err := a.PartitionExpr.Accept(visitor); err != nil {
+			return err
+		}
+	}
+	return visitor.VisitAlterTableClearProjection(a)
+}
+
 type AlterTableRenameColumn struct {
 	RenamePos Pos
 
diff --git a/parser/ast_visitor.go b/parser/ast_visitor.go
index 28af596..658dade 100644
--- a/parser/ast_visitor.go
+++ b/parser/ast_visitor.go
@@ -20,6 +20,7 @@ type ASTVisitor interface {
 	VisitAlterTableRemoveTTL(expr *AlterTableRemoveTTL) error
 	VisitAlterTableClearColumn(expr *AlterTableClearColumn) error
 	VisitAlterTableClearIndex(expr *AlterTableClearIndex) error
+	VisitAlterTableClearProjection(expr *AlterTableClearProjection) error
 	VisitAlterTableRenameColumn(expr *AlterTableRenameColumn) error
 	VisitAlterTableModifyTTL(expr *AlterTableModifyTTL) error
 	VisitAlterTableModifyColumn(expr *AlterTableModifyColumn) error
@@ -299,6 +300,13 @@ func (v *DefaultASTVisitor) VisitAlterTableClearIndex(expr *AlterTableClearIndex
 	return nil
 }
 
+func (v *DefaultASTVisitor) VisitAlterTableClearProjection(expr *AlterTableClearProjection) error {
+	if v.Visit != nil {
+		return v.Visit(expr)
+	}
+	return nil
+}
+
 func (v *DefaultASTVisitor) VisitAlterTableRenameColumn(expr *AlterTableRenameColumn) error {
 	if v.Visit != nil {
 		return v.Visit(expr)
diff --git a/parser/parser_alter.go b/parser/parser_alter.go
index 91e542b..9dc2e19 100644
--- a/parser/parser_alter.go
+++ b/parser/parser_alter.go
@@ -501,34 +501,34 @@ func (p *Parser) parseAlterTableClear(pos Pos) (AlterTableExpr, error) {
 	if err := p.consumeKeyword(KeywordClear); err != nil {
 		return nil, err
 	}
+	return p.parseAlterTableClearStatement(pos)
+}
 
+// Syntax: ALTER TABLE CLEAR COLUMN|INDEX|PROJECTION (IF EXISTS)? nestedIdentifier (IN partitionClause)?
+func (p *Parser) parseAlterTableClearStatement(pos Pos) (AlterTableExpr, error) {
+	var kind string
 	switch {
 	case p.matchKeyword(KeywordColumn):
-		return p.parseAlterTableClearColumn(pos)
+		kind = KeywordColumn
 	case p.matchKeyword(KeywordIndex):
-		return p.parseAlterTableClearIndex(pos)
-
+		kind = KeywordIndex
+	case p.matchKeyword(KeywordProjection):
+		kind = KeywordProjection
 	default:
-		return nil, errors.New("expected token: COLUMN|INDEX|PROJECTION")
-	}
-}
-
-// Syntax: ALTER TABLE CLEAR COLUMN (IF EXISTS)? nestedIdentifier (IN partitionClause)?
-func (p *Parser) parseAlterTableClearColumn(pos Pos) (AlterTableExpr, error) {
-	if err := p.consumeKeyword(KeywordColumn); err != nil {
-		return nil, err
+		return nil, fmt.Errorf("expected keyword: COLUMN|INDEX|PROJECTION, but got %q", p.lastTokenKind())
 	}
+	_ = p.lexer.consumeToken()
 
 	ifExists, err := p.tryParseIfExists()
 	if err != nil {
 		return nil, err
 	}
 
-	columnName, err := p.ParseNestedIdentifier(p.Pos())
+	name, err := p.ParseNestedIdentifier(p.Pos())
 	if err != nil {
 		return nil, err
 	}
-	statementEnd := columnName.End()
+	statementEnd := name.End()
 
 	var partitionExpr *PartitionExpr
 	if p.tryConsumeKeyword(KeywordIn) != nil {
@@ -541,50 +541,31 @@ func (p *Parser) parseAlterTableClearColumn(pos Pos) (AlterTableExpr, error) {
 		}
 	}
 
-	return &AlterTableClearColumn{
-		ClearPos:      pos,
-		StatementEnd:  statementEnd,
-		IfExists:      ifExists,
-		ColumnName:    columnName,
-		PartitionExpr: partitionExpr,
-	}, nil
-}
-
-// Syntax: ALTER TABLE CLEAR INDEX (IF EXISTS)? nestedIdentifier (IN partitionClause)?
-func (p *Parser) parseAlterTableClearIndex(pos Pos) (AlterTableExpr, error) {
-	if err := p.consumeKeyword(KeywordIndex); err != nil {
-		return nil, err
-	}
-
-	ifExists, err := p.tryParseIfExists()
-	if err != nil {
-		return nil, err
-	}
-
-	indexName, err := p.ParseNestedIdentifier(p.Pos())
-	if err != nil {
-		return nil, err
-	}
-	statementEnd := indexName.End()
-
-	var partitionExpr *PartitionExpr
-	if p.tryConsumeKeyword(KeywordIn) != nil {
-		partitionExpr, err = p.tryParsePartitionExpr(p.Pos())
-		if err != nil {
-			return nil, err
-		}
-		if partitionExpr != nil {
-			statementEnd = partitionExpr.End()
-		}
+	if kind == KeywordProjection {
+		return &AlterTableClearProjection{
+			ClearPos:       pos,
+			StatementEnd:   statementEnd,
+			IfExists:       ifExists,
+			ProjectionName: name,
+			PartitionExpr:  partitionExpr,
+		}, nil
+	} else if kind == KeywordColumn {
+		return &AlterTableClearColumn{
+			ClearPos:      pos,
+			StatementEnd:  statementEnd,
+			IfExists:      ifExists,
+			ColumnName:    name,
+			PartitionExpr: partitionExpr,
+		}, nil
+	} else {
+		return &AlterTableClearIndex{
+			ClearPos:      pos,
+			StatementEnd:  statementEnd,
+			IfExists:      ifExists,
+			IndexName:     name,
+			PartitionExpr: partitionExpr,
+		}, nil
 	}
-
-	return &AlterTableClearIndex{
-		ClearPos:      pos,
-		StatementEnd:  statementEnd,
-		IfExists:      ifExists,
-		IndexName:     indexName,
-		PartitionExpr: partitionExpr,
-	}, nil
 }
 
 // Syntax: ALTER TABLE RENAME COLUMN (IF EXISTS)? nestedIdentifier TO nestedIdentifier


Test patch:
diff --git a/parser/testdata/ddl/alter_table_clear_projection.sql b/parser/testdata/ddl/alter_table_clear_projection.sql
new file mode 100644
index 0000000..b62573a
--- /dev/null
+++ b/parser/testdata/ddl/alter_table_clear_projection.sql
@@ -0,0 +1,1 @@
+ALTER TABLE my_table CLEAR PROJECTION hello IN PARTITION partition_name;
\ No newline at end of file
diff --git a/parser/testdata/ddl/format/alter_table_clear_projection.sql b/parser/testdata/ddl/format/alter_table_clear_projection.sql
new file mode 100644
index 0000000..8ea066c
--- /dev/null
+++ b/parser/testdata/ddl/format/alter_table_clear_projection.sql
@@ -0,0 +1,7 @@
+-- Origin SQL:
+ALTER TABLE my_table CLEAR PROJECTION hello IN PARTITION partition_name;
+
+-- Format SQL:
+ALTER TABLE my_table
+CLEAR PROJECTION hello
+IN PARTITION partition_name;
diff --git a/parser/testdata/ddl/output/alter_table_clear_projection.sql.golden.json b/parser/testdata/ddl/output/alter_table_clear_projection.sql.golden.json
new file mode 100644
index 0000000..19d75bb
--- /dev/null
+++ b/parser/testdata/ddl/output/alter_table_clear_projection.sql.golden.json
@@ -0,0 +1,43 @@
+[
+  {
+    "AlterPos": 0,
+    "StatementEnd": 71,
+    "TableIdentifier": {
+      "Database": null,
+      "Table": {
+        "Name": "my_table",
+        "QuoteType": 1,
+        "NamePos": 12,
+        "NameEnd": 20
+      }
+    },
+    "OnCluster": null,
+    "AlterExprs": [
+      {
+        "ClearPos": 21,
+        "StatementEnd": 71,
+        "IfExists": false,
+        "ProjectionName": {
+          "Ident": {
+            "Name": "hello",
+            "QuoteType": 1,
+            "NamePos": 38,
+            "NameEnd": 43
+          },
+          "DotIdent": null
+        },
+        "PartitionExpr": {
+          "PartitionPos": 47,
+          "Expr": {
+            "Name": "partition_name",
+            "QuoteType": 1,
+            "NamePos": 57,
+            "NameEnd": 71
+          },
+          "ID": null,
+          "All": false
+        }
+      }
+    ]
+  }
+]
\ No newline at end of file