Skip to main content

reifydb_sub_flow/operator/
gate.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::sync::LazyLock;
5
6use reifydb_abi::operator::capabilities::OperatorCapability;
7use reifydb_codec::{encoded::row::EncodedRow, key::encoded::EncodedKey};
8use reifydb_core::{
9	interface::{
10		catalog::flow::FlowNodeId,
11		change::{Change, Diff},
12	},
13	key::flow_node_internal_state::FlowNodeInternalStateKey,
14	value::column::columns::Columns,
15};
16use reifydb_engine::{
17	expression::{
18		compile::{CompiledExpr, compile_expression},
19		context::{CompileContext, EvalContext},
20	},
21	vm::stack::SymbolTable,
22};
23use reifydb_routine::routine::registry::Routines;
24use reifydb_rql::expression::Expression;
25use reifydb_runtime::context::RuntimeContext;
26use reifydb_value::{
27	Result,
28	params::Params,
29	util::cowvec::CowVec,
30	value::{Value, identity::IdentityId, row_number::RowNumber},
31};
32
33use crate::{
34	operator::{Operator, OperatorCell, stateful::raw::RawStatefulOperator},
35	transaction::FlowTransaction,
36};
37
38static EMPTY_PARAMS: Params = Params::None;
39static EMPTY_SYMBOL_TABLE: LazyLock<SymbolTable> = LazyLock::new(SymbolTable::new);
40
41static VISIBLE_MARKER: LazyLock<EncodedRow> = LazyLock::new(|| EncodedRow(CowVec::new(vec![1])));
42
43pub struct GateOperator {
44	parent: OperatorCell,
45	node: FlowNodeId,
46	compiled_conditions: Vec<CompiledExpr>,
47	routines: Routines,
48	runtime_context: RuntimeContext,
49}
50
51impl GateOperator {
52	pub fn new(
53		parent: OperatorCell,
54		node: FlowNodeId,
55		conditions: Vec<Expression>,
56		routines: Routines,
57		runtime_context: RuntimeContext,
58	) -> Self {
59		let compile_ctx = CompileContext {
60			symbols: &EMPTY_SYMBOL_TABLE,
61		};
62		let compiled_conditions: Vec<CompiledExpr> = conditions
63			.iter()
64			.map(|e| compile_expression(&compile_ctx, e).expect("Failed to compile gate condition"))
65			.collect();
66
67		Self {
68			parent,
69			node,
70			compiled_conditions,
71			routines,
72			runtime_context,
73		}
74	}
75
76	pub(crate) fn output_schema(&self) -> Option<Columns> {
77		self.parent.output_schema()
78	}
79
80	fn evaluate(&self, columns: &Columns) -> Result<Vec<bool>> {
81		let row_count = columns.row_count();
82		if row_count == 0 {
83			return Ok(Vec::new());
84		}
85
86		let session = EvalContext {
87			params: &EMPTY_PARAMS,
88			symbols: &EMPTY_SYMBOL_TABLE,
89			routines: &self.routines,
90			runtime_context: &self.runtime_context,
91			arena: None,
92			identity: IdentityId::root(),
93			is_aggregate_context: false,
94			columns: Columns::empty(),
95			row_count: 1,
96			target: None,
97			take: None,
98		};
99		let exec_ctx = session.with_eval(columns.clone(), row_count);
100
101		let mut mask = vec![true; row_count];
102
103		for compiled_condition in &self.compiled_conditions {
104			let result_col = compiled_condition.execute(&exec_ctx)?;
105
106			for (row_idx, mask_val) in mask.iter_mut().enumerate() {
107				if *mask_val {
108					match result_col.data().get_value(row_idx) {
109						Value::Boolean(true) => {}
110						Value::Boolean(false) => *mask_val = false,
111						_ => *mask_val = false,
112					}
113				}
114			}
115		}
116
117		Ok(mask)
118	}
119
120	fn row_number_key(rn: RowNumber) -> EncodedKey {
121		let mut bytes = Vec::with_capacity(1 + 8);
122		bytes.push(FlowNodeInternalStateKey::GATE_VISIBILITY_TAG);
123		bytes.extend_from_slice(&rn.0.to_be_bytes());
124		EncodedKey::new(bytes)
125	}
126
127	fn is_visible(&self, txn: &mut FlowTransaction, rn: RowNumber) -> Result<bool> {
128		Ok(self.internal_state_get(txn, &Self::row_number_key(rn))?.is_some())
129	}
130
131	fn mark_visible(&self, txn: &mut FlowTransaction, rn: RowNumber) -> Result<()> {
132		self.internal_state_set(txn, &Self::row_number_key(rn), VISIBLE_MARKER.clone())
133	}
134
135	fn mark_invisible(&self, txn: &mut FlowTransaction, rn: RowNumber) -> Result<()> {
136		self.internal_state_drop(txn, &Self::row_number_key(rn))
137	}
138}
139
140impl RawStatefulOperator for GateOperator {}
141
142impl Operator for GateOperator {
143	fn id(&self) -> FlowNodeId {
144		self.node
145	}
146
147	fn capabilities(&self) -> &[OperatorCapability] {
148		OperatorCapability::STANDARD
149	}
150
151	fn apply(&self, txn: &mut FlowTransaction, change: Change) -> Result<Change> {
152		let mut result = Vec::new();
153
154		for diff in change.diffs {
155			match diff {
156				Diff::Insert {
157					post,
158					..
159				} => self.apply_gate_insert(txn, &post, &mut result)?,
160				Diff::Update {
161					pre,
162					post,
163					..
164				} => self.apply_gate_update(txn, pre, post, &mut result)?,
165				Diff::Remove {
166					pre,
167					..
168				} => self.apply_gate_remove(txn, pre, &mut result)?,
169			}
170		}
171
172		Ok(Change::from_flow(self.node, change.version, result, change.changed_at))
173	}
174}
175
176impl GateOperator {
177	#[inline]
178	fn apply_gate_insert(&self, txn: &mut FlowTransaction, post: &Columns, result: &mut Vec<Diff>) -> Result<()> {
179		if post.row_numbers.is_empty() {
180			let mask = self.evaluate(post)?;
181			let passing_indices: Vec<usize> =
182				mask.iter().enumerate().filter(|&(_, pass)| *pass).map(|(idx, _)| idx).collect();
183			if !passing_indices.is_empty() {
184				result.push(Diff::insert(post.extract_by_indices(&passing_indices)));
185			}
186			return Ok(());
187		}
188
189		let mask = self.evaluate(post)?;
190		let mut passing_indices = Vec::new();
191		for (i, &pass) in mask.iter().enumerate() {
192			let rn = post.row_numbers[i];
193			if pass {
194				self.mark_visible(txn, rn)?;
195				passing_indices.push(i);
196			}
197		}
198		if !passing_indices.is_empty() {
199			result.push(Diff::insert(post.extract_by_indices(&passing_indices)));
200		}
201		Ok(())
202	}
203
204	#[inline]
205	fn apply_gate_update(
206		&self,
207		txn: &mut FlowTransaction,
208		pre: Columns,
209		post: Columns,
210		result: &mut Vec<Diff>,
211	) -> Result<()> {
212		if post.row_numbers.is_empty() {
213			result.push(Diff::Update {
214				pre,
215				post,
216				origin: None,
217			});
218			return Ok(());
219		}
220
221		let mask = self.evaluate(&post)?;
222		let mut update_indices = Vec::new();
223		let mut insert_indices = Vec::new();
224
225		for (i, (&rn, &mask_val)) in post.row_numbers.iter().zip(mask.iter()).enumerate() {
226			if self.is_visible(txn, rn)? {
227				update_indices.push(i);
228			} else if mask_val {
229				self.mark_visible(txn, rn)?;
230				insert_indices.push(i);
231			}
232		}
233
234		if !update_indices.is_empty() {
235			result.push(Diff::update(
236				pre.extract_by_indices(&update_indices),
237				post.extract_by_indices(&update_indices),
238			));
239		}
240		if !insert_indices.is_empty() {
241			result.push(Diff::insert(post.extract_by_indices(&insert_indices)));
242		}
243		Ok(())
244	}
245
246	#[inline]
247	fn apply_gate_remove(&self, txn: &mut FlowTransaction, pre: Columns, result: &mut Vec<Diff>) -> Result<()> {
248		if pre.row_numbers.is_empty() {
249			result.push(Diff::Remove {
250				pre,
251				origin: None,
252			});
253			return Ok(());
254		}
255
256		let mut remove_indices = Vec::new();
257		for i in 0..pre.row_numbers.len() {
258			let rn = pre.row_numbers[i];
259			if self.is_visible(txn, rn)? {
260				self.mark_invisible(txn, rn)?;
261				remove_indices.push(i);
262			}
263		}
264
265		if !remove_indices.is_empty() {
266			result.push(Diff::remove(pre.extract_by_indices(&remove_indices)));
267		}
268		Ok(())
269	}
270}