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 +} 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 @@ +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