Skip to content
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

planner: fix join reorder will append remained other condition to an outer join #44409

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 16 additions & 1 deletion planner/core/rule_join_reorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func extractJoinGroup(p LogicalPlan) *joinGroupResult {
tmpOtherConds = append(tmpOtherConds, join.OtherConditions...)
tmpOtherConds = append(tmpOtherConds, join.LeftConditions...)
tmpOtherConds = append(tmpOtherConds, join.RightConditions...)
if join.JoinType == LeftOuterJoin || join.JoinType == RightOuterJoin {
if join.JoinType == LeftOuterJoin || join.JoinType == RightOuterJoin || join.JoinType == LeftOuterSemiJoin || join.JoinType == AntiLeftOuterSemiJoin {
for range join.EqualConditions {
abType := &joinTypeWithExtMsg{JoinType: join.JoinType}
// outer join's other condition should be bound with the connecting edge.
Expand Down Expand Up @@ -507,6 +507,13 @@ func (s *baseSingleGroupJoinOrderSolver) makeJoin(leftPlan, rightPlan LogicalPla
remainOtherConds, otherConds = expression.FilterOutInPlace(remainOtherConds, func(expr expression.Expression) bool {
return expression.ExprFromSchema(expr, mergedSchema)
})
if (joinType.JoinType == LeftOuterJoin || joinType.JoinType == RightOuterJoin || joinType.JoinType == LeftOuterSemiJoin || joinType.JoinType == AntiLeftOuterSemiJoin) && len(otherConds) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about fullOuter?

Copy link
Contributor Author

@AilinKid AilinKid Jun 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no full outer join type internally

// the original outer join's other conditions has been bound to the outer join Edge,
// these remained other condition here shouldn't be appended to it because on-mismatch
// logic will produce more append-null rows which is banned in original semantic.
remainOtherConds = append(remainOtherConds, otherConds...) // nozero
otherConds = otherConds[:0]
}
if len(joinType.outerBindCondition) > 0 {
remainOBOtherConds := make([]expression.Expression, len(joinType.outerBindCondition))
copy(remainOBOtherConds, joinType.outerBindCondition)
Expand Down Expand Up @@ -550,6 +557,14 @@ func (s *baseSingleGroupJoinOrderSolver) makeBushyJoin(cartesianJoinGroup []Logi
}
cartesianJoinGroup, resultJoinGroup = resultJoinGroup, cartesianJoinGroup
}
// other conditions may be possible to exist across different cartesian join group, resolving cartesianJoin first then adding another selection.
if len(s.otherConds) > 0 {
additionSelection := LogicalSelection{
Conditions: s.otherConds,
}.Init(cartesianJoinGroup[0].SCtx(), cartesianJoinGroup[0].SelectBlockOffset())
additionSelection.SetChildren(cartesianJoinGroup[0])
cartesianJoinGroup[0] = additionSelection
}
return cartesianJoinGroup[0]
}

Expand Down
30 changes: 30 additions & 0 deletions planner/core/rule_join_reorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ func TestJoinOrderHintWithBinding(t *testing.T) {
tk.MustExec("drop global binding for select * from t1 join t2 on t1.a=t2.a join t3 on t2.b=t3.b")
}

func TestAdditionOtherConditionsRemained4OuterJoin(t *testing.T) {
store, _ := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)

tk.MustExec("use test")
tk.MustExec("CREATE TABLE `queries_identifier` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,\n PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
tk.MustExec("CREATE TABLE `queries_program` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `identifier_id` int(11) NOT NULL,\n PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n UNIQUE KEY `identifier_id` (`identifier_id`),\n CONSTRAINT `queries_program_identifier_id_70ff12a6_fk_queries_identifier_id` FOREIGN KEY (`identifier_id`) REFERENCES `test`.`queries_identifier` (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
tk.MustExec("CREATE TABLE `queries_channel` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `identifier_id` int(11) NOT NULL,\n PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n UNIQUE KEY `identifier_id` (`identifier_id`),\n CONSTRAINT `queries_channel_identifier_id_06ac3513_fk_queries_identifier_id` FOREIGN KEY (`identifier_id`) REFERENCES `test`.`queries_identifier` (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")

tk.MustExec("INSERT INTO queries_identifier(`id`, `name`) values(13, 'i1'), (14, 'i2'), (15, 'i3');")
tk.MustExec("INSERT INTO queries_program(`id`, `identifier_id`) values(8, 13), (9, 14);")
tk.MustExec("INSERT INTO queries_channel(`id`, `identifier_id`) values(5, 13);")

tk.MustQuery("SELECT `queries_identifier`.`id`, `queries_identifier`.`name` FROM `queries_identifier` LEFT OUTER JOIN `queries_channel` ON (`queries_identifier`.`id` = `queries_channel`.`identifier_id`) INNER JOIN `queries_program` ON (`queries_identifier`.`id` = `queries_program`.`identifier_id`) WHERE ((`queries_channel`.`id` = 5 AND `queries_program`.`id` = 9) OR `queries_program`.`id` = 8) ORDER BY `queries_identifier`.`id` ASC;").Check(testkit.Rows("" +
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
"13 i1"))
tk.MustQuery("SELECT `queries_identifier`.`id`, `queries_identifier`.`name` FROM `queries_identifier` RIGHT OUTER JOIN `queries_channel` ON (`queries_identifier`.`id` = `queries_channel`.`identifier_id`) INNER JOIN `queries_program` ON (`queries_identifier`.`id` = `queries_program`.`identifier_id`) WHERE ((`queries_channel`.`id` = 5 AND `queries_program`.`id` = 9) OR `queries_program`.`id` = 8) ORDER BY `queries_identifier`.`id` ASC;").Check(testkit.Rows("" +
"13 i1"))
tk.MustQuery("explain format = 'brief' SELECT `queries_identifier`.`id`, `queries_identifier`.`name` FROM `queries_identifier` LEFT OUTER JOIN `queries_channel` ON (`queries_identifier`.`id` = `queries_channel`.`identifier_id`) INNER JOIN `queries_program` ON (`queries_identifier`.`id` = `queries_program`.`identifier_id`) WHERE ((`queries_channel`.`id` = 5 AND `queries_program`.`id` = 9) OR `queries_program`.`id` = 8) ORDER BY `queries_identifier`.`id` ASC;").Check(testkit.Rows(""+
"Sort 2.50 root test.queries_identifier.id",
"└─Projection 2.50 root test.queries_identifier.id, test.queries_identifier.name",
" └─Selection 2.50 root or(and(eq(test.queries_channel.id, 5), eq(test.queries_program.id, 9)), eq(test.queries_program.id, 8))",
" └─IndexJoin 3.12 root left outer join, inner:IndexReader, outer key:test.queries_identifier.id, inner key:test.queries_channel.identifier_id, equal cond:eq(test.queries_identifier.id, test.queries_channel.identifier_id)",
" ├─IndexHashJoin(Build) 2.50 root inner join, inner:TableReader, outer key:test.queries_program.identifier_id, inner key:test.queries_identifier.id, equal cond:eq(test.queries_program.identifier_id, test.queries_identifier.id)",
" │ ├─Batch_Point_Get(Build) 2.00 root table:queries_program handle:[8 9], keep order:false, desc:false",
" │ └─TableReader(Probe) 2.00 root data:TableRangeScan",
" │ └─TableRangeScan 2.00 cop[tikv] table:queries_identifier range: decided by [test.queries_program.identifier_id], keep order:false, stats:pseudo",
" └─IndexReader(Probe) 2.50 root index:IndexRangeScan",
" └─IndexRangeScan 2.50 cop[tikv] table:queries_channel, index:identifier_id(identifier_id) range: decided by [eq(test.queries_channel.identifier_id, test.queries_identifier.id)], keep order:false, stats:pseudo"))
}

func TestOuterJoinWIthEqCondCrossInnerJoin(t *testing.T) {
store := testkit.CreateMockStore(t)

Expand Down