1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::convert::TryFrom;
4use std::fmt;
5
6use prost::Message;
7use substrait::proto::fetch_rel::CountMode;
8use substrait::proto::plan_rel::RelType as PlanRelType;
9use substrait::proto::read_rel::ReadType;
10use substrait::proto::rel::RelType;
11use substrait::proto::rel_common::EmitKind;
12use substrait::proto::{
13 AggregateRel, CrossRel, ExtensionLeafRel, ExtensionMultiRel, ExtensionSingleRel, FetchRel,
14 FilterRel, JoinRel, NamedStruct, PlanRel, ProjectRel, ReadRel, Rel, RelCommon, RelRoot, SetRel,
15 SortRel, join_rel, set_rel,
16};
17
18use super::addenda::AddendumLines;
19use super::types::Name;
20use super::values::{Arguments, NamedArg, Value, ValueEnum, decode_enum_field};
21use super::{PlanError, Scope, Textify};
22use crate::FormatError;
23use crate::extensions::any::AnyRef;
24use crate::extensions::{ExtensionArgs, ExtensionError};
25
26pub trait NamedRelation {
27 fn name(&self) -> &'static str;
28}
29
30impl NamedRelation for Rel {
31 fn name(&self) -> &'static str {
32 match self.rel_type.as_ref() {
33 None => "UnknownRel",
34 Some(RelType::Read(_)) => "Read",
35 Some(RelType::Filter(_)) => "Filter",
36 Some(RelType::Project(_)) => "Project",
37 Some(RelType::Fetch(_)) => "Fetch",
38 Some(RelType::Aggregate(_)) => "Aggregate",
39 Some(RelType::Sort(_)) => "Sort",
40 Some(RelType::HashJoin(_)) => "HashJoin",
41 Some(RelType::Exchange(_)) => "Exchange",
42 Some(RelType::Join(_)) => "Join",
43 Some(RelType::Set(_)) => "Set",
44 Some(RelType::ExtensionLeaf(_)) => "ExtensionLeaf",
45 Some(RelType::Cross(_)) => "Cross",
46 Some(RelType::Reference(_)) => "Reference",
47 Some(RelType::ExtensionSingle(_)) => "ExtensionSingle",
48 Some(RelType::ExtensionMulti(_)) => "ExtensionMulti",
49 Some(RelType::Write(_)) => "Write",
50 Some(RelType::Ddl(_)) => "Ddl",
51 Some(RelType::Update(_)) => "Update",
52 Some(RelType::MergeJoin(_)) => "MergeJoin",
53 Some(RelType::NestedLoopJoin(_)) => "NestedLoopJoin",
54 Some(RelType::Window(_)) => "Window",
55 Some(RelType::Expand(_)) => "Expand",
56 }
57 }
58}
59
60impl Textify for Rel {
61 fn name() -> &'static str {
62 "Rel"
63 }
64
65 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
66 Relation::from_rel(self, ctx).textify(ctx, w)
69 }
70}
71
72fn schema_to_values<'a>(schema: &'a NamedStruct) -> Vec<Value<'a>> {
73 let mut fields = schema
74 .r#struct
75 .as_ref()
76 .map(|s| s.types.iter())
77 .into_iter()
78 .flatten();
79 let mut names = schema.names.iter();
80
81 let mut values = Vec::new();
85 loop {
86 let field = fields.next();
87 let name = names.next().map(|n| Name(n));
88 if field.is_none() && name.is_none() {
89 break;
90 }
91
92 values.push(Value::Field(name, field));
93 }
94
95 values
96}
97
98#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
100enum OutputSyntax {
101 #[default]
103 Implicit,
104 Explicit,
107}
108
109struct Emitted<'a> {
110 values: &'a [Value<'a>],
111 emit: Option<&'a EmitKind>,
112 output_syntax: Option<OutputSyntax>,
113}
114
115impl<'a> Emitted<'a> {
116 pub fn columns(values: &'a [Value<'a>], emit: Option<&'a EmitKind>) -> Self {
117 Self {
118 values,
119 emit,
120 output_syntax: None,
121 }
122 }
123
124 pub fn output_clause(
125 values: &'a [Value<'a>],
126 emit: Option<&'a EmitKind>,
127 output_syntax: OutputSyntax,
128 ) -> Self {
129 Self {
130 values,
131 emit,
132 output_syntax: Some(output_syntax),
133 }
134 }
135
136 fn write_output_clause<S: Scope, W: fmt::Write>(
137 &self,
138 ctx: &S,
139 w: &mut W,
140 output_syntax: OutputSyntax,
141 ) -> fmt::Result {
142 match output_syntax {
143 OutputSyntax::Implicit => {
144 write!(w, "=> ")?;
145 self.write_implicit_columns(ctx, w)
146 }
147 OutputSyntax::Explicit => {
148 write!(w, "+> ")?;
149 self.write_direct_columns(ctx, w)?;
150 self.write_emit_suffix(ctx, w)
151 }
152 }
153 }
154
155 fn write_direct_columns<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
156 write!(w, "{}", ctx.separated(self.values.iter(), ", "))
157 }
158
159 fn write_implicit_columns<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
160 if ctx.options().show_emit {
161 return self.write_direct_columns(ctx, w);
162 }
163
164 let indices = match &self.emit {
165 Some(EmitKind::Emit(e)) => &e.output_mapping,
166 Some(EmitKind::Direct(_)) => return self.write_direct_columns(ctx, w),
167 None => return self.write_direct_columns(ctx, w),
168 };
169
170 for (i, &index) in indices.iter().enumerate() {
171 if i > 0 {
172 write!(w, ", ")?;
173 }
174
175 match self.values.get(index as usize) {
176 Some(value) => write!(w, "{}", ctx.display(value))?,
177 None => write!(w, "{}", ctx.failure(PlanError::invalid(
178 "Emitted",
179 Some("output_mapping"),
180 format!(
181 "Output mapping index {} is out of bounds for values collection of size {}",
182 index, self.values.len()
183 )
184 )))?,
185 }
186 }
187
188 Ok(())
189 }
190
191 fn write_emit_suffix<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
192 let Some(EmitKind::Emit(emit)) = self.emit else {
193 return Ok(());
194 };
195 let mapping = emit
196 .output_mapping
197 .iter()
198 .copied()
199 .map(Value::Reference)
200 .collect::<Vec<_>>();
201 write!(w, " |> {}", ctx.separated(mapping.iter(), ", "))
202 }
203}
204
205impl<'a> Textify for Emitted<'a> {
206 fn name() -> &'static str {
207 "Emitted"
208 }
209
210 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
211 match self.output_syntax {
212 Some(output_syntax) => self.write_output_clause(ctx, w, output_syntax),
213 None => self.write_implicit_columns(ctx, w),
214 }
215 }
216}
217
218#[derive(Debug, Clone)]
220pub enum RelationArgs<'a> {
221 Inline(Arguments<'a>),
223 Rows {
227 rows: Vec<Value<'a>>,
228 named: Vec<NamedArg<'a>>,
229 },
230}
231
232impl<'a> RelationArgs<'a> {
233 pub fn inline(positional: Vec<Value<'a>>, named: Vec<NamedArg<'a>>) -> Self {
236 RelationArgs::Inline(Arguments::new(positional, named))
237 }
238
239 pub fn rows(rows: Vec<Value<'a>>, named: Vec<NamedArg<'a>>) -> Self {
242 RelationArgs::Rows { rows, named }
243 }
244}
245
246pub struct Relation<'a> {
247 pub name: Cow<'a, str>,
248 pub arguments: Option<RelationArgs<'a>>,
260 pub columns: Vec<Value<'a>>,
263 pub emit: Option<&'a EmitKind>,
265 output_syntax: OutputSyntax,
268 addenda: AddendumLines,
273 pub children: Vec<Option<Relation<'a>>>,
275}
276
277impl Textify for Relation<'_> {
278 fn name() -> &'static str {
279 "Relation"
280 }
281
282 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
283 self.write_header(ctx, w)?;
284 let child_scope = ctx.push_indent();
285 self.addenda.textify(&child_scope, w)?;
286 self.write_children(ctx, w)?;
287 Ok(())
288 }
289}
290
291impl Relation<'_> {
292 pub fn write_header<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
307 let indent = ctx.indent();
308 let name = &self.name;
309 match &self.arguments {
310 None => {
311 let cols = Emitted::columns(&self.columns, self.emit);
312 let cols = ctx.display(&cols);
313 write!(w, "{indent}{name}[{cols}]")
314 }
315 Some(RelationArgs::Rows { rows, named }) => {
316 let child = ctx.push_indent();
320 let child_indent = child.indent();
321 writeln!(w, "{indent}{name}[")?;
322 let last = rows.len().saturating_sub(1);
323 for (i, row) in rows.iter().enumerate() {
324 let row = ctx.display(row);
325 let comma = if i == last && named.is_empty() {
326 ""
327 } else {
328 ","
329 };
330 writeln!(w, "{child_indent}- {row}{comma}")?;
331 }
332 let last = named.len().saturating_sub(1);
333 for (i, named_arg) in named.iter().enumerate() {
334 let named_arg = ctx.display(named_arg);
335 let comma = if i == last { "" } else { "," };
336 writeln!(w, "{child_indent}- {named_arg}{comma}")?;
337 }
338 let output = Emitted::output_clause(&self.columns, self.emit, self.output_syntax);
339 let output = ctx.display(&output);
340 write!(w, "{child_indent}- {output}]")
341 }
342 Some(RelationArgs::Inline(args)) => {
343 let args = ctx.display(args);
344 let output = Emitted::output_clause(&self.columns, self.emit, self.output_syntax);
345 let output = ctx.display(&output);
346 write!(w, "{indent}{name}[{args} {output}]")
347 }
348 }
349 }
350
351 pub fn write_children<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
354 let child_scope = ctx.push_indent();
355 for child in self.children.iter().flatten() {
356 writeln!(w)?;
357 child.textify(&child_scope, w)?;
358 }
359 Ok(())
360 }
361}
362
363impl<'a> Relation<'a> {
364 pub fn emitted(&self) -> usize {
365 match self.emit {
366 Some(EmitKind::Emit(e)) => e.output_mapping.len(),
367 Some(EmitKind::Direct(_)) => self.columns.len(),
368 None => self.columns.len(),
369 }
370 }
371}
372
373impl<'a> Relation<'a> {
374 fn from_read<S: Scope>(rel: &'a ReadRel, ctx: &S) -> Self {
375 let columns = read_columns(rel);
376 let emit = rel.common.as_ref().and_then(|c| c.emit_kind.as_ref());
377
378 match &rel.read_type {
379 Some(ReadType::NamedTable(table)) => {
380 let table_name = Value::TableName(table.names.iter().map(|n| Name(n)).collect());
381 let output_syntax = if emit.is_some() {
388 OutputSyntax::Explicit
389 } else {
390 OutputSyntax::Implicit
391 };
392 Relation {
393 name: Cow::Borrowed("Read"),
394 arguments: Some(RelationArgs::inline(vec![table_name], vec![])),
395 columns,
396 emit,
397 output_syntax,
398 addenda: AddendumLines::from_advanced_extension(
399 ctx,
400 rel.advanced_extension.as_ref(),
401 ),
402 children: vec![],
403 }
404 }
405 Some(ReadType::VirtualTable(vt)) => {
406 let row_count = vt.expressions.len();
407 let mut positional: Vec<Value> = vt
408 .expressions
409 .iter()
410 .map(|row| Value::Tuple(row.fields.iter().map(Value::Expression).collect()))
411 .collect();
412 let mut named = vec![];
413 if let Some(filter) = rel.filter.as_ref() {
414 named.push(NamedArg {
415 name: Cow::Borrowed("filter"),
416 value: Value::Expression(filter.as_ref()),
417 });
418 }
419 if positional.is_empty() && !named.is_empty() {
420 positional.push(Value::EmptyGroup);
421 }
422
423 let multiline =
428 row_count > 0 && row_count >= ctx.options().virtual_table_multiline_threshold;
429 let arguments = if multiline {
430 RelationArgs::rows(positional, named)
431 } else {
432 RelationArgs::inline(positional, named)
433 };
434
435 Relation {
436 name: Cow::Borrowed("Read:Virtual"),
437 arguments: Some(arguments),
438 columns,
439 emit,
440 output_syntax: OutputSyntax::Implicit,
441 addenda: AddendumLines::from_advanced_extension(
442 ctx,
443 rel.advanced_extension.as_ref(),
444 ),
445 children: vec![],
446 }
447 }
448 Some(ReadType::ExtensionTable(table)) => {
449 let decoded = match table.detail.as_ref().map(AnyRef::from) {
450 Some(detail) => ctx.extension_registry().decode_extension_table(detail),
451 None => Err(ExtensionError::MissingDetail),
452 };
453
454 Relation {
455 name: Cow::Borrowed("Read:Extension"),
456 arguments: None,
457 columns,
458 emit,
459 output_syntax: OutputSyntax::Implicit,
460 addenda: AddendumLines::extension_table(
461 ctx,
462 decoded,
463 rel.advanced_extension.as_ref(),
464 ),
465 children: vec![],
466 }
467 }
468 other => {
469 let err = PlanError::unimplemented(
470 "ReadRel",
471 Some("read_type"),
472 format!("Unsupported read type {other:?}"),
473 );
474 Relation {
475 name: Cow::Borrowed("Read"),
476 arguments: Some(RelationArgs::inline(vec![Value::Missing(err)], vec![])),
477 columns,
478 emit,
479 output_syntax: OutputSyntax::Implicit,
480 addenda: AddendumLines::from_advanced_extension(
481 ctx,
482 rel.advanced_extension.as_ref(),
483 ),
484 children: vec![],
485 }
486 }
487 }
488 }
489}
490
491fn read_columns<'a>(rel: &'a ReadRel) -> Vec<Value<'a>> {
492 match rel.base_schema {
493 Some(ref schema) => schema_to_values(schema),
494 None => {
495 let err =
496 PlanError::unimplemented("ReadRel", Some("base_schema"), "Base schema is required");
497 vec![Value::Missing(err)]
498 }
499 }
500}
501
502pub fn get_emit(rel: Option<&RelCommon>) -> Option<&EmitKind> {
503 rel.as_ref().and_then(|c| c.emit_kind.as_ref())
504}
505
506impl<'a> Relation<'a> {
507 pub fn convert_children<S: Scope>(
511 refs: Vec<Option<&'a Rel>>,
512 ctx: &S,
513 ) -> (Vec<Option<Relation<'a>>>, usize) {
514 let mut children = vec![];
515 let mut inputs = 0;
516
517 for maybe_rel in refs {
518 match maybe_rel {
519 Some(rel) => {
520 let child = Relation::from_rel(rel, ctx);
521 inputs += child.emitted();
522 children.push(Some(child));
523 }
524 None => children.push(None),
525 }
526 }
527
528 (children, inputs)
529 }
530}
531
532impl<'a> Relation<'a> {
533 fn from_filter<S: Scope>(rel: &'a FilterRel, ctx: &S) -> Self {
534 let condition = rel
535 .condition
536 .as_ref()
537 .map(|c| Value::Expression(c.as_ref()));
538 let condition = Value::expect(condition, || {
539 PlanError::unimplemented("FilterRel", Some("condition"), "Condition is None")
540 });
541 let positional = vec![condition];
542 let arguments = Some(RelationArgs::inline(positional, vec![]));
543 let emit = get_emit(rel.common.as_ref());
544 let (children, columns) = Relation::convert_children(vec![rel.input.as_deref()], ctx);
545 let columns = (0..columns).map(|i| Value::Reference(i as i32)).collect();
546
547 Relation {
548 name: Cow::Borrowed("Filter"),
549 arguments,
550 columns,
551 emit,
552 output_syntax: OutputSyntax::Implicit,
553 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
554 children,
555 }
556 }
557
558 fn from_project<S: Scope>(rel: &'a ProjectRel, ctx: &S) -> Self {
559 let (children, input_columns) = Relation::convert_children(vec![rel.input.as_deref()], ctx);
560 let mut columns: Vec<Value> = vec![];
561 for i in 0..input_columns {
562 columns.push(Value::Reference(i as i32));
563 }
564 for expr in &rel.expressions {
565 columns.push(Value::Expression(expr));
566 }
567
568 Relation {
569 name: Cow::Borrowed("Project"),
570 arguments: None,
571 columns,
572 emit: get_emit(rel.common.as_ref()),
573 output_syntax: OutputSyntax::Implicit,
574 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
575 children,
576 }
577 }
578
579 pub fn from_rel<S: Scope>(rel: &'a Rel, ctx: &S) -> Self {
580 match rel.rel_type.as_ref() {
581 Some(RelType::Read(r)) => Relation::from_read(r, ctx),
582 Some(RelType::Filter(r)) => Relation::from_filter(r, ctx),
583 Some(RelType::Project(r)) => Relation::from_project(r, ctx),
584 Some(RelType::Aggregate(r)) => Relation::from_aggregate(r, ctx),
585 Some(RelType::Sort(r)) => Relation::from_sort(r, ctx),
586 Some(RelType::Fetch(r)) => Relation::from_fetch(r, ctx),
587 Some(RelType::Join(r)) => Relation::from_join(r, ctx),
588 Some(RelType::Set(r)) => Relation::from_set(r, ctx),
589 Some(RelType::Cross(r)) => Relation::from_cross(r, ctx),
590 Some(RelType::ExtensionLeaf(r)) => Relation::from_extension_leaf(r, ctx),
591 Some(RelType::ExtensionSingle(r)) => Relation::from_extension_single(r, ctx),
592 Some(RelType::ExtensionMulti(r)) => Relation::from_extension_multi(r, ctx),
593 _ => {
594 let name = rel.name();
595 let token = ctx.failure(FormatError::Format(PlanError::unimplemented(
596 "Rel",
597 Some(name),
598 format!("{name} is not yet supported in the text format"),
599 )));
600 Relation {
601 name: Cow::Owned(format!("{token}")),
602 arguments: None,
603 columns: vec![],
604 emit: None,
605 output_syntax: OutputSyntax::Implicit,
606 addenda: AddendumLines::none(),
607 children: vec![],
608 }
609 }
610 }
611 }
612
613 fn from_extension_leaf<S: Scope>(rel: &'a ExtensionLeafRel, ctx: &S) -> Self {
614 let detail_ref = rel.detail.as_ref().map(AnyRef::from);
615 let decoded = match detail_ref {
616 Some(d) => ctx.extension_registry().decode(d),
617 None => Err(ExtensionError::MissingDetail),
618 };
619 Relation::from_extension("ExtensionLeaf", decoded, vec![], ctx)
620 }
621
622 fn from_extension_single<S: Scope>(rel: &'a ExtensionSingleRel, ctx: &S) -> Self {
623 let detail_ref = rel.detail.as_ref().map(AnyRef::from);
624 let decoded = match detail_ref {
625 Some(d) => ctx.extension_registry().decode(d),
626 None => Err(ExtensionError::MissingDetail),
627 };
628 Relation::from_extension("ExtensionSingle", decoded, vec![rel.input.as_deref()], ctx)
629 }
630
631 fn from_extension_multi<S: Scope>(rel: &'a ExtensionMultiRel, ctx: &S) -> Self {
632 let detail_ref = rel.detail.as_ref().map(AnyRef::from);
633 let decoded = match detail_ref {
634 Some(d) => ctx.extension_registry().decode(d),
635 None => Err(ExtensionError::MissingDetail),
636 };
637 let mut child_refs: Vec<Option<&'a Rel>> = vec![];
638 for input in &rel.inputs {
639 child_refs.push(Some(input));
640 }
641 Relation::from_extension("ExtensionMulti", decoded, child_refs, ctx)
642 }
643
644 fn from_extension<S: Scope>(
645 ext_type: &'static str,
646 decoded: Result<(String, ExtensionArgs), ExtensionError>,
647 child_refs: Vec<Option<&'a Rel>>,
648 ctx: &S,
649 ) -> Self {
650 match decoded {
651 Ok((name, args)) => {
652 let (children, _) = Relation::convert_children(child_refs, ctx);
653 let mut positional = vec![];
654 for value in args.positional {
655 positional.push(Value::ExtensionArgument(value));
656 }
657 let mut named = vec![];
658 for (key, value) in args.named {
659 named.push(NamedArg {
660 name: Cow::Owned(key),
661 value: Value::ExtensionArgument(value),
662 });
663 }
664 let mut columns = vec![];
665 for col in args.output_columns {
666 columns.push(Value::ExtColumn(col));
667 }
668 Relation {
669 name: Cow::Owned(format!("{}:{}", ext_type, name)),
670 arguments: Some(RelationArgs::inline(positional, named)),
671 columns,
672 emit: None,
673 output_syntax: OutputSyntax::Implicit,
674 addenda: AddendumLines::none(),
678 children,
679 }
680 }
681 Err(error) => {
682 let (children, _) = Relation::convert_children(child_refs, ctx);
683 Relation {
684 name: Cow::Borrowed(ext_type),
685 arguments: None,
686 columns: vec![Value::Missing(PlanError::invalid(
687 "extension",
688 None::<&str>,
689 error.to_string(),
690 ))],
691 emit: None,
692 output_syntax: OutputSyntax::Implicit,
693 addenda: AddendumLines::none(),
694 children,
695 }
696 }
697 }
698 }
699
700 fn from_aggregate<S: Scope>(rel: &'a AggregateRel, ctx: &S) -> Self {
710 let mut grouping_sets: Vec<Vec<Value>> = vec![]; let expression_list: Vec<Value>; #[allow(deprecated)]
717 if rel.grouping_expressions.is_empty()
718 && !rel.groupings.is_empty()
719 && !rel.groupings[0].grouping_expressions.is_empty()
720 {
721 (expression_list, grouping_sets) = Relation::get_grouping_sets(rel);
722 } else {
723 expression_list = rel
724 .grouping_expressions
725 .iter()
726 .map(Value::Expression)
727 .collect::<Vec<_>>(); for group in &rel.groupings {
729 let mut grouping_set: Vec<Value> = vec![];
730 for i in &group.expression_references {
731 let value = match rel.grouping_expressions.get(*i as usize) {
732 Some(expr) => Value::Expression(expr),
733 None => Value::Missing(PlanError::invalid(
734 "AggregateRel",
735 Some("groupings.expression_references"),
736 format!(
737 "expression_reference {i} is out of bounds for grouping_expressions of length {}",
738 rel.grouping_expressions.len()
739 ),
740 )),
741 };
742 grouping_set.push(value);
743 }
744 grouping_sets.push(grouping_set);
745 }
746 if rel.groupings.is_empty() {
748 grouping_sets.push(vec![]);
749 }
750 }
751
752 let is_single = grouping_sets.len() == 1;
753 let mut positional: Vec<Value> = vec![];
754 for g in grouping_sets {
755 if g.is_empty() {
756 positional.push(Value::EmptyGroup);
757 } else if is_single {
758 positional.extend(g);
760 } else {
761 positional.push(Value::Tuple(g));
762 }
763 }
764
765 let arguments = Some(RelationArgs::inline(positional, vec![]));
767
768 let mut all_outputs: Vec<Value> = expression_list;
770
771 for m in &rel.measures {
774 if let Some(agg_fn) = m.measure.as_ref() {
775 all_outputs.push(Value::AggregateFunction(agg_fn));
776 }
777 }
778 let emit = get_emit(rel.common.as_ref());
779 let (children, _) = Relation::convert_children(vec![rel.input.as_deref()], ctx);
780
781 Relation {
782 name: Cow::Borrowed("Aggregate"),
783 arguments,
784 columns: all_outputs,
785 emit,
786 output_syntax: OutputSyntax::Implicit,
787 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
788 children,
789 }
790 }
791
792 fn get_grouping_sets(rel: &'a AggregateRel) -> (Vec<Value<'a>>, Vec<Vec<Value<'a>>>) {
793 let mut grouping_sets: Vec<Vec<Value>> = vec![];
794 let mut expression_list: Vec<Value> = Vec::new();
795
796 let mut seen_expressions = HashSet::new();
800
801 for group in &rel.groupings {
802 let mut grouping_set: Vec<Value> = vec![];
803 #[allow(deprecated)]
804 for exp in &group.grouping_expressions {
805 if seen_expressions.insert(exp.encode_to_vec()) {
809 expression_list.push(Value::Expression(exp)); }
811 grouping_set.push(Value::Expression(exp));
812 }
813 grouping_sets.push(grouping_set);
814 }
815 (expression_list, grouping_sets)
816 }
817}
818
819impl Textify for RelRoot {
820 fn name() -> &'static str {
821 "RelRoot"
822 }
823
824 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
825 let names = self.names.iter().map(|n| Name(n)).collect::<Vec<_>>();
826
827 write!(
828 w,
829 "{}Root[{}]",
830 ctx.indent(),
831 ctx.separated(names.iter(), ", ")
832 )?;
833 let child_scope = ctx.push_indent();
834 for child in self.input.iter() {
835 writeln!(w)?;
836 child.textify(&child_scope, w)?;
837 }
838
839 Ok(())
840 }
841}
842
843impl Textify for PlanRelType {
844 fn name() -> &'static str {
845 "PlanRelType"
846 }
847
848 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
849 match self {
850 PlanRelType::Rel(rel) => rel.textify(ctx, w),
851 PlanRelType::Root(root) => root.textify(ctx, w),
852 }
853 }
854}
855
856impl Textify for PlanRel {
857 fn name() -> &'static str {
858 "PlanRel"
859 }
860
861 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
864 write!(w, "{}", ctx.expect(self.rel_type.as_ref()))
865 }
866}
867
868impl<'a> Relation<'a> {
869 fn from_sort<S: Scope>(rel: &'a SortRel, ctx: &S) -> Self {
870 let (children, input_columns) = Relation::convert_children(vec![rel.input.as_deref()], ctx);
871 let mut positional = vec![];
872 for sort_field in &rel.sorts {
873 positional.push(Value::from(sort_field));
874 }
875 let arguments = Some(RelationArgs::inline(positional, vec![]));
876 let mut col_values = vec![];
878 for i in 0..input_columns {
879 col_values.push(Value::Reference(i as i32));
880 }
881 let emit = get_emit(rel.common.as_ref());
882 Relation {
883 name: Cow::Borrowed("Sort"),
884 arguments,
885 columns: col_values,
886 emit,
887 output_syntax: OutputSyntax::Implicit,
888 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
889 children,
890 }
891 }
892
893 fn from_fetch<S: Scope>(rel: &'a FetchRel, ctx: &S) -> Self {
894 let (children, input_columns) = Relation::convert_children(vec![rel.input.as_deref()], ctx);
895 let mut named_args: Vec<NamedArg> = vec![];
896 match &rel.count_mode {
897 Some(CountMode::CountExpr(expr)) => {
898 named_args.push(NamedArg {
899 name: Cow::Borrowed("limit"),
900 value: Value::Expression(expr),
901 });
902 }
903 #[allow(deprecated)]
904 Some(CountMode::Count(val)) => {
905 named_args.push(NamedArg {
906 name: Cow::Borrowed("limit"),
907 value: Value::Integer(*val),
908 });
909 }
910 None => {}
911 }
912 if let Some(offset) = &rel.offset_mode {
913 match offset {
914 substrait::proto::fetch_rel::OffsetMode::OffsetExpr(expr) => {
915 named_args.push(NamedArg {
916 name: Cow::Borrowed("offset"),
917 value: Value::Expression(expr),
918 });
919 }
920 #[allow(deprecated)]
921 substrait::proto::fetch_rel::OffsetMode::Offset(val) => {
922 named_args.push(NamedArg {
923 name: Cow::Borrowed("offset"),
924 value: Value::Integer(*val),
925 });
926 }
927 }
928 }
929
930 let emit = get_emit(rel.common.as_ref());
931 let columns: Vec<Value> = (0..input_columns)
933 .map(|i| Value::Reference(i as i32))
934 .collect();
935 Relation {
936 name: Cow::Borrowed("Fetch"),
937 arguments: Some(RelationArgs::inline(vec![], named_args)),
938 columns,
939 emit,
940 output_syntax: OutputSyntax::Implicit,
941 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
942 children,
943 }
944 }
945}
946
947fn join_output_columns(
948 join_type: join_rel::JoinType,
949 left_columns: usize,
950 right_columns: usize,
951) -> Vec<Value<'static>> {
952 let total_columns = match join_type {
953 join_rel::JoinType::Inner
955 | join_rel::JoinType::Left
956 | join_rel::JoinType::Right
957 | join_rel::JoinType::Outer => left_columns + right_columns,
958
959 join_rel::JoinType::LeftSemi | join_rel::JoinType::LeftAnti => left_columns,
961
962 join_rel::JoinType::RightSemi | join_rel::JoinType::RightAnti => right_columns,
964
965 join_rel::JoinType::LeftSingle => left_columns,
967 join_rel::JoinType::RightSingle => right_columns,
968
969 join_rel::JoinType::LeftMark => left_columns + 1,
971 join_rel::JoinType::RightMark => right_columns + 1,
972
973 join_rel::JoinType::Unspecified => left_columns + right_columns,
975 };
976
977 (0..total_columns)
979 .map(|i| Value::Reference(i as i32))
980 .collect()
981}
982
983impl<'a> Relation<'a> {
984 fn from_join<S: Scope>(rel: &'a JoinRel, ctx: &S) -> Self {
985 let (children, _total_columns) =
986 Relation::convert_children(vec![rel.left.as_deref(), rel.right.as_deref()], ctx);
987
988 assert_eq!(
990 children.len(),
991 2,
992 "convert_children should return same number of elements as input"
993 );
994
995 let left_columns = match &children[0] {
997 Some(child) => child.emitted(),
998 None => 0,
999 };
1000 let right_columns = match &children[1] {
1001 Some(child) => child.emitted(),
1002 None => 0,
1003 };
1004
1005 let (join_type, join_type_value) = match join_rel::JoinType::try_from(rel.r#type) {
1008 Ok(join_type) => {
1009 let join_type_value = match join_type.as_enum_str() {
1010 Ok(s) => Value::Enum(s),
1011 Err(e) => Value::Missing(e),
1012 };
1013 (join_type, join_type_value)
1014 }
1015 Err(_) => {
1016 let join_type_error = Value::Missing(PlanError::invalid(
1018 "JoinRel",
1019 Some("type"),
1020 format!("Unknown join type: {}", rel.r#type),
1021 ));
1022 (join_rel::JoinType::Unspecified, join_type_error)
1023 }
1024 };
1025
1026 let condition = rel
1028 .expression
1029 .as_ref()
1030 .map(|c| Value::Expression(c.as_ref()));
1031 let condition = Value::expect(condition, || {
1032 PlanError::unimplemented("JoinRel", Some("expression"), "Join condition is None")
1033 });
1034
1035 let positional = vec![join_type_value, condition];
1036 let mut named = vec![];
1037 if let Some(post_join_filter) = rel.post_join_filter.as_ref() {
1038 named.push(NamedArg {
1039 name: Cow::Borrowed("post_filter"),
1040 value: Value::Expression(post_join_filter.as_ref()),
1041 });
1042 }
1043 let arguments = Some(RelationArgs::inline(positional, named));
1044
1045 let emit = get_emit(rel.common.as_ref());
1046 let columns = join_output_columns(join_type, left_columns, right_columns);
1047
1048 Relation {
1049 name: Cow::Borrowed("Join"),
1050 arguments,
1051 columns,
1052 emit,
1053 output_syntax: OutputSyntax::Implicit,
1054 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
1055 children,
1056 }
1057 }
1058
1059 fn from_set<S: Scope>(rel: &'a SetRel, ctx: &S) -> Self {
1060 let child_refs: Vec<Option<&'a Rel>> = rel.inputs.iter().map(Some).collect();
1061 let (children, total_columns) = Relation::convert_children(child_refs, ctx);
1062
1063 let width = if children.is_empty() {
1068 0
1069 } else {
1070 total_columns / children.len()
1071 };
1072
1073 let op_value = decode_enum_field::<set_rel::SetOp>(rel.op, "SetRel", "op");
1074
1075 let arguments = Some(RelationArgs::inline(vec![op_value], vec![]));
1076 let emit = get_emit(rel.common.as_ref());
1077 let columns = (0..width).map(|i| Value::Reference(i as i32)).collect();
1078
1079 Relation {
1080 name: Cow::Borrowed("Set"),
1081 arguments,
1082 columns,
1083 emit,
1084 output_syntax: OutputSyntax::Implicit,
1085 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
1086 children,
1087 }
1088 }
1089
1090 fn from_cross<S: Scope>(rel: &'a CrossRel, ctx: &S) -> Self {
1091 let (children, total_columns) =
1092 Relation::convert_children(vec![rel.left.as_deref(), rel.right.as_deref()], ctx);
1093
1094 let columns = (0..total_columns)
1097 .map(|i| Value::Reference(i as i32))
1098 .collect();
1099
1100 Relation {
1101 name: Cow::Borrowed("Cross"),
1102 arguments: None,
1103 columns,
1104 emit: get_emit(rel.common.as_ref()),
1105 output_syntax: OutputSyntax::Implicit,
1106 addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()),
1107 children,
1108 }
1109 }
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114 use substrait::proto::aggregate_rel::Grouping;
1115 use substrait::proto::expression::literal::LiteralType;
1116 use substrait::proto::expression::{Literal, RexType, ScalarFunction};
1117 use substrait::proto::function_argument::ArgType;
1118 use substrait::proto::read_rel::{NamedTable, ReadType};
1119 use substrait::proto::rel_common::{Direct, Emit};
1120 use substrait::proto::r#type::{self as ptype, Boolean, I64, Kind, Nullability, Struct};
1121 use substrait::proto::{
1122 AggregateFunction, Expression, FunctionArgument, NamedStruct, ReadRel, Type, aggregate_rel,
1123 };
1124
1125 use super::*;
1126 use crate::fixtures::TestContext;
1127 use crate::parser::expressions::FieldIndex;
1128 use crate::textify::expressions::Reference;
1129
1130 #[test]
1131 fn test_read_rel() {
1132 let ctx = TestContext::new();
1133
1134 let read_rel = ReadRel {
1136 common: None,
1137 base_schema: Some(NamedStruct {
1138 names: vec!["col1".into(), "column 2".into()],
1139 r#struct: Some(Struct {
1140 type_variation_reference: 0,
1141 types: vec![
1142 Type {
1143 kind: Some(Kind::I32(ptype::I32 {
1144 type_variation_reference: 0,
1145 nullability: Nullability::Nullable as i32,
1146 })),
1147 },
1148 Type {
1149 kind: Some(Kind::String(ptype::String {
1150 type_variation_reference: 0,
1151 nullability: Nullability::Nullable as i32,
1152 })),
1153 },
1154 ],
1155 nullability: Nullability::Nullable as i32,
1156 }),
1157 }),
1158 filter: None,
1159 best_effort_filter: None,
1160 projection: None,
1161 advanced_extension: None,
1162 read_type: Some(ReadType::NamedTable(NamedTable {
1163 names: vec!["some_db".into(), "test_table".into()],
1164 advanced_extension: None,
1165 })),
1166 };
1167
1168 let rel = Rel {
1169 rel_type: Some(RelType::Read(Box::new(read_rel))),
1170 };
1171 let (result, errors) = ctx.textify(&rel);
1172 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1173 assert_eq!(
1174 result,
1175 "Read[some_db.test_table => col1:i32?, \"column 2\":string?]"
1176 );
1177 }
1178
1179 #[test]
1180 fn test_filter_rel() {
1181 let ctx = TestContext::new()
1182 .with_urn(1, "test_urn")
1183 .with_function(1, 10, "gt");
1184
1185 let read_rel = ReadRel {
1187 common: None,
1188 base_schema: Some(NamedStruct {
1189 names: vec!["col1".into(), "col2".into()],
1190 r#struct: Some(Struct {
1191 type_variation_reference: 0,
1192 types: vec![
1193 Type {
1194 kind: Some(Kind::I32(ptype::I32 {
1195 type_variation_reference: 0,
1196 nullability: Nullability::Nullable as i32,
1197 })),
1198 },
1199 Type {
1200 kind: Some(Kind::I32(ptype::I32 {
1201 type_variation_reference: 0,
1202 nullability: Nullability::Nullable as i32,
1203 })),
1204 },
1205 ],
1206 nullability: Nullability::Nullable as i32,
1207 }),
1208 }),
1209 filter: None,
1210 best_effort_filter: None,
1211 projection: None,
1212 advanced_extension: None,
1213 read_type: Some(ReadType::NamedTable(NamedTable {
1214 names: vec!["test_table".into()],
1215 advanced_extension: None,
1216 })),
1217 };
1218
1219 let filter_expr = Expression {
1221 rex_type: Some(RexType::ScalarFunction(ScalarFunction {
1222 function_reference: 10, arguments: vec![
1224 FunctionArgument {
1225 arg_type: Some(ArgType::Value(Reference(0).into())),
1226 },
1227 FunctionArgument {
1228 arg_type: Some(ArgType::Value(Expression {
1229 rex_type: Some(RexType::Literal(Literal {
1230 literal_type: Some(LiteralType::I32(10)),
1231 nullable: false,
1232 type_variation_reference: 0,
1233 })),
1234 })),
1235 },
1236 ],
1237 options: vec![],
1238 output_type: Some(Type {
1239 kind: Some(Kind::Bool(Boolean {
1240 nullability: Nullability::Required as i32,
1241 type_variation_reference: 0,
1242 })),
1243 }),
1244 #[allow(deprecated)]
1245 args: vec![],
1246 })),
1247 };
1248
1249 let filter_rel = FilterRel {
1250 common: None,
1251 input: Some(Box::new(Rel {
1252 rel_type: Some(RelType::Read(Box::new(read_rel))),
1253 })),
1254 condition: Some(Box::new(filter_expr)),
1255 advanced_extension: None,
1256 };
1257
1258 let rel = Rel {
1259 rel_type: Some(RelType::Filter(Box::new(filter_rel))),
1260 };
1261
1262 let (result, errors) = ctx.textify(&rel);
1263 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1264 let expected = r#"
1265Filter[gt($0, 10:i32):boolean => $0, $1]
1266 Read[test_table => col1:i32?, col2:i32?]"#
1267 .trim_start();
1268 assert_eq!(result, expected);
1269 }
1270
1271 #[test]
1272 fn test_aggregate_function_textify() {
1273 let ctx = TestContext::new()
1274 .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1275 .with_function(1, 10, "sum")
1276 .with_function(1, 11, "count");
1277
1278 let agg_fn = get_aggregate_func(10, 1);
1280
1281 let value = Value::AggregateFunction(&agg_fn);
1282 let (result, errors) = ctx.textify(&value);
1283
1284 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1285 assert_eq!(result, "sum($1):i64");
1286 }
1287
1288 #[test]
1289 fn test_aggregate_relation_textify() {
1290 let ctx = TestContext::new()
1291 .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1292 .with_function(1, 10, "sum")
1293 .with_function(1, 11, "count");
1294
1295 let agg_fn1 = get_aggregate_func(10, 1);
1297 let agg_fn2 = get_aggregate_func(11, 1);
1298
1299 let grouping_expressions = vec![Expression {
1300 rex_type: Some(RexType::Selection(Box::new(
1301 FieldIndex(0).to_field_reference(),
1302 ))),
1303 }];
1304
1305 let measures = vec![
1306 aggregate_rel::Measure {
1307 measure: Some(agg_fn1),
1308 filter: None,
1309 },
1310 aggregate_rel::Measure {
1311 measure: Some(agg_fn2),
1312 filter: None,
1313 },
1314 ];
1315
1316 let common = Some(RelCommon {
1317 emit_kind: Some(EmitKind::Emit(Emit {
1318 output_mapping: vec![1, 2], })),
1320 ..Default::default()
1321 });
1322
1323 let aggregate_rel = create_aggregate_rel(grouping_expressions, vec![], measures, common);
1324
1325 let rel = Rel {
1326 rel_type: Some(RelType::Aggregate(Box::new(aggregate_rel))),
1327 };
1328 let (result, errors) = ctx.textify(&rel);
1329
1330 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1331 assert!(result.contains("Aggregate[_ => sum($1):i64, count($1):i64]"));
1333 }
1334
1335 #[test]
1336 fn test_multiple_groupings_on_aggregate_deprecated() {
1337 let ctx = TestContext::new()
1340 .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1341 .with_function(1, 11, "count");
1342
1343 let grouping_expr_0 = create_exp(0);
1344 let grouping_expr_1 = create_exp(1);
1345
1346 let grouping_sets = vec![
1347 aggregate_rel::Grouping {
1348 #[allow(deprecated)]
1349 grouping_expressions: vec![grouping_expr_0.clone()],
1350 expression_references: vec![],
1351 },
1352 aggregate_rel::Grouping {
1353 #[allow(deprecated)]
1354 grouping_expressions: vec![grouping_expr_0.clone(), grouping_expr_1.clone()],
1355 expression_references: vec![],
1356 },
1357 ];
1358
1359 let aggregate_rel = create_aggregate_rel(vec![], grouping_sets, vec![], None);
1360
1361 let rel = Rel {
1362 rel_type: Some(RelType::Aggregate(Box::new(aggregate_rel))),
1363 };
1364 let (result, errors) = ctx.textify(&rel);
1365
1366 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1367 assert!(result.contains("Aggregate[($0), ($0, $1) => $0, $1]"));
1368 }
1369
1370 #[test]
1371 fn test_multiple_groupings_with_measure_deprecated() {
1372 let ctx = TestContext::new()
1375 .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1376 .with_function(1, 11, "count");
1377
1378 let agg_fn1 = get_aggregate_func(11, 2);
1379
1380 let grouping_expr_0 = create_exp(0);
1381 let grouping_expr_1 = create_exp(1);
1382
1383 let grouping_sets = vec![
1384 aggregate_rel::Grouping {
1385 #[allow(deprecated)]
1386 grouping_expressions: vec![grouping_expr_0.clone()],
1387 expression_references: vec![],
1388 },
1389 aggregate_rel::Grouping {
1390 #[allow(deprecated)]
1391 grouping_expressions: vec![grouping_expr_0.clone(), grouping_expr_1.clone()],
1392 expression_references: vec![],
1393 },
1394 ];
1395
1396 let measures = vec![aggregate_rel::Measure {
1397 measure: Some(agg_fn1),
1398 filter: None,
1399 }];
1400
1401 let aggregate_rel = create_aggregate_rel(vec![], grouping_sets, measures, None);
1402
1403 let rel = Rel {
1404 rel_type: Some(RelType::Aggregate(Box::new(aggregate_rel))),
1405 };
1406 let (result, errors) = ctx.textify(&rel);
1407
1408 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1409 assert!(result.contains("($0), ($0, $1) => $0, $1, count($2):i64"));
1410 }
1411
1412 #[test]
1413 fn test_multiple_groupings_on_aggregate() {
1414 let ctx = TestContext::new()
1415 .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1416 .with_function(1, 11, "count");
1417
1418 let agg_fn2 = get_aggregate_func(11, 2);
1419
1420 let grouping_expressions = vec![
1421 Expression {
1422 rex_type: Some(RexType::Selection(Box::new(
1423 FieldIndex(0).to_field_reference(),
1424 ))),
1425 },
1426 Expression {
1427 rex_type: Some(RexType::Selection(Box::new(
1428 FieldIndex(1).to_field_reference(),
1429 ))),
1430 },
1431 ];
1432
1433 let grouping_sets = vec![
1434 Grouping {
1435 #[allow(deprecated)]
1436 grouping_expressions: vec![],
1437 expression_references: vec![0, 1],
1438 },
1439 Grouping {
1440 #[allow(deprecated)]
1441 grouping_expressions: vec![],
1442 expression_references: vec![0, 1],
1443 },
1444 Grouping {
1445 #[allow(deprecated)]
1446 grouping_expressions: vec![],
1447 expression_references: vec![1],
1448 },
1449 Grouping {
1450 #[allow(deprecated)]
1451 grouping_expressions: vec![],
1452 expression_references: vec![1, 1],
1453 },
1454 Grouping {
1455 #[allow(deprecated)]
1456 grouping_expressions: vec![],
1457 expression_references: vec![],
1458 },
1459 ];
1460
1461 let measures = vec![aggregate_rel::Measure {
1462 measure: Some(agg_fn2),
1463 filter: None,
1464 }];
1465
1466 let aggregate_rel =
1467 create_aggregate_rel(grouping_expressions, grouping_sets, measures, None);
1468
1469 let rel = Rel {
1470 rel_type: Some(RelType::Aggregate(Box::new(aggregate_rel))),
1471 };
1472 let (result, errors) = ctx.textify(&rel);
1473
1474 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1475 assert!(
1476 result.contains(
1477 "Aggregate[($0, $1), ($0, $1), ($1), ($1, $1), _ => $0, $1, count($2):i64]"
1478 )
1479 );
1480 }
1481
1482 #[test]
1483 fn test_deprecated_reordered_grouping() {
1484 let ctx = TestContext::new();
1492 let grouping_expr_5 = create_exp(5);
1493
1494 let grouping_sets = vec![aggregate_rel::Grouping {
1495 #[allow(deprecated)]
1496 grouping_expressions: vec![grouping_expr_5],
1497 expression_references: vec![],
1498 }];
1499
1500 let aggregate_rel = create_aggregate_rel(vec![], grouping_sets, vec![], None);
1501 let rel = Rel {
1502 rel_type: Some(RelType::Aggregate(Box::new(aggregate_rel))),
1503 };
1504 let (result, errors) = ctx.textify(&rel);
1505
1506 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1507 assert!(result.contains("Aggregate[$5 => $5]"));
1508 }
1509
1510 #[test]
1511 fn test_reordered_grouping_textifies_expression_not_raw_index() {
1512 let ctx = TestContext::new()
1513 .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1514 .with_function(1, 11, "count");
1515
1516 let agg_fn2 = get_aggregate_func(11, 1);
1517
1518 let grouping_expressions = vec![
1524 Expression {
1525 rex_type: Some(RexType::Selection(Box::new(
1526 FieldIndex(2).to_field_reference(),
1527 ))),
1528 },
1529 Expression {
1530 rex_type: Some(RexType::Selection(Box::new(
1531 FieldIndex(0).to_field_reference(),
1532 ))),
1533 },
1534 ];
1535
1536 let grouping_sets = vec![Grouping {
1537 #[allow(deprecated)]
1538 grouping_expressions: vec![],
1539 expression_references: vec![0, 1],
1540 }];
1541
1542 let measures = vec![aggregate_rel::Measure {
1543 measure: Some(agg_fn2),
1544 filter: None,
1545 }];
1546
1547 let aggregate_rel =
1548 create_aggregate_rel(grouping_expressions, grouping_sets, measures, None);
1549
1550 let rel = Rel {
1551 rel_type: Some(RelType::Aggregate(Box::new(aggregate_rel))),
1552 };
1553 let (result, errors) = ctx.textify(&rel);
1554
1555 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1556 assert!(result.contains("Aggregate[$2, $0 => $2, $0, count($1):i64]"));
1557 }
1558
1559 #[test]
1560 fn test_join_relation_unknown_type() {
1561 let ctx = TestContext::new();
1562
1563 let join_rel = JoinRel {
1565 left: Some(Box::new(Rel {
1566 rel_type: Some(RelType::Read(Box::default())),
1567 })),
1568 right: Some(Box::new(Rel {
1569 rel_type: Some(RelType::Read(Box::default())),
1570 })),
1571 expression: Some(Box::new(Expression::default())),
1572 r#type: 999, common: None,
1574 post_join_filter: None,
1575 advanced_extension: None,
1576 };
1577
1578 let rel = Rel {
1579 rel_type: Some(RelType::Join(Box::new(join_rel))),
1580 };
1581 let (result, errors) = ctx.textify(&rel);
1582
1583 assert!(!errors.is_empty(), "Expected errors for unknown join type");
1585 assert!(
1586 result.contains("!{JoinRel}"),
1587 "Expected error token for unknown join type"
1588 );
1589 assert!(
1590 result.contains("Join["),
1591 "Expected Join relation to be formatted"
1592 );
1593 }
1594
1595 #[test]
1596 fn test_set_relation_unknown_op() {
1597 let ctx = TestContext::new();
1598
1599 let set_rel = SetRel {
1600 common: None,
1601 inputs: vec![
1602 Rel {
1603 rel_type: Some(RelType::Read(Box::default())),
1604 },
1605 Rel {
1606 rel_type: Some(RelType::Read(Box::default())),
1607 },
1608 ],
1609 op: 999, advanced_extension: None,
1611 };
1612 let rel = Rel {
1613 rel_type: Some(RelType::Set(set_rel)),
1614 };
1615
1616 let (result, errors) = ctx.textify(&rel);
1617 assert!(!errors.is_empty(), "Expected errors for unknown set op");
1618 assert!(
1619 result.contains("!{SetRel}"),
1620 "Expected error token for unknown set op, got: {result}"
1621 );
1622 assert!(
1623 result.contains("Set["),
1624 "Expected Set relation to be formatted"
1625 );
1626 }
1627
1628 fn basic_read(table: &str) -> Rel {
1629 Rel {
1630 rel_type: Some(RelType::Read(Box::new(ReadRel {
1631 common: None,
1632 base_schema: Some(get_basic_schema()),
1633 filter: None,
1634 best_effort_filter: None,
1635 projection: None,
1636 advanced_extension: None,
1637 read_type: Some(ReadType::NamedTable(NamedTable {
1638 names: vec![table.into()],
1639 advanced_extension: None,
1640 })),
1641 }))),
1642 }
1643 }
1644
1645 #[test]
1646 fn test_cross_relation() {
1647 let ctx = TestContext::new();
1648
1649 let cross = CrossRel {
1652 common: Some(RelCommon {
1653 emit_kind: Some(EmitKind::Direct(Direct {})),
1654 ..Default::default()
1655 }),
1656 left: Some(Box::new(basic_read("left_tbl"))),
1657 right: Some(Box::new(basic_read("right_tbl"))),
1658 advanced_extension: None,
1659 };
1660 let rel = Rel {
1661 rel_type: Some(RelType::Cross(Box::new(cross))),
1662 };
1663
1664 let (result, errors) = ctx.textify(&rel);
1665 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1666 let expected = r#"
1667Cross[$0, $1, $2, $3, $4, $5]
1668 Read[left_tbl => category:string?, amount:fp64?, value:i32?]
1669 Read[right_tbl => category:string?, amount:fp64?, value:i32?]"#
1670 .trim_start();
1671 assert_eq!(result, expected);
1672 }
1673
1674 #[test]
1675 fn test_cross_relation_prunes_columns() {
1676 let ctx = TestContext::new();
1677
1678 let cross = CrossRel {
1680 common: Some(RelCommon {
1681 emit_kind: Some(EmitKind::Emit(Emit {
1682 output_mapping: vec![0, 3],
1683 })),
1684 ..Default::default()
1685 }),
1686 left: Some(Box::new(basic_read("left_tbl"))),
1687 right: Some(Box::new(basic_read("right_tbl"))),
1688 advanced_extension: None,
1689 };
1690 let rel = Rel {
1691 rel_type: Some(RelType::Cross(Box::new(cross))),
1692 };
1693
1694 let (result, errors) = ctx.textify(&rel);
1695 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
1696 let expected = r#"
1697Cross[$0, $3]
1698 Read[left_tbl => category:string?, amount:fp64?, value:i32?]
1699 Read[right_tbl => category:string?, amount:fp64?, value:i32?]"#
1700 .trim_start();
1701 assert_eq!(result, expected);
1702 }
1703
1704 #[test]
1705 fn test_join_type_enum_textify() {
1706 assert_eq!(join_rel::JoinType::Inner.as_enum_str().unwrap(), "Inner");
1708 assert_eq!(join_rel::JoinType::Left.as_enum_str().unwrap(), "Left");
1709 assert_eq!(
1710 join_rel::JoinType::LeftSemi.as_enum_str().unwrap(),
1711 "LeftSemi"
1712 );
1713 assert_eq!(
1714 join_rel::JoinType::LeftAnti.as_enum_str().unwrap(),
1715 "LeftAnti"
1716 );
1717 }
1718
1719 #[test]
1720 fn test_join_output_columns() {
1721 let inner_cols = super::join_output_columns(join_rel::JoinType::Inner, 2, 3);
1723 assert_eq!(inner_cols.len(), 5); assert!(matches!(inner_cols[0], Value::Reference(0)));
1725 assert!(matches!(inner_cols[4], Value::Reference(4)));
1726
1727 let left_semi_cols = super::join_output_columns(join_rel::JoinType::LeftSemi, 2, 3);
1729 assert_eq!(left_semi_cols.len(), 2); assert!(matches!(left_semi_cols[0], Value::Reference(0)));
1731 assert!(matches!(left_semi_cols[1], Value::Reference(1)));
1732
1733 let right_semi_cols = super::join_output_columns(join_rel::JoinType::RightSemi, 2, 3);
1735 assert_eq!(right_semi_cols.len(), 3); assert!(matches!(right_semi_cols[0], Value::Reference(0))); assert!(matches!(right_semi_cols[1], Value::Reference(1)));
1738 assert!(matches!(right_semi_cols[2], Value::Reference(2))); let left_mark_cols = super::join_output_columns(join_rel::JoinType::LeftMark, 2, 3);
1742 assert_eq!(left_mark_cols.len(), 3); assert!(matches!(left_mark_cols[0], Value::Reference(0)));
1744 assert!(matches!(left_mark_cols[1], Value::Reference(1)));
1745 assert!(matches!(left_mark_cols[2], Value::Reference(2))); let right_mark_cols = super::join_output_columns(join_rel::JoinType::RightMark, 2, 3);
1749 assert_eq!(right_mark_cols.len(), 4); assert!(matches!(right_mark_cols[0], Value::Reference(0))); assert!(matches!(right_mark_cols[1], Value::Reference(1)));
1752 assert!(matches!(right_mark_cols[2], Value::Reference(2))); assert!(matches!(right_mark_cols[3], Value::Reference(3))); }
1755
1756 fn get_aggregate_func(func_ref: u32, column_ind: i32) -> AggregateFunction {
1757 AggregateFunction {
1758 function_reference: func_ref,
1759 arguments: vec![FunctionArgument {
1760 arg_type: Some(ArgType::Value(Expression {
1761 rex_type: Some(RexType::Selection(Box::new(
1762 FieldIndex(column_ind).to_field_reference(),
1763 ))),
1764 })),
1765 }],
1766 options: vec![],
1767 output_type: Some(Type {
1768 kind: Some(Kind::I64(I64 {
1769 nullability: Nullability::Required as i32,
1770 type_variation_reference: 0,
1771 })),
1772 }),
1773 invocation: 0,
1774 phase: 0,
1775 sorts: vec![],
1776 #[allow(deprecated)]
1777 args: vec![],
1778 }
1779 }
1780
1781 fn create_aggregate_rel(
1782 grouping_expressions: Vec<Expression>,
1783 grouping_sets: Vec<Grouping>,
1784 measures: Vec<aggregate_rel::Measure>,
1785 common: Option<RelCommon>,
1786 ) -> AggregateRel {
1787 let common = common.or_else(|| {
1788 Some(RelCommon {
1789 emit_kind: Some(EmitKind::Direct(Direct {})),
1790 ..Default::default()
1791 })
1792 });
1793 AggregateRel {
1794 input: Some(Box::new(Rel {
1795 rel_type: Some(RelType::Read(Box::new(ReadRel {
1796 common: None,
1797 base_schema: Some(get_basic_schema()),
1798 filter: None,
1799 best_effort_filter: None,
1800 projection: None,
1801 advanced_extension: None,
1802 read_type: Some(ReadType::NamedTable(NamedTable {
1803 names: vec!["orders".into()],
1804 advanced_extension: None,
1805 })),
1806 }))),
1807 })),
1808 grouping_expressions,
1809 groupings: grouping_sets,
1810 measures,
1811 common,
1812 advanced_extension: None,
1813 }
1814 }
1815
1816 fn get_basic_schema() -> NamedStruct {
1817 NamedStruct {
1818 names: vec!["category".into(), "amount".into(), "value".into()],
1819 r#struct: Some(Struct {
1820 type_variation_reference: 0,
1821 types: vec![
1822 Type {
1823 kind: Some(Kind::String(ptype::String {
1824 type_variation_reference: 0,
1825 nullability: Nullability::Nullable as i32,
1826 })),
1827 },
1828 Type {
1829 kind: Some(Kind::Fp64(ptype::Fp64 {
1830 type_variation_reference: 0,
1831 nullability: Nullability::Nullable as i32,
1832 })),
1833 },
1834 Type {
1835 kind: Some(Kind::I32(ptype::I32 {
1836 type_variation_reference: 0,
1837 nullability: Nullability::Nullable as i32,
1838 })),
1839 },
1840 ],
1841 nullability: Nullability::Nullable as i32,
1842 }),
1843 }
1844 }
1845
1846 fn create_exp(column_ind: i32) -> Expression {
1847 Expression {
1848 rex_type: Some(RexType::Selection(Box::new(
1849 FieldIndex(column_ind).to_field_reference(),
1850 ))),
1851 }
1852 }
1853
1854 #[test]
1855 fn test_unsupported_rel_type_produces_failure_token() {
1856 use substrait::proto::ReferenceRel;
1857
1858 let ctx = TestContext::new();
1859
1860 let rel = Rel {
1864 rel_type: Some(RelType::Reference(ReferenceRel { subtree_ordinal: 0 })),
1865 };
1866
1867 let (result, errors) = ctx.textify(&rel);
1868
1869 assert!(
1871 result.contains("!{Rel}"),
1872 "Expected '!{{Rel}}' in output, got: {result}"
1873 );
1874
1875 assert_eq!(errors.0.len(), 1, "Expected exactly one error: {errors:?}");
1877
1878 match &errors.0[0] {
1880 FormatError::Format(plan_err) => {
1881 assert_eq!(plan_err.message, "Rel");
1882 assert_eq!(
1883 plan_err.error_type,
1884 crate::textify::foundation::FormatErrorType::Unimplemented
1885 );
1886 assert!(
1887 plan_err
1888 .lookup
1889 .as_deref()
1890 .unwrap_or("")
1891 .contains("Reference"),
1892 "Expected lookup to mention 'Reference', got: {:?}",
1893 plan_err.lookup
1894 );
1895 }
1896 other => panic!("Expected FormatError::Format, got: {other:?}"),
1897 }
1898 }
1899}