Skip to content
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

planner: support building data source from View #8757

Merged
merged 12 commits into from
Dec 26, 2018
35 changes: 35 additions & 0 deletions executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3404,6 +3404,41 @@ func (s *testSuite3) TestSelectHashPartitionTable(c *C) {
))
}

func (s *testSuite) TestSelectView(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec("create table view_t (a int,b int)")
tk.MustExec("insert into view_t values(1,2)")
tk.MustExec("create view view1 as select * from view_t")
tk.MustExec("create view view2(c,d) as select * from view_t")
tk.MustExec("create view view3(c,d) as select a,b from view_t")
tk.MustQuery("select * from view1;").Check(testkit.Rows("1 2"))
tk.MustQuery("select * from view2;").Check(testkit.Rows("1 2"))
tk.MustQuery("select * from view3;").Check(testkit.Rows("1 2"))
tk.MustExec("drop table view_t;")
tk.MustExec("create table view_t(c int,d int)")
_, err := tk.Exec("select * from view1")
c.Assert(err.Error(), Equals, plannercore.ErrViewInvalid.GenWithStackByArgs("test", "view1").Error())
_, err = tk.Exec("select * from view2")
c.Assert(err.Error(), Equals, plannercore.ErrViewInvalid.GenWithStackByArgs("test", "view2").Error())
_, err = tk.Exec("select * from view3")
c.Assert(err.Error(), Equals, "[planner:1054]Unknown column 'a' in 'field list'")
tk.MustExec("drop table view_t;")
tk.MustExec("create table view_t(a int,b int,c int)")
tk.MustExec("insert into view_t values(1,2,3)")
tk.MustQuery("select * from view1;").Check(testkit.Rows("1 2"))
tk.MustQuery("select * from view2;").Check(testkit.Rows("1 2"))
tk.MustQuery("select * from view3;").Check(testkit.Rows("1 2"))
tk.MustExec("alter table view_t drop column a")
tk.MustExec("alter table view_t add column a int after b")
tk.MustExec("update view_t set a=1;")
tk.MustQuery("select * from view1;").Check(testkit.Rows("1 2"))
tk.MustQuery("select * from view2;").Check(testkit.Rows("1 2"))
tk.MustQuery("select * from view3;").Check(testkit.Rows("1 2"))
tk.MustExec("drop table view_t;")
tk.MustExec("drop view view1,view2,view3;")
}

type testSuite2 struct {
cluster *mocktikv.Cluster
mvccStore mocktikv.MVCCStore
Expand Down
1 change: 1 addition & 0 deletions planner/core/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ var (
ErrMixOfGroupFuncAndFields = terror.ClassOptimizer.New(codeMixOfGroupFuncAndFields, "In aggregated query without GROUP BY, expression #%d of SELECT list contains nonaggregated column '%s'; this is incompatible with sql_mode=only_full_group_by")
ErrNonUniqTable = terror.ClassOptimizer.New(codeNonUniqTable, mysql.MySQLErrName[mysql.ErrNonuniqTable])
ErrWrongValueCountOnRow = terror.ClassOptimizer.New(mysql.ErrWrongValueCountOnRow, mysql.MySQLErrName[mysql.ErrWrongValueCountOnRow])
ErrViewInvalid = terror.ClassOptimizer.New(mysql.ErrViewInvalid, mysql.MySQLErrName[mysql.ErrViewInvalid])
)

func init() {
Expand Down
44 changes: 44 additions & 0 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,10 @@ func (b *PlanBuilder) buildDataSource(tn *ast.TableName) (LogicalPlan, error) {
tableInfo := tbl.Meta()
b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SelectPriv, dbName.L, tableInfo.Name.L, "")

if tableInfo.IsView() {
return b.buildDataSourceFromView(dbName, tableInfo)
}

if tableInfo.GetPartitionInfo() != nil {
b.optFlag = b.optFlag | flagPartitionProcessor
}
Expand Down Expand Up @@ -1994,6 +1998,46 @@ func (b *PlanBuilder) buildDataSource(tn *ast.TableName) (LogicalPlan, error) {
return result, nil
}

func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *model.TableInfo) (LogicalPlan, error) {
var (
Copy link
Member

Choose a reason for hiding this comment

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

These variable declaration looks ugly. We can avoid this by using the := operator in golang.

selectNode ast.StmtNode
selectLogicalPlan Plan
err error
)
charset, collation := b.ctx.GetSessionVars().GetCharsetInfo()
selectNode, err = parser.New().ParseOneStmt(tableInfo.View.SelectStmt, charset, collation)
AndrewDi marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
selectLogicalPlan, err = b.Build(selectNode)
AndrewDi marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
projSchema := expression.NewSchema(make([]*expression.Column, 0, len(tableInfo.View.Cols))...)
projExprs := make([]expression.Expression, 0, len(tableInfo.View.Cols))
for i := range tableInfo.View.Cols {
col := selectLogicalPlan.Schema().FindColumnByName(tableInfo.View.Cols[i].L)
if col == nil {
return nil, ErrViewInvalid.GenWithStackByArgs(dbName.O, tableInfo.Name.O)
}
projSchema.Append(&expression.Column{
UniqueID: b.ctx.GetSessionVars().AllocPlanColumnID(),
TblName: col.TblName,
OrigTblName: col.OrigTblName,
ColName: tableInfo.Cols()[i].Name,
OrigColName: tableInfo.View.Cols[i],
DBName: col.DBName,
RetType: col.GetType(),
})
projExprs = append(projExprs, col)
}

projUponView := LogicalProjection{Exprs: projExprs}.Init(b.ctx)
projUponView.SetChildren(selectLogicalPlan.(LogicalPlan))
projUponView.SetSchema(projSchema)
return projUponView, nil
}

// projectVirtualColumns is only for DataSource. If some table has virtual generated columns,
// we add a projection on the original DataSource, and calculate those columns in the projection
// so that plans above it can reference generated columns by their name.
Expand Down
33 changes: 32 additions & 1 deletion planner/core/logical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type testPlanSuite struct {
}

func (s *testPlanSuite) SetUpSuite(c *C) {
s.is = infoschema.MockInfoSchema([]*model.TableInfo{MockTable()})
s.is = infoschema.MockInfoSchema([]*model.TableInfo{MockTable(), MockView()})
s.ctx = MockContext()
s.Parser = parser.New()
}
Expand Down Expand Up @@ -1873,3 +1873,34 @@ func (s *testPlanSuite) TestOuterJoinEliminator(c *C) {
c.Assert(ToString(p), Equals, tt.best, comment)
}
}

func (s *testPlanSuite) TestSelectView(c *C) {
defer func() {
testleak.AfterTest(c)()
}()
tests := []struct {
sql string
best string
}{
{
sql: "select * from v",
best: "DataScan(t)->Projection",
},
}
for i, tt := range tests {
comment := Commentf("case:%v sql:%s", i, tt.sql)
stmt, err := s.ParseOneStmt(tt.sql, "", "")
c.Assert(err, IsNil, comment)
Preprocess(s.ctx, stmt, s.is, false)
builder := &PlanBuilder{
ctx: MockContext(),
is: s.is,
colMapper: make(map[*ast.ColumnNameExpr]int),
}
p, err := builder.Build(stmt)
c.Assert(err, IsNil)
p, err = logicalOptimize(builder.optFlag, p.(LogicalPlan))
c.Assert(err, IsNil)
c.Assert(ToString(p), Equals, tt.best, comment)
}
}
31 changes: 31 additions & 0 deletions planner/core/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package core

import (
"github.com/pingcap/parser/auth"
"github.com/pingcap/parser/model"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/tidb/domain"
Expand Down Expand Up @@ -249,6 +250,36 @@ func MockTable() *model.TableInfo {
return table
}

// MockView is only used for plan related tests.
func MockView() *model.TableInfo {
selectStmt := "select b,c,d from t"
col0 := &model.ColumnInfo{
State: model.StatePublic,
Offset: 0,
Name: model.NewCIStr("b"),
ID: 1,
}
col1 := &model.ColumnInfo{
State: model.StatePublic,
Offset: 1,
Name: model.NewCIStr("c"),
ID: 2,
}
col2 := &model.ColumnInfo{
State: model.StatePublic,
Offset: 2,
Name: model.NewCIStr("d"),
ID: 3,
}
view := &model.ViewInfo{SelectStmt: selectStmt, Security: model.SecurityDefiner, Definer: &auth.UserIdentity{Username: "root", Hostname: ""}, Cols: []model.CIStr{col0.Name, col1.Name, col2.Name}}
table := &model.TableInfo{
Name: model.NewCIStr("v"),
Columns: []*model.ColumnInfo{col0, col1, col2},
View: view,
}
return table
}

// MockContext is only used for plan related tests.
func MockContext() sessionctx.Context {
ctx := mock.NewContext()
Expand Down