Repository: AfterShip/clickhouse-sql-parser
Pull request #4 (2023-09-25 01:50:02)

Add the support of DELETE FROM expression


Patch:
diff --git a/parser/ast.go b/parser/ast.go
index b26bdea..2366972 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -3250,3 +3250,34 @@ func (s *SampleRatioExpr) String(level int) string {
 	}
 	return builder.String()
 }
+
+type DeleteFromExpr struct {
+	DeletePos Pos
+	Table     *TableIdentifier
+	OnCluster *OnClusterExpr
+	WhereExpr Expr
+}
+
+func (d *DeleteFromExpr) Pos() Pos {
+	return d.DeletePos
+}
+
+func (d *DeleteFromExpr) End() Pos {
+	return d.WhereExpr.End()
+}
+
+func (d *DeleteFromExpr) String(level int) string {
+	var builder strings.Builder
+	builder.WriteString("DELETE FROM ")
+	builder.WriteString(d.Table.String(level))
+	if d.OnCluster != nil {
+		builder.WriteString(NewLine(level))
+		builder.WriteString(d.OnCluster.String(level))
+	}
+	if d.WhereExpr != nil {
+		builder.WriteString(NewLine(level))
+		builder.WriteString("WHERE ")
+		builder.WriteString(d.WhereExpr.String(level))
+	}
+	return builder.String()
+}
diff --git a/parser/parser_table.go b/parser/parser_table.go
index 22fc697..2e09123 100644
--- a/parser/parser_table.go
+++ b/parser/parser_table.go
@@ -894,6 +894,8 @@ func (p *Parser) parseStatement(pos Pos) (Expr, error) {
 		expr, err = p.parseDDL(pos)
 	case p.matchKeyword(KeywordSelect), p.matchKeyword(KeywordWith):
 		expr, err = p.parseSelectQuery(pos)
+	case p.matchKeyword(KeywordDelete):
+		expr, err = p.parseDeleteFrom(pos)
 	case p.matchKeyword(KeywordUse):
 		expr, err = p.parseUseStatement(pos)
 	case p.matchKeyword(KeywordSet):
@@ -996,3 +998,35 @@ func (p *Parser) parseTruncateTable(pos Pos) (*TruncateTable, error) {
 
 	return truncateTable, nil
 }
+
+func (p *Parser) parseDeleteFrom(pos Pos) (*DeleteFromExpr, error) {
+	if err := p.consumeKeyword(KeywordDelete); err != nil {
+		return nil, err
+	}
+	if err := p.consumeKeyword(KeywordFrom); err != nil {
+		return nil, err
+	}
+	tableIdentifier, err := p.parseTableIdentifier(p.Pos())
+	if err != nil {
+		return nil, err
+	}
+	onCluster, err := p.tryParseOnCluster(p.Pos())
+	if err != nil {
+		return nil, err
+	}
+
+	if err := p.consumeKeyword(KeywordWhere); err != nil {
+		return nil, err
+	}
+	whereExpr, err := p.parseExpr(p.Pos())
+	if err != nil {
+		return nil, err
+	}
+
+	return &DeleteFromExpr{
+		DeletePos: pos,
+		Table:     tableIdentifier,
+		OnCluster: onCluster,
+		WhereExpr: whereExpr,
+	}, nil
+}


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 4474048..8b494aa 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -56,7 +56,7 @@ func TestParser_Compatible(t *testing.T) {
 }
 
 func TestParser_ParseStatements(t *testing.T) {
-	for _, dir := range []string{"./testdata/ddl", "./testdata/query", "./testdata/basic"} {
+	for _, dir := range []string{"./testdata/dml", "./testdata/ddl", "./testdata/query", "./testdata/basic"} {
 		outputDir := dir + "/output"
 		entries, err := os.ReadDir(dir)
 		if err != nil {
@@ -87,7 +87,7 @@ func TestParser_ParseStatements(t *testing.T) {
 }
 
 func TestParser_Format(t *testing.T) {
-	for _, dir := range []string{"./testdata/ddl", "./testdata/query", "./testdata/basic"} {
+	for _, dir := range []string{"./testdata/dml", "./testdata/ddl", "./testdata/query", "./testdata/basic"} {
 		outputDir := dir + "/format"
 
 		entries, err := os.ReadDir(dir)
diff --git a/parser/set_test.go b/parser/set_test.go
index 8cf0f1f..b71b26f 100644
--- a/parser/set_test.go
+++ b/parser/set_test.go
@@ -1,8 +1,9 @@
 package parser
 
 import (
-	"reflect"
 	"testing"
+
+	"github.com/stretchr/testify/require"
 )
 
 func TestSet(t *testing.T) {
@@ -28,8 +29,5 @@ func TestSet(t *testing.T) {
 		t.Errorf("Set should not contain 4")
 	}
 
-	expected := []int{1, 2, 3}
-	if !reflect.DeepEqual(s.Members(), expected) {
-		t.Errorf("Set members should be %v, but got %v", expected, s.Members())
-	}
+	require.EqualValues(t, []int{1, 2, 3}, s.Members())
 }
diff --git a/parser/testdata/dml/delete_from.sql b/parser/testdata/dml/delete_from.sql
new file mode 100644
index 0000000..497e4ff
--- /dev/null
+++ b/parser/testdata/dml/delete_from.sql
@@ -0,0 +1,1 @@
+DELETE FROM hits WHERE Title LIKE '%hello%';
diff --git a/parser/testdata/dml/format/delete_from.sql b/parser/testdata/dml/format/delete_from.sql
new file mode 100644
index 0000000..7b6d9eb
--- /dev/null
+++ b/parser/testdata/dml/format/delete_from.sql
@@ -0,0 +1,7 @@
+-- Origin SQL:
+DELETE FROM hits WHERE Title LIKE '%hello%';
+
+
+-- Format SQL:
+DELETE FROM hits
+WHERE Title LIKE '%hello%';
diff --git a/parser/testdata/dml/output/delete_from.sql.golden.json b/parser/testdata/dml/output/delete_from.sql.golden.json
new file mode 100644
index 0000000..f667d16
--- /dev/null
+++ b/parser/testdata/dml/output/delete_from.sql.golden.json
@@ -0,0 +1,30 @@
+[
+  {
+    "DeletePos": 0,
+    "Table": {
+      "Database": null,
+      "Table": {
+        "Name": "hits",
+        "Unquoted": false,
+        "NamePos": 12,
+        "NameEnd": 16
+      }
+    },
+    "OnCluster": null,
+    "WhereExpr": {
+      "LeftExpr": {
+        "Name": "Title",
+        "Unquoted": false,
+        "NamePos": 23,
+        "NameEnd": 28
+      },
+      "Operation": "LIKE",
+      "RightExpr": {
+        "LiteralPos": 35,
+        "LiteralEnd": 42,
+        "Literal": "%hello%"
+      },
+      "HasNot": false
+    }
+  }
+]
\ No newline at end of file