Skip to content

Commit d1ad601

Browse files
author
Aaron Son
committed
Merge remote-tracking branch 'agarciamontoro/feature.views.drop' into aaron/views
2 parents a217057 + 9b8ecfa commit d1ad601

File tree

11 files changed

+542
-33
lines changed

11 files changed

+542
-33
lines changed

engine.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (e *Engine) Query(
120120
case *plan.CreateIndex:
121121
typ = sql.CreateIndexProcess
122122
perm = auth.ReadPerm | auth.WritePerm
123-
case *plan.InsertInto, *plan.DeleteFrom, *plan.Update, *plan.DropIndex, *plan.UnlockTables, *plan.LockTables, *plan.CreateView:
123+
case *plan.InsertInto, *plan.DeleteFrom, *plan.Update, *plan.DropIndex, *plan.UnlockTables, *plan.LockTables, *plan.CreateView, *plan.DropView:
124124
perm = auth.ReadPerm | auth.WritePerm
125125
}
126126

engine_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3149,6 +3149,7 @@ func TestReadOnly(t *testing.T) {
31493149
`DROP INDEX foo ON mytable`,
31503150
`INSERT INTO mytable (i, s) VALUES(42, 'yolo')`,
31513151
`CREATE VIEW myview AS SELECT i FROM mytable`,
3152+
`DROP VIEW myview`,
31523153
}
31533154

31543155
for _, query := range writingQueries {

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@ func convertCreateView(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
414414
sql.UnresolvedDatabase(""), c.View.Name.String(), []string{}, queryAlias, c.OrReplace), nil
415415
}
416416

417+
func convertDropView(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
418+
plans := make([]sql.Node, len(c.FromViews))
419+
for i, v := range c.FromViews {
420+
plans[i] = plan.NewSingleDropView(sql.UnresolvedDatabase(""), v.Name.String())
421+
}
422+
return plan.NewDropView(plans, c.IfExists), nil
423+
}
424+
417425
func convertInsert(ctx *sql.Context, i *sqlparser.Insert) (sql.Node, error) {
418426
if len(i.OnDup) > 0 {
419427
return nil, ErrUnsupportedFeature.New("ON DUPLICATE KEY")

sql/parse/util.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,65 @@ func maybeList(opening, separator, closing rune, list *[]string) parseFunc {
516516
}
517517
}
518518
}
519+
520+
// A qualifiedName represents an identifier of type "db_name.table_name"
521+
type qualifiedName struct {
522+
qualifier string
523+
name string
524+
}
525+
526+
// readQualifiedIdentifierList reads a comma-separated list of qualifiedNames.
527+
// Any number of spaces between the qualified names are accepted. The qualifier
528+
// may be empty, in which case the period is optional.
529+
// An example of a correctly formed list is:
530+
// "my_db.myview, db_2.mytable , aTable"
531+
func readQualifiedIdentifierList(list *[]qualifiedName) parseFunc {
532+
return func(rd *bufio.Reader) error {
533+
for {
534+
var newItem []string
535+
err := parseFuncs{
536+
skipSpaces,
537+
readIdentList('.', &newItem),
538+
skipSpaces,
539+
}.exec(rd)
540+
541+
if err != nil {
542+
return err
543+
}
544+
545+
if len(newItem) < 1 || len(newItem) > 2 {
546+
return errUnexpectedSyntax.New(
547+
"[qualifier.]name",
548+
strings.Join(newItem, "."),
549+
)
550+
}
551+
552+
var qualifier, name string
553+
554+
if len(newItem) == 1 {
555+
qualifier = ""
556+
name = newItem[0]
557+
} else {
558+
qualifier = newItem[0]
559+
name = newItem[1]
560+
}
561+
562+
*list = append(*list, qualifiedName{qualifier, name})
563+
564+
r, _, err := rd.ReadRune()
565+
if err != nil {
566+
if err == io.EOF {
567+
return nil
568+
}
569+
return err
570+
}
571+
572+
switch r {
573+
case ',':
574+
continue
575+
default:
576+
return rd.UnreadRune()
577+
}
578+
}
579+
}
580+
}

sql/parse/util_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,60 @@ func TestReadSpaces(t *testing.T) {
465465
require.Equal(fixture.expectedRemaining, actualRemaining)
466466
}
467467
}
468+
469+
// Tests that readQualifiedIdentifierList correctly parses well-formed lists,
470+
// populating the list of identifiers, and that it errors with partial lists
471+
// and when it does not found any identifiers
472+
func TestReadQualifiedIdentifierList(t *testing.T) {
473+
require := require.New(t)
474+
475+
testFixtures := []struct {
476+
string string
477+
expectedList []qualifiedName
478+
expectedError bool
479+
expectedRemaining string
480+
}{
481+
{
482+
"my_db.myview, db_2.mytable , aTable",
483+
[]qualifiedName{{"my_db", "myview"}, {"db_2", "mytable"}, {"", "aTable"}},
484+
false,
485+
"",
486+
},
487+
{
488+
"single_identifier -remaining",
489+
[]qualifiedName{{"", "single_identifier"}},
490+
false,
491+
"-remaining",
492+
},
493+
{
494+
"",
495+
nil,
496+
true,
497+
"",
498+
},
499+
{
500+
"partial_list,",
501+
[]qualifiedName{{"", "partial_list"}},
502+
true,
503+
"",
504+
},
505+
}
506+
507+
for _, fixture := range testFixtures {
508+
reader := bufio.NewReader(strings.NewReader(fixture.string))
509+
var actualList []qualifiedName
510+
511+
err := readQualifiedIdentifierList(&actualList)(reader)
512+
513+
if fixture.expectedError {
514+
require.Error(err)
515+
} else {
516+
require.NoError(err)
517+
}
518+
519+
require.Equal(fixture.expectedList, actualList)
520+
521+
actualRemaining, _ := reader.ReadString('\n')
522+
require.Equal(fixture.expectedRemaining, actualRemaining)
523+
}
524+
}

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)