Skip to content

Commit e88d41a

Browse files
author
Aaron Son
authored
Merge pull request #34 from liquidata-inc/aaron/views
Add basic VIEWS support.
2 parents ebf9aee + a213e83 commit e88d41a

18 files changed

+1785
-44
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:
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: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3685,17 +3685,19 @@ func TestReadOnly(t *testing.T) {
36853685
_, _, err := e.Query(newCtx(), `SELECT i FROM mytable`)
36863686
require.NoError(err)
36873687

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

3696-
_, _, err = e.Query(newCtx(), `INSERT INTO mytable (i, s) VALUES(42, 'yolo')`)
3697-
require.Error(err)
3698-
require.True(auth.ErrNotAuthorized.Is(err))
3696+
for _, query := range writingQueries {
3697+
_, _, err = e.Query(newCtx(), query)
3698+
require.Error(err)
3699+
require.True(auth.ErrNotAuthorized.Is(err))
3700+
}
36993701
}
37003702

37013703
func TestSessionVariables(t *testing.T) {

sql/analyzer/assign_catalog.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ func assignCatalog(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
6060
nc := *node
6161
nc.Catalog = a.Catalog
6262
return &nc, nil
63+
case *plan.CreateView:
64+
nc := *node
65+
nc.Catalog = a.Catalog
66+
return &nc, nil
67+
case *plan.DropView:
68+
nc := *node
69+
nc.Catalog = a.Catalog
70+
return &nc, nil
6371
default:
6472
return n, nil
6573
}

sql/analyzer/assign_catalog_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,18 @@ func TestAssignCatalog(t *testing.T) {
7373
ut, ok := node.(*plan.UnlockTables)
7474
require.True(ok)
7575
require.Equal(c, ut.Catalog)
76+
77+
mockSubquery := plan.NewSubqueryAlias("mock", plan.NewResolvedTable(tbl))
78+
mockView := plan.NewCreateView(db, "", nil, mockSubquery, false)
79+
node, err = f.Apply(sql.NewEmptyContext(), a, mockView)
80+
require.NoError(err)
81+
cv, ok := node.(*plan.CreateView)
82+
require.True(ok)
83+
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)
7690
}

sql/analyzer/resolve_tables.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,26 @@ func resolveTables(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
3939
}
4040

4141
rt, err := a.Catalog.Table(db, name)
42-
if err != nil {
43-
if sql.ErrTableNotFound.Is(err) && name == dualTableName {
42+
if err == nil {
43+
a.Log("table resolved: %q", t.Name())
44+
return plan.NewResolvedTable(rt), nil
45+
}
46+
47+
if sql.ErrTableNotFound.Is(err) {
48+
if name == dualTableName {
4449
rt = dualTable
4550
name = dualTableName
46-
} else {
47-
return nil, err
51+
52+
a.Log("table resolved: %q", t.Name())
53+
return plan.NewResolvedTable(rt), nil
4854
}
49-
}
5055

51-
a.Log("table resolved: %q", t.Name())
56+
if view, err := a.Catalog.ViewRegistry.View(db, name); err == nil {
57+
a.Log("table %q is a view: replacing plans", t.Name())
58+
return view.Definition(), nil
59+
}
60+
}
5261

53-
return plan.NewResolvedTable(rt), nil
62+
return nil, err
5463
})
5564
}

sql/analyzer/resolve_tables_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,45 @@ func TestResolveTablesNested(t *testing.T) {
9191
)
9292
require.Equal(expected, analyzed)
9393
}
94+
95+
func TestResolveViews(t *testing.T) {
96+
require := require.New(t)
97+
98+
f := getRule("resolve_tables")
99+
100+
table := memory.NewTable("mytable", sql.Schema{{Name: "i", Type: sql.Int32}})
101+
db := memory.NewDatabase("mydb")
102+
db.AddTable("mytable", table)
103+
104+
// Resolved plan that corresponds to query "SELECT i FROM mytable"
105+
subquery := plan.NewProject(
106+
[]sql.Expression{
107+
expression.NewGetFieldWithTable(
108+
1, sql.Int32, table.Name(), "i", true),
109+
},
110+
plan.NewResolvedTable(table),
111+
)
112+
subqueryAlias := plan.NewSubqueryAlias("myview", subquery)
113+
view := sql.NewView("myview", subqueryAlias)
114+
115+
catalog := sql.NewCatalog()
116+
catalog.AddDatabase(db)
117+
err := catalog.ViewRegistry.Register(db.Name(), view)
118+
require.NoError(err)
119+
120+
a := NewBuilder(catalog).AddPostAnalyzeRule(f.Name, f.Apply).Build()
121+
122+
var notAnalyzed sql.Node = plan.NewUnresolvedTable("myview", "")
123+
analyzed, err := f.Apply(sql.NewEmptyContext(), a, notAnalyzed)
124+
require.NoError(err)
125+
require.Equal(subqueryAlias, analyzed)
126+
127+
notAnalyzed = plan.NewUnresolvedTable("MyVieW", "")
128+
analyzed, err = f.Apply(sql.NewEmptyContext(), a, notAnalyzed)
129+
require.NoError(err)
130+
require.Equal(subqueryAlias, analyzed)
131+
132+
analyzed, err = f.Apply(sql.NewEmptyContext(), a, subqueryAlias)
133+
require.NoError(err)
134+
require.Equal(subqueryAlias, analyzed)
135+
}

sql/catalog.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var ErrDatabaseNotFound = errors.NewKind("database not found: %s")
1818
type Catalog struct {
1919
FunctionRegistry
2020
*IndexRegistry
21+
*ViewRegistry
2122
*ProcessList
2223
*MemoryManager
2324

@@ -38,6 +39,7 @@ func NewCatalog() *Catalog {
3839
return &Catalog{
3940
FunctionRegistry: NewFunctionRegistry(),
4041
IndexRegistry: NewIndexRegistry(),
42+
ViewRegistry: NewViewRegistry(),
4143
MemoryManager: NewMemoryManager(ProcessMemory),
4244
ProcessList: NewProcessList(),
4345
locks: make(sessionLocks),

sql/index.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ func exprListsEqual(a, b []string) bool {
589589
// marked as creating, so nobody can't register two indexes with the same
590590
// expression or id while the other is still being created.
591591
// When something is sent through the returned channel, it means the index has
592-
// finished it's creation and will be marked as ready.
592+
// finished its creation and will be marked as ready.
593593
// Another channel is returned to notify the user when the index is ready.
594594
func (r *IndexRegistry) AddIndex(
595595
idx Index,

sql/parse/parse.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ var (
4747
unlockTablesRegex = regexp.MustCompile(`^unlock\s+tables$`)
4848
lockTablesRegex = regexp.MustCompile(`^lock\s+tables\s`)
4949
setRegex = regexp.MustCompile(`^set\s+`)
50-
createViewRegex = regexp.MustCompile(`^create\s+view\s+`)
5150
)
5251

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

112108
stmt, err := sqlparser.Parse(s)
@@ -163,7 +159,7 @@ func convert(ctx *sql.Context, stmt sqlparser.Statement, query string) (sql.Node
163159
if err != nil {
164160
return nil, err
165161
}
166-
return convertDDL(ddl.(*sqlparser.DDL))
162+
return convertDDL(ctx, ddl.(*sqlparser.DDL))
167163
case *sqlparser.Set:
168164
return convertSet(ctx, n)
169165
case *sqlparser.Use:
@@ -369,11 +365,17 @@ func convertSelect(ctx *sql.Context, s *sqlparser.Select) (sql.Node, error) {
369365
return node, nil
370366
}
371367

372-
func convertDDL(c *sqlparser.DDL) (sql.Node, error) {
368+
func convertDDL(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
373369
switch c.Action {
374370
case sqlparser.CreateStr:
371+
if !c.View.IsEmpty() {
372+
return convertCreateView(ctx, c)
373+
}
375374
return convertCreateTable(c)
376375
case sqlparser.DropStr:
376+
if len(c.FromViews) != 0 {
377+
return convertDropView(ctx, c)
378+
}
377379
return convertDropTable(c)
378380
default:
379381
return nil, ErrUnsupportedSyntax.New(c)
@@ -398,6 +400,31 @@ func convertCreateTable(c *sqlparser.DDL) (sql.Node, error) {
398400
sql.UnresolvedDatabase(""), c.Table.Name.String(), schema), nil
399401
}
400402

403+
func convertCreateView(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
404+
selectStatement, ok := c.ViewExpr.(*sqlparser.Select)
405+
if !ok {
406+
return nil, ErrUnsupportedSyntax.New(c.ViewExpr)
407+
}
408+
409+
queryNode, err := convertSelect(ctx, selectStatement)
410+
if err != nil {
411+
return nil, err
412+
}
413+
414+
queryAlias := plan.NewSubqueryAlias(c.View.Name.String(), queryNode)
415+
416+
return plan.NewCreateView(
417+
sql.UnresolvedDatabase(""), c.View.Name.String(), []string{}, queryAlias, c.OrReplace), nil
418+
}
419+
420+
func convertDropView(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
421+
plans := make([]sql.Node, len(c.FromViews))
422+
for i, v := range c.FromViews {
423+
plans[i] = plan.NewSingleDropView(sql.UnresolvedDatabase(""), v.Name.String())
424+
}
425+
return plan.NewDropView(plans, c.IfExists), nil
426+
}
427+
401428
func convertInsert(ctx *sql.Context, i *sqlparser.Insert) (sql.Node, error) {
402429
if len(i.OnDup) > 0 {
403430
return nil, ErrUnsupportedFeature.New("ON DUPLICATE KEY")

sql/parse/parse_test.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,44 +55,44 @@ var fixtures = map[string]sql.Node{
5555
sql.UnresolvedDatabase(""),
5656
"t1",
5757
sql.Schema{{
58-
Name: "a",
59-
Type: sql.Int32,
60-
Nullable: false,
58+
Name: "a",
59+
Type: sql.Int32,
60+
Nullable: false,
6161
PrimaryKey: true,
6262
}, {
63-
Name: "b",
64-
Type: sql.Text,
65-
Nullable: true,
63+
Name: "b",
64+
Type: sql.Text,
65+
Nullable: true,
6666
PrimaryKey: false,
6767
}},
6868
),
6969
`CREATE TABLE t1(a INTEGER, b TEXT, PRIMARY KEY (a))`: plan.NewCreateTable(
7070
sql.UnresolvedDatabase(""),
7171
"t1",
7272
sql.Schema{{
73-
Name: "a",
74-
Type: sql.Int32,
75-
Nullable: false,
73+
Name: "a",
74+
Type: sql.Int32,
75+
Nullable: false,
7676
PrimaryKey: true,
7777
}, {
78-
Name: "b",
79-
Type: sql.Text,
80-
Nullable: true,
78+
Name: "b",
79+
Type: sql.Text,
80+
Nullable: true,
8181
PrimaryKey: false,
8282
}},
8383
),
8484
`CREATE TABLE t1(a INTEGER, b TEXT, PRIMARY KEY (a, b))`: plan.NewCreateTable(
8585
sql.UnresolvedDatabase(""),
8686
"t1",
8787
sql.Schema{{
88-
Name: "a",
89-
Type: sql.Int32,
90-
Nullable: false,
88+
Name: "a",
89+
Type: sql.Int32,
90+
Nullable: false,
9191
PrimaryKey: true,
9292
}, {
93-
Name: "b",
94-
Type: sql.Text,
95-
Nullable: false,
93+
Name: "b",
94+
Type: sql.Text,
95+
Nullable: false,
9696
PrimaryKey: true,
9797
}},
9898
),
@@ -1321,7 +1321,7 @@ var fixturesErrors = map[string]*errors.Kind{
13211321
`SELECT INTERVAL 1 DAY + INTERVAL 1 DAY`: ErrUnsupportedSyntax,
13221322
`SELECT '2018-05-01' + (INTERVAL 1 DAY + INTERVAL 1 DAY)`: ErrUnsupportedSyntax,
13231323
`SELECT AVG(DISTINCT foo) FROM b`: ErrUnsupportedSyntax,
1324-
`CREATE VIEW view1 AS SELECT x FROM t1 WHERE x>0`: ErrUnsupportedFeature,
1324+
`CREATE VIEW myview AS SELECT AVG(DISTINCT foo) FROM b`: ErrUnsupportedSyntax,
13251325
}
13261326

13271327
func TestParseErrors(t *testing.T) {

0 commit comments

Comments
 (0)