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