pub struct SemiJoinReductionFromSelectiveDimension { /* private fields */ }Expand description
Reduce a fact stream by a selective dimension it is inner-joined to, before the join that needs it.
§The query that motivated this
TPC-H q7’s FROM clause is
supplier, lineitem, orders, customer, nation n1, nation n2, and the plan
is left-deep in that order — so the two 25-row nation tables land at the
very TOP, above every big join:
Inner Join: n2.n_nationkey = customer.c_nationkey <- n_name IN (FRANCE, GERMANY)
Inner Join: n1.n_nationkey = supplier.s_nationkey <- n_name IN (FRANCE, GERMANY)
Inner Join: customer.c_custkey = orders.o_custkey
Inner Join: orders.o_orderkey = lineitem.l_orderkey
Inner Join: supplier.s_suppkey = lineitem.l_suppkey <- ALL 1M supplierss_nationkey is carried as payload from the bottom join all the way up,
through two shuffles measured at 9.48 GB each, before the nation filter
is ever applied. TPC-H spreads supplier nations uniformly over 25, so
~8% of suppliers qualify: the bottom join emits about twelve times more
rows than any of them can survive.
Measured at SF100 on 2026-08-08, those two shuffles and the stage that consumes them are 81% of q7 (s4 43.4%, s5 37.7%).
§The rewrite
Inner(N, Big) on N.k = Big.k ==> Inner(N, LeftSemi(Big, N') on Big.k)where N' is N descended to its nearest Filter and projected to the key
— the same selective_key_source the aggregate rule uses.
The reducer is introduced at the TOP of the big side and deliberately left
there: SemiJoinPushdownThroughInnerJoin already carries a LeftSemi
down through inner joins, one level per optimizer pass, and the optimizer
runs to a fixed point. So this rule does not need its own descent, and the
reducer ends up landing directly on the supplier scan.
§Why it is safe
- The join must be Inner. Under an outer join the unmatched rows are preserved, so removing them early changes the result.
- Removing exactly what the join would remove. A
Bigrow whose key has no match inNcannot appear inInner(N, Big). The reducer removes precisely those rows and no others, so the output is identical. - No duplication.
LeftSemiemits each left row at most once however manyNrows match, so multiplicity — and every count and sum above — is unchanged. - Nulls agree. A null key satisfies neither the reducer nor the join.
- The schema is untouched.
LeftSemiprojects only its left side, so the parent join’soncolumns resolve exactly as before.
§Why it is guarded
- The dimension must carry a
Filter. Without one the reducer removes nothing and costs an extra pass — the same guard, and the same reason, as the aggregate rule. - The big side must contain an inner join. If it is a bare scan there is nothing to push past: the reducer would sit directly beneath the join that already does that work.
- Idempotence is structural, not shallow. The pushdown rule moves the
reducer down, so after one pass the big side’s top node is an inner join
again and a shallow
already_reducedcheck would let this rule add a second reducer on every pass, forever.carries_reducersearches the whole subtree for this exact probe instead.
Implementations§
Trait Implementations§
Source§impl Default for SemiJoinReductionFromSelectiveDimension
impl Default for SemiJoinReductionFromSelectiveDimension
Source§fn default() -> SemiJoinReductionFromSelectiveDimension
fn default() -> SemiJoinReductionFromSelectiveDimension
Source§impl OptimizerRule for SemiJoinReductionFromSelectiveDimension
impl OptimizerRule for SemiJoinReductionFromSelectiveDimension
Source§fn apply_order(&self) -> Option<ApplyOrder>
fn apply_order(&self) -> Option<ApplyOrder>
ApplyOrder for details. Read moreSource§fn rewrite(
&self,
plan: LogicalPlan,
_config: &dyn OptimizerConfig,
) -> Result<Transformed<LogicalPlan>>
fn rewrite( &self, plan: LogicalPlan, _config: &dyn OptimizerConfig, ) -> Result<Transformed<LogicalPlan>>
plan to an optimized form, returning Transformed::yes
if the plan was rewritten and Transformed::no if it was not. Read moreSource§fn supports_rewrite(&self) -> bool
fn supports_rewrite(&self) -> bool
This method is no longer used
Auto Trait Implementations§
impl Freeze for SemiJoinReductionFromSelectiveDimension
impl RefUnwindSafe for SemiJoinReductionFromSelectiveDimension
impl Send for SemiJoinReductionFromSelectiveDimension
impl Sync for SemiJoinReductionFromSelectiveDimension
impl Unpin for SemiJoinReductionFromSelectiveDimension
impl UnsafeUnpin for SemiJoinReductionFromSelectiveDimension
impl UnwindSafe for SemiJoinReductionFromSelectiveDimension
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request