Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit f6ff425

Browse files
Parse and analyze DROP VIEW statements
Signed-off-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
1 parent fc97414 commit f6ff425

File tree

7 files changed

+334
-0
lines changed

7 files changed

+334
-0
lines changed

sql/analyzer/assign_catalog.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func assignCatalog(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
6464
nc := *node
6565
nc.Catalog = a.Catalog
6666
return &nc, nil
67+
case *plan.DropView:
68+
nc := *node
69+
nc.Catalog = a.Catalog
70+
return &nc, nil
6771
default:
6872
return n, nil
6973
}

sql/analyzer/assign_catalog_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,10 @@ func TestAssignCatalog(t *testing.T) {
8181
cv, ok := node.(*plan.CreateView)
8282
require.True(ok)
8383
require.Equal(c, cv.Catalog)
84+
85+
node, err = f.Apply(sql.NewEmptyContext(), a, plan.NewDropView(nil, false))
86+
require.NoError(err)
87+
dv, ok := node.(*plan.DropView)
88+
require.True(ok)
89+
require.Equal(c, dv.Catalog)
8490
}

sql/parse/parse.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var (
3737
describeTablesRegex = regexp.MustCompile(`^(describe|desc)\s+table\s+(.*)`)
3838
createIndexRegex = regexp.MustCompile(`^create\s+index\s+`)
3939
createViewRegex = regexp.MustCompile(`^create\s+(or\s+replace\s+)?view\s+`)
40+
dropViewRegex = regexp.MustCompile(`^drop\s+(if\s+exists\s+)?view\s+`)
4041
dropIndexRegex = regexp.MustCompile(`^drop\s+index\s+`)
4142
showIndexRegex = regexp.MustCompile(`^show\s+(index|indexes|keys)\s+(from|in)\s+\S+\s*`)
4243
showCreateRegex = regexp.MustCompile(`^show create\s+\S+\s*`)
@@ -84,6 +85,8 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
8485
return parseCreateIndex(ctx, s)
8586
case createViewRegex.MatchString(lowerQuery):
8687
return parseCreateView(ctx, s)
88+
case dropViewRegex.MatchString(lowerQuery):
89+
return parseDropView(ctx, s)
8790
case dropIndexRegex.MatchString(lowerQuery):
8891
return parseDropIndex(s)
8992
case showIndexRegex.MatchString(lowerQuery):

sql/parse/util.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,56 @@ func maybeList(opening, separator, closing rune, list *[]string) parseFunc {
516516
}
517517
}
518518
}
519+
520+
type QualifiedName struct {
521+
db string
522+
name string
523+
}
524+
525+
func readQualifiedIdentifierList(list *[]QualifiedName) parseFunc {
526+
return func(rd *bufio.Reader) error {
527+
for {
528+
var newItem []string
529+
err := parseFuncs{
530+
skipSpaces,
531+
readIdentList('.', &newItem),
532+
skipSpaces,
533+
}.exec(rd)
534+
535+
if err != nil {
536+
return err
537+
}
538+
539+
if len(newItem) < 1 || len(newItem) > 2 {
540+
return errUnexpectedSyntax.New("[db_name.]viewName", strings.Join(newItem, "."))
541+
}
542+
543+
var db, name string
544+
545+
if len(newItem) == 1 {
546+
db = ""
547+
name = newItem[0]
548+
} else {
549+
db = newItem[0]
550+
name = newItem[1]
551+
}
552+
553+
*list = append(*list, QualifiedName{db, name})
554+
555+
r, _, err := rd.ReadRune()
556+
if err != nil {
557+
if err == io.EOF {
558+
return nil
559+
}
560+
return err
561+
}
562+
563+
switch r {
564+
case ',':
565+
continue
566+
default:
567+
return rd.UnreadRune()
568+
}
569+
}
570+
}
571+
}

sql/parse/views.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
var ErrMalformedViewName = errors.NewKind("the view name '%s' is not correct")
1515
var ErrMalformedCreateView = errors.NewKind("view definition %#v is not a SELECT query")
16+
var ErrViewsToDropNotFound = errors.NewKind("the list of views to drop must contain at least one view")
1617

1718
// parseCreateView parses
1819
// CREATE [OR REPLACE] VIEW [db_name.]view_name AS select_statement
@@ -87,3 +88,48 @@ func parseCreateView(ctx *sql.Context, s string) (sql.Node, error) {
8788
sql.UnresolvedDatabase(databaseName), viewName, columns, subqueryAlias, isReplace,
8889
), nil
8990
}
91+
92+
// parseDropView parses
93+
// DROP VIEW [IF EXISTS] [db_name1.]view_name1 [, [db_name2.]view_name2, ...]
94+
// [RESTRICT] [CASCADE]
95+
// and returns a DropView node in case of success. As per MySQL specification,
96+
// RESTRICT and CASCADE, if given, are parsed and ignored.
97+
func parseDropView(ctx *sql.Context, s string) (sql.Node, error) {
98+
r := bufio.NewReader(strings.NewReader(s))
99+
100+
var (
101+
views []QualifiedName
102+
ifExists bool
103+
unusedBool bool
104+
)
105+
106+
err := parseFuncs{
107+
expect("drop"),
108+
skipSpaces,
109+
expect("view"),
110+
skipSpaces,
111+
multiMaybe(&ifExists, "if", "exists"),
112+
skipSpaces,
113+
readQualifiedIdentifierList(&views),
114+
skipSpaces,
115+
maybe(&unusedBool, "restrict"),
116+
skipSpaces,
117+
maybe(&unusedBool, "cascade"),
118+
checkEOF,
119+
}.exec(r)
120+
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
if len(views) < 1 {
126+
return nil, ErrViewsToDropNotFound.New()
127+
}
128+
129+
plans := make([]sql.Node, len(views))
130+
for i, view := range views {
131+
plans[i] = plan.NewSingleDropView(sql.UnresolvedDatabase(view.db), view.name)
132+
}
133+
134+
return plan.NewDropView(plans, ifExists), nil
135+
}

sql/parse/views_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,77 @@ func TestParseCreateView(t *testing.T) {
7575
})
7676
}
7777
}
78+
79+
func TestParseDropView(t *testing.T) {
80+
var fixtures = map[string]sql.Node{
81+
`DROP VIEW view1`: plan.NewDropView(
82+
[]sql.Node{plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view1")},
83+
false,
84+
),
85+
`DROP VIEW view1, view2`: plan.NewDropView(
86+
[]sql.Node{
87+
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view1"),
88+
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view2"),
89+
},
90+
false,
91+
),
92+
`DROP VIEW db1.view1`: plan.NewDropView(
93+
[]sql.Node{
94+
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
95+
},
96+
false,
97+
),
98+
`DROP VIEW db1.view1, view2`: plan.NewDropView(
99+
[]sql.Node{
100+
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
101+
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view2"),
102+
},
103+
false,
104+
),
105+
`DROP VIEW view1, db2.view2`: plan.NewDropView(
106+
[]sql.Node{
107+
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view1"),
108+
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
109+
},
110+
false,
111+
),
112+
`DROP VIEW db1.view1, db2.view2`: plan.NewDropView(
113+
[]sql.Node{
114+
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
115+
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
116+
},
117+
false,
118+
),
119+
`DROP VIEW IF EXISTS myview`: plan.NewDropView(
120+
[]sql.Node{plan.NewSingleDropView(sql.UnresolvedDatabase(""), "myview")},
121+
true,
122+
),
123+
`DROP VIEW IF EXISTS db1.view1, db2.view2`: plan.NewDropView(
124+
[]sql.Node{
125+
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
126+
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
127+
},
128+
true,
129+
),
130+
`DROP VIEW IF EXISTS db1.view1, db2.view2 RESTRICT CASCADE`: plan.NewDropView(
131+
[]sql.Node{
132+
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
133+
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
134+
},
135+
true,
136+
),
137+
}
138+
139+
for query, expectedPlan := range fixtures {
140+
t.Run(query, func(t *testing.T) {
141+
require := require.New(t)
142+
143+
ctx := sql.NewEmptyContext()
144+
lowerquery := strings.ToLower(query)
145+
result, err := parseDropView(ctx, lowerquery)
146+
147+
require.NoError(err)
148+
require.Equal(expectedPlan, result)
149+
})
150+
}
151+
}

sql/plan/drop_view.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package plan
2+
3+
import (
4+
"github.com/src-d/go-mysql-server/sql"
5+
errors "gopkg.in/src-d/go-errors.v1"
6+
)
7+
8+
var errDropViewChild = errors.NewKind("any child of DropView must be of type SingleDropView")
9+
10+
type SingleDropView struct {
11+
database sql.Database
12+
viewName string
13+
}
14+
15+
// NewSingleDropView creates a SingleDropView.
16+
func NewSingleDropView(
17+
database sql.Database,
18+
viewName string,
19+
) *SingleDropView {
20+
return &SingleDropView{database, viewName}
21+
}
22+
23+
// Children implements the Node interface. It always returns nil.
24+
func (dv *SingleDropView) Children() []sql.Node {
25+
return nil
26+
}
27+
28+
// Resolved implements the Node interface. This node is resolved if and only if
29+
// its database is resolved.
30+
func (dv *SingleDropView) Resolved() bool {
31+
_, ok := dv.database.(sql.UnresolvedDatabase)
32+
return !ok
33+
}
34+
35+
// RowIter implements the Node interface. It always returns an empty iterator.
36+
func (dv *SingleDropView) RowIter(ctx *sql.Context) (sql.RowIter, error) {
37+
return sql.RowsToRowIter(), nil
38+
}
39+
40+
// Schema implements the Node interface. It always returns nil.
41+
func (dv *SingleDropView) Schema() sql.Schema { return nil }
42+
43+
// String implements the fmt.Stringer interface, using sql.TreePrinter to
44+
// generate the string.
45+
func (dv *SingleDropView) String() string {
46+
pr := sql.NewTreePrinter()
47+
_ = pr.WriteNode("SingleDropView(%s.%s)", dv.database.Name(), dv.viewName)
48+
49+
return pr.String()
50+
}
51+
52+
// WithChildren implements the Node interface. It only succeeds if the length
53+
// of the specified children equals 0.
54+
func (dv *SingleDropView) WithChildren(children ...sql.Node) (sql.Node, error) {
55+
if len(children) != 0 {
56+
return nil, sql.ErrInvalidChildrenNumber.New(dv, len(children), 0)
57+
}
58+
59+
return dv, nil
60+
}
61+
62+
// Database implements the Databaser interfacee. It returns the node's database.
63+
func (dv *SingleDropView) Database() sql.Database {
64+
return dv.database
65+
}
66+
67+
// Database implements the Databaser interface, and it returns a copy of this
68+
// node with the specified database.
69+
func (dv *SingleDropView) WithDatabase(database sql.Database) (sql.Node, error) {
70+
newDrop := *dv
71+
newDrop.database = database
72+
return &newDrop, nil
73+
}
74+
75+
// DropView is a node representing the removal of a list of views, defined by
76+
// the children member. The flag ifExists represents whether the user wants the
77+
// node to fail if any of the views in children does not exist.
78+
type DropView struct {
79+
children []sql.Node
80+
Catalog *sql.Catalog
81+
ifExists bool
82+
}
83+
84+
// NewDropView creates a DropView node with the specified parameters,
85+
// setting its catalog to nil.
86+
func NewDropView(children []sql.Node, ifExists bool) *DropView {
87+
return &DropView{children, nil, ifExists}
88+
}
89+
90+
// Children implements the Node interface. It returns the children of the
91+
// CreateView node; i.e., all the views that will be dropped.
92+
func (dvs *DropView) Children() []sql.Node {
93+
return dvs.children
94+
}
95+
96+
// Resolved implements the Node interface. This node is resolved if and only if
97+
// all of its children are resolved.
98+
func (dvs *DropView) Resolved() bool {
99+
for _, child := range dvs.children {
100+
if !child.Resolved() {
101+
return false
102+
}
103+
}
104+
return true
105+
}
106+
107+
// RowIter implements the Node interface. When executed, this function drops
108+
// all the views defined by the node's children. It errors if the flag ifExists
109+
// is set to false and there is some view that does not exist.
110+
func (dvs *DropView) RowIter(ctx *sql.Context) (sql.RowIter, error) {
111+
viewList := make([]sql.ViewKey, len(dvs.children))
112+
for i, child := range dvs.children {
113+
drop, ok := child.(*SingleDropView)
114+
if !ok {
115+
return sql.RowsToRowIter(), errDropViewChild.New()
116+
}
117+
118+
viewList[i] = sql.NewViewKey(drop.database.Name(), drop.viewName)
119+
}
120+
121+
return sql.RowsToRowIter(), dvs.Catalog.ViewRegistry.DeleteList(viewList, !dvs.ifExists)
122+
}
123+
124+
// Schema implements the Node interface. It always returns nil.
125+
func (dvs *DropView) Schema() sql.Schema { return nil }
126+
127+
// String implements the fmt.Stringer interface, using sql.TreePrinter to
128+
// generate the string.
129+
func (dvs *DropView) String() string {
130+
childrenStrings := make([]string, len(dvs.children))
131+
for i, child := range dvs.children {
132+
childrenStrings[i] = child.String()
133+
}
134+
135+
pr := sql.NewTreePrinter()
136+
_ = pr.WriteNode("DropView")
137+
_ = pr.WriteChildren(childrenStrings...)
138+
139+
return pr.String()
140+
}
141+
142+
// WithChildren implements the Node interface. It always suceeds, returning a
143+
// copy of this node with the new array of nodes as children.
144+
func (dvs *DropView) WithChildren(children ...sql.Node) (sql.Node, error) {
145+
newDrop := dvs
146+
newDrop.children = children
147+
return newDrop, nil
148+
}

0 commit comments

Comments
 (0)