Skip to main content

reifydb_engine/vm/volcano/
inline.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	collections::{BTreeSet, HashMap, HashSet},
6	mem,
7	sync::Arc,
8};
9
10use reifydb_core::{
11	interface::{catalog::sumtype::SumType, evaluate::TargetColumn, resolved::ResolvedShape},
12	value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns, headers::ColumnHeaders},
13};
14use reifydb_rql::expression::{AliasExpression, ConstantExpression, Expression, IdentExpression};
15use reifydb_transaction::transaction::Transaction;
16use reifydb_value::{
17	fragment::Fragment,
18	reifydb_assertions,
19	value::{Value, constraint::Constraint, value_type::ValueType},
20};
21
22use crate::{
23	Result,
24	expression::{cast::cast_column_data, context::EvalContext, eval::evaluate},
25	vm::volcano::query::{QueryContext, QueryNode},
26};
27
28pub(crate) struct InlineDataNode {
29	rows: Vec<Vec<AliasExpression>>,
30	headers: Option<ColumnHeaders>,
31	context: Option<Arc<QueryContext>>,
32	executed: bool,
33}
34
35impl InlineDataNode {
36	pub fn new(rows: Vec<Vec<AliasExpression>>, context: Arc<QueryContext>) -> Self {
37		let cloned_context = context.clone();
38		let headers = cloned_context.source.as_ref().map(|source| {
39			let mut layout = Self::create_columns_layout_from_source(source);
40
41			if matches!(source, ResolvedShape::Series(_)) {
42				let existing: HashSet<String> =
43					layout.columns.iter().map(|c| c.text().to_string()).collect();
44				for row in &rows {
45					for alias in row {
46						let name = alias.alias.0.text().to_string();
47						if !existing.contains(&name) {
48							layout.columns.push(Fragment::internal(&name));
49						}
50					}
51				}
52			}
53			layout
54		});
55
56		Self {
57			rows,
58			headers,
59			context: Some(context),
60			executed: false,
61		}
62	}
63
64	fn create_columns_layout_from_source(source: &ResolvedShape) -> ColumnHeaders {
65		ColumnHeaders {
66			columns: source.columns().iter().map(|col| Fragment::internal(&col.name)).collect(),
67		}
68	}
69
70	fn expand_sumtype_constructors<'a>(&mut self, txn: &mut Transaction<'a>) -> Result<()> {
71		let Some(ctx) = self.context.as_ref().cloned() else {
72			return Ok(());
73		};
74		if !rows_need_sumtype_expansion(&self.rows) {
75			return Ok(());
76		}
77		for row in &mut self.rows {
78			let original = mem::take(row);
79			let mut expanded = Vec::with_capacity(original.len());
80			for alias_expr in original {
81				match alias_expr.expression.as_ref() {
82					Expression::SumTypeConstructor(_) => {
83						expand_sumtype_ctor(&ctx, txn, alias_expr, &mut expanded)?;
84					}
85					Expression::Column(_) => {
86						expand_unit_variant_column(&ctx, txn, alias_expr, &mut expanded)?;
87					}
88					_ => expanded.push(alias_expr),
89				}
90			}
91			*row = expanded;
92		}
93		Ok(())
94	}
95}
96
97#[inline]
98fn rows_need_sumtype_expansion(rows: &[Vec<AliasExpression>]) -> bool {
99	for row in rows {
100		for alias_expr in row {
101			if matches!(
102				alias_expr.expression.as_ref(),
103				Expression::SumTypeConstructor(_) | Expression::Column(_)
104			) {
105				return true;
106			}
107		}
108	}
109	false
110}
111
112fn expand_sumtype_ctor<'a>(
113	ctx: &Arc<QueryContext>,
114	txn: &mut Transaction<'a>,
115	alias_expr: AliasExpression,
116	expanded: &mut Vec<AliasExpression>,
117) -> Result<()> {
118	let col_name = alias_expr.alias.0.text().to_string();
119	let fragment = alias_expr.fragment.clone();
120
121	let Expression::SumTypeConstructor(ctor) = *alias_expr.expression else {
122		unreachable!()
123	};
124
125	let is_unresolved = ctor.namespace.text() == ctor.variant_name.text()
126		&& ctor.sumtype_name.text() == ctor.variant_name.text();
127
128	let sumtype = if is_unresolved {
129		resolve_unresolved_sumtype(ctx, txn, &col_name)?
130	} else {
131		let ns_name = ctor.namespace.text();
132		let ns = ctx.services.catalog.find_namespace_by_name(txn, ns_name)?.unwrap();
133		let sumtype_name = ctor.sumtype_name.text();
134		ctx.services.catalog.find_sumtype_by_name(txn, ns.id(), sumtype_name)?.unwrap()
135	};
136
137	let variant_name_lower = ctor.variant_name.text().to_lowercase();
138	let variant = sumtype.variants.iter().find(|v| v.name == variant_name_lower).unwrap();
139
140	expanded.push(AliasExpression {
141		alias: IdentExpression(Fragment::internal(format!("{}_tag", col_name))),
142		expression: Box::new(Expression::Constant(ConstantExpression::Number {
143			fragment: Fragment::internal(variant.tag.to_string()),
144		})),
145		fragment: fragment.clone(),
146	});
147
148	for (field_name, field_expr) in ctor.columns {
149		let phys_col_name = format!("{}_{}_{}", col_name, variant_name_lower, field_name.text().to_lowercase());
150		expanded.push(AliasExpression {
151			alias: IdentExpression(Fragment::internal(phys_col_name)),
152			expression: Box::new(field_expr),
153			fragment: fragment.clone(),
154		});
155	}
156
157	Ok(())
158}
159
160#[inline]
161fn resolve_unresolved_sumtype<'a>(
162	ctx: &Arc<QueryContext>,
163	txn: &mut Transaction<'a>,
164	col_name: &str,
165) -> Result<SumType> {
166	let tag_col_name = format!("{}_tag", col_name);
167	let source = ctx.source.as_ref().expect("source required for unresolved sumtype");
168
169	if let Some(tag_col) = source.columns().iter().find(|c| c.name == tag_col_name) {
170		let Some(Constraint::SumType(id)) = tag_col.constraint.constraint() else {
171			panic!("expected SumType constraint on tag column")
172		};
173		ctx.services.catalog.get_sumtype(txn, *id)
174	} else if let ResolvedShape::Series(series) = source {
175		let tag_id = series.def().tag.expect("series tag expected");
176		ctx.services.catalog.get_sumtype(txn, tag_id)
177	} else {
178		panic!("tag column not found: {}", tag_col_name)
179	}
180}
181
182fn expand_unit_variant_column<'a>(
183	ctx: &Arc<QueryContext>,
184	txn: &mut Transaction<'a>,
185	alias_expr: AliasExpression,
186	expanded: &mut Vec<AliasExpression>,
187) -> Result<()> {
188	let col_name = alias_expr.alias.0.text().to_string();
189
190	let resolved = if let Some(source) = ctx.source.as_ref() {
191		let Expression::Column(col) = alias_expr.expression.as_ref() else {
192			unreachable!()
193		};
194		try_resolve_unit_variant(ctx, txn, source, &col_name, col.0.name.text())?
195	} else {
196		None
197	};
198
199	let Some((sumtype, tag)) = resolved else {
200		expanded.push(alias_expr);
201		return Ok(());
202	};
203
204	let fragment = alias_expr.fragment.clone();
205	expanded.push(AliasExpression {
206		alias: IdentExpression(Fragment::internal(format!("{}_tag", col_name))),
207		expression: Box::new(Expression::Constant(ConstantExpression::Number {
208			fragment: Fragment::internal(tag.to_string()),
209		})),
210		fragment: fragment.clone(),
211	});
212	for v in &sumtype.variants {
213		for field in &v.fields {
214			let phys_col_name =
215				format!("{}_{}_{}", col_name, v.name.to_lowercase(), field.name.to_lowercase());
216			expanded.push(AliasExpression {
217				alias: IdentExpression(Fragment::internal(phys_col_name)),
218				expression: Box::new(Expression::Constant(ConstantExpression::None {
219					fragment: fragment.clone(),
220				})),
221				fragment: fragment.clone(),
222			});
223		}
224	}
225	Ok(())
226}
227
228#[inline]
229fn try_resolve_unit_variant<'a>(
230	ctx: &Arc<QueryContext>,
231	txn: &mut Transaction<'a>,
232	source: &ResolvedShape,
233	col_name: &str,
234	alias_text: &str,
235) -> Result<Option<(SumType, u8)>> {
236	let tag_col_name = format!("{}_tag", col_name);
237
238	if let Some(tag_col) = source.columns().iter().find(|c| c.name == tag_col_name) {
239		let Some(Constraint::SumType(id)) = tag_col.constraint.constraint() else {
240			return Ok(None);
241		};
242		let sumtype = ctx.services.catalog.get_sumtype(txn, *id)?;
243		let variant_name_lower = alias_text.to_lowercase();
244		let maybe_tag =
245			sumtype.variants.iter().find(|v| v.name.to_lowercase() == variant_name_lower).map(|v| v.tag);
246		return Ok(maybe_tag.map(|tag| (sumtype, tag)));
247	}
248
249	if let ResolvedShape::Series(series) = source
250		&& let Some(tag_id) = series.def().tag
251	{
252		let sumtype = ctx.services.catalog.get_sumtype(txn, tag_id)?;
253		let variant_name_lower = alias_text.to_lowercase();
254		let maybe_tag =
255			sumtype.variants.iter().find(|v| v.name.to_lowercase() == variant_name_lower).map(|v| v.tag);
256		return Ok(maybe_tag.map(|tag| (sumtype, tag)));
257	}
258
259	Ok(None)
260}
261
262impl QueryNode for InlineDataNode {
263	fn initialize<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
264		self.expand_sumtype_constructors(rx)?;
265		Ok(())
266	}
267
268	fn next<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<Columns>> {
269		reifydb_assertions! {
270			assert!(self.context.is_some(), "InlineDataNode::next() called before initialize()");
271		}
272		let stored_ctx = self.context.as_ref().unwrap().clone();
273
274		if self.executed {
275			return Ok(None);
276		}
277
278		self.executed = true;
279
280		if self.rows.is_empty() {
281			let columns = Columns::empty();
282			if self.headers.is_none() {
283				self.headers = Some(ColumnHeaders::from_columns(&columns));
284			}
285			return Ok(Some(columns));
286		}
287
288		if self.headers.is_some() {
289			self.next_with_source(&stored_ctx)
290		} else {
291			self.next_infer_namespace(&stored_ctx)
292		}
293	}
294
295	fn headers(&self) -> Option<ColumnHeaders> {
296		self.headers.clone()
297	}
298}
299
300impl InlineDataNode {
301	fn find_optimal_integer_type(column: &ColumnBuffer) -> ValueType {
302		let mut min_val = i128::MAX;
303		let mut max_val = i128::MIN;
304		let mut has_values = false;
305
306		for value in column.iter() {
307			match value {
308				Value::Int16(v) => {
309					has_values = true;
310					min_val = min_val.min(v);
311					max_val = max_val.max(v);
312				}
313				Value::None {
314					..
315				} => {}
316				_ => {
317					return ValueType::Int16;
318				}
319			}
320		}
321
322		if !has_values {
323			return ValueType::Int1;
324		}
325
326		if min_val >= i8::MIN as i128 && max_val <= i8::MAX as i128 {
327			ValueType::Int1
328		} else if min_val >= i16::MIN as i128 && max_val <= i16::MAX as i128 {
329			ValueType::Int2
330		} else if min_val >= i32::MIN as i128 && max_val <= i32::MAX as i128 {
331			ValueType::Int4
332		} else if min_val >= i64::MIN as i128 && max_val <= i64::MAX as i128 {
333			ValueType::Int8
334		} else {
335			ValueType::Int16
336		}
337	}
338
339	fn next_infer_namespace(&mut self, ctx: &QueryContext) -> Result<Option<Columns>> {
340		let mut all_columns: BTreeSet<String> = BTreeSet::new();
341
342		for row in &self.rows {
343			for keyed_expr in row {
344				let column_name = keyed_expr.alias.0.text().to_string();
345				all_columns.insert(column_name);
346			}
347		}
348
349		let mut rows_data: Vec<HashMap<String, &AliasExpression>> = Vec::new();
350
351		for row in &self.rows {
352			let mut row_map: HashMap<String, &AliasExpression> = HashMap::new();
353			for alias_expr in row {
354				let column_name = alias_expr.alias.0.text().to_string();
355				row_map.insert(column_name, alias_expr);
356			}
357			rows_data.push(row_map);
358		}
359
360		let session = EvalContext::from_query(ctx);
361
362		let mut columns = Vec::new();
363
364		for column_name in all_columns {
365			let mut all_values = Vec::new();
366			let mut first_value_type: Option<ValueType> = None;
367			let mut column_fragment: Option<Fragment> = None;
368
369			for row_data in &rows_data {
370				if let Some(alias_expr) = row_data.get(&column_name) {
371					if column_fragment.is_none() {
372						column_fragment = Some(alias_expr.fragment.clone());
373					}
374					let eval_ctx = session.with_eval_empty();
375
376					let evaluated = evaluate(&eval_ctx, &alias_expr.expression)?;
377
378					let mut iter = evaluated.data().iter();
379					if let Some(value) = iter.next() {
380						if first_value_type.is_none() && !matches!(value, Value::None { .. }) {
381							first_value_type = Some(value.get_type());
382						}
383						all_values.push(value);
384					} else {
385						all_values.push(Value::none());
386					}
387				} else {
388					all_values.push(Value::none());
389				}
390			}
391
392			let wide_type = if let Some(ref fvt) = first_value_type {
393				if *fvt == ValueType::Decimal {
394					Some(ValueType::Decimal)
395				} else if *fvt == ValueType::Int {
396					Some(ValueType::Int)
397				} else if *fvt == ValueType::Uint {
398					Some(ValueType::Uint)
399				} else if fvt.is_integer() {
400					Some(ValueType::Int16)
401				} else if fvt.is_floating_point() {
402					Some(ValueType::Float8)
403				} else if *fvt == ValueType::Utf8 {
404					Some(ValueType::Utf8)
405				} else if *fvt == ValueType::Boolean {
406					Some(ValueType::Boolean)
407				} else {
408					None
409				}
410			} else {
411				None
412			};
413
414			let mut column_data = if wide_type.is_none() {
415				ColumnBuffer::none_typed(ValueType::Boolean, all_values.len())
416			} else {
417				let mut data = ColumnBuffer::with_capacity(wide_type.clone().unwrap(), 0);
418
419				for value in &all_values {
420					if matches!(value, Value::None { .. }) {
421						data.push_none();
422					} else if wide_type.as_ref().is_some_and(|wt| value.get_type() == *wt) {
423						data.push_value(value.clone());
424					} else {
425						let temp_data = ColumnBuffer::from(value.clone());
426						let eval_ctx = session.with_eval_empty();
427
428						match cast_column_data(
429							&eval_ctx,
430							&temp_data,
431							wide_type.clone().unwrap(),
432							Fragment::none,
433						) {
434							Ok(casted) => {
435								if let Some(casted_value) = casted.iter().next() {
436									data.push_value(casted_value);
437								} else {
438									data.push_none();
439								}
440							}
441							Err(_) => {
442								data.push_none();
443							}
444						}
445					}
446				}
447
448				data
449			};
450
451			if wide_type == Some(ValueType::Int16) {
452				let optimal_type = Self::find_optimal_integer_type(&column_data);
453				if optimal_type != ValueType::Int16 {
454					let eval_ctx = session.with_eval(Columns::empty(), column_data.len());
455
456					if let Ok(demoted) =
457						cast_column_data(&eval_ctx, &column_data, optimal_type, || {
458							Fragment::none()
459						}) {
460						column_data = demoted;
461					}
462				}
463			}
464
465			columns.push(ColumnWithName::new(
466				column_fragment.unwrap_or_else(|| Fragment::internal(column_name)),
467				column_data,
468			));
469		}
470
471		let columns = Columns::new(columns);
472		self.headers = Some(ColumnHeaders::from_columns(&columns));
473
474		Ok(Some(columns))
475	}
476
477	fn next_with_source(&mut self, ctx: &QueryContext) -> Result<Option<Columns>> {
478		let source = ctx.source.as_ref().unwrap();
479		let headers = self.headers.as_ref().unwrap();
480		let session = EvalContext::from_query(ctx);
481
482		let mut rows_data: Vec<HashMap<String, &AliasExpression>> = Vec::new();
483
484		for row in &self.rows {
485			let mut row_map: HashMap<String, &AliasExpression> = HashMap::new();
486			for alias_expr in row {
487				let column_name = alias_expr.alias.0.text().to_string();
488				row_map.insert(column_name, alias_expr);
489			}
490			rows_data.push(row_map);
491		}
492
493		let mut columns = Vec::new();
494
495		for column_name in &headers.columns {
496			let table_column = source.columns().iter().find(|col| col.name == column_name.text());
497
498			let mut column_data = if let Some(tc) = table_column {
499				ColumnBuffer::none_typed(tc.constraint.get_type(), 0)
500			} else {
501				ColumnBuffer::with_capacity(ValueType::Int16, 0)
502			};
503			let mut column_fragment: Option<Fragment> = None;
504
505			for row_data in &rows_data {
506				if let Some(alias_expr) = row_data.get(column_name.text()) {
507					if column_fragment.is_none() {
508						column_fragment = Some(alias_expr.fragment.clone());
509					}
510					let mut eval_ctx = session.with_eval_empty();
511					eval_ctx.target = table_column.map(|tc| TargetColumn::Partial {
512						source_name: Some(source.identifier().text().to_string()),
513						column_name: Some(tc.name.clone()),
514						column_type: tc.constraint.get_type(),
515						properties: tc
516							.properties
517							.iter()
518							.map(|cp| cp.property.clone())
519							.collect(),
520					});
521
522					let evaluated = evaluate(&eval_ctx, &alias_expr.expression)?;
523
524					let eval_len = evaluated.data().len();
525					if table_column.is_some() {
526						if eval_len == 1 {
527							column_data.extend(evaluated.data().clone())?;
528						} else if eval_len == 0 {
529							column_data.push_value(Value::none());
530						} else {
531							let first_value =
532								evaluated.data().iter().next().unwrap_or(Value::none());
533							column_data.push_value(first_value);
534						}
535					} else {
536						let value = if eval_len > 0 {
537							evaluated.data().iter().next().unwrap_or(Value::none())
538						} else {
539							Value::none()
540						};
541						match &value {
542							Value::None {
543								..
544							} => column_data.push_none(),
545							Value::Int16(_) => column_data.push_value(value),
546							_ => {
547								let temp = ColumnBuffer::from(value.clone());
548								match cast_column_data(
549									&eval_ctx,
550									&temp,
551									ValueType::Int16,
552									Fragment::none,
553								) {
554									Ok(casted) => {
555										if let Some(v) = casted.iter().next() {
556											column_data.push_value(v);
557										} else {
558											column_data.push_none();
559										}
560									}
561									Err(_) => column_data.push_value(value),
562								}
563							}
564						}
565					}
566				} else {
567					column_data.push_value(Value::none());
568				}
569			}
570
571			if table_column.is_none() {
572				let optimal_type = Self::find_optimal_integer_type(&column_data);
573				if optimal_type != ValueType::Int16 {
574					let eval_ctx = session.with_eval(Columns::empty(), column_data.len());
575					if let Ok(demoted) =
576						cast_column_data(&eval_ctx, &column_data, optimal_type, || {
577							Fragment::none()
578						}) {
579						column_data = demoted;
580					}
581				}
582			}
583
584			columns.push(ColumnWithName::new(
585				column_fragment
586					.map(|f| f.with_text(column_name.text()))
587					.unwrap_or_else(|| column_name.clone()),
588				column_data,
589			));
590		}
591
592		let columns = Columns::new(columns);
593
594		Ok(Some(columns))
595	}
596}