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

Add support for DROP VIEW #860

Open
wants to merge 7 commits into
base: feature/views
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Parse and analyze DROP VIEW statements
Signed-off-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
  • Loading branch information
agarciamontoro committed Oct 28, 2019
commit f6ff42503eb08bf4511e623f58e679a998361367
4 changes: 4 additions & 0 deletions sql/analyzer/assign_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ func assignCatalog(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
nc := *node
nc.Catalog = a.Catalog
return &nc, nil
case *plan.DropView:
nc := *node
nc.Catalog = a.Catalog
return &nc, nil
default:
return n, nil
}
Expand Down
6 changes: 6 additions & 0 deletions sql/analyzer/assign_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,10 @@ func TestAssignCatalog(t *testing.T) {
cv, ok := node.(*plan.CreateView)
require.True(ok)
require.Equal(c, cv.Catalog)

node, err = f.Apply(sql.NewEmptyContext(), a, plan.NewDropView(nil, false))
require.NoError(err)
dv, ok := node.(*plan.DropView)
require.True(ok)
require.Equal(c, dv.Catalog)
}
3 changes: 3 additions & 0 deletions sql/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
describeTablesRegex = regexp.MustCompile(`^(describe|desc)\s+table\s+(.*)`)
createIndexRegex = regexp.MustCompile(`^create\s+index\s+`)
createViewRegex = regexp.MustCompile(`^create\s+(or\s+replace\s+)?view\s+`)
dropViewRegex = regexp.MustCompile(`^drop\s+(if\s+exists\s+)?view\s+`)
dropIndexRegex = regexp.MustCompile(`^drop\s+index\s+`)
showIndexRegex = regexp.MustCompile(`^show\s+(index|indexes|keys)\s+(from|in)\s+\S+\s*`)
showCreateRegex = regexp.MustCompile(`^show create\s+\S+\s*`)
Expand Down Expand Up @@ -84,6 +85,8 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
return parseCreateIndex(ctx, s)
case createViewRegex.MatchString(lowerQuery):
return parseCreateView(ctx, s)
case dropViewRegex.MatchString(lowerQuery):
return parseDropView(ctx, s)
case dropIndexRegex.MatchString(lowerQuery):
return parseDropIndex(s)
case showIndexRegex.MatchString(lowerQuery):
Expand Down
53 changes: 53 additions & 0 deletions sql/parse/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,56 @@ func maybeList(opening, separator, closing rune, list *[]string) parseFunc {
}
}
}

type QualifiedName struct {
db string
name string
}

func readQualifiedIdentifierList(list *[]QualifiedName) parseFunc {
return func(rd *bufio.Reader) error {
for {
var newItem []string
err := parseFuncs{
skipSpaces,
readIdentList('.', &newItem),
skipSpaces,
}.exec(rd)

if err != nil {
return err
}

if len(newItem) < 1 || len(newItem) > 2 {
return errUnexpectedSyntax.New("[db_name.]viewName", strings.Join(newItem, "."))
}

var db, name string

if len(newItem) == 1 {
db = ""
name = newItem[0]
} else {
db = newItem[0]
name = newItem[1]
}

*list = append(*list, QualifiedName{db, name})

r, _, err := rd.ReadRune()
if err != nil {
if err == io.EOF {
return nil
}
return err
}

switch r {
case ',':
continue
default:
return rd.UnreadRune()
}
}
}
}
46 changes: 46 additions & 0 deletions sql/parse/views.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

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

// parseCreateView parses
// CREATE [OR REPLACE] VIEW [db_name.]view_name AS select_statement
Expand Down Expand Up @@ -87,3 +88,48 @@ func parseCreateView(ctx *sql.Context, s string) (sql.Node, error) {
sql.UnresolvedDatabase(databaseName), viewName, columns, subqueryAlias, isReplace,
), nil
}

// parseDropView parses
// DROP VIEW [IF EXISTS] [db_name1.]view_name1 [, [db_name2.]view_name2, ...]
// [RESTRICT] [CASCADE]
// and returns a DropView node in case of success. As per MySQL specification,
// RESTRICT and CASCADE, if given, are parsed and ignored.
func parseDropView(ctx *sql.Context, s string) (sql.Node, error) {
r := bufio.NewReader(strings.NewReader(s))

var (
views []QualifiedName
ifExists bool
unusedBool bool
)

err := parseFuncs{
expect("drop"),
skipSpaces,
expect("view"),
skipSpaces,
multiMaybe(&ifExists, "if", "exists"),
skipSpaces,
readQualifiedIdentifierList(&views),
skipSpaces,
maybe(&unusedBool, "restrict"),
skipSpaces,
maybe(&unusedBool, "cascade"),
checkEOF,
}.exec(r)

if err != nil {
return nil, err
}

if len(views) < 1 {
return nil, ErrViewsToDropNotFound.New()
}

plans := make([]sql.Node, len(views))
for i, view := range views {
plans[i] = plan.NewSingleDropView(sql.UnresolvedDatabase(view.db), view.name)
}

return plan.NewDropView(plans, ifExists), nil
}
74 changes: 74 additions & 0 deletions sql/parse/views_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,77 @@ func TestParseCreateView(t *testing.T) {
})
}
}

func TestParseDropView(t *testing.T) {
var fixtures = map[string]sql.Node{
`DROP VIEW view1`: plan.NewDropView(
[]sql.Node{plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view1")},
false,
),
`DROP VIEW view1, view2`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view1"),
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view2"),
},
false,
),
`DROP VIEW db1.view1`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
},
false,
),
`DROP VIEW db1.view1, view2`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view2"),
},
false,
),
`DROP VIEW view1, db2.view2`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase(""), "view1"),
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
},
false,
),
`DROP VIEW db1.view1, db2.view2`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
},
false,
),
`DROP VIEW IF EXISTS myview`: plan.NewDropView(
[]sql.Node{plan.NewSingleDropView(sql.UnresolvedDatabase(""), "myview")},
true,
),
`DROP VIEW IF EXISTS db1.view1, db2.view2`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
},
true,
),
`DROP VIEW IF EXISTS db1.view1, db2.view2 RESTRICT CASCADE`: plan.NewDropView(
[]sql.Node{
plan.NewSingleDropView(sql.UnresolvedDatabase("db1"), "view1"),
plan.NewSingleDropView(sql.UnresolvedDatabase("db2"), "view2"),
},
true,
),
}

for query, expectedPlan := range fixtures {
t.Run(query, func(t *testing.T) {
require := require.New(t)

ctx := sql.NewEmptyContext()
lowerquery := strings.ToLower(query)
result, err := parseDropView(ctx, lowerquery)

require.NoError(err)
require.Equal(expectedPlan, result)
})
}
}
148 changes: 148 additions & 0 deletions sql/plan/drop_view.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package plan

import (
"github.com/src-d/go-mysql-server/sql"
errors "gopkg.in/src-d/go-errors.v1"
)

var errDropViewChild = errors.NewKind("any child of DropView must be of type SingleDropView")

type SingleDropView struct {
database sql.Database
viewName string
}

// NewSingleDropView creates a SingleDropView.
func NewSingleDropView(
database sql.Database,
viewName string,
) *SingleDropView {
return &SingleDropView{database, viewName}
}

// Children implements the Node interface. It always returns nil.
func (dv *SingleDropView) Children() []sql.Node {
return nil
}

// Resolved implements the Node interface. This node is resolved if and only if
// its database is resolved.
func (dv *SingleDropView) Resolved() bool {
_, ok := dv.database.(sql.UnresolvedDatabase)
return !ok
}

// RowIter implements the Node interface. It always returns an empty iterator.
func (dv *SingleDropView) RowIter(ctx *sql.Context) (sql.RowIter, error) {
return sql.RowsToRowIter(), nil
}

// Schema implements the Node interface. It always returns nil.
func (dv *SingleDropView) Schema() sql.Schema { return nil }

// String implements the fmt.Stringer interface, using sql.TreePrinter to
// generate the string.
func (dv *SingleDropView) String() string {
pr := sql.NewTreePrinter()
_ = pr.WriteNode("SingleDropView(%s.%s)", dv.database.Name(), dv.viewName)

return pr.String()
}

// WithChildren implements the Node interface. It only succeeds if the length
// of the specified children equals 0.
func (dv *SingleDropView) WithChildren(children ...sql.Node) (sql.Node, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(dv, len(children), 0)
}

return dv, nil
}

// Database implements the Databaser interfacee. It returns the node's database.
func (dv *SingleDropView) Database() sql.Database {
return dv.database
}

// Database implements the Databaser interface, and it returns a copy of this
// node with the specified database.
func (dv *SingleDropView) WithDatabase(database sql.Database) (sql.Node, error) {
newDrop := *dv
newDrop.database = database
return &newDrop, nil
}

// DropView is a node representing the removal of a list of views, defined by
// the children member. The flag ifExists represents whether the user wants the
// node to fail if any of the views in children does not exist.
type DropView struct {
children []sql.Node
Catalog *sql.Catalog
ifExists bool
}

// NewDropView creates a DropView node with the specified parameters,
// setting its catalog to nil.
func NewDropView(children []sql.Node, ifExists bool) *DropView {
return &DropView{children, nil, ifExists}
}

// Children implements the Node interface. It returns the children of the
// CreateView node; i.e., all the views that will be dropped.
func (dvs *DropView) Children() []sql.Node {
return dvs.children
}

// Resolved implements the Node interface. This node is resolved if and only if
// all of its children are resolved.
func (dvs *DropView) Resolved() bool {
for _, child := range dvs.children {
if !child.Resolved() {
return false
}
}
return true
}

// RowIter implements the Node interface. When executed, this function drops
// all the views defined by the node's children. It errors if the flag ifExists
// is set to false and there is some view that does not exist.
func (dvs *DropView) RowIter(ctx *sql.Context) (sql.RowIter, error) {
viewList := make([]sql.ViewKey, len(dvs.children))
for i, child := range dvs.children {
drop, ok := child.(*SingleDropView)
if !ok {
return sql.RowsToRowIter(), errDropViewChild.New()
}

viewList[i] = sql.NewViewKey(drop.database.Name(), drop.viewName)
}

return sql.RowsToRowIter(), dvs.Catalog.ViewRegistry.DeleteList(viewList, !dvs.ifExists)
}

// Schema implements the Node interface. It always returns nil.
func (dvs *DropView) Schema() sql.Schema { return nil }

// String implements the fmt.Stringer interface, using sql.TreePrinter to
// generate the string.
func (dvs *DropView) String() string {
childrenStrings := make([]string, len(dvs.children))
for i, child := range dvs.children {
childrenStrings[i] = child.String()
}

pr := sql.NewTreePrinter()
_ = pr.WriteNode("DropView")
_ = pr.WriteChildren(childrenStrings...)

return pr.String()
}

// WithChildren implements the Node interface. It always suceeds, returning a
// copy of this node with the new array of nodes as children.
func (dvs *DropView) WithChildren(children ...sql.Node) (sql.Node, error) {
newDrop := dvs
newDrop.children = children
return newDrop, nil
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy