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

plan: refactor projection elimination #3687

Merged
merged 21 commits into from Jul 19, 2017

Conversation

Projects
None yet
3 participants
@zz-jason
Member

zz-jason commented Jul 10, 2017

fix #3681

@zz-jason

This comment has been minimized.

Show comment
Hide comment
Member

zz-jason commented Jul 10, 2017

@hanfei1991

This comment has been minimized.

Show comment
Hide comment
@hanfei1991

hanfei1991 Jul 10, 2017

Member

@zz-jason Can we move this optimization to logical optimizations ?

Member

hanfei1991 commented Jul 10, 2017

@zz-jason Can we move this optimization to logical optimizations ?

@zz-jason

This comment has been minimized.

Show comment
Hide comment
@zz-jason

zz-jason Jul 10, 2017

Member

@hanfei1991 As the last stage of logical optimization ?

Member

zz-jason commented Jul 10, 2017

@hanfei1991 As the last stage of logical optimization ?

@hanfei1991

This comment has been minimized.

Show comment
Hide comment
@hanfei1991

hanfei1991 Jul 10, 2017

Member

I think it's ok to add after column prunning.

Member

hanfei1991 commented Jul 10, 2017

I think it's ok to add after column prunning.

Show outdated Hide outdated expression/column.go
@@ -146,6 +146,10 @@ func (col *Column) String() string {
return result
}
func (col *Column) Identify() string {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

We don't need a Identify. You can use HashCode() directly.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

We don't need a Identify. You can use HashCode() directly.

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

done

Show outdated Hide outdated plan/eliminate_projection.go
}
if col.FromID != child.Schema().Columns[i].FromID || col.Position != child.Schema().Columns[i].Position {
projCol, ok := expr.(*expression.Column)
if !ok || projCol.Identify() != childCols[i].Identify() {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Column has a Equal interface

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Column has a Equal interface

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

done

}
}
func resolveExprAndReplace(origin expression.Expression, replace map[string]*expression.Column) {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

There is a function named expression.ColumnSubstitute can do this.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

There is a function named expression.ColumnSubstitute can do this.

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

If use expression.ColumnSubstitute(expr Expression, schema *Schema, newExprs []Expression):

  1. We have to convert replace, whose type is map[string]*expression.Column, to a (schema *Schema, newExprs [] Expression) pair every time before function expression.ColumnSubstitute called. It's inconvenient
  2. expression.ColumnSubstitute uses a for loop to find whether a column exists in schema *Schema. It's inefficient

I prefer not to use the function expression.ColumnSubstitute

@zz-jason

zz-jason Jul 12, 2017

Member

If use expression.ColumnSubstitute(expr Expression, schema *Schema, newExprs []Expression):

  1. We have to convert replace, whose type is map[string]*expression.Column, to a (schema *Schema, newExprs [] Expression) pair every time before function expression.ColumnSubstitute called. It's inconvenient
  2. expression.ColumnSubstitute uses a for loop to find whether a column exists in schema *Schema. It's inefficient

I prefer not to use the function expression.ColumnSubstitute

This comment has been minimized.

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

@hanfei1991 updated, PTAL

@zz-jason

zz-jason Jul 12, 2017

Member

@hanfei1991 updated, PTAL

Show outdated Hide outdated plan/eliminate_projection.go
// 1. it's not the root operator
// 2. canProjectionBeEliminated(p) returns true
func canLogicalProjectionBeEliminated(p *Projection) bool {
if len(p.Parents()) == 0 {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

If the query is select * from t limit 1, the parents will also be 0 and can't be removed.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

If the query is select * from t limit 1, the parents will also be 0 and can't be removed.

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

yes, so it returns false

@zz-jason

zz-jason Jul 12, 2017

Member

yes, so it returns false

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

the parentes length will be 1 but can't be removed....

@hanfei1991

hanfei1991 Jul 12, 2017

Member

the parentes length will be 1 but can't be removed....

Show outdated Hide outdated plan/eliminate_projection.go
return false
}
}
return true
}
func resolveColumnAndReplace(origin *expression.Column, replace map[string]*expression.Column) {
dst := replace[string((*origin).HashCode())]

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Needn't be (*orgin)

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Needn't be (*orgin)

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

fixed

Show outdated Hide outdated plan/eliminate_projection.go
return p
}
type nonRootProjectionEliminater struct {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Needn't call it nonRoot..., it's confusing. ProjectonElimiater is ok.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Needn't call it nonRoot..., it's confusing. ProjectonElimiater is ok.

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

After refactoring, projection elimination is done in both logical and physical optimization, but they are different.

In logical optimization, we only eliminate the no-root projections, leave the root projection, if exists and can be eliminated, to be eliminated in the physical optimization

I hope to use these abnormal function and struct names, which are nonRootProjectionEliminater and eliminateRootProjection, to inform people the difference between them.

@zz-jason

zz-jason Jul 12, 2017

Member

After refactoring, projection elimination is done in both logical and physical optimization, but they are different.

In logical optimization, we only eliminate the no-root projections, leave the root projection, if exists and can be eliminated, to be eliminated in the physical optimization

I hope to use these abnormal function and struct names, which are nonRootProjectionEliminater and eliminateRootProjection, to inform people the difference between them.

Show outdated Hide outdated plan/eliminate_projection.go
}
}
func (p *DataSource) replaceExprColumns(replace map[string]*expression.Column) {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

I think DataSource needn't implement it.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

I think DataSource needn't implement it.

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

removed

@zz-jason

zz-jason Jul 12, 2017

Member

removed

Show outdated Hide outdated plan/eliminate_projection.go
}
}
func (p *Update) replaceExprColumns(replace map[string]*expression.Column) {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Update also needn't

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Update also needn't

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

removed

@zz-jason

zz-jason Jul 12, 2017

Member

removed

Show outdated Hide outdated plan/eliminate_projection.go
for _, gbyCol := range p.groupByCols {
resolveColumnAndReplace(gbyCol, replace)
}
for _, prop := range p.possibleProperties {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

needn't replace it

@hanfei1991

hanfei1991 Jul 12, 2017

Member

needn't replace it

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

removed

@zz-jason

zz-jason Jul 12, 2017

Member

removed

Show outdated Hide outdated plan/eliminate_projection.go
for _, gbyItem := range p.GroupByItems {
resolveExprAndReplace(gbyItem, replace)
}
for _, gbyCol := range p.groupByCols {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Just recollect it, needn't replace it

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Just recollect it, needn't replace it

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

done

Show outdated Hide outdated plan/eliminate_projection.go
resolveColumnAndReplace(prop, replace)
}
}
if p.redundantSchema != nil {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Needn't replace it, it's only needed by logicalplanbuilder

@hanfei1991

hanfei1991 Jul 12, 2017

Member

Needn't replace it, it's only needed by logicalplanbuilder

This comment has been minimized.

@zz-jason

zz-jason Jul 12, 2017

Member

removed

@zz-jason

zz-jason Jul 12, 2017

Member

removed

Show outdated Hide outdated plan/eliminate_projection.go
for _, rightKey := range p.RightJoinKeys {
resolveColumnAndReplace(rightKey, replace)
}
for _, leftProp := range p.leftProperties {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

No need 2 update properties

@hanfei1991

hanfei1991 Jul 12, 2017

Member

No need 2 update properties

if err != nil {
if !terror.ErrorEqual(err, types.ErrTruncated) {
is.Ranges = ranger.FullIndexRange()
if len(p.parents) > 0 {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 12, 2017

Member

if len(p.parents) > 0 && ...

@hanfei1991

hanfei1991 Jul 12, 2017

Member

if len(p.parents) > 0 && ...

@zz-jason zz-jason self-assigned this Jul 17, 2017

@zz-jason zz-jason changed the title from plan: use child's schema after projection eliminated to plan: refactor projection elimination Jul 17, 2017

Show outdated Hide outdated plan/optimizer.go
@@ -34,6 +34,7 @@ const (
flagPredicatePushDown
flagAggregationOptimize
flagPushDownTopN
flagEliminateProjection

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

We can put it right after PruneColumns.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

We can put it right after PruneColumns.

}
}
func resolveExprAndReplace(origin expression.Expression, replace map[string]*expression.Column) {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

As previous discussion, we can pass a schema and a slice of expression.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

As previous discussion, we can pass a schema and a slice of expression.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

As previous discussion, function expression.ColumnSubstitute(expr Expression, schema *Schema, newExprs []Expression) is too heavy and inefficient:

  1. for every ScalarFunction node in the expression tree, this function will do a copy for it.
  2. uses a loop to find whether a column exists in schema *Schema

I prefer not to use it

@zz-jason

zz-jason Jul 18, 2017

Member

As previous discussion, function expression.ColumnSubstitute(expr Expression, schema *Schema, newExprs []Expression) is too heavy and inefficient:

  1. for every ScalarFunction node in the expression tree, this function will do a copy for it.
  2. uses a loop to find whether a column exists in schema *Schema

I prefer not to use it

Show outdated Hide outdated plan/eliminate_projection.go
RemovePlan(p)
p = EliminateProjection(child)
// canProjectionBeEliminatedLoose checks whether a projection can be eliminated, returns true if:
// 1. the projection changes the order of its child's output columns, or:

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

These two rules are prolixity. It is clearer to say Every expression is a single column.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

These two rules are prolixity. It is clearer to say Every expression is a single column.

Show outdated Hide outdated plan/eliminate_projection.go
// 2. the projection just copy its child's output but set a different name for every column.
func canProjectionBeEliminatedLoose(p *Projection) bool {
child := p.Children()[0]
if p.Schema().Len() != child.Schema().Len() {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

No need to check the length. A projection like a -> b, a -> c can also be prunned.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

No need to check the length. A projection like a -> b, a -> c can also be prunned.

Show outdated Hide outdated plan/eliminate_projection.go
isOrderKeeper := isProj || isJoin || isAggr
_, isUnion := p.(*Union)
_, isSort := p.(*Sort)

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

It seems we need't to check sort

@hanfei1991

hanfei1991 Jul 17, 2017

Member

It seems we need't to check sort

Show outdated Hide outdated plan/eliminate_projection.go
// The order of output plan's schema columns can be changed if "haveOrderKeeper" is true.
func (pe *projectionEliminater) eliminate(p Plan, replace map[string]*expression.Column, haveOrderKeeper bool) Plan {
proj, isProj := p.(*Projection)
_, isJoin := p.(*LogicalJoin)

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

It seems we needn't to check join and projection.

@hanfei1991

hanfei1991 Jul 17, 2017

Member

It seems we needn't to check join and projection.

Show outdated Hide outdated plan/eliminate_projection.go
// eliminatePhysicalProjection should be called after physical optimization to eliminate the redundant projection
// left after logical projection elimination.
func eliminatePhysicalProjection(p Plan, replace map[string]*expression.Column) Plan {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Restrict p as PhysicalPlan.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Restrict p as PhysicalPlan.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

done

Show outdated Hide outdated plan/eliminate_projection.go
// eliminate eliminates the redundant projection in a logical plan.
// The order of output plan's schema columns can be changed if "haveProjection" is true.
func (pe *projectionEliminater) eliminate(p Plan, replace map[string]*expression.Column, haveProjection bool) Plan {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Restrict p as Logical Plan

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Restrict p as Logical Plan

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

done

Show outdated Hide outdated plan/eliminate_projection.go
_, isApply := p.(*LogicalApply)
_, isJoin := p.(*LogicalJoin)
if isSort || isTopN || isLimit || isSelect || isMaxOneRow {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

switch x := p.(type) {case *Sort, *TopN ...:} Make sure you don't forget any type by checking this file : https://github.com/pingcap/tidb/blob/master/plan/logical_plans.go

@hanfei1991

hanfei1991 Jul 18, 2017

Member

switch x := p.(type) {case *Sort, *TopN ...:} Make sure you don't forget any type by checking this file : https://github.com/pingcap/tidb/blob/master/plan/logical_plans.go

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

done

Show outdated Hide outdated plan/eliminate_projection.go
joinTp = p.(*LogicalJoin).JoinType
}
switch joinTp {
case InnerJoin:

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

case InnerJoin, LeftOuterJoin, RightOuterJoin blabla...

@hanfei1991

hanfei1991 Jul 18, 2017

Member

case InnerJoin, LeftOuterJoin, RightOuterJoin blabla...

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

done

Show outdated Hide outdated plan/eliminate_projection.go
for _, dst := range p.Schema().Columns {
resolveColumnAndReplace(dst, replace)
}
for _, key := range p.Schema().Keys {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

keys haven't been builded.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

keys haven't been builded.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

removed

@zz-jason

zz-jason Jul 18, 2017

Member

removed

Show outdated Hide outdated plan/eliminate_projection.go
return p
}
child := p.Children()[0]

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Why check child is join ?

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Why check child is join ?

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

to guarantee there is a projection between root/union and join

@zz-jason

zz-jason Jul 18, 2017

Member

to guarantee there is a projection between root/union and join

p.SetSchema(newSchema)
}
default:
for _, dst := range p.Schema().Columns {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

No need to do this. The rest of plans generate schema by itself without relying on children.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

No need to do this. The rest of plans generate schema by itself without relying on children.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

LogicalAggregation may rely on its child

@zz-jason

zz-jason Jul 18, 2017

Member

LogicalAggregation may rely on its child

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

LogicalAggregation's schema is decide by its own aggregate functions.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

LogicalAggregation's schema is decide by its own aggregate functions.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

Aggregation function "firstrow"'s corresponding output column is cloned from its child's. the default branch has to be retained.

@zz-jason

zz-jason Jul 18, 2017

Member

Aggregation function "firstrow"'s corresponding output column is cloned from its child's. the default branch has to be retained.

Show outdated Hide outdated plan/eliminate_projection.go
children := make([]Plan, 0, len(p.Children()))
_, isUnion := p.(*Union)
for _, child := range p.Children() {
children = append(children, pe.eliminate(child.(LogicalPlan), replace, !isUnion && (haveProjection || isProj)))

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Well, I think there are two points that can be improved:

  1. haveProjection is not a good name.
  2. !isUnion && (haveProjection || isProj) is not readable.
    The following style is more acceptable:
var childFlag = haveProjection
if isProj {
    childFlag = true
} else if _, isUnion := p.(*Union); isUnion {
    childFlag = false
}
@hanfei1991

hanfei1991 Jul 18, 2017

Member

Well, I think there are two points that can be improved:

  1. haveProjection is not a good name.
  2. !isUnion && (haveProjection || isProj) is not readable.
    The following style is more acceptable:
var childFlag = haveProjection
if isProj {
    childFlag = true
} else if _, isUnion := p.(*Union); isUnion {
    childFlag = false
}
Show outdated Hide outdated plan/eliminate_projection.go
exprs := proj.Exprs
for i, parentColumn := range proj.Schema().Columns {
col, _ := exprs[i].(*expression.Column)
col.DBName = parentColumn.DBName

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Why do this replacement ? It doesn't do favor for execution.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Why do this replacement ? It doesn't do favor for execution.

Show outdated Hide outdated plan/eliminate_projection.go
replace[string(parentColumn.HashCode())] = col
colNameMap[string(col.HashCode())] = parentColumn.ColName
}
for _, childCol := range child.Schema().Columns {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

ditto.

Show outdated Hide outdated plan/eliminate_projection.go
child := p.Children()[0]
exprs := proj.Exprs
childCols := child.Schema().Columns
for i, parentColumn := range proj.Schema().Columns {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

I think only root plan needs to refresh his child's schema.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

I think only root plan needs to refresh his child's schema.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

No. actually, we only need to refresh child's schema in physical projection elimination when the eliminated projection is root.

@zz-jason

zz-jason Jul 18, 2017

Member

No. actually, we only need to refresh child's schema in physical projection elimination when the eliminated projection is root.

Show outdated Hide outdated plan/eliminate_projection.go
for _, dst := range p.Schema().Columns {
resolveColumnAndReplace(dst, replace)
}
for _, key := range p.Schema().Keys {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Key Info is useless afterwards.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

Key Info is useless afterwards.

This comment has been minimized.

@zz-jason

zz-jason Jul 18, 2017

Member

removed

@zz-jason

zz-jason Jul 18, 2017

Member

removed

}
// eliminate eliminates the redundant projection in a logical plan.
func (pe *projectionEliminater) eliminate(p LogicalPlan, replace map[string]*expression.Column, canEliminate bool) LogicalPlan {

This comment has been minimized.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

It is better to make replace as a return value.

@hanfei1991

hanfei1991 Jul 18, 2017

Member

It is better to make replace as a return value.

@hanfei1991

This comment has been minimized.

Show comment
Hide comment
@hanfei1991

hanfei1991 Jul 18, 2017

Member

LGTM

Member

hanfei1991 commented Jul 18, 2017

LGTM

@hanfei1991

This comment has been minimized.

Show comment
Hide comment
Member

hanfei1991 commented Jul 18, 2017

return physicalOptimize(flag, logic, allocator)
if err != nil {
return nil, errors.Trace(err)
}

This comment has been minimized.

@lamxTyler

lamxTyler Jul 19, 2017

Member

Can it be put before line 130?

@lamxTyler

lamxTyler Jul 19, 2017

Member

Can it be put before line 130?

This comment has been minimized.

@zz-jason

zz-jason Jul 19, 2017

Member

moved

Show outdated Hide outdated plan/eliminate_projection.go
type projectionEliminater struct {
}
// optimize implements the logicalOptRule interface

This comment has been minimized.

@lamxTyler

lamxTyler Jul 19, 2017

Member

Add . at the end.

@lamxTyler

lamxTyler Jul 19, 2017

Member

Add . at the end.

This comment has been minimized.

@zz-jason

zz-jason Jul 19, 2017

Member

done

Show outdated Hide outdated plan/plan.go
@@ -61,6 +61,8 @@ type Plan interface {
SetParents(...Plan)
// SetChildren sets the children for the plan.
SetChildren(...Plan)
// replaceEpxrColumns replace all the column reference in the plan's expression node

This comment has been minimized.

@lamxTyler

lamxTyler Jul 19, 2017

Member

Add ..

This comment has been minimized.

@lamxTyler

lamxTyler Jul 19, 2017

Member

replaceEpxr... -> replaceExpr...`

@lamxTyler

lamxTyler Jul 19, 2017

Member

replaceEpxr... -> replaceExpr...`

This comment has been minimized.

@zz-jason

zz-jason Jul 19, 2017

Member

done

@lamxTyler

LGTM

@hanfei1991 hanfei1991 added status/LGT2 and removed status/LGT1 labels Jul 19, 2017

@zz-jason zz-jason merged commit 9459f1d into master Jul 19, 2017

3 checks passed

ci/circleci Your tests passed on CircleCI!
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
license/cla Contributor License Agreement is signed.
Details

@zz-jason zz-jason deleted the 3681-eliminate-projection branch Jul 19, 2017

dbjoa added a commit to cloud-pi/tidb that referenced this pull request Jul 20, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment