Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/planner/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ go_library(
"rule_join_reorder.go",
"rule_join_reorder_dp.go",
"rule_join_reorder_greedy.go",
"rule_order_aware_join_reorder.go",
"rule_outer_to_inner_join.go",
"rule_predicate_push_down.go",
"rule_push_down_sequence.go",
Expand Down
2 changes: 1 addition & 1 deletion pkg/planner/core/casetest/rule/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ go_test(
],
data = glob(["testdata/**"]),
flaky = True,
shard_count = 25,
shard_count = 27,
deps = [
"//pkg/config",
"//pkg/domain",
Expand Down
5 changes: 5 additions & 0 deletions pkg/planner/core/casetest/rule/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func TestMain(m *testing.M) {
testDataMap.LoadTestSuiteData("testdata", "predicate_simplification", true)
testDataMap.LoadTestSuiteData("testdata", "outer_to_semi_join_suite", true)
testDataMap.LoadTestSuiteData("testdata", "cdc_join_reorder_suite", true)
testDataMap.LoadTestSuiteData("testdata", "order_aware_join_reorder_suite", true)

opts := []goleak.Option{
goleak.IgnoreTopFunction("github.com/golang/glog.(*fileSink).flushDaemon"),
Expand Down Expand Up @@ -82,3 +83,7 @@ func GetOuterToSemiJoinSuiteData() testdata.TestData {
func GetCDCJoinReorderSuiteData() testdata.TestData {
return testDataMap["cdc_join_reorder_suite"]
}

func GetOrderAwareJoinReorderSuiteData() testdata.TestData {
return testDataMap["order_aware_join_reorder_suite"]
}
99 changes: 94 additions & 5 deletions pkg/planner/core/casetest/rule/rule_cdc_join_reorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,26 @@ import (

"github.com/pingcap/tidb/pkg/testkit"
"github.com/pingcap/tidb/pkg/testkit/testdata"
"github.com/pingcap/tidb/pkg/testkit/testfailpoint"
"github.com/stretchr/testify/require"
)

func prepareOrderAwareJoinReorderTables(tk *testkit.TestKit) {
tk.MustExec("use test")
tk.MustExec("drop table if exists t6, t7, t8, t9")
tk.MustExec("create table t6(id int not null, category varchar(20), payload int, key idx_category_id_payload(category, id, payload))")
tk.MustExec("create table t7(id int not null primary key, payload int)")
tk.MustExec("create table t8(id int not null primary key, payload int)")
tk.MustExec("create table t9(id int not null primary key, payload int)")
tk.MustExec("insert into t6 values (1,'hot',10),(2,'hot',20),(3,'cold',30),(4,'hot',40)")
tk.MustExec("insert into t7 values (1,100),(2,200),(4,400)")
tk.MustExec("insert into t8 values (1,1000),(2,2000),(4,4000)")
tk.MustExec("insert into t9 values (1,10000),(2,20000),(4,40000)")
tk.MustExec("analyze table t6 all columns")
tk.MustExec("analyze table t7 all columns")
tk.MustExec("analyze table t8 all columns")
tk.MustExec("analyze table t9 all columns")
}

func TestCDCJoinReorder(tt *testing.T) {
testkit.RunTestUnderCascades(tt, func(t *testing.T, tk *testkit.TestKit, cascades, caller string) {
tk.MustExec("use test")
Expand Down Expand Up @@ -64,8 +80,6 @@ func TestCDCJoinReorder(tt *testing.T) {

// Phase 2: Enable CD-C algorithm, then verify both the plan and the
// result correctness for every case.
testfailpoint.Enable(t, "github.com/pingcap/tidb/pkg/planner/core/enableCDCJoinReorder", `return(true)`)

for i, sql := range input {
testdata.OnRecord(func() {
output[i].SQL = sql
Expand Down Expand Up @@ -104,8 +118,6 @@ func TestJoinReorderPushSelection(tt *testing.T) {
tk.MustExec("analyze table t4 all columns")
tk.MustExec("analyze table t5 all columns")

testfailpoint.Enable(t, "github.com/pingcap/tidb/pkg/planner/core/enableCDCJoinReorder", `return(true)`)

var input []string
var output []struct {
SQL string
Expand Down Expand Up @@ -145,3 +157,80 @@ func TestJoinReorderPushSelection(tt *testing.T) {
"unexpected output case count, output=%d, actual explain cases=%d", len(output), planCaseIdx)
})
}

func TestOrderAwareCDCJoinReorder(tt *testing.T) {
testkit.RunTestUnderCascades(tt, func(t *testing.T, tk *testkit.TestKit, cascades, caller string) {
prepareOrderAwareJoinReorderTables(tk)

var input []string
var output []struct {
SQL string
Plan []string
Result []string
}
suite := GetOrderAwareJoinReorderSuiteData()
suite.LoadTestCasesByName("TestOrderAwareCDCJoinReorder", t, &input, &output, cascades, caller)

expectedResults := make([][]string, len(input))
for i, sql := range input {
expectedResults[i] = testdata.ConvertRowsToStrings(tk.MustQuery(sql).Rows())
}

for i, sql := range input {
testdata.OnRecord(func() {
if i >= len(output) {
output = append(output, struct {
SQL string
Plan []string
Result []string
}{})
}
output[i].SQL = sql
output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery("EXPLAIN FORMAT='plan_tree' " + sql).Rows())
output[i].Result = testdata.ConvertRowsToStrings(tk.MustQuery(sql).Rows())
})
require.Lessf(t, i, len(output), "missing expected output for case[%d], sql: %s", i, sql)
require.Equalf(t, sql, output[i].SQL, "input/output SQL mismatch at case[%d]", i)
tk.MustQuery("EXPLAIN FORMAT='plan_tree' " + sql).Check(testkit.Rows(output[i].Plan...))
require.NotContains(t, strings.Join(testdata.ConvertRowsToStrings(tk.MustQuery("show warnings").Rows()), "\n"),
"leading hint is inapplicable")

cdcResult := testdata.ConvertRowsToStrings(tk.MustQuery(sql).Rows())
require.Equalf(t, expectedResults[i], cdcResult,
"CD-C result differs from old algorithm for case[%d]: %s", i, sql)
}
})
}

func TestOrderAwareJoinReorderPushSelection(tt *testing.T) {
testkit.RunTestUnderCascades(tt, func(t *testing.T, tk *testkit.TestKit, cascades, caller string) {
prepareOrderAwareJoinReorderTables(tk)
tk.MustExec("set @@tidb_opt_join_reorder_through_sel = 1")

var input []string
var output []struct {
SQL string
Plan []string
}
suite := GetOrderAwareJoinReorderSuiteData()
suite.LoadTestCasesByName("TestOrderAwareJoinReorderPushSelection", t, &input, &output, cascades, caller)

for i, sql := range input {
testdata.OnRecord(func() {
if i >= len(output) {
output = append(output, struct {
SQL string
Plan []string
}{})
}
output[i].SQL = sql
output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(sql).Rows())
})
require.Lessf(t, i, len(output), "missing expected output for case[%d], sql: %s", i, sql)
require.Equalf(t, sql, output[i].SQL, "input/output SQL mismatch at case[%d]", i)
tk.MustQuery(sql).Check(testkit.Rows(output[i].Plan...))
require.NotContains(t, strings.Join(testdata.ConvertRowsToStrings(tk.MustQuery("show warnings").Rows()), "\n"),
"leading hint is inapplicable")
}
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"name": "TestOrderAwareCDCJoinReorder",
"cases": [
"SELECT t6.id, t7.payload, t8.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"SELECT /*+ LEADING(t7, t8, t6) */ t6.id, t7.payload, t8.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"SELECT /*+ TIDB_INLJ(t7, t8, t9) */ t6.id, t7.payload, t8.payload, t9.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t9 ON t8.id = t9.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"SELECT /*+ TIDB_INLJ(t7, t8, t9) LEADING(t7, t8, t9, t6) */ t6.id, t7.payload, t8.payload, t9.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t9 ON t8.id = t9.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2"
]
},
{
"name": "TestOrderAwareJoinReorderPushSelection",
"cases": [
"explain format = 'plan_tree' select t6.id, t7.payload, t8.payload from t7 join t8 on t7.id = t8.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"explain format = 'plan_tree' select /*+ LEADING(t7, t8, t6) */ t6.id, t7.payload, t8.payload from t7 join t8 on t7.id = t8.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"explain format = 'plan_tree' select /*+ TIDB_INLJ(t7, t8, t9) */ t6.id, t7.payload, t8.payload, t9.payload from t7 join t8 on t7.id = t8.id join t9 on t8.id = t9.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"explain format = 'plan_tree' select /*+ TIDB_INLJ(t7, t8, t9) LEADING(t7, t8, t9, t6) */ t6.id, t7.payload, t8.payload, t9.payload from t7 join t8 on t7.id = t8.id join t9 on t8.id = t9.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2"
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
[
{
"Name": "TestOrderAwareCDCJoinReorder",
Comment thread
AilinKid marked this conversation as resolved.
Comment thread
AilinKid marked this conversation as resolved.
"Cases": [
{
"SQL": "SELECT t6.id, t7.payload, t8.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload",
"└─TopN root test.t6.id, offset:0, count:2",
" └─Projection root test.t7.payload, test.t8.payload, test.t6.id",
" └─MergeJoin root inner join, left key:test.t7.id, right key:test.t8.id",
" ├─TableReader(Build) root data:TableFullScan",
" │ └─TableFullScan cop[tikv] table:t8 keep order:true",
" └─MergeJoin(Probe) root inner join, left key:test.t6.id, right key:test.t7.id",
" ├─TableReader(Build) root data:TableFullScan",
" │ └─TableFullScan cop[tikv] table:t7 keep order:true",
" └─IndexReader(Probe) root index:IndexRangeScan",
" └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true"
],
"Result": [
"1 100 1000",
"2 200 2000"
]
},
{
"SQL": "SELECT /*+ LEADING(t7, t8, t6) */ t6.id, t7.payload, t8.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload",
"└─Limit root offset:0, count:2",
" └─MergeJoin root inner join, left key:test.t7.id, right key:test.t6.id",
" ├─IndexReader(Build) root index:IndexRangeScan",
" │ └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true",
" └─MergeJoin(Probe) root inner join, left key:test.t7.id, right key:test.t8.id",
" ├─TableReader(Build) root data:TableFullScan",
" │ └─TableFullScan cop[tikv] table:t8 keep order:true",
" └─TableReader(Probe) root data:TableFullScan",
" └─TableFullScan cop[tikv] table:t7 keep order:true"
],
"Result": [
"1 100 1000",
"2 200 2000"
]
},
{
"SQL": "SELECT /*+ TIDB_INLJ(t7, t8, t9) */ t6.id, t7.payload, t8.payload, t9.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t9 ON t8.id = t9.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload, test.t9.payload",
"└─Limit root offset:0, count:2",
" └─Projection root test.t7.payload, test.t8.payload, test.t9.payload, test.t6.id",
" └─IndexJoin root inner join, inner:TableReader, outer key:test.t8.id, inner key:test.t9.id, equal cond:eq(test.t8.id, test.t9.id)",
" ├─IndexJoin(Build) root inner join, inner:TableReader, outer key:test.t7.id, inner key:test.t8.id, equal cond:eq(test.t7.id, test.t8.id)",
" │ ├─IndexJoin(Build) root inner join, inner:TableReader, outer key:test.t6.id, inner key:test.t7.id, equal cond:eq(test.t6.id, test.t7.id)",
" │ │ ├─IndexReader(Build) root index:IndexRangeScan",
" │ │ │ └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true",
" │ │ └─TableReader(Probe) root data:TableRangeScan",
" │ │ └─TableRangeScan cop[tikv] table:t7 range: decided by [test.t6.id], keep order:false",
" │ └─TableReader(Probe) root data:TableRangeScan",
" │ └─TableRangeScan cop[tikv] table:t8 range: decided by [test.t7.id], keep order:false",
" └─TableReader(Probe) root data:TableRangeScan",
" └─TableRangeScan cop[tikv] table:t9 range: decided by [test.t8.id], keep order:false"
],
"Result": [
"1 100 1000 10000",
"2 200 2000 20000"
]
},
{
"SQL": "SELECT /*+ TIDB_INLJ(t7, t8, t9) LEADING(t7, t8, t9, t6) */ t6.id, t7.payload, t8.payload, t9.payload FROM t7 JOIN t8 ON t7.id = t8.id JOIN t9 ON t8.id = t9.id JOIN t6 ON t6.id = t7.id WHERE t6.category = 'hot' ORDER BY t6.id LIMIT 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload, test.t9.payload",
"└─Limit root offset:0, count:2",
" └─MergeJoin root inner join, left key:test.t7.id, right key:test.t6.id",
" ├─IndexReader(Build) root index:IndexRangeScan",
" │ └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true",
" └─IndexJoin(Probe) root inner join, inner:TableReader, outer key:test.t8.id, inner key:test.t9.id, equal cond:eq(test.t8.id, test.t9.id)",
" ├─IndexJoin(Build) root inner join, inner:TableReader, outer key:test.t7.id, inner key:test.t8.id, equal cond:eq(test.t7.id, test.t8.id)",
" │ ├─TableReader(Build) root data:TableFullScan",
" │ │ └─TableFullScan cop[tikv] table:t7 keep order:true",
" │ └─TableReader(Probe) root data:TableRangeScan",
" │ └─TableRangeScan cop[tikv] table:t8 range: decided by [test.t7.id], keep order:false",
" └─TableReader(Probe) root data:TableRangeScan",
" └─TableRangeScan cop[tikv] table:t9 range: decided by [test.t8.id], keep order:false"
],
"Result": [
"1 100 1000 10000",
"2 200 2000 20000"
]
}
]
},
{
"Name": "TestOrderAwareJoinReorderPushSelection",
"Cases": [
{
"SQL": "explain format = 'plan_tree' select t6.id, t7.payload, t8.payload from t7 join t8 on t7.id = t8.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload",
"└─TopN root test.t6.id, offset:0, count:2",
" └─Projection root test.t7.payload, test.t8.payload, test.t6.id",
" └─MergeJoin root inner join, left key:test.t7.id, right key:test.t8.id",
" ├─TableReader(Build) root data:TableFullScan",
" │ └─TableFullScan cop[tikv] table:t8 keep order:true",
" └─MergeJoin(Probe) root inner join, left key:test.t6.id, right key:test.t7.id",
" ├─TableReader(Build) root data:TableFullScan",
" │ └─TableFullScan cop[tikv] table:t7 keep order:true",
" └─IndexReader(Probe) root index:IndexRangeScan",
" └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true"
]
},
{
"SQL": "explain format = 'plan_tree' select /*+ LEADING(t7, t8, t6) */ t6.id, t7.payload, t8.payload from t7 join t8 on t7.id = t8.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload",
"└─Limit root offset:0, count:2",
" └─MergeJoin root inner join, left key:test.t7.id, right key:test.t6.id",
" ├─IndexReader(Build) root index:IndexRangeScan",
" │ └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true",
" └─MergeJoin(Probe) root inner join, left key:test.t7.id, right key:test.t8.id",
" ├─TableReader(Build) root data:TableFullScan",
" │ └─TableFullScan cop[tikv] table:t8 keep order:true",
" └─TableReader(Probe) root data:TableFullScan",
" └─TableFullScan cop[tikv] table:t7 keep order:true"
]
},
{
"SQL": "explain format = 'plan_tree' select /*+ TIDB_INLJ(t7, t8, t9) */ t6.id, t7.payload, t8.payload, t9.payload from t7 join t8 on t7.id = t8.id join t9 on t8.id = t9.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload, test.t9.payload",
"└─Limit root offset:0, count:2",
" └─Projection root test.t7.payload, test.t8.payload, test.t9.payload, test.t6.id",
" └─IndexJoin root inner join, inner:TableReader, outer key:test.t8.id, inner key:test.t9.id, equal cond:eq(test.t8.id, test.t9.id)",
" ├─IndexJoin(Build) root inner join, inner:TableReader, outer key:test.t7.id, inner key:test.t8.id, equal cond:eq(test.t7.id, test.t8.id)",
" │ ├─IndexJoin(Build) root inner join, inner:TableReader, outer key:test.t6.id, inner key:test.t7.id, equal cond:eq(test.t6.id, test.t7.id)",
" │ │ ├─IndexReader(Build) root index:IndexRangeScan",
" │ │ │ └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true",
" │ │ └─TableReader(Probe) root data:TableRangeScan",
" │ │ └─TableRangeScan cop[tikv] table:t7 range: decided by [test.t6.id], keep order:false",
" │ └─TableReader(Probe) root data:TableRangeScan",
" │ └─TableRangeScan cop[tikv] table:t8 range: decided by [test.t7.id], keep order:false",
" └─TableReader(Probe) root data:TableRangeScan",
" └─TableRangeScan cop[tikv] table:t9 range: decided by [test.t8.id], keep order:false"
]
},
{
"SQL": "explain format = 'plan_tree' select /*+ TIDB_INLJ(t7, t8, t9) LEADING(t7, t8, t9, t6) */ t6.id, t7.payload, t8.payload, t9.payload from t7 join t8 on t7.id = t8.id join t9 on t8.id = t9.id join t6 on t6.id = t7.id where t6.category = 'hot' order by t6.id limit 2",
"Plan": [
"Projection root test.t6.id, test.t7.payload, test.t8.payload, test.t9.payload",
"└─Limit root offset:0, count:2",
" └─MergeJoin root inner join, left key:test.t7.id, right key:test.t6.id",
" ├─IndexReader(Build) root index:IndexRangeScan",
" │ └─IndexRangeScan cop[tikv] table:t6, index:idx_category_id_payload(category, id, payload) range:[\"hot\",\"hot\"], keep order:true",
" └─IndexJoin(Probe) root inner join, inner:TableReader, outer key:test.t8.id, inner key:test.t9.id, equal cond:eq(test.t8.id, test.t9.id)",
" ├─IndexJoin(Build) root inner join, inner:TableReader, outer key:test.t7.id, inner key:test.t8.id, equal cond:eq(test.t7.id, test.t8.id)",
" │ ├─TableReader(Build) root data:TableFullScan",
" │ │ └─TableFullScan cop[tikv] table:t7 keep order:true",
" │ └─TableReader(Probe) root data:TableRangeScan",
" │ └─TableRangeScan cop[tikv] table:t8 range: decided by [test.t7.id], keep order:false",
" └─TableReader(Probe) root data:TableRangeScan",
" └─TableRangeScan cop[tikv] table:t9 range: decided by [test.t8.id], keep order:false"
]
}
]
}
]
Loading
Loading