Skip to content

Add basic VIEWS support. #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Dec 2, 2019
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b820870
Parse CREATE VIEW statements
agarciamontoro Oct 17, 2019
079e419
Resolve Catalog in CreateView node
agarciamontoro Oct 18, 2019
06c7785
Fix maybeList so it does populate the passed list
agarciamontoro Oct 18, 2019
34a9f58
Add a registry for VIEWs to the catalog
agarciamontoro Oct 22, 2019
ef19c5f
Parse and analyze database-scoped names for views
agarciamontoro Oct 22, 2019
7de54c5
Plug views' definition when resolving tables
agarciamontoro Oct 22, 2019
4764cae
Document new util functions for parsing
agarciamontoro Oct 22, 2019
676f862
Ensure that keys in view registry are always lowercase
agarciamontoro Oct 23, 2019
0fd0480
Manage OR REPLACE cases in VIEW creation
agarciamontoro Oct 23, 2019
d71a9a5
Test new changes from VIEW implementation
agarciamontoro Oct 23, 2019
032fa6c
Improve View and ViewRegistry interfaces
agarciamontoro Oct 24, 2019
c2de021
Document and test VIEW-related changes
agarciamontoro Oct 24, 2019
9108273
Fix error returning in parsing function maybeList
agarciamontoro Oct 25, 2019
202d256
Make sure all errors are checked in VIEW-related tests
agarciamontoro Oct 25, 2019
d27b3d3
Rename readScopedIdent to readIdentList
agarciamontoro Oct 25, 2019
af59652
Modify VIEW-related comments to adhere to Go conventions
agarciamontoro Oct 25, 2019
16413c9
Refactor skipSpaces so it calls readSpaces discarding the count
agarciamontoro Oct 25, 2019
05a2456
Improve the efficiency of readIdentList (HT @erizocosmico)
agarciamontoro Oct 25, 2019
3fee47c
Improve error management in CreateView node
agarciamontoro Oct 25, 2019
660fb4f
Remove redundant comment in test function
agarciamontoro Oct 25, 2019
685c869
Shorten receiver names, adhering to Go conventions
agarciamontoro Oct 25, 2019
a006ec7
Check permissions on CREATE VIEW statements
agarciamontoro Oct 28, 2019
b513061
Merge pull request #858 from agarciamontoro/feature.views
erizocosmico Oct 28, 2019
02c010c
Make ViewKey struct public
agarciamontoro Oct 28, 2019
fc97414
Add Exists and DeleteList to ViewRegistry
agarciamontoro Oct 28, 2019
f6ff425
Parse and analyze DROP VIEW statements
agarciamontoro Oct 28, 2019
889bd1e
Test parsing utility readQualifiedIdentifierList
agarciamontoro Oct 29, 2019
38bf07f
Test DropView node
agarciamontoro Oct 29, 2019
cb3b61c
Make sure that DropView is not executed without write permissions
agarciamontoro Oct 29, 2019
9b8ecfa
Make qualifiedName type private to parse package
agarciamontoro Oct 29, 2019
a217057
Cleanup for vitess parser changes.
Nov 21, 2019
d1ad601
Merge remote-tracking branch 'agarciamontoro/feature.views.drop' into…
Nov 21, 2019
51d8367
Merge remote-tracking branch 'origin/ld-master' into aaron/views
Nov 21, 2019
51e0ced
Fix DROP VIEW parsing.
Nov 21, 2019
9da9885
go.mod: Keep go.mod looking like ld-master for now.
Nov 25, 2019
89302e3
Merge remote-tracking branch 'origin/ld-master' into aaron/views
Nov 25, 2019
a213e83
Fix identation.
Dec 2, 2019
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
2 changes: 1 addition & 1 deletion engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (e *Engine) Query(
case *plan.CreateIndex:
typ = sql.CreateIndexProcess
perm = auth.ReadPerm | auth.WritePerm
case *plan.InsertInto, *plan.DeleteFrom, *plan.Update, *plan.DropIndex, *plan.UnlockTables, *plan.LockTables:
case *plan.InsertInto, *plan.DeleteFrom, *plan.Update, *plan.DropIndex, *plan.UnlockTables, *plan.LockTables, *plan.CreateView, *plan.DropView:
perm = auth.ReadPerm | auth.WritePerm
}

Expand Down
22 changes: 12 additions & 10 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3661,17 +3661,19 @@ func TestReadOnly(t *testing.T) {
_, _, err := e.Query(newCtx(), `SELECT i FROM mytable`)
require.NoError(err)

_, _, err = e.Query(newCtx(), `CREATE INDEX foo ON mytable USING pilosa (i, s)`)
require.Error(err)
require.True(auth.ErrNotAuthorized.Is(err))

_, _, err = e.Query(newCtx(), `DROP INDEX foo ON mytable`)
require.Error(err)
require.True(auth.ErrNotAuthorized.Is(err))
writingQueries := []string{
`CREATE INDEX foo ON mytable USING pilosa (i, s)`,
`DROP INDEX foo ON mytable`,
`INSERT INTO mytable (i, s) VALUES(42, 'yolo')`,
`CREATE VIEW myview AS SELECT i FROM mytable`,
`DROP VIEW myview`,
}

_, _, err = e.Query(newCtx(), `INSERT INTO mytable (i, s) VALUES(42, 'yolo')`)
require.Error(err)
require.True(auth.ErrNotAuthorized.Is(err))
for _, query := range writingQueries {
_, _, err = e.Query(newCtx(), query)
require.Error(err)
require.True(auth.ErrNotAuthorized.Is(err))
}
}

func TestSessionVariables(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions sql/analyzer/assign_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ func assignCatalog(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
nc := *node
nc.Catalog = a.Catalog
return &nc, nil
case *plan.CreateView:
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
14 changes: 14 additions & 0 deletions sql/analyzer/assign_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,18 @@ func TestAssignCatalog(t *testing.T) {
ut, ok := node.(*plan.UnlockTables)
require.True(ok)
require.Equal(c, ut.Catalog)

mockSubquery := plan.NewSubqueryAlias("mock", plan.NewResolvedTable(tbl))
mockView := plan.NewCreateView(db, "", nil, mockSubquery, false)
node, err = f.Apply(sql.NewEmptyContext(), a, mockView)
require.NoError(err)
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)
}
23 changes: 16 additions & 7 deletions sql/analyzer/resolve_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,26 @@ func resolveTables(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
}

rt, err := a.Catalog.Table(db, name)
if err != nil {
if sql.ErrTableNotFound.Is(err) && name == dualTableName {
if err == nil {
a.Log("table resolved: %q", t.Name())
return plan.NewResolvedTable(rt), nil
}

if sql.ErrTableNotFound.Is(err) {
if name == dualTableName {
rt = dualTable
name = dualTableName
} else {
return nil, err

a.Log("table resolved: %q", t.Name())
return plan.NewResolvedTable(rt), nil
}
}

a.Log("table resolved: %q", t.Name())
if view, err := a.Catalog.ViewRegistry.View(db, name); err == nil {
a.Log("table %q is a view: replacing plans", t.Name())
return view.Definition(), nil
}
}

return plan.NewResolvedTable(rt), nil
return nil, err
})
}
42 changes: 42 additions & 0 deletions sql/analyzer/resolve_tables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,45 @@ func TestResolveTablesNested(t *testing.T) {
)
require.Equal(expected, analyzed)
}

func TestResolveViews(t *testing.T) {
require := require.New(t)

f := getRule("resolve_tables")

table := memory.NewTable("mytable", sql.Schema{{Name: "i", Type: sql.Int32}})
db := memory.NewDatabase("mydb")
db.AddTable("mytable", table)

// Resolved plan that corresponds to query "SELECT i FROM mytable"
subquery := plan.NewProject(
[]sql.Expression{
expression.NewGetFieldWithTable(
1, sql.Int32, table.Name(), "i", true),
},
plan.NewResolvedTable(table),
)
subqueryAlias := plan.NewSubqueryAlias("myview", subquery)
view := sql.NewView("myview", subqueryAlias)

catalog := sql.NewCatalog()
catalog.AddDatabase(db)
err := catalog.ViewRegistry.Register(db.Name(), view)
require.NoError(err)

a := NewBuilder(catalog).AddPostAnalyzeRule(f.Name, f.Apply).Build()

var notAnalyzed sql.Node = plan.NewUnresolvedTable("myview", "")
analyzed, err := f.Apply(sql.NewEmptyContext(), a, notAnalyzed)
require.NoError(err)
require.Equal(subqueryAlias, analyzed)

notAnalyzed = plan.NewUnresolvedTable("MyVieW", "")
analyzed, err = f.Apply(sql.NewEmptyContext(), a, notAnalyzed)
require.NoError(err)
require.Equal(subqueryAlias, analyzed)

analyzed, err = f.Apply(sql.NewEmptyContext(), a, subqueryAlias)
require.NoError(err)
require.Equal(subqueryAlias, analyzed)
}
2 changes: 2 additions & 0 deletions sql/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var ErrDatabaseNotFound = errors.NewKind("database not found: %s")
type Catalog struct {
FunctionRegistry
*IndexRegistry
*ViewRegistry
*ProcessList
*MemoryManager

Expand All @@ -38,6 +39,7 @@ func NewCatalog() *Catalog {
return &Catalog{
FunctionRegistry: NewFunctionRegistry(),
IndexRegistry: NewIndexRegistry(),
ViewRegistry: NewViewRegistry(),
MemoryManager: NewMemoryManager(ProcessMemory),
ProcessList: NewProcessList(),
locks: make(sessionLocks),
Expand Down
2 changes: 1 addition & 1 deletion sql/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ func exprListsEqual(a, b []string) bool {
// marked as creating, so nobody can't register two indexes with the same
// expression or id while the other is still being created.
// When something is sent through the returned channel, it means the index has
// finished it's creation and will be marked as ready.
// finished its creation and will be marked as ready.
// Another channel is returned to notify the user when the index is ready.
func (r *IndexRegistry) AddIndex(
idx Index,
Expand Down
39 changes: 33 additions & 6 deletions sql/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ var (
unlockTablesRegex = regexp.MustCompile(`^unlock\s+tables$`)
lockTablesRegex = regexp.MustCompile(`^lock\s+tables\s`)
setRegex = regexp.MustCompile(`^set\s+`)
createViewRegex = regexp.MustCompile(`^create\s+view\s+`)
)

// These constants aren't exported from vitess for some reason. This could be removed if we changed this.
Expand Down Expand Up @@ -104,9 +103,6 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
return parseLockTables(ctx, s)
case setRegex.MatchString(lowerQuery):
s = fixSetQuery(s)
case createViewRegex.MatchString(lowerQuery):
// CREATE VIEW parses as a CREATE DDL statement with an empty table spec
return nil, ErrUnsupportedFeature.New("CREATE VIEW")
}

stmt, err := sqlparser.Parse(s)
Expand Down Expand Up @@ -163,7 +159,7 @@ func convert(ctx *sql.Context, stmt sqlparser.Statement, query string) (sql.Node
if err != nil {
return nil, err
}
return convertDDL(ddl.(*sqlparser.DDL))
return convertDDL(ctx, ddl.(*sqlparser.DDL))
case *sqlparser.Set:
return convertSet(ctx, n)
case *sqlparser.Use:
Expand Down Expand Up @@ -369,11 +365,17 @@ func convertSelect(ctx *sql.Context, s *sqlparser.Select) (sql.Node, error) {
return node, nil
}

func convertDDL(c *sqlparser.DDL) (sql.Node, error) {
func convertDDL(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
switch c.Action {
case sqlparser.CreateStr:
if !c.View.IsEmpty() {
return convertCreateView(ctx, c)
}
return convertCreateTable(c)
case sqlparser.DropStr:
if len(c.FromViews) != 0 {
return convertDropView(ctx, c)
}
return convertDropTable(c)
default:
return nil, ErrUnsupportedSyntax.New(c)
Expand All @@ -398,6 +400,31 @@ func convertCreateTable(c *sqlparser.DDL) (sql.Node, error) {
sql.UnresolvedDatabase(""), c.Table.Name.String(), schema), nil
}

func convertCreateView(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
selectStatement, ok := c.ViewExpr.(*sqlparser.Select)
if !ok {
return nil, ErrUnsupportedSyntax.New(c.ViewExpr)
}

queryNode, err := convertSelect(ctx, selectStatement)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix indentation

if err != nil {
return nil, err
}

queryAlias := plan.NewSubqueryAlias(c.View.Name.String(), queryNode)

return plan.NewCreateView(
sql.UnresolvedDatabase(""), c.View.Name.String(), []string{}, queryAlias, c.OrReplace), nil
}

func convertDropView(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
plans := make([]sql.Node, len(c.FromViews))
for i, v := range c.FromViews {
plans[i] = plan.NewSingleDropView(sql.UnresolvedDatabase(""), v.Name.String())
}
return plan.NewDropView(plans, c.IfExists), nil
}

func convertInsert(ctx *sql.Context, i *sqlparser.Insert) (sql.Node, error) {
if len(i.OnDup) > 0 {
return nil, ErrUnsupportedFeature.New("ON DUPLICATE KEY")
Expand Down
38 changes: 19 additions & 19 deletions sql/parse/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,44 +55,44 @@ var fixtures = map[string]sql.Node{
sql.UnresolvedDatabase(""),
"t1",
sql.Schema{{
Name: "a",
Type: sql.Int32,
Nullable: false,
Name: "a",
Type: sql.Int32,
Nullable: false,
PrimaryKey: true,
}, {
Name: "b",
Type: sql.Text,
Nullable: true,
Name: "b",
Type: sql.Text,
Nullable: true,
PrimaryKey: false,
}},
),
`CREATE TABLE t1(a INTEGER, b TEXT, PRIMARY KEY (a))`: plan.NewCreateTable(
sql.UnresolvedDatabase(""),
"t1",
sql.Schema{{
Name: "a",
Type: sql.Int32,
Nullable: false,
Name: "a",
Type: sql.Int32,
Nullable: false,
PrimaryKey: true,
}, {
Name: "b",
Type: sql.Text,
Nullable: true,
Name: "b",
Type: sql.Text,
Nullable: true,
PrimaryKey: false,
}},
),
`CREATE TABLE t1(a INTEGER, b TEXT, PRIMARY KEY (a, b))`: plan.NewCreateTable(
sql.UnresolvedDatabase(""),
"t1",
sql.Schema{{
Name: "a",
Type: sql.Int32,
Nullable: false,
Name: "a",
Type: sql.Int32,
Nullable: false,
PrimaryKey: true,
}, {
Name: "b",
Type: sql.Text,
Nullable: false,
Name: "b",
Type: sql.Text,
Nullable: false,
PrimaryKey: true,
}},
),
Expand Down Expand Up @@ -1321,7 +1321,7 @@ var fixturesErrors = map[string]*errors.Kind{
`SELECT INTERVAL 1 DAY + INTERVAL 1 DAY`: ErrUnsupportedSyntax,
`SELECT '2018-05-01' + (INTERVAL 1 DAY + INTERVAL 1 DAY)`: ErrUnsupportedSyntax,
`SELECT AVG(DISTINCT foo) FROM b`: ErrUnsupportedSyntax,
`CREATE VIEW view1 AS SELECT x FROM t1 WHERE x>0`: ErrUnsupportedFeature,
`CREATE VIEW myview AS SELECT AVG(DISTINCT foo) FROM b`: ErrUnsupportedSyntax,
}

func TestParseErrors(t *testing.T) {
Expand Down
Loading