Documentation
¶
Overview ¶
Package physical defines stage-type string constants used by Stage.Type. Exchange stages are inserted by EnsureDistribution to bridge distribution mismatches between child output and parent required input.
Package physical converts logical plans to physical execution plans.
Index ¶
- Constants
- Variables
- func AssertExchangeConsistency(stages []Stage) error
- func BuildAggregateShuffleSQL(cand AggregateShuffleCandidate, stages []Stage) (string, error)
- func BuildSemiAntiFilter(filter string) ...
- func CanProbeSplit(stages []Stage, workerCount int) (probeAlias string, probeFiles []string, ok bool)
- func CountJoinStages(stages []Stage) int
- func HashPartitionCount(workerCount int) int
- func ParseSemiAntiNE(filter string) (probeCol, buildCol string, ok bool)
- func SemiAntiBuildStoreCols(rightKeys []string, joinFilter string) []string
- func SetExchangePartialAggEnabled(on bool) bool
- func ValidateNativeDAGShape(stages []Stage) error
- type AggSpec
- type AggregateShuffleCandidate
- type AggregateShuffleDiag
- type AggregateShuffleRejectReason
- type ChainedJoinSpec
- type ComputedCol
- type DistKind
- type Distribution
- type DynamicFilterConsume
- type DynamicFilterEmit
- type ExchangeStage
- type FusedJoinSpec
- type OutputRename
- type PhysicalPlan
- type Planner
- func (p *Planner) AnnotateScanColumns(ctx context.Context, node *logical.Node)
- func (p *Planner) EstimatePlanScanBytes(ctx context.Context, n *logical.Node) (int64, bool)
- func (p *Planner) ExpandFederatedScans(stages []Stage) []Stage
- func (p *Planner) Plan(ctx context.Context, node *logical.Node) (*PhysicalPlan, error)
- func (p *Planner) PlanDistributed(ctx context.Context, node *logical.Node) ([]Stage, error)
- func (p *Planner) ValidateColumns(ctx context.Context, info *plansql.SelectInfo) error
- type PreComputedAggregateMeta
- type ProjectExprSpec
- type QueryCost
- type RecordBatch
- type RequiredDistribution
- type RequiredKind
- type ShuffleCandidate
- type SortKeySpec
- type Stage
- type WindowColSpec
Constants ¶
const ( StageScan = "scan" StageAggregate = "aggregate" StageSort = "sort" StageHashJoin = "hash_join" StageBroadcastJoin = "broadcast_join" // StageSortMergeJoin is a hash-shuffled join executed as a sort-merge // join (docs/design/sort-merge-join.md): identical exchange children and // distribution properties to StageHashJoin — only the join operator // differs. Emitted when the SortMergeJoinBytes gate passes. StageSortMergeJoin = "sort_merge_join" StageWindow = "window" StagePipeline = "pipeline" // Exchange stages — inserted by EnsureDistribution. // Repartition is the rename of the legacy "shuffle" type; the string // value changes so that the old name does not silently leak through. StageExchangeRepartition = "exchange-repartition" StageExchangeReplicate = "exchange-replicate" StageExchangeGather = "exchange-gather" )
const RowLocColumn = "__row_loc"
RowLocColumn is the synthetic column carrying (rgUnit ordinal << 32 | row-in-group) through the narrow phase. The "__" prefix keeps it out of user column namespaces (sanitizeScanNeeds already passes such names).
Variables ¶
var ( ReverseBloomThreshold int64 = 10_000_000 ReverseBloomInnerThreshold int64 = 50_000_000 )
ReverseBloomThreshold and ReverseBloomInnerThreshold gate the reverse-bloom optimization (see buildJoin). Declared as vars so regression tests can lower them to fire on tiny SF0.x datasets, AND so they can be raised at runtime to disable the optimization while we hunt the SF100 Q05 0-rows bug whose triggering code path is somewhere in this optimization. The semi/anti threshold is left at 10M because we have no evidence of bugs there yet.
Init reads WADJET_REVERSE_BLOOM_INNER_THRESHOLD if set, so the bench can disable the inner-join path on SF100 without rebuilding the binary.
var AggOverExchange atomic.Bool
AggOverExchange gates rewireAggOverRawExchange. Kill switch WADJET_AGG_OVER_EXCHANGE=0. Exported atomic.Bool (ExchangeSubsume pattern) so tests can pin either arm.
var AttachOnArrivalConsumesPlanned atomic.Int64
AttachOnArrivalConsumesPlanned counts converted consume edges, process-wide. A/B observability, same family as DynamicFiltersPlanned.
var BehaviorPreservingMode = true
BehaviorPreservingMode controls assertion hardness. When true (Phase 1 default), AssertExchangeConsistency logs violations at WARN and returns nil — every existing distributed plan continues to execute unchanged even if the property algebra rules in this file are wrong. When false (tests, and Phase 2 onward), violations are returned as errors and callers must handle them.
Phase 2 deletes this var and makes the assertion always strict — the EnsureDistribution rule guarantees no violation can survive into the emitted plan.
var DFAttachOnArrival atomic.Bool
DFAttachOnArrival gates applyAttachOnArrival. Kill switch WADJET_DF_ATTACH_ON_ARRIVAL=0 restores the barrier on every edge.
var DFGuardedReemit atomic.Bool
DFGuardedReemit gates the rule-1 relaxation: a RE-EMITTING consumer (cascade mid-scan) may also convert to attach mode when every one of its emits is an AtScan accumulator — the emit op then buffers rows scanned before the consumed bloom lands and retro-filters them at finalize (guarded re-emit), preserving downstream filter quality without the start barrier.
DEFAULT OFF (opt-in WADJET_DF_GUARDED_REEMIT=1). The SF100 pair 2026-08-07 (ctl 6c173cf 16:07 / trt 944e640 16:29) proved the guard mechanism itself sound (guard_wait_ms median 46ms, retro-filter at exact dim selectivity) but exposed the relaxation's structural blind spot: the barrier also protects the mid-scan's OUTPUT volume. At SF100 the mid's 1-2s scan always ends before the dim bloom arrives (2-4s), so its output ships 100% unfiltered — full supplier (8×240K rows) into the broadcast replicate + join build instead of the nation-filtered ~8% — costing Q05/Q07/Q21 +33-60% in both guarded arms. Re-emitters keep the barrier until a shape exists that starts the scan early WITHOUT shipping the unfiltered head (e.g. worker-side scan-start hold on the deferred bloom).
var DimensionCascade atomic.Bool
DimensionCascade gates markDimensionCascade. Kill switch WADJET_DIMENSION_CASCADE=0.
var DimensionCascadesPlanned atomic.Int64
DimensionCascadesPlanned counts cascade annotations, process-wide.
var DynamicFiltersPlanned atomic.Int64
applyDynamicFilters walks stages and adds Emit/Consume annotations + the stat-dep edge for every eligible hash_join. Returns the (possibly mutated) stages slice. Pure local mutation; no error path because all failures are degraded silently (the join simply doesn't get a dynamic filter, which is always safe).
Safe to call multiple times — already-annotated stages are detected by the presence of EmitDynamicFilters/ConsumeDynamicFilters and skipped. DynamicFiltersPlanned counts join annotations the dynamic-filter pass produced, process-wide. Observability: A/B runs use it (via the coordinator's dispatch logs) to prove the pass actually fired — the 2026-07-08 revisit pair was unverifiable without it, exactly the failure mode SortMergeJoinsPlanned exists to prevent.
var ElidedCoPartitionedExchanges atomic.Int64
ElidedCoPartitionedExchanges counts identity exchanges removed at plan time (observability; mirrors coordinator.SkewSplitsPlanned).
var ExchangePartialAggMarked atomic.Int64
ExchangePartialAggMarked counts exchanges marked for sender-side partial aggregation at plan time (observability parity with ElidedCoPartitionedExchanges).
var ExchangeSubsume atomic.Bool
ExchangeSubsume gates dedupeSubsumedScanExchanges. Kill switch WADJET_EXCHANGE_SUBSUME=0. Exported atomic.Bool (ScalarAggSemijoin pattern) so tests can pin either arm.
var LateMatJoinsPlanned atomic.Int64
LateMatJoinsPlanned counts local-pipeline hash-join probes planned with late materialization enabled. Observability-first, mirroring SortMergeJoinsPlanned: dormancy tests assert it stays zero with the flag off, and A/B arms use it (with exec.LateMatBatchesEmitted) to prove the treatment engaged rather than inferring from wall-clock deltas.
var MetadataCountsPlanned atomic.Int64
MetadataCountsPlanned counts metadata-answered COUNT(*) plans (observability-first, mirrors TopNLateMatPlanned).
var MetadataMinMaxPlanned atomic.Int64
MetadataMinMaxPlanned counts metadata-answered MIN/MAX plans (observability-first, mirrors MetadataCountsPlanned).
var ProbeSplitMinBytes int64 = 64 * 1024 * 1024
ProbeSplitMinBytes is the minimum size of the largest scan required to activate probe-split. Below this, the orchestration overhead exceeds the parallelism benefit. Exported so tests can lower it to exercise the distributed path on tiny datasets — otherwise every test silently runs the single-worker path and distributed-only bugs (like the SF100 build cache Q02 regression) never get caught.
var ScanFilterPushdowns atomic.Int64
ScanFilterPushdowns counts filters (conjuncts) pushed into scans.
var SemiAntiBuildFilter atomic.Bool
SemiAntiBuildFilter gates markSemiAntiBuildFilters. Kill switch WADJET_SEMIANTI_BUILD_FILTER=0. Exported atomic.Bool (ExchangeSubsume pattern) so tests can pin either arm.
var SemiAntiBuildFiltersPlanned atomic.Int64
SemiAntiBuildFiltersPlanned counts build-filter annotations the pass produced, process-wide. Mechanism marker for A/B runs (the DynamicFiltersPlanned convention).
var SemiAntiNE atomic.Bool
BuildSemiAntiFilter compiles a non-equality join filter string (e.g., "l_suppkey != l_suppkey") into a function that evaluates the condition on probe and build batch rows. Convention: left of operator = probe column, right = build column.
The returned closure lazily resolves column indices on first call and caches them, avoiding per-row ColumnByName lookups. Comparisons use typed dispatch (int32, int64, float64, string) instead of fmt.Sprint conversion.
HashJoin's probe runs in parallel — multiple workers call this filter concurrently against probe and build batches whose schemas are stable across the lifetime of the query (same logical plan → same projected columns). Use sync.Once to resolve indices safely on first call; later calls become a single relaxed atomic load on the once.done flag. SemiAntiNE gates the distinct-pair semi/anti build fast path (exec/join_semianti_ne.go). Kill switch WADJET_SEMIANTI_NE=0.
var ShardedSortFinals atomic.Bool
ShardedSortFinals gates the shard-local sort/limit fold in fuseSortIntoPredecessor (sharded sort/limit finals). Kill switch WADJET_SHARDED_FINALS=0 restores the Singleton collapse. Exported atomic.Bool (the ScalarAggSemijoin pattern) so tests can pin either arm.
SharedSubplanDedup gates dedupeSharedSubplans. Kill switch WADJET_SHARED_SUBPLAN=0. Exported atomic.Bool (ScalarAggSemijoin pattern) so tests can pin either arm.
var SortMergeJoinsPlanned atomic.Int64
SortMergeJoinsPlanned counts joins routed through the sort-merge path, process-wide. Observability: the TPC-H forced-on gate test uses it to prove the route fired (and, on the default config, that it stayed dormant); ops dashboards can sample it the same way.
var StageFusion atomic.Bool
StageFusion gates fuseStageChains. Kill switch WADJET_STAGE_FUSION=0. Exported atomic.Bool (AggOverExchange pattern) so tests can pin either arm.
var StageFusionAgg atomic.Bool
StageFusionAgg gates the join→partial-aggregate absorb (step 2) on top of the join→join fusion. Sub-switch WADJET_STAGE_FUSION_AGG=0 isolates the two mechanisms for A/B; the master WADJET_STAGE_FUSION=0 kills both.
var TopNLateMatPlanned atomic.Int64
TopNLateMatPlanned counts top-N pipelines planned with late materialization. Observability-first (mirrors LateMatJoinsPlanned): dormancy tests assert zero with the switch off; engagement tests use it instead of inferring from wall clock.
Functions ¶
func AssertExchangeConsistency ¶
AssertExchangeConsistency walks every (producer, consumer, slot) edge in the stages slice and asserts that producer.Distribution.Satisfies( RequiredChildDistribution(consumer, slot)). Returns the first violation as an error, or nil if all edges are consistent.
In BehaviorPreservingMode, violations are logged at WARN and nil is returned — Phase 1 is purely additive and must not block any plan that the heuristic switch would otherwise accept.
Phase 2 promotes this to the satisfaction check that drives Exchange insertion: a violation triggers an Exchange stage being added, not a plan rejection.
func BuildAggregateShuffleSQL ¶
func BuildAggregateShuffleSQL(cand AggregateShuffleCandidate, stages []Stage) (string, error)
BuildAggregateShuffleSQL reconstructs the SQL text for the derived-aggregate pre-compute task from a candidate + the physical stage graph. The coordinator dispatches this SQL as a normal pipeline task so the workers run it via the same execution path that handles any other GROUP BY query; the output rows are written to S3 and later streamed into probe tasks as a pre-computed build input.
Phase 1 scope: single-scan-rooted aggregate with simple GROUP BY and supported aggregate functions. Scan filters push through unchanged. Any shape the reconstruction cannot represent causes a caller-level fallback to the existing in-pipeline execution — safety first, performance later.
func BuildSemiAntiFilter ¶
func BuildSemiAntiFilter(filter string) func(probe *batch.RecordBatch, probeRow int, build *batch.RecordBatch, buildRow int) bool
func CanProbeSplit ¶
func CanProbeSplit(stages []Stage, workerCount int) (probeAlias string, probeFiles []string, ok bool)
CanProbeSplit returns the scan alias and file list for probe-split pipeline routing. Probe-split distributes the dominant probe table's files across workers while each worker scans build tables in full. This enables parallel execution for join-heavy queries where compute is the bottleneck.
Returns the probe scan alias, its file list, and true if probe-split is viable.
func CountJoinStages ¶
CountJoinStages returns the total number of joins in the stage list, including hash_join and broadcast_join stages plus fused joins that were absorbed into parent stages by fuseJoinStages().
func HashPartitionCount ¶
HashPartitionCount is the one width rule for hash exchanges whose requirement doesn't pin a count: workerCount × 8, floor 16 — the same rule the join planner uses for its shuffle inputs (higher counts cut per-task hash state). Until 2026-08-03 the count-unpinned path (grouped final_aggregate and window inputs) defaulted to workerCount, so the REDUCE side ran node-count-wide while the map side ran core-scaled: SF100 Q20's 54.5M-group final_aggregate ran 3 tasks of ~23s and ~7.7GB tracked heap each, 4× slower than Trino on the same shape. Single- process planning (workerCount <= 1) keeps one partition — width there comes from morsel parallelism, not partition fan-out.
func ParseSemiAntiNE ¶
ParseSemiAntiNE recognizes a join filter that is EXACTLY one column-to-column not-equal condition ("l1.l_suppkey <> l2.l_suppkey"). That is the decorrelated-EXISTS self-inequality class the distinct-pair build serves; anything else (conjunctions, other operators, literals) returns ok=false and stays on the generic closure path.
func SemiAntiBuildStoreCols ¶
SemiAntiBuildStoreCols returns the build-side columns a filtered semi/anti join must retain in stored build batches: the join keys (required to re-index spilled partitions and to survive FixKeyAssignment's rebuild) plus the JoinFilter's build-side columns. Returns nil when the filter is empty — unfiltered semi/anti builds are key-only and store nothing. Shared by the single-process planner and the worker fragment executor so both paths narrow their builds identically.
func SetExchangePartialAggEnabled ¶
SetExchangePartialAggEnabled toggles the pass (A/B tests run both arms in one process, where the env-var gate is already latched). Returns the previous value so callers can restore it.
func ValidateNativeDAGShape ¶
ValidateNativeDAGShape walks the stage list and returns an error describing the first stage whose shape the native-DAG executor cannot consume. Called by the coordinator before dispatch so plan-shape problems surface as a clear fail-fast at plan time, instead of as silent timeouts (Q01 SF10 2026-04-23 case) or mid-execution input-mapping errors (Q02 SF10 case).
Each branch encodes a contract the executor relies on:
- hash_join / broadcast_join: exactly 2 deps, no FusedJoins. buildTaskInputsForStage maps probe→[0] / build→[1]; >2 deps means the planner left a fused-broadcast tree the dispatcher can't unpack.
- exchange-repartition / replicate / gather: exactly 1 dep (Exchange stages bridge a single child distribution to the parent's required distribution).
- MergeGroupCount > 0: stage is the intermediate tier of a multi-level merge_aggregate / merge_sort tree. collapseMergeTreesForNativeDAG should have flattened it; if it slipped through, dispatch creates the SF10 N-stage thrash.
Types ¶
type AggSpec ¶
type AggSpec struct {
Func string
InputCol string
OutputCol string
// InputExpr is the SQL text of a derived input expression, e.g.
// "l_extendedprice * (1 - l_discount)". Empty when InputCol is a
// bare column reference. Distributed workers compile this into a
// Project operator before the aggregate so HashAggregate sees a
// column whose name matches InputCol.
InputExpr string
}
AggSpec defines an aggregation in a stage.
type AggregateShuffleCandidate ¶
type AggregateShuffleCandidate struct {
JoinStageID string // outer join whose build side is a derived aggregate
AggregateStageID string // aggregate stage directly feeding the join (through any shuffles)
InputScanID string // base-table scan feeding the aggregate's input
InputScanAlias string // scan alias (e.g. "lineitem:1" for Q17's inner scan)
InputScanBytes int64 // EstimatedBytes of the aggregate's input scan
GroupByKeys []string // the aggregate's GROUP BY columns (= partition keys)
JoinBuildKeys []string // the outer join's keys on this side (must be ⊆ GroupByKeys)
JoinProbeKeys []string // the outer join's keys on the probe side
}
AggregateShuffleCandidate describes a join in the plan whose build side is a derived aggregate subplan (e.g. Q17's decorrelated scalar subquery aggregate over full lineitem). When the aggregate's input scan is large enough that broadcasting the whole subplan to every probe-split worker would cause memory pressure, the coordinator can dispatch a distributed partial-then- merge aggregate stage shuffled by the GROUP BY keys — mirroring the shape PickShuffleCandidate returns for base-table builds.
Phase 1 detection: aggregate(GROUP BY K)(scan(T)) feeds a hash_join, and K ⊇ join keys (so the partitioning lines up with the join).
func PickAggregateShuffleCandidate ¶
func PickAggregateShuffleCandidate(stages []Stage, thresholdBytes int64) (AggregateShuffleCandidate, bool)
PickAggregateShuffleCandidate scans stages for a join whose build side is a derived aggregate over a scan larger than thresholdBytes. Returns the first such candidate found. Phase 1: single candidate per query (matches PickShuffleCandidate's single-candidate contract).
The function is conservative: it only returns a candidate when the aggregate's GROUP BY columns include the join's equi-keys for this side. If they don't, shuffling by GROUP BY keys would not align the aggregate output with the probe side's partitioning, and the join would be incorrect. In that case we return !found and let the caller fall back to the existing probe-split or broadcast path.
type AggregateShuffleDiag ¶
type AggregateShuffleDiag struct {
Candidate AggregateShuffleCandidate
Reason AggregateShuffleRejectReason
// ObservedScanBytes holds the input scan size of the closest-matching
// rejected candidate (populated when we got past followToScan). Useful
// for tuning the threshold on real data.
ObservedScanBytes int64
// JoinStageID / InputScanAlias of the closest-matching rejected join, when
// available. Empty for NoJoin / BuildNotAggregate paths.
JoinStageID string
InputScanAlias string
}
AggregateShuffleDiag records the best observed rejection for telemetry. When a candidate IS found, Candidate is populated and Reason == None.
func PickAggregateShuffleCandidateDiag ¶
func PickAggregateShuffleCandidateDiag(stages []Stage, thresholdBytes int64) AggregateShuffleDiag
PickAggregateShuffleCandidateDiag is the diagnostic variant that also returns the reason for rejection (or Candidate + Reason=None for success). Use this when you want to log why detection declined — essential for threshold tuning on real production data.
type AggregateShuffleRejectReason ¶
type AggregateShuffleRejectReason int
AggregateShuffleRejectReason explains why a join stage was not chosen as an aggregate-shuffle candidate. Used by PickAggregateShuffleCandidateDiag so callers can log exactly which gate fired for visibility on real workloads. The set of reasons is intentionally coarse (one per gate) — finer-grained diagnostics belong in the caller's log line.
const ( AggShuffleRejectNone AggregateShuffleRejectReason = iota AggShuffleRejectNoJoin // no hash_join/broadcast_join stages at all AggShuffleRejectBuildNotAggregate // join's right dep chain doesn't terminate in an aggregate AggShuffleRejectAggNotScanRooted // aggregate isn't rooted in a single base scan AggShuffleRejectBelowThreshold // input scan bytes ≤ threshold AggShuffleRejectScanHasFilters // input scan has pushed predicates (Phase 2 scope) AggShuffleRejectKeysNotCovered // aggregate GROUP BY keys don't cover join build keys )
func (AggregateShuffleRejectReason) String ¶
func (r AggregateShuffleRejectReason) String() string
String renders a reject reason as a short tag suitable for logs.
type ChainedJoinSpec ¶
type ChainedJoinSpec struct {
JoinType string
JoinLeftKeys []string
JoinRightKeys []string
BuildDepStage string // stage providing build-side data
BuildTableAlias string
BuildColOrigins map[string]string
JoinFilter string
// FilterExprs are the absorbed stage's residual post-join filters —
// emitted as an OpFilter immediately after this chained probe.
FilterExprs []string
// BuildFilterExprs filter the build input rows before hash-table
// construction (exchange-subsume flag filters on the absorbed stage).
BuildFilterExprs []string
QualifyAllBuildCols bool
// Columns is the absorbed stage's output projection; applied as the
// chained probe's OutputFilter so the fused stage emits exactly what
// the absorbed stage emitted.
Columns []string
// Partitioned marks a hash-partitioned 1:1 build input (the absorbed
// stage was a hash_join): task i reads build partition i. False means
// a replicated broadcast build read whole by every task.
Partitioned bool
}
ChainedJoinSpec describes a 1:1 downstream join absorbed into an upstream hash_join stage by fuseStageChains. It carries everything the dispatcher needs to emit the join as a post-primary probe op in the fused fragment.
type ComputedCol ¶
ComputedCol is one appended expression column on a shuffle payload.
type DistKind ¶
type DistKind int
DistKind is the kind of partitioning a stage's output has.
const ( DistSingleton DistKind = iota // single worker has all rows DistBroadcast // every worker has all rows DistHashPartitioned // rows partitioned by hash(Keys) % Count // DistRoundRobin: multiple parallel tasks, no key clustering. Used to // model multi-task partial aggregates / multi-task scans where each task // emits its own subset of the input rows. Unlike DistSingleton, this kind // does NOT trivially satisfy RequiredClusteredOn — downstream consumers // that need keys co-located must shuffle. The Phase 1 spec deferred this // label to Phase 2/3; needed once the executor wires real fan-out into // the property graph (e.g. dispatchScanAggregateStage at workerCount > 1). DistRoundRobin )
type Distribution ¶
type Distribution struct {
Kind DistKind
Keys []string // for DistHashPartitioned
Count int // for DistHashPartitioned
}
Distribution describes how a stage's output is partitioned across workers.
func OutputDistribution ¶
func OutputDistribution(stage Stage, deps map[string]Distribution, workerCount int) Distribution
OutputDistribution computes the partitioning a stage's output has, given the resolved distributions of its dependencies. Pure function over stage fields + dep map + cluster size. Rules track how today's planner emits stages; see the Phase 1 spec §"OutputDistribution" for the per-stage table.
workerCount is needed to distinguish single-task (Singleton) from multi-task (RoundRobin) variants of stages whose dispatcher fans out at runtime (e.g. dispatchScanAggregateStage). Phase 1 punted on this and labeled everything Singleton (see spec Risk #2); Phase 3 wires the real label so the property algebra correctly forces hash-shuffles ahead of grouped finals.
func (Distribution) Equals ¶
func (d Distribution) Equals(other Distribution) bool
Equals reports whether two Distributions are identical.
func (Distribution) Satisfies ¶
func (d Distribution) Satisfies(req RequiredDistribution) bool
Satisfies reports whether this distribution meets a consumer's required distribution. Single mechanical predicate that mirrors Spark's Partitioning.satisfies(Distribution). The truth table is documented in the Phase 1 spec §"The property algebra".
RequiredAny: always true.
RequiredSingleton: only DistSingleton.
RequiredBroadcast: only DistBroadcast.
RequiredClusteredOn(K): DistBroadcast yes; DistSingleton yes;
DistHashPartitioned iff Keys==K;
DistRoundRobin no (multi-task, unclustered).
RequiredHashPartitionedOn(K, N): only DistHashPartitioned with Keys==K
and Count==N.
func (Distribution) SatisfiesJoinKeys ¶
func (d Distribution) SatisfiesJoinKeys(joinKeys []string) bool
SatisfiesJoinKeys reports whether this distribution allows a co-located join on the given keys without re-shuffling. Preserved as a thin wrapper over Satisfies for existing callers.
type DynamicFilterConsume ¶
type DynamicFilterConsume struct {
FilterID string
SourceStageID string
TargetColumn string
KeyType string
// AttachOnArrival marks this consume as non-blocking: the stat-dep edge
// is removed, the consumer dispatches immediately, and its tasks install
// the bloom mid-scan when the emitter's merged artifact lands at the
// deterministic staged key. Set only by applyAttachOnArrival under its
// structural rules; drop-only bloom semantics keep results identical.
AttachOnArrival bool
}
DynamicFilterConsume is the planner-side spec attached to a probe-side leaf scan stage. SourceStageID names the build-scan stage that emits the corresponding stats; the planner also appends SourceStageID to this stage's Dependencies so the stage DAG serializes them.
type DynamicFilterEmit ¶
type DynamicFilterEmit struct {
FilterID string
KeyColumn string
KeyType string // "int32" | "int64" | "date"
BloomBits int // total bloom-bitset size; identical across all tasks so union = bitwise OR
// AtOutput accumulates over the stage's OUTPUT stream (pre-sink) rather
// than the scan source — required when the emitting stage is a join or
// filtered scan whose output, not input, defines the key set
// (markSemiAntiBuildFilters).
AtOutput bool
// LateAttach forces the coordinator to stage the merged filter to its
// deterministic S3 key regardless of inline size — an attach-on-arrival
// consumer polls that key, so it must exist even for tiny blooms
// (applyAttachOnArrival; docs/design/attach-on-arrival-dynamic-filters.md).
LateAttach bool
// GuardConsumes lists the FilterIDs of this stage's OWN attach-mode
// consumes that must retro-filter this emit's buffered head rows at
// finalize (guarded re-emit — applyAttachOnArrival rule-1 relaxation).
// The worker's emit op buffers (emit-key, guard-column) pairs for rows
// scanned before those blooms install and drops non-matching pairs
// before the partial uploads, keeping the emitted bloom exactly as
// tight as under the start barrier.
GuardConsumes []string
// InFlow marks an emitter whose tasks must ride NORMAL scheduling
// instead of the priority lane. The lane's contract is planner-bounded
// tiny tasks (extra slots above MaxConcurrent are memory-safe only
// because dimension scans are tiny) and its purpose is overtaking bulk
// work that is ALREADY consuming the filter attach-mode. An in-flow
// cascade mid (e.g. 15M-row customer) violates the first and doesn't
// need the second: its consumer is WAIT-blocked on a stat-dep, so
// ordinary slots serve it correctly (docs/design/dimension-cascade.md
// §In-flow mid emitters).
InFlow bool
}
DynamicFilterEmit is the planner-side spec attached to a build-side leaf scan stage. Mirrors distributed.DynamicFilterEmit (separate copy keeps the physical package free of the wire-format dependency direction; the dispatcher converts at the boundary).
type ExchangeStage ¶
type ExchangeStage struct {
Keys []string // Repartition only
Count int // Repartition only
Ordering []SortKeySpec // Gather only (optional sort-merge gather)
BuildAlias string // Repartition, Replicate
ProbeAlias string // Repartition, Replicate
BuildBytes int64 // Repartition (for logging / threshold checks)
// ComputedCols are expression columns APPENDED to the shuffle payload
// (after the projected scan columns). Set by dedupeSubsumedScanExchanges
// so one raw exchange can serve a dropped filtered sibling: the
// sibling's scan filter ships as a cheap computed flag (1 byte/row)
// instead of a second full scan+shuffle of the table. Workers evaluate
// Expr per batch and append the result under Name.
ComputedCols []ComputedCol
// ExtraReadCols are columns the shuffle's source scan must READ so the
// ComputedCols expressions can evaluate, but which are NOT part of the
// shipped payload — the worker drops them after computing the flags.
ExtraReadCols []string
// PartialAggGroupBy/PartialAggSpecs mark this Repartition for
// sender-side partial aggregation (markExchangePartialAgg): the
// shuffle task pre-combines rows on PartialAggGroupBy, shipping
// name-preserving SUM/MIN/MAX partials (OutputCol == InputCol)
// instead of raw rows. Set only when every consumer is proven
// merge-compatible; empty means ship raw.
PartialAggGroupBy []string
PartialAggSpecs []AggSpec
}
ExchangeStage carries the per-variant payload attached to an Exchange Stage. Stored on Stage.Exchange (pointer) so non-Exchange stages pay no memory cost.
Keys, Count are Repartition-only. Ordering is Gather-only. BuildAlias, ProbeAlias, BuildBytes are populated by EnsureDistribution on Repartition and (BuildAlias/ProbeAlias only) Replicate stages, so the coordinator lowering pass can synthesize ShuffleCandidate without calling PickShuffleCandidate.
type FusedJoinSpec ¶
type FusedJoinSpec struct {
JoinType string
JoinLeftKeys []string
JoinRightKeys []string
BuildDepStage string // stage providing build-side data
BuildTableAlias string
BuildColOrigins map[string]string // bare build col → owning scan alias (multi-table builds only)
JoinFilter string
FilterExprs []string
}
FusedJoinSpec describes a broadcast join absorbed into a parent join stage.
type OutputRename ¶
OutputRename pairs a worker-emitted column name with the SELECT-list alias the user wrote. The coordinator rewrites batch and result schemas after Gather using these pairs. When Expr is non-nil, the coordinator compiles and evaluates the expression per row instead of doing a name rename — used for wrapped aggregates ("SUM(x)/7.0 AS y" emits Expr=BinaryOp{ColRef("__agg_0"), /, 7.0} because the logical planner replaces nested aggregates with refs to their synthetic OutputCol). From is the primary input column (used as the existence check); other column refs in Expr resolve via ColumnIndexFallback.
type PhysicalPlan ¶
type PhysicalPlan struct {
Pipeline *exec.Pipeline
Stages []Stage // for distributed execution
Cleanup func() // optional: called after pipeline finishes to clean up spill files
}
PhysicalPlan represents an executable query plan.
func (*PhysicalPlan) PrettyPrint ¶
func (p *PhysicalPlan) PrettyPrint() string
PrettyPrint returns a formatted string representation of the physical plan.
type Planner ¶
type Planner struct {
MemoryBudget int64 // per-query memory budget in bytes (0 = unlimited)
SpillDir string // directory for spill files (empty = os temp dir)
// SharedTracker / SharedSpillMgr (if set) are used in place of per-query
// Tracker+SpillManager creation. Workers set these to point at the
// executor-level pool so concurrent tasks on the same worker compete for
// ONE budget and spill cooperatively under pool pressure, matching the
// Trino/Spark unified memory manager model. When nil, getSpillManager()
// falls back to creating a per-query pool as before.
QueryLimits *config.QueryLimits // cost-based query guard (nil = no limits)
WorkerCount int // number of distributed workers (for shuffle partitioning)
// BroadcastBytesThreshold is the maximum estimated build-side size for
// a join to be planned as broadcast_join. Builds above this become
// hash_join (hash-shuffle), eliminating the N× build-cache duplication
// every worker pays under broadcast.
//
// Zero = use absolute default (100 MB), preserving legacy behavior for
// embedded callers that don't set this. Distributed callers should set
// this from per-worker pool budget — e.g., budget * 0.3 capped at
// 200 MB — so the broadcast/shuffle decision adapts to cluster memory
// instead of trusting an absolute constant. Negative = never broadcast
// (force every join to hash_join).
//
// Architectural rule the threshold encodes: broadcast is sound when
// every worker can comfortably hold the full build state in memory
// alongside concurrent hash tables. As cluster width or per-worker
// budget shrinks, the threshold shrinks too — without changing the
// planner's join-type logic.
BroadcastBytesThreshold int64
// SortMergeJoinBytes gates the sort-merge join path for big-vs-big inner
// equi-joins (docs/design/sort-merge-join.md). A join takes SMJ only when
// BOTH sides' estimated post-selectivity bytes reach this threshold —
// small builds keep the strictly-better hash/broadcast paths. Zero (the
// shipped default) disables SMJ entirely; the planner behaves exactly as
// before. Distributed callers should derive it from per-worker pool
// budget (the broadcast-threshold pattern) so "too big to sit resident"
// tracks cluster memory.
SortMergeJoinBytes int64
// LateMaterialization emits inner/left hash-join output as view
// (dictionary) columns over the probe input and build batches; the
// gather is deferred to the first consumer needing owned storage
// (docs/design/late-materialization.md). Off by default.
LateMaterialization bool
// DynamicFiltersEnabled gates the Trino-style dynamic-filter planner
// pass. When true, applyDynamicFilters annotates eligible hash_join
// build/probe leaf scans with Emit/Consume specs and adds the stat-dep
// edge from build-scan to probe-scan. Off by default for v1 rollout;
// distributed callers flip this on after the local-harness gate passes.
DynamicFiltersEnabled bool
// MaterializedInputs holds pre-scanned data for scan-split pipeline mode.
// When populated, buildScan uses these batches instead of reading from the
// object store, allowing parallel scan I/O with single-worker compute.
// Keyed by scan alias: "table" or "table:N" for self-joins.
MaterializedInputs map[string][]*batch.RecordBatch
// StreamingSources holds lazy sources for scan-split pipeline mode.
// Unlike MaterializedInputs, these yield batches on demand without
// materializing all data upfront. Checked before MaterializedInputs.
// Keyed by scan alias: "table" or "table:N" for self-joins.
StreamingSources map[string]exec.Source
// ScanFileFilter restricts which files each scan alias reads. Used in
// probe-split pipeline mode where the probe table is partitioned across
// workers while build tables read all files. Keyed by scan alias.
ScanFileFilter map[string][]string
// contains filtered or unexported fields
}
Planner converts logical plans to physical plans.
func NewPlanner ¶
NewPlanner creates a new physical planner.
func (*Planner) AnnotateScanColumns ¶
AnnotateScanColumns walks the logical plan tree and populates ScanColumns on Scan nodes from the catalog. This enables the logical optimizer to resolve unqualified column references for filter pushdown through joins.
func (*Planner) EstimatePlanScanBytes ¶
EstimatePlanScanBytes walks a logical plan and sums the catalog bytes (compressed parquet, after partition-filter pruning) of every scan. Returns ok=false when the plan's input size cannot be resolved from the catalog — unknown table (table functions, virtual sources) or a residual subquery expression whose scans are not visible in the tree. Callers use this to route small queries onto the coordinator-local fast path; the only safe failure mode is over-estimation, so every unknown is "too big".
func (*Planner) ExpandFederatedScans ¶
ExpandFederatedScans checks if scan stages reference tables that exist on multiple clusters. If so, it splits the scan into per-cluster scan stages and rewrites downstream dependencies. Returns stages unchanged if only one cluster has the table (or if federation lookup fails).
func (*Planner) PlanDistributed ¶
PlanDistributed generates a stage DAG for distributed execution. Returns stages with dependency ordering suitable for coordinator dispatch.
func (*Planner) ValidateColumns ¶
ValidateColumns checks that every column reference in a SELECT statement resolves to a column available in its scope, returning an error naming the first unresolvable reference. This is plan-time name binding: it runs over the SQL AST (where query-block boundaries — FROM sources, derived tables, CTEs, subqueries — are still explicit) rather than the inlined logical tree.
The binder is deliberately conservative: it errors only when a reference provably resolves to no column in any reachable source. Whenever a scope is uncertain — an open-schema source is present (table function, recursive CTE, SELECT *, a table absent from the catalog), an expression fails to parse, or a qualifier can't be matched — it skips rather than risk rejecting a valid query. A false positive breaks a working query; a false negative merely lets a typo through to the existing runtime check.
type PreComputedAggregateMeta ¶
type PreComputedAggregateMeta struct {
InputTable string
GroupByCols []string
AggSpecs []AggSpec
CacheFiles []string
}
PreComputedAggregateMeta travels on physical.Stage to tell task creation which cache files back a derived aggregate subtree the worker should substitute. Distinct from distributed.PreComputedAggregate (the wire type) — the coordinator converts between them when assembling tasks.
type ProjectExprSpec ¶
ProjectExprSpec is one SELECT-list item a scan fragment must emit: Name is the output column, Expr the SQL text the worker compiles and evaluates (bare column references become passthrough copies). Type is the plan-time inferred output type for computed expressions (inferProjectionType) — the worker cannot resolve it from the input schema because the output column doesn't exist there; zero means "resolve from source column" (bare refs).
type QueryCost ¶
type QueryCost struct {
TotalBytes int64
TotalRows int64
TotalFiles int
HasFilter bool
HasLimit bool
}
QueryCost summarizes the estimated cost of a query across all scan stages.
type RequiredDistribution ¶
type RequiredDistribution struct {
Kind RequiredKind
Keys []string
Count int
}
RequiredDistribution describes what a consumer stage requires of each input. Derived from existing stage fields (JoinLeftKeys, JoinRightKeys, GroupByCols, ShuffleKeys) by RequiredChildDistribution; never stored on Stage.
func RequiredChildDistribution ¶
func RequiredChildDistribution(stage Stage, slot int) RequiredDistribution
RequiredChildDistribution returns the per-slot required distribution for a stage's input. `slot` indexes into the stage's logical input list:
- For joins: slot 0 is the probe (LeftDepStage), slot 1 is the build (RightDepStage).
- For unary stages: slot 0 is the sole input.
- For stages with no inputs (scan, dual): RequiredAny is returned for any slot.
Never stored on Stage; recomputed by AssertExchangeConsistency. Rules are derived from how walkStages already implicitly constructs the plan; see the Phase 1 spec §"RequiredChildDistribution" for the per-stage table.
Unknown stage types return RequiredAny (no constraint asserted). New stage types added to the planner must add their rule here or accept the no-op default.
type RequiredKind ¶
type RequiredKind int
RequiredKind enumerates the partitioning a consumer needs from each input. Mirrors Spark's Distribution trait subclasses; see the Phase 1 spec (docs/archive/specs/2026-04-20-distribution-property-phase-1.md) §"The property algebra" for the satisfaction truth table.
const ( RequiredAny RequiredKind = iota // no constraint RequiredSingleton // exactly one partition (final result, coordinator merge) RequiredBroadcast // every worker has every row RequiredClusteredOn // co-partitioned on Keys, any partition count RequiredHashPartitionedOn // hash-partitioned on Keys with exactly Count partitions )
func (RequiredKind) String ¶
func (r RequiredKind) String() string
String renders a RequiredKind for log lines and assertion error messages. Stable text identifiers — do not change without checking telemetry consumers.
type ShuffleCandidate ¶
type ShuffleCandidate struct {
JoinStageID string // the join stage to be served by shuffled inputs
BuildAlias string // which scan stage produces the build side
ProbeAlias string // which scan stage produces the probe side (the largest scan)
BuildKeys []string // build-side join keys (the join's JoinRightKeys)
ProbeKeys []string // probe-side join keys (the join's JoinLeftKeys)
JoinKeys []string // canonical (build-side) join key names for partitioning
BuildBytes int64 // EstimatedBytes of the build scan (for logging)
}
ShuffleCandidate describes a join in the plan whose build side is large enough to warrant the shuffle execution path instead of broadcast.
func PickShuffleCandidate ¶
func PickShuffleCandidate(stages []Stage, thresholdBytes int64) (ShuffleCandidate, bool)
PickShuffleCandidate identifies the largest non-probe scan above thresholdBytes as the shuffle candidate — the table that would otherwise be broadcast-duplicated as the runtime build side — and returns the join stage that connects it to the probe.
The approach deliberately does NOT read BuildTableAlias on the join stage because in probe-split mode the planner's logical build/probe assignment is inverted at runtime: the planner labels the largest scan as the build (e.g. "lineitem"), but probe-split partitions that scan across workers, making the second-largest scan (e.g. "orders") the actual broadcast hash table. Shuffling orders instead of broadcasting it is the correction.
Algorithm:
- probeAlias = largest scan (matches CanProbeSplit's heuristic).
- candidate = largest non-probe scan above thresholdBytes.
- Walk join stages to find one that directly references the candidate scan (via LeftDepStage or RightDepStage) or via a FusedJoin entry whose BuildTableAlias matches the candidate alias.
- Extract build/probe keys from the matching join or fused-join entry.
Phase 1: returns the single best candidate. Phase 2 (chained shuffles) will return all candidates.
type SortKeySpec ¶
SortKeySpec defines a sort key in a stage.
type Stage ¶
type Stage struct {
ID string
Type string // see exchange.go constants (scan, aggregate, sort, hash_join, broadcast_join, window, pipeline, exchange-repartition, exchange-replicate, exchange-gather)
ClusterID string // target cluster for routing ("" = local/coordinator's cluster)
Dependencies []string
Tasks int
// Scan metadata
TableName string
ScanAlias string // unique scan identity: "table" or "table:N" for Nth duplicate
Columns []string
// OutputColumns, when non-empty, narrows the stage's EMITTED columns
// to this set (worker inserts a zero-copy ColumnPrune before the
// sink). Columns stays the READ set — a scan must read its pushed
// filter columns but must not ship them: Q13's orders scan read
// o_comment for the NOT LIKE and then materialized+shuffled it,
// 68.8 B/row where 16 were consumed (~16 GB excess at SF100).
// Set by pruneScanOutputColumns from consumer declarations.
OutputColumns []string
PartitionFilter map[string]string
ScanFiles []string // files to distribute across scan tasks
// ScanFileSizes aligns 1:1 with ScanFiles (catalog SizeBytes). Feeds
// byte-balanced affinity fan-outs (coordinator scan_affinity.go);
// empty/misaligned degrades to count-based splitting.
ScanFileSizes []int64
FilterExprs []string // SQL filter expressions pushed down to scan
// Aggregate metadata
GroupByCols []string
AggSpecs []AggSpec
// GroupByAll marks a keys-only hash aggregate over EVERY input column —
// the DISTINCT shape. The key set is resolved at runtime from the input
// schema (no plan-time column list), matching exec.HashAggregate.GroupByAll
// and the single-process buildDistinct path.
GroupByAll bool
// Sort metadata
SortKeys []SortKeySpec
Limit int
// SortShardLocal marks a grouped final_aggregate whose SortKeys/Limit
// are SHARD-LOCAL: the stage fans out across disjoint group-key shards
// (each computes exact aggregates for its groups, then sorts and
// applies Limit locally) and a surviving downstream Singleton sort
// stage merges the N sorted ≤Limit-row outputs. Distribution rules
// treat such a stage like a sort-free grouped final (input clustered
// on GroupByCols, output mirrors the input partitioning) — without
// this flag SortKeys/Limit force the Singleton collapse. Set only by
// fuseSortIntoPredecessor's shard-local fold.
SortShardLocal bool
// Join metadata
JoinType string // inner, left, right, full, cross
JoinLeftKeys []string
JoinRightKeys []string
LeftDepStage string // stage providing probe (left) side
RightDepStage string // stage providing build (right) side
BuildTableAlias string // build-side table alias for column disambiguation in self-joins
// BuildColOrigins maps each bare build-output column (lowercased) to the
// scan alias that owns it. Only set when the build subtree spans multiple
// tables (bushy shapes) — nil for single-scan builds, where
// BuildTableAlias is already exact. The join executor qualifies duplicate
// build columns with the OWNING alias instead of BuildTableAlias.
BuildColOrigins map[string]string
JoinFilter string // semi/anti join inequality filter (e.g., "l2.l_suppkey != l1.l_suppkey")
// BuildFilterExprs are row predicates applied to the BUILD input before
// hash-table insertion. Set by dedupeSubsumedScanExchanges when this
// join's build was rewired from a filtered exchange to a subsuming raw
// exchange: the dropped exchange's scan filter (or its computed
// __subsume flag) must now run at build-read time. Semantically
// identical to filtering at the dropped scan.
BuildFilterExprs []string
// Fused broadcast joins absorbed into this stage (avoids separate
// shuffle+join stages for small dimension tables like nation, region).
FusedJoins []FusedJoinSpec
// ChainedJoins are 1:1 downstream joins absorbed into this stage by
// fuseStageChains (docs/design/stage-chain-fusion.md). Unlike
// FusedJoins (broadcast probes applied BEFORE the primary join), these
// run AFTER it, in order — the fragment pipes the primary's output
// through each chained probe in-process, eliding the per-link
// materialization the separate stages paid.
ChainedJoins []ChainedJoinSpec
// ChainedAgg* describe a downstream PARTIAL aggregate absorbed as the
// chain's terminal step (fuseStageChains step 2): the fragment runs
// OpHashAggregate (raw mode) after the chained joins, so the join
// output collapses to partials in-process instead of materializing
// for a separate round-robin aggregate stage. Partial aggregation is
// partition-agnostic, so the fused stage keeps ITS OWN distribution
// and task count — it just emits N partial outputs instead of the
// dropped stage's fan-out count; finals merge either way.
ChainedAggGroupBy []string
ChainedAggSpecs []AggSpec
// Window metadata
WindowCols []WindowColSpec
// JoinPartitionCount is the number of partitions for a hash-join stage
// that was preceded by repartition exchanges. Zero means the join is
// not partitioned (broadcast or single-partition). Exchange stages carry
// their partition count on Exchange.Count instead.
JoinPartitionCount int
// Fused scan-aggregate: partial aggregation is performed at the scan
// level, eliminating the scan→aggregate S3 round-trip. Workers produce
// partial aggregate results instead of raw rows.
FusedAggGroupBy []string
FusedAggSpecs []AggSpec
// RawInputAggregate marks a final_aggregate whose input is RAW rows
// from an exchange hash-partitioned on the group keys, not partial
// aggregates — set by rewireAggOverRawExchange when it rewires the
// final from a duplicate fused scan-agg leg onto a sibling raw
// exchange. Partition-disjoint keys make per-partition raw aggregation
// exact, so the dispatcher builds the fragment with MergeMode=false
// (no InputCol→OutputCol remap, no COUNT→SUM rewrite). AggSpecs carry
// the raw form (the dropped scan's FusedAggSpecs).
RawInputAggregate bool
// Probe-split pipeline: partition the probe table's files across workers.
// Each worker scans build tables in full and probes its file partition.
ProbeSplitAlias string // scan alias to partition (e.g., "lineitem")
ProbeSplitFiles []string // full file list to split across tasks
// BuildCachePreScans holds pre-scanned result file paths for large build
// tables. When populated by the coordinator (after pre-scanning them once),
// workers load these cached files via PreScannedInputs instead of scanning
// the large source table N times — eliminating the N× build-side duplication
// that causes OOM on Q09 at SF100. Keyed by scan alias (e.g., "orders").
BuildCachePreScans map[string][]string
// PreComputedAggregates holds signatures + cache paths for derived-
// aggregate builds that were computed once by the coordinator before
// dispatch. Each probe-split task carries the same list; the worker's
// plan-rewrite pass matches logical Aggregate subtrees against the
// signatures and replaces them with synthetic scans of the cache files.
// Spec: 2026-04-18-shuffle-distributed-aggregate.md.
PreComputedAggregates []PreComputedAggregateMeta
// Multi-level merge: partitions upstream results among parallel merge groups.
// When MergeGroupCount > 0, this stage processes only the MergeGroup-th
// fraction of its dependency results. Independent merge groups run on
// different workers for parallel merging.
MergeGroup int // 0-based index of this merge group
MergeGroupCount int // total groups (0 = not grouped, process all results)
// Cost estimation (populated at plan time from manifest metadata)
EstimatedBytes int64
EstimatedRows int64
// Distribution describes how this stage's output is partitioned.
// Default zero value is {Kind: DistSingleton} which is correct for
// most existing stages (single-worker output). Shuffle stages set this
// to DistHashPartitioned with Keys and Count populated. Broadcast pre-scans
// (build cache) set Kind: DistBroadcast.
Distribution Distribution
// Exchange carries per-variant metadata for StageExchange* stages.
// nil for non-Exchange stages.
Exchange *ExchangeStage
// ScalarDependencies maps placeholder names (e.g. ":scalar_1") to
// producer stage IDs that emit a single-row, single-column output. The
// native-DAG coordinator awaits each producer, extracts the scalar from
// its stage output, and string-substitutes the placeholder in this
// stage's FilterExprs / AggSpecs.InputExpr before dispatching tasks.
// This lets CTE-referencing scalar subqueries share the distributed
// float-accumulation path with the filter-carrying stage's upstream,
// eliminating the single-process vs distributed bit-pattern divergence
// that caused Q15 to return 0 rows at SF0.1.
ScalarDependencies map[string]string
// OutputRenames is the SELECT-list alias map applied by the coordinator
// to the Gather stage's result schema. walkStages currently passes
// NodeProject through without applying its projections, so without this
// the final result schema carries raw worker column names ("n1.n_name",
// "substr(l_shipdate, 1, 4)") instead of the user's aliases
// ("supp_nation", "l_year"). Only populated on the Gather stage.
OutputRenames []OutputRename
// ProjectExprs, set on a leaf scan stage whose output feeds the gather
// directly, makes the scan fragment compute the SELECT list (worker-side
// exec.Project after scan+filter). Without it a bare expression SELECT
// over a scan reaches the gather as raw scan columns — the gather's
// applyOutputRenames can rename/drop but not evaluate (#169). Expression
// entries are named by the lowercased expression text, matching the
// convention extractOutputRenames already expects for worker-computed
// expressions; bare columns are passthrough entries so the fragment
// output is exactly the SELECT-list inputs.
ProjectExprs []ProjectExprSpec
// SecurityProjectExprs is the ABAC security barrier absorbed from a
// SecurityBarrier logical Project wrapping this scan
// (absorbSecurityBarrier): visible columns pass through, masked columns
// are literal expressions, denied columns are absent. Applied as the
// FIRST projection in the scan fragment — before ProjectExprs and
// before any aggregate — so restricted values never leave the worker.
SecurityProjectExprs []ProjectExprSpec
// Dynamic filter (Trino-style semi-join pushdown) annotations.
// EmitDynamicFilters is set on a build-side leaf scan stage; each task
// computes a partial KeyRange+Bloom and uploads as a sideband artifact.
// ConsumeDynamicFilters is set on a probe-side leaf scan stage; the
// coordinator unions the upstream partials and injects the result into
// each scan task's OpSpec.DynamicFilters.
//
// The stat-dep edge (emit stage ID appended to the consume stage's
// Dependencies) makes execute_stage_dag.go serialize them via the
// existing dependency mechanism — no new edge type or async broker.
EmitDynamicFilters []DynamicFilterEmit
ConsumeDynamicFilters []DynamicFilterConsume
// QualifyAllBuildCols, when true, instructs the join executor to always
// emit build-side columns under their qualified name
// ("BuildTableAlias.col_name") instead of the default behavior of
// qualifying only on probe-collision. Set by the planner when the same
// source table is scanned more than once and the scans co-path into the
// same join chain (Q07's "nation n1" + "nation n2"). Without this flag
// the FIRST self-join leaves its column unqualified, the SECOND qualifies
// only its own copy, and references to the FIRST alias resolve to NULL
// downstream.
QualifyAllBuildCols bool
}
Stage represents a unit of distributed work with metadata for task creation.
func EnsureDistribution ¶
EnsureDistribution walks the stage DAG and inserts Exchange stages wherever a child's OutputDistribution does not satisfy its parent's RequiredChildDistribution. Returns a new []Stage; does not mutate the input slice.
func LargeBuildScans ¶
LargeBuildScans returns scan stages that are build-side (not the probe alias) and whose estimated size exceeds the given threshold. These are candidates for the build-side broadcast cache: the coordinator pre-scans them once, caches the result in S3, and each worker loads the shared cache instead of independently scanning the large source table N times.
The cache provides two wins for queries with selective build-side filters or wide build tables:
- Avoids decoding the source parquet on every worker (parquet decode is CPU-expensive; the cached WSHF format is essentially raw typed bytes and reads in a fraction of the time).
- Lets the planner overlap the slow source scan with the rest of the query once instead of N times.
We previously gated this on len(large) >= 2 ("only cache when multiple large builds would compound a worker's hash table footprint"), reasoning that single-large-build queries can fit one hash table in memory and the cache only adds spill+upload latency. SF100 deploy disproved that: Q07's historical 3m13s was caching orders, and skipping it pushed the same query past 19 minutes (workers stuck spilling/scanning parquet 3 times). The win from caching orders comes mostly from amortising parquet decode, not from memory deduplication.
type WindowColSpec ¶
type WindowColSpec struct {
Func string
InputCol string
OutputCol string
PartitionBy []string
OrderBy []SortKeySpec
Frame *logical.WindowFrameSpec
}
WindowColSpec defines a window function column in a stage.
Source Files
¶
- agg_over_exchange.go
- aggregate_shuffle.go
- dimension_cascade.go
- distribution.go
- dynamic_filter.go
- dynamic_filter_attach.go
- elide_copartitioned_exchange.go
- ensure_distribution.go
- exchange.go
- exchange_partial_agg.go
- exchange_subsume.go
- fuse_join_shuffle.go
- fuse_scan_aggregate_shuffle.go
- fuse_scan_shuffle.go
- fuse_stage_chains.go
- fused_agg_read_set.go
- late_mat.go
- load_gate.go
- metadata_count.go
- metadata_minmax.go
- native_dag_rewrite.go
- plan.go
- scan_estimate.go
- scan_filter_pushdown.go
- scan_output_prune.go
- semi_anti_build_filter.go
- shared_subplan_dedup.go
- sort_merge_join.go
- subtree_naming.go
- table_func.go
- topn_late_mat.go
- util.go
- validate.go