Skip to content

Commit e6732ae

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

9 files changed

+141
-12
lines changed

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",

planner/core/issuetest/BUILD.bazel

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ go_test(
66
srcs = ["planner_issue_test.go"],
77
flaky = True,
88
race = "on",
9-
shard_count = 6,
9+
shard_count = 7,
1010
deps = ["//testkit"],
1111
)

planner/core/issuetest/planner_issue_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,65 @@ func TestIssue46083(t *testing.T) {
115115
tk.MustExec("CREATE TEMPORARY TABLE v0(v1 int)")
116116
tk.MustExec("INSERT INTO v0 WITH ta2 AS (TABLE v0) TABLE ta2 FOR UPDATE OF ta2;")
117117
}
118+
119+
func TestIssue47881(t *testing.T) {
120+
store := testkit.CreateMockStore(t)
121+
tk := testkit.NewTestKit(t, store)
122+
tk.MustExec("use test")
123+
tk.MustExec("create table t (id int,name varchar(10));")
124+
tk.MustExec("insert into t values(1,'tt');")
125+
tk.MustExec("create table t1(id int,name varchar(10),name1 varchar(10),name2 varchar(10));")
126+
tk.MustExec("insert into t1 values(1,'tt','ttt','tttt'),(2,'dd','ddd','dddd');")
127+
tk.MustExec("create table t2(id int,name varchar(10),name1 varchar(10),name2 varchar(10),`date1` date);")
128+
tk.MustExec("insert into t2 values(1,'tt','ttt','tttt','2099-12-31'),(2,'dd','ddd','dddd','2099-12-31');")
129+
rs := tk.MustQuery(`WITH bzzs AS (
130+
SELECT
131+
count(1) AS bzn
132+
FROM
133+
t c
134+
),
135+
tmp1 AS (
136+
SELECT
137+
t1.*
138+
FROM
139+
t1
140+
LEFT JOIN bzzs ON 1 = 1
141+
WHERE
142+
name IN ('tt')
143+
AND bzn <> 1
144+
),
145+
tmp2 AS (
146+
SELECT
147+
tmp1.*,
148+
date('2099-12-31') AS endate
149+
FROM
150+
tmp1
151+
),
152+
tmp3 AS (
153+
SELECT
154+
*
155+
FROM
156+
tmp2
157+
WHERE
158+
endate > CURRENT_DATE
159+
UNION ALL
160+
SELECT
161+
'1' AS id,
162+
'ss' AS name,
163+
'sss' AS name1,
164+
'ssss' AS name2,
165+
date('2099-12-31') AS endate
166+
FROM
167+
bzzs t1
168+
WHERE
169+
bzn = 1
170+
)
171+
SELECT
172+
c2.id,
173+
c3.id
174+
FROM
175+
t2 db
176+
LEFT JOIN tmp3 c2 ON c2.id = '1'
177+
LEFT JOIN tmp3 c3 ON c3.id = '1';`)
178+
rs.Check(testkit.Rows("1 1", "1 1"))
179+
}

planner/core/logical_plan_builder.go

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

44934493
if cte.cteClass == nil {
4494-
cte.cteClass = &CTEClass{IsDistinct: cte.isDistinct, seedPartLogicalPlan: cte.seedLP,
4495-
recursivePartLogicalPlan: cte.recurLP, IDForStorage: cte.storageID,
4496-
optFlag: cte.optFlag, HasLimit: hasLimit, LimitBeg: limitBeg,
4497-
LimitEnd: limitEnd, pushDownPredicates: make([]expression.Expression, 0), ColumnMap: make(map[string]*expression.Column)}
4494+
cte.cteClass = &CTEClass{
4495+
IsDistinct: cte.isDistinct,
4496+
seedPartLogicalPlan: cte.seedLP,
4497+
recursivePartLogicalPlan: cte.recurLP,
4498+
IDForStorage: cte.storageID,
4499+
optFlag: cte.optFlag,
4500+
HasLimit: hasLimit,
4501+
LimitBeg: limitBeg,
4502+
LimitEnd: limitEnd,
4503+
pushDownPredicates: make([]expression.Expression, 0),
4504+
ColumnMap: make(map[string]*expression.Column),
4505+
}
44984506
}
44994507
var p LogicalPlan
4500-
lp := LogicalCTE{cteAsName: tn.Name, cteName: tn.Name, cte: cte.cteClass, seedStat: cte.seedStat, isOuterMostCTE: !b.buildingCTE}.Init(b.ctx, b.getSelectOffset())
4508+
lp := LogicalCTE{cteAsName: tn.Name, cteName: tn.Name, cte: cte.cteClass, seedStat: cte.seedStat}.Init(b.ctx, b.getSelectOffset())
45014509
prevSchema := cte.seedLP.Schema().Clone()
45024510
lp.SetSchema(getResultCTESchema(cte.seedLP.Schema(), b.ctx.GetSessionVars()))
45034511

planner/core/logical_plans.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,7 @@ type CTEClass struct {
20062006
// pushDownPredicates may be push-downed by different references.
20072007
pushDownPredicates []expression.Expression
20082008
ColumnMap map[string]*expression.Column
2009+
isOuterMostCTE bool
20092010
}
20102011

20112012
const emptyCTEClassSize = int64(unsafe.Sizeof(CTEClass{}))
@@ -2037,11 +2038,10 @@ func (cc *CTEClass) MemoryUsage() (sum int64) {
20372038
type LogicalCTE struct {
20382039
logicalSchemaProducer
20392040

2040-
cte *CTEClass
2041-
cteAsName model.CIStr
2042-
cteName model.CIStr
2043-
seedStat *property.StatsInfo
2044-
isOuterMostCTE bool
2041+
cte *CTEClass
2042+
cteAsName model.CIStr
2043+
cteName model.CIStr
2044+
seedStat *property.StatsInfo
20452045
}
20462046

20472047
// LogicalCTETable is for CTE table

planner/core/optimizer.go

+3
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ func BuildLogicalPlanForTest(ctx context.Context, sctx sessionctx.Context, node
158158
if err != nil {
159159
return nil, nil, err
160160
}
161+
if logic, ok := p.(LogicalPlan); ok {
162+
RecheckCTE(logic)
163+
}
161164
return p, p.OutputNames(), err
162165
}
163166

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/planner/funcdep"
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 := funcdep.NewFastIntSet()
24+
findCTEs(p, &visited, true)
25+
}
26+
27+
func findCTEs(
28+
p LogicalPlan,
29+
visited *funcdep.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+
}

planner/core/rule_predicate_push_down.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ func (p *LogicalCTE) PredicatePushDown(predicates []expression.Expression, _ *lo
985985
// Doesn't support recursive CTE yet.
986986
return predicates, p.self
987987
}
988-
if !p.isOuterMostCTE {
988+
if !p.cte.isOuterMostCTE {
989989
return predicates, p.self
990990
}
991991
pushedPredicates := make([]expression.Expression, len(predicates))

planner/optimize.go

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

507+
core.RecheckCTE(logic)
508+
507509
// Handle the logical plan statement, use cascades planner if enabled.
508510
if sessVars.GetEnableCascadesPlanner() {
509511
finalPlan, cost, err := cascades.DefaultOptimizer.FindBestPlan(sctx, logic)

0 commit comments

Comments
 (0)