Skip to content

Commit d9f7ffe

Browse files
authored
plan: fix a bug when using correlated column as index (#7357)
1 parent 224ea31 commit d9f7ffe

File tree

5 files changed

+254
-3
lines changed

5 files changed

+254
-3
lines changed

expression/column.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,18 @@ func IndexInfo2Cols(cols []*Column, index *model.IndexInfo) ([]*Column, []int) {
375375
}
376376
return retCols, lengths
377377
}
378+
379+
// FindColumnsByUniqueIDs will find columns by checking the unique id.
380+
// Note: `ids` must be a subset of the column slice.
381+
func FindColumnsByUniqueIDs(cols []*Column, ids []int) []*Column {
382+
retCols := make([]*Column, 0, len(ids))
383+
for _, id := range ids {
384+
for _, col := range cols {
385+
if col.UniqueID == id {
386+
retCols = append(retCols, col)
387+
break
388+
}
389+
}
390+
}
391+
return retCols
392+
}

expression/simple_rewriter.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,33 @@ func RewriteSimpleExprWithTableInfo(ctx sessionctx.Context, tbl *model.TableInfo
5656
return rewriter.pop(), nil
5757
}
5858

59+
func ParseSimpleExprsWithSchema(ctx sessionctx.Context, exprStr string, schema *Schema) ([]Expression, error) {
60+
exprStr = "select " + exprStr
61+
stmts, err := parser.New().Parse(exprStr, "", "")
62+
if err != nil {
63+
return nil, errors.Trace(err)
64+
}
65+
fields := stmts[0].(*ast.SelectStmt).Fields.Fields
66+
exprs := make([]Expression, 0, len(fields))
67+
for _, field := range fields {
68+
expr, err := RewriteSimpleExprWithSchema(ctx, field.Expr, schema)
69+
if err != nil {
70+
return nil, errors.Trace(err)
71+
}
72+
exprs = append(exprs, expr)
73+
}
74+
return exprs, nil
75+
}
76+
77+
func RewriteSimpleExprWithSchema(ctx sessionctx.Context, expr ast.ExprNode, schema *Schema) (Expression, error) {
78+
rewriter := &simpleRewriter{ctx: ctx, schema: schema}
79+
expr.Accept(rewriter)
80+
if rewriter.err != nil {
81+
return nil, errors.Trace(rewriter.err)
82+
}
83+
return rewriter.pop(), nil
84+
}
85+
5986
func (sr *simpleRewriter) rewriteColumn(nodeColName *ast.ColumnNameExpr) (*Column, error) {
6087
col := sr.schema.FindColumnByName(nodeColName.Name.Name.L)
6188
if col != nil {

plan/cbo_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ func (s *testAnalyzeSuite) TestCorrelatedEstimation(c *C) {
623623
store.Close()
624624
}()
625625
tk.MustExec("use test")
626-
tk.MustExec("create table t(a int, b int, c int)")
626+
tk.MustExec("create table t(a int, b int, c int, index idx(c))")
627627
tk.MustExec("insert into t values(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5), (6,6,6), (7,7,7), (8,8,8), (9,9,9),(10,10,10)")
628628
tk.MustExec("analyze table t")
629629
tk.MustQuery("explain select t.c in (select count(*) from t s , t t1 where s.a = t.a and s.a = t1.a) from t;").
@@ -640,6 +640,19 @@ func (s *testAnalyzeSuite) TestCorrelatedEstimation(c *C) {
640640
" └─TableReader_27 10.00 root data:TableScan_26",
641641
" └─TableScan_26 10.00 cop table:t1, range:[-inf,+inf], keep order:false",
642642
))
643+
tk.MustQuery("explain select (select concat(t1.a, \",\", t1.b) from t t1 where t1.a=t.a and t1.c=t.c) from t").
644+
Check(testkit.Rows(
645+
"Projection_8 10.00 root concat(t1.a, \",\", t1.b)",
646+
"└─Apply_10 10.00 root left outer join, inner:MaxOneRow_13",
647+
" ├─TableReader_12 10.00 root data:TableScan_11",
648+
" │ └─TableScan_11 10.00 cop table:t, range:[-inf,+inf], keep order:false",
649+
" └─MaxOneRow_13 1.00 root ",
650+
" └─Projection_14 0.80 root concat(cast(t1.a), \",\", cast(t1.b))",
651+
" └─IndexLookUp_21 0.80 root ",
652+
" ├─IndexScan_18 1.00 cop table:t1, index:c, range: decided by [eq(t1.c, test.t.c)], keep order:false",
653+
" └─Selection_20 0.80 cop eq(t1.a, test.t.a)",
654+
" └─TableScan_19 1.00 cop table:t, keep order:false",
655+
))
643656
}
644657

645658
func (s *testAnalyzeSuite) TestInconsistentEstimation(c *C) {

plan/logical_plans.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,14 +484,15 @@ func (path *accessPath) splitCorColAccessCondFromFilters() (access, remained []e
484484
for i := path.eqCondCount; i < len(path.idxCols); i++ {
485485
matched := false
486486
for j, filter := range path.tableFilters {
487-
if !isColEqCorColOrConstant(filter, path.idxCols[i]) {
488-
break
487+
if used[j] || !isColEqCorColOrConstant(filter, path.idxCols[i]) {
488+
continue
489489
}
490490
matched = true
491491
access[i-path.eqCondCount] = filter
492492
if path.idxColLens[i] == types.UnspecifiedLength {
493493
used[j] = true
494494
}
495+
break
495496
}
496497
if !matched {
497498
access = access[:i-path.eqCondCount]

plan/logical_plans_test.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright 2018 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain col1 copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package plan
15+
16+
import (
17+
"fmt"
18+
19+
"github.com/juju/errors"
20+
. "github.com/pingcap/check"
21+
"github.com/pingcap/tidb/ast"
22+
"github.com/pingcap/tidb/expression"
23+
"github.com/pingcap/tidb/model"
24+
"github.com/pingcap/tidb/mysql"
25+
"github.com/pingcap/tidb/sessionctx"
26+
"github.com/pingcap/tidb/types"
27+
"github.com/pingcap/tidb/util/testleak"
28+
)
29+
30+
var _ = Suite(&testUnitTestSuit{})
31+
32+
type testUnitTestSuit struct {
33+
ctx sessionctx.Context
34+
}
35+
36+
func (s *testUnitTestSuit) SetUpSuite(c *C) {
37+
s.ctx = mockContext()
38+
}
39+
40+
func (s *testUnitTestSuit) newTypeWithFlen(typeByte byte, flen int) *types.FieldType {
41+
tp := types.NewFieldType(typeByte)
42+
tp.Flen = flen
43+
return tp
44+
}
45+
46+
func (s *testUnitTestSuit) SubstituteCol2CorCol(expr expression.Expression, colIDs map[int]struct{}) (expression.Expression, error) {
47+
switch x := expr.(type) {
48+
case *expression.ScalarFunction:
49+
newArgs := make([]expression.Expression, 0, len(x.GetArgs()))
50+
for _, arg := range x.GetArgs() {
51+
newArg, err := s.SubstituteCol2CorCol(arg, colIDs)
52+
if err != nil {
53+
return nil, errors.Trace(err)
54+
}
55+
newArgs = append(newArgs, newArg)
56+
}
57+
newSf, err := expression.NewFunction(x.GetCtx(), x.FuncName.L, x.GetType(), newArgs...)
58+
return newSf, errors.Trace(err)
59+
case *expression.Column:
60+
if _, ok := colIDs[x.UniqueID]; ok {
61+
return &expression.CorrelatedColumn{Column: *x}, nil
62+
}
63+
}
64+
return expr, nil
65+
}
66+
67+
func (s *testUnitTestSuit) TestIndexPathSplitCorColCond(c *C) {
68+
defer testleak.AfterTest(c)()
69+
totalSchema := expression.NewSchema()
70+
totalSchema.Append(&expression.Column{
71+
ColName: model.NewCIStr("col1"),
72+
UniqueID: 1,
73+
RetType: types.NewFieldType(mysql.TypeLonglong),
74+
})
75+
totalSchema.Append(&expression.Column{
76+
ColName: model.NewCIStr("col2"),
77+
UniqueID: 2,
78+
RetType: types.NewFieldType(mysql.TypeLonglong),
79+
})
80+
totalSchema.Append(&expression.Column{
81+
ColName: model.NewCIStr("col3"),
82+
UniqueID: 3,
83+
RetType: s.newTypeWithFlen(mysql.TypeVarchar, 10),
84+
})
85+
totalSchema.Append(&expression.Column{
86+
ColName: model.NewCIStr("col4"),
87+
UniqueID: 4,
88+
RetType: s.newTypeWithFlen(mysql.TypeVarchar, 10),
89+
})
90+
totalSchema.Append(&expression.Column{
91+
ColName: model.NewCIStr("col5"),
92+
UniqueID: 5,
93+
RetType: types.NewFieldType(mysql.TypeLonglong),
94+
})
95+
testCases := []struct {
96+
expr string
97+
corColIDs []int
98+
idxColIDs []int
99+
idxColLens []int
100+
access string
101+
remained string
102+
}{
103+
{
104+
expr: "col1 = col2",
105+
corColIDs: []int{2},
106+
idxColIDs: []int{1},
107+
idxColLens: []int{types.UnspecifiedLength},
108+
access: "[eq(col1, col2)]",
109+
remained: "[]",
110+
},
111+
{
112+
expr: "col1 = col5 and col2 = 1",
113+
corColIDs: []int{5},
114+
idxColIDs: []int{1, 2},
115+
idxColLens: []int{types.UnspecifiedLength, types.UnspecifiedLength},
116+
access: "[eq(col1, col5) eq(col2, 1)]",
117+
remained: "[]",
118+
},
119+
{
120+
expr: "col1 = col5 and col2 = 1",
121+
corColIDs: []int{5},
122+
idxColIDs: []int{2, 1},
123+
idxColLens: []int{types.UnspecifiedLength, types.UnspecifiedLength},
124+
access: "[eq(col2, 1) eq(col1, col5)]",
125+
remained: "[]",
126+
},
127+
{
128+
expr: "col1 = col5 and col2 = 1",
129+
corColIDs: []int{5},
130+
idxColIDs: []int{1},
131+
idxColLens: []int{types.UnspecifiedLength},
132+
access: "[eq(col1, col5)]",
133+
remained: "[eq(col2, 1)]",
134+
},
135+
{
136+
expr: "col2 = 1 and col1 = col5",
137+
corColIDs: []int{5},
138+
idxColIDs: []int{1},
139+
idxColLens: []int{types.UnspecifiedLength},
140+
access: "[eq(col1, col5)]",
141+
remained: "[eq(col2, 1)]",
142+
},
143+
{
144+
expr: "col1 = col2 and col3 = col4 and col5 = 1",
145+
corColIDs: []int{2, 4},
146+
idxColIDs: []int{1, 3},
147+
idxColLens: []int{types.UnspecifiedLength, types.UnspecifiedLength},
148+
access: "[eq(col1, col2) eq(col3, col4)]",
149+
remained: "[eq(col5, 1)]",
150+
},
151+
{
152+
expr: "col1 = col2 and col3 = col4 and col5 = 1",
153+
corColIDs: []int{2, 4},
154+
idxColIDs: []int{1, 3},
155+
idxColLens: []int{types.UnspecifiedLength, 2},
156+
access: "[eq(col1, col2) eq(col3, col4)]",
157+
remained: "[eq(col3, col4) eq(col5, 1)]",
158+
},
159+
{
160+
expr: `col1 = col5 and col3 = "col1" and col2 = col5`,
161+
corColIDs: []int{5},
162+
idxColIDs: []int{1, 2, 3},
163+
idxColLens: []int{types.UnspecifiedLength, types.UnspecifiedLength, types.UnspecifiedLength},
164+
access: "[eq(col1, col5) eq(col2, col5) eq(col3, col1)]",
165+
remained: "[]",
166+
},
167+
}
168+
for _, tt := range testCases {
169+
comment := Commentf("failed at case:\nexpr: %v\ncorColIDs: %v\nidxColIDs: %v\nidxColLens: %v\naccess: %v\nremained: %v\n", tt.expr, tt.corColIDs, tt.idxColIDs, tt.idxColLens, tt.access, tt.remained)
170+
filters, err := expression.ParseSimpleExprsWithSchema(s.ctx, tt.expr, totalSchema)
171+
if sf, ok := filters[0].(*expression.ScalarFunction); ok && sf.FuncName.L == ast.LogicAnd {
172+
filters = expression.FlattenCNFConditions(sf)
173+
}
174+
c.Assert(err, IsNil, comment)
175+
trueFilters := make([]expression.Expression, 0, len(filters))
176+
idMap := make(map[int]struct{})
177+
for _, id := range tt.corColIDs {
178+
idMap[id] = struct{}{}
179+
}
180+
for _, filter := range filters {
181+
trueFilter, err := s.SubstituteCol2CorCol(filter, idMap)
182+
c.Assert(err, IsNil, comment)
183+
trueFilters = append(trueFilters, trueFilter)
184+
}
185+
path := accessPath{
186+
eqCondCount: 0,
187+
tableFilters: trueFilters,
188+
idxCols: expression.FindColumnsByUniqueIDs(totalSchema.Columns, tt.idxColIDs),
189+
idxColLens: tt.idxColLens,
190+
}
191+
access, remained := path.splitCorColAccessCondFromFilters()
192+
c.Assert(fmt.Sprintf("%s", access), Equals, tt.access, comment)
193+
c.Assert(fmt.Sprintf("%s", remained), Equals, tt.remained, comment)
194+
}
195+
}

0 commit comments

Comments
 (0)