reifydb_engine/vm/volcano/
inline.rs1use 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}