1use reifydb_catalog::{catalog::Catalog, store::operator_settings::create::create_operator_settings};
5use reifydb_core::{
6 error::diagnostic::{
7 flow::{
8 flow_ephemeral_id_capacity_exceeded, flow_remote_source_unsupported,
9 flow_sort_must_be_terminal, flow_source_required,
10 },
11 subscription::subscription_operation_unsupported,
12 },
13 interface::catalog::{
14 flow::{FlowEdge, FlowEdgeId, FlowId, FlowNode, FlowNodeId},
15 id::SubscriptionId,
16 view::View,
17 },
18 internal,
19 row::{JoinTtl, OperatorSettings, Ttl},
20};
21use reifydb_routine::routine::registry::Routines;
22use reifydb_rql::{
23 flow::{
24 flow::{FlowBuilder, FlowDag},
25 node::{self, FlowNodeType},
26 },
27 query::QueryPlan,
28};
29use reifydb_value::{Result, error::Error, value::blob::Blob};
30
31pub mod operator;
32pub mod primitive;
33
34use postcard::to_stdvec;
35use reifydb_transaction::transaction::{Transaction, admin::AdminTransaction};
36
37use crate::flow::compiler::{
38 operator::{
39 aggregate::AggregateCompiler, append::AppendCompiler, apply::ApplyCompiler, distinct::DistinctCompiler,
40 extend::ExtendCompiler, filter::FilterCompiler, gate::GateCompiler, join::JoinCompiler,
41 map::MapCompiler, sort::SortCompiler, take::TakeCompiler, window::WindowCompiler,
42 },
43 primitive::{
44 dictionary_scan::DictionaryScanCompiler, inline_data::InlineDataCompiler,
45 ringbuffer_scan::RingBufferScanCompiler, series_scan::SeriesScanCompiler,
46 table_scan::TableScanCompiler, view_scan::ViewScanCompiler,
47 },
48};
49
50pub fn compile_flow(
51 catalog: &Catalog,
52 routines: &Routines,
53 txn: &mut AdminTransaction,
54 plan: QueryPlan,
55 sink: Option<&View>,
56 flow_id: FlowId,
57) -> Result<FlowDag> {
58 let compiler = FlowCompiler::new(catalog.clone(), routines.clone(), flow_id);
59 compiler.compile(&mut Transaction::Admin(txn), plan, sink)
60}
61
62pub fn compile_subscription_flow_ephemeral(
63 catalog: &Catalog,
64 routines: &Routines,
65 txn: &mut Transaction<'_>,
66 plan: QueryPlan,
67 subscription_id: SubscriptionId,
68 flow_id: FlowId,
69) -> Result<FlowDag> {
70 let compiler = FlowCompiler::new_ephemeral(catalog.clone(), routines.clone(), flow_id);
71 compiler.compile_with_subscription_id(txn, plan, subscription_id)
72}
73
74pub(crate) struct FlowCompiler {
75 pub(crate) catalog: Catalog,
76
77 pub(crate) routines: Routines,
78
79 builder: FlowBuilder,
80
81 pub(crate) sink: Option<View>,
82
83 ephemeral: bool,
84
85 local_node_counter: u64,
86
87 local_edge_counter: u64,
88
89 local_id_limit: u64,
90}
91
92impl FlowCompiler {
93 pub fn new(catalog: Catalog, routines: Routines, flow_id: FlowId) -> Self {
94 Self {
95 catalog,
96 routines,
97 builder: FlowDag::builder(flow_id),
98 sink: None,
99 ephemeral: false,
100 local_node_counter: 0,
101 local_edge_counter: 0,
102 local_id_limit: 0,
103 }
104 }
105
106 pub fn new_ephemeral(catalog: Catalog, routines: Routines, flow_id: FlowId) -> Self {
107 let base = flow_id.0 * 100;
108 Self {
109 catalog,
110 routines,
111 builder: FlowDag::builder(flow_id),
112 sink: None,
113 ephemeral: true,
114 local_node_counter: base,
115 local_edge_counter: base,
116 local_id_limit: base + 99,
117 }
118 }
119
120 fn next_node_id(&mut self, txn: &mut Transaction<'_>) -> Result<FlowNodeId> {
121 if self.ephemeral {
122 if self.local_node_counter >= self.local_id_limit {
123 return Err(Error(Box::new(flow_ephemeral_id_capacity_exceeded(self.builder.id().0))));
124 }
125 self.local_node_counter += 1;
126 Ok(FlowNodeId(self.local_node_counter))
127 } else {
128 self.catalog.next_flow_node_id(txn.admin_mut())
129 }
130 }
131
132 fn next_edge_id(&mut self, txn: &mut Transaction<'_>) -> Result<FlowEdgeId> {
133 if self.ephemeral {
134 if self.local_edge_counter >= self.local_id_limit {
135 return Err(Error(Box::new(flow_ephemeral_id_capacity_exceeded(self.builder.id().0))));
136 }
137 self.local_edge_counter += 1;
138 Ok(FlowEdgeId(self.local_edge_counter))
139 } else {
140 self.catalog.next_flow_edge_id(txn.admin_mut())
141 }
142 }
143
144 pub(crate) fn add_edge(&mut self, txn: &mut Transaction<'_>, from: &FlowNodeId, to: &FlowNodeId) -> Result<()> {
145 let edge_id = self.next_edge_id(txn)?;
146 let flow_id = self.builder.id();
147
148 if !self.ephemeral {
149 let edge_def = FlowEdge {
150 id: edge_id,
151 flow: flow_id,
152 source: *from,
153 target: *to,
154 };
155
156 self.catalog.create_flow_edge(txn.admin_mut(), &edge_def)?;
157 }
158
159 self.builder.add_edge(node::FlowEdge::new(edge_id, *from, *to))?;
160 Ok(())
161 }
162
163 pub(crate) fn add_node(&mut self, txn: &mut Transaction<'_>, node_type: FlowNodeType) -> Result<FlowNodeId> {
164 let node_id = self.next_node_id(txn)?;
165 let flow_id = self.builder.id();
166
167 if !self.ephemeral {
168 let data = to_stdvec(&node_type)
169 .map_err(|e| Error(Box::new(internal!("Failed to serialize FlowNodeType: {}", e))))?;
170
171 let node_def = FlowNode {
172 id: node_id,
173 flow: flow_id,
174 node_type: node_type.discriminator(),
175 data: Blob::from(data),
176 };
177
178 self.catalog.create_flow_node(txn.admin_mut(), &node_def)?;
179 }
180
181 self.builder.add_node(node::FlowNode::new(node_id, node_type));
182 Ok(node_id)
183 }
184
185 pub(crate) fn write_operator_settings(
186 &self,
187 txn: &mut Transaction<'_>,
188 node_id: FlowNodeId,
189 ttl: Option<Ttl>,
190 ) -> Result<()> {
191 if self.ephemeral {
192 return Ok(());
193 }
194 if let Some(ttl) = ttl {
195 create_operator_settings(
196 txn.admin_mut(),
197 node_id,
198 &OperatorSettings {
199 ttl: Some(ttl),
200 join: None,
201 },
202 )?;
203 }
204 Ok(())
205 }
206
207 pub(crate) fn write_operator_settings_join(
208 &self,
209 txn: &mut Transaction<'_>,
210 node_id: FlowNodeId,
211 join: Option<JoinTtl>,
212 ) -> Result<()> {
213 if self.ephemeral {
214 return Ok(());
215 }
216 let Some(join) = join else {
217 return Ok(());
218 };
219 if join.left.is_none() && join.right.is_none() {
220 return Ok(());
221 }
222 create_operator_settings(
223 txn.admin_mut(),
224 node_id,
225 &OperatorSettings {
226 ttl: None,
227 join: Some(join),
228 },
229 )?;
230 Ok(())
231 }
232
233 pub(crate) fn compile(
234 mut self,
235 txn: &mut Transaction<'_>,
236 plan: QueryPlan,
237 sink: Option<&View>,
238 ) -> Result<FlowDag> {
239 validate_sort_terminal(&plan)?;
240 self.sink = sink.cloned();
241 let root_node_id = self.compile_plan(txn, plan)?;
242
243 if let Some(sink_view) = sink {
244 self.attach_sink_node(txn, sink_view, &root_node_id)?;
245 }
246
247 self.build_validated_flow()
248 }
249
250 #[inline]
251 fn attach_sink_node(
252 &mut self,
253 txn: &mut Transaction<'_>,
254 sink_view: &View,
255 root_node_id: &FlowNodeId,
256 ) -> Result<()> {
257 let node_type = match sink_view {
258 View::Table(t) => FlowNodeType::SinkTableView {
259 view: sink_view.id(),
260 table: t.underlying,
261 },
262 View::RingBuffer(rb) => FlowNodeType::SinkRingBufferView {
263 view: sink_view.id(),
264 ringbuffer: rb.underlying,
265 capacity: rb.capacity,
266 propagate_evictions: rb.propagate_evictions,
267 },
268 View::Series(s) => FlowNodeType::SinkSeriesView {
269 view: sink_view.id(),
270 series: s.underlying,
271 key: s.key.clone(),
272 },
273 };
274 let result_node = self.add_node(txn, node_type)?;
275 self.add_edge(txn, root_node_id, &result_node)
276 }
277
278 #[inline]
279 fn build_validated_flow(self) -> Result<FlowDag> {
280 let flow = self.builder.build();
281
282 if !has_real_source(&flow) {
283 return Err(Error(Box::new(flow_source_required())));
284 }
285
286 Ok(flow)
287 }
288
289 pub(crate) fn compile_with_subscription_id(
290 mut self,
291 txn: &mut Transaction<'_>,
292 plan: QueryPlan,
293 subscription_id: SubscriptionId,
294 ) -> Result<FlowDag> {
295 validate_subscription_plan(&plan)?;
296 let root_node_id = self.compile_plan(txn, plan)?;
297
298 let result_node = self.add_node(
299 txn,
300 FlowNodeType::SinkSubscription {
301 subscription: subscription_id,
302 },
303 )?;
304
305 self.add_edge(txn, &root_node_id, &result_node)?;
306
307 let flow = self.builder.build();
308
309 if !has_real_source(&flow) {
310 return Err(Error(Box::new(flow_source_required())));
311 }
312
313 Ok(flow)
314 }
315
316 pub(crate) fn compile_plan(&mut self, txn: &mut Transaction<'_>, plan: QueryPlan) -> Result<FlowNodeId> {
317 match plan {
318 QueryPlan::IndexScan(_index_scan) => {
319 unimplemented!("IndexScan compilation not yet implemented for flow")
321 }
322 QueryPlan::TableScan(table_scan) => TableScanCompiler::from(table_scan).compile(self, txn),
323 QueryPlan::ViewScan(view_scan) => ViewScanCompiler::from(view_scan).compile(self, txn),
324 QueryPlan::InlineData(inline_data) => InlineDataCompiler::from(inline_data).compile(self, txn),
325 QueryPlan::Filter(filter) => FilterCompiler::from(filter).compile(self, txn),
326 QueryPlan::Gate(gate) => GateCompiler::from(gate).compile(self, txn),
327 QueryPlan::Map(map) => MapCompiler::from(map).compile(self, txn),
328 QueryPlan::Extend(extend) => ExtendCompiler::from(extend).compile(self, txn),
329 QueryPlan::Apply(apply) => ApplyCompiler::from(apply).compile(self, txn),
330 QueryPlan::Aggregate(aggregate) => AggregateCompiler::from(aggregate).compile(self, txn),
331 QueryPlan::Distinct(distinct) => DistinctCompiler::from(distinct).compile(self, txn),
332 QueryPlan::Take(take) => TakeCompiler::from(take).compile(self, txn),
333 QueryPlan::Sort(sort) => SortCompiler::from(sort).compile(self, txn),
334 QueryPlan::JoinInner(join) => JoinCompiler::from(join).compile(self, txn),
335 QueryPlan::JoinLeft(join) => JoinCompiler::from(join).compile(self, txn),
336 QueryPlan::JoinNatural(join) => JoinCompiler::from(join).compile(self, txn),
337 QueryPlan::Append(append) => AppendCompiler::from(append).compile(self, txn),
338 QueryPlan::Patch(_) => {
339 unimplemented!("Patch compilation not yet implemented for flow")
340 }
341 QueryPlan::TableVirtualScan(_scan) => {
342 unimplemented!("VirtualScan compilation not yet implemented")
344 }
345 QueryPlan::RingBufferScan(scan) => RingBufferScanCompiler::from(scan).compile(self, txn),
346 QueryPlan::Generator(_generator) => {
347 unimplemented!("Generator compilation not yet implemented for flow")
349 }
350 QueryPlan::Window(window) => WindowCompiler::from(window).compile(self, txn),
351 QueryPlan::Variable(_) => {
352 panic!("Variable references are not supported in flow graphs");
353 }
354 QueryPlan::Scalarize(_) => {
355 panic!("Scalarize operations are not supported in flow graphs");
356 }
357 QueryPlan::Environment(_) => {
358 panic!("Environment operations are not supported in flow graphs");
359 }
360 QueryPlan::RowPointLookup(_) => {
361 unimplemented!("RowPointLookup compilation not yet implemented for flow")
363 }
364 QueryPlan::RowListLookup(_) => {
365 unimplemented!("RowListLookup compilation not yet implemented for flow")
367 }
368 QueryPlan::RowRangeScan(_) => {
369 unimplemented!("RowRangeScan compilation not yet implemented for flow")
371 }
372 QueryPlan::DictionaryScan(dictionary_scan) => {
373 DictionaryScanCompiler::from(dictionary_scan).compile(self, txn)
374 }
375 QueryPlan::Assert(_) => {
376 unimplemented!("Assert compilation not yet implemented for flow")
377 }
378 QueryPlan::SeriesScan(series_scan) => SeriesScanCompiler::from(series_scan).compile(self, txn),
379 QueryPlan::RemoteScan(_) => Err(Error(Box::new(flow_remote_source_unsupported()))),
380 QueryPlan::RunTests(_) => {
381 panic!("RunTests is not supported in flow graphs");
382 }
383 QueryPlan::CallFunction(_) => {
384 panic!("CallFunction is not supported in flow graphs");
385 }
386 }
387 }
388}
389
390fn validate_subscription_plan(plan: &QueryPlan) -> Result<()> {
391 match plan {
392 QueryPlan::Filter(n) => validate_subscription_plan(&n.input),
393 QueryPlan::Gate(n) => validate_subscription_plan(&n.input),
394 QueryPlan::Take(n) => validate_subscription_plan(&n.input),
395 QueryPlan::Distinct(n) => validate_subscription_plan(&n.input),
396 QueryPlan::Map(n) => match &n.input {
397 Some(input) => validate_subscription_plan(input),
398 None => Ok(()),
399 },
400 QueryPlan::Extend(n) => match &n.input {
401 Some(input) => validate_subscription_plan(input),
402 None => Ok(()),
403 },
404 QueryPlan::TableScan(_)
405 | QueryPlan::ViewScan(_)
406 | QueryPlan::RingBufferScan(_)
407 | QueryPlan::SeriesScan(_)
408 | QueryPlan::DictionaryScan(_)
409 | QueryPlan::InlineData(_) => Ok(()),
410 other => Err(Error(Box::new(subscription_operation_unsupported(other.name())))),
411 }
412}
413
414fn validate_sort_terminal(plan: &QueryPlan) -> Result<()> {
415 let has_deeper_sort = match plan {
416 QueryPlan::Sort(n) => contains_sort(&n.input),
417 other => contains_sort(other),
418 };
419 if has_deeper_sort {
420 return Err(Error(Box::new(flow_sort_must_be_terminal())));
421 }
422 Ok(())
423}
424
425fn contains_sort(plan: &QueryPlan) -> bool {
426 matches!(plan, QueryPlan::Sort(_)) || child_plans(plan).iter().any(|child| contains_sort(child))
427}
428
429fn child_plans(plan: &QueryPlan) -> Vec<&QueryPlan> {
430 match plan {
431 QueryPlan::Filter(n) => vec![&n.input],
432 QueryPlan::Gate(n) => vec![&n.input],
433 QueryPlan::Aggregate(n) => vec![&n.input],
434 QueryPlan::Distinct(n) => vec![&n.input],
435 QueryPlan::Sort(n) => vec![&n.input],
436 QueryPlan::Take(n) => vec![&n.input],
437 QueryPlan::Scalarize(n) => vec![&n.input],
438 QueryPlan::Map(n) => n.input.as_deref().into_iter().collect(),
439 QueryPlan::Extend(n) => n.input.as_deref().into_iter().collect(),
440 QueryPlan::Patch(n) => n.input.as_deref().into_iter().collect(),
441 QueryPlan::Apply(n) => n.input.as_deref().into_iter().collect(),
442 QueryPlan::Assert(n) => n.input.as_deref().into_iter().collect(),
443 QueryPlan::Window(n) => n.input.as_deref().into_iter().collect(),
444 QueryPlan::JoinInner(n) => vec![&n.left, &n.right],
445 QueryPlan::JoinLeft(n) => vec![&n.left, &n.right],
446 QueryPlan::JoinNatural(n) => vec![&n.left, &n.right],
447 QueryPlan::Append(n) => vec![&n.left, &n.right],
448 QueryPlan::RemoteScan(_)
449 | QueryPlan::TableScan(_)
450 | QueryPlan::TableVirtualScan(_)
451 | QueryPlan::ViewScan(_)
452 | QueryPlan::RingBufferScan(_)
453 | QueryPlan::DictionaryScan(_)
454 | QueryPlan::SeriesScan(_)
455 | QueryPlan::IndexScan(_)
456 | QueryPlan::RowPointLookup(_)
457 | QueryPlan::RowListLookup(_)
458 | QueryPlan::RowRangeScan(_)
459 | QueryPlan::InlineData(_)
460 | QueryPlan::Generator(_)
461 | QueryPlan::Variable(_)
462 | QueryPlan::Environment(_)
463 | QueryPlan::RunTests(_)
464 | QueryPlan::CallFunction(_) => vec![],
465 }
466}
467
468fn has_real_source(flow: &FlowDag) -> bool {
469 flow.get_node_ids().any(|node_id| {
470 if let Some(node) = flow.get_node(&node_id) {
471 matches!(
472 node.ty,
473 FlowNodeType::SourceTable { .. }
474 | FlowNodeType::SourceView { .. } | FlowNodeType::SourceFlow { .. }
475 | FlowNodeType::SourceRingBuffer { .. }
476 | FlowNodeType::SourceSeries { .. } | FlowNodeType::SourceDictionary { .. }
477 )
478 } else {
479 false
480 }
481 })
482}
483
484pub(crate) trait CompileOperator {
485 fn compile(self, compiler: &mut FlowCompiler, txn: &mut Transaction<'_>) -> Result<FlowNodeId>;
486}