Skip to main content

reifydb_sub_flow/engine/
register.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::mem;
5
6use reifydb_core::{
7	common::{JoinType, WindowKind},
8	interface::{
9		catalog::{
10			flow::{FlowId, FlowNodeId},
11			id::{RingBufferId, SeriesId, TableId, ViewId},
12			series::SeriesKey,
13			shape::ShapeId,
14		},
15		identifier::{ColumnIdentifier, ColumnShape},
16	},
17	value::column::columns::Columns,
18	window::engine::LatePolicy,
19};
20use reifydb_rql::{
21	expression::{ColumnExpression, Expression},
22	flow::{
23		flow::FlowDag,
24		node::{
25			FlowNode,
26			FlowNodeType::{
27				Aggregate, Append, Apply, Distinct, Extend, Filter, Gate, Join, Map,
28				SinkRingBufferView, SinkSeriesView, SinkSubscription, SinkTableView, Sort,
29				SourceDictionary, SourceFlow, SourceInlineData, SourceRingBuffer, SourceSeries,
30				SourceTable, SourceView, Take, Window,
31			},
32		},
33	},
34};
35use reifydb_sdk::config::Config;
36use reifydb_transaction::transaction::{Transaction, command::CommandTransaction};
37use reifydb_value::{
38	Result,
39	error::Error,
40	fragment::Fragment,
41	reifydb_assertions,
42	value::{dictionary::DictionaryId, duration::Duration},
43};
44use tracing::instrument;
45
46use super::eval::evaluate_operator_config;
47#[cfg(reifydb_target = "native")]
48use crate::operator::apply::ApplyOperator;
49use crate::{
50	engine::FlowEngineInner,
51	error::FlowGraphError,
52	operator::{
53		OperatorCell, Operators,
54		append::AppendOperator,
55		distinct::operator::DistinctOperator,
56		extend::ExtendOperator,
57		filter::FilterOperator,
58		gate::GateOperator,
59		join::operator::{JoinOperator, JoinSideConfig},
60		map::MapOperator,
61		scan::{
62			dictionary::PrimitiveDictionaryOperator, flow::PrimitiveFlowOperator,
63			ringbuffer::PrimitiveRingBufferOperator, series::PrimitiveSeriesOperator,
64			table::PrimitiveTableOperator, view::PrimitiveViewOperator,
65		},
66		sink::{
67			ringbuffer_view::SinkRingBufferViewOperator, series_view::SinkSeriesViewOperator,
68			view::SinkTableViewOperator,
69		},
70		sort::SortOperator,
71		take::TakeOperator,
72		window::{
73			aggregate::AggregateOperator,
74			operator::{WindowConfig, WindowOperator},
75		},
76	},
77};
78
79impl FlowEngineInner {
80	#[instrument(name = "flow::register", level = "info", skip(self, txn), fields(flow_id = ?flow.id))]
81	pub fn register(&mut self, txn: &mut CommandTransaction, flow: FlowDag) -> Result<()> {
82		self.register_with_transaction(&mut Transaction::Command(txn), flow)
83	}
84
85	#[instrument(name = "flow::register_with_transaction", level = "info", skip(self, txn), fields(flow_id = ?flow.id))]
86	pub fn register_with_transaction(&mut self, txn: &mut Transaction<'_>, flow: FlowDag) -> Result<()> {
87		reifydb_assertions! {
88			assert!(!self.flows.contains_key(&flow.id), "Flow already registered");
89		}
90
91		let mut added: Vec<FlowNodeId> = Vec::new();
92		for node_id in flow.topological_order()? {
93			let node = flow.get_node(&node_id).unwrap();
94			if let Err(err) = self.add(txn, &flow, node) {
95				for id in &added {
96					self.operators.remove(id);
97				}
98				for entries in self.sources.values_mut() {
99					entries.retain(|(fid, _)| *fid != flow.id);
100				}
101				self.sources.retain(|_, v| !v.is_empty());
102				for entries in self.sinks.values_mut() {
103					entries.retain(|(fid, _)| *fid != flow.id);
104				}
105				self.sinks.retain(|_, v| !v.is_empty());
106				return Err(err);
107			}
108			added.push(node_id);
109		}
110
111		self.analyzer.add(flow.clone());
112		self.flows.insert(flow.id, flow.clone());
113		self.execution_level_cache.invalidate();
114		self.schedule_cache.invalidate();
115
116		Ok(())
117	}
118
119	#[instrument(name = "flow::add", level = "debug", skip(self, txn, flow), fields(flow_id = ?flow.id, node_id = ?node.id, node_type = ?mem::discriminant(&node.ty)))]
120	pub fn add(&mut self, txn: &mut Transaction<'_>, flow: &FlowDag, node: &FlowNode) -> Result<()> {
121		reifydb_assertions! {
122			assert!(!self.operators.contains_key(&node.id), "Operator already registered");
123		}
124		let node = node.clone();
125		let node_id = node.id;
126		let inputs = node.inputs;
127
128		match node.ty {
129			SourceInlineData {
130				..
131			} => unimplemented!(),
132			SourceTable {
133				table,
134			} => self.add_source_table(txn, flow, node_id, table)?,
135			SourceView {
136				view,
137			} => self.register_source_view(txn, flow, node_id, view)?,
138			SourceFlow {
139				flow: source_flow,
140			} => self.add_source_flow(txn, node_id, source_flow)?,
141			SourceRingBuffer {
142				ringbuffer,
143			} => self.add_source_ringbuffer(txn, flow, node_id, ringbuffer)?,
144			SourceSeries {
145				series,
146			} => self.add_source_series(txn, flow, node_id, series)?,
147			SourceDictionary {
148				dictionary,
149			} => self.add_source_dictionary(flow, node_id, dictionary),
150			SinkTableView {
151				view,
152				table,
153			} => self.add_sink_table_view(txn, flow, node_id, &inputs, view, table)?,
154			SinkRingBufferView {
155				view,
156				ringbuffer,
157				capacity,
158				propagate_evictions,
159			} => self.add_sink_ringbuffer_view(
160				txn,
161				flow,
162				node_id,
163				&inputs,
164				view,
165				ringbuffer,
166				capacity,
167				propagate_evictions,
168			)?,
169			SinkSeriesView {
170				view,
171				series,
172				key,
173			} => self.add_sink_series_view(txn, flow, node_id, &inputs, view, series, key)?,
174			SinkSubscription {
175				..
176			} => {
177				return Err(Error::from(FlowGraphError::UnsupportedNode {
178					kind: "SinkSubscription",
179				}));
180			}
181			Filter {
182				conditions,
183			} => self.add_filter(node_id, &inputs, conditions)?,
184			Gate {
185				conditions,
186			} => self.add_gate(node_id, &inputs, conditions)?,
187			Map {
188				expressions,
189			} => self.add_map(node_id, &inputs, expressions)?,
190			Extend {
191				expressions,
192			} => self.add_extend(node_id, &inputs, expressions)?,
193			Sort {
194				by: _,
195			} => self.add_sort(node_id, &inputs)?,
196			Take {
197				limit,
198			} => self.add_take(node_id, &inputs, limit)?,
199			Join {
200				join_type,
201				left,
202				right,
203				alias,
204				snapshot,
205				natural,
206				latest,
207			} => self.add_join(
208				txn, node_id, &inputs, join_type, left, right, alias, snapshot, natural, latest,
209			)?,
210			Distinct {
211				expressions,
212			} => self.add_distinct(txn, node_id, &inputs, expressions)?,
213			Append {} => self.add_append(txn, node_id, &inputs)?,
214			Apply {
215				operator,
216				expressions,
217			} => self.add_apply(node_id, &inputs, operator, expressions)?,
218			Aggregate {
219				by,
220				map,
221			} => self.add_aggregate(node_id, &inputs, by, map)?,
222			Window {
223				kind,
224				group_by,
225				aggregations,
226				ts,
227				lateness,
228				state_cache_size,
229				internal_state_cache_size,
230			} => self.add_window(
231				node_id,
232				&inputs,
233				kind,
234				group_by,
235				aggregations,
236				ts,
237				lateness,
238				state_cache_size,
239				internal_state_cache_size,
240			)?,
241		}
242
243		Ok(())
244	}
245
246	#[inline]
247	fn add_source_table(
248		&mut self,
249		txn: &mut Transaction<'_>,
250		flow: &FlowDag,
251		node_id: FlowNodeId,
252		table: TableId,
253	) -> Result<()> {
254		let table = self.catalog.get_table(&mut txn.reborrow(), table)?;
255
256		self.add_source(flow.id, node_id, ShapeId::table(table.id));
257		self.operators.insert(
258			node_id,
259			OperatorCell::new(Operators::SourceTable(PrimitiveTableOperator::new(node_id, table))),
260		);
261		Ok(())
262	}
263
264	#[inline]
265	fn add_source_flow(
266		&mut self,
267		txn: &mut Transaction<'_>,
268		node_id: FlowNodeId,
269		source_flow: FlowId,
270	) -> Result<()> {
271		let source_flow = self.catalog.get_flow(&mut txn.reborrow(), source_flow)?;
272		self.operators.insert(
273			node_id,
274			OperatorCell::new(Operators::SourceFlow(PrimitiveFlowOperator::new(node_id, source_flow))),
275		);
276		Ok(())
277	}
278
279	#[inline]
280	fn add_source_ringbuffer(
281		&mut self,
282		txn: &mut Transaction<'_>,
283		flow: &FlowDag,
284		node_id: FlowNodeId,
285		ringbuffer: RingBufferId,
286	) -> Result<()> {
287		let rb = self.catalog.get_ringbuffer(&mut txn.reborrow(), ringbuffer)?;
288		self.add_source(flow.id, node_id, ShapeId::ringbuffer(rb.id));
289		self.operators.insert(
290			node_id,
291			OperatorCell::new(Operators::SourceRingBuffer(PrimitiveRingBufferOperator::new(node_id, rb))),
292		);
293		Ok(())
294	}
295
296	#[inline]
297	fn add_source_series(
298		&mut self,
299		txn: &mut Transaction<'_>,
300		flow: &FlowDag,
301		node_id: FlowNodeId,
302		series: SeriesId,
303	) -> Result<()> {
304		let s = self.catalog.get_series(&mut txn.reborrow(), series)?;
305		self.add_source(flow.id, node_id, ShapeId::series(s.id));
306		self.operators.insert(
307			node_id,
308			OperatorCell::new(Operators::SourceSeries(PrimitiveSeriesOperator::new(node_id))),
309		);
310		Ok(())
311	}
312
313	#[inline]
314	fn add_source_dictionary(&mut self, flow: &FlowDag, node_id: FlowNodeId, dictionary: DictionaryId) {
315		self.add_source(flow.id, node_id, ShapeId::dictionary(dictionary));
316		self.operators.insert(
317			node_id,
318			OperatorCell::new(Operators::SourceDictionary(PrimitiveDictionaryOperator::new(node_id))),
319		);
320	}
321
322	#[inline]
323	fn add_sink_table_view(
324		&mut self,
325		txn: &mut Transaction<'_>,
326		flow: &FlowDag,
327		node_id: FlowNodeId,
328		inputs: &[FlowNodeId],
329		view: ViewId,
330		table: TableId,
331	) -> Result<()> {
332		let parent = self.parent(first_input(inputs)?)?;
333
334		self.add_sink(flow.id, node_id, ShapeId::view(*view));
335		let resolved = self.catalog.resolve_view(&mut txn.reborrow(), view)?;
336		self.operators.insert(
337			node_id,
338			OperatorCell::new(Operators::SinkTableView(SinkTableViewOperator::new(
339				parent, node_id, resolved, table,
340			))),
341		);
342		Ok(())
343	}
344
345	#[inline]
346	#[allow(clippy::too_many_arguments)]
347	fn add_sink_ringbuffer_view(
348		&mut self,
349		txn: &mut Transaction<'_>,
350		flow: &FlowDag,
351		node_id: FlowNodeId,
352		inputs: &[FlowNodeId],
353		view: ViewId,
354		ringbuffer: RingBufferId,
355		capacity: u64,
356		propagate_evictions: bool,
357	) -> Result<()> {
358		let parent = self.parent(first_input(inputs)?)?;
359		self.add_sink(flow.id, node_id, ShapeId::view(*view));
360		let resolved = self.catalog.resolve_view(&mut txn.reborrow(), view)?;
361		self.operators.insert(
362			node_id,
363			OperatorCell::new(Operators::SinkRingBufferView(SinkRingBufferViewOperator::new(
364				parent,
365				node_id,
366				resolved,
367				ringbuffer,
368				capacity,
369				propagate_evictions,
370			))),
371		);
372		Ok(())
373	}
374
375	#[inline]
376	#[allow(clippy::too_many_arguments)]
377	fn add_sink_series_view(
378		&mut self,
379		txn: &mut Transaction<'_>,
380		flow: &FlowDag,
381		node_id: FlowNodeId,
382		inputs: &[FlowNodeId],
383		view: ViewId,
384		series: SeriesId,
385		key: SeriesKey,
386	) -> Result<()> {
387		let parent = self.parent(first_input(inputs)?)?;
388		self.add_sink(flow.id, node_id, ShapeId::view(*view));
389		let resolved = self.catalog.resolve_view(&mut txn.reborrow(), view)?;
390		self.operators.insert(
391			node_id,
392			OperatorCell::new(Operators::SinkSeriesView(SinkSeriesViewOperator::new(
393				parent,
394				node_id,
395				resolved,
396				series,
397				key.clone(),
398			))),
399		);
400		Ok(())
401	}
402
403	#[inline]
404	fn add_filter(
405		&mut self,
406		node_id: FlowNodeId,
407		inputs: &[FlowNodeId],
408		conditions: Vec<Expression>,
409	) -> Result<()> {
410		let parent = self.parent(first_input(inputs)?)?;
411		self.operators.insert(
412			node_id,
413			OperatorCell::new(Operators::Filter(FilterOperator::new(
414				parent,
415				node_id,
416				conditions,
417				self.executor.routines.clone(),
418				self.runtime_context.clone(),
419			))),
420		);
421		Ok(())
422	}
423
424	#[inline]
425	fn add_gate(&mut self, node_id: FlowNodeId, inputs: &[FlowNodeId], conditions: Vec<Expression>) -> Result<()> {
426		let parent = self.parent(first_input(inputs)?)?;
427		self.operators.insert(
428			node_id,
429			OperatorCell::new(Operators::Gate(GateOperator::new(
430				parent,
431				node_id,
432				conditions,
433				self.executor.routines.clone(),
434				self.runtime_context.clone(),
435			))),
436		);
437		Ok(())
438	}
439
440	#[inline]
441	fn add_map(&mut self, node_id: FlowNodeId, inputs: &[FlowNodeId], expressions: Vec<Expression>) -> Result<()> {
442		let parent = self.parent(first_input(inputs)?)?;
443		self.operators.insert(
444			node_id,
445			OperatorCell::new(Operators::Map(MapOperator::new(
446				parent,
447				node_id,
448				expressions,
449				self.executor.routines.clone(),
450				self.runtime_context.clone(),
451			))),
452		);
453		Ok(())
454	}
455
456	#[inline]
457	fn add_extend(
458		&mut self,
459		node_id: FlowNodeId,
460		inputs: &[FlowNodeId],
461		expressions: Vec<Expression>,
462	) -> Result<()> {
463		let parent = self.parent(first_input(inputs)?)?;
464		self.operators.insert(
465			node_id,
466			OperatorCell::new(Operators::Extend(ExtendOperator::new(
467				parent,
468				node_id,
469				expressions,
470				self.executor.routines.clone(),
471				self.runtime_context.clone(),
472			))),
473		);
474		Ok(())
475	}
476
477	#[inline]
478	fn add_sort(&mut self, node_id: FlowNodeId, inputs: &[FlowNodeId]) -> Result<()> {
479		let parent = self.parent(first_input(inputs)?)?;
480		self.operators.insert(
481			node_id,
482			OperatorCell::new(Operators::Sort(SortOperator::new(parent, node_id, Vec::new()))),
483		);
484		Ok(())
485	}
486
487	#[inline]
488	fn add_take(&mut self, node_id: FlowNodeId, inputs: &[FlowNodeId], limit: usize) -> Result<()> {
489		let parent = self.parent(first_input(inputs)?)?;
490		self.operators
491			.insert(node_id, OperatorCell::new(Operators::Take(TakeOperator::new(parent, node_id, limit))));
492		Ok(())
493	}
494
495	#[inline]
496	#[allow(clippy::too_many_arguments)]
497	fn add_join(
498		&mut self,
499		txn: &mut Transaction<'_>,
500		node_id: FlowNodeId,
501		inputs: &[FlowNodeId],
502		join_type: JoinType,
503		left: Vec<Expression>,
504		right: Vec<Expression>,
505		alias: Option<String>,
506		snapshot: bool,
507		natural: bool,
508		latest: bool,
509	) -> Result<()> {
510		if inputs.len() != 2 {
511			return Err(Error::from(FlowGraphError::NodeInputArity {
512				node: "Join",
513				expected: "exactly 2",
514				found: inputs.len(),
515			}));
516		}
517
518		let left_node = inputs[0];
519		let right_node = inputs[1];
520
521		let left_parent = self
522			.operators
523			.get(&left_node)
524			.ok_or_else(|| {
525				Error::from(FlowGraphError::ParentOperatorNotFound {
526					input: "left parent".to_string(),
527				})
528			})?
529			.clone();
530
531		let right_parent = self
532			.operators
533			.get(&right_node)
534			.ok_or_else(|| {
535				Error::from(FlowGraphError::ParentOperatorNotFound {
536					input: "right parent".to_string(),
537				})
538			})?
539			.clone();
540
541		let left_schema = left_parent.output_schema().unwrap_or_default();
542		let right_schema =
543			right_parent.output_schema().expect("right side of join must have a statically known schema");
544
545		let (left_exprs, right_exprs) = if natural {
546			let common = common_column_names(&left_schema, &right_schema);
547			let keys: Vec<Expression> = common.iter().map(|name| natural_key_expr(name)).collect();
548			(keys.clone(), keys)
549		} else {
550			(left, right)
551		};
552
553		let join_ttl = self.catalog.find_operator_settings(txn, node_id)?.and_then(|s| s.join);
554		let left = join_ttl.as_ref().and_then(|j| j.left.as_ref());
555		let left_ttl = left.map(|t| t.duration);
556		let right = join_ttl.as_ref().and_then(|j| j.right.as_ref());
557		let right_ttl = right.map(|t| t.duration);
558
559		self.operators.insert(
560			node_id,
561			OperatorCell::new(Operators::Join(JoinOperator::new(
562				JoinSideConfig {
563					schema: left_schema,
564					node: left_node,
565					exprs: left_exprs,
566				},
567				JoinSideConfig {
568					schema: right_schema,
569					node: right_node,
570					exprs: right_exprs,
571				},
572				node_id,
573				join_type,
574				alias,
575				self.executor.clone(),
576				snapshot,
577				natural,
578				latest,
579				left_ttl,
580				right_ttl,
581			))),
582		);
583		Ok(())
584	}
585
586	#[inline]
587	fn add_distinct(
588		&mut self,
589		txn: &mut Transaction<'_>,
590		node_id: FlowNodeId,
591		inputs: &[FlowNodeId],
592		expressions: Vec<Expression>,
593	) -> Result<()> {
594		let parent = self.parent(first_input(inputs)?)?;
595		let ttl = self.catalog.find_operator_settings(txn, node_id)?.and_then(|s| s.ttl);
596		self.operators.insert(
597			node_id,
598			OperatorCell::new(Operators::Distinct(DistinctOperator::new(
599				parent,
600				node_id,
601				expressions,
602				self.executor.routines.clone(),
603				self.runtime_context.clone(),
604				ttl.map(|t| {
605					t.duration.as_nanos().expect("operator ttl duration fits in i64 nanoseconds")
606						as u64
607				}),
608			))),
609		);
610		Ok(())
611	}
612
613	#[inline]
614	fn add_append(&mut self, txn: &mut Transaction<'_>, node_id: FlowNodeId, inputs: &[FlowNodeId]) -> Result<()> {
615		if inputs.len() < 2 {
616			return Err(Error::from(FlowGraphError::NodeInputArity {
617				node: "Append",
618				expected: "at least 2",
619				found: inputs.len(),
620			}));
621		}
622
623		let mut parents = Vec::with_capacity(inputs.len());
624
625		for input_node_id in inputs {
626			let parent = self
627				.operators
628				.get(input_node_id)
629				.ok_or_else(|| {
630					Error::from(FlowGraphError::ParentOperatorNotFound {
631						input: format!("{:?}", input_node_id),
632					})
633				})?
634				.clone();
635			parents.push(parent);
636		}
637
638		let ttl = self.catalog.find_operator_settings(txn, node_id)?.and_then(|s| s.ttl);
639		let ttl_nanos = ttl
640			.as_ref()
641			.map(|t| t.duration.as_nanos().expect("operator ttl duration fits in i64 nanoseconds") as u64);
642
643		self.operators.insert(
644			node_id,
645			OperatorCell::new(Operators::Append(AppendOperator::new(
646				node_id,
647				parents,
648				inputs.to_vec(),
649				ttl_nanos,
650				self.executor.runtime_context.version_epoch.clone(),
651			))),
652		);
653		Ok(())
654	}
655
656	#[inline]
657	fn add_apply(
658		&mut self,
659		node_id: FlowNodeId,
660		inputs: &[FlowNodeId],
661		operator: String,
662		expressions: Vec<Expression>,
663	) -> Result<()> {
664		let config = evaluate_operator_config(
665			expressions.as_slice(),
666			&self.executor.routines,
667			&self.runtime_context,
668		)?;
669		let cfg = Config::new(operator.as_str(), config.clone());
670
671		if let Some(factory) = self.custom_operators.get(operator.as_str()) {
672			let op = factory(node_id, &cfg)?;
673			self.operators.insert(node_id, OperatorCell::new(Operators::Custom(op)));
674		} else {
675			#[cfg(reifydb_target = "native")]
676			{
677				let parent = self.parent(first_input(inputs)?)?;
678
679				let inner = if self.is_native_operator(operator.as_str()) {
680					self.create_native_operator(operator.as_str(), node_id, &cfg)?
681				} else if self.is_ffi_operator(operator.as_str()) {
682					self.create_ffi_operator(operator.as_str(), node_id, &config)?
683				} else {
684					return Err(Error::from(FlowGraphError::UnknownOperator {
685						operator: operator.to_string(),
686					}));
687				};
688
689				self.operators.insert(
690					node_id,
691					OperatorCell::new(Operators::Apply(ApplyOperator::new(parent, node_id, inner))),
692				);
693			}
694			#[cfg(not(reifydb_target = "native"))]
695			{
696				let _ = (operator, inputs);
697
698				return Err(Error::from(FlowGraphError::FfiUnsupportedOnWasm));
699			}
700		}
701		Ok(())
702	}
703
704	#[inline]
705	#[allow(clippy::too_many_arguments)]
706	fn add_window(
707		&mut self,
708		node_id: FlowNodeId,
709		inputs: &[FlowNodeId],
710		kind: WindowKind,
711		group_by: Vec<Expression>,
712		aggregations: Vec<Expression>,
713		ts: Option<String>,
714		lateness: Option<Duration>,
715		state_cache_size: Option<usize>,
716		internal_state_cache_size: Option<usize>,
717	) -> Result<()> {
718		let parent = self.parent(first_input(inputs)?)?;
719		let operator = WindowOperator::new(WindowConfig {
720			parent,
721			node: node_id,
722			kind: kind.clone(),
723			group_by: group_by.clone(),
724			aggregations: aggregations.clone(),
725			ts: ts.clone(),
726			runtime_context: self.runtime_context.clone(),
727			routines: self.executor.routines.clone(),
728			late_policy: LatePolicy::Process,
729			lateness,
730			state_cache_size,
731			internal_state_cache_size,
732		});
733		self.operators.insert(node_id, OperatorCell::new(Operators::Window(operator)));
734		Ok(())
735	}
736
737	#[inline]
738	fn add_aggregate(
739		&mut self,
740		node_id: FlowNodeId,
741		inputs: &[FlowNodeId],
742		by: Vec<Expression>,
743		map: Vec<Expression>,
744	) -> Result<()> {
745		let parent = self.parent(first_input(inputs)?)?;
746		self.operators.insert(
747			node_id,
748			OperatorCell::new(Operators::Aggregate(AggregateOperator::new(
749				parent,
750				node_id,
751				by,
752				map,
753				self.executor.routines.clone(),
754				self.runtime_context.clone(),
755			))),
756		);
757		Ok(())
758	}
759
760	fn parent(&self, input: FlowNodeId) -> Result<OperatorCell> {
761		Ok(self.operators
762			.get(&input)
763			.ok_or_else(|| {
764				Error::from(FlowGraphError::ParentOperatorNotFound {
765					input: format!("{:?}", input),
766				})
767			})?
768			.clone())
769	}
770
771	#[inline]
772	fn register_source_view(
773		&mut self,
774		txn: &mut Transaction<'_>,
775		flow: &FlowDag,
776		node_id: FlowNodeId,
777		view: ViewId,
778	) -> Result<()> {
779		let view = self.catalog.get_view(&mut txn.reborrow(), view)?;
780		self.add_source(flow.id, node_id, ShapeId::view(view.id()));
781
782		self.add_source(flow.id, node_id, view.underlying_id());
783
784		self.operators.insert(
785			node_id,
786			OperatorCell::new(Operators::SourceView(PrimitiveViewOperator::new(node_id, view))),
787		);
788		Ok(())
789	}
790
791	pub fn add_source(&mut self, flow: FlowId, node: FlowNodeId, shape: ShapeId) {
792		let nodes = self.sources.entry(shape).or_default();
793
794		let entry = (flow, node);
795		if !nodes.contains(&entry) {
796			nodes.push(entry);
797		}
798	}
799
800	pub fn add_sink(&mut self, flow: FlowId, node: FlowNodeId, sink: ShapeId) {
801		let nodes = self.sinks.entry(sink).or_default();
802
803		let entry = (flow, node);
804		if !nodes.contains(&entry) {
805			nodes.push(entry);
806		}
807	}
808}
809
810fn first_input(inputs: &[FlowNodeId]) -> Result<FlowNodeId> {
811	inputs.first().copied().ok_or_else(|| Error::from(FlowGraphError::MissingInputEdge))
812}
813
814fn common_column_names(left: &Columns, right: &Columns) -> Vec<String> {
815	let right_names: Vec<String> = right.names.iter().map(|n| n.text().to_string()).collect();
816	left.names.iter().map(|n| n.text().to_string()).filter(|name| right_names.contains(name)).collect()
817}
818
819fn natural_key_expr(name: &str) -> Expression {
820	Expression::Column(ColumnExpression(ColumnIdentifier {
821		shape: ColumnShape::Qualified {
822			namespace: Fragment::internal("_context"),
823			name: Fragment::internal("_context"),
824		},
825		name: Fragment::internal(name),
826	}))
827}