Expand description
v6.2.3 — JOIN reorder planner pass.
Runs after parse + clock rewrite + ORDER BY position
resolution. For SelectStatements with multiple INNER-joined
tables, picks an ordering that minimises the cumulative
nested-loop work — leveraging v6.2.0’s Statistics for
per-edge selectivity estimates.
Algorithm:
- Identify tables:
from.primary+from.joins[*]. - Identify edges: each
INNER JOIN ... ON <expr>adds one edge whose endpoints are the table names referenced by the ON expression (extracted by walkingColumnNamenodes). LEFT / CROSS joins disable reorder — they have semantics-preserving order constraints we don’t unpack in v6.2.3. - Enumerate orderings:
- For
n ≤ 4tables: brute force alln!orderings. - For
n > 4: greedy — pick the smallest table first, then at each step pick the next table that gives the smallest expected output size given the edges already applicable.
- For
- Cost an ordering: walk left-to-right tracking the running
output size. Every edge becomes applicable as soon as both
its endpoint tables are in the prefix; multiplying the
running size by
selectivity::equal(stats, …) / n_distinctfor that edge updates the size. Step cost =running_size × new_table_size. Total cost = sum of step costs. - Pick the minimum-cost ordering and rewrite
from.primary+from.joinsin that order. ON predicates travel with their edges — they re-attach to whichever join introduces both endpoint tables.
All of v6.2.3 ships pure-AST rewriting. The executor at
exec_joined_select doesn’t change shape — it consumes the
newly-ordered FROM clause unchanged.
Constants§
- FULL_
ENUM_ MAX - v6.2.3 — full-enumeration cap. v6.2.x can re-tune; the value
determines
n!plan-space size (4! = 24, 5! = 120, 6! = 720 — 6 is on the verge of “noticeable” in micro-bench terms).
Statics§
- REORDER_
INNER_ RUN_ FIRED - REORDER_
INNER_ RUN_ TRIED - v7.37.9 Phase 0 diagnostic counters — see
.claude/notes/v7.37.9-class-a-c-cascade-closure-plan.md. These are read-only telemetry, do not gate any code path.
Functions§
- choose_
order_ for_ test - v6.2.3 — entry point. Rewrites
stmt.from(when present) so the join chain is in cost-minimising order. No-op when: - reorder_
joins - reorder_
joins_ with - v7.38 元机制 D acceptor —
SPG_TEST_PLAN_DETERMINISTIC=1makes cost-based join reorder a no-op so regression tests that pin “same SQL → same plan order” don’t drift when statistics shift. Production reads callreorder_joins(deterministic=false); the engine’s gate iscrates/spg-engine/src/execute.rs::Engine::env_cfg().plan_deterministic.