Skip to main content

substrait_explain/extensions/
args.rs

1//! Text-format data structures used by registered advanced extension handlers.
2//!
3//! These types describe the arguments accepted by custom relation types,
4//! enhancements, and optimization hints. Relation extensions can additionally
5//! describe output columns.
6//!
7//! The interface presented to extension handlers is structured rather than
8//! textual: handlers read and write values such as [`ExtensionArgs`], [`Expr`],
9//! and [`proto::Type`]. `substrait-explain` handles the surrounding
10//! parsing/textification. Some values need plan context before they reach a
11//! handler; for example, an expression argument like `add($0, $1)` is parsed
12//! using [`SimpleExtensions`](crate::extensions::SimpleExtensions) to resolve
13//! the text function name to the protobuf function anchor, and formatted by
14//! resolving that anchor back to a text name.
15//!
16//! The extension-facing interface for Substrait objects (e.g. [`proto::Type`])
17//! should map directly to Substrait protobuf concepts. Sometimes that means
18//! storing the protobuf type directly, as named output columns do with
19//! [`proto::Type`]; sometimes it means using a small wrapper, as
20//! expression-compatible arguments do with [`Expr`] around
21//! [`proto::Expression`].
22//!
23//! Untyped scalar literals (e.g. `2`, `2.435`, `'string'`) are kept as
24//! extension scalar values so text rendering can preserve scalar syntax even in
25//! verbose output, while handlers that accept expressions can still widen them
26//! into default Substrait literal expressions.
27
28use std::collections::HashSet;
29use std::slice::Iter as SliceIter;
30use std::vec::IntoIter as VecIntoIter;
31use std::{fmt, thread};
32
33use indexmap::IndexMap;
34use substrait::proto;
35use substrait::proto::expression::field_reference::ReferenceType;
36use substrait::proto::expression::literal::LiteralType;
37use substrait::proto::expression::{RexType, reference_segment};
38
39use super::ExtensionError;
40use crate::textify::expressions::Reference;
41
42/// Kind of relation addendum in the text format.
43///
44/// Addenda are `+`-prefixed lines attached to relations. They are syntax-level
45/// constructs, distinct from [`crate::extensions::registry::ExtensionType`],
46/// which describes registry namespaces.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub(crate) enum AddendumKind {
49    Enhancement,
50    Optimization,
51    ExtensionTable,
52}
53
54impl AddendumKind {
55    pub(crate) fn prefix(self) -> &'static str {
56        match self {
57            AddendumKind::Enhancement => "Enh",
58            AddendumKind::Optimization => "Opt",
59            AddendumKind::ExtensionTable => "Ext",
60        }
61    }
62}
63
64/// A Substrait expression carried as an extension argument or output column.
65///
66/// Boxed because `proto::Expression` is large (multiple `Vec` fields in
67/// variants like `ScalarFunction`).
68#[derive(Debug, Clone)]
69pub struct Expr(Box<proto::Expression>);
70
71impl Expr {
72    /// Create a direct field-reference expression (`$N`).
73    pub fn field(index: i32) -> Self {
74        Reference(index).into()
75    }
76
77    /// Borrow the underlying Substrait expression protobuf.
78    pub fn as_proto(&self) -> &proto::Expression {
79        self.0.as_ref()
80    }
81
82    /// Clone the underlying Substrait expression protobuf.
83    pub fn to_proto(&self) -> proto::Expression {
84        self.as_proto().clone()
85    }
86
87    /// If this expression is a direct field reference (`$N`), return it.
88    pub fn as_direct_reference(&self) -> Option<i32> {
89        let Some(RexType::Selection(field_ref)) = self.as_proto().rex_type.as_ref() else {
90            return None;
91        };
92        let Some(ReferenceType::DirectReference(segment)) = field_ref.reference_type.as_ref()
93        else {
94            return None;
95        };
96        let Some(reference_segment::ReferenceType::StructField(field)) =
97            segment.reference_type.as_ref()
98        else {
99            return None;
100        };
101        if field.child.is_some() {
102            return None;
103        }
104        Some(field.field)
105    }
106}
107
108impl From<proto::Expression> for Expr {
109    fn from(expr: proto::Expression) -> Self {
110        Expr(Box::new(expr))
111    }
112}
113
114impl From<proto::expression::Literal> for Expr {
115    fn from(literal: proto::expression::Literal) -> Self {
116        proto::Expression {
117            rex_type: Some(RexType::Literal(literal)),
118        }
119        .into()
120    }
121}
122
123impl From<Reference> for Expr {
124    fn from(reference: Reference) -> Self {
125        proto::Expression::from(reference).into()
126    }
127}
128
129impl From<Expr> for proto::Expression {
130    fn from(expr: Expr) -> Self {
131        *expr.0
132    }
133}
134
135impl From<i64> for Expr {
136    fn from(value: i64) -> Self {
137        proto::expression::Literal {
138            literal_type: Some(LiteralType::I64(value)),
139            nullable: false,
140            type_variation_reference: 0,
141        }
142        .into()
143    }
144}
145
146impl From<f64> for Expr {
147    fn from(value: f64) -> Self {
148        proto::expression::Literal {
149            literal_type: Some(LiteralType::Fp64(value)),
150            nullable: false,
151            type_variation_reference: 0,
152        }
153        .into()
154    }
155}
156
157impl From<bool> for Expr {
158    fn from(value: bool) -> Self {
159        proto::expression::Literal {
160            literal_type: Some(LiteralType::Boolean(value)),
161            nullable: false,
162            type_variation_reference: 0,
163        }
164        .into()
165    }
166}
167
168impl From<String> for Expr {
169    fn from(value: String) -> Self {
170        proto::expression::Literal {
171            literal_type: Some(LiteralType::String(value)),
172            nullable: false,
173            type_variation_reference: 0,
174        }
175        .into()
176    }
177}
178
179impl From<&str> for Expr {
180    fn from(value: &str) -> Self {
181        value.to_string().into()
182    }
183}
184
185/// Represents extension arguments plus optional output columns.
186///
187/// Named arguments are stored in an [`IndexMap`] whose iteration order
188/// determines display order. Extension [`super::Explainable::to_args()`]
189/// implementations should insert named arguments in the order they should
190/// appear in the text format.
191#[derive(Debug, Clone, Default)]
192pub struct ExtensionArgs {
193    /// Positional arguments.
194    pub positional: Vec<ExtensionValue>,
195    /// Named arguments, displayed in the order they were inserted
196    pub named: IndexMap<String, ExtensionValue>,
197    /// Output columns for custom relation types.
198    pub output_columns: Vec<ExtensionColumn>,
199}
200
201/// Helper struct for extracting named arguments with validation.
202///
203/// Tracks which arguments have been consumed. Callers **must** call
204/// [`check_exhausted`](ArgsExtractor::check_exhausted) before dropping to
205/// verify no unexpected arguments remain. In debug builds, dropping without
206/// calling `check_exhausted` will panic. This catches [`Explainable`](super::Explainable)
207/// implementations that forget to reject unexpected named arguments.
208pub struct ArgsExtractor<'a> {
209    args: &'a ExtensionArgs,
210    consumed: HashSet<&'a str>,
211    checked: bool,
212}
213
214impl<'a> ArgsExtractor<'a> {
215    /// Create a new extractor for the given arguments
216    pub fn new(args: &'a ExtensionArgs) -> Self {
217        Self {
218            args,
219            consumed: HashSet::new(),
220            checked: false,
221        }
222    }
223
224    /// Get a named argument value, marking it as consumed if found.
225    pub fn get_named_arg(&mut self, name: &str) -> Option<&'a ExtensionValue> {
226        match self.args.named.get_key_value(name) {
227            Some((k, value)) => {
228                self.consumed.insert(k);
229                Some(value)
230            }
231            None => None,
232        }
233    }
234
235    /// Get a named argument value or return an error
236    /// Marks the argument as consumed if found
237    pub fn expect_named_arg<T>(&mut self, name: &str) -> Result<T, ExtensionError>
238    where
239        T: TryFrom<&'a ExtensionValue>,
240        T::Error: Into<ExtensionError>,
241    {
242        match self.get_named_arg(name) {
243            Some(value) => T::try_from(value).map_err(Into::into),
244            None => Err(ExtensionError::MissingArgument {
245                name: name.to_string(),
246            }),
247        }
248    }
249
250    /// Get a named argument value or default
251    /// Marks the argument as consumed if it exists in the source args
252    pub fn get_named_or<T>(&mut self, name: &str, default: T) -> Result<T, ExtensionError>
253    where
254        T: TryFrom<&'a ExtensionValue>,
255        T::Error: Into<ExtensionError>,
256    {
257        match self.get_named_arg(name) {
258            Some(value) => T::try_from(value).map_err(Into::into),
259            None => Ok(default),
260        }
261    }
262
263    /// Check that all named arguments in the source have been consumed,
264    /// returning an error if not.
265    ///
266    /// Must be called before the extractor is dropped, to validate that all
267    /// args are correctly handled. In debug builds, dropping without calling
268    /// this method will panic.
269    pub fn check_exhausted(&mut self) -> Result<(), ExtensionError> {
270        self.checked = true;
271
272        let mut unknown_args = Vec::new();
273        for name in self.args.named.keys() {
274            if !self.consumed.contains(name.as_str()) {
275                unknown_args.push(name.as_str());
276            }
277        }
278
279        if unknown_args.is_empty() {
280            Ok(())
281        } else {
282            // Sort for stable error messages
283            unknown_args.sort();
284            Err(ExtensionError::InvalidArgument(format!(
285                "Unknown named arguments: {}",
286                unknown_args.join(", ")
287            )))
288        }
289    }
290}
291
292impl Drop for ArgsExtractor<'_> {
293    fn drop(&mut self) {
294        if self.checked || thread::panicking() {
295            return;
296        }
297        // If we get here, the caller forgot to call check_exhausted().
298        debug_assert!(
299            false,
300            "ArgsExtractor dropped without calling check_exhausted()"
301        );
302    }
303}
304
305/// A tuple-valued extension argument.
306///
307/// Tuple values preserve positional order and can be iterated by value or by
308/// reference.
309#[derive(Debug, Clone)]
310pub struct TupleValue(Vec<ExtensionValue>);
311
312impl TupleValue {
313    pub fn len(&self) -> usize {
314        self.0.len()
315    }
316
317    pub fn is_empty(&self) -> bool {
318        self.0.is_empty()
319    }
320
321    pub fn iter(&self) -> SliceIter<'_, ExtensionValue> {
322        self.0.iter()
323    }
324}
325
326impl<'a> IntoIterator for &'a TupleValue {
327    type Item = &'a ExtensionValue;
328    type IntoIter = SliceIter<'a, ExtensionValue>;
329
330    fn into_iter(self) -> Self::IntoIter {
331        self.0.iter()
332    }
333}
334
335impl IntoIterator for TupleValue {
336    type Item = ExtensionValue;
337    type IntoIter = VecIntoIter<ExtensionValue>;
338
339    fn into_iter(self) -> Self::IntoIter {
340        self.0.into_iter()
341    }
342}
343
344impl FromIterator<ExtensionValue> for TupleValue {
345    fn from_iter<I: IntoIterator<Item = ExtensionValue>>(iter: I) -> Self {
346        TupleValue(iter.into_iter().collect())
347    }
348}
349
350impl From<Vec<ExtensionValue>> for TupleValue {
351    fn from(items: Vec<ExtensionValue>) -> Self {
352        TupleValue(items)
353    }
354}
355
356/// Represents a value in extension arguments.
357///
358/// These values are the structured form of text-format extension arguments,
359/// fully resolved - i.e. any additional context (such as function anchors etc)
360/// are part of this struct itself.
361#[derive(Debug, Clone)]
362pub enum ExtensionValue {
363    /// Untyped literals. These are not input or output with types (e.g. `2`,
364    /// not `2:i64`), and suitable for protobuf extension fields that are not
365    /// substrait types.
366    String(String),
367    Integer(i64),
368    Float(f64),
369    Boolean(bool),
370
371    /// Substrait expression value, including typed literals and field references.
372    ///
373    /// Use `TryFrom<&ExtensionValue> for Expr` when a handler accepts either an
374    /// expression or a scalar value widened into an expression.
375    Expr(Expr),
376    /// Enum value (e.g. &CORE, &Inner) — the string holds the identifier
377    /// without the `&` prefix
378    Enum(String),
379    /// Tuple of values, e.g. (&HASH, &RANGE) or (42, 'hello')
380    Tuple(TupleValue),
381    // TODO: Consider adding support for types as arguments. May need dedicated
382    // syntax (`:typename`, perhaps?), as type names may not be distinguishable
383    // from identifiers
384}
385
386/// The variant kind of an [`ExtensionValue`], used in diagnostics.
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
388pub enum ExtensionValueKind {
389    String,
390    Integer,
391    Float,
392    Boolean,
393    Reference,
394    Enum,
395    Tuple,
396    Expression,
397}
398
399impl fmt::Display for ExtensionValueKind {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        match self {
402            ExtensionValueKind::String => write!(f, "string"),
403            ExtensionValueKind::Integer => write!(f, "integer"),
404            ExtensionValueKind::Float => write!(f, "float"),
405            ExtensionValueKind::Boolean => write!(f, "boolean"),
406            ExtensionValueKind::Reference => write!(f, "reference"),
407            ExtensionValueKind::Enum => write!(f, "enum"),
408            ExtensionValueKind::Tuple => write!(f, "tuple"),
409            ExtensionValueKind::Expression => write!(f, "expression"),
410        }
411    }
412}
413
414impl ExtensionValue {
415    /// Return the variant kind of this value for structured diagnostics.
416    pub fn kind(&self) -> ExtensionValueKind {
417        match self {
418            ExtensionValue::String(_) => ExtensionValueKind::String,
419            ExtensionValue::Integer(_) => ExtensionValueKind::Integer,
420            ExtensionValue::Float(_) => ExtensionValueKind::Float,
421            ExtensionValue::Boolean(_) => ExtensionValueKind::Boolean,
422            ExtensionValue::Expr(_) => ExtensionValueKind::Expression,
423            ExtensionValue::Enum(_) => ExtensionValueKind::Enum,
424            ExtensionValue::Tuple(_) => ExtensionValueKind::Tuple,
425        }
426    }
427}
428
429impl From<Expr> for ExtensionValue {
430    fn from(expr: Expr) -> Self {
431        ExtensionValue::Expr(expr)
432    }
433}
434
435impl From<proto::Expression> for ExtensionValue {
436    fn from(expr: proto::Expression) -> Self {
437        Expr::from(expr).into()
438    }
439}
440
441impl From<proto::expression::Literal> for ExtensionValue {
442    fn from(literal: proto::expression::Literal) -> Self {
443        Expr::from(literal).into()
444    }
445}
446
447impl From<Reference> for ExtensionValue {
448    fn from(reference: Reference) -> Self {
449        Expr::from(reference).into()
450    }
451}
452
453impl From<i64> for ExtensionValue {
454    fn from(value: i64) -> Self {
455        ExtensionValue::Integer(value)
456    }
457}
458
459impl From<f64> for ExtensionValue {
460    fn from(value: f64) -> Self {
461        ExtensionValue::Float(value)
462    }
463}
464
465impl From<bool> for ExtensionValue {
466    fn from(value: bool) -> Self {
467        ExtensionValue::Boolean(value)
468    }
469}
470
471impl From<String> for ExtensionValue {
472    fn from(value: String) -> Self {
473        ExtensionValue::String(value)
474    }
475}
476
477impl From<&str> for ExtensionValue {
478    fn from(value: &str) -> Self {
479        ExtensionValue::String(value.to_string())
480    }
481}
482
483fn invalid_type(expected: ExtensionValueKind, actual: &ExtensionValue) -> ExtensionError {
484    ExtensionError::InvalidArgumentType {
485        expected,
486        actual: actual.kind(),
487    }
488}
489
490impl<'a> TryFrom<&'a ExtensionValue> for &'a str {
491    type Error = ExtensionError;
492
493    fn try_from(value: &'a ExtensionValue) -> Result<&'a str, Self::Error> {
494        match value {
495            ExtensionValue::String(s) => Ok(s),
496            v => Err(invalid_type(ExtensionValueKind::String, v)),
497        }
498    }
499}
500
501impl TryFrom<ExtensionValue> for String {
502    type Error = ExtensionError;
503
504    fn try_from(value: ExtensionValue) -> Result<String, Self::Error> {
505        <&str>::try_from(&value).map(ToOwned::to_owned)
506    }
507}
508
509/// Helper for extracting the identifier from an [`ExtensionValue::Enum`].
510pub struct EnumValue(pub String);
511
512impl<'a> TryFrom<&'a ExtensionValue> for EnumValue {
513    type Error = ExtensionError;
514
515    fn try_from(value: &'a ExtensionValue) -> Result<EnumValue, Self::Error> {
516        match value {
517            ExtensionValue::Enum(s) => Ok(EnumValue(s.clone())),
518            v => Err(invalid_type(ExtensionValueKind::Enum, v)),
519        }
520    }
521}
522
523impl<'a> TryFrom<&'a ExtensionValue> for &'a TupleValue {
524    type Error = ExtensionError;
525
526    fn try_from(value: &'a ExtensionValue) -> Result<&'a TupleValue, Self::Error> {
527        match value {
528            ExtensionValue::Tuple(tv) => Ok(tv),
529            v => Err(invalid_type(ExtensionValueKind::Tuple, v)),
530        }
531    }
532}
533
534impl TryFrom<&ExtensionValue> for i64 {
535    type Error = ExtensionError;
536
537    fn try_from(value: &ExtensionValue) -> Result<i64, Self::Error> {
538        match value {
539            ExtensionValue::Integer(i) => Ok(*i),
540            v => Err(invalid_type(ExtensionValueKind::Integer, v)),
541        }
542    }
543}
544
545impl TryFrom<&ExtensionValue> for f64 {
546    type Error = ExtensionError;
547
548    fn try_from(value: &ExtensionValue) -> Result<f64, Self::Error> {
549        match value {
550            ExtensionValue::Float(f) => Ok(*f),
551            v => Err(invalid_type(ExtensionValueKind::Float, v)),
552        }
553    }
554}
555
556impl TryFrom<&ExtensionValue> for bool {
557    type Error = ExtensionError;
558
559    fn try_from(value: &ExtensionValue) -> Result<bool, Self::Error> {
560        match value {
561            ExtensionValue::Boolean(b) => Ok(*b),
562            v => Err(invalid_type(ExtensionValueKind::Boolean, v)),
563        }
564    }
565}
566
567impl TryFrom<&ExtensionValue> for Reference {
568    type Error = ExtensionError;
569
570    fn try_from(value: &ExtensionValue) -> Result<Reference, Self::Error> {
571        match value {
572            ExtensionValue::Expr(expr) => expr
573                .as_direct_reference()
574                .map(Reference)
575                .ok_or_else(|| invalid_type(ExtensionValueKind::Reference, value)),
576            v => Err(invalid_type(ExtensionValueKind::Reference, v)),
577        }
578    }
579}
580
581impl TryFrom<&ExtensionValue> for Expr {
582    type Error = ExtensionError;
583
584    fn try_from(value: &ExtensionValue) -> Result<Expr, Self::Error> {
585        match value {
586            ExtensionValue::Expr(e) => Ok(e.clone()),
587            // Untyped extension scalars are intentionally expression-compatible:
588            // `arg=2` carries no syntax that distinguishes "configuration
589            // integer" from "i64 literal expression". Scalar-specific
590            // extraction (`i64`, `&str`, `bool`, etc.) still requires the scalar
591            // variants, while expression extraction widens them to default
592            // non-nullable Substrait literal expressions.
593            ExtensionValue::Integer(i) => Ok(Expr::from(*i)),
594            ExtensionValue::Float(f) => Ok(Expr::from(*f)),
595            ExtensionValue::String(s) => Ok(Expr::from(s.as_str())),
596            ExtensionValue::Boolean(b) => Ok(Expr::from(*b)),
597            v => Err(invalid_type(ExtensionValueKind::Expression, v)),
598        }
599    }
600}
601
602/// Represents an output column specification.
603///
604/// These values mirror the text-format output column forms. Named columns keep
605/// the parsed Substrait type protobuf so handlers can convert directly to
606/// relation schemas.
607#[derive(Debug, Clone)]
608pub enum ExtensionColumn {
609    /// Named column with a parsed Substrait type (e.g. `name:i64?`).
610    Named {
611        /// Column name as it appears in the extension relation output.
612        name: String,
613        /// Parsed Substrait type for the column.
614        ///
615        /// This uses the protobuf field name, hence the raw identifier.
616        r#type: proto::Type,
617    },
618    /// Expression-compatible output column, including field references.
619    Expr(Expr),
620}
621
622impl ExtensionColumn {
623    /// Create an expression output column that references an existing input field (`$N`).
624    pub fn field(index: i32) -> Self {
625        Self::Expr(Expr::field(index))
626    }
627}
628
629impl ExtensionArgs {
630    /// Push a positional extension argument.
631    pub fn push<T>(&mut self, value: T)
632    where
633        T: Into<ExtensionValue>,
634    {
635        self.positional.push(value.into());
636    }
637
638    /// Insert a named extension argument, returning any previous value.
639    pub fn insert<K, V>(&mut self, name: K, value: V) -> Option<ExtensionValue>
640    where
641        K: Into<String>,
642        V: Into<ExtensionValue>,
643    {
644        self.named.insert(name.into(), value.into())
645    }
646
647    /// Create an extractor for validating named arguments
648    pub fn extractor(&self) -> ArgsExtractor<'_> {
649        ArgsExtractor::new(self)
650    }
651}