Skip to main content

linkrs_query/executor/streaming/
state.rs

1//! Typed GlobalState / LocalState arenas for per-execution operator state.
2//!
3//! State ownership split:
4//! - **GlobalState**: hash build, global aggregate, sort runs, exchange,
5//!   result collector — addressed by [`PhysicalOperatorId`].
6//! - **LocalState**: scan cursor, probe state, partial accumulator,
7//!   expression workspace — addressed by `(PhysicalOperatorId, TaskId)`.
8//!
9//! State is kept separate from specs so that [`PhysicalPlan`] and
10//! [`OperatorSpec`] remain immutable and cacheable.  State is allocated
11//! once per execution instance and indexed via typed arenas rather than
12//! inline fields, avoiding `dyn Any` downcast patterns.
13
14use super::operators::state::{
15    BlockingState, ExchangeState, FulltextState, GraphState, JoinState, SetState, SinkState,
16    SourceState, TxnState, VectorState,
17};
18
19/// A task identifier within a fragment execution.
20pub type TaskId = usize;
21
22/// Global operator state — lives for the duration of the query and is
23/// shared across all tasks/partitions that execute the same operator.
24///
25/// Examples: hash table for HashJoin, sort run for Sort, exchange
26/// result buffer.
27#[derive(Debug)]
28pub enum GlobalState {
29    Source(SourceState),
30    Blocking(Box<BlockingState>),
31    Join(JoinState),
32    Graph(GraphState),
33    Sink(SinkState),
34    Set(SetState),
35    Exchange(ExchangeState),
36    Fulltext(FulltextState),
37    Vector(VectorState),
38    Txn(TxnState),
39}
40
41/// Local (per-task) operator state — each task or partition gets its own
42/// copy.  Lives for the duration of a single task execution.
43///
44/// Typed per domain, matching the variant structure of [`GlobalState`].
45/// The runtime accesses the correct variant via [`PhysicalOperatorId`]
46/// + [`TaskId`] lookup — no `dyn Any` downcasts.
47///
48/// Examples: scan cursor position, probe cursor, partial aggregate
49/// accumulator, chunk buffer.
50#[derive(Debug)]
51pub enum LocalState {
52    Source(SourceState),
53    Blocking(Box<BlockingState>),
54    Join(JoinState),
55    Graph(GraphState),
56    Sink(SinkState),
57    Set(SetState),
58    Exchange(ExchangeState),
59    Fulltext(FulltextState),
60    Vector(VectorState),
61    Txn(TxnState),
62}
63
64// ── Index key types ─────────────────────────────────────────────────────────
65
66/// Key for indexing global state: the physical operator ID and partition.
67/// `None` identifies global or non-partitioned state.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub struct GlobalStateKey(
70    pub crate::executor::streaming::plan::types::PhysicalOperatorId,
71    pub Option<usize>,
72);
73
74/// Key for indexing local state: operator ID + partition ID + task ID.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub struct LocalStateKey(
77    pub crate::executor::streaming::plan::types::PhysicalOperatorId,
78    pub Option<usize>,
79    pub TaskId,
80);
81
82// ── GlobalStateArena ────────────────────────────────────────────────────────
83
84/// Typed arena of global operator state objects.
85///
86/// Addressed by [`PhysicalOperatorId`].  Created once per execution
87/// instance and populated during operator tree materialization.
88#[derive(Debug, Default)]
89pub struct GlobalStateArena {
90    states: std::collections::HashMap<GlobalStateKey, GlobalState>,
91}
92
93impl GlobalStateArena {
94    pub fn new() -> Self {
95        Self {
96            states: std::collections::HashMap::new(),
97        }
98    }
99
100    /// Insert global state for an operator.
101    pub fn insert(&mut self, key: GlobalStateKey, state: GlobalState) {
102        self.states.insert(key, state);
103    }
104
105    /// Get a reference to global state.
106    pub fn get(&self, key: &GlobalStateKey) -> Option<&GlobalState> {
107        self.states.get(key)
108    }
109
110    /// Get a mutable reference to global state.
111    pub fn get_mut(&mut self, key: &GlobalStateKey) -> Option<&mut GlobalState> {
112        self.states.get_mut(key)
113    }
114
115    /// Remove and return global state (for cleanup).
116    pub fn remove(&mut self, key: &GlobalStateKey) -> Option<GlobalState> {
117        self.states.remove(key)
118    }
119
120    /// Clear all state.
121    pub fn clear(&mut self) {
122        self.states.clear();
123    }
124
125    /// Number of entries.
126    pub fn len(&self) -> usize {
127        self.states.len()
128    }
129
130    pub fn is_empty(&self) -> bool {
131        self.states.is_empty()
132    }
133}
134
135// ── LocalStateArena ─────────────────────────────────────────────────────────
136
137/// Typed arena of per-task local operator state.
138///
139/// Addressed by [`PhysicalOperatorId`] + [`TaskId`].  Created per
140/// execution instance, with one entry per (operator, task) pair.
141#[derive(Debug, Default)]
142pub struct LocalStateArena {
143    states: std::collections::HashMap<LocalStateKey, LocalState>,
144}
145
146impl LocalStateArena {
147    pub fn new() -> Self {
148        Self {
149            states: std::collections::HashMap::new(),
150        }
151    }
152
153    /// Insert local state for an operator + task.
154    pub fn insert(&mut self, key: LocalStateKey, state: LocalState) {
155        self.states.insert(key, state);
156    }
157
158    /// Get a reference to local state.
159    pub fn get(&self, key: &LocalStateKey) -> Option<&LocalState> {
160        self.states.get(key)
161    }
162
163    /// Get a mutable reference to local state.
164    pub fn get_mut(&mut self, key: &LocalStateKey) -> Option<&mut LocalState> {
165        self.states.get_mut(key)
166    }
167
168    /// Remove and return local state (for cleanup).
169    pub fn remove(&mut self, key: &LocalStateKey) -> Option<LocalState> {
170        self.states.remove(key)
171    }
172
173    /// Clear all state for a given operator (all tasks).
174    pub fn clear_operator(
175        &mut self,
176        op_id: &crate::executor::streaming::plan::types::PhysicalOperatorId,
177    ) {
178        self.states.retain(|k, _| k.0 != *op_id);
179    }
180
181    /// Clear all state.
182    pub fn clear(&mut self) {
183        self.states.clear();
184    }
185
186    /// Number of entries.
187    pub fn len(&self) -> usize {
188        self.states.len()
189    }
190
191    pub fn is_empty(&self) -> bool {
192        self.states.is_empty()
193    }
194}
195
196// ── StateArenaSet (combined) ────────────────────────────────────────────────
197
198/// Combined set of global and local state arenas for an execution instance.
199#[derive(Debug, Default)]
200pub struct StateArenaSet {
201    pub global: GlobalStateArena,
202    pub local: LocalStateArena,
203}
204
205impl StateArenaSet {
206    pub fn new() -> Self {
207        Self {
208            global: GlobalStateArena::new(),
209            local: LocalStateArena::new(),
210        }
211    }
212
213    /// Clear all state (both global and local).
214    pub fn clear(&mut self) {
215        self.global.clear();
216        self.local.clear();
217    }
218}