Skip to main content

substrait_explain/parser/
relations.rs

1use std::collections::HashMap;
2
3use pest::iterators::Pair;
4use prost::Message;
5use substrait::proto::aggregate_rel::Grouping;
6use substrait::proto::expression::literal::LiteralType;
7use substrait::proto::expression::{Literal, RexType, nested};
8use substrait::proto::extensions::AdvancedExtension;
9use substrait::proto::fetch_rel::{CountMode, OffsetMode};
10use substrait::proto::rel::RelType;
11use substrait::proto::rel_common::{Direct, Emit, EmitKind};
12use substrait::proto::sort_field::{SortDirection, SortKind};
13use substrait::proto::{
14    AggregateRel, CrossRel, Expression, FetchRel, FilterRel, JoinRel, NamedStruct, ProjectRel,
15    ReadRel, Rel, RelCommon, SetRel, SortField, SortRel, Type, aggregate_rel, join_rel, read_rel,
16    set_rel, r#type,
17};
18
19use super::{MessageParseError, ParsePair, Rule, RuleIter, ScopedParsePair, unwrap_single_pair};
20use crate::extensions::any::Any;
21use crate::extensions::registry::ExtensionError;
22use crate::extensions::{AddendumKind, ExtensionArgs, ExtensionRegistry, SimpleExtensions};
23use crate::parser::errors::{ParseContext, ParseError};
24use crate::parser::expressions::{FieldIndex, Name};
25
26/// Parsing context for relations that includes extensions, registry, and optional warning collection
27pub struct RelationParsingContext<'a> {
28    pub registry: &'a ExtensionRegistry,
29    pub line_no: i64,
30    pub line: &'a str,
31}
32
33impl<'a> RelationParsingContext<'a> {
34    /// Resolve extension detail using registry. Any failure is treated as a hard parse error.
35    pub fn resolve_extension_detail(
36        &self,
37        extension_name: &str,
38        extension_args: &ExtensionArgs,
39    ) -> Result<Option<Any>, ParseError> {
40        let detail = self
41            .registry
42            .parse_extension(extension_name, extension_args);
43
44        match detail {
45            Ok(any) => Ok(Some(any)),
46            Err(ExtensionError::NotFound { .. }) => Err(ParseError::UnregisteredExtension {
47                name: extension_name.to_string(),
48                context: ParseContext::new(self.line_no, self.line.to_string()),
49            }),
50            Err(err) => Err(ParseError::ExtensionDetail(
51                ParseContext::new(self.line_no, self.line.to_string()),
52                err,
53            )),
54        }
55    }
56
57    /// Resolve an addendum detail using the registry entry for its kind.
58    /// Any failure is treated as a hard parse error.
59    pub(crate) fn resolve_addendum_detail(
60        &self,
61        kind: AddendumKind,
62        name: &str,
63        args: &ExtensionArgs,
64    ) -> Result<Any, ParseError> {
65        let result = match kind {
66            AddendumKind::Enhancement => self.registry.parse_enhancement(name, args),
67            AddendumKind::Optimization => self.registry.parse_optimization(name, args),
68            AddendumKind::ExtensionTable => self.registry.parse_extension_table(name, args),
69        };
70        result.map_err(|err| match err {
71            ExtensionError::NotFound { .. } => ParseError::UnregisteredExtension {
72                name: name.to_string(),
73                context: ParseContext::new(self.line_no, self.line.to_string()),
74            },
75            err => ParseError::ExtensionDetail(
76                ParseContext::new(self.line_no, self.line.to_string()),
77                err,
78            ),
79        })
80    }
81}
82
83/// A trait for parsing relations with full context for tree building.
84pub trait RelationParsePair: Sized {
85    fn rule() -> Rule;
86    fn message() -> &'static str;
87
88    /// Parse the grammar pair into this relation type and its output field
89    /// count.
90    ///
91    /// Returns `(Self, usize)` where `usize` is the output field count —
92    /// computed during parsing when `input_field_count` is available.
93    fn parse_pair_with_context(
94        extensions: &SimpleExtensions,
95        pair: Pair<Rule>,
96        input_children: Vec<Rel>,
97        input_field_count: usize,
98    ) -> Result<(Self, usize), MessageParseError>;
99
100    /// Consume this parsed relation, apply the advanced extension, and produce
101    /// the final `Rel`.
102    fn into_rel(self, adv_ext: Option<AdvancedExtension>) -> Rel;
103}
104
105pub struct TableName(Vec<String>);
106
107impl ParsePair for TableName {
108    fn rule() -> Rule {
109        Rule::table_name
110    }
111
112    fn message() -> &'static str {
113        "TableName"
114    }
115
116    fn parse_pair(pair: Pair<Rule>) -> Self {
117        assert_eq!(pair.as_rule(), Self::rule());
118        let pairs = pair.into_inner();
119        let mut names = Vec::with_capacity(pairs.len());
120        let mut iter = RuleIter::from(pairs);
121        while let Some(name) = iter.parse_if_next::<Name>() {
122            names.push(name.0);
123        }
124        iter.done();
125        Self(names)
126    }
127}
128
129#[derive(Debug, Clone)]
130pub struct Column {
131    pub name: String,
132    pub typ: Type,
133}
134
135impl ScopedParsePair for Column {
136    fn rule() -> Rule {
137        Rule::named_column
138    }
139
140    fn message() -> &'static str {
141        "Column"
142    }
143
144    fn parse_pair(
145        extensions: &SimpleExtensions,
146        pair: Pair<Rule>,
147    ) -> Result<Self, MessageParseError> {
148        assert_eq!(pair.as_rule(), Self::rule());
149        let mut iter = RuleIter::from(pair.into_inner());
150        let name = iter.parse_next::<Name>().0;
151        let typ = iter.parse_next_scoped(extensions)?;
152        iter.done();
153        Ok(Self { name, typ })
154    }
155}
156
157pub(crate) struct NamedColumnList(pub(crate) Vec<Column>);
158
159impl ScopedParsePair for NamedColumnList {
160    fn rule() -> Rule {
161        Rule::named_column_list
162    }
163
164    fn message() -> &'static str {
165        "NamedColumnList"
166    }
167
168    fn parse_pair(
169        extensions: &SimpleExtensions,
170        pair: Pair<Rule>,
171    ) -> Result<Self, MessageParseError> {
172        assert_eq!(pair.as_rule(), Self::rule());
173        let mut columns = Vec::new();
174        for col in pair.into_inner() {
175            columns.push(Column::parse_pair(extensions, col)?);
176        }
177        Ok(Self(columns))
178    }
179}
180
181/// This is a utility function for extracting a single child from the list of
182/// children, to be used in the RelationParsePair trait. The RelationParsePair
183/// trait passes a Vec of children, because some relations have multiple
184/// children - but most accept exactly one child.
185pub(crate) fn expect_one_child(
186    message: &'static str,
187    pair: &Pair<Rule>,
188    mut input_children: Vec<Rel>,
189) -> Result<Box<Rel>, MessageParseError> {
190    match input_children.len() {
191        0 => Err(MessageParseError::invalid(
192            message,
193            pair.as_span(),
194            format!("{message} missing child"),
195        )),
196        1 => Ok(Box::new(input_children.pop().unwrap())),
197        n => Err(MessageParseError::invalid(
198            message,
199            pair.as_span(),
200            format!("{message} should have 1 input child, got {n}"),
201        )),
202    }
203}
204
205/// Parse a reference list Pair and return an EmitKind::Emit.
206/// Parse a reference list into field indices for emit mapping.
207fn parse_output_mapping(pair: Pair<Rule>) -> Vec<i32> {
208    assert_eq!(pair.as_rule(), Rule::reference_list);
209    pair.into_inner()
210        .map(|p| FieldIndex::parse_pair(p).0)
211        .collect()
212}
213
214/// Build an emit: `Direct` if the mapping is the identity `[0, 1, ..., N-1]`
215/// (where N = `direct_output_count`), otherwise `Emit` with the explicit mapping.
216fn make_emit(output_mapping: Vec<i32>, direct_output_count: usize) -> EmitKind {
217    let is_identity = output_mapping.len() == direct_output_count
218        && output_mapping
219            .iter()
220            .enumerate()
221            .all(|(i, &v)| v == i as i32);
222    if is_identity {
223        EmitKind::Direct(Direct {})
224    } else {
225        EmitKind::Emit(Emit { output_mapping })
226    }
227}
228
229/// Parse a reference list into an emit and output field count.
230fn parse_emit(reference_list: Pair<Rule>, direct_output_count: usize) -> (EmitKind, usize) {
231    let output_mapping = parse_output_mapping(reference_list);
232    let output_count = output_mapping.len();
233    let emit = make_emit(output_mapping, direct_output_count);
234    (emit, output_count)
235}
236
237fn parse_output(
238    extensions: &SimpleExtensions,
239    output: Pair<Rule>,
240) -> Result<(Vec<Column>, Option<RelCommon>, usize), MessageParseError> {
241    assert_eq!(output.as_rule(), Rule::output);
242    let output = unwrap_single_pair(output);
243    match output.as_rule() {
244        Rule::implicit_output => {
245            let mut iter = RuleIter::from(output.into_inner());
246            let columns = iter.parse_next_scoped::<NamedColumnList>(extensions)?.0;
247            iter.done();
248            let output_count = columns.len();
249            Ok((columns, None, output_count))
250        }
251        Rule::direct_output => {
252            let mut iter = RuleIter::from(output.into_inner());
253            let columns = iter.parse_next_scoped::<NamedColumnList>(extensions)?.0;
254            let emit_suffix = iter.pop(Rule::emit_suffix);
255            iter.done();
256
257            let direct_output_count = columns.len();
258            let (emit, output_count) = parse_emit_suffix(emit_suffix)
259                .unwrap_or((EmitKind::Direct(Direct {}), direct_output_count));
260            let common = Some(RelCommon {
261                emit_kind: Some(emit),
262                ..Default::default()
263            });
264            Ok((columns, common, output_count))
265        }
266        other => unreachable!("Unexpected rule in output: {other:?}"),
267    }
268}
269
270fn parse_emit_suffix(suffix: Pair<Rule>) -> Option<(EmitKind, usize)> {
271    assert_eq!(suffix.as_rule(), Rule::emit_suffix);
272    let reference_list = suffix.into_inner().next()?;
273    assert_eq!(reference_list.as_rule(), Rule::reference_list);
274    let output_mapping = parse_output_mapping(reference_list);
275    let output_count = output_mapping.len();
276    Some((EmitKind::Emit(Emit { output_mapping }), output_count))
277}
278
279/// Extracts named arguments from pest pairs with duplicate detection and completeness checking.
280///
281/// Usage: `extractor.pop("limit", Rule::fetch_value).0.pop("offset", Rule::fetch_value).0.done()`
282///
283/// The fluent API ensures all arguments are processed exactly once and none are forgotten.
284pub struct ParsedNamedArgs<'a> {
285    map: HashMap<&'a str, Pair<'a, Rule>>,
286}
287
288impl<'a> ParsedNamedArgs<'a> {
289    pub fn new(
290        pairs: pest::iterators::Pairs<'a, Rule>,
291        rule: Rule,
292    ) -> Result<Self, MessageParseError> {
293        let mut map = HashMap::new();
294        for pair in pairs {
295            assert_eq!(pair.as_rule(), rule);
296            let mut inner = pair.clone().into_inner();
297            let name_pair = inner.next().unwrap();
298            let value_pair = inner.next().unwrap();
299            assert_eq!(inner.next(), None);
300            let name = name_pair.as_str();
301            if map.contains_key(name) {
302                return Err(MessageParseError::invalid(
303                    "NamedArg",
304                    name_pair.as_span(),
305                    format!("Duplicate argument: {name}"),
306                ));
307            }
308            map.insert(name, value_pair);
309        }
310        Ok(Self { map })
311    }
312
313    // Returns the pair if it exists and matches the rule, otherwise None.
314    // Asserts that the rule must match the rule of the pair (and therefore
315    // panics in non-release-mode if not)
316    pub fn pop(mut self, name: &str, rule: Rule) -> (Self, Option<Pair<'a, Rule>>) {
317        let pair = self.map.remove(name).inspect(|pair| {
318            assert_eq!(pair.as_rule(), rule, "Rule mismatch for argument {name}");
319        });
320        (self, pair)
321    }
322
323    // Returns an error if there are any unused arguments.
324    pub fn done(self) -> Result<(), MessageParseError> {
325        if let Some((name, pair)) = self.map.iter().next() {
326            return Err(MessageParseError::invalid(
327                "NamedArgExtractor",
328                // No span available for all unused args; use default.
329                pair.as_span(),
330                format!("Unknown argument: {name}"),
331            ));
332        }
333        Ok(())
334    }
335}
336
337impl RelationParsePair for ReadRel {
338    fn rule() -> Rule {
339        Rule::read_relation
340    }
341
342    fn message() -> &'static str {
343        "ReadRel"
344    }
345
346    fn parse_pair_with_context(
347        extensions: &SimpleExtensions,
348        pair: Pair<Rule>,
349        input_children: Vec<Rel>,
350        input_field_count: usize,
351    ) -> Result<(Self, usize), MessageParseError> {
352        assert_eq!(pair.as_rule(), Self::rule());
353        // ReadRel is a leaf node - it should have no input children and 0 input fields
354        if !input_children.is_empty() {
355            return Err(MessageParseError::invalid(
356                Self::message(),
357                pair.as_span(),
358                "ReadRel should have no input children",
359            ));
360        }
361        if input_field_count != 0 {
362            return Err(MessageParseError::invalid(
363                "ReadRel",
364                pair.as_span(),
365                "ReadRel should have 0 input fields",
366            ));
367        }
368
369        let mut iter = RuleIter::from(pair.into_inner());
370        let table = iter.parse_next::<TableName>().0;
371        let output_pair = iter.pop(Rule::output);
372        iter.done();
373
374        let (columns, common, output_count) = parse_output(extensions, output_pair)?;
375
376        Ok((
377            ReadRel {
378                base_schema: Some(build_named_struct(columns)),
379                read_type: Some(read_rel::ReadType::NamedTable(read_rel::NamedTable {
380                    names: table,
381                    advanced_extension: None,
382                })),
383                common,
384                ..Default::default()
385            },
386            output_count,
387        ))
388    }
389
390    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
391        self.advanced_extension = adv_ext;
392        Rel {
393            rel_type: Some(RelType::Read(Box::new(self))),
394        }
395    }
396}
397
398/// Parsed `Read:Virtual[rows => columns]` relation. Needs a newtype because the
399/// proto type is `ReadRel` (same as `NamedTable`), but the grammar and handling
400/// are different.
401pub(crate) struct VirtualReadRel(ReadRel);
402
403impl RelationParsePair for VirtualReadRel {
404    fn rule() -> Rule {
405        Rule::virtual_read_relation
406    }
407
408    fn message() -> &'static str {
409        "VirtualReadRel"
410    }
411
412    fn parse_pair_with_context(
413        extensions: &SimpleExtensions,
414        pair: Pair<Rule>,
415        input_children: Vec<Rel>,
416        _input_field_count: usize,
417    ) -> Result<(Self, usize), MessageParseError> {
418        assert_eq!(pair.as_rule(), Self::rule());
419        if !input_children.is_empty() {
420            return Err(MessageParseError::invalid(
421                Self::message(),
422                pair.as_span(),
423                "Read:Virtual should have no input children",
424            ));
425        }
426
427        let mut iter = RuleIter::from(pair.into_inner());
428        let rows_pair = iter.pop(Rule::virtual_read_rows);
429        let filter = iter
430            .try_pop(Rule::virtual_read_filter)
431            .map(|pair| {
432                let expression_pair = unwrap_single_pair(pair);
433                Expression::parse_pair(extensions, expression_pair).map(Box::new)
434            })
435            .transpose()?;
436        let columns_pair = iter.pop(Rule::named_column_list);
437        iter.done();
438
439        let rows = parse_virtual_read_rows(extensions, rows_pair)?;
440        let columns = NamedColumnList::parse_pair(extensions, columns_pair)?.0;
441
442        // TODO: Validate that each row has the same number of expressions as
443        // columns. Currently no parser-side warning mechanism exists, and while
444        // this is an invalid plan, it is constructible as Substrait. Consider
445        // adding once a warning collection path is available.
446        let output_count = columns.len();
447        Ok((
448            VirtualReadRel(ReadRel {
449                base_schema: Some(build_named_struct(columns)),
450                read_type: Some(read_rel::ReadType::VirtualTable(read_rel::VirtualTable {
451                    expressions: rows,
452                    ..Default::default()
453                })),
454                filter,
455                ..Default::default()
456            }),
457            output_count,
458        ))
459    }
460
461    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
462        self.0.advanced_extension = adv_ext;
463        Rel {
464            rel_type: Some(RelType::Read(Box::new(self.0))),
465        }
466    }
467}
468
469/// Parsed `Read:Extension[columns]` relation. Needs a newtype because the
470/// proto type is `ReadRel` (same as `NamedTable` and `VirtualTable`), but the
471/// read detail is supplied by a required `+ Ext:` addendum.
472pub(crate) struct ExtensionReadRel(ReadRel);
473
474impl ExtensionReadRel {
475    pub(crate) fn parse_pair_with_detail(
476        extensions: &SimpleExtensions,
477        pair: Pair<Rule>,
478        input_children: Vec<Rel>,
479        input_field_count: usize,
480        detail: Any,
481        advanced_extension: Option<AdvancedExtension>,
482    ) -> Result<(Rel, usize), MessageParseError> {
483        assert_eq!(pair.as_rule(), Rule::extension_read_relation);
484        if !input_children.is_empty() {
485            return Err(MessageParseError::invalid(
486                "ExtensionReadRel",
487                pair.as_span(),
488                "Read:Extension should have no input children",
489            ));
490        }
491        if input_field_count != 0 {
492            return Err(MessageParseError::invalid(
493                "ExtensionReadRel",
494                pair.as_span(),
495                "Read:Extension should have 0 input fields",
496            ));
497        }
498
499        let mut iter = RuleIter::from(pair.into_inner());
500        let columns = iter.parse_next_scoped::<NamedColumnList>(extensions)?.0;
501        iter.done();
502
503        let output_count = columns.len();
504        let rel = ExtensionReadRel(ReadRel {
505            base_schema: Some(build_named_struct(columns)),
506            read_type: Some(read_rel::ReadType::ExtensionTable(
507                read_rel::ExtensionTable {
508                    detail: Some(detail.into()),
509                },
510            )),
511            advanced_extension,
512            ..Default::default()
513        })
514        .into_rel();
515
516        Ok((rel, output_count))
517    }
518
519    fn into_rel(self) -> Rel {
520        Rel {
521            rel_type: Some(RelType::Read(Box::new(self.0))),
522        }
523    }
524}
525
526/// Build a `NamedStruct` from parsed columns.
527pub(crate) fn build_named_struct(columns: Vec<Column>) -> NamedStruct {
528    let (names, types): (Vec<_>, Vec<_>) = columns.into_iter().map(|c| (c.name, c.typ)).unzip();
529    NamedStruct {
530        names,
531        r#struct: Some(r#type::Struct {
532            types,
533            type_variation_reference: 0,
534            nullability: r#type::Nullability::Required as i32,
535        }),
536    }
537}
538
539/// `Read:Virtual` rows: either `empty` or a list of row tuples.
540fn parse_virtual_read_rows(
541    extensions: &SimpleExtensions,
542    pair: Pair<Rule>,
543) -> Result<Vec<nested::Struct>, MessageParseError> {
544    assert_eq!(pair.as_rule(), Rule::virtual_read_rows);
545    let inner = unwrap_single_pair(pair);
546    match inner.as_rule() {
547        Rule::empty => Ok(vec![]),
548        Rule::virtual_row_list => inner
549            .into_inner()
550            .map(|row| parse_virtual_row(extensions, row))
551            .collect(),
552        _ => unreachable!(
553            "Unexpected rule in virtual_read_rows: {:?}",
554            inner.as_rule()
555        ),
556    }
557}
558
559/// Parse a single `virtual_row` (`(expr, expr, ...)` or `()`) into a `nested::Struct`.
560fn parse_virtual_row(
561    extensions: &SimpleExtensions,
562    pair: Pair<Rule>,
563) -> Result<nested::Struct, MessageParseError> {
564    assert_eq!(pair.as_rule(), Rule::virtual_row);
565    let fields = match pair.into_inner().next() {
566        Some(expression_list) => {
567            assert_eq!(expression_list.as_rule(), Rule::expression_list);
568            parse_expression_list(extensions, expression_list)?
569        }
570        // Empty virtual row. An unusual but valid case.
571        None => vec![],
572    };
573    Ok(nested::Struct { fields })
574}
575
576impl RelationParsePair for FilterRel {
577    fn rule() -> Rule {
578        Rule::filter_relation
579    }
580
581    fn message() -> &'static str {
582        "FilterRel"
583    }
584
585    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
586        self.advanced_extension = adv_ext;
587        Rel {
588            rel_type: Some(RelType::Filter(Box::new(self))),
589        }
590    }
591
592    fn parse_pair_with_context(
593        extensions: &SimpleExtensions,
594        pair: Pair<Rule>,
595        input_children: Vec<Rel>,
596        input_field_count: usize,
597    ) -> Result<(Self, usize), MessageParseError> {
598        assert_eq!(pair.as_rule(), Self::rule());
599        let input = expect_one_child(Self::message(), &pair, input_children)?;
600        let mut iter = RuleIter::from(pair.into_inner());
601        let condition = iter.parse_next_scoped::<Expression>(extensions)?;
602        let references_pair = iter.pop(Rule::reference_list);
603        iter.done();
604
605        let (emit, output_count) = parse_emit(references_pair, input_field_count);
606        let common = RelCommon {
607            emit_kind: Some(emit),
608            ..Default::default()
609        };
610
611        Ok((
612            FilterRel {
613                input: Some(input),
614                condition: Some(Box::new(condition)),
615                common: Some(common),
616                advanced_extension: None,
617            },
618            output_count,
619        ))
620    }
621}
622
623impl RelationParsePair for ProjectRel {
624    fn rule() -> Rule {
625        Rule::project_relation
626    }
627
628    fn message() -> &'static str {
629        "ProjectRel"
630    }
631
632    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
633        self.advanced_extension = adv_ext;
634        Rel {
635            rel_type: Some(RelType::Project(Box::new(self))),
636        }
637    }
638
639    fn parse_pair_with_context(
640        extensions: &SimpleExtensions,
641        pair: Pair<Rule>,
642        input_children: Vec<Rel>,
643        input_field_count: usize,
644    ) -> Result<(Self, usize), MessageParseError> {
645        assert_eq!(pair.as_rule(), Self::rule());
646        let input = expect_one_child(Self::message(), &pair, input_children)?;
647
648        let arguments_pair = unwrap_single_pair(pair);
649
650        let mut expressions = Vec::new();
651        let mut output_mapping = Vec::new();
652
653        for arg in arguments_pair.into_inner() {
654            let inner_arg = unwrap_single_pair(arg);
655            match inner_arg.as_rule() {
656                Rule::reference => {
657                    let field_index = FieldIndex::parse_pair(inner_arg);
658                    output_mapping.push(field_index.0);
659                }
660                Rule::expression => {
661                    let expr = Expression::parse_pair(extensions, inner_arg)?;
662                    expressions.push(expr);
663                    // Index into the combined schema: [input fields][computed expressions].
664                    output_mapping.push(input_field_count as i32 + (expressions.len() as i32 - 1));
665                }
666                _ => panic!("Unexpected inner argument rule: {:?}", inner_arg.as_rule()),
667            }
668        }
669
670        let output_count = output_mapping.len();
671        let direct_count = input_field_count + expressions.len();
672        let emit = make_emit(output_mapping, direct_count);
673        let common = RelCommon {
674            emit_kind: Some(emit),
675            ..Default::default()
676        };
677
678        Ok((
679            ProjectRel {
680                input: Some(input),
681                expressions,
682                common: Some(common),
683                advanced_extension: None,
684            },
685            output_count,
686        ))
687    }
688}
689
690impl RelationParsePair for AggregateRel {
691    fn rule() -> Rule {
692        Rule::aggregate_relation
693    }
694
695    fn message() -> &'static str {
696        "AggregateRel"
697    }
698
699    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
700        self.advanced_extension = adv_ext;
701        Rel {
702            rel_type: Some(RelType::Aggregate(Box::new(self))),
703        }
704    }
705
706    fn parse_pair_with_context(
707        extensions: &SimpleExtensions,
708        pair: Pair<Rule>,
709        input_children: Vec<Rel>,
710        // Aggregate defines its own output schema (grouping keys + measures),
711        // so the input field count isn't needed for emit construction.
712        _input_field_count: usize,
713    ) -> Result<(Self, usize), MessageParseError> {
714        assert_eq!(pair.as_rule(), Self::rule());
715        let input = expect_one_child(Self::message(), &pair, input_children)?;
716        let mut iter = RuleIter::from(pair.into_inner());
717        let group_by_pair = iter.pop(Rule::aggregate_group_by);
718        let output_pair = iter.pop(Rule::aggregate_output);
719        iter.done();
720
721        let inner = group_by_pair
722            .into_inner()
723            .next()
724            .expect("aggregate_group_by must have one inner item");
725
726        let grouping_sets = parse_grouping_sets(extensions, inner)?;
727        let (groupings, grouping_expressions) = build_grouping_fields(&grouping_sets);
728
729        let (measures, output_mapping) =
730            parse_aggregate_output(extensions, output_pair, &grouping_expressions)?;
731
732        let output_count = output_mapping.len();
733        let direct_count = grouping_expressions.len() + measures.len();
734        let emit = make_emit(output_mapping, direct_count);
735        let common = RelCommon {
736            emit_kind: Some(emit),
737            ..Default::default()
738        };
739
740        Ok((
741            AggregateRel {
742                input: Some(input),
743                grouping_expressions,
744                groupings,
745                measures,
746                common: Some(common),
747                advanced_extension: None,
748            },
749            output_count,
750        ))
751    }
752}
753
754/// Encodes an expression to bytes for use as a structural-equality map key.
755/// TODO: use a better key here than encoding to bytes. Ideally, substrait-rs
756/// would support `PartialEq` and `Hash`, but as there isn't an easy way to do
757/// that now, we'll skip.
758fn expression_key(expression: &Expression) -> Vec<u8> {
759    expression.encode_to_vec()
760}
761
762/// Parses the output section of an aggregate (everything after `=>`).
763///
764/// For example, in `Aggregate[($0, $1), _ => sum($2), $0, count($2)]`,
765/// this parses `sum($2), $0, count($2)`.
766fn parse_aggregate_output(
767    extensions: &SimpleExtensions,
768    output_pair: Pair<'_, Rule>,
769    grouping_expressions: &[Expression],
770) -> Result<(Vec<aggregate_rel::Measure>, Vec<i32>), MessageParseError> {
771    assert_eq!(output_pair.as_rule(), Rule::aggregate_output);
772
773    // every output item is either:
774    // - an expression which must already be one of `grouping_expressions`,
775    // - a new function call, which we assume must be an aggregate measure.
776    //
777    // While it would probably be best to check if a function call was an
778    // aggregate function, the substrait-explain text does not distinguish
779    // between aggregate measures and grouping expressions, and the
780    // `SimpleExtensions` do not have that information either (neither here in
781    // the Registry nor in the actual Protobuf `ExtensionFunction` definition),
782    // so we can only check whether it's a previous expression.
783    let grouping_positions: HashMap<Vec<u8>, usize> = grouping_expressions
784        .iter()
785        .enumerate()
786        .map(|(index, expression)| (expression_key(expression), index))
787        .collect();
788
789    let mut measures = Vec::new();
790    let mut output_mapping = Vec::new();
791
792    for output_item in output_pair.into_inner() {
793        assert_eq!(output_item.as_rule(), Rule::expression);
794        let span = output_item.as_span();
795        let inner_item = unwrap_single_pair(output_item.clone());
796
797        if inner_item.as_rule() == Rule::function_call {
798            let expression = Expression::parse_pair(extensions, output_item)?;
799            if let Some(&index) = grouping_positions.get(&expression_key(&expression)) {
800                output_mapping.push(index as i32);
801                continue;
802            }
803
804            let measure = aggregate_rel::Measure::parse_pair(extensions, inner_item)?;
805            output_mapping.push(grouping_expressions.len() as i32 + measures.len() as i32);
806            measures.push(measure);
807            continue;
808        }
809
810        let expression = Expression::parse_pair(extensions, output_item)?;
811        match grouping_positions.get(&expression_key(&expression)) {
812            Some(&index) => output_mapping.push(index as i32),
813            None => {
814                return Err(MessageParseError::invalid(
815                    "AggregateRel",
816                    span,
817                    "output expression is not an aggregate measure and does not match any grouping expression",
818                ));
819            }
820        }
821    }
822
823    Ok((measures, output_mapping))
824}
825
826/// Parses the grouping section of an aggregate (everything before `=>`).
827///
828/// For example, in `Aggregate[($0, $1), _ => sum($2), $0, count($2)]`,
829/// this parses `($0, $1), _`.
830///
831/// Each inner Vec is one grouping set; an empty vec represents no grouping (global aggregate).
832///
833/// Grammar: `aggregate_group_by = { grouping_set_list | expression_list }`
834fn parse_grouping_sets(
835    extensions: &SimpleExtensions,
836    inner: Pair<'_, Rule>,
837) -> Result<Vec<Vec<Expression>>, MessageParseError> {
838    assert!(
839        matches!(
840            inner.as_rule(),
841            Rule::expression_list | Rule::grouping_set_list
842        ),
843        "Expected expression_list or grouping_set_list, got {:?}",
844        inner.as_rule()
845    );
846    match inner.as_rule() {
847        Rule::expression_list => Ok(vec![parse_expression_list(extensions, inner)?]),
848        Rule::grouping_set_list => inner
849            .into_inner()
850            .map(|pair| parse_grouping_set(extensions, pair))
851            .collect(),
852        _ => unreachable!(
853            "Unexpected rule in aggregate_group_by: {:?}",
854            inner.as_rule()
855        ),
856    }
857}
858
859/// Parses a single grouping set, e.g. `($0, $1)` or `_`.
860///
861/// Grammar: `grouping_set = { ("(" ~ expression_list ~ ")") | empty }`
862fn parse_grouping_set(
863    extensions: &SimpleExtensions,
864    pair: Pair<'_, Rule>,
865) -> Result<Vec<Expression>, MessageParseError> {
866    assert_eq!(pair.as_rule(), Rule::grouping_set);
867    let inner = pair
868        .into_inner()
869        .next()
870        .expect("grouping_set must have one inner item");
871    match inner.as_rule() {
872        Rule::empty => Ok(vec![]),
873        Rule::expression_list => parse_expression_list(extensions, inner),
874        _ => unreachable!("Unexpected item in grouping_set: {:?}", inner.as_rule()),
875    }
876}
877
878/// Grammar: `expression_list = { expression ~ ("," ~ expression)* }`
879pub(crate) fn parse_expression_list(
880    extensions: &SimpleExtensions,
881    pair: Pair<'_, Rule>,
882) -> Result<Vec<Expression>, MessageParseError> {
883    pair.into_inner()
884        .map(|expr_pair| Expression::parse_pair(extensions, expr_pair))
885        .collect()
886}
887
888/// Deduplicates expressions across all sets and produces the AggregateRel's
889/// protobuf fields: a flat deduplicated expression list and per-set Grouping
890/// messages with index references into that list.
891fn build_grouping_fields(expression_sets: &[Vec<Expression>]) -> (Vec<Grouping>, Vec<Expression>) {
892    let mut expressions: Vec<Expression> = Vec::new();
893    let mut seen: HashMap<Vec<u8>, u32> = HashMap::new();
894
895    let groupings = expression_sets
896        .iter()
897        .map(|set| {
898            let expression_references = set
899                .iter()
900                .map(|exp| {
901                    let key = expression_key(exp);
902                    let next_idx = expressions.len() as u32;
903                    *seen.entry(key).or_insert_with(|| {
904                        expressions.push(exp.clone());
905                        next_idx
906                    })
907                })
908                .collect();
909            Grouping {
910                expression_references,
911                #[allow(deprecated)]
912                grouping_expressions: vec![],
913            }
914        })
915        .collect();
916
917    (groupings, expressions)
918}
919
920impl ScopedParsePair for SortField {
921    fn rule() -> Rule {
922        Rule::sort_field
923    }
924
925    fn message() -> &'static str {
926        "SortField"
927    }
928
929    fn parse_pair(
930        _extensions: &SimpleExtensions,
931        pair: Pair<Rule>,
932    ) -> Result<Self, MessageParseError> {
933        assert_eq!(pair.as_rule(), Self::rule());
934        let mut iter = RuleIter::from(pair.into_inner());
935        let reference_pair = iter.pop(Rule::reference);
936        let field_index = FieldIndex::parse_pair(reference_pair);
937        let direction_pair = iter.pop(Rule::sort_direction);
938        // Strip the '&' prefix from enum syntax (e.g., "&AscNullsFirst" ->
939        // "AscNullsFirst") The grammar includes '&' to distinguish enums from
940        // identifiers, but the enum variant names don't include it
941        let direction = match direction_pair.as_str().trim_start_matches('&') {
942            "AscNullsFirst" => SortDirection::AscNullsFirst,
943            "AscNullsLast" => SortDirection::AscNullsLast,
944            "DescNullsFirst" => SortDirection::DescNullsFirst,
945            "DescNullsLast" => SortDirection::DescNullsLast,
946            other => {
947                return Err(MessageParseError::invalid(
948                    "SortDirection",
949                    direction_pair.as_span(),
950                    format!("Unknown sort direction: {other}"),
951                ));
952            }
953        };
954        iter.done();
955        Ok(SortField {
956            expr: Some(Expression {
957                rex_type: Some(substrait::proto::expression::RexType::Selection(Box::new(
958                    field_index.to_field_reference(),
959                ))),
960            }),
961            // TODO: Add support for SortKind::ComparisonFunctionReference
962            sort_kind: Some(SortKind::Direction(direction as i32)),
963        })
964    }
965}
966
967impl RelationParsePair for SortRel {
968    fn rule() -> Rule {
969        Rule::sort_relation
970    }
971
972    fn message() -> &'static str {
973        "SortRel"
974    }
975
976    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
977        self.advanced_extension = adv_ext;
978        Rel {
979            rel_type: Some(RelType::Sort(Box::new(self))),
980        }
981    }
982
983    fn parse_pair_with_context(
984        extensions: &SimpleExtensions,
985        pair: Pair<Rule>,
986        input_children: Vec<Rel>,
987        input_field_count: usize,
988    ) -> Result<(Self, usize), MessageParseError> {
989        assert_eq!(pair.as_rule(), Self::rule());
990        let input = expect_one_child(Self::message(), &pair, input_children)?;
991        let mut iter = RuleIter::from(pair.into_inner());
992        let sort_field_list_pair = iter.pop(Rule::sort_field_list);
993        let references_pair = iter.pop(Rule::reference_list);
994        let mut sorts = Vec::new();
995        for sort_field_pair in sort_field_list_pair.into_inner() {
996            let sort_field = SortField::parse_pair(extensions, sort_field_pair)?;
997            sorts.push(sort_field);
998        }
999        let (emit, output_count) = parse_emit(references_pair, input_field_count);
1000        let common = RelCommon {
1001            emit_kind: Some(emit),
1002            ..Default::default()
1003        };
1004        iter.done();
1005        Ok((
1006            SortRel {
1007                input: Some(input),
1008                sorts,
1009                common: Some(common),
1010                advanced_extension: None,
1011            },
1012            output_count,
1013        ))
1014    }
1015}
1016
1017impl ScopedParsePair for CountMode {
1018    fn rule() -> Rule {
1019        Rule::fetch_value
1020    }
1021    fn message() -> &'static str {
1022        "CountMode"
1023    }
1024    fn parse_pair(
1025        extensions: &SimpleExtensions,
1026        pair: Pair<Rule>,
1027    ) -> Result<Self, MessageParseError> {
1028        assert_eq!(pair.as_rule(), Self::rule());
1029        let mut arg_inner = RuleIter::from(pair.into_inner());
1030        let value_pair = if let Some(int_pair) = arg_inner.try_pop(Rule::integer) {
1031            int_pair
1032        } else {
1033            arg_inner.pop(Rule::expression)
1034        };
1035        match value_pair.as_rule() {
1036            Rule::integer => {
1037                let value = value_pair.as_str().parse::<i64>().map_err(|e| {
1038                    MessageParseError::invalid(
1039                        Self::message(),
1040                        value_pair.as_span(),
1041                        format!("Invalid integer: {e}"),
1042                    )
1043                })?;
1044                if value < 0 {
1045                    return Err(MessageParseError::invalid(
1046                        Self::message(),
1047                        value_pair.as_span(),
1048                        format!("Fetch limit must be non-negative, got: {value}"),
1049                    ));
1050                }
1051                Ok(CountMode::CountExpr(i64_literal_expr(value)))
1052            }
1053            Rule::expression => {
1054                let expr = Expression::parse_pair(extensions, value_pair)?;
1055                Ok(CountMode::CountExpr(Box::new(expr)))
1056            }
1057            _ => Err(MessageParseError::invalid(
1058                Self::message(),
1059                value_pair.as_span(),
1060                format!("Unexpected rule for CountMode: {:?}", value_pair.as_rule()),
1061            )),
1062        }
1063    }
1064}
1065
1066fn i64_literal_expr(value: i64) -> Box<Expression> {
1067    Box::new(Expression {
1068        rex_type: Some(RexType::Literal(Literal {
1069            nullable: false,
1070            type_variation_reference: 0,
1071            literal_type: Some(LiteralType::I64(value)),
1072        })),
1073    })
1074}
1075
1076impl ScopedParsePair for OffsetMode {
1077    fn rule() -> Rule {
1078        Rule::fetch_value
1079    }
1080    fn message() -> &'static str {
1081        "OffsetMode"
1082    }
1083    fn parse_pair(
1084        extensions: &SimpleExtensions,
1085        pair: Pair<Rule>,
1086    ) -> Result<Self, MessageParseError> {
1087        assert_eq!(pair.as_rule(), Self::rule());
1088        let mut arg_inner = RuleIter::from(pair.into_inner());
1089        let value_pair = if let Some(int_pair) = arg_inner.try_pop(Rule::integer) {
1090            int_pair
1091        } else {
1092            arg_inner.pop(Rule::expression)
1093        };
1094        match value_pair.as_rule() {
1095            Rule::integer => {
1096                let value = value_pair.as_str().parse::<i64>().map_err(|e| {
1097                    MessageParseError::invalid(
1098                        Self::message(),
1099                        value_pair.as_span(),
1100                        format!("Invalid integer: {e}"),
1101                    )
1102                })?;
1103                if value < 0 {
1104                    return Err(MessageParseError::invalid(
1105                        Self::message(),
1106                        value_pair.as_span(),
1107                        format!("Fetch offset must be non-negative, got: {value}"),
1108                    ));
1109                }
1110                Ok(OffsetMode::OffsetExpr(i64_literal_expr(value)))
1111            }
1112            Rule::expression => {
1113                let expr = Expression::parse_pair(extensions, value_pair)?;
1114                Ok(OffsetMode::OffsetExpr(Box::new(expr)))
1115            }
1116            _ => Err(MessageParseError::invalid(
1117                Self::message(),
1118                value_pair.as_span(),
1119                format!("Unexpected rule for OffsetMode: {:?}", value_pair.as_rule()),
1120            )),
1121        }
1122    }
1123}
1124
1125impl RelationParsePair for FetchRel {
1126    fn rule() -> Rule {
1127        Rule::fetch_relation
1128    }
1129
1130    fn message() -> &'static str {
1131        "FetchRel"
1132    }
1133
1134    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
1135        self.advanced_extension = adv_ext;
1136        Rel {
1137            rel_type: Some(RelType::Fetch(Box::new(self))),
1138        }
1139    }
1140
1141    fn parse_pair_with_context(
1142        extensions: &SimpleExtensions,
1143        pair: Pair<Rule>,
1144        input_children: Vec<Rel>,
1145        input_field_count: usize,
1146    ) -> Result<(Self, usize), MessageParseError> {
1147        assert_eq!(pair.as_rule(), Self::rule());
1148        let input = expect_one_child(Self::message(), &pair, input_children)?;
1149        let mut iter = RuleIter::from(pair.into_inner());
1150
1151        // Extract all pairs before any validation: RuleIter's Drop panics on
1152        // incomplete consumption, so we must exhaust the iterator before any
1153        // early return. Validation runs after iter.done() below.
1154        let (limit_pair, offset_pair) = match iter.try_pop(Rule::fetch_named_arg_list) {
1155            None => {
1156                iter.pop(Rule::empty);
1157                (None, None)
1158            }
1159            Some(fetch_args_pair) => {
1160                let extractor =
1161                    ParsedNamedArgs::new(fetch_args_pair.into_inner(), Rule::fetch_named_arg)?;
1162                let (extractor, limit_pair) = extractor.pop("limit", Rule::fetch_value);
1163                let (extractor, offset_pair) = extractor.pop("offset", Rule::fetch_value);
1164                extractor.done()?;
1165                (limit_pair, offset_pair)
1166            }
1167        };
1168
1169        let references_pair = iter.pop(Rule::reference_list);
1170        let (emit, output_count) = parse_emit(references_pair, input_field_count);
1171        let common = RelCommon {
1172            emit_kind: Some(emit),
1173            ..Default::default()
1174        };
1175        iter.done();
1176
1177        let count_mode = limit_pair
1178            .map(|pair| CountMode::parse_pair(extensions, pair))
1179            .transpose()?;
1180        let offset_mode = offset_pair
1181            .map(|pair| OffsetMode::parse_pair(extensions, pair))
1182            .transpose()?;
1183        Ok((
1184            FetchRel {
1185                input: Some(input),
1186                common: Some(common),
1187                advanced_extension: None,
1188                offset_mode,
1189                count_mode,
1190            },
1191            output_count,
1192        ))
1193    }
1194}
1195
1196impl ParsePair for join_rel::JoinType {
1197    fn rule() -> Rule {
1198        Rule::join_type
1199    }
1200
1201    fn message() -> &'static str {
1202        "JoinType"
1203    }
1204
1205    fn parse_pair(pair: Pair<Rule>) -> Self {
1206        assert_eq!(pair.as_rule(), Self::rule());
1207        let join_type_str = pair.as_str().trim_start_matches('&');
1208        match join_type_str {
1209            "Inner" => join_rel::JoinType::Inner,
1210            "Left" => join_rel::JoinType::Left,
1211            "Right" => join_rel::JoinType::Right,
1212            "Outer" => join_rel::JoinType::Outer,
1213            "LeftSemi" => join_rel::JoinType::LeftSemi,
1214            "RightSemi" => join_rel::JoinType::RightSemi,
1215            "LeftAnti" => join_rel::JoinType::LeftAnti,
1216            "RightAnti" => join_rel::JoinType::RightAnti,
1217            "LeftSingle" => join_rel::JoinType::LeftSingle,
1218            "RightSingle" => join_rel::JoinType::RightSingle,
1219            "LeftMark" => join_rel::JoinType::LeftMark,
1220            "RightMark" => join_rel::JoinType::RightMark,
1221            _ => panic!("Unknown join type: {join_type_str} (this should be caught by grammar)"),
1222        }
1223    }
1224}
1225
1226impl RelationParsePair for JoinRel {
1227    fn rule() -> Rule {
1228        Rule::join_relation
1229    }
1230
1231    fn message() -> &'static str {
1232        "JoinRel"
1233    }
1234
1235    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
1236        self.advanced_extension = adv_ext;
1237        Rel {
1238            rel_type: Some(RelType::Join(Box::new(self))),
1239        }
1240    }
1241
1242    fn parse_pair_with_context(
1243        extensions: &SimpleExtensions,
1244        pair: Pair<Rule>,
1245        input_children: Vec<Rel>,
1246        input_field_count: usize,
1247    ) -> Result<(Self, usize), MessageParseError> {
1248        assert_eq!(pair.as_rule(), Self::rule());
1249
1250        if input_children.len() != 2 {
1251            return Err(MessageParseError::invalid(
1252                Self::message(),
1253                pair.as_span(),
1254                format!(
1255                    "JoinRel should have exactly 2 input children, got {}",
1256                    input_children.len()
1257                ),
1258            ));
1259        }
1260
1261        let mut children_iter = input_children.into_iter();
1262        let left = Box::new(children_iter.next().unwrap());
1263        let right = Box::new(children_iter.next().unwrap());
1264
1265        let mut iter = RuleIter::from(pair.into_inner());
1266        let join_type = iter.parse_next::<join_rel::JoinType>();
1267        let condition = iter.parse_next_scoped::<Expression>(extensions)?;
1268        let post_join_filter = iter
1269            .try_pop(Rule::join_post_join_filter)
1270            .map(|pair| {
1271                let expression_pair = unwrap_single_pair(pair);
1272                Expression::parse_pair(extensions, expression_pair).map(Box::new)
1273            })
1274            .transpose()?;
1275        let references_pair = iter.pop(Rule::reference_list);
1276        iter.done();
1277
1278        // TODO: For semi/anti joins, the direct output width differs from
1279        // left+right — `input_field_count` would misclassify the emit as Direct.
1280        // Revisit when those join types are supported.
1281        let (emit, output_count) = parse_emit(references_pair, input_field_count);
1282        let common = RelCommon {
1283            emit_kind: Some(emit),
1284            ..Default::default()
1285        };
1286
1287        Ok((
1288            JoinRel {
1289                common: Some(common),
1290                left: Some(left),
1291                right: Some(right),
1292                expression: Some(Box::new(condition)),
1293                post_join_filter,
1294                r#type: join_type as i32,
1295                advanced_extension: None,
1296            },
1297            output_count,
1298        ))
1299    }
1300}
1301
1302impl RelationParsePair for CrossRel {
1303    fn rule() -> Rule {
1304        Rule::cross_relation
1305    }
1306
1307    fn message() -> &'static str {
1308        "CrossRel"
1309    }
1310
1311    fn into_rel(mut self, adv_ext: Option<AdvancedExtension>) -> Rel {
1312        self.advanced_extension = adv_ext;
1313        Rel {
1314            rel_type: Some(RelType::Cross(Box::new(self))),
1315        }
1316    }
1317
1318    fn parse_pair_with_context(
1319        _extensions: &SimpleExtensions,
1320        pair: Pair<Rule>,
1321        input_children: Vec<Rel>,
1322        input_field_count: usize,
1323    ) -> Result<(Self, usize), MessageParseError> {
1324        assert_eq!(pair.as_rule(), Self::rule());
1325
1326        if input_children.len() != 2 {
1327            return Err(MessageParseError::invalid(
1328                Self::message(),
1329                pair.as_span(),
1330                format!(
1331                    "CrossRel should have exactly 2 input children, got {}",
1332                    input_children.len()
1333                ),
1334            ));
1335        }
1336
1337        let mut children_iter = input_children.into_iter();
1338        let left = Box::new(children_iter.next().unwrap());
1339        let right = Box::new(children_iter.next().unwrap());
1340
1341        let mut iter = RuleIter::from(pair.into_inner());
1342        let reference_list_pair = iter.pop(Rule::reference_list);
1343        iter.done();
1344
1345        let (emit, output_count) = parse_emit(reference_list_pair, input_field_count);
1346        let common = RelCommon {
1347            emit_kind: Some(emit),
1348            ..Default::default()
1349        };
1350
1351        Ok((
1352            CrossRel {
1353                common: Some(common),
1354                left: Some(left),
1355                right: Some(right),
1356                advanced_extension: None,
1357            },
1358            output_count,
1359        ))
1360    }
1361}
1362
1363impl ParsePair for set_rel::SetOp {
1364    fn rule() -> Rule {
1365        Rule::set_op
1366    }
1367
1368    fn message() -> &'static str {
1369        "SetOp"
1370    }
1371
1372    fn parse_pair(pair: Pair<Rule>) -> Self {
1373        assert_eq!(pair.as_rule(), Self::rule());
1374        let set_op_str = pair.as_str().trim_start_matches('&');
1375        match set_op_str {
1376            "MinusPrimary" => set_rel::SetOp::MinusPrimary,
1377            "MinusPrimaryAll" => set_rel::SetOp::MinusPrimaryAll,
1378            "MinusMultiset" => set_rel::SetOp::MinusMultiset,
1379            "IntersectionPrimary" => set_rel::SetOp::IntersectionPrimary,
1380            "IntersectionMultiset" => set_rel::SetOp::IntersectionMultiset,
1381            "IntersectionMultisetAll" => set_rel::SetOp::IntersectionMultisetAll,
1382            "UnionDistinct" => set_rel::SetOp::UnionDistinct,
1383            "UnionAll" => set_rel::SetOp::UnionAll,
1384            _ => panic!("Unknown set op: {set_op_str} (this should be caught by grammar)"),
1385        }
1386    }
1387}
1388
1389/// Parse a `set_relation` pair given the real per-child output widths (not
1390/// just their sum), so mismatched input schemas are always caught
1391pub(crate) fn parse_set_relation_pair(
1392    pair: Pair<Rule>,
1393    input_children: Vec<Rel>,
1394    child_field_counts: &[usize],
1395    advanced_extension: Option<AdvancedExtension>,
1396) -> Result<(Rel, usize), MessageParseError> {
1397    assert_eq!(pair.as_rule(), Rule::set_relation);
1398
1399    if input_children.len() < 2 {
1400        return Err(MessageParseError::invalid(
1401            "SetRel",
1402            pair.as_span(),
1403            format!(
1404                "SetRel should have at least 2 input children, got {}",
1405                input_children.len()
1406            ),
1407        ));
1408    }
1409
1410    // All inputs must share the same output width (Set is a pass-through
1411    // over a common schema, not a concatenation like Join).
1412    let child_width = child_field_counts[0];
1413    if child_field_counts.iter().any(|&w| w != child_width) {
1414        return Err(MessageParseError::invalid(
1415            "SetRel",
1416            pair.as_span(),
1417            format!(
1418                "SetRel inputs must all have the same number of columns, got widths {child_field_counts:?}"
1419            ),
1420        ));
1421    }
1422
1423    let mut iter = RuleIter::from(pair.into_inner());
1424    let op = iter.parse_next::<set_rel::SetOp>();
1425    let reference_list_pair = iter.pop(Rule::reference_list);
1426    iter.done();
1427
1428    let (emit, output_count) = parse_emit(reference_list_pair, child_width);
1429    let common = RelCommon {
1430        emit_kind: Some(emit),
1431        ..Default::default()
1432    };
1433
1434    Ok((
1435        Rel {
1436            rel_type: Some(RelType::Set(SetRel {
1437                common: Some(common),
1438                inputs: input_children,
1439                op: op as i32,
1440                advanced_extension,
1441            })),
1442        },
1443        output_count,
1444    ))
1445}
1446
1447#[cfg(test)]
1448mod tests {
1449    use pest::Parser;
1450
1451    use super::*;
1452    use crate::fixtures::TestContext;
1453    use crate::parser::{ExpressionParser, Rule};
1454
1455    #[test]
1456    fn test_parse_relation() {
1457        // Removed: test_parse_relation for old Relation struct
1458    }
1459
1460    #[test]
1461    fn test_parse_read_relation() {
1462        let extensions = SimpleExtensions::default();
1463        let read = ReadRel::parse_pair_with_context(
1464            &extensions,
1465            parse_exact(Rule::read_relation, "Read[ab.cd.ef => a:i32, b:string?]"),
1466            vec![],
1467            0,
1468        )
1469        .unwrap()
1470        .0;
1471        let names = match &read.read_type {
1472            Some(read_rel::ReadType::NamedTable(table)) => &table.names,
1473            _ => panic!("Expected NamedTable"),
1474        };
1475        assert_eq!(names, &["ab", "cd", "ef"]);
1476        let columns = &read
1477            .base_schema
1478            .as_ref()
1479            .unwrap()
1480            .r#struct
1481            .as_ref()
1482            .unwrap()
1483            .types;
1484        assert_eq!(columns.len(), 2);
1485        assert!(read.common.is_none());
1486    }
1487
1488    #[test]
1489    fn test_parse_read_relation_explicit_emit() {
1490        let extensions = SimpleExtensions::default();
1491        let read = ReadRel::parse_pair_with_context(
1492            &extensions,
1493            parse_exact(
1494                Rule::read_relation,
1495                "Read[ab.cd.ef +> a:i32, b:string?, c:i64 |> $2, $0]",
1496            ),
1497            vec![],
1498            0,
1499        )
1500        .unwrap();
1501        let (read, output_count) = read;
1502        assert_eq!(output_count, 2);
1503        let emit_kind = read.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
1504        match emit_kind {
1505            EmitKind::Emit(emit) => assert_eq!(emit.output_mapping, vec![2, 0]),
1506            other => panic!("Expected EmitKind::Emit, got {other:?}"),
1507        }
1508    }
1509
1510    #[test]
1511    fn test_parse_read_relation_explicit_identity_emit_not_collapsed() {
1512        // An explicit `|>` with an identity mapping must stay `Emit`, not
1513        // collapse to `Direct` - that's the whole point of `|>`.
1514        let extensions = SimpleExtensions::default();
1515        let read = ReadRel::parse_pair_with_context(
1516            &extensions,
1517            parse_exact(
1518                Rule::read_relation,
1519                "Read[ab.cd.ef +> a:i32, b:string? |> $0, $1]",
1520            ),
1521            vec![],
1522            0,
1523        )
1524        .unwrap()
1525        .0;
1526        let emit_kind = read.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
1527        assert!(
1528            matches!(emit_kind, EmitKind::Emit(_)),
1529            "Expected explicit Emit to survive even with an identity mapping, got {emit_kind:?}"
1530        );
1531    }
1532
1533    #[test]
1534    fn test_parse_read_relation_explicit_direct() {
1535        let extensions = SimpleExtensions::default();
1536        let (read, output_count) = ReadRel::parse_pair_with_context(
1537            &extensions,
1538            parse_exact(Rule::read_relation, "Read[ab.cd.ef +> a:i32, b:string?]"),
1539            vec![],
1540            0,
1541        )
1542        .unwrap();
1543        assert_eq!(output_count, 2);
1544        let emit_kind = read.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
1545        assert!(
1546            matches!(emit_kind, EmitKind::Direct(_)),
1547            "Expected +> without |> to produce Direct, got {emit_kind:?}"
1548        );
1549    }
1550
1551    #[test]
1552    fn test_parse_virtual_read_relation_filter() {
1553        let extensions = TestContext::new()
1554            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
1555            .with_function(1, 10, "gt")
1556            .extensions;
1557
1558        let read = VirtualReadRel::parse_pair_with_context(
1559            &extensions,
1560            parse_exact(
1561                Rule::virtual_read_relation,
1562                "Read:Virtual[(1, 'alice'), filter=gt($0, 0:i64):boolean => id:i64, name:string]",
1563            ),
1564            vec![],
1565            0,
1566        )
1567        .unwrap()
1568        .0
1569        .0;
1570
1571        match read.read_type {
1572            Some(read_rel::ReadType::VirtualTable(table)) => {
1573                assert_eq!(table.expressions.len(), 1);
1574            }
1575            other => panic!("Expected VirtualTable, got {other:?}"),
1576        }
1577        assert!(read.filter.is_some());
1578    }
1579
1580    #[test]
1581    fn test_parse_read_relation_rejects_arrow_with_emit() {
1582        let parsed = ExpressionParser::parse(
1583            Rule::read_relation,
1584            "Read[ab.cd.ef => a:i32, b:string? |> $0, $1]",
1585        );
1586        assert!(
1587            parsed.is_err(),
1588            "Read cannot combine legacy => output with explicit |>"
1589        );
1590    }
1591
1592    /// Produces a ReadRel with 3 columns: a:i32, b:string?, c:i64
1593    fn example_read_relation() -> ReadRel {
1594        let extensions = SimpleExtensions::default();
1595        ReadRel::parse_pair_with_context(
1596            &extensions,
1597            parse_exact(
1598                Rule::read_relation,
1599                "Read[ab.cd.ef => a:i32, b:string?, c:i64]",
1600            ),
1601            vec![],
1602            0,
1603        )
1604        .unwrap()
1605        .0
1606    }
1607
1608    #[test]
1609    fn test_parse_filter_relation() {
1610        let extensions = SimpleExtensions::default();
1611        let filter = FilterRel::parse_pair_with_context(
1612            &extensions,
1613            parse_exact(Rule::filter_relation, "Filter[$1 => $0, $1, $2]"),
1614            vec![example_read_relation().into_rel(None)],
1615            3,
1616        )
1617        .unwrap()
1618        .0;
1619        // Identity mapping [0, 1, 2] over 3 inputs → Direct
1620        let emit_kind = filter.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
1621        assert!(
1622            matches!(emit_kind, EmitKind::Direct(_)),
1623            "Expected Direct for identity emit, got {emit_kind:?}"
1624        );
1625    }
1626
1627    #[test]
1628    fn test_parse_project_relation() {
1629        let extensions = SimpleExtensions::default();
1630        let project = ProjectRel::parse_pair_with_context(
1631            &extensions,
1632            parse_exact(Rule::project_relation, "Project[$0, $1, 42]"),
1633            vec![example_read_relation().into_rel(None)],
1634            3,
1635        )
1636        .unwrap()
1637        .0;
1638
1639        // Should have 1 expression (42) and 2 references ($0, $1)
1640        assert_eq!(project.expressions.len(), 1);
1641
1642        let emit_kind = &project.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
1643        let emit = match emit_kind {
1644            EmitKind::Emit(emit) => &emit.output_mapping,
1645            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
1646        };
1647        // Output mapping should be [0, 1, 3]. References are 0-2; expression is 3.
1648        assert_eq!(emit, &[0, 1, 3]);
1649    }
1650
1651    #[test]
1652    fn test_parse_project_relation_complex() {
1653        let extensions = SimpleExtensions::default();
1654        let project = ProjectRel::parse_pair_with_context(
1655            &extensions,
1656            parse_exact(Rule::project_relation, "Project[42, $0, 100, $2, $1]"),
1657            vec![example_read_relation().into_rel(None)],
1658            5, // Assume 5 input fields
1659        )
1660        .unwrap()
1661        .0;
1662
1663        // Should have 2 expressions (42, 100) and 3 references ($0, $2, $1)
1664        assert_eq!(project.expressions.len(), 2);
1665
1666        let emit_kind = &project.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
1667        let emit = match emit_kind {
1668            EmitKind::Emit(emit) => &emit.output_mapping,
1669            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
1670        };
1671        // Direct mapping: [input_fields..., 42, 100] (input fields first, then expressions)
1672        // Output mapping: [5, 0, 6, 2, 1] (to get: 42, $0, 100, $2, $1)
1673        assert_eq!(emit, &[5, 0, 6, 2, 1]);
1674    }
1675
1676    #[test]
1677    fn test_parse_aggregate_relation() {
1678        let extensions = TestContext::new()
1679            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1680            .with_function(1, 10, "sum")
1681            .with_function(1, 11, "count")
1682            .extensions;
1683
1684        let aggregate = AggregateRel::parse_pair_with_context(
1685            &extensions,
1686            parse_exact(
1687                Rule::aggregate_relation,
1688                "Aggregate[($0, $1), _ => sum($2):i64, $0, count($2):i64]",
1689            ),
1690            vec![example_read_relation().into_rel(None)],
1691            3,
1692        )
1693        .unwrap()
1694        .0;
1695
1696        // Should have 2 group-by sets ($0, $1) and an empty group, and emit 2 measures (sum($2), count($2))
1697        assert_eq!(aggregate.grouping_expressions.len(), 2);
1698        assert_eq!(aggregate.groupings[0].expression_references.len(), 2);
1699        assert_eq!(aggregate.groupings.len(), 2);
1700        assert_eq!(aggregate.measures.len(), 2);
1701
1702        let emit_kind = &aggregate
1703            .common
1704            .as_ref()
1705            .unwrap()
1706            .emit_kind
1707            .as_ref()
1708            .unwrap();
1709        let emit = match emit_kind {
1710            EmitKind::Emit(emit) => &emit.output_mapping,
1711            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
1712        };
1713        // Output mapping should be [2, 0, 3] (measures and group-by fields in order)
1714        // sum($2) -> 2, $0 -> 0, count($2) -> 3
1715        assert_eq!(emit, &[2, 0, 3]);
1716    }
1717
1718    #[test]
1719    fn test_parse_aggregate_relation_maintain_column_order() {
1720        let extensions = TestContext::new()
1721            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1722            .with_function(1, 10, "sum")
1723            .with_function(1, 11, "count")
1724            .extensions;
1725
1726        let aggregate = AggregateRel::parse_pair_with_context(
1727            &extensions,
1728            parse_exact(
1729                Rule::aggregate_relation,
1730                "Aggregate[$0 => sum($1):i64, $0, count($1):i64]",
1731            ),
1732            vec![example_read_relation().into_rel(None)],
1733            3,
1734        )
1735        .unwrap()
1736        .0;
1737
1738        // Should have 1 group-by field ($0) and 2 measures (sum($1), count($1))
1739        assert_eq!(aggregate.grouping_expressions.len(), 1);
1740        assert_eq!(aggregate.groupings.len(), 1);
1741        assert_eq!(aggregate.measures.len(), 2);
1742
1743        let emit_kind = &aggregate
1744            .common
1745            .as_ref()
1746            .unwrap()
1747            .emit_kind
1748            .as_ref()
1749            .unwrap();
1750        let emit = match emit_kind {
1751            EmitKind::Emit(emit) => &emit.output_mapping,
1752            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
1753        };
1754        // Output mapping should be [1, 0, 2] (grouping fields + measures)
1755        assert_eq!(emit, &[1, 0, 2]);
1756    }
1757
1758    #[test]
1759    fn test_parse_aggregate_relation_simple() {
1760        let extensions = TestContext::new()
1761            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1762            .with_function(1, 10, "sum")
1763            .extensions;
1764
1765        let aggregate = AggregateRel::parse_pair_with_context(
1766            &extensions,
1767            parse_exact(Rule::aggregate_relation, "Aggregate[$2, $0 => sum($1):i64]"),
1768            vec![example_read_relation().into_rel(None)],
1769            3,
1770        )
1771        .unwrap()
1772        .0;
1773
1774        assert_eq!(aggregate.grouping_expressions.len(), 2);
1775        assert_eq!(aggregate.groupings.len(), 1);
1776        // expression_references must be positions [0, 1], not raw field indices [2, 0]
1777        assert_eq!(aggregate.groupings[0].expression_references, vec![0, 1]);
1778    }
1779
1780    #[test]
1781    fn test_parse_aggregate_relation_output_reference_uses_grouping_position() {
1782        let extensions = TestContext::new()
1783            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1784            .with_function(1, 10, "sum")
1785            .extensions;
1786
1787        // grouping_expressions ends up as [$2, $0] (grouping's textual order),
1788        // so `$0`/`$2` in the output must resolve to their grouping
1789        // positions (1 and 0 respectively), not their literal reference
1790        // indices. Output order is swapped relative to grouping order so
1791        // the resulting mapping isn't an identity (which would collapse to
1792        // EmitKind::Direct and give us nothing to assert on).
1793        let aggregate = AggregateRel::parse_pair_with_context(
1794            &extensions,
1795            parse_exact(
1796                Rule::aggregate_relation,
1797                "Aggregate[$2, $0 => $0, $2, sum($1):i64]",
1798            ),
1799            vec![example_read_relation().into_rel(None)],
1800            3,
1801        )
1802        .unwrap()
1803        .0;
1804
1805        assert_eq!(aggregate.grouping_expressions.len(), 2);
1806        let emit_kind = &aggregate
1807            .common
1808            .as_ref()
1809            .unwrap()
1810            .emit_kind
1811            .as_ref()
1812            .unwrap();
1813        let emit = match emit_kind {
1814            EmitKind::Emit(emit) => &emit.output_mapping,
1815            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
1816        };
1817        // direct schema is [$2, $0, sum($1)]; output is [$0, $2, sum($1)] -> [1, 0, 2]
1818        assert_eq!(emit, &[1, 0, 2]);
1819    }
1820
1821    #[test]
1822    fn test_parse_aggregate_relation_output_not_in_grouping_expressions_errors() {
1823        let extensions = TestContext::new()
1824            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1825            .with_function(1, 10, "sum")
1826            .extensions;
1827
1828        // $1 is not part of the grouping expressions ($0) and is not an
1829        // aggregate measure, so it has no valid slot in the output schema.
1830        let result = AggregateRel::parse_pair_with_context(
1831            &extensions,
1832            parse_exact(Rule::aggregate_relation, "Aggregate[$0 => $1, sum($1):i64]"),
1833            vec![example_read_relation().into_rel(None)],
1834            3,
1835        );
1836
1837        let error = result.unwrap_err();
1838        assert!(error.to_string().contains(
1839            "output expression is not an aggregate measure and does not match any grouping expression"
1840        ));
1841    }
1842
1843    #[test]
1844    fn test_parse_aggregate_relation_output_with_literal_expression() {
1845        let extensions = TestContext::new()
1846            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1847            .with_function(1, 10, "sum")
1848            .extensions;
1849
1850        // The first grouping expression is a literal, not a reference or a
1851        // function call, so it can only match the output item by structural
1852        // equality (encode_to_vec).
1853        let aggregate = AggregateRel::parse_pair_with_context(
1854            &extensions,
1855            parse_exact(
1856                Rule::aggregate_relation,
1857                "Aggregate[42, $0 => $0, 42, sum($1):i64]",
1858            ),
1859            vec![example_read_relation().into_rel(None)],
1860            3,
1861        )
1862        .unwrap()
1863        .0;
1864
1865        assert_eq!(aggregate.grouping_expressions.len(), 2);
1866        let emit_kind = &aggregate
1867            .common
1868            .as_ref()
1869            .unwrap()
1870            .emit_kind
1871            .as_ref()
1872            .unwrap();
1873        let emit = match emit_kind {
1874            EmitKind::Emit(emit) => &emit.output_mapping,
1875            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
1876        };
1877        // direct schema is [42, $0, sum($1)]; output is [$0, 42, sum($1)] -> [1, 0, 2]
1878        assert_eq!(emit, &[1, 0, 2]);
1879    }
1880
1881    #[test]
1882    fn test_parse_aggregate_relation_function_call_is_not_a_measure() {
1883        let extensions = TestContext::new()
1884            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
1885            .with_urn(2, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1886            .with_function(1, 10, "eq")
1887            .with_function(2, 11, "sum")
1888            .extensions;
1889
1890        // `eq($0, $1)` is both the grouping expression and repeated
1891        // verbatim in the output, so it must resolve to the grouping
1892        // position rather than being parsed as a second, duplicate
1893        // aggregate measure.
1894        let aggregate = AggregateRel::parse_pair_with_context(
1895            &extensions,
1896            parse_exact(
1897                Rule::aggregate_relation,
1898                "Aggregate[eq($0, $1):boolean => eq($0, $1):boolean, sum($2):i64]",
1899            ),
1900            vec![example_read_relation().into_rel(None)],
1901            3,
1902        )
1903        .unwrap()
1904        .0;
1905
1906        assert_eq!(aggregate.grouping_expressions.len(), 1);
1907        assert_eq!(aggregate.measures.len(), 1);
1908        let emit_kind = &aggregate
1909            .common
1910            .as_ref()
1911            .unwrap()
1912            .emit_kind
1913            .as_ref()
1914            .unwrap();
1915        assert!(matches!(emit_kind, EmitKind::Direct(_)));
1916    }
1917
1918    #[test]
1919    fn test_parse_aggregate_relation_global_aggregate() {
1920        let extensions = TestContext::new()
1921            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1922            .with_function(1, 10, "sum")
1923            .with_function(1, 11, "count")
1924            .extensions;
1925
1926        let aggregate = AggregateRel::parse_pair_with_context(
1927            &extensions,
1928            parse_exact(
1929                Rule::aggregate_relation,
1930                "Aggregate[_ => sum($0):i64, count($1):i64]",
1931            ),
1932            vec![example_read_relation().into_rel(None)],
1933            3,
1934        )
1935        .unwrap()
1936        .0;
1937
1938        // Should have 0 group-by fields and 2 measures
1939        assert_eq!(aggregate.grouping_expressions.len(), 0);
1940        assert_eq!(aggregate.groupings.len(), 1);
1941        assert_eq!(aggregate.groupings[0].expression_references.len(), 0);
1942        assert_eq!(aggregate.measures.len(), 2);
1943
1944        // Identity mapping [0, 1] over 2 outputs (0 grouping + 2 measures) → Direct
1945        let emit_kind = aggregate
1946            .common
1947            .as_ref()
1948            .unwrap()
1949            .emit_kind
1950            .as_ref()
1951            .unwrap();
1952        assert!(
1953            matches!(emit_kind, EmitKind::Direct(_)),
1954            "Expected Direct for identity emit, got {emit_kind:?}"
1955        );
1956    }
1957
1958    #[test]
1959    fn test_parse_aggregate_relation_grouping_sets() {
1960        let extensions = TestContext::new()
1961            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_aggregate.yaml")
1962            .with_function(1, 11, "count")
1963            .extensions;
1964
1965        let read_rel = ReadRel::parse_pair_with_context(
1966            &extensions,
1967            parse_exact(
1968                Rule::read_relation,
1969                "Read[ab.cd.ef => a:i32, b:string?, c:i64, d:i64]",
1970            ),
1971            vec![],
1972            0,
1973        )
1974        .unwrap()
1975        .0;
1976
1977        let aggregate = AggregateRel::parse_pair_with_context(
1978            &extensions,
1979            parse_exact(
1980                Rule::aggregate_relation,
1981                "Aggregate[($0, $1, $2), ($2, $0), ($1), _ => $0, $1, $2, count($3):i64]",
1982            ),
1983            vec![read_rel.into_rel(None)],
1984            4,
1985        )
1986        .unwrap()
1987        .0;
1988
1989        assert_eq!(aggregate.grouping_expressions.len(), 3);
1990        assert_eq!(aggregate.groupings.len(), 4);
1991        // ($0, $1, $2) -> [0, 1, 2]
1992        assert_eq!(aggregate.groupings[0].expression_references, vec![0, 1, 2]);
1993        // ($2, $0) -> [2, 0] (reuses indices, different order)
1994        assert_eq!(aggregate.groupings[1].expression_references, vec![2, 0]);
1995        // ($1) -> [1]
1996        assert_eq!(aggregate.groupings[2].expression_references, vec![1]);
1997        // _ -> empty
1998        assert!(aggregate.groupings[3].expression_references.is_empty());
1999        assert_eq!(aggregate.measures.len(), 1);
2000    }
2001
2002    #[test]
2003    fn test_fetch_relation_positive_values() {
2004        let extensions = SimpleExtensions::default();
2005
2006        // Test valid positive values should work
2007        let fetch_rel = FetchRel::parse_pair_with_context(
2008            &extensions,
2009            parse_exact(Rule::fetch_relation, "Fetch[limit=10, offset=5 => $0]"),
2010            vec![example_read_relation().into_rel(None)],
2011            3,
2012        )
2013        .unwrap()
2014        .0;
2015
2016        // Verify the limit and offset values are correct
2017        assert_eq!(
2018            fetch_rel.count_mode,
2019            Some(CountMode::CountExpr(i64_literal_expr(10)))
2020        );
2021        assert_eq!(
2022            fetch_rel.offset_mode,
2023            Some(OffsetMode::OffsetExpr(i64_literal_expr(5)))
2024        );
2025    }
2026
2027    #[test]
2028    fn test_fetch_relation_negative_limit_rejected() {
2029        let extensions = SimpleExtensions::default();
2030
2031        // Test that fetch relations with negative limits are properly rejected
2032        let parsed_result = ExpressionParser::parse(Rule::fetch_relation, "Fetch[limit=-5 => $0]");
2033        if let Ok(mut pairs) = parsed_result {
2034            let pair = pairs.next().unwrap();
2035            if pair.as_str() == "Fetch[limit=-5 => $0]" {
2036                // Full parse succeeded, now test that validation catches the negative value
2037                let result = FetchRel::parse_pair_with_context(
2038                    &extensions,
2039                    pair,
2040                    vec![example_read_relation().into_rel(None)],
2041                    3,
2042                );
2043                assert!(result.is_err());
2044                let error_msg = result.unwrap_err().to_string();
2045                assert!(error_msg.contains("Fetch limit must be non-negative"));
2046            } else {
2047                // If grammar doesn't fully support negative values, that's also acceptable
2048                // since it would prevent negative values at parse time
2049                println!("Grammar prevents negative limit values at parse time");
2050            }
2051        } else {
2052            // Grammar doesn't support negative values in fetch context
2053            println!("Grammar prevents negative limit values at parse time");
2054        }
2055    }
2056
2057    #[test]
2058    fn test_fetch_relation_negative_offset_rejected() {
2059        let extensions = SimpleExtensions::default();
2060
2061        // Test that fetch relations with negative offsets are properly rejected
2062        let parsed_result =
2063            ExpressionParser::parse(Rule::fetch_relation, "Fetch[offset=-10 => $0]");
2064        if let Ok(mut pairs) = parsed_result {
2065            let pair = pairs.next().unwrap();
2066            if pair.as_str() == "Fetch[offset=-10 => $0]" {
2067                // Full parse succeeded, now test that validation catches the negative value
2068                let result = FetchRel::parse_pair_with_context(
2069                    &extensions,
2070                    pair,
2071                    vec![example_read_relation().into_rel(None)],
2072                    3,
2073                );
2074                assert!(result.is_err());
2075                let error_msg = result.unwrap_err().to_string();
2076                assert!(error_msg.contains("Fetch offset must be non-negative"));
2077            } else {
2078                // If grammar doesn't fully support negative values, that's also acceptable
2079                println!("Grammar prevents negative offset values at parse time");
2080            }
2081        } else {
2082            // Grammar doesn't support negative values in fetch context
2083            println!("Grammar prevents negative offset values at parse time");
2084        }
2085    }
2086
2087    #[test]
2088    fn test_parse_join_relation() {
2089        let extensions = TestContext::new()
2090            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
2091            .with_function(1, 10, "eq")
2092            .extensions;
2093
2094        let left_rel = example_read_relation().into_rel(None);
2095        let right_rel = example_read_relation().into_rel(None);
2096
2097        let join = JoinRel::parse_pair_with_context(
2098            &extensions,
2099            parse_exact(
2100                Rule::join_relation,
2101                "Join[&Inner, eq($0, $3):boolean => $0, $1, $3, $4]",
2102            ),
2103            vec![left_rel, right_rel],
2104            6, // left (3) + right (3) = 6 total input fields
2105        )
2106        .unwrap()
2107        .0;
2108
2109        // Should be an Inner join
2110        assert_eq!(join.r#type, join_rel::JoinType::Inner as i32);
2111
2112        // Should have left and right relations
2113        assert!(join.left.is_some());
2114        assert!(join.right.is_some());
2115
2116        // Should have a join condition
2117        assert!(join.expression.is_some());
2118        assert!(join.post_join_filter.is_none());
2119
2120        let emit_kind = &join.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
2121        let emit = match emit_kind {
2122            EmitKind::Emit(emit) => &emit.output_mapping,
2123            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
2124        };
2125        // Output mapping should be [0, 1, 3, 4] (selected columns)
2126        assert_eq!(emit, &[0, 1, 3, 4]);
2127    }
2128
2129    #[test]
2130    fn test_parse_join_relation_post_join_filter() {
2131        let extensions = TestContext::new()
2132            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
2133            .with_function(1, 10, "eq")
2134            .with_function(1, 11, "gt")
2135            .extensions;
2136
2137        let left_rel = example_read_relation().into_rel(None);
2138        let right_rel = example_read_relation().into_rel(None);
2139
2140        let join = JoinRel::parse_pair_with_context(
2141            &extensions,
2142            parse_exact(
2143                Rule::join_relation,
2144                "Join[&RightSemi, eq($0, $3):boolean, post_filter=gt($1, 100:i32):boolean => $0, $1]",
2145            ),
2146            vec![left_rel, right_rel],
2147            6,
2148        )
2149        .unwrap()
2150        .0;
2151
2152        assert_eq!(join.r#type, join_rel::JoinType::RightSemi as i32);
2153        assert!(join.expression.is_some());
2154        assert!(join.post_join_filter.is_some());
2155
2156        let emit_kind = &join.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
2157        let emit = match emit_kind {
2158            EmitKind::Emit(emit) => &emit.output_mapping,
2159            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
2160        };
2161        assert_eq!(emit, &[0, 1]);
2162    }
2163
2164    #[test]
2165    fn test_parse_join_relation_left_outer() {
2166        let extensions = TestContext::new()
2167            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
2168            .with_function(1, 10, "eq")
2169            .extensions;
2170
2171        let left_rel = example_read_relation().into_rel(None);
2172        let right_rel = example_read_relation().into_rel(None);
2173
2174        let join = JoinRel::parse_pair_with_context(
2175            &extensions,
2176            parse_exact(
2177                Rule::join_relation,
2178                "Join[&Left, eq($0, $3):boolean => $0, $1, $2]",
2179            ),
2180            vec![left_rel, right_rel],
2181            6,
2182        )
2183        .unwrap()
2184        .0;
2185
2186        // Should be a Left join
2187        assert_eq!(join.r#type, join_rel::JoinType::Left as i32);
2188
2189        let emit_kind = &join.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
2190        let emit = match emit_kind {
2191            EmitKind::Emit(emit) => &emit.output_mapping,
2192            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
2193        };
2194        // Output mapping should be [0, 1, 2]
2195        assert_eq!(emit, &[0, 1, 2]);
2196    }
2197
2198    #[test]
2199    fn test_parse_join_relation_left_semi() {
2200        let extensions = TestContext::new()
2201            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
2202            .with_function(1, 10, "eq")
2203            .extensions;
2204
2205        let left_rel = example_read_relation().into_rel(None);
2206        let right_rel = example_read_relation().into_rel(None);
2207
2208        let join = JoinRel::parse_pair_with_context(
2209            &extensions,
2210            parse_exact(
2211                Rule::join_relation,
2212                "Join[&LeftSemi, eq($0, $3):boolean => $0, $1]",
2213            ),
2214            vec![left_rel, right_rel],
2215            6,
2216        )
2217        .unwrap()
2218        .0;
2219
2220        // Should be a LeftSemi join
2221        assert_eq!(join.r#type, join_rel::JoinType::LeftSemi as i32);
2222
2223        let emit_kind = &join.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
2224        let emit = match emit_kind {
2225            EmitKind::Emit(emit) => &emit.output_mapping,
2226            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
2227        };
2228        // Output mapping should be [0, 1] (only left columns for semi join)
2229        assert_eq!(emit, &[0, 1]);
2230    }
2231
2232    #[test]
2233    fn test_parse_join_relation_right_semi() {
2234        let extensions = TestContext::new()
2235            .with_urn(1, "https://github.com/substrait-io/substrait/blob/main/extensions/functions_comparison.yaml")
2236            .with_function(1, 10, "eq")
2237            .extensions;
2238
2239        let left_rel = example_read_relation().into_rel(None);
2240        let right_rel = example_read_relation().into_rel(None);
2241
2242        let join = JoinRel::parse_pair_with_context(
2243            &extensions,
2244            parse_exact(
2245                Rule::join_relation,
2246                "Join[&RightSemi, eq($0, $3):boolean => $0, $1]",
2247            ),
2248            vec![left_rel, right_rel],
2249            6,
2250        )
2251        .unwrap()
2252        .0;
2253
2254        // Should be a RightSemi join
2255        assert_eq!(join.r#type, join_rel::JoinType::RightSemi as i32);
2256
2257        let emit_kind = &join.common.as_ref().unwrap().emit_kind.as_ref().unwrap();
2258        let emit = match emit_kind {
2259            EmitKind::Emit(emit) => &emit.output_mapping,
2260            _ => panic!("Expected EmitKind::Emit, got {emit_kind:?}"),
2261        };
2262        // Output mapping should be [0, 1] over right-semi direct output columns.
2263        assert_eq!(emit, &[0, 1]);
2264    }
2265
2266    #[test]
2267    fn test_parse_join_relation_requires_two_children() {
2268        let extensions = SimpleExtensions::default();
2269
2270        // Test with 0 children
2271        let result = JoinRel::parse_pair_with_context(
2272            &extensions,
2273            parse_exact(
2274                Rule::join_relation,
2275                "Join[&Inner, eq($0, $1):boolean => $0, $1]",
2276            ),
2277            vec![],
2278            0,
2279        );
2280        assert!(result.is_err());
2281
2282        // Test with 1 child
2283        let result = JoinRel::parse_pair_with_context(
2284            &extensions,
2285            parse_exact(
2286                Rule::join_relation,
2287                "Join[&Inner, eq($0, $1):boolean => $0, $1]",
2288            ),
2289            vec![example_read_relation().into_rel(None)],
2290            3,
2291        );
2292        assert!(result.is_err());
2293
2294        // Test with 3 children
2295        let result = JoinRel::parse_pair_with_context(
2296            &extensions,
2297            parse_exact(
2298                Rule::join_relation,
2299                "Join[&Inner, eq($0, $1):boolean => $0, $1]",
2300            ),
2301            vec![
2302                example_read_relation().into_rel(None),
2303                example_read_relation().into_rel(None),
2304                example_read_relation().into_rel(None),
2305            ],
2306            9,
2307        );
2308        assert!(result.is_err());
2309    }
2310
2311    fn parse_exact(rule: Rule, input: &'_ str) -> pest::iterators::Pair<'_, Rule> {
2312        let mut pairs = ExpressionParser::parse(rule, input).unwrap();
2313        assert_eq!(pairs.as_str(), input);
2314        let pair = pairs.next().unwrap();
2315        assert_eq!(pairs.next(), None);
2316        pair
2317    }
2318}