Skip to main content

mongreldb_query/
distributed.rs

1//! Distributed SQL plan groundwork (Stage 3J, spec section 12.10).
2//!
3//! This module is the planning and coordination seam for tablet-distributed
4//! queries. It deliberately depends on small local metadata traits
5//! ([`TabletLocator`], [`ClusterMetadata`]) instead of `mongreldb-cluster`, so
6//! the query crate stays free of the cluster dependency; the cluster wave
7//! adapts `mongreldb_cluster::tablet` routing metadata onto these traits.
8//!
9//! # What lives here
10//!
11//! * [`DistributedPlan`] — the plan structure from spec section 12.10: pinned
12//!   [`QueryId`] + [`MetadataVersion`], [`PlanFragment`]s with tablet
13//!   assignments, and [`ExchangeDescriptor`] edges between them.
14//! * [`distribute`] — the planner. It lowers a [`LogicalPlanLite`] tree (the
15//!   simplified input IR) onto tablets: colocated joins when both sides share
16//!   partitioning and layout, broadcast joins when the small side fits under a
17//!   byte threshold, repartition joins otherwise. Every fragment carries
18//!   estimated rows/bytes and a maximum spill allowance (spec section 12.10).
19//! * Distributed top-k — [`merge_top_k`] / [`exact_top_k`] are pure functions
20//!   implementing the deterministic coordinator merge (final score descending,
21//!   tablet id ascending, [`RowId`] ascending) with adaptive refill when a
22//!   tablet's unseen-score bound shows it could still contribute winners.
23//! * Execution skeleton — [`FragmentExecutor`], [`FragmentTransport`], and the
24//!   [`Coordinator`] runtime: per-fragment resource reservation, cancellation
25//!   fan-out wired to the existing [`SqlQueryRegistry`](crate::SqlQueryRegistry),
26//!   worker lease expiry that cleans abandoned fragments, and real in-memory
27//!   merge operators (k-way [`MergeSort`](FragmentOperator::MergeSort),
28//!   [`FinalAggregate`](FragmentOperator::FinalAggregate) combine, distributed
29//!   top-k).
30//! * Remote execution — [`RemoteFragmentTransport`] and
31//!   [`RemoteFragmentEndpoint`] use a versioned, bounded Arrow IPC pull
32//!   protocol. One batch per pull provides backpressure; query-scoped
33//!   cancellation and adaptive top-k refill cross the same authenticated
34//!   node-internal RPC carrier.
35//!
36//! # Integration point with DataFusion
37//!
38//! `LogicalPlanLite` is intentionally minimal. The DataFusion integration wave
39//! lowers `datafusion::logical_expr::LogicalPlan` (produced by
40//! [`MongrelSession`](crate::MongrelSession) after catalog/schema resolution)
41//! onto `LogicalPlanLite` — scans of `MongrelScanExec`-backed tables become
42//! [`LogicalPlanLite::Scan`], aggregates/joins/sorts/limits map one-to-one —
43//! and hands the tree to [`distribute`]. Full DataFusion plan conversion is
44//! explicitly out of scope for this wave.
45
46use std::cmp::Ordering;
47use std::collections::{BTreeMap, BinaryHeap, HashMap};
48use std::io::Cursor;
49use std::sync::Arc;
50use std::time::{Duration, Instant};
51
52use arrow::array::{
53    Array, ArrayRef, Float64Array, Int64Array, RecordBatch, UInt32Array, UInt64Array,
54};
55use arrow::compute::{concat_batches, interleave, take};
56use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
57use arrow::ipc::reader::StreamReader;
58use arrow::ipc::writer::StreamWriter;
59use arrow::row::{RowConverter, SortField};
60use arrow::util::display::array_value_to_string;
61use bincode::Options;
62use futures::stream::{self, BoxStream, FuturesUnordered, StreamExt};
63use mongreldb_core::{CancellationReason, ExecutionControl, RowId};
64use mongreldb_types::ids::{MetadataVersion, QueryId, TabletId};
65use parking_lot::Mutex;
66use serde::{Deserialize, Serialize};
67
68use crate::query_registry::{CancelOutcome, RegisteredSqlQuery, SqlQueryOptions, SqlQueryRegistry};
69
70/// Small-side byte threshold below which a join is planned as a broadcast
71/// join (spec section 12.10: "broadcast small side").
72pub const DEFAULT_BROADCAST_THRESHOLD_BYTES: u64 = 8 * 1024 * 1024;
73
74/// Default per-fragment maximum spill allowance (spec section 12.10: "every
75/// plan includes ... a maximum spill allowance"). Bound to the core
76/// [`mongreldb_core::SpillManager`] via [`FragmentControl::begin_spill`].
77pub const DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT: u64 = 256 * 1024 * 1024;
78/// Maximum server-issued authorization envelope forwarded to a worker.
79pub const MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES: usize = 64 * 1024;
80
81/// Rows per `RecordBatch` emitted by coordinator merge operators.
82const COORDINATOR_OUTPUT_BATCH_ROWS: usize = 8_192;
83
84/// Name of the unsigned row-id column every scored (top-k) stream carries.
85/// Tablet scans feeding a distributed top-k must project this column; it is
86/// stripped from the coordinator's final top-k output.
87pub const TOPK_ROWID_COLUMN: &str = "__rowid";
88
89/// Errors raised by distributed planning and coordination.
90#[non_exhaustive]
91#[derive(Debug, thiserror::Error)]
92pub enum DistributedError {
93    /// The locator or metadata has no entry for the table.
94    #[error("unknown table `{0}`")]
95    UnknownTable(String),
96    /// The table's layout has no tablets to scan.
97    #[error("table `{table}` has no tablets in its layout")]
98    EmptyLayout { table: String },
99    /// The input IR or a computed plan is not well-formed.
100    #[error("invalid plan: {0}")]
101    InvalidPlan(String),
102    /// A fragment's resource reservation was denied.
103    #[error("fragment {fragment_id} resource reservation denied: {reason}")]
104    Reservation { fragment_id: u32, reason: String },
105    /// A fragment failed on its worker.
106    #[error("fragment {fragment_id} failed on worker {worker}: {message}")]
107    FragmentExecution {
108        fragment_id: u32,
109        worker: String,
110        message: String,
111    },
112    /// The query was cancelled; carries the first observed reason.
113    #[error("distributed query cancelled: {0:?}")]
114    Cancelled(CancellationReason),
115    /// A groundwork boundary was crossed (documented later-wave work).
116    #[error("unsupported in this wave: {0}")]
117    Unsupported(String),
118    /// Arrow kernel failure inside a merge operator.
119    #[error("arrow error: {0}")]
120    Arrow(String),
121    /// A remote fragment transport call failed.
122    #[error("remote fragment transport error: {0}")]
123    RemoteTransport(String),
124    /// A remote fragment peer violated the versioned wire contract.
125    #[error("remote fragment protocol error: {0}")]
126    RemoteProtocol(String),
127}
128
129/// Result alias for distributed planning and coordination.
130pub type DistributedResult<T> = Result<T, DistributedError>;
131
132impl From<arrow::error::ArrowError> for DistributedError {
133    fn from(error: arrow::error::ArrowError) -> Self {
134        Self::Arrow(error.to_string())
135    }
136}
137
138/// FNV-1a 64-bit hash (same convention as the cluster routing code).
139fn fnv1a64(bytes: &[u8]) -> u64 {
140    let mut hash = 0xcbf2_9ce4_8422_2325_u64;
141    for byte in bytes {
142        hash ^= u64::from(*byte);
143        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
144    }
145    hash
146}
147
148// ---------------------------------------------------------------------------
149// Cluster metadata traits (the dependency-free seam)
150// ---------------------------------------------------------------------------
151
152/// How one table's rows map onto tablets — the planner's dependency-free
153/// mirror of `mongreldb_cluster::tablet::Partitioning` (spec section 12.2).
154/// The cluster wave adapts the cluster type onto this enum one-to-one.
155#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
156pub enum PartitionSpec {
157    /// Hash of the declared columns into a fixed bucket space.
158    Hash { columns: Vec<String>, buckets: u32 },
159    /// Lexicographic ranges over the declared columns.
160    Range { columns: Vec<String> },
161    /// One bucket space per tenant.
162    Tenant {
163        column: String,
164        buckets_per_tenant: u32,
165    },
166    /// Time-bucketed ranges over the timestamp column.
167    TimeRange { column: String },
168    /// Single-tablet (unpartitioned) table.
169    Unpartitioned,
170}
171
172impl PartitionSpec {
173    /// The declared partition-key columns, in key order.
174    pub fn partition_columns(&self) -> Vec<&str> {
175        match self {
176            Self::Hash { columns, .. } | Self::Range { columns } => {
177                columns.iter().map(String::as_str).collect()
178            }
179            Self::Tenant { column, .. } | Self::TimeRange { column } => vec![column.as_str()],
180            Self::Unpartitioned => Vec::new(),
181        }
182    }
183
184    /// True when two tables partition identically, so equal partition keys
185    /// land on the same tablet and a join on the partition key can be planned
186    /// colocated (spec section 12.10: "colocated join"). Two unpartitioned
187    /// tables are trivially colocated (both live on their single tablet).
188    pub fn colocated_with(&self, other: &PartitionSpec) -> bool {
189        match (self, other) {
190            (Self::Unpartitioned, Self::Unpartitioned) => true,
191            (Self::Unpartitioned, _) | (_, Self::Unpartitioned) => false,
192            _ => self == other,
193        }
194    }
195}
196
197/// Planner statistics for one table.
198#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
199pub struct TableStats {
200    /// Estimated live row count.
201    pub row_count: u64,
202    /// Estimated total size in bytes.
203    pub total_bytes: u64,
204}
205
206/// Resolves which tablets serve one table. Defined here (not in
207/// `mongreldb-cluster`) so the query crate stays free of the cluster
208/// dependency; the cluster wave binds `TabletLayout`/routing metadata onto
209/// this trait.
210pub trait TabletLocator: Send + Sync {
211    /// The tablets serving `table`, in deterministic layout order.
212    fn tablets_for_table(&self, table: &str) -> DistributedResult<Vec<TabletId>>;
213    /// How `table` is partitioned across those tablets.
214    fn partitioning(&self, table: &str) -> DistributedResult<PartitionSpec>;
215}
216
217/// Control-plane metadata the planner pins against (spec section 12.10:
218/// "coordinator plans using metadata version").
219pub trait ClusterMetadata: Send + Sync {
220    /// The control-plane metadata version this plan is pinned to.
221    fn metadata_version(&self) -> MetadataVersion;
222    /// Planner statistics for one table.
223    fn table_stats(&self, table: &str) -> DistributedResult<TableStats>;
224}
225
226// ---------------------------------------------------------------------------
227// Input IR (LogicalPlanLite)
228// ---------------------------------------------------------------------------
229
230/// One join-key equality pair (`left.col = right.col`).
231#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
232pub struct JoinKey {
233    /// Column name on the left input.
234    pub left: String,
235    /// Column name on the right input.
236    pub right: String,
237}
238
239/// One sort key.
240#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
241pub struct SortKey {
242    /// Column name.
243    pub column: String,
244    /// True for `ORDER BY ... DESC`.
245    pub descending: bool,
246}
247
248/// One aggregate expression.
249#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
250pub struct AggregateExpr {
251    /// The aggregate function.
252    pub function: AggregateFunction,
253    /// The aggregated column; `None` only for `COUNT(*)`.
254    pub column: Option<String>,
255}
256
257/// Supported aggregate functions.
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
259pub enum AggregateFunction {
260    /// Row count (`column == None`) or non-null count.
261    Count,
262    /// Numeric sum.
263    Sum,
264    /// Numeric minimum.
265    Min,
266    /// Numeric maximum.
267    Max,
268    /// Numeric average.
269    Avg,
270}
271
272impl AggregateFunction {
273    fn name(self) -> &'static str {
274        match self {
275            Self::Count => "count",
276            Self::Sum => "sum",
277            Self::Min => "min",
278            Self::Max => "max",
279            Self::Avg => "avg",
280        }
281    }
282}
283
284/// The simplified logical IR [`distribute`] lowers onto tablets.
285///
286/// This is the seam documented at the module level: the DataFusion
287/// integration wave lowers real DataFusion plans onto this enum; this wave
288/// plans from it directly.
289#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
290pub enum LogicalPlanLite {
291    /// Base-table scan with an optional opaque predicate and projection.
292    Scan {
293        /// Table name.
294        table: String,
295        /// Opaque predicate text (pushed down verbatim; not interpreted in
296        /// this wave).
297        predicate: Option<String>,
298        /// Projected column names; empty = all columns.
299        projection: Vec<String>,
300    },
301    /// Grouped aggregation.
302    Aggregate {
303        /// Input subtree.
304        input: Box<LogicalPlanLite>,
305        /// Group-by columns.
306        group_by: Vec<String>,
307        /// Aggregate expressions.
308        aggregates: Vec<AggregateExpr>,
309    },
310    /// Equi-join.
311    Join {
312        /// Left input subtree.
313        left: Box<LogicalPlanLite>,
314        /// Right input subtree.
315        right: Box<LogicalPlanLite>,
316        /// Equality key pairs.
317        on: Vec<JoinKey>,
318    },
319    /// Sort with an optional limit (`ORDER BY ... [LIMIT k]`).
320    Sort {
321        /// Input subtree.
322        input: Box<LogicalPlanLite>,
323        /// Sort keys, most significant first.
324        keys: Vec<SortKey>,
325        /// Optional limit.
326        limit: Option<usize>,
327    },
328    /// Limit without an ordering.
329    Limit {
330        /// Input subtree.
331        input: Box<LogicalPlanLite>,
332        /// Row limit.
333        limit: usize,
334    },
335}
336
337// ---------------------------------------------------------------------------
338// Plan model (spec section 12.10)
339// ---------------------------------------------------------------------------
340
341/// Plan-local fragment identifier (index into [`DistributedPlan::fragments`]).
342pub type FragmentId = u32;
343/// Plan-local exchange identifier (index into [`DistributedPlan::exchanges`]).
344pub type ExchangeId = u32;
345
346/// Where one fragment executes.
347#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
348pub enum FragmentAssignment {
349    /// On the worker serving this tablet's data.
350    Tablet(TabletId),
351    /// On the query coordinator (gateway) itself.
352    Coordinator,
353}
354
355/// Which join input is the broadcast (build) side.
356#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
357pub enum BuildSide {
358    /// The left input is broadcast.
359    Left,
360    /// The right input is broadcast.
361    Right,
362}
363
364/// One physical operator inside a fragment (spec section 12.10).
365#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
366pub enum FragmentOperator {
367    /// Scan one tablet's slice of a table.
368    TabletScan {
369        /// Table name.
370        table: String,
371        /// Opaque pushed-down predicate text.
372        predicate: Option<String>,
373        /// Projected columns; empty = all.
374        projection: Vec<String>,
375    },
376    /// Receive one exchange edge from a producer fragment.
377    RemoteExchangeSource {
378        /// The exchange edge this source consumes.
379        exchange: ExchangeId,
380    },
381    /// Emit this fragment's output onto one exchange edge.
382    RemoteExchangeSink {
383        /// The exchange edge this sink feeds.
384        exchange: ExchangeId,
385    },
386    /// Per-tablet partial aggregation (pre-shuffle combine).
387    PartialAggregate {
388        /// Group-by columns.
389        group_by: Vec<String>,
390        /// Aggregate expressions.
391        aggregates: Vec<AggregateExpr>,
392    },
393    /// Coordinator-side combine of partial aggregates.
394    FinalAggregate {
395        /// Group-by columns.
396        group_by: Vec<String>,
397        /// Aggregate expressions.
398        aggregates: Vec<AggregateExpr>,
399    },
400    /// Hash join over colocated inputs (no exchange needed).
401    DistributedHashJoin {
402        /// Equality key pairs.
403        on: Vec<JoinKey>,
404    },
405    /// Join where the small build side is broadcast to every big-side
406    /// fragment.
407    BroadcastJoin {
408        /// Equality key pairs.
409        on: Vec<JoinKey>,
410        /// Which input is broadcast.
411        build_side: BuildSide,
412    },
413    /// Join after both sides are hash-repartitioned on the join keys.
414    RepartitionJoin {
415        /// Equality key pairs.
416        on: Vec<JoinKey>,
417    },
418    /// Producer side: local bounded sort. Coordinator side: deterministic
419    /// k-way merge of sorted streams.
420    MergeSort {
421        /// Sort keys.
422        keys: Vec<SortKey>,
423        /// Optional row limit applied after the sort/merge.
424        limit: Option<usize>,
425    },
426    /// Producer side: bounded local top-k plus tie information. Coordinator
427    /// side: deterministic merge (score desc, tablet asc, [`RowId`] asc) with
428    /// adaptive refill (spec section 12.10).
429    DistributedTopK {
430        /// Number of winners.
431        k: usize,
432        /// The (single, descending) score key.
433        score: SortKey,
434    },
435    /// Row limit (per-fragment locally; global at the coordinator).
436    DistributedLimit {
437        /// Row limit.
438        limit: usize,
439    },
440}
441
442/// How rows move across one exchange edge (spec section 12.10).
443#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
444pub enum ExchangeKind {
445    /// Rows are hash-routed on `keys` across the sibling consumers of the
446    /// producing fragment.
447    HashRepartition {
448        /// Hash key columns (in the producer's output schema).
449        keys: Vec<String>,
450    },
451    /// The producer's full output is replicated to every consumer.
452    Broadcast,
453    /// The producer's output flows to its single consumer.
454    Merge,
455}
456
457/// One exchange edge between two fragments (spec section 12.10).
458#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
459pub struct ExchangeDescriptor {
460    /// Plan-local exchange identifier.
461    pub exchange_id: ExchangeId,
462    /// Producing fragment.
463    pub producer: FragmentId,
464    /// Consuming fragment.
465    pub consumer: FragmentId,
466    /// Row movement kind.
467    pub kind: ExchangeKind,
468    /// FNV-1a fingerprint of the producer's output column names (types join
469    /// the fingerprint with the DataFusion lowering wave).
470    pub schema_fingerprint: u64,
471}
472
473/// One executable unit of a [`DistributedPlan`].
474#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
475pub struct PlanFragment {
476    /// Plan-local fragment identifier.
477    pub fragment_id: FragmentId,
478    /// Where the fragment executes.
479    pub assignment: FragmentAssignment,
480    /// Operators, in execution order (scans/sources first, sink last).
481    pub operators: Vec<FragmentOperator>,
482    /// Estimated output rows (spec section 12.10).
483    pub estimated_rows: u64,
484    /// Estimated output bytes (spec section 12.10).
485    pub estimated_bytes: u64,
486    /// Maximum spill allowance in bytes (spec section 12.10).
487    pub max_spill_bytes: u64,
488}
489
490/// A distributed query plan pinned to one control-plane metadata version
491/// (spec section 12.10).
492#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
493pub struct DistributedPlan {
494    /// The query execution this plan belongs to.
495    pub query_id: QueryId,
496    /// The control-plane metadata version the plan was built against.
497    pub metadata_version: MetadataVersion,
498    /// All fragments; `fragment_id` equals the vector index.
499    pub fragments: Vec<PlanFragment>,
500    /// All exchange edges; `exchange_id` equals the vector index.
501    pub exchanges: Vec<ExchangeDescriptor>,
502}
503
504impl DistributedPlan {
505    /// The fragment with no outgoing exchange (the coordinator root).
506    pub fn root_fragment_id(&self) -> Option<FragmentId> {
507        self.fragments
508            .iter()
509            .map(|fragment| fragment.fragment_id)
510            .find(|id| !self.exchanges.iter().any(|edge| edge.producer == *id))
511    }
512
513    /// Looks up a fragment by id.
514    pub fn fragment(&self, fragment_id: FragmentId) -> Option<&PlanFragment> {
515        self.fragments.get(fragment_id as usize)
516    }
517
518    /// Exchange edges out of one fragment.
519    pub fn exchanges_from(
520        &self,
521        producer: FragmentId,
522    ) -> impl Iterator<Item = &ExchangeDescriptor> {
523        self.exchanges
524            .iter()
525            .filter(move |edge| edge.producer == producer)
526    }
527
528    /// Exchange edges into one fragment, ordered by producer id.
529    pub fn exchanges_into(&self, consumer: FragmentId) -> Vec<&ExchangeDescriptor> {
530        let mut edges: Vec<&ExchangeDescriptor> = self
531            .exchanges
532            .iter()
533            .filter(|edge| edge.consumer == consumer)
534            .collect();
535        edges.sort_by_key(|edge| edge.producer);
536        edges
537    }
538}
539
540// ---------------------------------------------------------------------------
541// Planner
542// ---------------------------------------------------------------------------
543
544/// Everything [`distribute`] needs to build a [`DistributedPlan`].
545#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
546pub struct PlanDescription {
547    /// The query execution id the plan is pinned to.
548    pub query_id: QueryId,
549    /// The logical input tree.
550    pub root: LogicalPlanLite,
551    /// Planner tuning.
552    pub options: PlannerOptions,
553}
554
555/// Planner tuning knobs.
556#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
557pub struct PlannerOptions {
558    /// Small-side byte threshold below which a join is planned as broadcast.
559    pub broadcast_threshold_bytes: u64,
560    /// Per-fragment maximum spill allowance stamped on every fragment.
561    pub max_spill_bytes_per_fragment: u64,
562}
563
564impl Default for PlannerOptions {
565    fn default() -> Self {
566        Self {
567            broadcast_threshold_bytes: DEFAULT_BROADCAST_THRESHOLD_BYTES,
568            max_spill_bytes_per_fragment: DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT,
569        }
570    }
571}
572
573/// Lowers a [`LogicalPlanLite`] tree onto tablets (spec section 12.10).
574///
575/// Join strategy, in decision order:
576///
577/// 1. **Colocated** — both inputs are scans whose partition specs are
578///    colocated ([`PartitionSpec::colocated_with`]) over the identical tablet
579///    list; the hash join runs fused inside each shared tablet's fragment.
580/// 2. **Broadcast** — the smaller input's estimated bytes fit under
581///    [`PlannerOptions::broadcast_threshold_bytes`]; the small side is
582///    replicated to every big-side fragment.
583/// 3. **Repartition** — otherwise; both sides are hash-repartitioned on their
584///    join keys into fresh join fragments.
585///
586/// `Sort` with a single descending key and a limit plans as
587/// [`FragmentOperator::DistributedTopK`]; any other sort plans as
588/// [`FragmentOperator::MergeSort`]. Every fragment carries estimated
589/// rows/bytes and the configured maximum spill allowance.
590pub fn distribute(
591    description: &PlanDescription,
592    locator: &dyn TabletLocator,
593    metadata: &dyn ClusterMetadata,
594) -> DistributedResult<DistributedPlan> {
595    validate_lite(&description.root)?;
596    let mut planner = Planner {
597        locator,
598        metadata,
599        options: description.options,
600        fragments: Vec::new(),
601        exchanges: Vec::new(),
602    };
603    let stage = planner.plan_node(&description.root)?;
604    let root = match stage.producers.as_slice() {
605        [only]
606            if planner.fragments[*only as usize].assignment == FragmentAssignment::Coordinator =>
607        {
608            *only
609        }
610        _ => {
611            let gather = planner.push_fragment(
612                FragmentAssignment::Coordinator,
613                Vec::new(),
614                stage.estimated_rows,
615                stage.estimated_bytes,
616            );
617            planner.wire(&stage.producers, &[gather], ExchangeKind::Merge);
618            gather
619        }
620    };
621    debug_assert_eq!(
622        planner
623            .fragments
624            .iter()
625            .filter(|fragment| {
626                !planner
627                    .exchanges
628                    .iter()
629                    .any(|edge| edge.producer == fragment.fragment_id)
630            })
631            .count(),
632        1,
633        "exactly one root fragment"
634    );
635    let _ = root;
636    Ok(DistributedPlan {
637        query_id: description.query_id,
638        metadata_version: metadata.metadata_version(),
639        fragments: planner.fragments,
640        exchanges: planner.exchanges,
641    })
642}
643
644/// Validates the input IR before planning.
645fn validate_lite(node: &LogicalPlanLite) -> DistributedResult<()> {
646    match node {
647        LogicalPlanLite::Scan { table, .. } => {
648            if table.is_empty() {
649                return Err(DistributedError::InvalidPlan(
650                    "scan needs a table name".to_owned(),
651                ));
652            }
653        }
654        LogicalPlanLite::Aggregate {
655            input, aggregates, ..
656        } => {
657            for aggregate in aggregates {
658                if aggregate.function != AggregateFunction::Count && aggregate.column.is_none() {
659                    return Err(DistributedError::InvalidPlan(format!(
660                        "{} needs a column",
661                        aggregate.function.name()
662                    )));
663                }
664            }
665            validate_lite(input)?;
666        }
667        LogicalPlanLite::Join { left, right, on } => {
668            if on.is_empty() {
669                return Err(DistributedError::InvalidPlan(
670                    "join needs at least one key".to_owned(),
671                ));
672            }
673            validate_lite(left)?;
674            validate_lite(right)?;
675        }
676        LogicalPlanLite::Sort { input, keys, .. } => {
677            if keys.is_empty() {
678                return Err(DistributedError::InvalidPlan(
679                    "sort needs at least one key".to_owned(),
680                ));
681            }
682            validate_lite(input)?;
683        }
684        LogicalPlanLite::Limit { input, .. } => validate_lite(input)?,
685    }
686    Ok(())
687}
688
689/// The planner's view of one planned subtree's output stage.
690struct Stage {
691    /// Fragments producing this stage's output (sinks not yet attached).
692    producers: Vec<FragmentId>,
693    estimated_rows: u64,
694    estimated_bytes: u64,
695    /// Output partitioning, when known (scan stages and colocated joins).
696    partitioning: Option<PartitionSpec>,
697    /// Tablets the stage's fragments run on (empty for coordinator stages).
698    tablets: Vec<TabletId>,
699}
700
701struct Planner<'a> {
702    locator: &'a dyn TabletLocator,
703    metadata: &'a dyn ClusterMetadata,
704    options: PlannerOptions,
705    fragments: Vec<PlanFragment>,
706    exchanges: Vec<ExchangeDescriptor>,
707}
708
709impl Planner<'_> {
710    fn push_fragment(
711        &mut self,
712        assignment: FragmentAssignment,
713        operators: Vec<FragmentOperator>,
714        estimated_rows: u64,
715        estimated_bytes: u64,
716    ) -> FragmentId {
717        let fragment_id = self.fragments.len() as FragmentId;
718        self.fragments.push(PlanFragment {
719            fragment_id,
720            assignment,
721            operators,
722            estimated_rows,
723            estimated_bytes,
724            max_spill_bytes: self.options.max_spill_bytes_per_fragment,
725        });
726        fragment_id
727    }
728
729    /// Connects every producer to every consumer with fresh exchange edges,
730    /// appending the sink operators to producers and source operators to
731    /// consumers (consumers must not have transform operators yet).
732    fn wire(&mut self, producers: &[FragmentId], consumers: &[FragmentId], kind: ExchangeKind) {
733        for &producer in producers {
734            let schema_fingerprint = self.schema_fingerprint(producer);
735            for &consumer in consumers {
736                let exchange_id = self.exchanges.len() as ExchangeId;
737                self.exchanges.push(ExchangeDescriptor {
738                    exchange_id,
739                    producer,
740                    consumer,
741                    kind: kind.clone(),
742                    schema_fingerprint,
743                });
744                self.fragments[producer as usize].operators.push(
745                    FragmentOperator::RemoteExchangeSink {
746                        exchange: exchange_id,
747                    },
748                );
749                self.fragments[consumer as usize].operators.push(
750                    FragmentOperator::RemoteExchangeSource {
751                        exchange: exchange_id,
752                    },
753                );
754            }
755        }
756    }
757
758    /// FNV-1a over the producer fragment's output column names.
759    fn schema_fingerprint(&self, fragment_id: FragmentId) -> u64 {
760        let columns = fragment_output_columns(&self.fragments[fragment_id as usize]);
761        let mut bytes = Vec::new();
762        for column in &columns {
763            bytes.extend_from_slice(&(column.len() as u32).to_le_bytes());
764            bytes.extend_from_slice(column.as_bytes());
765        }
766        fnv1a64(&bytes)
767    }
768
769    fn metadata_stats(&self, table: &str) -> DistributedResult<TableStats> {
770        self.metadata.table_stats(table)
771    }
772
773    fn plan_node(&mut self, node: &LogicalPlanLite) -> DistributedResult<Stage> {
774        match node {
775            LogicalPlanLite::Scan {
776                table,
777                predicate,
778                projection,
779            } => self.plan_scan(table, predicate, projection),
780            LogicalPlanLite::Aggregate {
781                input,
782                group_by,
783                aggregates,
784            } => self.plan_aggregate(input, group_by, aggregates),
785            LogicalPlanLite::Join { left, right, on } => self.plan_join(left, right, on),
786            LogicalPlanLite::Sort { input, keys, limit } => self.plan_sort(input, keys, *limit),
787            LogicalPlanLite::Limit { input, limit } => self.plan_limit(input, *limit),
788        }
789    }
790
791    fn plan_scan(
792        &mut self,
793        table: &str,
794        predicate: &Option<String>,
795        projection: &[String],
796    ) -> DistributedResult<Stage> {
797        let tablets = self.locator.tablets_for_table(table)?;
798        if tablets.is_empty() {
799            return Err(DistributedError::EmptyLayout {
800                table: table.to_owned(),
801            });
802        }
803        let spec = self.locator.partitioning(table)?;
804        let stats = self.metadata_stats(table)?;
805        let count = tablets.len() as u64;
806        let per_rows = stats.row_count.div_ceil(count);
807        let per_bytes = stats.total_bytes.div_ceil(count);
808        let mut producers = Vec::with_capacity(tablets.len());
809        for tablet in &tablets {
810            producers.push(self.push_fragment(
811                FragmentAssignment::Tablet(*tablet),
812                vec![FragmentOperator::TabletScan {
813                    table: table.to_owned(),
814                    predicate: predicate.clone(),
815                    projection: projection.to_vec(),
816                }],
817                per_rows,
818                per_bytes,
819            ));
820        }
821        Ok(Stage {
822            producers,
823            estimated_rows: stats.row_count,
824            estimated_bytes: stats.total_bytes,
825            partitioning: Some(spec),
826            tablets,
827        })
828    }
829
830    fn plan_aggregate(
831        &mut self,
832        input: &LogicalPlanLite,
833        group_by: &[String],
834        aggregates: &[AggregateExpr],
835    ) -> DistributedResult<Stage> {
836        let child = self.plan_node(input)?;
837        let estimated_rows = if group_by.is_empty() {
838            1
839        } else {
840            (child.estimated_rows / 2).max(1)
841        };
842        let per_row = child
843            .estimated_bytes
844            .checked_div(child.estimated_rows.max(1))
845            .unwrap_or(0)
846            .max(1);
847        let estimated_bytes = estimated_rows.saturating_mul(per_row);
848        if let [only] = child.producers.as_slice() {
849            if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
850                // Single coordinator producer: combine in place, no shuffle.
851                self.fragments[*only as usize].operators.extend([
852                    FragmentOperator::PartialAggregate {
853                        group_by: group_by.to_vec(),
854                        aggregates: aggregates.to_vec(),
855                    },
856                    FragmentOperator::FinalAggregate {
857                        group_by: group_by.to_vec(),
858                        aggregates: aggregates.to_vec(),
859                    },
860                ]);
861                return Ok(Stage {
862                    estimated_rows,
863                    estimated_bytes,
864                    ..child
865                });
866            }
867        }
868        for &producer in &child.producers {
869            self.fragments[producer as usize]
870                .operators
871                .push(FragmentOperator::PartialAggregate {
872                    group_by: group_by.to_vec(),
873                    aggregates: aggregates.to_vec(),
874                });
875        }
876        let consumer = self.push_fragment(
877            FragmentAssignment::Coordinator,
878            Vec::new(),
879            estimated_rows,
880            estimated_bytes,
881        );
882        let kind = if group_by.is_empty() {
883            ExchangeKind::Merge
884        } else {
885            ExchangeKind::HashRepartition {
886                keys: group_by.to_vec(),
887            }
888        };
889        self.wire(&child.producers, &[consumer], kind);
890        self.fragments[consumer as usize]
891            .operators
892            .push(FragmentOperator::FinalAggregate {
893                group_by: group_by.to_vec(),
894                aggregates: aggregates.to_vec(),
895            });
896        Ok(Stage {
897            producers: vec![consumer],
898            estimated_rows,
899            estimated_bytes,
900            partitioning: None,
901            tablets: Vec::new(),
902        })
903    }
904
905    fn plan_join(
906        &mut self,
907        left: &LogicalPlanLite,
908        right: &LogicalPlanLite,
909        on: &[JoinKey],
910    ) -> DistributedResult<Stage> {
911        // Colocation can be decided from metadata alone (no fragments built).
912        if let (
913            LogicalPlanLite::Scan {
914                table: left_table, ..
915            },
916            LogicalPlanLite::Scan {
917                table: right_table, ..
918            },
919        ) = (left, right)
920        {
921            let left_tablets = self.locator.tablets_for_table(left_table)?;
922            if left_tablets.is_empty() {
923                return Err(DistributedError::EmptyLayout {
924                    table: left_table.clone(),
925                });
926            }
927            let right_tablets = self.locator.tablets_for_table(right_table)?;
928            if right_tablets.is_empty() {
929                return Err(DistributedError::EmptyLayout {
930                    table: right_table.clone(),
931                });
932            }
933            let left_spec = self.locator.partitioning(left_table)?;
934            let right_spec = self.locator.partitioning(right_table)?;
935            if left_spec.colocated_with(&right_spec) && left_tablets == right_tablets {
936                return self.plan_colocated_join(left, right, on, &left_tablets, &left_spec);
937            }
938        }
939        let left_stage = self.plan_node(left)?;
940        let right_stage = self.plan_node(right)?;
941        if left_stage.estimated_bytes.min(right_stage.estimated_bytes)
942            <= self.options.broadcast_threshold_bytes
943        {
944            self.plan_broadcast_join(left_stage, right_stage, on)
945        } else {
946            self.plan_repartition_join(left_stage, right_stage, on)
947        }
948    }
949
950    /// Fuses two colocated scans and the hash join into one fragment per
951    /// shared tablet — no exchange at all (spec section 12.10).
952    fn plan_colocated_join(
953        &mut self,
954        left: &LogicalPlanLite,
955        right: &LogicalPlanLite,
956        on: &[JoinKey],
957        tablets: &[TabletId],
958        spec: &PartitionSpec,
959    ) -> DistributedResult<Stage> {
960        let (
961            LogicalPlanLite::Scan {
962                table: left_table,
963                predicate: left_predicate,
964                projection: left_projection,
965            },
966            LogicalPlanLite::Scan {
967                table: right_table,
968                predicate: right_predicate,
969                projection: right_projection,
970            },
971        ) = (left, right)
972        else {
973            return Err(DistributedError::InvalidPlan(
974                "colocated join needs scan inputs".to_owned(),
975            ));
976        };
977        let left_stats = self.metadata_stats(left_table)?;
978        let right_stats = self.metadata_stats(right_table)?;
979        let estimated_rows = left_stats.row_count.max(right_stats.row_count);
980        let estimated_bytes = left_stats.total_bytes.max(right_stats.total_bytes);
981        let count = tablets.len() as u64;
982        let mut producers = Vec::with_capacity(tablets.len());
983        for tablet in tablets {
984            producers.push(self.push_fragment(
985                FragmentAssignment::Tablet(*tablet),
986                vec![
987                    FragmentOperator::TabletScan {
988                        table: left_table.clone(),
989                        predicate: left_predicate.clone(),
990                        projection: left_projection.clone(),
991                    },
992                    FragmentOperator::TabletScan {
993                        table: right_table.clone(),
994                        predicate: right_predicate.clone(),
995                        projection: right_projection.clone(),
996                    },
997                    FragmentOperator::DistributedHashJoin { on: on.to_vec() },
998                ],
999                estimated_rows.div_ceil(count),
1000                estimated_bytes.div_ceil(count),
1001            ));
1002        }
1003        Ok(Stage {
1004            producers,
1005            estimated_rows,
1006            estimated_bytes,
1007            partitioning: Some(spec.clone()),
1008            tablets: tablets.to_vec(),
1009        })
1010    }
1011
1012    /// Replicates the small side to every big-side fragment (spec section
1013    /// 12.10: "broadcast small side").
1014    fn plan_broadcast_join(
1015        &mut self,
1016        left_stage: Stage,
1017        right_stage: Stage,
1018        on: &[JoinKey],
1019    ) -> DistributedResult<Stage> {
1020        let (big, small, build_side) = if right_stage.estimated_bytes <= left_stage.estimated_bytes
1021        {
1022            (left_stage, right_stage, BuildSide::Right)
1023        } else {
1024            (right_stage, left_stage, BuildSide::Left)
1025        };
1026        self.wire(&small.producers, &big.producers, ExchangeKind::Broadcast);
1027        for &producer in &big.producers {
1028            self.fragments[producer as usize]
1029                .operators
1030                .push(FragmentOperator::BroadcastJoin {
1031                    on: on.to_vec(),
1032                    build_side,
1033                });
1034        }
1035        let estimated_rows = big.estimated_rows;
1036        let estimated_bytes = big.estimated_bytes.saturating_add(small.estimated_bytes);
1037        Ok(Stage {
1038            producers: big.producers,
1039            estimated_rows,
1040            estimated_bytes,
1041            partitioning: big.partitioning,
1042            tablets: big.tablets,
1043        })
1044    }
1045
1046    /// Hash-repartitions both inputs on their join keys into fresh join
1047    /// fragments (spec section 12.10: "repartition both sides").
1048    fn plan_repartition_join(
1049        &mut self,
1050        left_stage: Stage,
1051        right_stage: Stage,
1052        on: &[JoinKey],
1053    ) -> DistributedResult<Stage> {
1054        let join_tablets = if left_stage.tablets.len() >= right_stage.tablets.len() {
1055            left_stage.tablets.clone()
1056        } else {
1057            right_stage.tablets.clone()
1058        };
1059        if join_tablets.is_empty() {
1060            return Err(DistributedError::InvalidPlan(
1061                "repartition join needs at least one tablet-backed input".to_owned(),
1062            ));
1063        }
1064        let width = left_stage.producers.len().max(right_stage.producers.len());
1065        let estimated_rows = left_stage.estimated_rows.max(right_stage.estimated_rows);
1066        let estimated_bytes = left_stage
1067            .estimated_bytes
1068            .saturating_add(right_stage.estimated_bytes);
1069        let mut consumers = Vec::with_capacity(width);
1070        for index in 0..width {
1071            consumers.push(self.push_fragment(
1072                FragmentAssignment::Tablet(join_tablets[index % join_tablets.len()]),
1073                Vec::new(),
1074                estimated_rows.div_ceil(width as u64),
1075                estimated_bytes.div_ceil(width as u64),
1076            ));
1077        }
1078        let left_keys: Vec<String> = on.iter().map(|key| key.left.clone()).collect();
1079        let right_keys: Vec<String> = on.iter().map(|key| key.right.clone()).collect();
1080        self.wire(
1081            &left_stage.producers,
1082            &consumers,
1083            ExchangeKind::HashRepartition { keys: left_keys },
1084        );
1085        self.wire(
1086            &right_stage.producers,
1087            &consumers,
1088            ExchangeKind::HashRepartition { keys: right_keys },
1089        );
1090        for &consumer in &consumers {
1091            self.fragments[consumer as usize]
1092                .operators
1093                .push(FragmentOperator::RepartitionJoin { on: on.to_vec() });
1094        }
1095        Ok(Stage {
1096            producers: consumers,
1097            estimated_rows,
1098            estimated_bytes,
1099            partitioning: None,
1100            tablets: join_tablets,
1101        })
1102    }
1103
1104    fn plan_sort(
1105        &mut self,
1106        input: &LogicalPlanLite,
1107        keys: &[SortKey],
1108        limit: Option<usize>,
1109    ) -> DistributedResult<Stage> {
1110        let child = self.plan_node(input)?;
1111        // The distributed top-k shape (spec section 12.10): one descending
1112        // score key plus a limit. Anything else is a merge sort.
1113        let top_k = match (limit, keys) {
1114            (Some(k), [score]) if score.descending => Some((k, score.clone())),
1115            _ => None,
1116        };
1117        let estimated_rows = limit.map_or(child.estimated_rows, |limit| {
1118            child.estimated_rows.min(limit as u64)
1119        });
1120        let estimated_bytes =
1121            scaled_bytes(child.estimated_bytes, child.estimated_rows, estimated_rows);
1122        let local_op = match &top_k {
1123            Some((k, score)) => FragmentOperator::DistributedTopK {
1124                k: *k,
1125                score: score.clone(),
1126            },
1127            None => FragmentOperator::MergeSort {
1128                keys: keys.to_vec(),
1129                limit,
1130            },
1131        };
1132        if let [only] = child.producers.as_slice() {
1133            if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
1134                self.fragments[*only as usize].operators.push(local_op);
1135                return Ok(Stage {
1136                    estimated_rows,
1137                    estimated_bytes,
1138                    ..child
1139                });
1140            }
1141        }
1142        for &producer in &child.producers {
1143            self.fragments[producer as usize]
1144                .operators
1145                .push(local_op.clone());
1146        }
1147        let consumer = self.push_fragment(
1148            FragmentAssignment::Coordinator,
1149            Vec::new(),
1150            estimated_rows,
1151            estimated_bytes,
1152        );
1153        self.wire(&child.producers, &[consumer], ExchangeKind::Merge);
1154        let root_op = match &top_k {
1155            Some((k, score)) => FragmentOperator::DistributedTopK {
1156                k: *k,
1157                score: score.clone(),
1158            },
1159            None => FragmentOperator::MergeSort {
1160                keys: keys.to_vec(),
1161                limit,
1162            },
1163        };
1164        self.fragments[consumer as usize].operators.push(root_op);
1165        Ok(Stage {
1166            producers: vec![consumer],
1167            estimated_rows,
1168            estimated_bytes,
1169            partitioning: None,
1170            tablets: Vec::new(),
1171        })
1172    }
1173
1174    fn plan_limit(&mut self, input: &LogicalPlanLite, limit: usize) -> DistributedResult<Stage> {
1175        let child = self.plan_node(input)?;
1176        let estimated_rows = child.estimated_rows.min(limit as u64);
1177        let estimated_bytes =
1178            scaled_bytes(child.estimated_bytes, child.estimated_rows, estimated_rows);
1179        if let [only] = child.producers.as_slice() {
1180            if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
1181                self.fragments[*only as usize]
1182                    .operators
1183                    .push(FragmentOperator::DistributedLimit { limit });
1184                return Ok(Stage {
1185                    estimated_rows,
1186                    estimated_bytes,
1187                    ..child
1188                });
1189            }
1190        }
1191        for &producer in &child.producers {
1192            self.fragments[producer as usize]
1193                .operators
1194                .push(FragmentOperator::DistributedLimit { limit });
1195        }
1196        let consumer = self.push_fragment(
1197            FragmentAssignment::Coordinator,
1198            Vec::new(),
1199            estimated_rows,
1200            estimated_bytes,
1201        );
1202        self.wire(&child.producers, &[consumer], ExchangeKind::Merge);
1203        self.fragments[consumer as usize]
1204            .operators
1205            .push(FragmentOperator::DistributedLimit { limit });
1206        Ok(Stage {
1207            producers: vec![consumer],
1208            estimated_rows,
1209            estimated_bytes,
1210            partitioning: None,
1211            tablets: Vec::new(),
1212        })
1213    }
1214}
1215
1216/// Scales a byte estimate to a new row estimate.
1217fn scaled_bytes(bytes: u64, rows: u64, new_rows: u64) -> u64 {
1218    if rows == 0 {
1219        return 0;
1220    }
1221    bytes
1222        .saturating_mul(new_rows)
1223        .checked_div(rows)
1224        .unwrap_or(bytes)
1225}
1226
1227/// Derives a fragment's output column names (fingerprint input).
1228fn fragment_output_columns(fragment: &PlanFragment) -> Vec<String> {
1229    let mut columns = Vec::new();
1230    for operator in &fragment.operators {
1231        match operator {
1232            FragmentOperator::TabletScan {
1233                table, projection, ..
1234            } => {
1235                if projection.is_empty() {
1236                    columns = vec![format!("{table}.*")];
1237                } else {
1238                    columns = projection
1239                        .iter()
1240                        .map(|column| format!("{table}.{column}"))
1241                        .collect();
1242                }
1243            }
1244            FragmentOperator::PartialAggregate {
1245                group_by,
1246                aggregates,
1247            } => {
1248                columns = group_by.to_vec();
1249                columns.extend(partial_column_names(aggregates));
1250            }
1251            FragmentOperator::FinalAggregate {
1252                group_by,
1253                aggregates,
1254            } => {
1255                columns = group_by.to_vec();
1256                columns.extend(aggregates.iter().map(aggregate_output_name));
1257            }
1258            FragmentOperator::DistributedHashJoin { .. }
1259            | FragmentOperator::BroadcastJoin { .. }
1260            | FragmentOperator::RepartitionJoin { .. } => {
1261                columns = vec!["*join*".to_owned()];
1262            }
1263            FragmentOperator::RemoteExchangeSource { .. }
1264            | FragmentOperator::RemoteExchangeSink { .. }
1265            | FragmentOperator::MergeSort { .. }
1266            | FragmentOperator::DistributedTopK { .. }
1267            | FragmentOperator::DistributedLimit { .. } => {}
1268        }
1269    }
1270    columns
1271}
1272
1273/// Partial-aggregate output column names (`__partial_0`, and `__partial_i_sum`
1274/// / `__partial_i_count` for averages).
1275fn partial_column_names(aggregates: &[AggregateExpr]) -> Vec<String> {
1276    let mut names = Vec::new();
1277    for (index, aggregate) in aggregates.iter().enumerate() {
1278        if aggregate.function == AggregateFunction::Avg {
1279            names.push(format!("__partial_{index}_sum"));
1280            names.push(format!("__partial_{index}_count"));
1281        } else {
1282            names.push(format!("__partial_{index}"));
1283        }
1284    }
1285    names
1286}
1287
1288/// Final-aggregate output column name (`count_star`, `sum_cost`, ...).
1289fn aggregate_output_name(aggregate: &AggregateExpr) -> String {
1290    format!(
1291        "{}_{}",
1292        aggregate.function.name(),
1293        aggregate.column.as_deref().unwrap_or("star")
1294    )
1295}
1296
1297// ---------------------------------------------------------------------------
1298// Distributed top-k (spec section 12.10): deterministic merge + adaptive refill
1299// ---------------------------------------------------------------------------
1300
1301/// One ranked row in the top-k model: the score plus the exact tie-break
1302/// identity (spec section 12.10: final score descending, tablet id ascending,
1303/// [`RowId`] ascending).
1304#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1305pub struct TopKCandidate {
1306    /// Score mapped onto an order-preserving `u64` key (higher wins).
1307    pub score: u64,
1308    /// The tablet that contributed the row.
1309    pub tablet: TabletId,
1310    /// The row's id within the table.
1311    pub row_id: RowId,
1312}
1313
1314/// The deterministic winner order: score descending, then tablet id
1315/// ascending, then [`RowId`] ascending. Returns [`Ordering::Less`] when `a`
1316/// ranks strictly better than `b`.
1317pub fn topk_cmp(a: &TopKCandidate, b: &TopKCandidate) -> Ordering {
1318    b.score
1319        .cmp(&a.score)
1320        .then_with(|| a.tablet.cmp(&b.tablet))
1321        .then_with(|| a.row_id.cmp(&b.row_id))
1322}
1323
1324/// One tablet's bounded local top-k plus tie information (spec section
1325/// 12.10: "each tablet returns a bounded local top-k plus tie information").
1326#[derive(Clone, Debug)]
1327pub struct TabletTopK {
1328    /// The contributing tablet.
1329    pub tablet: TabletId,
1330    /// Local winners, best first (at most the bounded size).
1331    pub rows: Vec<TopKCandidate>,
1332    /// Upper bound on the score of any row NOT in `rows` (`None` = the tablet
1333    /// is exhausted — every local row has been returned).
1334    pub unseen_bound: Option<u64>,
1335}
1336
1337/// The outcome of one deterministic coordinator merge step.
1338#[derive(Clone, Debug, PartialEq, Eq)]
1339pub struct TopKMerge {
1340    /// The best `k` received candidates, best first.
1341    pub winners: Vec<TopKCandidate>,
1342    /// Tablets that must be refilled before `winners` is provably the exact
1343    /// global top-k (empty = the result is exact).
1344    pub refill: Vec<TabletId>,
1345}
1346
1347/// Deterministically merges bounded local top-ks (spec section 12.10:
1348/// "coordinator merges deterministically").
1349///
1350/// Winners are the best `k` received candidates under [`topk_cmp`]. A tablet
1351/// lands in [`TopKMerge::refill`] when its unseen rows could still displace a
1352/// winner — i.e. when fewer than `k` candidates were received in total and
1353/// the tablet is not exhausted, or when the tablet's most optimistic unseen
1354/// candidate (score = `unseen_bound`, smallest possible [`RowId`]) ranks at
1355/// least as well as the current `k`-th winner. The tie case is deliberately
1356/// conservative (unseen row ids are unknown), so a refill may be requested
1357/// that returns no winning rows; correctness never depends on it.
1358///
1359/// Exactness invariant: when `refill` is empty, `winners` equals the global
1360/// top-`k` of all rows on all tablets. Proof sketch: any unseen row of tablet
1361/// `t` ranks no better than the optimistic candidate `(unseen_bound, t,
1362/// RowId::MIN)` — its score is at most the bound, and at equal score its row
1363/// id is larger — so when the optimistic candidate ranks strictly worse than
1364/// the `k`-th winner, no unseen row of `t` can enter the top-`k`. When `t` is
1365/// exhausted it has no unseen rows at all. With every tablet in one of those
1366/// two states, the received top-`k` is the global top-`k`. When fewer than
1367/// `k` candidates were received, refill is empty iff every tablet is
1368/// exhausted, in which case all rows were seen.
1369pub fn merge_top_k(shards: &[TabletTopK], k: usize) -> TopKMerge {
1370    if k == 0 {
1371        return TopKMerge {
1372            winners: Vec::new(),
1373            refill: Vec::new(),
1374        };
1375    }
1376    let mut received: Vec<TopKCandidate> = shards
1377        .iter()
1378        .flat_map(|shard| shard.rows.iter().copied())
1379        .collect();
1380    received.sort_by(topk_cmp);
1381    received.truncate(k);
1382    let mut refill = Vec::new();
1383    if received.len() < k {
1384        for shard in shards {
1385            if shard.unseen_bound.is_some() {
1386                refill.push(shard.tablet);
1387            }
1388        }
1389    } else {
1390        let threshold = received[k - 1];
1391        for shard in shards {
1392            let Some(bound) = shard.unseen_bound else {
1393                continue;
1394            };
1395            let optimistic = TopKCandidate {
1396                score: bound,
1397                tablet: shard.tablet,
1398                row_id: RowId::MIN,
1399            };
1400            if topk_cmp(&optimistic, &threshold) != Ordering::Greater {
1401                refill.push(shard.tablet);
1402            }
1403        }
1404    }
1405    refill.sort();
1406    refill.dedup();
1407    TopKMerge {
1408        winners: received,
1409        refill,
1410    }
1411}
1412
1413/// Drives [`merge_top_k`] with adaptive refill until the result is provably
1414/// exact (spec section 12.10: "for exact global top-k, use adaptive refill
1415/// when local bounds show unseen rows could still win").
1416///
1417/// `initial` holds each tablet's first bounded contribution. `refill_batch`
1418/// must return the NEXT batch of local winners for one tablet (rows not
1419/// returned before, best first) together with a tightened unseen bound
1420/// (`None` when the tablet is exhausted). Iteration order over tablets is
1421/// sorted by id, so the driver is fully deterministic. Fails when a refill
1422/// makes no progress (no new rows and an unchanged bound), which indicates a
1423/// broken producer contract rather than a planning problem.
1424pub fn exact_top_k(
1425    k: usize,
1426    initial: Vec<TabletTopK>,
1427    mut refill_batch: impl FnMut(TabletId) -> TabletTopK,
1428) -> DistributedResult<Vec<TopKCandidate>> {
1429    let mut shards: BTreeMap<TabletId, TabletTopK> = initial
1430        .into_iter()
1431        .map(|shard| (shard.tablet, shard))
1432        .collect();
1433    loop {
1434        let ordered: Vec<TabletTopK> = shards.values().cloned().collect();
1435        let merge = merge_top_k(&ordered, k);
1436        if merge.refill.is_empty() {
1437            return Ok(merge.winners);
1438        }
1439        for tablet in merge.refill {
1440            let batch = refill_batch(tablet);
1441            let entry = shards.get_mut(&tablet).ok_or_else(|| {
1442                DistributedError::InvalidPlan(format!(
1443                    "top-k refill requested for unknown tablet {tablet}"
1444                ))
1445            })?;
1446            if batch.rows.is_empty() && batch.unseen_bound == entry.unseen_bound {
1447                return Err(DistributedError::InvalidPlan(format!(
1448                    "top-k refill for tablet {tablet} made no progress"
1449                )));
1450            }
1451            entry.rows.extend(batch.rows);
1452            entry.unseen_bound = batch.unseen_bound;
1453        }
1454    }
1455}
1456
1457// ---------------------------------------------------------------------------
1458// Execution skeleton (spec section 12.10)
1459// ---------------------------------------------------------------------------
1460
1461/// Cooperative per-fragment execution control: cancellation/deadline shared
1462/// with the query's [`ExecutionControl`] hierarchy plus the fragment's spill
1463/// allowance.
1464#[derive(Debug, Clone)]
1465pub struct FragmentControl {
1466    /// Cooperative cancellation handle (child of the query control, so a
1467    /// registry cancel fans out to every fragment).
1468    pub control: ExecutionControl,
1469    /// Maximum spill allowance stamped by the planner (spec section 12.10).
1470    pub max_spill_bytes: u64,
1471    /// Opaque server-issued user/session authorization envelope. Worker
1472    /// executors validate it before touching tablet data.
1473    pub authorization_context: Arc<[u8]>,
1474}
1475
1476impl FragmentControl {
1477    /// Open a core spill session for this fragment against `manager`, capped
1478    /// at [`Self::max_spill_bytes`]. Query operators that sort/join/aggregate
1479    /// under pressure call this instead of inventing a second spill path.
1480    pub fn begin_spill(
1481        &self,
1482        manager: &mongreldb_core::SpillManager,
1483        query_id: mongreldb_types::ids::QueryId,
1484    ) -> Result<mongreldb_core::SpillSession, mongreldb_core::SpillError> {
1485        manager.begin_query(query_id, self.max_spill_bytes)
1486    }
1487}
1488
1489/// Tie information for scored (top-k) streams, carried on a producer's
1490/// terminal frame (spec section 12.10: "bounded local top-k plus tie
1491/// information").
1492#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1493pub enum ScoreBound {
1494    /// Not a scored stream, or the producer cannot report bounds; the
1495    /// coordinator treats such producers as exhausted.
1496    Unknown,
1497    /// Unsent rows may have score keys up to this value.
1498    AtMost(u64),
1499    /// The producer emitted every local row.
1500    Exhausted,
1501}
1502
1503/// One Arrow-ish record-batch frame on a fragment stream.
1504#[derive(Debug, Clone)]
1505pub struct BatchFrame {
1506    /// The record batch payload.
1507    pub batch: RecordBatch,
1508    /// Tie information; meaningful only on a scored stream's terminal frame.
1509    pub score_bound: ScoreBound,
1510}
1511
1512impl BatchFrame {
1513    /// A plain data frame (no tie information).
1514    pub fn data(batch: RecordBatch) -> Self {
1515        Self {
1516            batch,
1517            score_bound: ScoreBound::Unknown,
1518        }
1519    }
1520}
1521
1522/// A fragment's output stream.
1523pub type FragmentStream = BoxStream<'static, DistributedResult<BatchFrame>>;
1524
1525/// The next batch of a tablet's local top-k (adaptive refill, spec section
1526/// 12.10).
1527#[derive(Debug)]
1528pub struct TopKRefill {
1529    /// The next local candidates (rows not returned before, best first),
1530    /// aligned with `payload`'s rows.
1531    pub rows: Vec<TopKCandidate>,
1532    /// The candidates' full row payload (same schema as the stream output).
1533    pub payload: RecordBatch,
1534    /// Tightened unseen-score bound (`None` = tablet exhausted).
1535    pub unseen_bound: Option<u64>,
1536}
1537
1538/// Executes one fragment on its worker. Server/engine bindings install an
1539/// implementation behind [`RemoteFragmentEndpoint`];
1540/// [`InMemoryFragmentExecutor`] remains the deterministic reference executor.
1541#[async_trait::async_trait]
1542pub trait FragmentExecutor: Send + Sync {
1543    /// Runs the fragment. `inputs` carries one resolved stream per
1544    /// [`FragmentOperator::RemoteExchangeSource`] operator, in operator order.
1545    async fn execute(
1546        &self,
1547        fragment: &PlanFragment,
1548        inputs: Vec<FragmentStream>,
1549        control: FragmentControl,
1550    ) -> DistributedResult<FragmentStream>;
1551
1552    /// Returns the next `limit` local top-k candidates after `offset` (the
1553    /// number already returned), with a tightened unseen bound. Executors
1554    /// without scored streams leave the default, which rejects.
1555    fn refill_top_k(
1556        &self,
1557        fragment: &PlanFragment,
1558        offset: usize,
1559        limit: usize,
1560        control: FragmentControl,
1561    ) -> DistributedResult<TopKRefill> {
1562        let _ = (fragment, offset, limit, control);
1563        Err(DistributedError::Unsupported(
1564            "top-k refill is not implemented by this executor".to_owned(),
1565        ))
1566    }
1567}
1568
1569/// Moves fragments, refill, and cancellation between the coordinator and
1570/// workers (spec section 12.10). Implementations include the deterministic
1571/// [`InMemoryTransport`] and the bounded Arrow IPC
1572/// [`RemoteFragmentTransport`].
1573#[async_trait::async_trait]
1574pub trait FragmentTransport: Send + Sync {
1575    /// Starts a fragment on its assigned worker and returns its output
1576    /// stream.
1577    async fn execute_fragment(
1578        &self,
1579        query_id: QueryId,
1580        fragment: &PlanFragment,
1581        inputs: Vec<FragmentStream>,
1582        control: FragmentControl,
1583    ) -> DistributedResult<FragmentStream>;
1584
1585    /// Best-effort cancellation of a running (or abandoned) fragment.
1586    fn cancel_fragment(&self, query_id: QueryId, fragment_id: FragmentId) -> DistributedResult<()>;
1587
1588    /// Fetches the next top-k batch of a fragment's tablet (adaptive
1589    /// refill). Transports without a refill binding leave the default.
1590    async fn refill_top_k(
1591        &self,
1592        query_id: QueryId,
1593        fragment: &PlanFragment,
1594        offset: usize,
1595        limit: usize,
1596        control: FragmentControl,
1597    ) -> DistributedResult<TopKRefill> {
1598        let _ = (query_id, fragment, offset, limit, control);
1599        Err(DistributedError::Unsupported(
1600            "top-k refill over this transport is not bound in this wave".to_owned(),
1601        ))
1602    }
1603}
1604
1605/// In-memory [`FragmentTransport`]: routes fragments to per-tablet executors,
1606/// records starts/cancellations/refills for test introspection, and keeps
1607/// each fragment's [`ExecutionControl`] so cancellations are observable.
1608pub struct InMemoryTransport {
1609    default_executor: Arc<dyn FragmentExecutor>,
1610    executors: parking_lot::RwLock<HashMap<TabletId, Arc<dyn FragmentExecutor>>>,
1611    started: Mutex<Vec<FragmentId>>,
1612    cancelled: Mutex<Vec<FragmentId>>,
1613    controls: Mutex<HashMap<FragmentId, ExecutionControl>>,
1614    refills: Mutex<Vec<(FragmentId, usize, usize)>>,
1615}
1616
1617impl InMemoryTransport {
1618    /// A transport whose every fragment runs on `default_executor`.
1619    pub fn new(default_executor: Arc<dyn FragmentExecutor>) -> Self {
1620        Self {
1621            default_executor,
1622            executors: parking_lot::RwLock::new(HashMap::new()),
1623            started: Mutex::new(Vec::new()),
1624            cancelled: Mutex::new(Vec::new()),
1625            controls: Mutex::new(HashMap::new()),
1626            refills: Mutex::new(Vec::new()),
1627        }
1628    }
1629
1630    /// Pins one tablet to its own executor.
1631    pub fn with_executor(self, tablet: TabletId, executor: Arc<dyn FragmentExecutor>) -> Self {
1632        self.executors.write().insert(tablet, executor);
1633        self
1634    }
1635
1636    fn executor_for(&self, assignment: &FragmentAssignment) -> Arc<dyn FragmentExecutor> {
1637        match assignment {
1638            FragmentAssignment::Tablet(tablet) => self
1639                .executors
1640                .read()
1641                .get(tablet)
1642                .cloned()
1643                .unwrap_or_else(|| Arc::clone(&self.default_executor)),
1644            FragmentAssignment::Coordinator => Arc::clone(&self.default_executor),
1645        }
1646    }
1647
1648    /// Fragments started so far, in start order.
1649    pub fn started_fragments(&self) -> Vec<FragmentId> {
1650        self.started.lock().clone()
1651    }
1652
1653    /// Fragments cancelled so far, in cancel order.
1654    pub fn cancelled_fragments(&self) -> Vec<FragmentId> {
1655        self.cancelled.lock().clone()
1656    }
1657
1658    /// Top-k refills issued so far: `(fragment_id, offset, limit)`.
1659    pub fn refill_log(&self) -> Vec<(FragmentId, usize, usize)> {
1660        self.refills.lock().clone()
1661    }
1662
1663    /// The control handed to a started fragment, for cancellation assertions.
1664    pub fn control_for(&self, fragment_id: FragmentId) -> Option<ExecutionControl> {
1665        self.controls.lock().get(&fragment_id).cloned()
1666    }
1667}
1668
1669#[async_trait::async_trait]
1670impl FragmentTransport for InMemoryTransport {
1671    async fn execute_fragment(
1672        &self,
1673        _query_id: QueryId,
1674        fragment: &PlanFragment,
1675        inputs: Vec<FragmentStream>,
1676        control: FragmentControl,
1677    ) -> DistributedResult<FragmentStream> {
1678        self.started.lock().push(fragment.fragment_id);
1679        self.controls
1680            .lock()
1681            .insert(fragment.fragment_id, control.control.clone());
1682        self.executor_for(&fragment.assignment)
1683            .execute(fragment, inputs, control)
1684            .await
1685    }
1686
1687    fn cancel_fragment(
1688        &self,
1689        _query_id: QueryId,
1690        fragment_id: FragmentId,
1691    ) -> DistributedResult<()> {
1692        self.cancelled.lock().push(fragment_id);
1693        if let Some(control) = self.controls.lock().get(&fragment_id) {
1694            control.cancel(CancellationReason::ClientRequest);
1695        }
1696        Ok(())
1697    }
1698
1699    async fn refill_top_k(
1700        &self,
1701        _query_id: QueryId,
1702        fragment: &PlanFragment,
1703        offset: usize,
1704        limit: usize,
1705        control: FragmentControl,
1706    ) -> DistributedResult<TopKRefill> {
1707        self.refills
1708            .lock()
1709            .push((fragment.fragment_id, offset, limit));
1710        self.executor_for(&fragment.assignment)
1711            .refill_top_k(fragment, offset, limit, control)
1712    }
1713}
1714
1715// ---------------------------------------------------------------------------
1716// Remote Arrow IPC fragment transport
1717// ---------------------------------------------------------------------------
1718
1719/// Version of the private cluster fragment protocol.
1720///
1721/// This is an internal wire generation, not a MongrelDB release or database
1722/// format version. Peers fail closed when it differs.
1723pub const REMOTE_FRAGMENT_PROTOCOL_VERSION: u16 = 1;
1724/// Stable service discriminator inside the cluster's authenticated internal
1725/// RPC multiplexer.
1726pub const REMOTE_FRAGMENT_SERVICE_ID: u32 = 1;
1727
1728/// Default maximum encoded request or response body for one fragment RPC.
1729pub const DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES: usize = 16 * 1024 * 1024;
1730
1731/// Default maximum number of fragment streams held by one worker.
1732pub const DEFAULT_REMOTE_FRAGMENT_EXECUTIONS: usize = 1_024;
1733
1734#[derive(Debug, Clone, Serialize, Deserialize)]
1735struct RemoteFragmentEnvelope {
1736    version: u16,
1737    request: RemoteFragmentRequest,
1738}
1739
1740#[derive(Debug, Clone, Serialize, Deserialize)]
1741enum RemoteFragmentRequest {
1742    Start {
1743        query_id: QueryId,
1744        fragment: PlanFragment,
1745        inputs: Vec<Vec<RemoteBatchFrame>>,
1746        max_spill_bytes: u64,
1747        authorization_context: Vec<u8>,
1748        deadline_ms: Option<u64>,
1749    },
1750    Pull {
1751        query_id: QueryId,
1752        fragment_id: FragmentId,
1753    },
1754    Cancel {
1755        query_id: QueryId,
1756        fragment_id: FragmentId,
1757    },
1758    RefillTopK {
1759        query_id: QueryId,
1760        fragment: PlanFragment,
1761        offset: usize,
1762        limit: usize,
1763        authorization_context: Vec<u8>,
1764        deadline_ms: Option<u64>,
1765    },
1766}
1767
1768#[derive(Debug, Clone, Serialize, Deserialize)]
1769struct RemoteFragmentResponseEnvelope {
1770    version: u16,
1771    response: RemoteFragmentResponse,
1772}
1773
1774#[derive(Debug, Clone, Serialize, Deserialize)]
1775enum RemoteFragmentResponse {
1776    Started,
1777    Frame(Option<RemoteBatchFrame>),
1778    Cancelled,
1779    TopKRefill {
1780        rows: Vec<TopKCandidate>,
1781        payload: Vec<u8>,
1782        unseen_bound: Option<u64>,
1783    },
1784    Error(String),
1785}
1786
1787#[derive(Debug, Clone, Serialize, Deserialize)]
1788struct RemoteBatchFrame {
1789    ipc: Vec<u8>,
1790    score_bound: ScoreBound,
1791}
1792
1793type RemoteExecutionKey = (QueryId, FragmentId);
1794
1795struct RemoteExecution {
1796    stream: tokio::sync::Mutex<Option<FragmentStream>>,
1797    control: ExecutionControl,
1798}
1799
1800/// Worker-side endpoint for the internal fragment protocol.
1801///
1802/// The cluster transport supplies authenticated node-to-node delivery. This
1803/// endpoint owns bounded active cursors and returns one Arrow IPC batch per
1804/// pull. Pull framing provides backpressure without buffering a whole result
1805/// on either peer.
1806pub struct RemoteFragmentEndpoint {
1807    executor: Arc<dyn FragmentExecutor>,
1808    executions: parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<RemoteExecution>>>,
1809    max_executions: usize,
1810    max_message_bytes: usize,
1811}
1812
1813impl RemoteFragmentEndpoint {
1814    /// Creates a bounded worker endpoint.
1815    pub fn new(executor: Arc<dyn FragmentExecutor>) -> Self {
1816        Self::with_limits(
1817            executor,
1818            DEFAULT_REMOTE_FRAGMENT_EXECUTIONS,
1819            DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES,
1820        )
1821    }
1822
1823    /// Creates a worker endpoint with explicit cursor and frame bounds.
1824    pub fn with_limits(
1825        executor: Arc<dyn FragmentExecutor>,
1826        max_executions: usize,
1827        max_message_bytes: usize,
1828    ) -> Self {
1829        Self {
1830            executor,
1831            executions: parking_lot::Mutex::new(HashMap::new()),
1832            max_executions: max_executions.max(1),
1833            max_message_bytes: max_message_bytes.max(1),
1834        }
1835    }
1836
1837    /// Number of live remote fragment cursors.
1838    pub fn active_executions(&self) -> usize {
1839        self.executions.lock().len()
1840    }
1841
1842    /// Handles one authenticated internal RPC body.
1843    pub async fn handle(&self, bytes: &[u8]) -> DistributedResult<Vec<u8>> {
1844        if bytes.len() > self.max_message_bytes {
1845            return Err(DistributedError::RemoteProtocol(format!(
1846                "fragment request is {} bytes; limit is {}",
1847                bytes.len(),
1848                self.max_message_bytes
1849            )));
1850        }
1851        let envelope: RemoteFragmentEnvelope = decode_remote_wire(bytes, self.max_message_bytes)?;
1852        if envelope.version != REMOTE_FRAGMENT_PROTOCOL_VERSION {
1853            return Err(DistributedError::RemoteProtocol(format!(
1854                "unsupported fragment protocol version {}; supported version is {}",
1855                envelope.version, REMOTE_FRAGMENT_PROTOCOL_VERSION
1856            )));
1857        }
1858        let response = match self.handle_request(envelope.request).await {
1859            Ok(response) => response,
1860            Err(error) => RemoteFragmentResponse::Error(error.to_string()),
1861        };
1862        encode_remote_wire(
1863            &RemoteFragmentResponseEnvelope {
1864                version: REMOTE_FRAGMENT_PROTOCOL_VERSION,
1865                response,
1866            },
1867            self.max_message_bytes,
1868        )
1869    }
1870
1871    async fn handle_request(
1872        &self,
1873        request: RemoteFragmentRequest,
1874    ) -> DistributedResult<RemoteFragmentResponse> {
1875        match request {
1876            RemoteFragmentRequest::Start {
1877                query_id,
1878                fragment,
1879                inputs,
1880                max_spill_bytes,
1881                authorization_context,
1882                deadline_ms,
1883            } => {
1884                if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
1885                    return Err(DistributedError::RemoteProtocol(format!(
1886                        "fragment authorization context is {} bytes; limit is {}",
1887                        authorization_context.len(),
1888                        MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
1889                    )));
1890                }
1891                let key = (query_id, fragment.fragment_id);
1892                {
1893                    let executions = self.executions.lock();
1894                    if executions.contains_key(&key) {
1895                        return Err(DistributedError::RemoteProtocol(format!(
1896                            "fragment {} for query {query_id} is already running",
1897                            fragment.fragment_id
1898                        )));
1899                    }
1900                    if executions.len() >= self.max_executions {
1901                        return Err(DistributedError::Reservation {
1902                            fragment_id: fragment.fragment_id,
1903                            reason: format!(
1904                                "worker holds {} remote fragments; limit is {}",
1905                                executions.len(),
1906                                self.max_executions
1907                            ),
1908                        });
1909                    }
1910                }
1911                let inputs = inputs
1912                    .into_iter()
1913                    .map(|frames| {
1914                        frames
1915                            .into_iter()
1916                            .map(|frame| decode_remote_frame(frame, self.max_message_bytes))
1917                            .collect::<DistributedResult<Vec<_>>>()
1918                            .map(|frames| {
1919                                Box::pin(stream::iter(frames.into_iter().map(Ok))) as FragmentStream
1920                            })
1921                    })
1922                    .collect::<DistributedResult<Vec<_>>>()?;
1923                let control = deadline_ms.map_or_else(
1924                    || ExecutionControl::new(None),
1925                    |milliseconds| {
1926                        ExecutionControl::with_timeout(Duration::from_millis(milliseconds))
1927                    },
1928                );
1929                let execution = Arc::new(RemoteExecution {
1930                    stream: tokio::sync::Mutex::new(None),
1931                    control: control.clone(),
1932                });
1933                {
1934                    let mut executions = self.executions.lock();
1935                    if executions.contains_key(&key) {
1936                        return Err(DistributedError::RemoteProtocol(format!(
1937                            "fragment {} for query {query_id} raced another start",
1938                            fragment.fragment_id
1939                        )));
1940                    }
1941                    if executions.len() >= self.max_executions {
1942                        return Err(DistributedError::Reservation {
1943                            fragment_id: fragment.fragment_id,
1944                            reason: "remote fragment limit reached during start".to_owned(),
1945                        });
1946                    }
1947                    executions.insert(key, Arc::clone(&execution));
1948                }
1949                let stream = match self
1950                    .executor
1951                    .execute(
1952                        &fragment,
1953                        inputs,
1954                        FragmentControl {
1955                            control,
1956                            max_spill_bytes,
1957                            authorization_context: authorization_context.into(),
1958                        },
1959                    )
1960                    .await
1961                {
1962                    Ok(stream) => stream,
1963                    Err(error) => {
1964                        self.executions.lock().remove(&key);
1965                        return Err(error);
1966                    }
1967                };
1968                if self.executions.lock().get(&key).is_none() {
1969                    return Err(DistributedError::Cancelled(
1970                        CancellationReason::ClientRequest,
1971                    ));
1972                }
1973                if let Err(error) = checkpoint(&execution.control) {
1974                    self.executions.lock().remove(&key);
1975                    return Err(error);
1976                }
1977                *execution.stream.lock().await = Some(stream);
1978                Ok(RemoteFragmentResponse::Started)
1979            }
1980            RemoteFragmentRequest::Pull {
1981                query_id,
1982                fragment_id,
1983            } => {
1984                let key = (query_id, fragment_id);
1985                let execution = self.executions.lock().get(&key).cloned().ok_or_else(|| {
1986                    DistributedError::RemoteProtocol(format!(
1987                        "fragment {fragment_id} for query {query_id} is not running"
1988                    ))
1989                })?;
1990                let next = {
1991                    checkpoint(&execution.control)?;
1992                    let mut stream = execution.stream.lock().await;
1993                    let stream = stream.as_mut().ok_or_else(|| {
1994                        DistributedError::RemoteProtocol(format!(
1995                            "fragment {fragment_id} for query {query_id} is not ready"
1996                        ))
1997                    })?;
1998                    stream.next().await.transpose()?
1999                };
2000                match next {
2001                    Some(frame) => Ok(RemoteFragmentResponse::Frame(Some(encode_remote_frame(
2002                        &frame,
2003                        self.max_message_bytes,
2004                    )?))),
2005                    None => {
2006                        self.executions.lock().remove(&key);
2007                        Ok(RemoteFragmentResponse::Frame(None))
2008                    }
2009                }
2010            }
2011            RemoteFragmentRequest::Cancel {
2012                query_id,
2013                fragment_id,
2014            } => {
2015                if let Some(execution) = self.executions.lock().remove(&(query_id, fragment_id)) {
2016                    execution.control.cancel(CancellationReason::ClientRequest);
2017                }
2018                Ok(RemoteFragmentResponse::Cancelled)
2019            }
2020            RemoteFragmentRequest::RefillTopK {
2021                query_id: _,
2022                fragment,
2023                offset,
2024                limit,
2025                authorization_context,
2026                deadline_ms,
2027            } => {
2028                if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
2029                    return Err(DistributedError::RemoteProtocol(format!(
2030                        "fragment authorization context is {} bytes; limit is {}",
2031                        authorization_context.len(),
2032                        MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
2033                    )));
2034                }
2035                let control = deadline_ms.map_or_else(
2036                    || ExecutionControl::new(None),
2037                    |milliseconds| {
2038                        ExecutionControl::with_timeout(Duration::from_millis(milliseconds))
2039                    },
2040                );
2041                let refill = self.executor.refill_top_k(
2042                    &fragment,
2043                    offset,
2044                    limit,
2045                    FragmentControl {
2046                        control,
2047                        max_spill_bytes: fragment.max_spill_bytes,
2048                        authorization_context: authorization_context.into(),
2049                    },
2050                )?;
2051                let payload = encode_record_batch(&refill.payload, self.max_message_bytes)?;
2052                Ok(RemoteFragmentResponse::TopKRefill {
2053                    rows: refill.rows,
2054                    payload,
2055                    unseen_bound: refill.unseen_bound,
2056                })
2057            }
2058        }
2059    }
2060}
2061
2062/// One authenticated request/response carrier for remote fragment bodies.
2063///
2064/// Production implements this over the cluster's node-identity-bound mTLS
2065/// transport. Tests may use [`LoopbackFragmentRpcClient`] to isolate the
2066/// query-side protocol.
2067#[async_trait::async_trait]
2068pub trait FragmentRpcClient: Send + Sync {
2069    /// Performs one bounded internal RPC.
2070    async fn call(&self, request: Vec<u8>) -> DistributedResult<Vec<u8>>;
2071}
2072
2073/// In-process carrier for protocol and endpoint tests.
2074pub struct LoopbackFragmentRpcClient {
2075    endpoint: Arc<RemoteFragmentEndpoint>,
2076}
2077
2078impl LoopbackFragmentRpcClient {
2079    /// Wraps one worker endpoint.
2080    pub fn new(endpoint: Arc<RemoteFragmentEndpoint>) -> Self {
2081        Self { endpoint }
2082    }
2083}
2084
2085#[async_trait::async_trait]
2086impl FragmentRpcClient for LoopbackFragmentRpcClient {
2087    async fn call(&self, request: Vec<u8>) -> DistributedResult<Vec<u8>> {
2088        self.endpoint.handle(&request).await
2089    }
2090}
2091
2092/// Coordinator-side Arrow IPC transport for tablet fragments.
2093///
2094/// Each tablet route names an authenticated RPC carrier. Output is pulled one
2095/// batch at a time, so consumer polling is the backpressure mechanism.
2096pub struct RemoteFragmentTransport {
2097    default_client: Option<Arc<dyn FragmentRpcClient>>,
2098    clients: parking_lot::RwLock<HashMap<TabletId, Arc<dyn FragmentRpcClient>>>,
2099    active: Arc<parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<dyn FragmentRpcClient>>>>,
2100    max_message_bytes: usize,
2101}
2102
2103impl RemoteFragmentTransport {
2104    /// Creates a transport whose tablets use `default_client`.
2105    pub fn new(default_client: Arc<dyn FragmentRpcClient>) -> Self {
2106        Self::with_message_limit(default_client, DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES)
2107    }
2108
2109    /// Creates a transport with an explicit per-call body bound.
2110    pub fn with_message_limit(
2111        default_client: Arc<dyn FragmentRpcClient>,
2112        max_message_bytes: usize,
2113    ) -> Self {
2114        Self {
2115            default_client: Some(default_client),
2116            clients: parking_lot::RwLock::new(HashMap::new()),
2117            active: Arc::new(parking_lot::Mutex::new(HashMap::new())),
2118            max_message_bytes: max_message_bytes.max(1),
2119        }
2120    }
2121
2122    /// Creates a fail-closed transport with no fallback route.
2123    pub fn routed() -> Self {
2124        Self {
2125            default_client: None,
2126            clients: parking_lot::RwLock::new(HashMap::new()),
2127            active: Arc::new(parking_lot::Mutex::new(HashMap::new())),
2128            max_message_bytes: DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES,
2129        }
2130    }
2131
2132    /// Routes one tablet through a specific peer carrier.
2133    pub fn with_client(self, tablet: TabletId, client: Arc<dyn FragmentRpcClient>) -> Self {
2134        self.clients.write().insert(tablet, client);
2135        self
2136    }
2137
2138    fn client_for(&self, fragment: &PlanFragment) -> DistributedResult<Arc<dyn FragmentRpcClient>> {
2139        let tablet = tablet_of(fragment)?;
2140        self.clients
2141            .read()
2142            .get(&tablet)
2143            .cloned()
2144            .or_else(|| self.default_client.as_ref().map(Arc::clone))
2145            .ok_or_else(|| {
2146                DistributedError::RemoteTransport(format!(
2147                    "no authenticated fragment route for tablet {tablet}"
2148                ))
2149            })
2150    }
2151
2152    async fn call(
2153        &self,
2154        client: &Arc<dyn FragmentRpcClient>,
2155        request: RemoteFragmentRequest,
2156    ) -> DistributedResult<RemoteFragmentResponse> {
2157        remote_call(client, request, self.max_message_bytes).await
2158    }
2159}
2160
2161struct RemoteStreamState {
2162    client: Arc<dyn FragmentRpcClient>,
2163    key: RemoteExecutionKey,
2164    active: Arc<parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<dyn FragmentRpcClient>>>>,
2165    max_message_bytes: usize,
2166    complete: bool,
2167    yielded_error: bool,
2168}
2169
2170impl RemoteStreamState {
2171    fn mark_complete(&mut self) {
2172        self.complete = true;
2173    }
2174}
2175
2176impl Drop for RemoteStreamState {
2177    fn drop(&mut self) {
2178        self.active.lock().remove(&self.key);
2179        if self.complete {
2180            return;
2181        }
2182        let request = RemoteFragmentRequest::Cancel {
2183            query_id: self.key.0,
2184            fragment_id: self.key.1,
2185        };
2186        let client = Arc::clone(&self.client);
2187        let max_message_bytes = self.max_message_bytes;
2188        if let Ok(runtime) = tokio::runtime::Handle::try_current() {
2189            runtime.spawn(async move {
2190                let _ = remote_call(&client, request, max_message_bytes).await;
2191            });
2192        }
2193    }
2194}
2195
2196#[async_trait::async_trait]
2197impl FragmentTransport for RemoteFragmentTransport {
2198    async fn execute_fragment(
2199        &self,
2200        query_id: QueryId,
2201        fragment: &PlanFragment,
2202        inputs: Vec<FragmentStream>,
2203        control: FragmentControl,
2204    ) -> DistributedResult<FragmentStream> {
2205        let client = self.client_for(fragment)?;
2206        let mut wire_inputs = Vec::with_capacity(inputs.len());
2207        for input in inputs {
2208            let frames = drain_stream(input, &control.control).await?;
2209            wire_inputs.push(
2210                frames
2211                    .iter()
2212                    .map(|frame| encode_remote_frame(frame, self.max_message_bytes))
2213                    .collect::<DistributedResult<Vec<_>>>()?,
2214            );
2215        }
2216        let key = (query_id, fragment.fragment_id);
2217        self.active.lock().insert(key, Arc::clone(&client));
2218        let start = self.call(
2219            &client,
2220            RemoteFragmentRequest::Start {
2221                query_id,
2222                fragment: fragment.clone(),
2223                inputs: wire_inputs,
2224                max_spill_bytes: control.max_spill_bytes,
2225                authorization_context: control.authorization_context.to_vec(),
2226                deadline_ms: control
2227                    .control
2228                    .remaining_duration()
2229                    .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64),
2230            },
2231        );
2232        let response = tokio::select! {
2233            response = start => response,
2234            _ = control.control.cancelled() => {
2235                let _ = remote_call(
2236                    &client,
2237                    RemoteFragmentRequest::Cancel {
2238                        query_id,
2239                        fragment_id: fragment.fragment_id,
2240                    },
2241                    self.max_message_bytes,
2242                ).await;
2243                Err(DistributedError::Cancelled(control.control.reason()))
2244            }
2245        };
2246        let response = match response {
2247            Ok(response) => response,
2248            Err(error) => {
2249                self.active.lock().remove(&key);
2250                return Err(error);
2251            }
2252        };
2253        match response {
2254            RemoteFragmentResponse::Started => {}
2255            other => {
2256                self.active.lock().remove(&key);
2257                return Err(unexpected_remote_response("Started", &other));
2258            }
2259        }
2260        let state = RemoteStreamState {
2261            client,
2262            key,
2263            active: Arc::clone(&self.active),
2264            max_message_bytes: self.max_message_bytes,
2265            complete: false,
2266            yielded_error: false,
2267        };
2268        let output = stream::unfold(state, |mut state| async move {
2269            if state.complete || state.yielded_error {
2270                return None;
2271            }
2272            let response = remote_call(
2273                &state.client,
2274                RemoteFragmentRequest::Pull {
2275                    query_id: state.key.0,
2276                    fragment_id: state.key.1,
2277                },
2278                state.max_message_bytes,
2279            )
2280            .await;
2281            match response {
2282                Ok(RemoteFragmentResponse::Frame(Some(frame))) => {
2283                    let item = decode_remote_frame(frame, state.max_message_bytes);
2284                    if item.is_err() {
2285                        state.yielded_error = true;
2286                    }
2287                    Some((item, state))
2288                }
2289                Ok(RemoteFragmentResponse::Frame(None)) => {
2290                    state.mark_complete();
2291                    None
2292                }
2293                Ok(other) => {
2294                    state.yielded_error = true;
2295                    Some((Err(unexpected_remote_response("Frame", &other)), state))
2296                }
2297                Err(error) => {
2298                    state.yielded_error = true;
2299                    Some((Err(error), state))
2300                }
2301            }
2302        });
2303        Ok(Box::pin(output))
2304    }
2305
2306    fn cancel_fragment(&self, query_id: QueryId, fragment_id: FragmentId) -> DistributedResult<()> {
2307        let key = (query_id, fragment_id);
2308        let Some(client) = self.active.lock().remove(&key) else {
2309            return Ok(());
2310        };
2311        let max_message_bytes = self.max_message_bytes;
2312        let runtime = tokio::runtime::Handle::try_current().map_err(|error| {
2313            DistributedError::RemoteTransport(format!(
2314                "cannot schedule fragment cancellation outside Tokio: {error}"
2315            ))
2316        })?;
2317        runtime.spawn(async move {
2318            let _ = remote_call(
2319                &client,
2320                RemoteFragmentRequest::Cancel {
2321                    query_id,
2322                    fragment_id,
2323                },
2324                max_message_bytes,
2325            )
2326            .await;
2327        });
2328        Ok(())
2329    }
2330
2331    async fn refill_top_k(
2332        &self,
2333        query_id: QueryId,
2334        fragment: &PlanFragment,
2335        offset: usize,
2336        limit: usize,
2337        control: FragmentControl,
2338    ) -> DistributedResult<TopKRefill> {
2339        let client = self.client_for(fragment)?;
2340        match self
2341            .call(
2342                &client,
2343                RemoteFragmentRequest::RefillTopK {
2344                    query_id,
2345                    fragment: fragment.clone(),
2346                    offset,
2347                    limit,
2348                    authorization_context: control.authorization_context.to_vec(),
2349                    deadline_ms: control
2350                        .control
2351                        .remaining_duration()
2352                        .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64),
2353                },
2354            )
2355            .await?
2356        {
2357            RemoteFragmentResponse::TopKRefill {
2358                rows,
2359                payload,
2360                unseen_bound,
2361            } => Ok(TopKRefill {
2362                rows,
2363                payload: decode_record_batch(&payload, self.max_message_bytes)?,
2364                unseen_bound,
2365            }),
2366            other => Err(unexpected_remote_response("TopKRefill", &other)),
2367        }
2368    }
2369}
2370
2371async fn remote_call(
2372    client: &Arc<dyn FragmentRpcClient>,
2373    request: RemoteFragmentRequest,
2374    max_message_bytes: usize,
2375) -> DistributedResult<RemoteFragmentResponse> {
2376    let request = encode_remote_wire(
2377        &RemoteFragmentEnvelope {
2378            version: REMOTE_FRAGMENT_PROTOCOL_VERSION,
2379            request,
2380        },
2381        max_message_bytes,
2382    )?;
2383    let response = client.call(request).await?;
2384    let envelope: RemoteFragmentResponseEnvelope =
2385        decode_remote_wire(&response, max_message_bytes)?;
2386    if envelope.version != REMOTE_FRAGMENT_PROTOCOL_VERSION {
2387        return Err(DistributedError::RemoteProtocol(format!(
2388            "peer answered with fragment protocol version {}; supported version is {}",
2389            envelope.version, REMOTE_FRAGMENT_PROTOCOL_VERSION
2390        )));
2391    }
2392    match envelope.response {
2393        RemoteFragmentResponse::Error(message) => Err(DistributedError::RemoteTransport(message)),
2394        response => Ok(response),
2395    }
2396}
2397
2398fn remote_wire_options() -> impl Options {
2399    bincode::DefaultOptions::new()
2400        .with_fixint_encoding()
2401        .reject_trailing_bytes()
2402}
2403
2404fn encode_remote_wire<T: Serialize>(
2405    value: &T,
2406    max_message_bytes: usize,
2407) -> DistributedResult<Vec<u8>> {
2408    let bytes = remote_wire_options()
2409        .serialize(value)
2410        .map_err(|error| DistributedError::RemoteProtocol(error.to_string()))?;
2411    if bytes.len() > max_message_bytes {
2412        return Err(DistributedError::RemoteProtocol(format!(
2413            "encoded fragment message is {} bytes; limit is {max_message_bytes}",
2414            bytes.len()
2415        )));
2416    }
2417    Ok(bytes)
2418}
2419
2420fn decode_remote_wire<T: for<'de> Deserialize<'de>>(
2421    bytes: &[u8],
2422    max_message_bytes: usize,
2423) -> DistributedResult<T> {
2424    if bytes.len() > max_message_bytes {
2425        return Err(DistributedError::RemoteProtocol(format!(
2426            "fragment message is {} bytes; limit is {max_message_bytes}",
2427            bytes.len()
2428        )));
2429    }
2430    remote_wire_options()
2431        .with_limit(max_message_bytes as u64)
2432        .deserialize(bytes)
2433        .map_err(|error| DistributedError::RemoteProtocol(error.to_string()))
2434}
2435
2436fn encode_record_batch(
2437    batch: &RecordBatch,
2438    max_message_bytes: usize,
2439) -> DistributedResult<Vec<u8>> {
2440    let mut ipc = Vec::new();
2441    {
2442        let mut writer = StreamWriter::try_new(&mut ipc, &batch.schema())?;
2443        writer.write(batch)?;
2444        writer.finish()?;
2445    }
2446    if ipc.len() > max_message_bytes {
2447        return Err(DistributedError::RemoteProtocol(format!(
2448            "Arrow IPC batch is {} bytes; limit is {max_message_bytes}",
2449            ipc.len()
2450        )));
2451    }
2452    Ok(ipc)
2453}
2454
2455fn decode_record_batch(ipc: &[u8], max_message_bytes: usize) -> DistributedResult<RecordBatch> {
2456    if ipc.len() > max_message_bytes {
2457        return Err(DistributedError::RemoteProtocol(format!(
2458            "Arrow IPC batch is {} bytes; limit is {max_message_bytes}",
2459            ipc.len()
2460        )));
2461    }
2462    let mut reader = StreamReader::try_new(Cursor::new(ipc), None)?;
2463    let batch = reader.next().transpose()?.ok_or_else(|| {
2464        DistributedError::RemoteProtocol("Arrow IPC frame contains no record batch".to_owned())
2465    })?;
2466    if reader.next().transpose()?.is_some() {
2467        return Err(DistributedError::RemoteProtocol(
2468            "Arrow IPC frame contains more than one record batch".to_owned(),
2469        ));
2470    }
2471    Ok(batch)
2472}
2473
2474fn encode_remote_frame(
2475    frame: &BatchFrame,
2476    max_message_bytes: usize,
2477) -> DistributedResult<RemoteBatchFrame> {
2478    Ok(RemoteBatchFrame {
2479        ipc: encode_record_batch(&frame.batch, max_message_bytes)?,
2480        score_bound: frame.score_bound,
2481    })
2482}
2483
2484fn decode_remote_frame(
2485    frame: RemoteBatchFrame,
2486    max_message_bytes: usize,
2487) -> DistributedResult<BatchFrame> {
2488    Ok(BatchFrame {
2489        batch: decode_record_batch(&frame.ipc, max_message_bytes)?,
2490        score_bound: frame.score_bound,
2491    })
2492}
2493
2494fn unexpected_remote_response(expected: &str, actual: &RemoteFragmentResponse) -> DistributedError {
2495    DistributedError::RemoteProtocol(format!(
2496        "expected remote {expected} response, got {actual:?}"
2497    ))
2498}
2499
2500/// Batch source consumed by the reference fragment operator engine.
2501///
2502/// Implementations validate `control.authorization_context` before reading.
2503pub trait FragmentTableSource: Send + Sync {
2504    /// Reads one table slice from a hosted tablet.
2505    fn scan(
2506        &self,
2507        table: &str,
2508        tablet: TabletId,
2509        include_row_id: bool,
2510        control: Option<&FragmentControl>,
2511    ) -> DistributedResult<Vec<RecordBatch>>;
2512
2513    /// Returns the empty-result schema for one table, when known.
2514    fn schema(&self, table: &str, tablet: TabletId) -> DistributedResult<Option<SchemaRef>>;
2515}
2516
2517/// Resolves the local storage owner for one hosted tablet.
2518pub trait FragmentDatabaseProvider: Send + Sync {
2519    /// Returns `None` when this node does not host `tablet`.
2520    fn database(&self, tablet: TabletId) -> Option<Arc<mongreldb_core::Database>>;
2521}
2522
2523/// Validates the forwarded server-issued authorization envelope.
2524pub trait FragmentAuthorizationResolver: Send + Sync {
2525    /// Resolves the exact principal for the local database. `None` is valid
2526    /// only for a credentialless database.
2527    fn resolve(
2528        &self,
2529        database: &mongreldb_core::Database,
2530        context: &[u8],
2531    ) -> DistributedResult<Option<mongreldb_core::Principal>>;
2532}
2533
2534/// Core MVCC-backed tablet source used by remote SQL workers.
2535pub struct CoreFragmentTableSource {
2536    databases: Arc<dyn FragmentDatabaseProvider>,
2537    authorization: Arc<dyn FragmentAuthorizationResolver>,
2538}
2539
2540impl CoreFragmentTableSource {
2541    /// Creates an authorized core-backed source.
2542    pub fn new(
2543        databases: Arc<dyn FragmentDatabaseProvider>,
2544        authorization: Arc<dyn FragmentAuthorizationResolver>,
2545    ) -> Self {
2546        Self {
2547            databases,
2548            authorization,
2549        }
2550    }
2551}
2552
2553impl FragmentTableSource for CoreFragmentTableSource {
2554    fn scan(
2555        &self,
2556        table: &str,
2557        tablet: TabletId,
2558        include_row_id: bool,
2559        control: Option<&FragmentControl>,
2560    ) -> DistributedResult<Vec<RecordBatch>> {
2561        let control = control.ok_or_else(|| {
2562            DistributedError::RemoteProtocol(
2563                "core tablet scan requires fragment authorization control".to_owned(),
2564            )
2565        })?;
2566        checkpoint(&control.control)?;
2567        let database = self.databases.database(tablet).ok_or_else(|| {
2568            DistributedError::RemoteTransport(format!("this node does not host tablet {tablet}"))
2569        })?;
2570        let principal = self
2571            .authorization
2572            .resolve(&database, &control.authorization_context)?;
2573        let schema = database
2574            .table(table)
2575            .map_err(|error| DistributedError::RemoteTransport(error.to_string()))?
2576            .lock()
2577            .schema()
2578            .clone();
2579        let rows = database
2580            .query_as_principal_controlled(
2581                table,
2582                &mongreldb_core::Query {
2583                    conditions: Vec::new(),
2584                    // Fragment scans are an internal streaming input, not a
2585                    // public final-result window. Capping this at
2586                    // MAX_FINAL_LIMIT silently dropped every row after
2587                    // 10,000 before aggregation, sorting, or exchange.
2588                    limit: None,
2589                    offset: 0,
2590                },
2591                None,
2592                principal.as_ref(),
2593                &control.control,
2594            )
2595            .map_err(|error| DistributedError::RemoteTransport(error.to_string()))?;
2596        let batch = crate::arrow_conv::rows_to_batch(&rows, &schema)
2597            .map_err(|error| DistributedError::Arrow(error.to_string()))?;
2598        if !include_row_id {
2599            return Ok(vec![batch]);
2600        }
2601        let mut fields = batch.schema().fields().iter().cloned().collect::<Vec<_>>();
2602        fields.push(Arc::new(Field::new(
2603            TOPK_ROWID_COLUMN,
2604            DataType::UInt64,
2605            false,
2606        )));
2607        let mut columns = batch.columns().to_vec();
2608        columns.push(Arc::new(UInt64Array::from(
2609            rows.iter().map(|row| row.row_id.0).collect::<Vec<_>>(),
2610        )));
2611        let batch = RecordBatch::try_new(Arc::new(Schema::new(fields)), columns)?;
2612        Ok(vec![batch])
2613    }
2614
2615    fn schema(&self, _table: &str, _tablet: TabletId) -> DistributedResult<Option<SchemaRef>> {
2616        // `scan` always returns one (possibly empty) batch with the authorized
2617        // schema, so this fallback is never needed.
2618        Ok(None)
2619    }
2620}
2621
2622/// In-memory per-`(table, tablet)` record-batch source backing
2623/// [`InMemoryFragmentExecutor`].
2624#[derive(Default)]
2625pub struct InMemoryTableStore {
2626    tables: parking_lot::RwLock<HashMap<(String, TabletId), Vec<RecordBatch>>>,
2627    schemas: parking_lot::RwLock<HashMap<String, SchemaRef>>,
2628}
2629
2630impl InMemoryTableStore {
2631    /// An empty store.
2632    pub fn new() -> Self {
2633        Self::default()
2634    }
2635
2636    /// Appends one batch to a tablet of a table, registering the table's
2637    /// schema from the batch on first sight.
2638    pub fn insert(&self, table: &str, tablet: TabletId, batch: RecordBatch) {
2639        self.schemas
2640            .write()
2641            .entry(table.to_owned())
2642            .or_insert_with(|| batch.schema());
2643        self.tables
2644            .write()
2645            .entry((table.to_owned(), tablet))
2646            .or_default()
2647            .push(batch);
2648    }
2649
2650    /// Registers a table schema explicitly (covers tablets with no rows).
2651    pub fn register_schema(&self, table: &str, schema: SchemaRef) {
2652        self.schemas.write().insert(table.to_owned(), schema);
2653    }
2654
2655    /// All batches stored for one tablet of a table (empty when none).
2656    pub fn snapshot(&self, table: &str, tablet: TabletId) -> Vec<RecordBatch> {
2657        self.tables
2658            .read()
2659            .get(&(table.to_owned(), tablet))
2660            .cloned()
2661            .unwrap_or_default()
2662    }
2663
2664    /// The table's registered schema, when known.
2665    pub fn schema(&self, table: &str) -> Option<SchemaRef> {
2666        self.schemas.read().get(table).cloned()
2667    }
2668}
2669
2670impl FragmentTableSource for InMemoryTableStore {
2671    fn scan(
2672        &self,
2673        table: &str,
2674        tablet: TabletId,
2675        _include_row_id: bool,
2676        _control: Option<&FragmentControl>,
2677    ) -> DistributedResult<Vec<RecordBatch>> {
2678        Ok(self.snapshot(table, tablet))
2679    }
2680
2681    fn schema(&self, table: &str, _tablet: TabletId) -> DistributedResult<Option<SchemaRef>> {
2682        Ok(self.schema(table))
2683    }
2684}
2685
2686/// Reference [`FragmentExecutor`] over an [`InMemoryTableStore`]. Interprets
2687/// scans, projections, partial aggregates, local merge sorts, bounded local
2688/// top-ks (with exact tie information), and limits for real; exchange
2689/// sources drain their input streams; join operators reject (their execution
2690/// binding lands with the tablet wave — plan shape is fully tested).
2691pub struct InMemoryFragmentExecutor {
2692    store: Arc<dyn FragmentTableSource>,
2693    /// Wire batch for the local top-k: at most this many rows are emitted
2694    /// before reporting a bound (`None` = emit `k`, which never needs a
2695    /// refill). Smaller values exercise the coordinator's adaptive refill.
2696    topk_emit_batch: Option<usize>,
2697}
2698
2699impl InMemoryFragmentExecutor {
2700    /// An executor over `store` that emits up to `k` top-k rows locally.
2701    pub fn new(store: Arc<InMemoryTableStore>) -> Self {
2702        Self {
2703            store,
2704            topk_emit_batch: None,
2705        }
2706    }
2707
2708    /// Bounds the local top-k emission batch (refill exerciser).
2709    pub fn with_topk_emit_batch(store: Arc<InMemoryTableStore>, batch: usize) -> Self {
2710        Self {
2711            store,
2712            topk_emit_batch: Some(batch),
2713        }
2714    }
2715
2716    /// Uses an arbitrary authorized tablet source with the same operator
2717    /// engine as the deterministic in-memory reference.
2718    pub fn from_source(store: Arc<dyn FragmentTableSource>) -> Self {
2719        Self {
2720            store,
2721            topk_emit_batch: None,
2722        }
2723    }
2724
2725    /// Replays the scan (+ projection) of a fragment from the store.
2726    fn scan_batches(
2727        &self,
2728        fragment: &PlanFragment,
2729        control: Option<&FragmentControl>,
2730    ) -> DistributedResult<Vec<RecordBatch>> {
2731        let tablet = tablet_of(fragment)?;
2732        let scan = fragment
2733            .operators
2734            .iter()
2735            .find_map(|operator| match operator {
2736                FragmentOperator::TabletScan {
2737                    table,
2738                    predicate,
2739                    projection,
2740                } => Some((table, predicate, projection)),
2741                _ => None,
2742            })
2743            .ok_or_else(|| {
2744                DistributedError::InvalidPlan(format!(
2745                    "fragment {} has no tablet scan",
2746                    fragment.fragment_id
2747                ))
2748            })?;
2749        if scan.1.is_some() {
2750            return Err(DistributedError::Unsupported(
2751                "tablet predicate execution is not bound to the fragment operator engine"
2752                    .to_owned(),
2753            ));
2754        }
2755        let include_row_id = fragment
2756            .operators
2757            .iter()
2758            .any(|operator| matches!(operator, FragmentOperator::DistributedTopK { .. }));
2759        let mut batches = self.store.scan(scan.0, tablet, include_row_id, control)?;
2760        if batches.is_empty() {
2761            if let Some(schema) = self.store.schema(scan.0, tablet)? {
2762                batches = vec![RecordBatch::new_empty(schema)];
2763            }
2764        }
2765        if !scan.2.is_empty() {
2766            batches = project_batches(&batches, scan.2)?;
2767        }
2768        Ok(batches)
2769    }
2770}
2771
2772#[async_trait::async_trait]
2773impl FragmentExecutor for InMemoryFragmentExecutor {
2774    async fn execute(
2775        &self,
2776        fragment: &PlanFragment,
2777        inputs: Vec<FragmentStream>,
2778        control: FragmentControl,
2779    ) -> DistributedResult<FragmentStream> {
2780        let mut batches: Vec<RecordBatch> = Vec::new();
2781        let mut inputs = inputs.into_iter();
2782        let mut bound = ScoreBound::Unknown;
2783        for operator in &fragment.operators {
2784            checkpoint(&control.control)?;
2785            match operator {
2786                FragmentOperator::TabletScan { .. } => {
2787                    batches = self.scan_batches(fragment, Some(&control))?;
2788                }
2789                FragmentOperator::RemoteExchangeSource { .. } => {
2790                    let input = inputs.next().ok_or_else(|| {
2791                        DistributedError::InvalidPlan(format!(
2792                            "fragment {} is missing an exchange input stream",
2793                            fragment.fragment_id
2794                        ))
2795                    })?;
2796                    let frames = drain_stream(input, &control.control).await?;
2797                    batches.extend(frames.into_iter().map(|frame| frame.batch));
2798                }
2799                FragmentOperator::PartialAggregate {
2800                    group_by,
2801                    aggregates,
2802                } => {
2803                    batches = vec![partial_aggregate_batches(&batches, group_by, aggregates)?];
2804                }
2805                FragmentOperator::FinalAggregate {
2806                    group_by,
2807                    aggregates,
2808                } => {
2809                    batches = vec![final_aggregate_batches(&batches, group_by, aggregates)?];
2810                }
2811                FragmentOperator::MergeSort { keys, limit } => {
2812                    batches = sort_batches_local(&batches, keys, *limit)?;
2813                }
2814                FragmentOperator::DistributedTopK { k, score } => {
2815                    let tablet = tablet_of(fragment)?;
2816                    let input = prepare_top_k(&batches, &score.column, tablet)?;
2817                    let emit = self.topk_emit_batch.unwrap_or(*k).min(*k);
2818                    let (_rows, payload, next) = input.emit(0, emit);
2819                    batches = vec![payload];
2820                    bound = match next {
2821                        Some(next) => ScoreBound::AtMost(next),
2822                        None => ScoreBound::Exhausted,
2823                    };
2824                }
2825                FragmentOperator::DistributedLimit { limit } => {
2826                    batches = limit_batches(&batches, *limit);
2827                }
2828                FragmentOperator::RemoteExchangeSink { .. } => {
2829                    // Routing is the transport/coordinator's job; the sink is
2830                    // a pass-through here.
2831                }
2832                FragmentOperator::DistributedHashJoin { .. }
2833                | FragmentOperator::BroadcastJoin { .. }
2834                | FragmentOperator::RepartitionJoin { .. } => {
2835                    return Err(DistributedError::Unsupported(
2836                        "join execution binding lands with the tablet wave".to_owned(),
2837                    ));
2838                }
2839            }
2840        }
2841        let mut frames: Vec<BatchFrame> = batches.into_iter().map(BatchFrame::data).collect();
2842        if bound != ScoreBound::Unknown {
2843            if let Some(last) = frames.last_mut() {
2844                last.score_bound = bound;
2845            }
2846        }
2847        Ok(Box::pin(stream::iter(frames.into_iter().map(Ok))))
2848    }
2849
2850    fn refill_top_k(
2851        &self,
2852        fragment: &PlanFragment,
2853        offset: usize,
2854        limit: usize,
2855        control: FragmentControl,
2856    ) -> DistributedResult<TopKRefill> {
2857        let tablet = tablet_of(fragment)?;
2858        let score = fragment
2859            .operators
2860            .iter()
2861            .find_map(|operator| match operator {
2862                FragmentOperator::DistributedTopK { score, .. } => Some(score.clone()),
2863                _ => None,
2864            })
2865            .ok_or_else(|| {
2866                DistributedError::InvalidPlan(format!(
2867                    "fragment {} has no distributed top-k operator",
2868                    fragment.fragment_id
2869                ))
2870            })?;
2871        let batches = self.scan_batches(fragment, Some(&control))?;
2872        let input = prepare_top_k(&batches, &score.column, tablet)?;
2873        let (rows, payload, unseen_bound) = input.emit(offset, limit);
2874        Ok(TopKRefill {
2875            rows,
2876            payload,
2877            unseen_bound,
2878        })
2879    }
2880}
2881
2882// ---------------------------------------------------------------------------
2883// Shared execution helpers and real merge operators
2884// ---------------------------------------------------------------------------
2885
2886/// Maps an [`ExecutionControl`] checkpoint onto a distributed cancellation.
2887fn checkpoint(control: &ExecutionControl) -> DistributedResult<()> {
2888    control
2889        .checkpoint()
2890        .map_err(|_| DistributedError::Cancelled(control.reason()))
2891}
2892
2893/// The tablet a fragment is assigned to (errors for coordinator fragments).
2894fn tablet_of(fragment: &PlanFragment) -> DistributedResult<TabletId> {
2895    match fragment.assignment {
2896        FragmentAssignment::Tablet(tablet) => Ok(tablet),
2897        FragmentAssignment::Coordinator => Err(DistributedError::InvalidPlan(format!(
2898            "fragment {} operator requires a tablet assignment",
2899            fragment.fragment_id
2900        ))),
2901    }
2902}
2903
2904/// Drains a fragment stream with cooperative cancellation.
2905async fn drain_stream(
2906    mut stream: FragmentStream,
2907    control: &ExecutionControl,
2908) -> DistributedResult<Vec<BatchFrame>> {
2909    let mut frames = Vec::new();
2910    while let Some(item) = stream.next().await {
2911        checkpoint(control)?;
2912        frames.push(item?);
2913    }
2914    Ok(frames)
2915}
2916
2917/// Concatenates batches (`None` when the input is empty).
2918fn concat_all(batches: &[RecordBatch]) -> DistributedResult<Option<RecordBatch>> {
2919    let Some(first) = batches.first() else {
2920        return Ok(None);
2921    };
2922    if batches.len() == 1 {
2923        return Ok(Some(first.clone()));
2924    }
2925    Ok(Some(concat_batches(&first.schema(), batches)?))
2926}
2927
2928/// Projects every batch onto the named columns.
2929fn project_batches(
2930    batches: &[RecordBatch],
2931    projection: &[String],
2932) -> DistributedResult<Vec<RecordBatch>> {
2933    batches
2934        .iter()
2935        .map(|batch| {
2936            let schema = batch.schema();
2937            let indexes: Vec<usize> = projection
2938                .iter()
2939                .map(|name| {
2940                    schema.index_of(name).map_err(|_| {
2941                        DistributedError::InvalidPlan(format!(
2942                            "projection column `{name}` not in schema"
2943                        ))
2944                    })
2945                })
2946                .collect::<DistributedResult<Vec<usize>>>()?;
2947            Ok(batch.project(&indexes)?)
2948        })
2949        .collect()
2950}
2951
2952/// Builds a row converter + key column indexes for sort keys.
2953fn row_converter(
2954    schema: &Schema,
2955    keys: &[SortKey],
2956) -> DistributedResult<(RowConverter, Vec<usize>)> {
2957    let mut fields = Vec::with_capacity(keys.len());
2958    let mut indexes = Vec::with_capacity(keys.len());
2959    for key in keys {
2960        let index = schema.index_of(&key.column).map_err(|_| {
2961            DistributedError::InvalidPlan(format!("sort key `{}` not in schema", key.column))
2962        })?;
2963        // Groundwork null semantics: nulls sort first under descending keys
2964        // and last under ascending keys. The DataFusion lowering wave maps
2965        // explicit NULLS FIRST/LAST clauses onto this.
2966        let options = arrow::compute::SortOptions {
2967            descending: key.descending,
2968            nulls_first: key.descending,
2969        };
2970        fields.push(SortField::new_with_options(
2971            schema.field(index).data_type().clone(),
2972            options,
2973        ));
2974        indexes.push(index);
2975    }
2976    Ok((RowConverter::new(fields)?, indexes))
2977}
2978
2979/// Gathers `order`ed rows of one batch into chunked output batches.
2980fn take_rows(batch: &RecordBatch, order: &[usize]) -> DistributedResult<Vec<RecordBatch>> {
2981    let mut out = Vec::new();
2982    for chunk in order.chunks(COORDINATOR_OUTPUT_BATCH_ROWS) {
2983        let indices = UInt32Array::from(
2984            chunk
2985                .iter()
2986                .map(|index| u32::try_from(*index).unwrap_or(u32::MAX))
2987                .collect::<Vec<u32>>(),
2988        );
2989        let mut columns = Vec::with_capacity(batch.num_columns());
2990        for column in batch.columns() {
2991            columns.push(take(column, &indices, None)?);
2992        }
2993        out.push(RecordBatch::try_new(batch.schema(), columns)?);
2994    }
2995    if out.is_empty() {
2996        out.push(RecordBatch::new_empty(batch.schema()));
2997    }
2998    Ok(out)
2999}
3000
3001/// Interleaves rows from several same-schema streams in `order`
3002/// (`(stream, row)` pairs) into chunked output batches.
3003fn emit_interleaved(
3004    streams: &[RecordBatch],
3005    order: &[(usize, usize)],
3006) -> DistributedResult<Vec<RecordBatch>> {
3007    let Some(first) = streams.first() else {
3008        return Ok(Vec::new());
3009    };
3010    if order.is_empty() {
3011        return Ok(vec![RecordBatch::new_empty(first.schema())]);
3012    }
3013    let schema = first.schema();
3014    let mut out = Vec::new();
3015    for chunk in order.chunks(COORDINATOR_OUTPUT_BATCH_ROWS) {
3016        let mut columns = Vec::with_capacity(schema.fields().len());
3017        for column_index in 0..schema.fields().len() {
3018            let refs: Vec<&dyn Array> = streams
3019                .iter()
3020                .map(|batch| batch.column(column_index).as_ref())
3021                .collect();
3022            columns.push(interleave(&refs, chunk)?);
3023        }
3024        out.push(RecordBatch::try_new(schema.clone(), columns)?);
3025    }
3026    Ok(out)
3027}
3028
3029/// Producer-side local sort: full deterministic sort of the (unsorted)
3030/// input, optionally truncated to `limit` rows.
3031fn sort_batches_local(
3032    batches: &[RecordBatch],
3033    keys: &[SortKey],
3034    limit: Option<usize>,
3035) -> DistributedResult<Vec<RecordBatch>> {
3036    let Some(batch) = concat_all(batches)? else {
3037        return Ok(Vec::new());
3038    };
3039    if batch.num_rows() == 0 {
3040        return Ok(vec![RecordBatch::new_empty(batch.schema())]);
3041    }
3042    let (converter, indexes) = row_converter(&batch.schema(), keys)?;
3043    let columns: Vec<ArrayRef> = indexes
3044        .iter()
3045        .map(|index| batch.column(*index).clone())
3046        .collect();
3047    let rows = converter.convert_columns(&columns)?;
3048    let mut order: Vec<usize> = (0..batch.num_rows()).collect();
3049    order.sort_by(|left, right| {
3050        rows.row(*left)
3051            .cmp(&rows.row(*right))
3052            .then_with(|| left.cmp(right))
3053    });
3054    if let Some(limit) = limit {
3055        order.truncate(limit);
3056    }
3057    take_rows(&batch, &order)
3058}
3059
3060/// Coordinator-side merge sort: a deterministic k-way merge over streams
3061/// that are each already sorted on `keys` (ties break by stream index, so
3062/// the result is fully deterministic).
3063fn merge_sorted_streams(
3064    streams: &[RecordBatch],
3065    keys: &[SortKey],
3066    limit: Option<usize>,
3067) -> DistributedResult<Vec<RecordBatch>> {
3068    /// Min-heap entry (via `Reverse`): smallest key, then smallest stream.
3069    struct MergeItem {
3070        key: Vec<u8>,
3071        stream: usize,
3072    }
3073    impl PartialEq for MergeItem {
3074        fn eq(&self, other: &Self) -> bool {
3075            self.key == other.key && self.stream == other.stream
3076        }
3077    }
3078    impl Eq for MergeItem {}
3079    impl PartialOrd for MergeItem {
3080        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3081            Some(self.cmp(other))
3082        }
3083    }
3084    impl Ord for MergeItem {
3085        fn cmp(&self, other: &Self) -> Ordering {
3086            self.key
3087                .cmp(&other.key)
3088                .then_with(|| self.stream.cmp(&other.stream))
3089        }
3090    }
3091
3092    let mut per_stream: Vec<Vec<Vec<u8>>> = Vec::with_capacity(streams.len());
3093    for batch in streams {
3094        let (converter, indexes) = row_converter(&batch.schema(), keys)?;
3095        let columns: Vec<ArrayRef> = indexes
3096            .iter()
3097            .map(|index| batch.column(*index).clone())
3098            .collect();
3099        let rows = converter.convert_columns(&columns)?;
3100        per_stream.push(
3101            (0..batch.num_rows())
3102                .map(|row| rows.row(row).as_ref().to_vec())
3103                .collect(),
3104        );
3105    }
3106    let mut cursors: Vec<usize> = vec![0; streams.len()];
3107    let mut heap = BinaryHeap::new();
3108    for (stream, keys) in per_stream.iter().enumerate() {
3109        if let Some(key) = keys.first() {
3110            heap.push(std::cmp::Reverse(MergeItem {
3111                key: key.clone(),
3112                stream,
3113            }));
3114        }
3115    }
3116    let mut order = Vec::new();
3117    while let Some(std::cmp::Reverse(item)) = heap.pop() {
3118        let row = cursors[item.stream];
3119        order.push((item.stream, row));
3120        if limit.is_some_and(|limit| order.len() >= limit) {
3121            break;
3122        }
3123        cursors[item.stream] += 1;
3124        let cursor = cursors[item.stream];
3125        if let Some(key) = per_stream[item.stream].get(cursor) {
3126            heap.push(std::cmp::Reverse(MergeItem {
3127                key: key.clone(),
3128                stream: item.stream,
3129            }));
3130        }
3131    }
3132    emit_interleaved(streams, &order)
3133}
3134
3135/// Truncates batches to `limit` rows, preserving stream order.
3136fn limit_batches(batches: &[RecordBatch], limit: usize) -> Vec<RecordBatch> {
3137    let mut remaining = limit;
3138    let mut out = Vec::new();
3139    for batch in batches {
3140        if remaining == 0 {
3141            break;
3142        }
3143        let rows = batch.num_rows().min(remaining);
3144        if rows == 0 {
3145            continue;
3146        }
3147        out.push(if rows == batch.num_rows() {
3148            batch.clone()
3149        } else {
3150            batch.slice(0, rows)
3151        });
3152        remaining -= rows;
3153    }
3154    out
3155}
3156
3157/// Routes a producer's output across the sibling consumers of a
3158/// hash-repartition boundary: rows whose FNV-1a key hash modulo `width`
3159/// equals `index`.
3160fn repartition_frames(
3161    frames: &[BatchFrame],
3162    keys: &[String],
3163    width: usize,
3164    index: usize,
3165) -> DistributedResult<Vec<BatchFrame>> {
3166    let batches: Vec<RecordBatch> = frames.iter().map(|frame| frame.batch.clone()).collect();
3167    let Some(batch) = concat_all(&batches)? else {
3168        return Ok(Vec::new());
3169    };
3170    if batch.num_rows() == 0 || width <= 1 {
3171        return Ok(vec![BatchFrame::data(batch)]);
3172    }
3173    let schema = batch.schema();
3174    let mut key_columns = Vec::with_capacity(keys.len());
3175    for key in keys {
3176        let column_index = schema.index_of(key).map_err(|_| {
3177            DistributedError::InvalidPlan(format!("repartition key `{key}` not in schema"))
3178        })?;
3179        key_columns.push(batch.column(column_index).clone());
3180    }
3181    let converter = RowConverter::new(
3182        key_columns
3183            .iter()
3184            .map(|column| SortField::new(column.data_type().clone()))
3185            .collect::<Vec<SortField>>(),
3186    )?;
3187    let rows = converter.convert_columns(&key_columns)?;
3188    let mut order = Vec::new();
3189    for row in 0..batch.num_rows() {
3190        let bucket = (fnv1a64(rows.row(row).as_ref()) % width as u64) as usize;
3191        if bucket == index {
3192            order.push(row);
3193        }
3194    }
3195    Ok(take_rows(&batch, &order)?
3196        .into_iter()
3197        .map(BatchFrame::data)
3198        .collect())
3199}
3200
3201/// Maps one numeric score cell onto an order-preserving `u64` key (higher
3202/// sorts better). Null scores map to the minimum key.
3203fn score_key(array: &dyn Array, row: usize) -> DistributedResult<u64> {
3204    if array.is_null(row) {
3205        return Ok(0);
3206    }
3207    match array.data_type() {
3208        DataType::UInt64 => Ok(array
3209            .as_any()
3210            .downcast_ref::<UInt64Array>()
3211            .expect("type checked")
3212            .value(row)),
3213        DataType::Int64 => {
3214            let value = array
3215                .as_any()
3216                .downcast_ref::<Int64Array>()
3217                .expect("type checked")
3218                .value(row);
3219            Ok((value as u64) ^ (1_u64 << 63))
3220        }
3221        DataType::Float64 => {
3222            let bits = array
3223                .as_any()
3224                .downcast_ref::<Float64Array>()
3225                .expect("type checked")
3226                .value(row)
3227                .to_bits();
3228            Ok(if bits & (1_u64 << 63) != 0 {
3229                !bits
3230            } else {
3231                bits | (1_u64 << 63)
3232            })
3233        }
3234        other => Err(DistributedError::Unsupported(format!(
3235            "top-k score column of type {other} is not supported in this wave"
3236        ))),
3237    }
3238}
3239
3240/// Reads the `__rowid` column of a top-k batch.
3241fn row_ids(batch: &RecordBatch) -> DistributedResult<UInt64Array> {
3242    let index = batch.schema().index_of(TOPK_ROWID_COLUMN).map_err(|_| {
3243        DistributedError::InvalidPlan(format!(
3244            "top-k stream is missing the `{TOPK_ROWID_COLUMN}` column"
3245        ))
3246    })?;
3247    let array = batch.column(index);
3248    if array.data_type() != &DataType::UInt64 {
3249        return Err(DistributedError::InvalidPlan(format!(
3250            "`{TOPK_ROWID_COLUMN}` must be UInt64, found {}",
3251            array.data_type()
3252        )));
3253    }
3254    Ok(array
3255        .as_any()
3256        .downcast_ref::<UInt64Array>()
3257        .expect("type checked")
3258        .clone())
3259}
3260
3261/// A top-k input fully prepared for bounded emission: the concatenated
3262/// payload plus the globally sorted candidate list.
3263struct TopKInput {
3264    batch: RecordBatch,
3265    /// Candidates, best first, aligned with `positions`.
3266    candidates: Vec<TopKCandidate>,
3267    /// Row index in `batch` of each candidate.
3268    positions: Vec<usize>,
3269}
3270
3271impl TopKInput {
3272    /// Emits candidates `[offset, offset + limit)`: the candidates
3273    /// themselves, their payload rows, and the tightened unseen bound
3274    /// (`None` when nothing remains).
3275    fn emit(&self, offset: usize, limit: usize) -> (Vec<TopKCandidate>, RecordBatch, Option<u64>) {
3276        let offset = offset.min(self.candidates.len());
3277        let end = (offset + limit).min(self.candidates.len());
3278        let rows = self.candidates[offset..end].to_vec();
3279        let positions = &self.positions[offset..end];
3280        let payload = take_rows(&self.batch, positions)
3281            .unwrap_or_else(|_| vec![RecordBatch::new_empty(self.batch.schema())])
3282            .into_iter()
3283            .next()
3284            .unwrap_or_else(|| RecordBatch::new_empty(self.batch.schema()));
3285        let bound = self.candidates.get(end).map(|candidate| candidate.score);
3286        (rows, payload, bound)
3287    }
3288}
3289
3290/// Builds the sorted candidate list of a top-k input.
3291fn prepare_top_k(
3292    batches: &[RecordBatch],
3293    score_column: &str,
3294    tablet: TabletId,
3295) -> DistributedResult<TopKInput> {
3296    let Some(batch) = concat_all(batches)? else {
3297        return Err(DistributedError::InvalidPlan(
3298            "top-k input has no batches".to_owned(),
3299        ));
3300    };
3301    let schema = batch.schema();
3302    let score_index = schema.index_of(score_column).map_err(|_| {
3303        DistributedError::InvalidPlan(format!("top-k score column `{score_column}` not in schema"))
3304    })?;
3305    let score_array = batch.column(score_index).clone();
3306    let row_id_array = row_ids(&batch)?;
3307    let mut candidates = Vec::with_capacity(batch.num_rows());
3308    for row in 0..batch.num_rows() {
3309        candidates.push(TopKCandidate {
3310            score: score_key(score_array.as_ref(), row)?,
3311            tablet,
3312            row_id: RowId(row_id_array.value(row)),
3313        });
3314    }
3315    let mut positions: Vec<usize> = (0..candidates.len()).collect();
3316    positions.sort_by(|left, right| topk_cmp(&candidates[*left], &candidates[*right]));
3317    let candidates = positions.iter().map(|index| candidates[*index]).collect();
3318    Ok(TopKInput {
3319        batch,
3320        candidates,
3321        positions,
3322    })
3323}
3324
3325// ---------------------------------------------------------------------------
3326// Aggregation combine (partial + final)
3327// ---------------------------------------------------------------------------
3328
3329/// One numeric cell (the groundwork supports Int64 and Float64 aggregate
3330/// inputs).
3331#[derive(Clone, Copy, Debug)]
3332enum AggValue {
3333    I64(i64),
3334    F64(f64),
3335}
3336
3337/// Reads one numeric cell (`None` for nulls).
3338fn numeric_cell(array: &dyn Array, row: usize) -> DistributedResult<Option<AggValue>> {
3339    if array.is_null(row) {
3340        return Ok(None);
3341    }
3342    match array.data_type() {
3343        DataType::Int64 => Ok(Some(AggValue::I64(
3344            array
3345                .as_any()
3346                .downcast_ref::<Int64Array>()
3347                .expect("type checked")
3348                .value(row),
3349        ))),
3350        DataType::Float64 => Ok(Some(AggValue::F64(
3351            array
3352                .as_any()
3353                .downcast_ref::<Float64Array>()
3354                .expect("type checked")
3355                .value(row),
3356        ))),
3357        other => Err(DistributedError::Unsupported(format!(
3358            "aggregate over {other} is not supported in this wave"
3359        ))),
3360    }
3361}
3362
3363/// Per-group accumulator for one aggregate expression.
3364#[derive(Clone, Debug)]
3365enum AggAccum {
3366    Count(i64),
3367    SumI(i128, bool),
3368    SumF(f64, bool),
3369    MinI(i64, bool),
3370    MinF(f64, bool),
3371    MaxI(i64, bool),
3372    MaxF(f64, bool),
3373    Avg { sum: f64, count: i64 },
3374}
3375
3376impl AggAccum {
3377    /// A fresh accumulator for `function` over a value column of type
3378    /// `float` (`true` = Float64, `false` = Int64).
3379    fn fresh(function: AggregateFunction, float: bool) -> Self {
3380        match function {
3381            AggregateFunction::Count => Self::Count(0),
3382            AggregateFunction::Sum if float => Self::SumF(0.0, false),
3383            AggregateFunction::Sum => Self::SumI(0, false),
3384            AggregateFunction::Min if float => Self::MinF(f64::INFINITY, false),
3385            AggregateFunction::Min => Self::MinI(i64::MAX, false),
3386            AggregateFunction::Max if float => Self::MaxF(f64::NEG_INFINITY, false),
3387            AggregateFunction::Max => Self::MaxI(i64::MIN, false),
3388            AggregateFunction::Avg => Self::Avg { sum: 0.0, count: 0 },
3389        }
3390    }
3391
3392    /// Folds one value cell (shared by partial folds and final combines).
3393    fn fold_value(&mut self, cell: Option<AggValue>) {
3394        match (self, cell) {
3395            (Self::SumI(sum, seen), Some(AggValue::I64(value))) => {
3396                *sum += i128::from(value);
3397                *seen = true;
3398            }
3399            (Self::SumF(sum, seen), Some(AggValue::F64(value))) => {
3400                *sum += value;
3401                *seen = true;
3402            }
3403            (Self::MinI(min, seen), Some(AggValue::I64(value))) => {
3404                *min = (*min).min(value);
3405                *seen = true;
3406            }
3407            (Self::MinF(min, seen), Some(AggValue::F64(value))) => {
3408                *min = min.min(value);
3409                *seen = true;
3410            }
3411            (Self::MaxI(max, seen), Some(AggValue::I64(value))) => {
3412                *max = (*max).max(value);
3413                *seen = true;
3414            }
3415            (Self::MaxF(max, seen), Some(AggValue::F64(value))) => {
3416                *max = max.max(value);
3417                *seen = true;
3418            }
3419            (Self::Avg { sum, count }, Some(AggValue::I64(value))) => {
3420                *sum += value as f64;
3421                *count += 1;
3422            }
3423            (Self::Avg { sum, count }, Some(AggValue::F64(value))) => {
3424                *sum += value;
3425                *count += 1;
3426            }
3427            _ => {}
3428        }
3429    }
3430}
3431
3432/// One folded group.
3433struct GroupEntry {
3434    /// Global row (in the concatenated input) whose key columns represent
3435    /// this group in the output.
3436    first_row: usize,
3437    accums: Vec<AggAccum>,
3438}
3439
3440/// Groups in first-seen order (deterministic for a fixed input order).
3441#[derive(Default)]
3442struct GroupFold {
3443    order: Vec<String>,
3444    groups: HashMap<String, GroupEntry>,
3445}
3446
3447impl GroupFold {
3448    fn entry(&mut self, key: String, row: usize, templates: &[AggAccum]) -> &mut GroupEntry {
3449        if !self.groups.contains_key(&key) {
3450            self.order.push(key.clone());
3451            self.groups.insert(
3452                key.clone(),
3453                GroupEntry {
3454                    first_row: row,
3455                    accums: templates.to_vec(),
3456                },
3457            );
3458        }
3459        self.groups.get_mut(&key).expect("inserted above")
3460    }
3461}
3462
3463/// The deterministic group key of one row (display form of the key columns).
3464fn group_key(batch: &RecordBatch, key_indexes: &[usize], row: usize) -> DistributedResult<String> {
3465    let mut parts = Vec::with_capacity(key_indexes.len());
3466    for index in key_indexes {
3467        parts.push(array_value_to_string(batch.column(*index).as_ref(), row)?);
3468    }
3469    Ok(parts.join("\u{1f}"))
3470}
3471
3472/// Resolves group-by column indexes.
3473fn resolve_columns(schema: &Schema, columns: &[String]) -> DistributedResult<Vec<usize>> {
3474    columns
3475        .iter()
3476        .map(|column| {
3477            schema.index_of(column).map_err(|_| {
3478                DistributedError::InvalidPlan(format!("group-by column `{column}` not in schema"))
3479            })
3480        })
3481        .collect()
3482}
3483
3484/// The value-column type marker of one aggregate (`true` = Float64).
3485fn aggregate_float(schema: &Schema, aggregate: &AggregateExpr) -> DistributedResult<bool> {
3486    match &aggregate.column {
3487        None => Ok(false),
3488        Some(column) => {
3489            let index = schema.index_of(column).map_err(|_| {
3490                DistributedError::InvalidPlan(format!("aggregate column `{column}` not in schema"))
3491            })?;
3492            match schema.field(index).data_type() {
3493                DataType::Int64 => Ok(false),
3494                DataType::Float64 => Ok(true),
3495                other => Err(DistributedError::Unsupported(format!(
3496                    "aggregate over {other} is not supported in this wave"
3497                ))),
3498            }
3499        }
3500    }
3501}
3502
3503/// Gathers group key columns at each group's first-seen row.
3504fn take_group_keys(
3505    batch: &RecordBatch,
3506    key_indexes: &[usize],
3507    fold: &GroupFold,
3508) -> DistributedResult<Vec<ArrayRef>> {
3509    if key_indexes.is_empty() {
3510        return Ok(Vec::new());
3511    }
3512    let indices = UInt32Array::from(
3513        fold.order
3514            .iter()
3515            .map(|key| u32::try_from(fold.groups[key].first_row).unwrap_or(u32::MAX))
3516            .collect::<Vec<u32>>(),
3517    );
3518    let mut columns = Vec::with_capacity(key_indexes.len());
3519    for index in key_indexes {
3520        columns.push(take(batch.column(*index), &indices, None)?);
3521    }
3522    Ok(columns)
3523}
3524
3525/// Emits one integer accumulator column (partial or final).
3526fn emit_int_accum(groups: &GroupFold, index: usize) -> DistributedResult<Vec<Option<i64>>> {
3527    let mut values = Vec::with_capacity(groups.order.len());
3528    for key in &groups.order {
3529        let accum = &groups.groups[key].accums[index];
3530        values.push(match accum {
3531            AggAccum::Count(count) => Some(*count),
3532            AggAccum::SumI(sum, seen) => seen
3533                .then(|| {
3534                    i64::try_from(*sum).map_err(|_| {
3535                        DistributedError::Unsupported(
3536                            "integer sum overflow in this wave".to_owned(),
3537                        )
3538                    })
3539                })
3540                .transpose()?,
3541            AggAccum::MinI(value, seen) | AggAccum::MaxI(value, seen) => seen.then_some(*value),
3542            other => {
3543                return Err(DistributedError::InvalidPlan(format!(
3544                    "internal: accumulator {other:?} is not an integer column"
3545                )))
3546            }
3547        });
3548    }
3549    Ok(values)
3550}
3551
3552/// Emits one float accumulator column (partial or final).
3553fn emit_float_accum(groups: &GroupFold, index: usize) -> DistributedResult<Vec<Option<f64>>> {
3554    let mut values = Vec::with_capacity(groups.order.len());
3555    for key in &groups.order {
3556        let accum = &groups.groups[key].accums[index];
3557        values.push(match accum {
3558            AggAccum::SumF(sum, seen) => seen.then_some(*sum),
3559            AggAccum::MinF(value, seen) | AggAccum::MaxF(value, seen) => seen.then_some(*value),
3560            other => {
3561                return Err(DistributedError::InvalidPlan(format!(
3562                    "internal: accumulator {other:?} is not a float column"
3563                )))
3564            }
3565        });
3566    }
3567    Ok(values)
3568}
3569
3570/// Per-tablet partial aggregation (the producer half of the two-phase
3571/// aggregate, spec section 12.10).
3572fn partial_aggregate_batches(
3573    batches: &[RecordBatch],
3574    group_by: &[String],
3575    aggregates: &[AggregateExpr],
3576) -> DistributedResult<RecordBatch> {
3577    let Some(batch) = concat_all(batches)? else {
3578        return Err(DistributedError::InvalidPlan(
3579            "aggregate input has no batches".to_owned(),
3580        ));
3581    };
3582    let schema = batch.schema();
3583    let key_indexes = resolve_columns(&schema, group_by)?;
3584    let mut value_indexes = Vec::with_capacity(aggregates.len());
3585    let mut templates = Vec::with_capacity(aggregates.len());
3586    for aggregate in aggregates {
3587        let float = aggregate_float(&schema, aggregate)?;
3588        value_indexes.push(match &aggregate.column {
3589            Some(column) => Some(schema.index_of(column).map_err(|_| {
3590                DistributedError::InvalidPlan(format!("aggregate column `{column}` not in schema"))
3591            })?),
3592            None => None,
3593        });
3594        templates.push(AggAccum::fresh(aggregate.function, float));
3595    }
3596    let mut fold = GroupFold::default();
3597    for row in 0..batch.num_rows() {
3598        let key = group_key(&batch, &key_indexes, row)?;
3599        let entry = fold.entry(key, row, &templates);
3600        for (index, aggregate) in aggregates.iter().enumerate() {
3601            match aggregate.function {
3602                AggregateFunction::Count => {
3603                    let counted = match value_indexes[index] {
3604                        Some(column) => !batch.column(column).is_null(row),
3605                        None => true,
3606                    };
3607                    if counted {
3608                        let AggAccum::Count(count) = &mut entry.accums[index] else {
3609                            unreachable!("count template");
3610                        };
3611                        *count += 1;
3612                    }
3613                }
3614                _ => {
3615                    let cell = match value_indexes[index] {
3616                        Some(column) => numeric_cell(batch.column(column).as_ref(), row)?,
3617                        None => None,
3618                    };
3619                    entry.accums[index].fold_value(cell);
3620                }
3621            }
3622        }
3623    }
3624    // Emit: key columns at first-seen rows, then partial columns.
3625    let mut columns = take_group_keys(&batch, &key_indexes, &fold)?;
3626    let mut fields: Vec<Field> = key_indexes
3627        .iter()
3628        .map(|index| schema.field(*index).clone())
3629        .collect();
3630    for (index, aggregate) in aggregates.iter().enumerate() {
3631        match aggregate.function {
3632            AggregateFunction::Avg => {
3633                let mut sums = Vec::with_capacity(fold.order.len());
3634                let mut counts = Vec::with_capacity(fold.order.len());
3635                for key in &fold.order {
3636                    let AggAccum::Avg { sum, count } = &fold.groups[key].accums[index] else {
3637                        unreachable!("avg template");
3638                    };
3639                    sums.push(Some(*sum));
3640                    counts.push(Some(*count));
3641                }
3642                fields.push(Field::new(
3643                    format!("__partial_{index}_sum"),
3644                    DataType::Float64,
3645                    true,
3646                ));
3647                columns.push(Arc::new(Float64Array::from(sums)));
3648                fields.push(Field::new(
3649                    format!("__partial_{index}_count"),
3650                    DataType::Int64,
3651                    true,
3652                ));
3653                columns.push(Arc::new(Int64Array::from(counts)));
3654            }
3655            AggregateFunction::Count => {
3656                fields.push(Field::new(
3657                    format!("__partial_{index}"),
3658                    DataType::Int64,
3659                    true,
3660                ));
3661                columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
3662            }
3663            _ => match &templates[index] {
3664                AggAccum::SumI(..) | AggAccum::MinI(..) | AggAccum::MaxI(..) => {
3665                    fields.push(Field::new(
3666                        format!("__partial_{index}"),
3667                        DataType::Int64,
3668                        true,
3669                    ));
3670                    columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
3671                }
3672                AggAccum::SumF(..) | AggAccum::MinF(..) | AggAccum::MaxF(..) => {
3673                    fields.push(Field::new(
3674                        format!("__partial_{index}"),
3675                        DataType::Float64,
3676                        true,
3677                    ));
3678                    columns.push(Arc::new(Float64Array::from(emit_float_accum(
3679                        &fold, index,
3680                    )?)));
3681                }
3682                other => {
3683                    return Err(DistributedError::InvalidPlan(format!(
3684                        "internal: unexpected accumulator {other:?}"
3685                    )))
3686                }
3687            },
3688        }
3689    }
3690    Ok(RecordBatch::try_new(Schema::new(fields).into(), columns)?)
3691}
3692
3693/// Coordinator-side combine of partial aggregates into final results (spec
3694/// section 12.10).
3695fn final_aggregate_batches(
3696    batches: &[RecordBatch],
3697    group_by: &[String],
3698    aggregates: &[AggregateExpr],
3699) -> DistributedResult<RecordBatch> {
3700    let Some(batch) = concat_all(batches)? else {
3701        return Err(DistributedError::InvalidPlan(
3702            "aggregate input has no batches".to_owned(),
3703        ));
3704    };
3705    let schema = batch.schema();
3706    let key_indexes = resolve_columns(&schema, group_by)?;
3707    // Resolve the partial columns produced by `partial_aggregate_batches`.
3708    let mut partial_indexes: Vec<(usize, Option<usize>)> = Vec::with_capacity(aggregates.len());
3709    let mut templates = Vec::with_capacity(aggregates.len());
3710    for (index, aggregate) in aggregates.iter().enumerate() {
3711        let value_name = if aggregate.function == AggregateFunction::Avg {
3712            format!("__partial_{index}_sum")
3713        } else {
3714            format!("__partial_{index}")
3715        };
3716        let value_index = schema.index_of(&value_name).map_err(|_| {
3717            DistributedError::InvalidPlan(format!(
3718                "partial aggregate column `{value_name}` not in schema"
3719            ))
3720        })?;
3721        let float = match schema.field(value_index).data_type() {
3722            DataType::Int64 => false,
3723            DataType::Float64 => true,
3724            other => {
3725                return Err(DistributedError::Unsupported(format!(
3726                    "aggregate partial of type {other} is not supported in this wave"
3727                )))
3728            }
3729        };
3730        templates.push(AggAccum::fresh(aggregate.function, float));
3731        let count_index = if aggregate.function == AggregateFunction::Avg {
3732            let count_name = format!("__partial_{index}_count");
3733            Some(schema.index_of(&count_name).map_err(|_| {
3734                DistributedError::InvalidPlan(format!(
3735                    "partial aggregate column `{count_name}` not in schema"
3736                ))
3737            })?)
3738        } else {
3739            None
3740        };
3741        partial_indexes.push((value_index, count_index));
3742    }
3743    let mut fold = GroupFold::default();
3744    for row in 0..batch.num_rows() {
3745        let key = group_key(&batch, &key_indexes, row)?;
3746        let entry = fold.entry(key, row, &templates);
3747        for (index, aggregate) in aggregates.iter().enumerate() {
3748            let (value_index, count_index) = partial_indexes[index];
3749            match aggregate.function {
3750                AggregateFunction::Count => {
3751                    if let Some(AggValue::I64(value)) =
3752                        numeric_cell(batch.column(value_index).as_ref(), row)?
3753                    {
3754                        let AggAccum::Count(count) = &mut entry.accums[index] else {
3755                            unreachable!("count template");
3756                        };
3757                        *count += value;
3758                    }
3759                }
3760                AggregateFunction::Avg => {
3761                    let sum = numeric_cell(batch.column(value_index).as_ref(), row)?;
3762                    let count = match count_index {
3763                        Some(count_index) => numeric_cell(batch.column(count_index).as_ref(), row)?,
3764                        None => None,
3765                    };
3766                    let AggAccum::Avg {
3767                        sum: total,
3768                        count: rows,
3769                    } = &mut entry.accums[index]
3770                    else {
3771                        unreachable!("avg template");
3772                    };
3773                    match sum {
3774                        Some(AggValue::F64(value)) => *total += value,
3775                        Some(AggValue::I64(value)) => *total += value as f64,
3776                        None => {}
3777                    }
3778                    if let Some(AggValue::I64(value)) = count {
3779                        *rows += value;
3780                    }
3781                }
3782                _ => {
3783                    let cell = numeric_cell(batch.column(value_index).as_ref(), row)?;
3784                    entry.accums[index].fold_value(cell);
3785                }
3786            }
3787        }
3788    }
3789    // SQL semantics: an empty input with no group-by still yields one row.
3790    if fold.order.is_empty() && group_by.is_empty() {
3791        fold.entry(String::new(), 0, &templates);
3792    }
3793    // Emit: key columns at first-seen rows, then one column per aggregate.
3794    let mut columns = take_group_keys(&batch, &key_indexes, &fold)?;
3795    let mut fields: Vec<Field> = key_indexes
3796        .iter()
3797        .map(|index| schema.field(*index).clone())
3798        .collect();
3799    for (index, aggregate) in aggregates.iter().enumerate() {
3800        let name = aggregate_output_name(aggregate);
3801        match &templates[index] {
3802            AggAccum::Count(_) | AggAccum::SumI(..) | AggAccum::MinI(..) | AggAccum::MaxI(..) => {
3803                fields.push(Field::new(name, DataType::Int64, true));
3804                columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
3805            }
3806            AggAccum::SumF(..) | AggAccum::MinF(..) | AggAccum::MaxF(..) => {
3807                fields.push(Field::new(name, DataType::Float64, true));
3808                columns.push(Arc::new(Float64Array::from(emit_float_accum(
3809                    &fold, index,
3810                )?)));
3811            }
3812            AggAccum::Avg { .. } => {
3813                let values: Vec<Option<f64>> = fold
3814                    .order
3815                    .iter()
3816                    .map(|key| match &fold.groups[key].accums[index] {
3817                        AggAccum::Avg { sum, count } => {
3818                            (*count > 0).then_some(*sum / *count as f64)
3819                        }
3820                        _ => unreachable!("avg template"),
3821                    })
3822                    .collect();
3823                fields.push(Field::new(name, DataType::Float64, true));
3824                columns.push(Arc::new(Float64Array::from(values)));
3825            }
3826        }
3827    }
3828    Ok(RecordBatch::try_new(Schema::new(fields).into(), columns)?)
3829}
3830
3831// ---------------------------------------------------------------------------
3832// Coordinator runtime (spec section 12.10)
3833// ---------------------------------------------------------------------------
3834
3835/// Per-query fragment resource ledger. Reservations are RAII: dropping a
3836/// [`ResourcePermit`] releases its share.
3837#[derive(Debug)]
3838pub struct ResourceLedger {
3839    state: Mutex<LedgerState>,
3840    max_fragments: usize,
3841    max_bytes: u64,
3842}
3843
3844#[derive(Default, Debug)]
3845struct LedgerState {
3846    fragments: usize,
3847    bytes: u64,
3848}
3849
3850/// A held resource reservation; released on drop.
3851#[derive(Debug)]
3852pub struct ResourcePermit {
3853    ledger: Arc<ResourceLedger>,
3854    bytes: u64,
3855}
3856
3857impl Drop for ResourcePermit {
3858    fn drop(&mut self) {
3859        let mut state = self.ledger.state.lock();
3860        state.fragments = state.fragments.saturating_sub(1);
3861        state.bytes = state.bytes.saturating_sub(self.bytes);
3862    }
3863}
3864
3865impl ResourceLedger {
3866    /// A ledger admitting at most `max_fragments` concurrent fragments and
3867    /// `max_bytes` total estimated bytes.
3868    pub fn new(max_fragments: usize, max_bytes: u64) -> Self {
3869        Self {
3870            state: Mutex::new(LedgerState::default()),
3871            max_fragments,
3872            max_bytes,
3873        }
3874    }
3875
3876    /// Reserves one fragment's estimated resources (spec section 12.10:
3877    /// "workers reserve resources").
3878    pub fn reserve(self: &Arc<Self>, fragment: &PlanFragment) -> DistributedResult<ResourcePermit> {
3879        let mut state = self.state.lock();
3880        if state.fragments >= self.max_fragments {
3881            return Err(DistributedError::Reservation {
3882                fragment_id: fragment.fragment_id,
3883                reason: format!("fragment concurrency limit {} reached", self.max_fragments),
3884            });
3885        }
3886        if state.bytes.saturating_add(fragment.estimated_bytes) > self.max_bytes {
3887            return Err(DistributedError::Reservation {
3888                fragment_id: fragment.fragment_id,
3889                reason: format!(
3890                    "estimated bytes {} exceed the {} byte budget",
3891                    state.bytes.saturating_add(fragment.estimated_bytes),
3892                    self.max_bytes
3893                ),
3894            });
3895        }
3896        state.fragments += 1;
3897        state.bytes = state.bytes.saturating_add(fragment.estimated_bytes);
3898        Ok(ResourcePermit {
3899            ledger: Arc::clone(self),
3900            bytes: fragment.estimated_bytes,
3901        })
3902    }
3903
3904    /// Currently reserved fragment count.
3905    pub fn reserved_fragments(&self) -> usize {
3906        self.state.lock().fragments
3907    }
3908
3909    /// Currently reserved estimated bytes.
3910    pub fn reserved_bytes(&self) -> u64 {
3911        self.state.lock().bytes
3912    }
3913}
3914
3915/// Worker lease ledger (spec section 12.10: "worker lease expiry cleans
3916/// abandoned fragments"). Workers are keyed by the tablet whose data they
3917/// serve this wave; the node-level binding lands with the transport wave.
3918#[derive(Default)]
3919pub struct LeaseLedger {
3920    leases: Mutex<HashMap<TabletId, Instant>>,
3921}
3922
3923impl LeaseLedger {
3924    /// Renews a worker's lease to `expiry`.
3925    pub fn renew(&self, worker: TabletId, expiry: Instant) {
3926        self.leases.lock().insert(worker, expiry);
3927    }
3928
3929    /// A worker's current lease expiry.
3930    pub fn expiry(&self, worker: &TabletId) -> Option<Instant> {
3931        self.leases.lock().get(worker).copied()
3932    }
3933
3934    /// Removes and returns every worker whose lease expired at or before
3935    /// `now`.
3936    pub fn sweep(&self, now: Instant) -> Vec<TabletId> {
3937        let mut leases = self.leases.lock();
3938        let expired: Vec<TabletId> = leases
3939            .iter()
3940            .filter(|(_, expiry)| **expiry <= now)
3941            .map(|(worker, _)| *worker)
3942            .collect();
3943        for worker in &expired {
3944            leases.remove(worker);
3945        }
3946        expired
3947    }
3948}
3949
3950/// One in-flight fragment of a running query.
3951struct InFlight {
3952    worker: Option<TabletId>,
3953    control: ExecutionControl,
3954    _permit: ResourcePermit,
3955}
3956
3957/// Per-query execution state tracked by the coordinator.
3958struct ExecutionState {
3959    query_id: QueryId,
3960    in_flight: Mutex<HashMap<FragmentId, InFlight>>,
3961}
3962
3963impl ExecutionState {
3964    fn new(query_id: QueryId) -> Self {
3965        Self {
3966            query_id,
3967            in_flight: Mutex::new(HashMap::new()),
3968        }
3969    }
3970}
3971
3972/// One producer stream feeding a coordinator (root) fragment.
3973struct ProducerInput {
3974    fragment_id: FragmentId,
3975    tablet: Option<TabletId>,
3976    frames: Vec<BatchFrame>,
3977}
3978
3979/// The root fragment's working data: either per-producer streams (fresh from
3980/// the exchange edges) or already-combined batches (after a coordinator
3981/// operator ran).
3982enum RootData {
3983    Streams(Vec<ProducerInput>),
3984    Batches(Vec<RecordBatch>),
3985}
3986
3987impl RootData {
3988    /// All payload batches, in stream order.
3989    fn flatten(&self) -> Vec<RecordBatch> {
3990        match self {
3991            Self::Streams(inputs) => inputs
3992                .iter()
3993                .flat_map(|input| input.frames.iter().map(|frame| frame.batch.clone()))
3994                .collect(),
3995            Self::Batches(batches) => batches.clone(),
3996        }
3997    }
3998}
3999
4000/// The query coordinator (spec section 12.10): registers the query with the
4001/// existing [`SqlQueryRegistry`] (so `registry.cancel(...)` reaches every
4002/// fragment through the [`ExecutionControl`] hierarchy), reserves resources
4003/// per fragment, fans out cancellation to every fragment, sweeps expired
4004/// worker leases to clean abandoned fragments, and merges producer streams
4005/// per the exchange descriptors with the real in-memory operators.
4006pub struct Coordinator {
4007    transport: Arc<dyn FragmentTransport>,
4008    registry: Arc<SqlQueryRegistry>,
4009    resources: Arc<ResourceLedger>,
4010    leases: LeaseLedger,
4011    lease_ttl: Duration,
4012    executions: Mutex<HashMap<QueryId, Arc<ExecutionState>>>,
4013}
4014
4015impl Coordinator {
4016    /// A coordinator with default limits (1024 concurrent fragments, 16 GiB
4017    /// of estimated bytes, 30 second worker leases).
4018    pub fn new(transport: Arc<dyn FragmentTransport>, registry: Arc<SqlQueryRegistry>) -> Self {
4019        Self::with_limits(
4020            transport,
4021            registry,
4022            1_024,
4023            16 * 1024 * 1024 * 1024,
4024            Duration::from_secs(30),
4025        )
4026    }
4027
4028    /// A coordinator with explicit resource limits and lease TTL.
4029    pub fn with_limits(
4030        transport: Arc<dyn FragmentTransport>,
4031        registry: Arc<SqlQueryRegistry>,
4032        max_fragments: usize,
4033        max_bytes: u64,
4034        lease_ttl: Duration,
4035    ) -> Self {
4036        Self {
4037            transport,
4038            registry,
4039            resources: Arc::new(ResourceLedger::new(max_fragments, max_bytes)),
4040            leases: LeaseLedger::default(),
4041            lease_ttl,
4042            executions: Mutex::new(HashMap::new()),
4043        }
4044    }
4045
4046    /// The fragment resource ledger (test introspection).
4047    pub fn resources(&self) -> &Arc<ResourceLedger> {
4048        &self.resources
4049    }
4050
4051    /// Executes a distributed plan to completion, returning the root
4052    /// fragment's output batches.
4053    ///
4054    /// The plan's [`QueryId`] is registered with the query registry first;
4055    /// every fragment runs under a child [`ExecutionControl`] of that
4056    /// registration, so a registry-level cancel fans out to all fragments.
4057    /// Fragments execute layer by layer (producers before consumers),
4058    /// concurrently within a layer. The coordinator-local root fragment's
4059    /// merge operators ([`FragmentOperator::MergeSort`],
4060    /// [`FragmentOperator::FinalAggregate`],
4061    /// [`FragmentOperator::DistributedTopK`],
4062    /// [`FragmentOperator::DistributedLimit`]) run over the producer streams
4063    /// for real.
4064    pub async fn execute(&self, plan: &DistributedPlan) -> DistributedResult<Vec<RecordBatch>> {
4065        self.execute_with_authorization(plan, &[]).await
4066    }
4067
4068    /// Executes a plan while forwarding a bounded, server-issued
4069    /// authorization envelope to every tablet worker.
4070    pub async fn execute_with_authorization(
4071        &self,
4072        plan: &DistributedPlan,
4073        authorization_context: &[u8],
4074    ) -> DistributedResult<Vec<RecordBatch>> {
4075        if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
4076            return Err(DistributedError::InvalidPlan(format!(
4077                "fragment authorization context is {} bytes; limit is {}",
4078                authorization_context.len(),
4079                MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
4080            )));
4081        }
4082        let registry_id = to_registry_query_id(&plan.query_id)?;
4083        let registered = self
4084            .registry
4085            .register(SqlQueryOptions {
4086                query_id: Some(registry_id),
4087                ..Default::default()
4088            })
4089            .map_err(|error| {
4090                DistributedError::InvalidPlan(format!("query registration failed: {error}"))
4091            })?;
4092        let result = self
4093            .execute_registered(plan, &registered, authorization_context.into())
4094            .await;
4095        match &result {
4096            Ok(_) => {
4097                let _ = registered.try_complete();
4098            }
4099            Err(_) => registered.fail(),
4100        }
4101        result
4102    }
4103
4104    /// Cancels a running distributed query: registry cancel (which reaches
4105    /// every fragment control through the parent-child hierarchy) plus an
4106    /// explicit transport cancel per in-flight fragment (spec section 12.10:
4107    /// "cancellation fans out to every fragment"). Returns true when the
4108    /// registry accepted (or already observed) the cancellation.
4109    pub fn cancel_query(&self, query_id: &QueryId) -> DistributedResult<bool> {
4110        let outcome = self.registry.cancel(to_registry_query_id(query_id)?);
4111        let state = self.executions.lock().get(query_id).cloned();
4112        if let Some(state) = state {
4113            let in_flight = state.in_flight.lock();
4114            for (fragment_id, inflight) in in_flight.iter() {
4115                inflight.control.cancel(CancellationReason::ClientRequest);
4116                let _ = self.transport.cancel_fragment(*query_id, *fragment_id);
4117            }
4118        }
4119        Ok(matches!(
4120            outcome,
4121            CancelOutcome::Accepted | CancelOutcome::AlreadyCancelling
4122        ))
4123    }
4124
4125    /// Sweeps expired worker leases and cleans their abandoned in-flight
4126    /// fragments: cancels the fragment control (as
4127    /// [`CancellationReason::ServerShutdown`]), notifies the transport, and
4128    /// releases the resource reservation (spec section 12.10: "worker lease
4129    /// expiry cleans abandoned fragments"). Returns the number of fragments
4130    /// cleaned.
4131    pub fn sweep_expired_leases(&self, now: Instant) -> usize {
4132        let expired = self.leases.sweep(now);
4133        if expired.is_empty() {
4134            return 0;
4135        }
4136        let states: Vec<Arc<ExecutionState>> = self.executions.lock().values().cloned().collect();
4137        let mut cleaned = 0;
4138        for state in states {
4139            let victims: Vec<FragmentId> = {
4140                let in_flight = state.in_flight.lock();
4141                in_flight
4142                    .iter()
4143                    .filter(|(_, inflight)| {
4144                        inflight
4145                            .worker
4146                            .is_some_and(|worker| expired.contains(&worker))
4147                    })
4148                    .map(|(fragment_id, _)| *fragment_id)
4149                    .collect()
4150            };
4151            for fragment_id in victims {
4152                let inflight = state.in_flight.lock().remove(&fragment_id);
4153                if let Some(inflight) = inflight {
4154                    inflight.control.cancel(CancellationReason::ServerShutdown);
4155                    drop(inflight);
4156                    let _ = self.transport.cancel_fragment(state.query_id, fragment_id);
4157                    cleaned += 1;
4158                }
4159            }
4160        }
4161        cleaned
4162    }
4163
4164    async fn execute_registered(
4165        &self,
4166        plan: &DistributedPlan,
4167        registered: &RegisteredSqlQuery,
4168        authorization_context: Arc<[u8]>,
4169    ) -> DistributedResult<Vec<RecordBatch>> {
4170        validate_plan_shape(plan)?;
4171        let root = plan
4172            .root_fragment_id()
4173            .ok_or_else(|| DistributedError::InvalidPlan("plan has no root fragment".to_owned()))?;
4174        let state = Arc::new(ExecutionState::new(plan.query_id));
4175        self.executions
4176            .lock()
4177            .insert(plan.query_id, Arc::clone(&state));
4178        let result = self
4179            .run_plan(plan, root, registered, &state, authorization_context)
4180            .await;
4181        self.executions.lock().remove(&plan.query_id);
4182        result
4183    }
4184
4185    async fn run_plan(
4186        &self,
4187        plan: &DistributedPlan,
4188        root: FragmentId,
4189        registered: &RegisteredSqlQuery,
4190        state: &Arc<ExecutionState>,
4191        authorization_context: Arc<[u8]>,
4192    ) -> DistributedResult<Vec<RecordBatch>> {
4193        let parent = registered.control().clone();
4194        let layers = fragment_layers(plan, root)?;
4195        let mut outputs: HashMap<FragmentId, Vec<BatchFrame>> = HashMap::new();
4196        for layer in &layers {
4197            checkpoint(&parent)?;
4198            let now = Instant::now();
4199            for &fragment_id in layer {
4200                if let FragmentAssignment::Tablet(tablet) =
4201                    plan.fragments[fragment_id as usize].assignment
4202                {
4203                    self.leases.renew(tablet, now + self.lease_ttl);
4204                }
4205            }
4206            let mut tasks = FuturesUnordered::new();
4207            let mut spawn_error: Option<DistributedError> = None;
4208            for &fragment_id in layer {
4209                let fragment = &plan.fragments[fragment_id as usize];
4210                let reserved = match self.resources.reserve(fragment) {
4211                    Ok(permit) => permit,
4212                    Err(error) => {
4213                        spawn_error = Some(error);
4214                        break;
4215                    }
4216                };
4217                let inputs = match build_inputs(plan, fragment_id, &outputs) {
4218                    Ok(inputs) => inputs,
4219                    Err(error) => {
4220                        spawn_error = Some(error);
4221                        break;
4222                    }
4223                };
4224                let control = parent.child_with_deadline(None);
4225                let worker = match fragment.assignment {
4226                    FragmentAssignment::Tablet(tablet) => Some(tablet),
4227                    FragmentAssignment::Coordinator => None,
4228                };
4229                state.in_flight.lock().insert(
4230                    fragment_id,
4231                    InFlight {
4232                        worker,
4233                        control: control.clone(),
4234                        _permit: reserved,
4235                    },
4236                );
4237                let fragment = fragment.clone();
4238                let fragment_control = FragmentControl {
4239                    control,
4240                    max_spill_bytes: fragment.max_spill_bytes,
4241                    authorization_context: Arc::clone(&authorization_context),
4242                };
4243                let transport = Arc::clone(&self.transport);
4244                tasks.push(async move {
4245                    let worker = worker_label(&fragment);
4246                    let drain_control = fragment_control.control.clone();
4247                    let result = async {
4248                        let stream = transport
4249                            .execute_fragment(plan.query_id, &fragment, inputs, fragment_control)
4250                            .await?;
4251                        drain_stream(stream, &drain_control).await
4252                    }
4253                    .await;
4254                    (fragment.fragment_id, worker, result)
4255                });
4256            }
4257            if let Some(error) = spawn_error {
4258                self.abort(plan, state);
4259                while let Some((fragment_id, _, _)) = tasks.next().await {
4260                    state.in_flight.lock().remove(&fragment_id);
4261                }
4262                return Err(error);
4263            }
4264            let mut failure: Option<DistributedError> = None;
4265            while let Some((fragment_id, worker, result)) = tasks.next().await {
4266                state.in_flight.lock().remove(&fragment_id);
4267                match result {
4268                    Ok(frames) => {
4269                        outputs.insert(fragment_id, frames);
4270                    }
4271                    Err(error) => {
4272                        if failure.is_none() {
4273                            failure = Some(match error {
4274                                DistributedError::Cancelled(reason) => {
4275                                    DistributedError::Cancelled(reason)
4276                                }
4277                                other => DistributedError::FragmentExecution {
4278                                    fragment_id,
4279                                    worker,
4280                                    message: other.to_string(),
4281                                },
4282                            });
4283                        }
4284                    }
4285                }
4286            }
4287            if let Some(error) = failure {
4288                self.abort(plan, state);
4289                return Err(error);
4290            }
4291        }
4292        checkpoint(&parent)?;
4293        self.run_root(plan, root, &outputs, &parent, authorization_context)
4294            .await
4295    }
4296
4297    /// Cancels every in-flight fragment control and notifies the transport
4298    /// for every fragment (the abort path's cancellation fan-out).
4299    fn abort(&self, plan: &DistributedPlan, state: &Arc<ExecutionState>) {
4300        {
4301            let in_flight = state.in_flight.lock();
4302            for inflight in in_flight.values() {
4303                inflight.control.cancel(CancellationReason::ClientRequest);
4304            }
4305        }
4306        for fragment in &plan.fragments {
4307            let _ = self
4308                .transport
4309                .cancel_fragment(plan.query_id, fragment.fragment_id);
4310        }
4311    }
4312
4313    /// Runs the coordinator-local root fragment over the producer outputs.
4314    async fn run_root(
4315        &self,
4316        plan: &DistributedPlan,
4317        root: FragmentId,
4318        outputs: &HashMap<FragmentId, Vec<BatchFrame>>,
4319        parent: &ExecutionControl,
4320        authorization_context: Arc<[u8]>,
4321    ) -> DistributedResult<Vec<RecordBatch>> {
4322        let fragment = &plan.fragments[root as usize];
4323        let mut inputs: Vec<ProducerInput> = Vec::new();
4324        for operator in &fragment.operators {
4325            if let FragmentOperator::RemoteExchangeSource { exchange } = operator {
4326                let edge = plan.exchanges.get(*exchange as usize).ok_or_else(|| {
4327                    DistributedError::InvalidPlan(format!("unknown exchange {exchange}"))
4328                })?;
4329                let producer = plan.fragments.get(edge.producer as usize).ok_or_else(|| {
4330                    DistributedError::InvalidPlan(format!(
4331                        "unknown producer fragment {}",
4332                        edge.producer
4333                    ))
4334                })?;
4335                let tablet = match producer.assignment {
4336                    FragmentAssignment::Tablet(tablet) => Some(tablet),
4337                    FragmentAssignment::Coordinator => None,
4338                };
4339                let frames = outputs.get(&edge.producer).cloned().ok_or_else(|| {
4340                    DistributedError::InvalidPlan(format!(
4341                        "missing output of producer fragment {}",
4342                        edge.producer
4343                    ))
4344                })?;
4345                let frames = route_frames(plan, edge, frames)?;
4346                inputs.push(ProducerInput {
4347                    fragment_id: edge.producer,
4348                    tablet,
4349                    frames,
4350                });
4351            }
4352        }
4353        let mut current = RootData::Streams(inputs);
4354        for operator in &fragment.operators {
4355            checkpoint(parent)?;
4356            match operator {
4357                FragmentOperator::RemoteExchangeSource { .. } => {}
4358                FragmentOperator::FinalAggregate {
4359                    group_by,
4360                    aggregates,
4361                } => {
4362                    let batches = current.flatten();
4363                    current = RootData::Batches(vec![final_aggregate_batches(
4364                        &batches, group_by, aggregates,
4365                    )?]);
4366                }
4367                FragmentOperator::MergeSort { keys, limit } => {
4368                    current = RootData::Batches(match &current {
4369                        RootData::Streams(_) => {
4370                            let streams = per_stream_batches(&current);
4371                            merge_sorted_streams(&streams, keys, *limit)?
4372                        }
4373                        RootData::Batches(batches) => sort_batches_local(batches, keys, *limit)?,
4374                    });
4375                }
4376                FragmentOperator::DistributedTopK { k, score } => {
4377                    current = RootData::Batches(
4378                        self.coordinator_top_k(
4379                            plan,
4380                            &current,
4381                            *k,
4382                            score,
4383                            parent,
4384                            &authorization_context,
4385                        )
4386                        .await?,
4387                    );
4388                }
4389                FragmentOperator::DistributedLimit { limit } => {
4390                    let batches = current.flatten();
4391                    current = RootData::Batches(limit_batches(&batches, *limit));
4392                }
4393                other => {
4394                    return Err(DistributedError::Unsupported(format!(
4395                    "root fragment operator {other:?} is not coordinator-executable in this wave"
4396                )))
4397                }
4398            }
4399        }
4400        Ok(current.flatten())
4401    }
4402
4403    /// The coordinator-side deterministic top-k merge with adaptive refill
4404    /// (spec section 12.10): merges every producer's bounded local top-k
4405    /// under the exact tie-break, refilling tablets whose unseen-score bound
4406    /// could still contribute winners through the transport.
4407    async fn coordinator_top_k(
4408        &self,
4409        plan: &DistributedPlan,
4410        data: &RootData,
4411        k: usize,
4412        score: &SortKey,
4413        control: &ExecutionControl,
4414        authorization_context: &[u8],
4415    ) -> DistributedResult<Vec<RecordBatch>> {
4416        // A pre-combined input (chained after another coordinator operator)
4417        // is a single synthetic stream that never refills.
4418        let synthetic;
4419        let inputs: &[ProducerInput] = match data {
4420            RootData::Streams(inputs) => inputs,
4421            RootData::Batches(batches) => {
4422                synthetic = ProducerInput {
4423                    fragment_id: u32::MAX,
4424                    tablet: Some(TabletId::ZERO),
4425                    frames: batches.iter().cloned().map(BatchFrame::data).collect(),
4426                };
4427                std::slice::from_ref(&synthetic)
4428            }
4429        };
4430        let mut all_batches: Vec<RecordBatch> = Vec::new();
4431        let mut batch_tablet: Vec<TabletId> = Vec::new();
4432        let mut shards: BTreeMap<TabletId, TabletTopK> = BTreeMap::new();
4433        let mut fragment_of: HashMap<TabletId, FragmentId> = HashMap::new();
4434        for input in inputs {
4435            let tablet = input.tablet.ok_or_else(|| {
4436                DistributedError::InvalidPlan(
4437                    "distributed top-k inputs must be tablet fragments".to_owned(),
4438                )
4439            })?;
4440            fragment_of.insert(tablet, input.fragment_id);
4441            let mut rows = Vec::new();
4442            let mut bound = None;
4443            for frame in &input.frames {
4444                let batch = &frame.batch;
4445                if batch.num_rows() > 0 {
4446                    let score_index = batch.schema().index_of(&score.column).map_err(|_| {
4447                        DistributedError::InvalidPlan(format!(
4448                            "top-k score column `{}` not in schema",
4449                            score.column
4450                        ))
4451                    })?;
4452                    let score_array = batch.column(score_index).clone();
4453                    let row_id_array = row_ids(batch)?;
4454                    for row in 0..batch.num_rows() {
4455                        rows.push(TopKCandidate {
4456                            score: score_key(score_array.as_ref(), row)?,
4457                            tablet,
4458                            row_id: RowId(row_id_array.value(row)),
4459                        });
4460                    }
4461                }
4462                batch_tablet.push(tablet);
4463                all_batches.push(batch.clone());
4464                match frame.score_bound {
4465                    ScoreBound::AtMost(next) => bound = Some(next),
4466                    ScoreBound::Exhausted => bound = None,
4467                    // No bound reporting: treated as exhausted (documented
4468                    // producer contract).
4469                    ScoreBound::Unknown => {}
4470                }
4471            }
4472            shards.insert(
4473                tablet,
4474                TabletTopK {
4475                    tablet,
4476                    rows,
4477                    unseen_bound: bound,
4478                },
4479            );
4480        }
4481        let winners = loop {
4482            let ordered: Vec<TabletTopK> = shards.values().cloned().collect();
4483            let merge = merge_top_k(&ordered, k);
4484            if merge.refill.is_empty() {
4485                break merge.winners;
4486            }
4487            for tablet in merge.refill {
4488                let fragment_id = fragment_of[&tablet];
4489                let already = shards[&tablet].rows.len();
4490                let refill = self
4491                    .transport
4492                    .refill_top_k(
4493                        plan.query_id,
4494                        &plan.fragments[fragment_id as usize],
4495                        already,
4496                        k,
4497                        FragmentControl {
4498                            control: control.child_with_deadline(None),
4499                            max_spill_bytes: plan.fragments[fragment_id as usize].max_spill_bytes,
4500                            authorization_context: authorization_context.into(),
4501                        },
4502                    )
4503                    .await?;
4504                let entry = shards.get_mut(&tablet).expect("shard exists");
4505                if refill.rows.is_empty() && refill.unseen_bound == entry.unseen_bound {
4506                    return Err(DistributedError::InvalidPlan(format!(
4507                        "top-k refill for fragment {fragment_id} made no progress"
4508                    )));
4509                }
4510                batch_tablet.push(tablet);
4511                all_batches.push(refill.payload);
4512                entry.rows.extend(refill.rows);
4513                entry.unseen_bound = refill.unseen_bound;
4514            }
4515        };
4516        if winners.is_empty() {
4517            return Ok(Vec::new());
4518        }
4519        // Locate every winner's payload row.
4520        let mut locations: HashMap<(TabletId, u64), (usize, usize)> = HashMap::new();
4521        for (batch_index, batch) in all_batches.iter().enumerate() {
4522            if batch.num_rows() == 0 {
4523                continue;
4524            }
4525            let row_id_array = row_ids(batch)?;
4526            let tablet = batch_tablet[batch_index];
4527            for row in 0..batch.num_rows() {
4528                locations.insert((tablet, row_id_array.value(row)), (batch_index, row));
4529            }
4530        }
4531        let mut order = Vec::with_capacity(winners.len());
4532        for winner in &winners {
4533            let location = locations
4534                .get(&(winner.tablet, winner.row_id.0))
4535                .ok_or_else(|| {
4536                    DistributedError::InvalidPlan(format!(
4537                        "top-k winner {:?} has no payload row",
4538                        winner.row_id
4539                    ))
4540                })?;
4541            order.push(*location);
4542        }
4543        let merged = emit_interleaved(&all_batches, &order)?;
4544        // Strip the internal row-id column from the result.
4545        let schema = merged.first().map(|batch| batch.schema()).ok_or_else(|| {
4546            DistributedError::InvalidPlan("top-k merge produced no output".to_owned())
4547        })?;
4548        let keep: Vec<usize> = schema
4549            .fields()
4550            .iter()
4551            .enumerate()
4552            .filter(|(_, field)| field.name() != TOPK_ROWID_COLUMN)
4553            .map(|(index, _)| index)
4554            .collect();
4555        if keep.len() == schema.fields().len() {
4556            return Ok(merged);
4557        }
4558        merged
4559            .iter()
4560            .map(|batch| Ok(batch.project(&keep)?))
4561            .collect()
4562    }
4563}
4564
4565/// One stream's concatenated payload batches (for the k-way merge).
4566fn per_stream_batches(data: &RootData) -> Vec<RecordBatch> {
4567    match data {
4568        RootData::Streams(inputs) => inputs
4569            .iter()
4570            .filter_map(|input| {
4571                let batches: Vec<RecordBatch> = input
4572                    .frames
4573                    .iter()
4574                    .map(|frame| frame.batch.clone())
4575                    .collect();
4576                concat_all(&batches).ok().flatten()
4577            })
4578            .collect(),
4579        RootData::Batches(batches) => batches.clone(),
4580    }
4581}
4582
4583/// A short worker label for fragment errors.
4584fn worker_label(fragment: &PlanFragment) -> String {
4585    match fragment.assignment {
4586        FragmentAssignment::Tablet(tablet) => format!("tablet {tablet}"),
4587        FragmentAssignment::Coordinator => "coordinator".to_owned(),
4588    }
4589}
4590
4591/// Validates the structural invariants execution relies on.
4592fn validate_plan_shape(plan: &DistributedPlan) -> DistributedResult<()> {
4593    if plan.fragments.is_empty() {
4594        return Err(DistributedError::InvalidPlan(
4595            "plan has no fragments".to_owned(),
4596        ));
4597    }
4598    for (index, fragment) in plan.fragments.iter().enumerate() {
4599        if fragment.fragment_id as usize != index {
4600            return Err(DistributedError::InvalidPlan(
4601                "fragment ids must equal their vector index".to_owned(),
4602            ));
4603        }
4604    }
4605    for edge in &plan.exchanges {
4606        if edge.producer as usize >= plan.fragments.len()
4607            || edge.consumer as usize >= plan.fragments.len()
4608        {
4609            return Err(DistributedError::InvalidPlan(format!(
4610                "exchange {} references a missing fragment",
4611                edge.exchange_id
4612            )));
4613        }
4614    }
4615    let roots = plan
4616        .fragments
4617        .iter()
4618        .filter(|fragment| {
4619            !plan
4620                .exchanges
4621                .iter()
4622                .any(|edge| edge.producer == fragment.fragment_id)
4623        })
4624        .count();
4625    if roots != 1 {
4626        return Err(DistributedError::InvalidPlan(format!(
4627            "plan must have exactly one root fragment, found {roots}"
4628        )));
4629    }
4630    if let Some(root) = plan.root_fragment_id() {
4631        for fragment in &plan.fragments {
4632            if fragment.fragment_id != root
4633                && fragment.assignment == FragmentAssignment::Coordinator
4634            {
4635                return Err(DistributedError::InvalidPlan(
4636                    "only the root fragment may be coordinator-assigned".to_owned(),
4637                ));
4638            }
4639        }
4640    }
4641    Ok(())
4642}
4643
4644/// Groups non-root fragments into dependency layers (producers first).
4645fn fragment_layers(
4646    plan: &DistributedPlan,
4647    root: FragmentId,
4648) -> DistributedResult<Vec<Vec<FragmentId>>> {
4649    fn depth(
4650        plan: &DistributedPlan,
4651        fragment: FragmentId,
4652        memo: &mut [Option<usize>],
4653        visiting: &mut [bool],
4654    ) -> DistributedResult<usize> {
4655        if let Some(depth) = memo[fragment as usize] {
4656            return Ok(depth);
4657        }
4658        if visiting[fragment as usize] {
4659            return Err(DistributedError::InvalidPlan(
4660                "exchange graph has a cycle".to_owned(),
4661            ));
4662        }
4663        visiting[fragment as usize] = true;
4664        let mut best = 0;
4665        for edge in plan.exchanges_into(fragment) {
4666            best = best.max(depth(plan, edge.producer, memo, visiting)? + 1);
4667        }
4668        visiting[fragment as usize] = false;
4669        memo[fragment as usize] = Some(best);
4670        Ok(best)
4671    }
4672
4673    let mut memo = vec![None; plan.fragments.len()];
4674    let mut visiting = vec![false; plan.fragments.len()];
4675    let mut layers: Vec<Vec<FragmentId>> = Vec::new();
4676    for fragment in &plan.fragments {
4677        if fragment.fragment_id == root {
4678            continue;
4679        }
4680        let depth = depth(plan, fragment.fragment_id, &mut memo, &mut visiting)?;
4681        if layers.len() <= depth {
4682            layers.resize(depth + 1, Vec::new());
4683        }
4684        layers[depth].push(fragment.fragment_id);
4685    }
4686    Ok(layers)
4687}
4688
4689/// Builds one consumer's input streams from producer outputs, routing each
4690/// edge per its exchange kind.
4691fn build_inputs(
4692    plan: &DistributedPlan,
4693    consumer: FragmentId,
4694    outputs: &HashMap<FragmentId, Vec<BatchFrame>>,
4695) -> DistributedResult<Vec<FragmentStream>> {
4696    let mut inputs = Vec::new();
4697    for edge in plan.exchanges_into(consumer) {
4698        let frames = outputs.get(&edge.producer).cloned().ok_or_else(|| {
4699            DistributedError::InvalidPlan(format!(
4700                "missing output of producer fragment {}",
4701                edge.producer
4702            ))
4703        })?;
4704        let frames = route_frames(plan, edge, frames)?;
4705        inputs.push(Box::pin(stream::iter(frames.into_iter().map(Ok))) as FragmentStream);
4706    }
4707    Ok(inputs)
4708}
4709
4710/// Routes one producer's output frames to one consumer per the edge kind:
4711/// hash-repartitioned rows are split across the producer's sibling edges.
4712fn route_frames(
4713    plan: &DistributedPlan,
4714    edge: &ExchangeDescriptor,
4715    frames: Vec<BatchFrame>,
4716) -> DistributedResult<Vec<BatchFrame>> {
4717    match &edge.kind {
4718        ExchangeKind::Merge | ExchangeKind::Broadcast => Ok(frames),
4719        ExchangeKind::HashRepartition { keys } => {
4720            let mut siblings: Vec<&ExchangeDescriptor> = plan
4721                .exchanges
4722                .iter()
4723                .filter(|candidate| {
4724                    candidate.producer == edge.producer
4725                        && matches!(candidate.kind, ExchangeKind::HashRepartition { .. })
4726                })
4727                .collect();
4728            siblings.sort_by_key(|candidate| candidate.consumer);
4729            let width = siblings.len();
4730            let index = siblings
4731                .iter()
4732                .position(|candidate| candidate.consumer == edge.consumer)
4733                .ok_or_else(|| {
4734                    DistributedError::InvalidPlan(format!(
4735                        "exchange {} is missing from its repartition boundary",
4736                        edge.exchange_id
4737                    ))
4738                })?;
4739            repartition_frames(&frames, keys, width, index)
4740        }
4741    }
4742}
4743
4744/// Bridges a types-level [`QueryId`] onto the query registry's id space.
4745fn to_registry_query_id(query_id: &QueryId) -> DistributedResult<crate::query_registry::QueryId> {
4746    query_id
4747        .to_hex()
4748        .parse()
4749        .map_err(|error| DistributedError::InvalidPlan(format!("query id bridge failed: {error}")))
4750}
4751
4752// ---------------------------------------------------------------------------
4753// Tests
4754// ---------------------------------------------------------------------------
4755
4756#[cfg(test)]
4757mod tests {
4758    use super::*;
4759    use arrow::datatypes::Field;
4760
4761    /// Deterministic tablet id for tests.
4762    fn tablet(n: u8) -> TabletId {
4763        let mut bytes = [0u8; 16];
4764        bytes[15] = n;
4765        TabletId::from_bytes(bytes)
4766    }
4767
4768    /// Seeded RNG (SplitMix64) for reproducible property tests.
4769    struct SplitMix64(u64);
4770
4771    impl SplitMix64 {
4772        fn next(&mut self) -> u64 {
4773            self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
4774            let mut z = self.0;
4775            z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
4776            z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
4777            z ^ (z >> 31)
4778        }
4779
4780        fn below(&mut self, n: u64) -> u64 {
4781            self.next() % n
4782        }
4783    }
4784
4785    #[derive(Default)]
4786    struct StaticLocator {
4787        tablets: HashMap<String, Vec<TabletId>>,
4788        specs: HashMap<String, PartitionSpec>,
4789    }
4790
4791    impl TabletLocator for StaticLocator {
4792        fn tablets_for_table(&self, table: &str) -> DistributedResult<Vec<TabletId>> {
4793            self.tablets
4794                .get(table)
4795                .cloned()
4796                .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
4797        }
4798
4799        fn partitioning(&self, table: &str) -> DistributedResult<PartitionSpec> {
4800            self.specs
4801                .get(table)
4802                .cloned()
4803                .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
4804        }
4805    }
4806
4807    struct StaticMetadata {
4808        version: MetadataVersion,
4809        stats: HashMap<String, TableStats>,
4810    }
4811
4812    impl ClusterMetadata for StaticMetadata {
4813        fn metadata_version(&self) -> MetadataVersion {
4814            self.version
4815        }
4816
4817        fn table_stats(&self, table: &str) -> DistributedResult<TableStats> {
4818            self.stats
4819                .get(table)
4820                .copied()
4821                .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
4822        }
4823    }
4824
4825    /// Builds locator + metadata for a set of tables.
4826    fn world(
4827        entries: &[(&str, &[TabletId], PartitionSpec, TableStats)],
4828    ) -> (StaticLocator, StaticMetadata) {
4829        let mut locator = StaticLocator::default();
4830        let mut stats = HashMap::new();
4831        for (table, tablets, spec, stat) in entries {
4832            locator
4833                .tablets
4834                .insert((*table).to_owned(), tablets.to_vec());
4835            locator.specs.insert((*table).to_owned(), spec.clone());
4836            stats.insert((*table).to_owned(), *stat);
4837        }
4838        (
4839            locator,
4840            StaticMetadata {
4841                version: MetadataVersion::new(7),
4842                stats,
4843            },
4844        )
4845    }
4846
4847    fn scan(table: &str) -> LogicalPlanLite {
4848        LogicalPlanLite::Scan {
4849            table: table.to_owned(),
4850            predicate: None,
4851            projection: Vec::new(),
4852        }
4853    }
4854
4855    fn desc(root: LogicalPlanLite) -> PlanDescription {
4856        PlanDescription {
4857            query_id: QueryId::new_random(),
4858            root,
4859            options: PlannerOptions::default(),
4860        }
4861    }
4862
4863    fn hash_spec(column: &str) -> PartitionSpec {
4864        PartitionSpec::Hash {
4865            columns: vec![column.to_owned()],
4866            buckets: 16,
4867        }
4868    }
4869
4870    fn stats(rows: u64, bytes: u64) -> TableStats {
4871        TableStats {
4872            row_count: rows,
4873            total_bytes: bytes,
4874        }
4875    }
4876
4877    fn i64_batch(columns: &[(&str, Vec<i64>)]) -> RecordBatch {
4878        let schema = Schema::new(
4879            columns
4880                .iter()
4881                .map(|(name, _)| Field::new(*name, DataType::Int64, true))
4882                .collect::<Vec<_>>(),
4883        );
4884        let arrays: Vec<ArrayRef> = columns
4885            .iter()
4886            .map(|(_, values)| Arc::new(Int64Array::from(values.clone())) as ArrayRef)
4887            .collect();
4888        RecordBatch::try_new(schema.into(), arrays).unwrap()
4889    }
4890
4891    fn score_batch(scores: Vec<u64>, row_ids: Vec<u64>, payloads: Vec<i64>) -> RecordBatch {
4892        let schema = Schema::new(vec![
4893            Field::new("score", DataType::UInt64, true),
4894            Field::new(TOPK_ROWID_COLUMN, DataType::UInt64, false),
4895            Field::new("payload", DataType::Int64, true),
4896        ]);
4897        RecordBatch::try_new(
4898            schema.into(),
4899            vec![
4900                Arc::new(UInt64Array::from(scores)),
4901                Arc::new(UInt64Array::from(row_ids)),
4902                Arc::new(Int64Array::from(payloads)),
4903            ],
4904        )
4905        .unwrap()
4906    }
4907
4908    fn collect_i64(batches: &[RecordBatch], column: &str) -> Vec<i64> {
4909        let mut values = Vec::new();
4910        for batch in batches {
4911            let index = batch.schema().index_of(column).unwrap();
4912            let array = batch
4913                .column(index)
4914                .as_any()
4915                .downcast_ref::<Int64Array>()
4916                .unwrap();
4917            for row in 0..batch.num_rows() {
4918                assert!(
4919                    !array.is_null(row),
4920                    "column {column} has an unexpected null"
4921                );
4922                values.push(array.value(row));
4923            }
4924        }
4925        values
4926    }
4927
4928    fn collect_u64(batches: &[RecordBatch], column: &str) -> Vec<u64> {
4929        let mut values = Vec::new();
4930        for batch in batches {
4931            let index = batch.schema().index_of(column).unwrap();
4932            let array = batch
4933                .column(index)
4934                .as_any()
4935                .downcast_ref::<UInt64Array>()
4936                .unwrap();
4937            for row in 0..batch.num_rows() {
4938                values.push(array.value(row));
4939            }
4940        }
4941        values
4942    }
4943
4944    fn collect_f64(batches: &[RecordBatch], column: &str) -> Vec<Option<f64>> {
4945        let mut values = Vec::new();
4946        for batch in batches {
4947            let index = batch.schema().index_of(column).unwrap();
4948            let array = batch
4949                .column(index)
4950                .as_any()
4951                .downcast_ref::<Float64Array>()
4952                .unwrap();
4953            for row in 0..batch.num_rows() {
4954                values.push((!array.is_null(row)).then(|| array.value(row)));
4955            }
4956        }
4957        values
4958    }
4959
4960    fn operators(plan: &DistributedPlan, fragment_id: FragmentId) -> &[FragmentOperator] {
4961        &plan.fragments[fragment_id as usize].operators
4962    }
4963
4964    // -----------------------------------------------------------------------
4965    // Plan shape tests
4966    // -----------------------------------------------------------------------
4967
4968    #[test]
4969    fn scan_plan_shape_estimates_and_spill() {
4970        let tablets = [tablet(1), tablet(2), tablet(3)];
4971        let (locator, metadata) = world(&[(
4972            "t",
4973            &tablets,
4974            hash_spec("id"),
4975            stats(3_000, 3 * 1024 * 1024),
4976        )]);
4977        let description = desc(scan("t"));
4978        let plan = distribute(&description, &locator, &metadata).unwrap();
4979        assert_eq!(plan.query_id, description.query_id);
4980        assert_eq!(plan.metadata_version, MetadataVersion::new(7));
4981        assert_eq!(
4982            plan.fragments.len(),
4983            4,
4984            "3 scan fragments + coordinator gather"
4985        );
4986        for (index, id) in tablets.iter().enumerate() {
4987            let fragment = &plan.fragments[index];
4988            assert_eq!(fragment.assignment, FragmentAssignment::Tablet(*id));
4989            assert!(
4990                matches!(
4991                    operators(&plan, fragment.fragment_id)[0],
4992                    FragmentOperator::TabletScan { .. }
4993                ),
4994                "fragment {index} starts with a tablet scan"
4995            );
4996            assert!(
4997                matches!(
4998                    operators(&plan, fragment.fragment_id).last().unwrap(),
4999                    FragmentOperator::RemoteExchangeSink { .. }
5000                ),
5001                "fragment {index} ends with a sink"
5002            );
5003            assert_eq!(fragment.estimated_rows, 1_000);
5004            assert_eq!(fragment.estimated_bytes, 1024 * 1024);
5005            assert_eq!(
5006                fragment.max_spill_bytes,
5007                DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT
5008            );
5009        }
5010        let root = plan.root_fragment_id().unwrap();
5011        assert_eq!(root, 3);
5012        assert_eq!(
5013            plan.fragments[3].assignment,
5014            FragmentAssignment::Coordinator
5015        );
5016        assert_eq!(plan.exchanges.len(), 3);
5017        for edge in &plan.exchanges {
5018            assert_eq!(edge.kind, ExchangeKind::Merge);
5019            assert_eq!(edge.consumer, root);
5020            assert_eq!(
5021                edge.schema_fingerprint,
5022                plan.exchanges[0].schema_fingerprint
5023            );
5024        }
5025        // The configured spill allowance is stamped on every fragment.
5026        let mut custom = desc(scan("t"));
5027        custom.options.max_spill_bytes_per_fragment = 1_234;
5028        let plan = distribute(&custom, &locator, &metadata).unwrap();
5029        assert!(plan
5030            .fragments
5031            .iter()
5032            .all(|fragment| fragment.max_spill_bytes == 1_234));
5033    }
5034
5035    #[test]
5036    fn fragment_control_begin_spill_opens_core_spill_session() {
5037        use mongreldb_core::ExecutionControl;
5038        use mongreldb_types::ids::QueryId;
5039
5040        let dir = tempfile::tempdir().unwrap();
5041        // Real shipped path: Database::create owns the SpillManager.
5042        let db = mongreldb_core::Database::create(dir.path()).unwrap();
5043        let control = FragmentControl {
5044            control: ExecutionControl::new(None),
5045            max_spill_bytes: 64 * 1024,
5046            authorization_context: Arc::from([]),
5047        };
5048        let session = control
5049            .begin_spill(db.spill_manager(), QueryId::from_bytes([0xAB; 16]))
5050            .expect("begin_spill binds planner allowance to core SpillManager");
5051        let stats = db.spill_manager().stats();
5052        assert_eq!(
5053            stats.global_budget_bytes,
5054            db.spill_manager().config().global_bytes
5055        );
5056        drop(session);
5057    }
5058
5059    #[test]
5060    fn aggregate_plan_shape_grouped_and_ungrouped() {
5061        let tablets = [tablet(1), tablet(2), tablet(3)];
5062        let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(900, 900_000))]);
5063        let aggregates = vec![
5064            AggregateExpr {
5065                function: AggregateFunction::Count,
5066                column: None,
5067            },
5068            AggregateExpr {
5069                function: AggregateFunction::Sum,
5070                column: Some("v".to_owned()),
5071            },
5072        ];
5073        // Grouped: partial on producers, hash-repartition exchange, final at
5074        // the coordinator.
5075        let grouped = LogicalPlanLite::Aggregate {
5076            input: Box::new(scan("t")),
5077            group_by: vec!["g".to_owned()],
5078            aggregates: aggregates.clone(),
5079        };
5080        let plan = distribute(&desc(grouped), &locator, &metadata).unwrap();
5081        assert_eq!(plan.fragments.len(), 4);
5082        for index in 0..3 {
5083            assert!(matches!(
5084                operators(&plan, index as FragmentId)[1],
5085                FragmentOperator::PartialAggregate { .. }
5086            ));
5087        }
5088        assert_eq!(plan.exchanges.len(), 3);
5089        for edge in &plan.exchanges {
5090            assert_eq!(
5091                edge.kind,
5092                ExchangeKind::HashRepartition {
5093                    keys: vec!["g".to_owned()]
5094                }
5095            );
5096        }
5097        let root = plan.root_fragment_id().unwrap();
5098        assert!(matches!(
5099            operators(&plan, root).last().unwrap(),
5100            FragmentOperator::FinalAggregate { .. }
5101        ));
5102        // Ungrouped: merge exchange, single-row estimate.
5103        let ungrouped = LogicalPlanLite::Aggregate {
5104            input: Box::new(scan("t")),
5105            group_by: Vec::new(),
5106            aggregates: aggregates[..1].to_vec(),
5107        };
5108        let plan = distribute(&desc(ungrouped), &locator, &metadata).unwrap();
5109        assert!(plan
5110            .exchanges
5111            .iter()
5112            .all(|edge| edge.kind == ExchangeKind::Merge));
5113        let root = plan.root_fragment_id().unwrap();
5114        assert_eq!(plan.fragments[root as usize].estimated_rows, 1);
5115    }
5116
5117    #[test]
5118    fn colocated_join_plan_fuses_scans_without_exchange() {
5119        let tablets = [tablet(1), tablet(2), tablet(3)];
5120        let (locator, metadata) = world(&[
5121            (
5122                "a",
5123                &tablets,
5124                hash_spec("id"),
5125                stats(6_000, 6 * 1024 * 1024),
5126            ),
5127            (
5128                "b",
5129                &tablets,
5130                hash_spec("id"),
5131                stats(3_000, 3 * 1024 * 1024),
5132            ),
5133        ]);
5134        let join = LogicalPlanLite::Join {
5135            left: Box::new(scan("a")),
5136            right: Box::new(scan("b")),
5137            on: vec![JoinKey {
5138                left: "id".to_owned(),
5139                right: "id".to_owned(),
5140            }],
5141        };
5142        let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5143        assert_eq!(plan.fragments.len(), 4, "3 fused fragments + gather");
5144        for index in 0..3 {
5145            let ops = operators(&plan, index as FragmentId);
5146            assert!(
5147                matches!(&ops[0], FragmentOperator::TabletScan { table, .. } if table == "a"),
5148                "left scan first: {ops:?}"
5149            );
5150            assert!(
5151                matches!(&ops[1], FragmentOperator::TabletScan { table, .. } if table == "b"),
5152                "right scan second: {ops:?}"
5153            );
5154            assert!(
5155                matches!(&ops[2], FragmentOperator::DistributedHashJoin { .. }),
5156                "fused hash join: {ops:?}"
5157            );
5158        }
5159        assert!(
5160            plan.exchanges
5161                .iter()
5162                .all(|edge| edge.kind == ExchangeKind::Merge),
5163            "a colocated join has no shuffle: {:?}",
5164            plan.exchanges
5165        );
5166        assert_eq!(plan.exchanges.len(), 3, "only the gather edges remain");
5167    }
5168
5169    #[test]
5170    fn broadcast_join_plan_replicates_the_small_side() {
5171        let big_tablets = [tablet(1), tablet(2), tablet(3)];
5172        let small_tablets = [tablet(4), tablet(5)];
5173        let (locator, metadata) = world(&[
5174            (
5175                "big",
5176                &big_tablets,
5177                hash_spec("id"),
5178                stats(8_000, 64 * 1024 * 1024),
5179            ),
5180            (
5181                "small",
5182                &small_tablets,
5183                hash_spec("key"),
5184                stats(100, 1024 * 1024),
5185            ),
5186        ]);
5187        let join = LogicalPlanLite::Join {
5188            left: Box::new(scan("big")),
5189            right: Box::new(scan("small")),
5190            on: vec![JoinKey {
5191                left: "id".to_owned(),
5192                right: "key".to_owned(),
5193            }],
5194        };
5195        let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5196        // 3 big + 2 small + gather.
5197        assert_eq!(plan.fragments.len(), 6);
5198        let root = plan.root_fragment_id().unwrap();
5199        let broadcast_edges: Vec<&ExchangeDescriptor> = plan
5200            .exchanges
5201            .iter()
5202            .filter(|edge| edge.kind == ExchangeKind::Broadcast)
5203            .collect();
5204        assert_eq!(
5205            broadcast_edges.len(),
5206            6,
5207            "each small producer x each big fragment"
5208        );
5209        for edge in &broadcast_edges {
5210            assert!(edge.producer >= 3, "small side produces the broadcast");
5211            assert!(edge.consumer < 3, "big side consumes the broadcast");
5212        }
5213        for index in 0..3 {
5214            let ops = operators(&plan, index as FragmentId);
5215            let last_transform = ops
5216                .iter()
5217                .rfind(|op| !matches!(op, FragmentOperator::RemoteExchangeSink { .. }))
5218                .unwrap();
5219            assert!(matches!(
5220                last_transform,
5221                FragmentOperator::BroadcastJoin {
5222                    build_side: BuildSide::Right,
5223                    ..
5224                }
5225            ));
5226            let sources = ops
5227                .iter()
5228                .filter(|op| matches!(op, FragmentOperator::RemoteExchangeSource { .. }))
5229                .count();
5230            assert_eq!(sources, 2, "one source per small producer");
5231        }
5232        assert!(plan
5233            .exchanges
5234            .iter()
5235            .filter(|edge| edge.consumer == root)
5236            .all(|edge| edge.kind == ExchangeKind::Merge && edge.producer < 3));
5237    }
5238
5239    #[test]
5240    fn repartition_join_plan_shuffles_both_sides() {
5241        let left_tablets = [tablet(1), tablet(2)];
5242        let right_tablets = [tablet(3), tablet(4), tablet(5)];
5243        let (locator, metadata) = world(&[
5244            (
5245                "l",
5246                &left_tablets,
5247                hash_spec("a"),
5248                stats(8_000, 64 * 1024 * 1024),
5249            ),
5250            (
5251                "r",
5252                &right_tablets,
5253                hash_spec("b"),
5254                stats(9_000, 96 * 1024 * 1024),
5255            ),
5256        ]);
5257        let join = LogicalPlanLite::Join {
5258            left: Box::new(scan("l")),
5259            right: Box::new(scan("r")),
5260            on: vec![JoinKey {
5261                left: "a".to_owned(),
5262                right: "b".to_owned(),
5263            }],
5264        };
5265        let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5266        // 2 left scans + 3 right scans + 3 join fragments + gather.
5267        assert_eq!(plan.fragments.len(), 9);
5268        let root = plan.root_fragment_id().unwrap();
5269        let shuffles: Vec<&ExchangeDescriptor> = plan
5270            .exchanges
5271            .iter()
5272            .filter(|edge| matches!(edge.kind, ExchangeKind::HashRepartition { .. }))
5273            .collect();
5274        assert_eq!(shuffles.len(), 2 * 3 + 3 * 3);
5275        let left_keys: Vec<&ExchangeDescriptor> = shuffles
5276            .iter()
5277            .filter(|edge| edge.producer < 2)
5278            .copied()
5279            .collect();
5280        assert!(
5281            left_keys
5282                .iter()
5283                .all(|edge| matches!(&edge.kind, ExchangeKind::HashRepartition { keys } if keys == &vec!["a".to_owned()]))
5284        );
5285        for join_fragment in 5..8 {
5286            let fragment = &plan.fragments[join_fragment];
5287            assert_eq!(
5288                fragment.assignment,
5289                FragmentAssignment::Tablet(right_tablets[(join_fragment - 5) % 3]),
5290                "join fragments round-robin over the larger side's tablets"
5291            );
5292            let ops = operators(&plan, fragment.fragment_id);
5293            let sources = ops
5294                .iter()
5295                .filter(|op| matches!(op, FragmentOperator::RemoteExchangeSource { .. }))
5296                .count();
5297            assert_eq!(sources, 5, "2 left + 3 right sources: {ops:?}");
5298            let last_transform = ops
5299                .iter()
5300                .rfind(|op| !matches!(op, FragmentOperator::RemoteExchangeSink { .. }))
5301                .unwrap();
5302            assert!(matches!(
5303                last_transform,
5304                FragmentOperator::RepartitionJoin { .. }
5305            ));
5306        }
5307        assert!(plan
5308            .exchanges
5309            .iter()
5310            .filter(|edge| edge.consumer == root)
5311            .all(|edge| edge.kind == ExchangeKind::Merge));
5312    }
5313
5314    #[test]
5315    fn sort_and_limit_plan_shapes() {
5316        let tablets = [tablet(1), tablet(2), tablet(3)];
5317        let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(900, 900_000))]);
5318
5319        // Single descending key + limit: distributed top-k on both sides.
5320        let topk = LogicalPlanLite::Sort {
5321            input: Box::new(scan("t")),
5322            keys: vec![SortKey {
5323                column: "score".to_owned(),
5324                descending: true,
5325            }],
5326            limit: Some(10),
5327        };
5328        let plan = distribute(&desc(topk), &locator, &metadata).unwrap();
5329        for index in 0..3 {
5330            assert!(matches!(
5331                operators(&plan, index as FragmentId)[1],
5332                FragmentOperator::DistributedTopK { k: 10, .. }
5333            ));
5334        }
5335        let root = plan.root_fragment_id().unwrap();
5336        assert!(matches!(
5337            operators(&plan, root).last().unwrap(),
5338            FragmentOperator::DistributedTopK { k: 10, .. }
5339        ));
5340        assert!(plan
5341            .exchanges
5342            .iter()
5343            .all(|edge| edge.kind == ExchangeKind::Merge));
5344
5345        // Plain sort: merge sort on both sides.
5346        let sort = LogicalPlanLite::Sort {
5347            input: Box::new(scan("t")),
5348            keys: vec![SortKey {
5349                column: "x".to_owned(),
5350                descending: false,
5351            }],
5352            limit: None,
5353        };
5354        let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
5355        for index in 0..3 {
5356            assert!(matches!(
5357                operators(&plan, index as FragmentId)[1],
5358                FragmentOperator::MergeSort { limit: None, .. }
5359            ));
5360        }
5361        let root = plan.root_fragment_id().unwrap();
5362        assert!(matches!(
5363            operators(&plan, root).last().unwrap(),
5364            FragmentOperator::MergeSort { limit: None, .. }
5365        ));
5366
5367        // Multi-key sort with a limit is NOT a top-k (single descending key
5368        // only): merge sort with the limit pushed down.
5369        let multi = LogicalPlanLite::Sort {
5370            input: Box::new(scan("t")),
5371            keys: vec![
5372                SortKey {
5373                    column: "a".to_owned(),
5374                    descending: false,
5375                },
5376                SortKey {
5377                    column: "b".to_owned(),
5378                    descending: true,
5379                },
5380            ],
5381            limit: Some(5),
5382        };
5383        let plan = distribute(&desc(multi), &locator, &metadata).unwrap();
5384        for index in 0..3 {
5385            assert!(matches!(
5386                operators(&plan, index as FragmentId)[1],
5387                FragmentOperator::MergeSort { limit: Some(5), .. }
5388            ));
5389        }
5390        let root = plan.root_fragment_id().unwrap();
5391        assert!(matches!(
5392            operators(&plan, root).last().unwrap(),
5393            FragmentOperator::MergeSort { limit: Some(5), .. }
5394        ));
5395
5396        // Bare limit: pushed down and re-applied at the coordinator.
5397        let limit = LogicalPlanLite::Limit {
5398            input: Box::new(scan("t")),
5399            limit: 7,
5400        };
5401        let plan = distribute(&desc(limit), &locator, &metadata).unwrap();
5402        for index in 0..3 {
5403            assert!(matches!(
5404                operators(&plan, index as FragmentId)[1],
5405                FragmentOperator::DistributedLimit { limit: 7 }
5406            ));
5407        }
5408        let root = plan.root_fragment_id().unwrap();
5409        assert!(matches!(
5410            operators(&plan, root).last().unwrap(),
5411            FragmentOperator::DistributedLimit { limit: 7 }
5412        ));
5413
5414        // A limit over a scalar aggregate stays in the one coordinator
5415        // fragment (no extra hop).
5416        let chained = LogicalPlanLite::Limit {
5417            input: Box::new(LogicalPlanLite::Aggregate {
5418                input: Box::new(scan("t")),
5419                group_by: Vec::new(),
5420                aggregates: vec![AggregateExpr {
5421                    function: AggregateFunction::Count,
5422                    column: None,
5423                }],
5424            }),
5425            limit: 5,
5426        };
5427        let plan = distribute(&desc(chained), &locator, &metadata).unwrap();
5428        assert_eq!(plan.fragments.len(), 4);
5429        let root = plan.root_fragment_id().unwrap();
5430        let ops = operators(&plan, root);
5431        assert!(matches!(
5432            ops[ops.len() - 2],
5433            FragmentOperator::FinalAggregate { .. }
5434        ));
5435        assert!(matches!(
5436            ops[ops.len() - 1],
5437            FragmentOperator::DistributedLimit { limit: 5 }
5438        ));
5439    }
5440
5441    #[test]
5442    fn planner_rejects_invalid_inputs() {
5443        let tablets = [tablet(1)];
5444        let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(10, 100))]);
5445        // Unknown table.
5446        let error = distribute(&desc(scan("nope")), &locator, &metadata).unwrap_err();
5447        assert!(
5448            matches!(error, DistributedError::UnknownTable(_)),
5449            "{error}"
5450        );
5451        // Empty layout.
5452        let (locator, metadata) = world(&[("e", &[], hash_spec("id"), stats(0, 0))]);
5453        let error = distribute(&desc(scan("e")), &locator, &metadata).unwrap_err();
5454        assert!(
5455            matches!(error, DistributedError::EmptyLayout { .. }),
5456            "{error}"
5457        );
5458        let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(10, 100))]);
5459        // Join without keys.
5460        let join = LogicalPlanLite::Join {
5461            left: Box::new(scan("t")),
5462            right: Box::new(scan("t")),
5463            on: Vec::new(),
5464        };
5465        let error = distribute(&desc(join), &locator, &metadata).unwrap_err();
5466        assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5467        // Sum without a column.
5468        let aggregate = LogicalPlanLite::Aggregate {
5469            input: Box::new(scan("t")),
5470            group_by: Vec::new(),
5471            aggregates: vec![AggregateExpr {
5472                function: AggregateFunction::Sum,
5473                column: None,
5474            }],
5475        };
5476        let error = distribute(&desc(aggregate), &locator, &metadata).unwrap_err();
5477        assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5478        // Sort without keys.
5479        let sort = LogicalPlanLite::Sort {
5480            input: Box::new(scan("t")),
5481            keys: Vec::new(),
5482            limit: None,
5483        };
5484        let error = distribute(&desc(sort), &locator, &metadata).unwrap_err();
5485        assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5486    }
5487
5488    // -----------------------------------------------------------------------
5489    // Distributed top-k: pure merge + adaptive refill
5490    // -----------------------------------------------------------------------
5491
5492    fn candidate(score: u64, tablet_n: u8, row_id: u64) -> TopKCandidate {
5493        TopKCandidate {
5494            score,
5495            tablet: tablet(tablet_n),
5496            row_id: RowId(row_id),
5497        }
5498    }
5499
5500    #[test]
5501    fn topk_tie_break_is_exact() {
5502        // Final score descending, tablet id ascending, RowId ascending.
5503        let mut rows = vec![
5504            candidate(5, 2, 0),
5505            candidate(5, 1, 2),
5506            candidate(7, 3, 0),
5507            candidate(5, 1, 0),
5508            candidate(3, 1, 0),
5509        ];
5510        rows.sort_by(topk_cmp);
5511        assert_eq!(
5512            rows,
5513            vec![
5514                candidate(7, 3, 0),
5515                candidate(5, 1, 0),
5516                candidate(5, 1, 2),
5517                candidate(5, 2, 0),
5518                candidate(3, 1, 0),
5519            ]
5520        );
5521    }
5522
5523    #[test]
5524    fn merge_top_k_needs_no_refill_when_tablets_are_exhausted() {
5525        let shards = vec![
5526            TabletTopK {
5527                tablet: tablet(1),
5528                rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
5529                unseen_bound: None,
5530            },
5531            TabletTopK {
5532                tablet: tablet(2),
5533                rows: vec![candidate(96, 2, 0)],
5534                unseen_bound: None,
5535            },
5536        ];
5537        let merge = merge_top_k(&shards, 2);
5538        assert_eq!(
5539            merge.winners,
5540            vec![candidate(100, 1, 0), candidate(96, 2, 0)]
5541        );
5542        assert!(merge.refill.is_empty());
5543    }
5544
5545    #[test]
5546    fn merge_top_k_refill_rules_follow_the_bounds() {
5547        // Bound below the k-th winner: no refill possible.
5548        let shards = vec![
5549            TabletTopK {
5550                tablet: tablet(1),
5551                rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
5552                unseen_bound: Some(95),
5553            },
5554            TabletTopK {
5555                tablet: tablet(2),
5556                rows: vec![candidate(96, 2, 0)],
5557                unseen_bound: None,
5558            },
5559        ];
5560        let merge = merge_top_k(&shards, 2);
5561        assert!(
5562            merge.refill.is_empty(),
5563            "unseen scores of tablet 1 are at most 95 < 96: {:?}",
5564            merge.refill
5565        );
5566        // Bound above the k-th winner: refill required.
5567        let shards = vec![
5568            TabletTopK {
5569                tablet: tablet(1),
5570                rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
5571                unseen_bound: Some(97),
5572            },
5573            TabletTopK {
5574                tablet: tablet(2),
5575                rows: vec![candidate(96, 2, 0)],
5576                unseen_bound: None,
5577            },
5578        ];
5579        let merge = merge_top_k(&shards, 2);
5580        assert_eq!(merge.refill, vec![tablet(1)]);
5581        // Tie at the k-th winner's score with a better tablet id: the
5582        // conservative tie case refills.
5583        let shards = vec![
5584            TabletTopK {
5585                tablet: tablet(1),
5586                rows: vec![candidate(100, 1, 0)],
5587                unseen_bound: Some(96),
5588            },
5589            TabletTopK {
5590                tablet: tablet(2),
5591                rows: vec![candidate(96, 2, 9)],
5592                unseen_bound: None,
5593            },
5594        ];
5595        let merge = merge_top_k(&shards, 2);
5596        assert_eq!(
5597            merge.refill,
5598            vec![tablet(1)],
5599            "(96, tablet 1, min row id) ranks better than (96, tablet 2, 9)"
5600        );
5601        // Fewer than k candidates with unseen rows: refill.
5602        let shards = vec![
5603            TabletTopK {
5604                tablet: tablet(1),
5605                rows: vec![candidate(5, 1, 0)],
5606                unseen_bound: Some(1),
5607            },
5608            TabletTopK {
5609                tablet: tablet(2),
5610                rows: Vec::new(),
5611                unseen_bound: None,
5612            },
5613        ];
5614        let merge = merge_top_k(&shards, 3);
5615        assert_eq!(merge.winners, vec![candidate(5, 1, 0)]);
5616        assert_eq!(merge.refill, vec![tablet(1)]);
5617        // k = 0 is trivially exact.
5618        let merge = merge_top_k(&shards, 0);
5619        assert!(merge.winners.is_empty() && merge.refill.is_empty());
5620    }
5621
5622    #[test]
5623    fn exact_top_k_fills_winners_via_adaptive_refill() {
5624        // Tablet 1 holds 3 of the true top-3 but only emits 2 up front.
5625        let tablet_one_rows = vec![
5626            candidate(100, 1, 0),
5627            candidate(99, 1, 1),
5628            candidate(98, 1, 2),
5629            candidate(97, 1, 3),
5630        ];
5631        let shards = vec![
5632            TabletTopK {
5633                tablet: tablet(1),
5634                rows: tablet_one_rows[..2].to_vec(),
5635                unseen_bound: Some(98),
5636            },
5637            TabletTopK {
5638                tablet: tablet(2),
5639                rows: vec![candidate(96, 2, 0), candidate(95, 2, 1)],
5640                unseen_bound: None,
5641            },
5642        ];
5643        let remaining = tablet_one_rows.clone();
5644        let result = exact_top_k(3, shards, move |tablet_id| {
5645            assert_eq!(tablet_id, tablet(1));
5646            TabletTopK {
5647                tablet: tablet_id,
5648                rows: remaining[2..].to_vec(),
5649                unseen_bound: None,
5650            }
5651        })
5652        .unwrap();
5653        assert_eq!(
5654            result,
5655            vec![
5656                candidate(100, 1, 0),
5657                candidate(99, 1, 1),
5658                candidate(98, 1, 2),
5659            ]
5660        );
5661    }
5662
5663    #[test]
5664    fn exact_top_k_rejects_a_stuck_refill() {
5665        let shards = vec![TabletTopK {
5666            tablet: tablet(1),
5667            rows: Vec::new(),
5668            unseen_bound: Some(10),
5669        }];
5670        let error = exact_top_k(1, shards, |tablet| TabletTopK {
5671            tablet,
5672            rows: Vec::new(),
5673            unseen_bound: Some(10),
5674        })
5675        .unwrap_err();
5676        assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5677    }
5678
5679    #[test]
5680    fn exact_top_k_matches_single_node_oracle_across_1000_sharded_datasets() {
5681        let mut total_refills = 0usize;
5682        for seed in 0..1_000u64 {
5683            let mut rng = SplitMix64(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(1));
5684            let tablet_count = 1 + rng.below(8) as usize;
5685            let k = rng.below(30) as usize;
5686            let batch = 1 + rng.below(4) as usize;
5687            let mut oracle_rows = Vec::new();
5688            let mut per_tablet: HashMap<TabletId, Vec<TopKCandidate>> = HashMap::new();
5689            for index in 0..tablet_count {
5690                let tablet = tablet(index as u8 + 1);
5691                let row_count = rng.below(40) as usize;
5692                let mut rows: Vec<TopKCandidate> = (0..row_count)
5693                    .map(|row| TopKCandidate {
5694                        score: rng.below(12),
5695                        tablet,
5696                        row_id: RowId(row as u64),
5697                    })
5698                    .collect();
5699                rows.sort_by(topk_cmp);
5700                oracle_rows.extend(rows.iter().copied());
5701                per_tablet.insert(tablet, rows);
5702            }
5703            let mut oracle = oracle_rows;
5704            oracle.sort_by(topk_cmp);
5705            oracle.truncate(k);
5706            let contribution = |tablet: TabletId, offset: usize| -> TabletTopK {
5707                let rows = &per_tablet[&tablet];
5708                let start = offset.min(rows.len());
5709                let end = (offset + batch).min(rows.len());
5710                TabletTopK {
5711                    tablet,
5712                    rows: rows[start..end].to_vec(),
5713                    unseen_bound: rows.get(end).map(|candidate| candidate.score),
5714                }
5715            };
5716            let initial: Vec<TabletTopK> = (0..tablet_count)
5717                .map(|index| contribution(tablet(index as u8 + 1), 0))
5718                .collect();
5719            let mut returned: HashMap<TabletId, usize> = (0..tablet_count)
5720                .map(|index| (tablet(index as u8 + 1), batch))
5721                .collect();
5722            let result = exact_top_k(k, initial, |tablet| {
5723                total_refills += 1;
5724                let offset = returned[&tablet];
5725                returned.insert(tablet, offset + batch);
5726                contribution(tablet, offset)
5727            })
5728            .unwrap_or_else(|error| panic!("seed {seed}: {error}"));
5729            assert_eq!(result, oracle, "seed {seed}: k={k} batch={batch}");
5730        }
5731        assert!(
5732            total_refills > 0,
5733            "the property run must actually exercise adaptive refill"
5734        );
5735    }
5736
5737    // -----------------------------------------------------------------------
5738    // Execution skeleton: merge operators, cancellation, leases
5739    // -----------------------------------------------------------------------
5740
5741    /// Builds a coordinator + in-memory transport + registry over one
5742    /// executor.
5743    fn coordinator_with(
5744        executor: Arc<dyn FragmentExecutor>,
5745    ) -> (
5746        Arc<Coordinator>,
5747        Arc<InMemoryTransport>,
5748        Arc<SqlQueryRegistry>,
5749    ) {
5750        let transport = Arc::new(InMemoryTransport::new(executor));
5751        let registry = Arc::new(SqlQueryRegistry::default());
5752        let coordinator = Arc::new(Coordinator::new(
5753            Arc::clone(&transport) as Arc<dyn FragmentTransport>,
5754            Arc::clone(&registry),
5755        ));
5756        (coordinator, transport, registry)
5757    }
5758
5759    /// An executor that signals arrival and then waits for cancellation —
5760    /// the fixture for cancellation/lease tests.
5761    struct BlockingExecutor {
5762        barrier: Arc<tokio::sync::Barrier>,
5763    }
5764
5765    #[async_trait::async_trait]
5766    impl FragmentExecutor for BlockingExecutor {
5767        async fn execute(
5768            &self,
5769            _fragment: &PlanFragment,
5770            _inputs: Vec<FragmentStream>,
5771            control: FragmentControl,
5772        ) -> DistributedResult<FragmentStream> {
5773            self.barrier.wait().await;
5774            control.control.cancelled().await;
5775            Err(DistributedError::Cancelled(control.control.reason()))
5776        }
5777    }
5778
5779    /// A scan-only plan over `table` with 3 tablets, suitable for
5780    /// cancellation fixtures.
5781    fn scan_plan(table: &str) -> (DistributedPlan, Vec<TabletId>) {
5782        let tablets = [tablet(1), tablet(2), tablet(3)];
5783        let (locator, metadata) = world(&[(
5784            table,
5785            &tablets,
5786            hash_spec("id"),
5787            stats(3_000, 3 * 1024 * 1024),
5788        )]);
5789        let plan = distribute(&desc(scan(table)), &locator, &metadata).unwrap();
5790        (plan, tablets.to_vec())
5791    }
5792
5793    #[tokio::test]
5794    async fn scan_gather_returns_all_rows() {
5795        let store = Arc::new(InMemoryTableStore::new());
5796        let tablets = [tablet(1), tablet(2), tablet(3)];
5797        store.insert("t", tablets[0], i64_batch(&[("v", vec![1, 2])]));
5798        store.insert("t", tablets[1], i64_batch(&[("v", vec![3])]));
5799        // Tablet 3 intentionally holds no rows.
5800        let (plan, _) = scan_plan("t");
5801        let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
5802        let batches = coordinator.execute(&plan).await.unwrap();
5803        let mut values = collect_i64(&batches, "v");
5804        values.sort_unstable();
5805        assert_eq!(values, vec![1, 2, 3]);
5806    }
5807
5808    #[tokio::test]
5809    async fn remote_arrow_ipc_transport_gathers_with_pull_backpressure() {
5810        let store = Arc::new(InMemoryTableStore::new());
5811        let tablets = [tablet(1), tablet(2), tablet(3)];
5812        store.insert("t", tablets[0], i64_batch(&[("v", vec![1, 2])]));
5813        store.insert("t", tablets[1], i64_batch(&[("v", vec![3])]));
5814        let executor: Arc<dyn FragmentExecutor> = Arc::new(InMemoryFragmentExecutor::new(store));
5815        let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
5816        let client: Arc<dyn FragmentRpcClient> =
5817            Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
5818        let transport: Arc<dyn FragmentTransport> = Arc::new(RemoteFragmentTransport::new(client));
5819        let coordinator = Coordinator::new(transport, Arc::new(SqlQueryRegistry::default()));
5820        let (plan, _) = scan_plan("t");
5821
5822        let batches = coordinator.execute(&plan).await.unwrap();
5823        let mut values = collect_i64(&batches, "v");
5824        values.sort_unstable();
5825        assert_eq!(values, vec![1, 2, 3]);
5826        assert_eq!(
5827            endpoint.active_executions(),
5828            0,
5829            "terminal pulls release every worker cursor"
5830        );
5831    }
5832
5833    #[tokio::test]
5834    async fn merge_sort_matches_single_node_sort() {
5835        let store = Arc::new(InMemoryTableStore::new());
5836        let tablets = [tablet(1), tablet(2), tablet(3)];
5837        store.insert("s", tablets[0], i64_batch(&[("x", vec![5, 1, 9])]));
5838        store.insert("s", tablets[1], i64_batch(&[("x", vec![3, 7])]));
5839        store.insert("s", tablets[2], i64_batch(&[("x", vec![8, 2, 6, 4])]));
5840        let (locator, metadata) = world(&[("s", &tablets, hash_spec("id"), stats(9, 900))]);
5841        let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
5842
5843        // Full merge sort.
5844        let sort = LogicalPlanLite::Sort {
5845            input: Box::new(scan("s")),
5846            keys: vec![SortKey {
5847                column: "x".to_owned(),
5848                descending: false,
5849            }],
5850            limit: None,
5851        };
5852        let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
5853        let batches = coordinator.execute(&plan).await.unwrap();
5854        assert_eq!(collect_i64(&batches, "x"), vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
5855
5856        // Bounded merge sort.
5857        let sort = LogicalPlanLite::Sort {
5858            input: Box::new(scan("s")),
5859            keys: vec![SortKey {
5860                column: "x".to_owned(),
5861                descending: false,
5862            }],
5863            limit: Some(4),
5864        };
5865        let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
5866        let batches = coordinator.execute(&plan).await.unwrap();
5867        assert_eq!(collect_i64(&batches, "x"), vec![1, 2, 3, 4]);
5868    }
5869
5870    #[tokio::test]
5871    async fn final_aggregate_matches_single_node_aggregate() {
5872        let store = Arc::new(InMemoryTableStore::new());
5873        let tablets = [tablet(1), tablet(2), tablet(3)];
5874        store.insert(
5875            "t",
5876            tablets[0],
5877            i64_batch(&[("g", vec![1, 2, 1, 3]), ("v", vec![10, 20, 30, 40])]),
5878        );
5879        store.insert(
5880            "t",
5881            tablets[1],
5882            i64_batch(&[("g", vec![2, 1, 3]), ("v", vec![50, 60, 70])]),
5883        );
5884        store.insert(
5885            "t",
5886            tablets[2],
5887            i64_batch(&[("g", vec![3, 2]), ("v", vec![80, 90])]),
5888        );
5889        let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(9, 900))]);
5890        let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
5891
5892        let aggregates = vec![
5893            AggregateExpr {
5894                function: AggregateFunction::Count,
5895                column: None,
5896            },
5897            AggregateExpr {
5898                function: AggregateFunction::Sum,
5899                column: Some("v".to_owned()),
5900            },
5901            AggregateExpr {
5902                function: AggregateFunction::Min,
5903                column: Some("v".to_owned()),
5904            },
5905            AggregateExpr {
5906                function: AggregateFunction::Max,
5907                column: Some("v".to_owned()),
5908            },
5909            AggregateExpr {
5910                function: AggregateFunction::Avg,
5911                column: Some("v".to_owned()),
5912            },
5913        ];
5914        let grouped = LogicalPlanLite::Aggregate {
5915            input: Box::new(scan("t")),
5916            group_by: vec!["g".to_owned()],
5917            aggregates,
5918        };
5919        let plan = distribute(&desc(grouped), &locator, &metadata).unwrap();
5920        let batches = coordinator.execute(&plan).await.unwrap();
5921        let groups = collect_i64(&batches, "g");
5922        let counts = collect_i64(&batches, "count_star");
5923        let sums = collect_i64(&batches, "sum_v");
5924        let mins = collect_i64(&batches, "min_v");
5925        let maxs = collect_i64(&batches, "max_v");
5926        let avgs = collect_f64(&batches, "avg_v");
5927        // Single-node oracle, group order-independent.
5928        let mut oracle: HashMap<i64, (i64, i64, i64, i64)> = HashMap::new();
5929        for (g, v) in [
5930            (1, 10),
5931            (2, 20),
5932            (1, 30),
5933            (3, 40),
5934            (2, 50),
5935            (1, 60),
5936            (3, 70),
5937            (3, 80),
5938            (2, 90),
5939        ] {
5940            let entry = oracle.entry(g).or_insert((0, 0, i64::MAX, i64::MIN));
5941            entry.0 += 1;
5942            entry.1 += v;
5943            entry.2 = entry.2.min(v);
5944            entry.3 = entry.3.max(v);
5945        }
5946        assert_eq!(groups.len(), 3);
5947        for index in 0..groups.len() {
5948            let (count, sum, min, max) = oracle[&groups[index]];
5949            assert_eq!(counts[index], count, "count for g={}", groups[index]);
5950            assert_eq!(sums[index], sum, "sum for g={}", groups[index]);
5951            assert_eq!(mins[index], min, "min for g={}", groups[index]);
5952            assert_eq!(maxs[index], max, "max for g={}", groups[index]);
5953            let expected_avg = sum as f64 / count as f64;
5954            let actual_avg = avgs[index].expect("avg is set");
5955            assert!(
5956                (actual_avg - expected_avg).abs() < 1e-12,
5957                "avg for g={}: {actual_avg} vs {expected_avg}",
5958                groups[index]
5959            );
5960        }
5961
5962        // Scalar aggregate: one row over all tablets.
5963        let scalar = LogicalPlanLite::Aggregate {
5964            input: Box::new(scan("t")),
5965            group_by: Vec::new(),
5966            aggregates: vec![
5967                AggregateExpr {
5968                    function: AggregateFunction::Count,
5969                    column: None,
5970                },
5971                AggregateExpr {
5972                    function: AggregateFunction::Sum,
5973                    column: Some("v".to_owned()),
5974                },
5975            ],
5976        };
5977        let plan = distribute(&desc(scalar), &locator, &metadata).unwrap();
5978        let batches = coordinator.execute(&plan).await.unwrap();
5979        assert_eq!(collect_i64(&batches, "count_star"), vec![9]);
5980        assert_eq!(collect_i64(&batches, "sum_v"), vec![450]);
5981    }
5982
5983    #[tokio::test]
5984    async fn distributed_top_k_matches_oracle_with_and_without_refill() {
5985        // Heavy score ties across three tablets; payloads mirror row ids so
5986        // the exact tie order is observable after `__rowid` is stripped.
5987        let store = Arc::new(InMemoryTableStore::new());
5988        let tablets = [tablet(1), tablet(2), tablet(3)];
5989        store.insert(
5990            "k",
5991            tablets[0],
5992            score_batch(vec![100, 90, 80, 70], vec![0, 1, 2, 3], vec![0, 1, 2, 3]),
5993        );
5994        store.insert(
5995            "k",
5996            tablets[1],
5997            score_batch(vec![90, 80, 60], vec![0, 1, 2], vec![0, 1, 2]),
5998        );
5999        store.insert(
6000            "k",
6001            tablets[2],
6002            score_batch(vec![90, 80, 50], vec![0, 1, 2], vec![0, 1, 2]),
6003        );
6004        let (locator, metadata) = world(&[("k", &tablets, hash_spec("id"), stats(10, 1_000))]);
6005        let topk = LogicalPlanLite::Sort {
6006            input: Box::new(scan("k")),
6007            keys: vec![SortKey {
6008                column: "score".to_owned(),
6009                descending: true,
6010            }],
6011            limit: Some(5),
6012        };
6013        // Single-node oracle under the exact tie-break.
6014        let mut oracle: Vec<TopKCandidate> = Vec::new();
6015        for (tablet_n, rows) in [
6016            (1u8, vec![(100u64, 0u64), (90, 1), (80, 2), (70, 3)]),
6017            (2u8, vec![(90, 0), (80, 1), (60, 2)]),
6018            (3u8, vec![(90, 0), (80, 1), (50, 2)]),
6019        ] {
6020            for (score, row_id) in rows {
6021                oracle.push(candidate(score, tablet_n, row_id));
6022            }
6023        }
6024        oracle.sort_by(topk_cmp);
6025        oracle.truncate(5);
6026        let expected: Vec<(u64, i64)> = oracle
6027            .iter()
6028            .map(|winner| (winner.score, winner.row_id.0 as i64))
6029            .collect();
6030
6031        let run = async |emit_batch: Option<usize>| {
6032            let executor = match emit_batch {
6033                Some(batch) => InMemoryFragmentExecutor::with_topk_emit_batch(store.clone(), batch),
6034                None => InMemoryFragmentExecutor::new(store.clone()),
6035            };
6036            let (coordinator, transport, _) = coordinator_with(Arc::new(executor));
6037            let plan = distribute(&desc(topk.clone()), &locator, &metadata).unwrap();
6038            let batches = coordinator.execute(&plan).await.unwrap();
6039            (batches, transport)
6040        };
6041
6042        // Default emission (up to k rows): exact without refills.
6043        let (batches, transport) = run(None).await;
6044        let scores = collect_u64(&batches, "score");
6045        let payloads = collect_i64(&batches, "payload");
6046        let actual: Vec<(u64, i64)> = scores.into_iter().zip(payloads).collect();
6047        assert_eq!(actual, expected);
6048        assert!(
6049            transport.refill_log().is_empty(),
6050            "exact local top-k never needs refill"
6051        );
6052        assert!(
6053            batches
6054                .iter()
6055                .all(|batch| batch.schema().index_of(TOPK_ROWID_COLUMN).is_err()),
6056            "the internal row-id column is stripped"
6057        );
6058
6059        // Bounded emission (2 rows per round): the coordinator must refill
6060        // and still match the oracle exactly.
6061        let (batches, transport) = run(Some(2)).await;
6062        let scores = collect_u64(&batches, "score");
6063        let payloads = collect_i64(&batches, "payload");
6064        let actual: Vec<(u64, i64)> = scores.into_iter().zip(payloads).collect();
6065        assert_eq!(actual, expected);
6066        assert!(
6067            !transport.refill_log().is_empty(),
6068            "bounded emission must trigger adaptive refill"
6069        );
6070
6071        // The same bounded producer over the remote protocol must cross the
6072        // refill RPC and preserve the exact winners.
6073        let executor: Arc<dyn FragmentExecutor> = Arc::new(
6074            InMemoryFragmentExecutor::with_topk_emit_batch(Arc::clone(&store), 2),
6075        );
6076        let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6077        let client: Arc<dyn FragmentRpcClient> =
6078            Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6079        let coordinator = Coordinator::new(
6080            Arc::new(RemoteFragmentTransport::new(client)),
6081            Arc::new(SqlQueryRegistry::default()),
6082        );
6083        let plan = distribute(&desc(topk), &locator, &metadata).unwrap();
6084        let batches = coordinator.execute(&plan).await.unwrap();
6085        let actual = collect_u64(&batches, "score")
6086            .into_iter()
6087            .zip(collect_i64(&batches, "payload"))
6088            .collect::<Vec<_>>();
6089        assert_eq!(actual, expected);
6090        assert_eq!(endpoint.active_executions(), 0);
6091    }
6092
6093    #[tokio::test]
6094    async fn cancellation_fans_out_to_every_fragment() {
6095        let barrier = Arc::new(tokio::sync::Barrier::new(4));
6096        let executor = Arc::new(BlockingExecutor {
6097            barrier: Arc::clone(&barrier),
6098        });
6099        let (coordinator, transport, _registry) = coordinator_with(executor);
6100        let (plan, _) = scan_plan("t");
6101        let task = {
6102            let coordinator = Arc::clone(&coordinator);
6103            let plan = plan.clone();
6104            tokio::spawn(async move { coordinator.execute(&plan).await })
6105        };
6106        tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6107            .await
6108            .expect("all fragments started");
6109        assert!(coordinator.cancel_query(&plan.query_id).unwrap());
6110        let result = task.await.unwrap();
6111        assert!(
6112            matches!(
6113                result,
6114                Err(DistributedError::Cancelled(
6115                    CancellationReason::ClientRequest
6116                ))
6117            ),
6118            "cancellation surfaces as ClientRequest: {result:?}"
6119        );
6120        let cancelled = transport.cancelled_fragments();
6121        for fragment_id in 0..3 {
6122            assert!(
6123                cancelled.contains(&fragment_id),
6124                "fragment {fragment_id} received the transport cancel: {cancelled:?}"
6125            );
6126            let control = transport.control_for(fragment_id).unwrap();
6127            assert_eq!(control.reason(), CancellationReason::ClientRequest);
6128        }
6129    }
6130
6131    #[tokio::test]
6132    async fn remote_cancellation_reaches_worker_during_fragment_start() {
6133        let barrier = Arc::new(tokio::sync::Barrier::new(4));
6134        let executor: Arc<dyn FragmentExecutor> = Arc::new(BlockingExecutor {
6135            barrier: Arc::clone(&barrier),
6136        });
6137        let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6138        let client: Arc<dyn FragmentRpcClient> =
6139            Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6140        let coordinator = Arc::new(Coordinator::new(
6141            Arc::new(RemoteFragmentTransport::new(client)),
6142            Arc::new(SqlQueryRegistry::default()),
6143        ));
6144        let (plan, _) = scan_plan("t");
6145        let task = {
6146            let coordinator = Arc::clone(&coordinator);
6147            let plan = plan.clone();
6148            tokio::spawn(async move { coordinator.execute(&plan).await })
6149        };
6150        tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6151            .await
6152            .expect("all remote workers entered fragment start");
6153        assert!(coordinator.cancel_query(&plan.query_id).unwrap());
6154        let result = tokio::time::timeout(Duration::from_secs(30), task)
6155            .await
6156            .expect("remote cancellation must not hang")
6157            .unwrap();
6158        assert!(matches!(result, Err(DistributedError::Cancelled(_))));
6159        assert_eq!(endpoint.active_executions(), 0);
6160    }
6161
6162    #[tokio::test]
6163    async fn registry_cancel_reaches_all_fragments() {
6164        let barrier = Arc::new(tokio::sync::Barrier::new(4));
6165        let executor = Arc::new(BlockingExecutor {
6166            barrier: Arc::clone(&barrier),
6167        });
6168        let (coordinator, transport, registry) = coordinator_with(executor);
6169        let (plan, _) = scan_plan("t");
6170        let task = {
6171            let coordinator = Arc::clone(&coordinator);
6172            let plan = plan.clone();
6173            tokio::spawn(async move { coordinator.execute(&plan).await })
6174        };
6175        tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6176            .await
6177            .expect("all fragments started");
6178        // The existing registry cancel path — this is the wiring proof.
6179        let bridged: crate::query_registry::QueryId = plan.query_id.to_hex().parse().unwrap();
6180        assert_eq!(registry.cancel(bridged), CancelOutcome::Accepted);
6181        let result = task.await.unwrap();
6182        assert!(
6183            matches!(result, Err(DistributedError::Cancelled(_))),
6184            "registry cancellation aborts the distributed query: {result:?}"
6185        );
6186        for fragment_id in 0..3 {
6187            let control = transport.control_for(fragment_id).unwrap();
6188            assert_eq!(
6189                control.reason(),
6190                CancellationReason::ClientRequest,
6191                "fragment {fragment_id} observed the registry cancel"
6192            );
6193        }
6194    }
6195
6196    #[tokio::test]
6197    async fn lease_expiry_reclaims_abandoned_fragments() {
6198        let barrier = Arc::new(tokio::sync::Barrier::new(4));
6199        let executor = Arc::new(BlockingExecutor {
6200            barrier: Arc::clone(&barrier),
6201        });
6202        let transport = Arc::new(InMemoryTransport::new(executor));
6203        let registry = Arc::new(SqlQueryRegistry::default());
6204        let coordinator = Arc::new(Coordinator::with_limits(
6205            Arc::clone(&transport) as Arc<dyn FragmentTransport>,
6206            registry,
6207            1_024,
6208            16 * 1024 * 1024 * 1024,
6209            Duration::from_secs(30),
6210        ));
6211        let (plan, _) = scan_plan("t");
6212        let task = {
6213            let coordinator = Arc::clone(&coordinator);
6214            let plan = plan.clone();
6215            tokio::spawn(async move { coordinator.execute(&plan).await })
6216        };
6217        tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6218            .await
6219            .expect("all fragments started");
6220        assert_eq!(coordinator.resources().reserved_fragments(), 3);
6221        // All worker leases expired an hour from now's perspective.
6222        let cleaned = coordinator.sweep_expired_leases(Instant::now() + Duration::from_secs(3_600));
6223        assert_eq!(cleaned, 3, "every abandoned fragment is reclaimed");
6224        assert_eq!(
6225            coordinator.resources().reserved_fragments(),
6226            0,
6227            "reclaimed fragments release their reservations"
6228        );
6229        for fragment_id in 0..3 {
6230            let control = transport.control_for(fragment_id).unwrap();
6231            assert_eq!(
6232                control.reason(),
6233                CancellationReason::ServerShutdown,
6234                "fragment {fragment_id} observes the worker loss"
6235            );
6236        }
6237        let result = task.await.unwrap();
6238        assert!(
6239            matches!(
6240                result,
6241                Err(DistributedError::Cancelled(
6242                    CancellationReason::ServerShutdown
6243                ))
6244            ),
6245            "the query fails with the worker-loss reason: {result:?}"
6246        );
6247    }
6248
6249    #[test]
6250    fn resource_reservation_denies_then_releases() {
6251        let ledger = Arc::new(ResourceLedger::new(1, u64::MAX));
6252        let fragment = PlanFragment {
6253            fragment_id: 0,
6254            assignment: FragmentAssignment::Coordinator,
6255            operators: Vec::new(),
6256            estimated_rows: 1,
6257            estimated_bytes: 100,
6258            max_spill_bytes: 0,
6259        };
6260        let permit = ledger.reserve(&fragment).unwrap();
6261        assert_eq!(ledger.reserved_fragments(), 1);
6262        assert_eq!(ledger.reserved_bytes(), 100);
6263        let denied = ledger.reserve(&fragment).unwrap_err();
6264        assert!(
6265            matches!(denied, DistributedError::Reservation { fragment_id: 0, .. }),
6266            "{denied}"
6267        );
6268        drop(permit);
6269        assert_eq!(ledger.reserved_fragments(), 0);
6270        assert_eq!(ledger.reserved_bytes(), 0);
6271        ledger.reserve(&fragment).unwrap();
6272        // Byte budget enforcement.
6273        let tight = Arc::new(ResourceLedger::new(8, 50));
6274        let denied = tight.reserve(&fragment).unwrap_err();
6275        assert!(
6276            matches!(denied, DistributedError::Reservation { .. }),
6277            "{denied}"
6278        );
6279    }
6280
6281    #[test]
6282    fn repartition_routes_every_row_exactly_once() {
6283        let batch = i64_batch(&[("k", (0..100).collect()), ("v", (0..100).collect())]);
6284        let frames = vec![BatchFrame::data(batch)];
6285        let keys = vec!["k".to_owned()];
6286        let mut partitions = Vec::new();
6287        for index in 0..3 {
6288            partitions.push(repartition_frames(&frames, &keys, 3, index).unwrap());
6289        }
6290        let total: usize = partitions
6291            .iter()
6292            .map(|frames| {
6293                frames
6294                    .iter()
6295                    .map(|frame| frame.batch.num_rows())
6296                    .sum::<usize>()
6297            })
6298            .sum();
6299        assert_eq!(total, 100, "every row lands in exactly one partition");
6300        // Re-routing a partition keeps all of its rows in the same partition
6301        // (disjointness proof by idempotence).
6302        for (index, partition) in partitions.iter().enumerate() {
6303            for other in 0..3 {
6304                let rerouted = repartition_frames(partition, &keys, 3, other).unwrap();
6305                let rows: usize = rerouted.iter().map(|frame| frame.batch.num_rows()).sum();
6306                if other == index {
6307                    let expected: usize =
6308                        partition.iter().map(|frame| frame.batch.num_rows()).sum();
6309                    assert_eq!(rows, expected, "partition {index} rows stay in {index}");
6310                } else {
6311                    assert_eq!(rows, 0, "partition {index} leaks rows into {other}");
6312                }
6313            }
6314        }
6315    }
6316
6317    #[test]
6318    fn score_key_mapping_preserves_order() {
6319        let ints = Int64Array::from(vec![i64::MIN, -5, 0, 5, i64::MAX]);
6320        let keys: Vec<u64> = (0..5).map(|row| score_key(&ints, row).unwrap()).collect();
6321        let mut sorted = keys.clone();
6322        sorted.sort_unstable();
6323        assert_eq!(keys, sorted, "int64 mapping is order-preserving");
6324        let floats = Float64Array::from(vec![f64::MIN, -1.5, 0.0, 2.5, f64::MAX]);
6325        let keys: Vec<u64> = (0..5).map(|row| score_key(&floats, row).unwrap()).collect();
6326        let mut sorted = keys.clone();
6327        sorted.sort_unstable();
6328        assert_eq!(keys, sorted, "float64 mapping is order-preserving");
6329    }
6330}