Skip to content

Commit cbdf436

Browse files
authored
planner: fixing wrong result after applying predicate push down for CTEs (#47891)
close #47881
1 parent fc6166e commit cbdf436

File tree

9 files changed

+194
-11
lines changed

9 files changed

+194
-11
lines changed

pkg/planner/core/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ go_library(
4444
"point_get_plan.go",
4545
"preprocess.go",
4646
"property_cols_prune.go",
47+
"recheck_cte.go",
4748
"resolve_indices.go",
4849
"rule_aggregation_elimination.go",
4950
"rule_aggregation_push_down.go",

pkg/planner/core/logical_plan_builder.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -4858,13 +4858,21 @@ func (b *PlanBuilder) tryBuildCTE(ctx context.Context, tn *ast.TableName, asName
48584858
}
48594859

48604860
if cte.cteClass == nil {
4861-
cte.cteClass = &CTEClass{IsDistinct: cte.isDistinct, seedPartLogicalPlan: cte.seedLP,
4862-
recursivePartLogicalPlan: cte.recurLP, IDForStorage: cte.storageID,
4863-
optFlag: cte.optFlag, HasLimit: hasLimit, LimitBeg: limitBeg,
4864-
LimitEnd: limitEnd, pushDownPredicates: make([]expression.Expression, 0), ColumnMap: make(map[string]*expression.Column)}
4861+
cte.cteClass = &CTEClass{
4862+
IsDistinct: cte.isDistinct,
4863+
seedPartLogicalPlan: cte.seedLP,
4864+
recursivePartLogicalPlan: cte.recurLP,
4865+
IDForStorage: cte.storageID,
4866+
optFlag: cte.optFlag,
4867+
HasLimit: hasLimit,
4868+
LimitBeg: limitBeg,
4869+
LimitEnd: limitEnd,
4870+
pushDownPredicates: make([]expression.Expression, 0),
4871+
ColumnMap: make(map[string]*expression.Column),
4872+
}
48654873
}
48664874
var p LogicalPlan
4867-
lp := LogicalCTE{cteAsName: tn.Name, cteName: tn.Name, cte: cte.cteClass, seedStat: cte.seedStat, isOuterMostCTE: !b.buildingCTE}.Init(b.ctx, b.getSelectOffset())
4875+
lp := LogicalCTE{cteAsName: tn.Name, cteName: tn.Name, cte: cte.cteClass, seedStat: cte.seedStat}.Init(b.ctx, b.getSelectOffset())
48684876
prevSchema := cte.seedLP.Schema().Clone()
48694877
lp.SetSchema(getResultCTESchema(cte.seedLP.Schema(), b.ctx.GetSessionVars()))
48704878

pkg/planner/core/logical_plans.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -2339,6 +2339,7 @@ type CTEClass struct {
23392339
// pushDownPredicates may be push-downed by different references.
23402340
pushDownPredicates []expression.Expression
23412341
ColumnMap map[string]*expression.Column
2342+
isOuterMostCTE bool
23422343
}
23432344

23442345
const emptyCTEClassSize = int64(unsafe.Sizeof(CTEClass{}))
@@ -2370,11 +2371,10 @@ func (cc *CTEClass) MemoryUsage() (sum int64) {
23702371
type LogicalCTE struct {
23712372
logicalSchemaProducer
23722373

2373-
cte *CTEClass
2374-
cteAsName model.CIStr
2375-
cteName model.CIStr
2376-
seedStat *property.StatsInfo
2377-
isOuterMostCTE bool
2374+
cte *CTEClass
2375+
cteAsName model.CIStr
2376+
cteName model.CIStr
2377+
seedStat *property.StatsInfo
23782378

23792379
onlyUsedAsStorage bool
23802380
}

pkg/planner/core/optimizer.go

+3
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ func BuildLogicalPlanForTest(ctx context.Context, sctx sessionctx.Context, node
180180
if err != nil {
181181
return nil, err
182182
}
183+
if logic, ok := p.(LogicalPlan); ok {
184+
RecheckCTE(logic)
185+
}
183186
return p, err
184187
}
185188

pkg/planner/core/recheck_cte.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 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 a 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+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package core
16+
17+
import "github.com/pingcap/tidb/pkg/util/intset"
18+
19+
// RecheckCTE fills the IsOuterMostCTE field for CTEs.
20+
// It's a temp solution to before we fully use the Sequence to optimize the CTEs.
21+
// This func checks whether the CTE is referenced only by the main query or not.
22+
func RecheckCTE(p LogicalPlan) {
23+
visited := intset.NewFastIntSet()
24+
findCTEs(p, &visited, true)
25+
}
26+
27+
func findCTEs(
28+
p LogicalPlan,
29+
visited *intset.FastIntSet,
30+
isRootTree bool,
31+
) {
32+
if cteReader, ok := p.(*LogicalCTE); ok {
33+
cte := cteReader.cte
34+
if !isRootTree {
35+
// Set it to false since it's referenced by other CTEs.
36+
cte.isOuterMostCTE = false
37+
}
38+
if visited.Has(cte.IDForStorage) {
39+
return
40+
}
41+
visited.Insert(cte.IDForStorage)
42+
// Set it when we meet it first time.
43+
cte.isOuterMostCTE = isRootTree
44+
findCTEs(cte.seedPartLogicalPlan, visited, false)
45+
if cte.recursivePartLogicalPlan != nil {
46+
findCTEs(cte.recursivePartLogicalPlan, visited, false)
47+
}
48+
return
49+
}
50+
for _, child := range p.Children() {
51+
findCTEs(child, visited, isRootTree)
52+
}
53+
}

pkg/planner/core/rule_predicate_push_down.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ func (p *LogicalCTE) PredicatePushDown(predicates []expression.Expression, _ *lo
998998
// Doesn't support recursive CTE yet.
999999
return predicates, p.self
10001000
}
1001-
if !p.isOuterMostCTE {
1001+
if !p.cte.isOuterMostCTE {
10021002
return predicates, p.self
10031003
}
10041004
pushedPredicates := make([]expression.Expression, len(predicates))

pkg/planner/optimize.go

+2
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,8 @@ func optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
520520
return p, names, 0, nil
521521
}
522522

523+
core.RecheckCTE(logic)
524+
523525
// Handle the logical plan statement, use cascades planner if enabled.
524526
if sessVars.GetEnableCascadesPlanner() {
525527
finalPlan, cost, err := cascades.DefaultOptimizer.FindBestPlan(sctx, logic)

tests/integrationtest/r/planner/core/issuetest/planner_issue.result

+59
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,62 @@ select /*+ use_index_merge( tbl_39) */ col_239 from tbl_39 where not( tbl_39.c
121121
col_239
122122
1994
123123
1997
124+
drop table if exists t, t1, t2;
125+
create table t (id int,name varchar(10));
126+
insert into t values(1,'tt');
127+
create table t1(id int,name varchar(10),name1 varchar(10),name2 varchar(10));
128+
insert into t1 values(1,'tt','ttt','tttt'),(2,'dd','ddd','dddd');
129+
create table t2(id int,name varchar(10),name1 varchar(10),name2 varchar(10),`date1` date);
130+
insert into t2 values(1,'tt','ttt','tttt','2099-12-31'),(2,'dd','ddd','dddd','2099-12-31');
131+
WITH bzzs AS (
132+
SELECT
133+
count(1) AS bzn
134+
FROM
135+
t c
136+
),
137+
tmp1 AS (
138+
SELECT
139+
t1.*
140+
FROM
141+
t1
142+
LEFT JOIN bzzs ON 1 = 1
143+
WHERE
144+
name IN ('tt')
145+
AND bzn <> 1
146+
),
147+
tmp2 AS (
148+
SELECT
149+
tmp1.*,
150+
date('2099-12-31') AS endate
151+
FROM
152+
tmp1
153+
),
154+
tmp3 AS (
155+
SELECT
156+
*
157+
FROM
158+
tmp2
159+
WHERE
160+
endate > CURRENT_DATE
161+
UNION ALL
162+
SELECT
163+
'1' AS id,
164+
'ss' AS name,
165+
'sss' AS name1,
166+
'ssss' AS name2,
167+
date('2099-12-31') AS endate
168+
FROM
169+
bzzs t1
170+
WHERE
171+
bzn = 1
172+
)
173+
SELECT
174+
c2.id,
175+
c3.id
176+
FROM
177+
t2 db
178+
LEFT JOIN tmp3 c2 ON c2.id = '1'
179+
LEFT JOIN tmp3 c3 ON c3.id = '1';
180+
id id
181+
1 1
182+
1 1

tests/integrationtest/t/planner/core/issuetest/planner_issue.test

+57
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,60 @@ insert into tbl_39 values (1994),(1995),(1996),(1997);
7979
explain select /*+ use_index_merge( tbl_39) */ col_239 from tbl_39 where not( tbl_39.col_239 not in ( '1994' ) ) and tbl_39.col_239 not in ( '2004' , '2010' , '2010' ) or not( tbl_39.col_239 <= '1996' ) and not( tbl_39.col_239 between '2026' and '2011' ) order by tbl_39.col_239 limit 382;
8080
select /*+ use_index_merge( tbl_39) */ col_239 from tbl_39 where not( tbl_39.col_239 not in ( '1994' ) ) and tbl_39.col_239 not in ( '2004' , '2010' , '2010' ) or not( tbl_39.col_239 <= '1996' ) and not( tbl_39.col_239 between '2026' and '2011' ) order by tbl_39.col_239 limit 382;
8181

82+
# https://github.com/pingcap/tidb/issues/47881
83+
drop table if exists t, t1, t2;
84+
create table t (id int,name varchar(10));
85+
insert into t values(1,'tt');
86+
create table t1(id int,name varchar(10),name1 varchar(10),name2 varchar(10));
87+
insert into t1 values(1,'tt','ttt','tttt'),(2,'dd','ddd','dddd');
88+
create table t2(id int,name varchar(10),name1 varchar(10),name2 varchar(10),`date1` date);
89+
insert into t2 values(1,'tt','ttt','tttt','2099-12-31'),(2,'dd','ddd','dddd','2099-12-31');
90+
WITH bzzs AS (
91+
SELECT
92+
count(1) AS bzn
93+
FROM
94+
t c
95+
),
96+
tmp1 AS (
97+
SELECT
98+
t1.*
99+
FROM
100+
t1
101+
LEFT JOIN bzzs ON 1 = 1
102+
WHERE
103+
name IN ('tt')
104+
AND bzn <> 1
105+
),
106+
tmp2 AS (
107+
SELECT
108+
tmp1.*,
109+
date('2099-12-31') AS endate
110+
FROM
111+
tmp1
112+
),
113+
tmp3 AS (
114+
SELECT
115+
*
116+
FROM
117+
tmp2
118+
WHERE
119+
endate > CURRENT_DATE
120+
UNION ALL
121+
SELECT
122+
'1' AS id,
123+
'ss' AS name,
124+
'sss' AS name1,
125+
'ssss' AS name2,
126+
date('2099-12-31') AS endate
127+
FROM
128+
bzzs t1
129+
WHERE
130+
bzn = 1
131+
)
132+
SELECT
133+
c2.id,
134+
c3.id
135+
FROM
136+
t2 db
137+
LEFT JOIN tmp3 c2 ON c2.id = '1'
138+
LEFT JOIN tmp3 c3 ON c3.id = '1';

0 commit comments

Comments
 (0)