Skip to content

Commit 37a2476

Browse files
committed
update
1 parent bdac51b commit 37a2476

File tree

6 files changed

+249
-245
lines changed

6 files changed

+249
-245
lines changed

pkg/planner/core/rule_join_reorder.go

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -48,58 +48,77 @@ func extractJoinGroup(p base.LogicalPlan) *joinGroupResult {
4848
// 3. Build current node's colExprMap and return
4949
// This approach is consistent with rule_eliminate_projection.go.
5050
func extractJoinGroupImpl(p base.LogicalPlan) *joinGroupResult {
51-
// Check if we should try to inline projections
52-
if proj, ok := p.(*logicalop.LogicalProjection); ok &&
53-
p.SCtx() != nil && p.SCtx().GetSessionVars().TiDBOptJoinReorderProj {
54-
// Only inline projections whose child is a join; otherwise the projection is treated as an atomic leaf.
55-
if _, isJoin := proj.Children()[0].(*logicalop.LogicalJoin); isJoin {
56-
// Fast safety checks first to avoid unnecessary recursion.
57-
if canInlineProjectionBasic(proj) {
58-
childResult := extractJoinGroupImpl(proj.Children()[0])
59-
// The single-leaf check needs the extracted join group and child's colExprMap.
60-
if canInlineProjection(proj, childResult) {
61-
// Build current Projection's colExprMap.
62-
colExprMap := make(map[int64]expression.Expression, len(proj.Schema().Columns))
63-
for i, col := range proj.Schema().Columns {
64-
expr := proj.Exprs[i]
65-
66-
// Use child's colExprMap to substitute column references in current expression.
67-
if len(childResult.colExprMap) > 0 {
68-
expr = substituteColsInExpr(expr, childResult.colExprMap)
51+
// NOTE: We only support extracting join groups through a single Selection/Projection layer for now.
52+
// TODO: Support stacked unary operators like Projection->Selection->Join or Selection->Projection->Join.
53+
if selection, isSelection := p.(*logicalop.LogicalSelection); isSelection {
54+
// If its child is a join, add the selection conditions to otherConds and continue extracting
55+
// the join group from the child.
56+
//
57+
// Join reorder may distribute/push down conditions during constructing the new join tree.
58+
// For volatile or side-effect expressions, moving them can change evaluation times/orders,
59+
// thus may change query results, so we skip reordering through Selection in such cases.
60+
if p.SCtx() != nil && p.SCtx().GetSessionVars().TiDBOptJoinReorderThroughSel &&
61+
!slices.ContainsFunc(selection.Conditions, expression.IsMutableEffectsExpr) {
62+
child := selection.Children()[0]
63+
if _, isChildJoin := child.(*logicalop.LogicalJoin); isChildJoin {
64+
childResult := extractJoinGroup(child)
65+
childResult.otherConds = append(childResult.otherConds, selection.Conditions...)
66+
return childResult
67+
}
68+
}
69+
} else if proj, ok := p.(*logicalop.LogicalProjection); ok {
70+
// Check if we should try to inline projections
71+
if p.SCtx() != nil && p.SCtx().GetSessionVars().TiDBOptJoinReorderThroughProj {
72+
// Only inline projections whose child is a join; otherwise the projection is treated as an atomic leaf.
73+
if _, isJoin := proj.Children()[0].(*logicalop.LogicalJoin); isJoin {
74+
// Fast safety checks first to avoid unnecessary recursion.
75+
if canInlineProjectionBasic(proj) {
76+
childResult := extractJoinGroupImpl(proj.Children()[0])
77+
// The single-leaf check needs the extracted join group and child's colExprMap.
78+
if canInlineProjection(proj, childResult) {
79+
// Build current Projection's colExprMap.
80+
colExprMap := make(map[int64]expression.Expression, len(proj.Schema().Columns))
81+
for i, col := range proj.Schema().Columns {
82+
expr := proj.Exprs[i]
83+
84+
// Use child's colExprMap to substitute column references in current expression.
85+
if len(childResult.colExprMap) > 0 {
86+
expr = substituteColsInExpr(expr, childResult.colExprMap)
87+
}
88+
89+
// For pass-through columns (where the projection expression is just a Column reference
90+
// that has been substituted to the underlying expression), we need to check if the output
91+
// column's UniqueID matches the input column's UniqueID. This can happen when a projection
92+
// simply passes through a column without transformation (e.g., SELECT dt1.key_a FROM ...).
93+
// In such cases, the output column may share the same UniqueID as the input column.
94+
//
95+
// If expr is a Column and its UniqueID equals col.UniqueID, this is a pass-through scenario.
96+
// We should NOT add it to colExprMap because:
97+
// 1. It would create a self-referential mapping (UniqueID -> Column with same UniqueID)
98+
// 2. It could conflict with child's colExprMap entry for the same UniqueID
99+
//
100+
// The child's colExprMap will be merged later and will provide the proper mapping for this UniqueID.
101+
if colExpr, isCol := expr.(*expression.Column); isCol && colExpr.UniqueID == col.UniqueID {
102+
// Skip pass-through columns - they will get their mapping from child's colExprMap.
103+
continue
104+
}
105+
106+
colExprMap[col.UniqueID] = expr
69107
}
70108

71-
// For pass-through columns (where the projection expression is just a Column reference
72-
// that has been substituted to the underlying expression), we need to check if the output
73-
// column's UniqueID matches the input column's UniqueID. This can happen when a projection
74-
// simply passes through a column without transformation (e.g., SELECT dt1.key_a FROM ...).
75-
// In such cases, the output column may share the same UniqueID as the input column.
76-
//
77-
// If expr is a Column and its UniqueID equals col.UniqueID, this is a pass-through scenario.
78-
// We should NOT add it to colExprMap because:
79-
// 1. It would create a self-referential mapping (UniqueID -> Column with same UniqueID)
80-
// 2. It could conflict with child's colExprMap entry for the same UniqueID
81-
//
82-
// The child's colExprMap will be merged later and will provide the proper mapping for this UniqueID.
83-
if colExpr, isCol := expr.(*expression.Column); isCol && colExpr.UniqueID == col.UniqueID {
84-
// Skip pass-through columns - they will get their mapping from child's colExprMap.
85-
continue
109+
// Merge child's colExprMap entries.
110+
// Note: A key conflict here should not happen because each projection layer
111+
// produces new UniqueIDs. If it occurs, we skip the child's entry and keep
112+
// the current projection's entry, which is the correct behavior.
113+
for k, v := range childResult.colExprMap {
114+
if _, exists := colExprMap[k]; !exists {
115+
colExprMap[k] = v
116+
}
86117
}
87118

88-
colExprMap[col.UniqueID] = expr
89-
}
90-
91-
// Merge child's colExprMap entries.
92-
// Note: A key conflict here should not happen because each projection layer
93-
// produces new UniqueIDs. If it occurs, we skip the child's entry and keep
94-
// the current projection's entry, which is the correct behavior.
95-
for k, v := range childResult.colExprMap {
96-
if _, exists := colExprMap[k]; !exists {
97-
colExprMap[k] = v
98-
}
119+
childResult.colExprMap = colExprMap
120+
return childResult
99121
}
100-
101-
childResult.colExprMap = colExprMap
102-
return childResult
103122
}
104123
}
105124
}
@@ -115,21 +134,6 @@ func extractJoinGroupImpl(p base.LogicalPlan) *joinGroupResult {
115134
hasOuterJoin bool
116135
)
117136

118-
// Check if the current plan is a Selection. If its child is a join, add the selection conditions
119-
// to otherConds and continue extracting the join group from the child.
120-
// Join reorder may distribute/push down conditions during constructing the new join tree.
121-
// For volatile or side-effect expressions, moving them can change evaluation times/orders
122-
// thus may change query results, so we skip reordering through Selection in such cases.
123-
if selection, isSelection := p.(*logicalop.LogicalSelection); isSelection && p.SCtx().GetSessionVars().TiDBOptJoinReorderThroughSel &&
124-
!slices.ContainsFunc(selection.Conditions, expression.IsMutableEffectsExpr) {
125-
child := selection.Children()[0]
126-
if _, isChildJoin := child.(*logicalop.LogicalJoin); isChildJoin {
127-
childResult := extractJoinGroup(child)
128-
childResult.otherConds = append(childResult.otherConds, selection.Conditions...)
129-
return childResult
130-
}
131-
}
132-
133137
join, isJoin := p.(*logicalop.LogicalJoin)
134138
if isJoin && join.PreferJoinOrder {
135139
// When there is a leading hint, the hint may not take effect for other reasons.
@@ -593,12 +597,12 @@ func (s *JoinReOrderSolver) optimizeRecursive(ctx base.PlanContext, p base.Logic
593597
origJoinRoot := p
594598
originalSchema := p.Schema()
595599
fallbackOnErr := func(err error) (base.LogicalPlan, error) {
596-
if ctx.GetSessionVars().TiDBOptJoinReorderProj {
600+
if ctx.GetSessionVars().TiDBOptJoinReorderThroughProj {
597601
// This optimization is best-effort. If anything goes wrong, fallback to the
598602
// original behavior (treat projections as atomic leaves) to keep correctness.
599-
saved := ctx.GetSessionVars().TiDBOptJoinReorderProj
600-
ctx.GetSessionVars().TiDBOptJoinReorderProj = false
601-
defer func() { ctx.GetSessionVars().TiDBOptJoinReorderProj = saved }()
603+
saved := ctx.GetSessionVars().TiDBOptJoinReorderThroughProj
604+
ctx.GetSessionVars().TiDBOptJoinReorderThroughProj = false
605+
defer func() { ctx.GetSessionVars().TiDBOptJoinReorderThroughProj = saved }()
602606
return s.optimizeRecursive(ctx, origJoinRoot)
603607
}
604608
return nil, err

pkg/sessionctx/vardef/tidb_vars.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,10 +660,10 @@ const (
660660
// we'll choose a rather time-consuming algorithm to calculate the join order.
661661
TiDBOptJoinReorderThreshold = "tidb_opt_join_reorder_threshold"
662662

663-
// TiDBOptJoinReorderProj enables join reorder to look through projection operators
663+
// TiDBOptJoinReorderThroughProj enables join reorder to look through projection operators
664664
// when extracting join groups. This allows join reorder to work with derived columns from CTEs,
665665
// views, or subqueries that have expression computations in their SELECT list.
666-
TiDBOptJoinReorderProj = "tidb_opt_join_reorder_proj"
666+
TiDBOptJoinReorderThroughProj = "tidb_opt_join_reorder_through_proj"
667667

668668
// TiDBOptJoinReorderThroughSel enables pushing selection conditions down to
669669
// reordered join trees when applicable.
@@ -1524,7 +1524,7 @@ const (
15241524
DefEnableStrictDoubleTypeCheck = true
15251525
DefEnableVectorizedExpression = true
15261526
DefTiDBOptJoinReorderThreshold = 0
1527-
DefTiDBOptJoinReorderProj = false
1527+
DefTiDBOptJoinReorderThroughProj = false
15281528
DefTiDBOptJoinReorderThroughSel = false
15291529
DefTiDBDDLSlowOprThreshold = 300
15301530
DefTiDBUseFastAnalyze = false

pkg/sessionctx/variable/session.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,8 +1207,8 @@ type SessionVars struct {
12071207
// to use the greedy join reorder algorithm.
12081208
TiDBOptJoinReorderThreshold int
12091209

1210-
// TiDBOptJoinReorderProj enables join reorder to look through projections.
1211-
TiDBOptJoinReorderProj bool
1210+
// TiDBOptJoinReorderThroughProj enables join reorder to look through projections.
1211+
TiDBOptJoinReorderThroughProj bool
12121212

12131213
// TiDBOptJoinReorderThroughSel enables pushing selection conditions down to
12141214
// reordered join trees when applicable.
@@ -2320,7 +2320,7 @@ func NewSessionVars(hctx HookContext) *SessionVars {
23202320
EnableVectorizedExpression: vardef.DefEnableVectorizedExpression,
23212321
CommandValue: uint32(mysql.ComSleep),
23222322
TiDBOptJoinReorderThreshold: vardef.DefTiDBOptJoinReorderThreshold,
2323-
TiDBOptJoinReorderProj: vardef.DefTiDBOptJoinReorderProj,
2323+
TiDBOptJoinReorderThroughProj: vardef.DefTiDBOptJoinReorderThroughProj,
23242324
TiDBOptJoinReorderThroughSel: vardef.DefTiDBOptJoinReorderThroughSel,
23252325
SlowQueryFile: config.GetGlobalConfig().Log.SlowQueryFile,
23262326
WaitSplitRegionFinish: vardef.DefTiDBWaitSplitRegionFinish,

pkg/sessionctx/variable/sysvar.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2582,8 +2582,8 @@ var defaultSysVars = []*SysVar{
25822582
s.TiDBOptJoinReorderThreshold = tidbOptPositiveInt32(val, vardef.DefTiDBOptJoinReorderThreshold)
25832583
return nil
25842584
}},
2585-
{Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBOptJoinReorderProj, Value: BoolToOnOff(vardef.DefTiDBOptJoinReorderProj), Type: vardef.TypeBool, SetSession: func(s *SessionVars, val string) error {
2586-
s.TiDBOptJoinReorderProj = TiDBOptOn(val)
2585+
{Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBOptJoinReorderThroughProj, Value: BoolToOnOff(vardef.DefTiDBOptJoinReorderThroughProj), Type: vardef.TypeBool, SetSession: func(s *SessionVars, val string) error {
2586+
s.TiDBOptJoinReorderThroughProj = TiDBOptOn(val)
25872587
return nil
25882588
}},
25892589
{Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBOptJoinReorderThroughSel, Value: BoolToOnOff(vardef.DefTiDBOptJoinReorderThroughSel), Type: vardef.TypeBool, SetSession: func(s *SessionVars, val string) error {

0 commit comments

Comments
 (0)