Skip to content

Commit b513061

Browse files
authored
Merge pull request #858 from agarciamontoro/feature.views
Add VIEWs support
2 parents 68b087c + a006ec7 commit b513061

18 files changed

+1420
-42
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:
124124
perm = auth.ReadPerm | auth.WritePerm
125125
}
126126

engine_test.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,17 +3144,18 @@ func TestReadOnly(t *testing.T) {
31443144
_, _, err := e.Query(newCtx(), `SELECT i FROM mytable`)
31453145
require.NoError(err)
31463146

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

3155-
_, _, err = e.Query(newCtx(), `INSERT INTO mytable (i, s) VALUES(42, 'yolo')`)
3156-
require.Error(err)
3157-
require.True(auth.ErrNotAuthorized.Is(err))
3154+
for _, query := range writingQueries {
3155+
_, _, err = e.Query(newCtx(), query)
3156+
require.Error(err)
3157+
require.True(auth.ErrNotAuthorized.Is(err))
3158+
}
31583159
}
31593160

31603161
func TestSessionVariables(t *testing.T) {

sql/analyzer/assign_catalog.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ 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
6367
default:
6468
return n, nil
6569
}

sql/analyzer/assign_catalog_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,12 @@ 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)
7684
}

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
@@ -17,6 +17,7 @@ var ErrDatabaseNotFound = errors.NewKind("database not found: %s")
1717
type Catalog struct {
1818
FunctionRegistry
1919
*IndexRegistry
20+
*ViewRegistry
2021
*ProcessList
2122
*MemoryManager
2223

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

sql/index.go

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

sql/parse/parse.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
var (
3737
describeTablesRegex = regexp.MustCompile(`^(describe|desc)\s+table\s+(.*)`)
3838
createIndexRegex = regexp.MustCompile(`^create\s+index\s+`)
39+
createViewRegex = regexp.MustCompile(`^create\s+(or\s+replace\s+)?view\s+`)
3940
dropIndexRegex = regexp.MustCompile(`^drop\s+index\s+`)
4041
showIndexRegex = regexp.MustCompile(`^show\s+(index|indexes|keys)\s+(from|in)\s+\S+\s*`)
4142
showCreateRegex = regexp.MustCompile(`^show create\s+\S+\s*`)
@@ -47,7 +48,6 @@ var (
4748
unlockTablesRegex = regexp.MustCompile(`^unlock\s+tables$`)
4849
lockTablesRegex = regexp.MustCompile(`^lock\s+tables\s`)
4950
setRegex = regexp.MustCompile(`^set\s+`)
50-
createViewRegex = regexp.MustCompile(`^create\s+view\s+`)
5151
)
5252

5353
// These constants aren't exported from vitess for some reason. This could be removed if we changed this.
@@ -82,6 +82,8 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
8282
return parseDescribeTables(lowerQuery)
8383
case createIndexRegex.MatchString(lowerQuery):
8484
return parseCreateIndex(ctx, s)
85+
case createViewRegex.MatchString(lowerQuery):
86+
return parseCreateView(ctx, s)
8587
case dropIndexRegex.MatchString(lowerQuery):
8688
return parseDropIndex(s)
8789
case showIndexRegex.MatchString(lowerQuery):
@@ -104,9 +106,6 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
104106
return parseLockTables(ctx, s)
105107
case setRegex.MatchString(lowerQuery):
106108
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")
110109
}
111110

112111
stmt, err := sqlparser.Parse(s)

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: true,
73+
Name: "a",
74+
Type: sql.Int32,
75+
Nullable: true,
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: true,
88+
Name: "a",
89+
Type: sql.Int32,
90+
Nullable: true,
9191
PrimaryKey: true,
9292
}, {
93-
Name: "b",
94-
Type: sql.Text,
95-
Nullable: true,
93+
Name: "b",
94+
Type: sql.Text,
95+
Nullable: true,
9696
PrimaryKey: true,
9797
}},
9898
),
@@ -1296,7 +1296,7 @@ var fixturesErrors = map[string]*errors.Kind{
12961296
`SELECT INTERVAL 1 DAY + INTERVAL 1 DAY`: ErrUnsupportedSyntax,
12971297
`SELECT '2018-05-01' + (INTERVAL 1 DAY + INTERVAL 1 DAY)`: ErrUnsupportedSyntax,
12981298
`SELECT AVG(DISTINCT foo) FROM b`: ErrUnsupportedSyntax,
1299-
`CREATE VIEW view1 AS SELECT x FROM t1 WHERE x>0`: ErrUnsupportedFeature,
1299+
`CREATE VIEW myview (col1) AS SELECT 1`: ErrUnsupportedSyntax,
13001300
}
13011301

13021302
func TestParseErrors(t *testing.T) {

0 commit comments

Comments
 (0)