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