-
Notifications
You must be signed in to change notification settings - Fork 6.1k
planner: fix join reorder correctness with conflict detection algorithm | tidb-test=pr/2676 #65705
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
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This is a work-in-progress pull request that introduces a new join reorder implementation to address correctness problems with outer join reordering. The PR adds a new joinorder package with conflict detection capabilities based on join reorder theory, while maintaining the existing join reorder logic as a fallback.
Changes:
- Adds new join order optimizer with conflict detector for handling outer joins
- Introduces conditional logic to enable the new implementation via session variable
- Adds schema equality checking method for validation after reorder
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 24 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/planner/core/rule_join_reorder.go | Adds conditional branching to use new join order implementation when EnableOuterJoinReorder is enabled |
| pkg/planner/core/joinorder/join_order.go | Implements new join order optimization with greedy algorithm, leading hint support, and join group extraction |
| pkg/planner/core/joinorder/conflict_detector.go | Implements conflict detection logic for join reordering with associativity and commutativity rules |
| pkg/planner/core/base/plan_base.go | Adds documentation comment noting that JoinType enum order must remain unchanged for conflict detector |
| pkg/expression/util.go | Adds TODO comment about refining the FilterOutInPlace function |
| pkg/expression/schema.go | Adds Equal method to Schema for comparing schema equality after join reorder |
| resNodes = append(resNodes, cartesianNodes[i]) | ||
| break | ||
| } | ||
| newJoin, err := newCartesianJoin(ctx, cartesianNodes[i].p, cartesianNodes[i+1].p) |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function newCartesianJoin accepts a joinType parameter but is called without specifying the join type. The call should be newCartesianJoin(ctx, base.InnerJoin, left, right) to specify an inner join for Cartesian products.
| newJoin, err := newCartesianJoin(ctx, cartesianNodes[i].p, cartesianNodes[i+1].p) | |
| newJoin, err := newCartesianJoin(ctx, base.InnerJoin, cartesianNodes[i].p, cartesianNodes[i+1].p) |
| } | ||
| } | ||
| // gjt todo set children? | ||
| if p, err = j.optimizeForJoinGroup(p.SCtx(), joinGroup); err != nil { |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable j is used but not defined. The function optimizeRecursive calls j.optimizeForJoinGroup but j is not a receiver and is not declared anywhere in scope.
| selection.Conditions = append(selection.Conditions, e.eqConds...) | ||
| selection.Conditions = append(selection.Conditions, e.nonEQConds...) |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function attempts to convert expressions from []*expression.ScalarFunction to []expression.Expression by appending them directly. While ScalarFunction implements Expression, the type conversion in line 380 may cause issues. More critically, lines 380-381 should use type conversion or a helper function to properly handle the conversion.
| selection.Conditions = append(selection.Conditions, e.eqConds...) | |
| selection.Conditions = append(selection.Conditions, e.nonEQConds...) | |
| selection.Conditions = append(selection.Conditions, expression.ScalarFuncs2Exprs(e.eqConds)...) | |
| selection.Conditions = append(selection.Conditions, expression.ScalarFuncs2Exprs(e.nonEQConds)...) |
| vertexMap[v.ID()] = &Node{ | ||
| bitSet: newBitSet(int64(i)), | ||
| p: v, | ||
| } |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cumCost field is not initialized when creating Nodes for vertexes. This field is used in the greedy join order algorithm to sort and compare nodes (line 276 in join_order.go), but leaf nodes will have cumCost of 0. The cumCost should be initialized based on the vertex plan's statistics or row count.
| vertexMap[v.ID()] = &Node{ | |
| bitSet: newBitSet(int64(i)), | |
| p: v, | |
| } | |
| node := &Node{ | |
| bitSet: newBitSet(int64(i)), | |
| p: v, | |
| } | |
| if stats := v.StatsInfo(); stats != nil { | |
| node.cumCost = stats.RowCount | |
| } | |
| vertexMap[v.ID()] = node |
| // It could be otherCond, leftCond or rightCond. | ||
| nonEQConds expression.CNFExprs | ||
|
|
||
| tes BitSet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an imported bitset
| // 0: rule doesn't apply | ||
| // 1: rule applies | ||
| // 2: rule applies when null-rejective holds | ||
| type ruleTableEntry int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it's not bool
| return rule | ||
| } | ||
|
|
||
| func (d *ConflictDetector) calcTES(conds []base.Expression) BitSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comments
| // Append selections to existing join | ||
| selection := logicalop.LogicalSelection{ | ||
| Conditions: []expression.Expression{}, // gjt todo reserve space | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When will we enter this? Why is it not in makeNonInnerJoin?
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
|
/retest |
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
|
/retest |
Signed-off-by: guo-shaoge <[email protected]>
|
/retest |
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
Signed-off-by: guo-shaoge <[email protected]>
|
@guo-shaoge: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
@guo-shaoge: The following tests failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
@pantheon-bot please review |
|
| // JoinType contains CrossJoin, InnerJoin, LeftOuterJoin, RightOuterJoin, SemiJoin, AntiJoin. | ||
| type JoinType int | ||
|
|
||
| // NOTE: keep the order and value unchanged, because they are used in conflict_detector.go!!! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an intest.Assert instead.
| // It could be otherCond, leftCond or rightCond. | ||
| nonEQConds expression.CNFExprs | ||
|
|
||
| tes intset.FastIntSet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use the imported bitset instead.
FastIntSet is used for sparse case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go.mod: github.com/bits-and-blooms/bitset v1.14.3
| return nodes, nil | ||
| } | ||
|
|
||
| // TODO add example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remember to modify this comment
| where t1.c1 < 10 or t1.c2 < 10 for update`).Check(testkit.Rows( | ||
| "Projection 16635.64 root test.t1.c1", | ||
| "└─SelectLock 16635.64 root for update 0", | ||
| " └─Projection 16635.64 root test.t1.c1, test.t1._tidb_rowid, test.t1._tidb_tid, test.t2._tidb_rowid", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this test change? Is this expected?
| @@ -1956,7 +1950,7 @@ HashJoin root CARTESIAN inner join | |||
| └─Selection cop[tikv] not(isnull(planner__core__casetest__rule__rule_join_reorder.t8.a)) | |||
| └─TableFullScan cop[tikv] table:t8 keep order:false, stats:pseudo | |||
| Level Code Message | |||
| Warning 1815 We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change expected?
What problem does this PR solve?
Issue Number: close #63887
Problem Summary:
What changed and how does it work?
Check List
Tests
Side effects
Documentation
Release note
Please refer to Release Notes Language Style Guide to write a quality release note.