-
Notifications
You must be signed in to change notification settings - Fork 6.2k
planner: correlate subquery rule #66206
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
Changes from 2 commits
da93b6f
171333a
fca2997
1d7e38a
0f6c877
62df8f8
5ba3cca
a0631a5
2a39db6
c1a3d75
88efc6a
0511568
a807f60
7fb0ecc
14ff48e
155437a
261c6e8
b4b1be2
88bfc9b
9e14702
ac97f9e
e53692a
7d49f71
4c32338
6121e3a
dd0f84f
448665b
34bbc9e
be086e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // Copyright 2025 PingCAP, Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package rule | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/pingcap/tidb/pkg/testkit" | ||
| "github.com/pingcap/tidb/pkg/testkit/testdata" | ||
| ) | ||
|
|
||
| func TestCorrelate(tt *testing.T) { | ||
| testkit.RunTestUnderCascades(tt, func(t *testing.T, tk *testkit.TestKit, cascades, caller string) { | ||
| tk.MustExec("use test") | ||
| tk.MustExec("drop table if exists t1, t2") | ||
| tk.MustExec("create table t1 (a int, b int, key(a))") | ||
| tk.MustExec("create table t2 (a int, b int, key(a))") | ||
| tk.MustExec("insert into t1 values (1,1),(2,2),(3,3)") | ||
| tk.MustExec("insert into t2 values (1,10),(2,20)") | ||
|
|
||
| // Enable the correlate rule. | ||
| tk.MustExec("set tidb_opt_enable_correlate_subquery = ON") | ||
|
|
||
| var input []string | ||
| var output []struct { | ||
| SQL string | ||
| Plan []string | ||
| Result []string | ||
| } | ||
| suite := GetCorrelateSuiteData() | ||
| suite.LoadTestCases(t, &input, &output, cascades, caller) | ||
| for i, sql := range input { | ||
| testdata.OnRecord(func() { | ||
| output[i].SQL = sql | ||
| output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery("explain format = 'brief' " + sql).Rows()) | ||
| output[i].Result = testdata.ConvertRowsToStrings(tk.MustQuery(sql).Rows()) | ||
| }) | ||
| tk.MustQuery("explain format = 'brief' " + sql).Check(testkit.Rows(output[i].Plan...)) | ||
| tk.MustQuery(sql).Check(testkit.Rows(output[i].Result...)) | ||
| } | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [ | ||
| { | ||
| "name": "TestCorrelate", | ||
| "cases": [ | ||
| "select * from t1 where exists (select 1 from t2 where t2.a = t1.a)", | ||
| "select * from t1 where not exists (select 1 from t2 where t2.a = t1.a)" | ||
| ] | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| [ | ||
| { | ||
| "Name": "TestCorrelate", | ||
| "Cases": [ | ||
| { | ||
| "SQL": "select * from t1 where exists (select 1 from t2 where t2.a = t1.a)", | ||
| "Plan": [ | ||
| "Apply 9990.00 root CARTESIAN semi join, left side:TableReader", | ||
| "├─TableReader(Build) 9990.00 root data:Selection", | ||
| "│ └─Selection 9990.00 cop[tikv] not(isnull(test.t1.a))", | ||
| "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", | ||
| "└─IndexReader(Probe) 99900.00 root index:IndexRangeScan", | ||
| " └─IndexRangeScan 99900.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo" | ||
| ], | ||
| "Result": [ | ||
| "1 1", | ||
| "2 2" | ||
| ] | ||
| }, | ||
| { | ||
| "SQL": "select * from t1 where not exists (select 1 from t2 where t2.a = t1.a)", | ||
| "Plan": [ | ||
| "Apply 10000.00 root CARTESIAN anti semi join, left side:TableReader", | ||
| "├─TableReader(Build) 10000.00 root data:TableFullScan", | ||
| "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", | ||
| "└─IndexReader(Probe) 100000.00 root index:IndexRangeScan", | ||
| " └─IndexRangeScan 100000.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo" | ||
| ], | ||
| "Result": [ | ||
| "3 3" | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| [ | ||
| { | ||
| "Name": "TestCorrelate", | ||
| "Cases": [ | ||
| { | ||
| "SQL": "select * from t1 where exists (select 1 from t2 where t2.a = t1.a)", | ||
| "Plan": [ | ||
| "Apply 9990.00 root CARTESIAN semi join, left side:TableReader", | ||
| "├─TableReader(Build) 9990.00 root data:Selection", | ||
| "│ └─Selection 9990.00 cop[tikv] not(isnull(test.t1.a))", | ||
| "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", | ||
| "└─IndexReader(Probe) 99900.00 root index:IndexRangeScan", | ||
| " └─IndexRangeScan 99900.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo" | ||
| ], | ||
| "Result": [ | ||
| "1 1", | ||
| "2 2" | ||
| ] | ||
| }, | ||
| { | ||
| "SQL": "select * from t1 where not exists (select 1 from t2 where t2.a = t1.a)", | ||
| "Plan": [ | ||
| "Apply 10000.00 root CARTESIAN anti semi join, left side:TableReader", | ||
| "├─TableReader(Build) 10000.00 root data:TableFullScan", | ||
| "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", | ||
| "└─IndexReader(Probe) 100000.00 root index:IndexRangeScan", | ||
| " └─IndexRangeScan 100000.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo" | ||
| ], | ||
| "Result": [ | ||
| "3 3" | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,6 +110,7 @@ var optRuleList = []base.LogicalOptRule{ | |
| &EliminateUnionAllDualItem{}, | ||
| &EmptySelectionEliminator{}, | ||
| &ResolveExpand{}, | ||
| &CorrelateSolver{}, | ||
| } | ||
|
|
||
| // Interaction Rule List | ||
|
|
@@ -344,10 +345,6 @@ func VolcanoOptimize(ctx context.Context, sctx base.PlanContext, flag uint64, lo | |
| } | ||
|
|
||
| func adjustOptimizationFlags(flag uint64, logic base.LogicalPlan) uint64 { | ||
| // If there is something after flagPrunColumns, do FlagPruneColumnsAgain. | ||
| if flag&rule.FlagPruneColumns > 0 && flag-rule.FlagPruneColumns > rule.FlagPruneColumns { | ||
| flag |= rule.FlagPruneColumnsAgain | ||
| } | ||
| if checkStableResultMode(logic.SCtx()) { | ||
| flag |= rule.FlagStabilizeResults | ||
| } | ||
|
|
@@ -363,6 +360,20 @@ func adjustOptimizationFlags(flag uint64, logic base.LogicalPlan) uint64 { | |
| if !logic.SCtx().GetSessionVars().StmtCtx.UseDynamicPruneMode { | ||
| flag |= rule.FlagPartitionProcessor // apply partition pruning under static mode | ||
| } | ||
| if logic.SCtx().GetSessionVars().EnableCorrelateSubquery { | ||
| flag |= rule.FlagCorrelate | ||
| } | ||
| // Recompute FlagPruneColumnsAgain after all conditional flag mutations so | ||
| // that conditionally-added flags (FlagCorrelate, FlagPartitionProcessor, …) | ||
| // are taken into account. A second column-prune pass is worthwhile when | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this flag revert is complex
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call — moving CorrelateSolver (and FlagCorrelate) ahead of the second ColumnPruner in both the rule list and the iota block would let the standard FlagPruneColumnsAgain logic work naturally, eliminating the recomputation block. The correlate rule doesn't depend on PushDownSequence/EliminateUnionAllDualItem/EmptySelectionEliminator/ResolveExpand, so the reorder should be safe. Change will be made in next commit. |
||
| // any rule above column pruning is enabled. | ||
| if flag&rule.FlagPruneColumns != 0 { | ||
| // Mask of all flag bits strictly above FlagPruneColumns. | ||
| const abovePruneColumns = ^(rule.FlagPruneColumns | (rule.FlagPruneColumns - 1)) | ||
| if flag&abovePruneColumns != 0 { | ||
| flag |= rule.FlagPruneColumnsAgain | ||
| } | ||
| } | ||
| return flag | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.