diff --git a/planner/core/casetest/physical_plan_test.go b/planner/core/casetest/physical_plan_test.go index 90a191dcc6504..22d2304fff9aa 100644 --- a/planner/core/casetest/physical_plan_test.go +++ b/planner/core/casetest/physical_plan_test.go @@ -997,6 +997,62 @@ func TestMPPRightSemiJoin(t *testing.T) { } } +func TestMPPRightOuterJoin(t *testing.T) { + store := testkit.CreateMockStore(t, internal.WithMockTiFlash(3)) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1 (a int, c int)") + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t2 (b int, d int)") + + tk.MustExec("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50);") + tk.MustExec("insert into t2 values (1, 12), (2, 18), (7, 66);") + + { + tk.MustExec("alter table t1 set tiflash replica 1") + tb := external.GetTableByName(t, tk, "test", "t1") + err := domain.GetDomain(tk.Session()).DDL().UpdateTableReplicaInfo(tk.Session(), tb.Meta().ID, true) + require.NoError(t, err) + } + { + tk.MustExec("alter table t2 set tiflash replica 1") + tb := external.GetTableByName(t, tk, "test", "t2") + err := domain.GetDomain(tk.Session()).DDL().UpdateTableReplicaInfo(tk.Session(), tb.Meta().ID, true) + require.NoError(t, err) + } + tk.MustExec("analyze table t1") + tk.MustExec("analyze table t2") + tk.MustExec("set @@tidb_allow_mpp=1; set @@tidb_enforce_mpp=1;") + { + var input []string + var output []struct { + SQL string + Plan []string + Warn []string + } + planSuiteData := GetPlanSuiteData() + planSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + }) + if strings.HasPrefix(tt, "set") || strings.HasPrefix(tt, "insert") { + tk.MustExec(tt) + continue + } + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + output[i].Warn = testdata.ConvertSQLWarnToStrings(tk.Session().GetSessionVars().StmtCtx.GetWarnings()) + }) + res := tk.MustQuery(tt) + res.Check(testkit.Rows(output[i].Plan...)) + require.Equal(t, output[i].Warn, testdata.ConvertSQLWarnToStrings(tk.Session().GetSessionVars().StmtCtx.GetWarnings())) + } + } +} + func TestHintScope(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) diff --git a/planner/core/casetest/testdata/integration_suite_out.json b/planner/core/casetest/testdata/integration_suite_out.json index 54ae756ac6907..062219d5ca773 100644 --- a/planner/core/casetest/testdata/integration_suite_out.json +++ b/planner/core/casetest/testdata/integration_suite_out.json @@ -3832,13 +3832,13 @@ " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", " └─HashAgg 1.00 mpp[tiflash] funcs:count(1)->Column#12", " └─HashJoin 32.00 mpp[tiflash] right outer join, equal:[eq(test.fact_t.d1_k, test.d1_t.d1_k)], right cond:gt(test.d1_t.value, 10), other cond:gt(test.fact_t.col1, test.d1_t.value)", - " ├─ExchangeReceiver(Build) 16.00 mpp[tiflash] ", - " │ └─ExchangeSender 16.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.fact_t.d1_k, collate: binary]", - " │ └─Selection 16.00 mpp[tiflash] not(isnull(test.fact_t.col1)), not(isnull(test.fact_t.d1_k))", - " │ └─TableFullScan 16.00 mpp[tiflash] table:fact_t pushed down filter:empty, keep order:false", - " └─ExchangeReceiver(Probe) 4.00 mpp[tiflash] ", - " └─ExchangeSender 4.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.d1_t.d1_k, collate: binary]", - " └─TableFullScan 4.00 mpp[tiflash] table:d1_t keep order:false" + " ├─ExchangeReceiver(Build) 4.00 mpp[tiflash] ", + " │ └─ExchangeSender 4.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.d1_t.d1_k, collate: binary]", + " │ └─TableFullScan 4.00 mpp[tiflash] table:d1_t keep order:false", + " └─ExchangeReceiver(Probe) 16.00 mpp[tiflash] ", + " └─ExchangeSender 16.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.fact_t.d1_k, collate: binary]", + " └─Selection 16.00 mpp[tiflash] not(isnull(test.fact_t.col1)), not(isnull(test.fact_t.d1_k))", + " └─TableFullScan 16.00 mpp[tiflash] table:fact_t pushed down filter:empty, keep order:false" ] }, { diff --git a/planner/core/casetest/testdata/plan_suite_in.json b/planner/core/casetest/testdata/plan_suite_in.json index 9621e57e9db88..ec2c383b60bd7 100644 --- a/planner/core/casetest/testdata/plan_suite_in.json +++ b/planner/core/casetest/testdata/plan_suite_in.json @@ -166,6 +166,17 @@ "explain select * from t1 where exists (select * from t2 where t1.a=t2.b)" ] }, + { + "name": "TestMPPRightOuterJoin", + "cases": [ + "set @@session.tidb_allow_mpp=true", + "explain select * from t1 right join t2 on t1.a=t2.b and t1.c < t2.d", + "set @@session.tidb_prefer_broadcast_join_by_exchange_data_size=0", + "set @@session.tidb_broadcast_join_threshold_size=0", + "set @@session.tidb_broadcast_join_threshold_count=0", + "explain select * from t1 right join t2 on t1.a=t2.b and t1.c < t2.d" + ] + }, { "name": "TestIssue37520", "cases": [ diff --git a/planner/core/casetest/testdata/plan_suite_out.json b/planner/core/casetest/testdata/plan_suite_out.json index 8710357fc8f16..056a964d6c5aa 100644 --- a/planner/core/casetest/testdata/plan_suite_out.json +++ b/planner/core/casetest/testdata/plan_suite_out.json @@ -1629,6 +1629,61 @@ } ] }, + { + "Name": "TestMPPRightOuterJoin", + "Cases": [ + { + "SQL": "set @@session.tidb_allow_mpp=true", + "Plan": null, + "Warn": null + }, + { + "SQL": "explain select * from t1 right join t2 on t1.a=t2.b and t1.c < t2.d", + "Plan": [ + "TableReader_30 3.00 root MppVersion: 1, data:ExchangeSender_29", + "└─ExchangeSender_29 3.00 mpp[tiflash] ExchangeType: PassThrough", + " └─HashJoin_28 3.00 mpp[tiflash] right outer join, equal:[eq(test.t1.a, test.t2.b)], other cond:lt(test.t1.c, test.t2.d)", + " ├─ExchangeReceiver_14(Build) 5.00 mpp[tiflash] ", + " │ └─ExchangeSender_13 5.00 mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Selection_12 5.00 mpp[tiflash] not(isnull(test.t1.a)), not(isnull(test.t1.c))", + " │ └─TableFullScan_11 5.00 mpp[tiflash] table:t1 pushed down filter:empty, keep order:false", + " └─TableFullScan_15(Probe) 3.00 mpp[tiflash] table:t2 keep order:false" + ], + "Warn": null + }, + { + "SQL": "set @@session.tidb_prefer_broadcast_join_by_exchange_data_size=0", + "Plan": null, + "Warn": null + }, + { + "SQL": "set @@session.tidb_broadcast_join_threshold_size=0", + "Plan": null, + "Warn": null + }, + { + "SQL": "set @@session.tidb_broadcast_join_threshold_count=0", + "Plan": null, + "Warn": null + }, + { + "SQL": "explain select * from t1 right join t2 on t1.a=t2.b and t1.c < t2.d", + "Plan": [ + "TableReader_32 3.00 root MppVersion: 1, data:ExchangeSender_31", + "└─ExchangeSender_31 3.00 mpp[tiflash] ExchangeType: PassThrough", + " └─HashJoin_30 3.00 mpp[tiflash] right outer join, equal:[eq(test.t1.a, test.t2.b)], other cond:lt(test.t1.c, test.t2.d)", + " ├─ExchangeReceiver_17(Build) 3.00 mpp[tiflash] ", + " │ └─ExchangeSender_16 3.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t2.b, collate: binary]", + " │ └─TableFullScan_15 3.00 mpp[tiflash] table:t2 keep order:false", + " └─ExchangeReceiver_14(Probe) 5.00 mpp[tiflash] ", + " └─ExchangeSender_13 5.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.a, collate: binary]", + " └─Selection_12 5.00 mpp[tiflash] not(isnull(test.t1.a)), not(isnull(test.t1.c))", + " └─TableFullScan_11 5.00 mpp[tiflash] table:t1 pushed down filter:empty, keep order:false" + ], + "Warn": null + } + ] + }, { "Name": "TestIssue37520", "Cases": [ diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index c3fb82fde886e..5e7b8d96f5d7e 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -2416,8 +2416,8 @@ func (p *LogicalJoin) tryToGetMppHashJoin(prop *property.PhysicalProperty, useBC // so we can choose the build side based on the row count, except that: // 1. it is a broadcast join(for broadcast join, it makes sense to use the broadcast side as the build side) // 2. or session variable MPPOuterJoinFixedBuildSide is set to true - // 3. or there are otherConditions for this join - if useBCJ || p.ctx.GetSessionVars().MPPOuterJoinFixedBuildSide || len(p.OtherConditions) > 0 { + // 3. or nullAware/cross joins + if useBCJ || p.isNAAJ() || len(p.EqualConditions) == 0 || p.ctx.GetSessionVars().MPPOuterJoinFixedBuildSide { if !p.ctx.GetSessionVars().MPPOuterJoinFixedBuildSide { // The hint has higher priority than variable. fixedBuildSide = true