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},
6	mem,
7	sync::Arc,
8};
9
10use arrow_array::{Array, ArrayRef, RecordBatch};
11use arrow_schema::FieldRef;
12use reifydb_catalog::catalog::Catalog;
13use reifydb_core::{
14	error::diagnostic::{
15		catalog::{column_not_sumtype, namespace_not_found, sumtype_not_found, sumtype_variant_not_found},
16		query::{column_not_found, duplicate_field},
17	},
18	expression::{
19		AliasExpression, CastExpression, ConstantExpression, Expression, IdentExpression,
20		SumTypeConstructorExpression, TypeExpression, name::display_label,
21	},
22	interface::{catalog::sumtype::SumType, evaluate::TargetColumn, resolved::ResolvedObject},
23	value::{
24		batch::{batch, empty_batch},
25		column::{
26			builder::ColumnBuilder,
27			cast::cast_column_data,
28			factory::{from_one, none, none_typed},
29			headers::ColumnHeaders,
30			view::group_by::common_key_type,
31		},
32	},
33};
34use reifydb_evaluate::expression::{context::EvalContext, eval::evaluate};
35use reifydb_rql::expression::variant::for_each_is_variant;
36use reifydb_transaction::transaction::Transaction;
37use reifydb_value::{
38	error::Error,
39	fragment::Fragment,
40	reifydb_assertions, return_error,
41	value::{Value, column_view::ColumnView, constraint::Constraint, sumtype::SumTypeId, value_type::ValueType},
42};
43use tracing::instrument;
44
45use crate::{
46	Result,
47	vm::{
48		instruction::dml::series_insert::resolve_variant_tag,
49		volcano::query::{QueryContext, QueryNode, eval_context_from_query},
50	},
51};
52
53pub(crate) struct InlineDataNode {
54	rows: Vec<Vec<AliasExpression>>,
55	headers: Option<ColumnHeaders>,
56	context: Option<Arc<QueryContext>>,
57	series_tag: Option<SumType>,
58	executed: bool,
59}
60
61impl InlineDataNode {
62	pub fn new(rows: Vec<Vec<AliasExpression>>, context: Arc<QueryContext>) -> Self {
63		let cloned_context = context.clone();
64		let headers = cloned_context.source.as_ref().map(|source| {
65			let mut layout = Self::create_columns_layout_from_source(source);
66			if Self::series_tag_id(source).is_some()
67				&& !layout.columns.iter().any(|c| c.text() == "tag")
68				&& rows.iter().flatten().any(|alias| alias.alias.0.text() == "tag")
69			{
70				layout.columns.push(Fragment::internal("tag"));
71			}
72			layout
73		});
74
75		Self {
76			rows,
77			headers,
78			context: Some(context),
79			series_tag: None,
80			executed: false,
81		}
82	}
83
84	fn create_columns_layout_from_source(source: &ResolvedObject) -> ColumnHeaders {
85		ColumnHeaders {
86			columns: source.columns().iter().map(|col| Fragment::internal(&col.name)).collect(),
87		}
88	}
89
90	fn series_tag_id(source: &ResolvedObject) -> Option<SumTypeId> {
91		match source {
92			ResolvedObject::Series(series) => series.def().tag,
93			_ => None,
94		}
95	}
96
97	fn load_series_tag<'a>(&mut self, txn: &mut Transaction<'a>) -> Result<()> {
98		let Some(ctx) = self.context.as_ref() else {
99			return Ok(());
100		};
101		if let Some(tag_id) = ctx.source.as_ref().and_then(Self::series_tag_id) {
102			self.series_tag = Some(ctx.services.catalog.get_sumtype(txn, tag_id)?);
103		}
104		Ok(())
105	}
106
107	#[instrument(level = "trace", skip_all, name = "volcano::inline::expand_sumtypes")]
108	fn expand_sumtype_constructors<'a>(&mut self, txn: &mut Transaction<'a>) -> Result<()> {
109		let Some(ctx) = self.context.as_ref().cloned() else {
110			return Ok(());
111		};
112		if !rows_need_sumtype_expansion(&self.rows) {
113			return Ok(());
114		}
115		for row in &mut self.rows {
116			let original = mem::take(row);
117			let mut expanded = Vec::with_capacity(original.len());
118			let mut written = Vec::with_capacity(original.len());
119			for alias_expr in original {
120				let name = alias_expr.alias.0.clone();
121				if let Some(sumtype) = self.series_tag.as_ref()
122					&& alias_expr.alias.0.text() == "tag"
123				{
124					expanded.push(resolve_series_tag_name(&ctx, txn, sumtype, alias_expr)?);
125					written.push(name);
126					continue;
127				}
128				match alias_expr.expression.as_ref() {
129					Expression::SumTypeConstructor(_) => {
130						expand_sumtype_ctor(
131							&ctx,
132							txn,
133							ctx.source.as_ref(),
134							alias_expr,
135							&mut expanded,
136						)?;
137					}
138					Expression::Column(_) => {
139						expand_unit_variant_column(
140							&ctx,
141							txn,
142							ctx.source.as_ref(),
143							alias_expr,
144							&mut expanded,
145						)?;
146					}
147					_ => {
148						expand_sumtype_none(
149							&ctx,
150							txn,
151							ctx.source.as_ref(),
152							alias_expr,
153							&mut expanded,
154						)?;
155					}
156				}
157				written.resize(expanded.len(), name);
158			}
159			let names: Vec<Fragment> = expanded.iter().map(|alias| alias.alias.0.clone()).collect();
160			reject_variant_column_clashes(&names, &written)?;
161			*row = expanded;
162		}
163		Ok(())
164	}
165}
166
167#[inline]
168fn rows_need_sumtype_expansion(rows: &[Vec<AliasExpression>]) -> bool {
169	for row in rows {
170		for alias_expr in row {
171			if matches!(
172				alias_expr.expression.as_ref(),
173				Expression::SumTypeConstructor(_)
174					| Expression::Column(_) | Expression::Constant(ConstantExpression::None { .. })
175			) {
176				return true;
177			}
178		}
179	}
180	false
181}
182
183pub(crate) fn expand_aliases(
184	expressions: Vec<Expression>,
185	mut expand: impl FnMut(AliasExpression, &mut Vec<AliasExpression>) -> Result<()>,
186) -> Result<(Vec<Expression>, Vec<Fragment>)> {
187	let mut result = Vec::with_capacity(expressions.len());
188	let mut written = Vec::with_capacity(expressions.len());
189	let mut expanded = Vec::new();
190	for expr in expressions {
191		let name = display_label(&expr);
192		let Expression::Alias(alias_expr) = expr else {
193			result.push(expr);
194			written.push(name);
195			continue;
196		};
197		expand(alias_expr, &mut expanded)?;
198		written.extend(expanded.iter().map(|_| name.clone()));
199		result.extend(expanded.drain(..).map(Expression::Alias));
200	}
201	Ok((result, written))
202}
203
204pub(crate) fn reject_variant_column_clashes(names: &[Fragment], written: &[Fragment]) -> Result<()> {
205	for (index, name) in names.iter().enumerate() {
206		let expanded = name.text() != written[index].text();
207		if names[..index].iter().zip(written).any(|(earlier, earlier_written)| {
208			earlier.text() == name.text() && (expanded || earlier.text() != earlier_written.text())
209		}) {
210			return_error!(duplicate_field(written[index].clone(), name.text()));
211		}
212	}
213	Ok(())
214}
215
216pub(crate) fn expand_sumtype_ctor(
217	ctx: &QueryContext,
218	txn: &mut Transaction<'_>,
219	source: Option<&ResolvedObject>,
220	alias_expr: AliasExpression,
221	expanded: &mut Vec<AliasExpression>,
222) -> Result<()> {
223	let Expression::SumTypeConstructor(ctor) = alias_expr.expression.as_ref() else {
224		expanded.push(alias_expr);
225		return Ok(());
226	};
227	let (sumtype, variant) = resolve_sumtype_ctor(&ctx.services.catalog, txn, source, &alias_expr.alias.0, ctor)?;
228	let col_name = alias_expr.alias.0.text();
229	let variant_name = sumtype.variants[variant].name.to_lowercase();
230
231	expanded.push(tag_alias(source, col_name, sumtype.variants[variant].tag, &alias_expr.fragment));
232	for (field_name, field_expr) in &ctor.columns {
233		let phys_col_name = format!("{}_{}_{}", col_name, variant_name, field_name.text().to_lowercase());
234		expanded.push(AliasExpression {
235			alias: IdentExpression(Fragment::internal(phys_col_name)),
236			expression: Box::new(field_expr.clone()),
237			fragment: alias_expr.fragment.clone(),
238		});
239	}
240
241	Ok(())
242}
243
244fn expand_sumtype_none(
245	ctx: &QueryContext,
246	txn: &mut Transaction<'_>,
247	source: Option<&ResolvedObject>,
248	alias_expr: AliasExpression,
249	expanded: &mut Vec<AliasExpression>,
250) -> Result<()> {
251	let col_name = alias_expr.alias.0.text();
252	let is_none = matches!(alias_expr.expression.as_ref(), Expression::Constant(ConstantExpression::None { .. }));
253	let declared = source.and_then(|source| declared_sumtype_id(source, col_name));
254
255	let (true, Some(id)) = (is_none, declared) else {
256		expanded.push(alias_expr);
257		return Ok(());
258	};
259
260	let sumtype = ctx.services.catalog.get_sumtype(txn, id)?;
261	let fragment = alias_expr.fragment.clone();
262	let none = || {
263		Box::new(Expression::Constant(ConstantExpression::None {
264			fragment: fragment.clone(),
265		}))
266	};
267
268	expanded.push(AliasExpression {
269		alias: IdentExpression(Fragment::internal(format!("{col_name}_tag"))),
270		expression: none(),
271		fragment: fragment.clone(),
272	});
273	for variant in &sumtype.variants {
274		for field in &variant.fields {
275			let phys_col_name =
276				format!("{}_{}_{}", col_name, variant.name.to_lowercase(), field.name.to_lowercase());
277			expanded.push(AliasExpression {
278				alias: IdentExpression(Fragment::internal(phys_col_name)),
279				expression: none(),
280				fragment: fragment.clone(),
281			});
282		}
283	}
284
285	Ok(())
286}
287
288pub(crate) fn expand_sumtype_assignment(
289	ctx: &QueryContext,
290	txn: &mut Transaction<'_>,
291	source: &ResolvedObject,
292	alias_expr: AliasExpression,
293	expanded: &mut Vec<AliasExpression>,
294) -> Result<()> {
295	match alias_expr.expression.as_ref() {
296		Expression::SumTypeConstructor(ctor) => {
297			let (sumtype, variant) = resolve_sumtype_ctor(
298				&ctx.services.catalog,
299				txn,
300				Some(source),
301				&alias_expr.alias.0,
302				ctor,
303			)?;
304			let given = ctor
305				.columns
306				.iter()
307				.map(|(name, expr)| (name.text().to_lowercase(), expr.clone()))
308				.collect();
309			push_all_variant_columns(
310				source,
311				ctx.source.is_none().then_some(source),
312				alias_expr.alias.0.text(),
313				&sumtype,
314				variant,
315				given,
316				&alias_expr.fragment,
317				expanded,
318			);
319			Ok(())
320		}
321		Expression::Column(_) => expand_unit_variant_column(ctx, txn, Some(source), alias_expr, expanded),
322		_ => {
323			expanded.push(alias_expr);
324			Ok(())
325		}
326	}
327}
328
329pub(crate) fn resolve_sumtype_ctor(
330	catalog: &Catalog,
331	txn: &mut Transaction<'_>,
332	source: Option<&ResolvedObject>,
333	column: &Fragment,
334	ctor: &SumTypeConstructorExpression,
335) -> Result<(SumType, usize)> {
336	let col_name = column.text();
337	let declared = source.and_then(|source| declared_sumtype_id(source, col_name));
338	let sumtype = if is_unresolved_ctor(ctor) {
339		let Some(id) = declared else {
340			return_error!(column_not_sumtype(ctor.variant_name.clone(), col_name));
341		};
342		catalog.get_sumtype(txn, id)?
343	} else {
344		let sumtype = find_ctor_sumtype(catalog, txn, ctor)?;
345		if let Some(source) = source {
346			match declared {
347				None if source.columns().iter().any(|c| c.name == col_name) => {
348					return_error!(column_not_sumtype(ctor.variant_name.clone(), col_name));
349				}
350				None => return_error!(column_not_found(column.clone())),
351				Some(id) if id != sumtype.id => {
352					let declared_name = catalog.get_sumtype(txn, id)?.name;
353					return_error!(sumtype_variant_not_found(
354						qualified_ctor_fragment(ctor),
355						&declared_name
356					));
357				}
358				Some(_) => {}
359			}
360		}
361		sumtype
362	};
363
364	let variant_name_lower = ctor.variant_name.text().to_lowercase();
365	let Some(variant) = sumtype.variants.iter().position(|v| v.name == variant_name_lower) else {
366		return_error!(sumtype_variant_not_found(ctor.variant_name.clone(), &sumtype.name));
367	};
368	if let Some((unknown, _)) = ctor.columns.iter().find(|(field_name, _)| {
369		!sumtype.variants[variant].fields.iter().any(|f| f.name.eq_ignore_ascii_case(field_name.text()))
370	}) {
371		return_error!(column_not_found(unknown.clone()));
372	}
373
374	Ok((sumtype, variant))
375}
376
377pub(crate) fn resolve_is_variants(
378	catalog: &Catalog,
379	txn: &mut Transaction<'_>,
380	source: &ResolvedObject,
381	expr: &mut Expression,
382) -> Result<()> {
383	for_each_is_variant(expr, &mut |is| {
384		let column = match is.expression.as_ref() {
385			Expression::Column(column) => column.0.name.clone(),
386			other => display_label(other),
387		};
388		let ctor = SumTypeConstructorExpression {
389			namespace: is.namespace.clone().unwrap_or_else(|| is.sumtype_name.clone()),
390			sumtype_name: is.sumtype_name.clone(),
391			variant_name: is.variant_name.clone(),
392			columns: Vec::new(),
393			fragment: is.fragment.clone(),
394		};
395		let (sumtype, variant) = resolve_sumtype_ctor(catalog, txn, Some(source), &column, &ctor)?;
396		is.tag = Some(sumtype.variants[variant].tag);
397		Ok(())
398	})
399}
400
401#[allow(clippy::too_many_arguments)]
402fn push_all_variant_columns(
403	source: &ResolvedObject,
404	field_types: Option<&ResolvedObject>,
405	col_name: &str,
406	sumtype: &SumType,
407	variant: usize,
408	mut given: HashMap<String, Expression>,
409	fragment: &Fragment,
410	expanded: &mut Vec<AliasExpression>,
411) {
412	expanded.push(tag_alias(Some(source), col_name, sumtype.variants[variant].tag, fragment));
413	for (index, v) in sumtype.variants.iter().enumerate() {
414		for field in &v.fields {
415			let field_name = field.name.to_lowercase();
416			let value = match given.remove(&field_name) {
417				Some(expr) if index == variant => expr,
418				_ => Expression::Constant(ConstantExpression::None {
419					fragment: fragment.clone(),
420				}),
421			};
422			let phys_col_name = format!("{}_{}_{}", col_name, v.name.to_lowercase(), field_name);
423			expanded.push(AliasExpression {
424				expression: Box::new(cast_to_stored_type(field_types, &phys_col_name, value, fragment)),
425				alias: IdentExpression(Fragment::internal(phys_col_name)),
426				fragment: fragment.clone(),
427			});
428		}
429	}
430}
431
432fn tag_alias(source: Option<&ResolvedObject>, col_name: &str, tag: u8, fragment: &Fragment) -> AliasExpression {
433	let tag_col_name = format!("{}_tag", col_name);
434	let constant = Expression::Constant(ConstantExpression::Number {
435		fragment: Fragment::internal(tag.to_string()),
436	});
437	AliasExpression {
438		expression: Box::new(cast_to_stored_type(source, &tag_col_name, constant, fragment)),
439		alias: IdentExpression(Fragment::internal(tag_col_name)),
440		fragment: fragment.clone(),
441	}
442}
443
444fn cast_to_stored_type(
445	source: Option<&ResolvedObject>,
446	col_name: &str,
447	expression: Expression,
448	fragment: &Fragment,
449) -> Expression {
450	match source.and_then(|source| source.columns().iter().find(|c| c.name == col_name)) {
451		Some(column) => Expression::Cast(CastExpression {
452			fragment: fragment.clone(),
453			expression: Box::new(expression),
454			to: TypeExpression {
455				fragment: Fragment::internal(column.constraint.get_type().to_string()),
456				ty: column.constraint.get_type(),
457				constraint: None,
458			},
459		}),
460		None => expression,
461	}
462}
463
464fn resolve_series_tag_name<'a>(
465	ctx: &Arc<QueryContext>,
466	txn: &mut Transaction<'a>,
467	sumtype: &SumType,
468	alias_expr: AliasExpression,
469) -> Result<AliasExpression> {
470	let variant = match alias_expr.expression.as_ref() {
471		Expression::Column(column) => column.0.name.clone(),
472		Expression::SumTypeConstructor(ctor) => {
473			if let Some((field, _)) = ctor.columns.first() {
474				return_error!(column_not_found(field.clone()));
475			}
476			if !is_unresolved_ctor(ctor)
477				&& find_ctor_sumtype(&ctx.services.catalog, txn, ctor)?.id != sumtype.id
478			{
479				return_error!(sumtype_variant_not_found(qualified_ctor_fragment(ctor), &sumtype.name));
480			}
481			ctor.variant_name.clone()
482		}
483		_ => return Ok(alias_expr),
484	};
485	let Some(tag) = sumtype.variants.iter().find(|v| v.name.eq_ignore_ascii_case(variant.text())).map(|v| v.tag)
486	else {
487		return_error!(sumtype_variant_not_found(variant, &sumtype.name));
488	};
489	Ok(AliasExpression {
490		alias: alias_expr.alias,
491		expression: Box::new(Expression::Constant(ConstantExpression::Number {
492			fragment: Fragment::internal(tag.to_string()),
493		})),
494		fragment: alias_expr.fragment,
495	})
496}
497
498fn is_unresolved_ctor(ctor: &SumTypeConstructorExpression) -> bool {
499	ctor.namespace.text() == ctor.variant_name.text() && ctor.sumtype_name.text() == ctor.variant_name.text()
500}
501
502fn qualified_ctor_fragment(ctor: &SumTypeConstructorExpression) -> Fragment {
503	ctor.namespace.with_text(format!(
504		"{}::{}::{}",
505		ctor.namespace.text(),
506		ctor.sumtype_name.text(),
507		ctor.variant_name.text()
508	))
509}
510
511fn find_ctor_sumtype(
512	catalog: &Catalog,
513	txn: &mut Transaction<'_>,
514	ctor: &SumTypeConstructorExpression,
515) -> Result<SumType> {
516	let ns_name = ctor.namespace.text();
517	let Some(ns) = catalog.find_namespace_by_name(txn, ns_name)? else {
518		return_error!(namespace_not_found(ctor.namespace.clone(), ns_name));
519	};
520	let sumtype_name = ctor.sumtype_name.text();
521	let Some(sumtype) = catalog.find_sumtype_by_name(txn, ns.id(), sumtype_name)? else {
522		return_error!(sumtype_not_found(ctor.sumtype_name.clone(), ns_name, sumtype_name));
523	};
524	Ok(sumtype)
525}
526
527fn declared_sumtype_id(source: &ResolvedObject, col_name: &str) -> Option<SumTypeId> {
528	let tag_col_name = format!("{}_tag", col_name);
529	let tag_col = source.columns().iter().find(|c| c.name == tag_col_name)?;
530	match tag_col.constraint.constraint() {
531		Some(Constraint::SumType(id)) => Some(*id),
532		_ => None,
533	}
534}
535
536fn expand_unit_variant_column(
537	ctx: &QueryContext,
538	txn: &mut Transaction<'_>,
539	source: Option<&ResolvedObject>,
540	alias_expr: AliasExpression,
541	expanded: &mut Vec<AliasExpression>,
542) -> Result<()> {
543	let (Some(source), Expression::Column(col)) = (source, alias_expr.expression.as_ref()) else {
544		expanded.push(alias_expr);
545		return Ok(());
546	};
547	let Some((sumtype, variant)) = try_resolve_unit_variant(
548		ctx,
549		txn,
550		source,
551		alias_expr.alias.0.text(),
552		col.0.name.text(),
553		alias_expr.expression.full_fragment_owned(),
554	)?
555	else {
556		expanded.push(alias_expr);
557		return Ok(());
558	};
559
560	push_all_variant_columns(
561		source,
562		ctx.source.is_none().then_some(source),
563		alias_expr.alias.0.text(),
564		&sumtype,
565		variant,
566		HashMap::new(),
567		&alias_expr.fragment,
568		expanded,
569	);
570	Ok(())
571}
572
573#[inline]
574fn try_resolve_unit_variant(
575	ctx: &QueryContext,
576	txn: &mut Transaction<'_>,
577	source: &ResolvedObject,
578	col_name: &str,
579	alias_text: &str,
580	variant: Fragment,
581) -> Result<Option<(SumType, usize)>> {
582	let Some(id) = declared_sumtype_id(source, col_name) else {
583		return Ok(None);
584	};
585	let sumtype = ctx.services.catalog.get_sumtype(txn, id)?;
586	let variant_name_lower = alias_text.to_lowercase();
587	let Some(index) = sumtype.variants.iter().position(|v| v.name.to_lowercase() == variant_name_lower) else {
588		return_error!(sumtype_variant_not_found(variant, &sumtype.name));
589	};
590	Ok(Some((sumtype, index)))
591}
592
593impl QueryNode for InlineDataNode {
594	#[instrument(level = "trace", skip_all, name = "volcano::inline::initialize")]
595	fn initialize<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
596		self.load_series_tag(rx)?;
597		self.expand_sumtype_constructors(rx)?;
598		Ok(())
599	}
600
601	#[instrument(level = "trace", skip_all, name = "volcano::inline::next")]
602	fn next<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<RecordBatch>> {
603		reifydb_assertions! {
604			assert!(self.context.is_some(), "InlineDataNode::next() called before initialize()");
605		}
606		let stored_ctx = self.context.as_ref().unwrap().clone();
607
608		if self.executed {
609			return Ok(None);
610		}
611
612		self.executed = true;
613
614		if self.rows.is_empty() {
615			let columns = empty_batch();
616			if self.headers.is_none() {
617				self.headers = Some(ColumnHeaders::from_batch(&columns));
618			}
619			return Ok(Some(columns));
620		}
621
622		if self.headers.is_some() {
623			self.next_with_source(&stored_ctx)
624		} else {
625			self.next_infer_namespace(&stored_ctx)
626		}
627	}
628
629	fn headers(&self) -> Option<ColumnHeaders> {
630		self.headers.clone()
631	}
632}
633
634type EvaluatedColumnValues = (Vec<(Value, ValueType, Fragment)>, Option<ValueType>);
635
636impl InlineDataNode {
637	fn find_optimal_integer_type(column: &ColumnView<'_>) -> ValueType {
638		let mut min_val = i128::MAX;
639		let mut max_val = i128::MIN;
640		let mut has_values = false;
641
642		for value in column.iter() {
643			match value {
644				Value::Int16(v) => {
645					has_values = true;
646					min_val = min_val.min(v);
647					max_val = max_val.max(v);
648				}
649				Value::None {
650					..
651				} => {}
652				_ => {
653					return ValueType::Int16;
654				}
655			}
656		}
657
658		if !has_values {
659			return ValueType::Int1;
660		}
661
662		if min_val >= i8::MIN as i128 && max_val <= i8::MAX as i128 {
663			ValueType::Int1
664		} else if min_val >= i16::MIN as i128 && max_val <= i16::MAX as i128 {
665			ValueType::Int2
666		} else if min_val >= i32::MIN as i128 && max_val <= i32::MAX as i128 {
667			ValueType::Int4
668		} else if min_val >= i64::MIN as i128 && max_val <= i64::MAX as i128 {
669			ValueType::Int8
670		} else {
671			ValueType::Int16
672		}
673	}
674
675	fn widen_numeric(wide: ValueType, fractional: ValueType) -> ValueType {
676		match wide {
677			ValueType::Decimal {
678				..
679			} if matches!(fractional, ValueType::Decimal { .. }) => common_key_type(&wide, &fractional).unwrap_or(wide),
680			_ if wide.is_number() => ValueType::promote(wide, fractional),
681			_ => wide,
682		}
683	}
684
685	#[instrument(level = "trace", skip_all, name = "volcano::inline::column_names")]
686	fn collect_column_names(rows: &[Vec<AliasExpression>]) -> BTreeSet<String> {
687		let mut all_columns: BTreeSet<String> = BTreeSet::new();
688
689		for row in rows {
690			for keyed_expr in row {
691				let column_name = keyed_expr.alias.0.text().to_string();
692				all_columns.insert(column_name);
693			}
694		}
695
696		all_columns
697	}
698
699	#[instrument(level = "trace", skip_all, name = "volcano::inline::row_maps")]
700	fn build_row_maps(rows: &[Vec<AliasExpression>]) -> Vec<HashMap<String, &AliasExpression>> {
701		let mut rows_data: Vec<HashMap<String, &AliasExpression>> = Vec::new();
702
703		for row in rows {
704			let mut row_map: HashMap<String, &AliasExpression> = HashMap::new();
705			for alias_expr in row {
706				let column_name = alias_expr.alias.0.text().to_string();
707				row_map.insert(column_name, alias_expr);
708			}
709			rows_data.push(row_map);
710		}
711
712		rows_data
713	}
714
715	#[instrument(level = "trace", skip_all, name = "volcano::inline::eval")]
716	fn eval_column_values(
717		session: &EvalContext<'_>,
718		rows_data: &[HashMap<String, &AliasExpression>],
719		column_name: &str,
720	) -> Result<EvaluatedColumnValues> {
721		let mut all_values = Vec::new();
722		let mut first_value_type: Option<ValueType> = None;
723
724		for row_data in rows_data {
725			if let Some(alias_expr) = row_data.get(column_name) {
726				let eval_ctx = session.with_eval_empty();
727
728				let evaluated = evaluate(&eval_ctx, &alias_expr.expression)?;
729				let evaluated = ColumnView::try_from(&evaluated)?;
730
731				let evaluated_type = evaluated.get_type().inner_type().clone();
732				let mut iter = evaluated.iter();
733				if let Some(value) = iter.next() {
734					if first_value_type.is_none() && !matches!(value, Value::None { .. }) {
735						first_value_type = Some(evaluated_type.clone());
736					}
737					all_values.push((
738						value,
739						evaluated_type,
740						alias_expr.expression.full_fragment_owned(),
741					));
742				} else {
743					all_values.push((Value::none(), ValueType::Any, Fragment::none()));
744				}
745			} else {
746				all_values.push((Value::none(), ValueType::Any, Fragment::none()));
747			}
748		}
749
750		Ok((all_values, first_value_type))
751	}
752
753	#[instrument(level = "trace", skip_all, name = "volcano::inline::materialize")]
754	fn materialize_inferred_column(
755		session: &EvalContext<'_>,
756		all_values: &[(Value, ValueType, Fragment)],
757		first_value_type: Option<ValueType>,
758		name: &str,
759	) -> Result<(FieldRef, ArrayRef)> {
760		let wide_type = first_value_type.map(|fvt| {
761			let first = match fvt {
762				_ if fvt.is_integer() => ValueType::Int16,
763				_ if fvt.is_floating_point() => ValueType::Float8,
764				_ => fvt,
765			};
766			all_values
767				.iter()
768				.filter(|(value, _, _)| !matches!(value, Value::None { .. }))
769				.map(|(_, ty, _)| ty.clone())
770				.filter(|ty| {
771					ty.is_floating_point()
772						|| ty.is_integer() || matches!(ty, ValueType::Decimal { .. })
773				})
774				.fold(first, |wide, ty| match wide {
775					ValueType::Decimal {
776						..
777					} => Self::widen_numeric(wide, ty),
778					_ if ty.is_integer() => wide,
779					_ => Self::widen_numeric(wide, ty),
780				})
781		});
782
783		let mut column_data = if wide_type.is_none() {
784			let none_type = all_values.iter().find_map(|(value, _, _)| match value {
785				Value::None {
786					inner,
787				} if *inner != ValueType::Any => Some(inner.clone()),
788				_ => None,
789			});
790			match none_type {
791				Some(none_type) => none_typed(name, none_type, all_values.len()),
792				None => none(name, all_values.len()),
793			}
794		} else {
795			let wide = wide_type.clone().unwrap();
796			let mut casts = Self::cast_value_groups(session, all_values, &wide, name)?;
797			let mut data = ColumnBuilder::with_capacity(wide.clone(), 0);
798
799			for (row, (value, value_type, _)) in all_values.iter().enumerate() {
800				if matches!(value, Value::None { .. }) {
801					data.push_none();
802				} else if *value_type == wide {
803					data.push_value(value.clone());
804				} else if let Some(casted_value) = casts[row].take() {
805					data.push_value(casted_value);
806				} else {
807					data.push_none();
808				}
809			}
810
811			data.finish(name)
812		};
813
814		if wide_type == Some(ValueType::Int16) {
815			let optimal_type = Self::find_optimal_integer_type(&ColumnView::try_from(&column_data)?);
816			if optimal_type != ValueType::Int16 {
817				let eval_ctx = session.with_eval(empty_batch(), column_data.1.len());
818				let casted = cast_column_data(
819					&eval_ctx,
820					&ColumnView::try_from(&column_data)?,
821					optimal_type,
822					Fragment::none,
823				)?;
824				column_data = casted;
825			}
826		}
827
828		Ok(column_data)
829	}
830
831	fn cast_value_groups(
832		session: &EvalContext<'_>,
833		all_values: &[(Value, ValueType, Fragment)],
834		wide: &ValueType,
835		name: &str,
836	) -> Result<Vec<Option<Value>>> {
837		let mut groups: Vec<(ValueType, Vec<usize>)> = Vec::new();
838		let mut singles: Vec<usize> = Vec::new();
839		for (row, (value, value_type, _)) in all_values.iter().enumerate() {
840			if matches!(value, Value::None { .. }) || value_type == wide {
841				continue;
842			}
843			if !Self::groups_by_type(value_type) {
844				singles.push(row);
845				continue;
846			}
847			match groups.iter_mut().find(|(ty, _)| ty == value_type) {
848				Some((_, rows)) => rows.push(row),
849				None => groups.push((value_type.clone(), vec![row])),
850			}
851		}
852
853		let mut casts: Vec<Option<Value>> = vec![None; all_values.len()];
854		let mut first_error: Option<(usize, Error)> = None;
855		for (value_type, rows) in groups {
856			let mut data = ColumnBuilder::with_capacity(value_type, rows.len());
857			for &row in &rows {
858				data.push_value(all_values[row].0.clone());
859			}
860			let data = data.finish(name);
861			let eval_ctx = session.with_eval_empty();
862			let fragment = all_values[rows[0]].2.clone();
863			match cast_column_data(&eval_ctx, &ColumnView::try_from(&data)?, wide.clone(), fragment) {
864				Ok(casted) => {
865					let casted = ColumnView::try_from(&casted)?;
866					for (position, &row) in rows.iter().enumerate() {
867						casts[row] = Some(casted.get_value(position));
868					}
869				}
870				Err(_) => Self::cast_each(
871					session,
872					all_values,
873					&rows,
874					wide,
875					name,
876					&mut casts,
877					&mut first_error,
878				)?,
879			}
880		}
881		Self::cast_each(session, all_values, &singles, wide, name, &mut casts, &mut first_error)?;
882
883		match first_error {
884			Some((_, error)) => Err(error),
885			None => Ok(casts),
886		}
887	}
888
889	fn groups_by_type(value_type: &ValueType) -> bool {
890		value_type.is_number()
891			|| value_type.is_bool()
892			|| value_type.is_utf8()
893			|| value_type.is_blob()
894			|| value_type.is_temporal()
895			|| value_type.is_uuid()
896			|| *value_type == ValueType::IdentityId
897	}
898
899	fn cast_each(
900		session: &EvalContext<'_>,
901		all_values: &[(Value, ValueType, Fragment)],
902		rows: &[usize],
903		wide: &ValueType,
904		name: &str,
905		casts: &mut [Option<Value>],
906		first_error: &mut Option<(usize, Error)>,
907	) -> Result<()> {
908		for &row in rows {
909			if first_error.as_ref().is_some_and(|(failed, _)| *failed < row) {
910				break;
911			}
912			let (value, _, fragment) = &all_values[row];
913			let temp_data = from_one(name, value.clone());
914			let eval_ctx = session.with_eval_empty();
915			match cast_column_data(
916				&eval_ctx,
917				&ColumnView::try_from(&temp_data)?,
918				wide.clone(),
919				fragment.clone(),
920			) {
921				Ok(casted) => casts[row] = ColumnView::try_from(&casted)?.iter().next(),
922				Err(error) => {
923					*first_error = Some((row, error));
924					break;
925				}
926			}
927		}
928		Ok(())
929	}
930
931	fn next_infer_namespace(&mut self, ctx: &QueryContext) -> Result<Option<RecordBatch>> {
932		let all_columns = Self::collect_column_names(&self.rows);
933		let rows_data = Self::build_row_maps(&self.rows);
934
935		let session = eval_context_from_query(ctx);
936
937		let mut columns = Vec::new();
938
939		for column_name in all_columns {
940			let (all_values, first_value_type) =
941				Self::eval_column_values(&session, &rows_data, &column_name)?;
942
943			columns.push(Self::materialize_inferred_column(
944				&session,
945				&all_values,
946				first_value_type,
947				&column_name,
948			)?);
949		}
950
951		let columns = batch(columns)?;
952		self.headers = Some(ColumnHeaders::from_batch(&columns));
953
954		Ok(Some(columns))
955	}
956
957	fn next_with_source(&mut self, ctx: &QueryContext) -> Result<Option<RecordBatch>> {
958		let source = ctx.source.as_ref().unwrap();
959		let headers = self.headers.as_ref().unwrap();
960		let session = eval_context_from_query(ctx);
961
962		if let Some(unknown) = self
963			.rows
964			.iter()
965			.flatten()
966			.find(|alias| !headers.columns.iter().any(|c| c.text() == alias.alias.0.text()))
967		{
968			return_error!(column_not_found(unknown.fragment.clone()));
969		}
970
971		let rows_data = Self::build_row_maps(&self.rows);
972
973		let mut columns = Vec::new();
974
975		for column_name in &headers.columns {
976			columns.push(Self::build_source_column(
977				&session,
978				source,
979				self.series_tag.as_ref(),
980				&rows_data,
981				column_name,
982			)?);
983		}
984
985		let columns = batch(columns)?;
986
987		Ok(Some(columns))
988	}
989
990	#[instrument(level = "trace", skip_all, name = "volcano::inline::source_column")]
991	fn build_source_column(
992		session: &EvalContext<'_>,
993		source: &ResolvedObject,
994		series_tag: Option<&SumType>,
995		rows_data: &[HashMap<String, &AliasExpression>],
996		column_name: &Fragment,
997	) -> Result<(FieldRef, ArrayRef)> {
998		let table_column = source.columns().iter().find(|col| col.name == column_name.text());
999
1000		let mut column_data = if let Some(tc) = table_column {
1001			ColumnBuilder::with_capacity(tc.constraint.get_type(), rows_data.len())
1002		} else {
1003			ColumnBuilder::with_capacity(ValueType::Uint1, 0)
1004		};
1005
1006		for row_data in rows_data {
1007			if let Some(alias_expr) = row_data.get(column_name.text()) {
1008				let mut eval_ctx = session.with_eval_empty();
1009				eval_ctx.target = table_column.map(|tc| TargetColumn::Partial {
1010					source_name: Some(source.identifier().text().to_string()),
1011					column_name: Some(tc.name.clone()),
1012					column_type: tc.constraint.get_type(),
1013					properties: tc.properties.iter().map(|cp| cp.property.clone()).collect(),
1014				});
1015
1016				let evaluated = evaluate(&eval_ctx, &alias_expr.expression)?;
1017				let evaluated = ColumnView::try_from(&evaluated)?;
1018
1019				let eval_len = evaluated.len();
1020				if table_column.is_some() {
1021					if eval_len == 1 {
1022						column_data.extend(&evaluated)?;
1023					} else if eval_len == 0 {
1024						column_data.push_value(Value::none());
1025					} else {
1026						let first_value = evaluated.iter().next().unwrap_or(Value::none());
1027						column_data.push_value(first_value);
1028					}
1029				} else {
1030					let value = if eval_len > 0 {
1031						evaluated.iter().next().unwrap_or(Value::none())
1032					} else {
1033						Value::none()
1034					};
1035					if matches!(value, Value::None { .. }) {
1036						column_data.push_none();
1037					} else {
1038						let sumtype = series_tag.expect(
1039							"the only inline column outside the schema is the series tag",
1040						);
1041						column_data.push_value(Value::Uint1(resolve_variant_tag(
1042							sumtype,
1043							&value,
1044							alias_expr.expression.full_fragment_owned(),
1045						)?));
1046					}
1047				}
1048			} else {
1049				column_data.push_value(Value::none());
1050			}
1051		}
1052
1053		Ok(column_data.finish(column_name.text()))
1054	}
1055}