Skip to main content

query_planning/expr/
column.rs

1//! Column reference IR shared across native expressions.
2//!
3//! See [`ColumnRef`] for the lookup/display-name split and qualifier rules.
4
5use std::fmt;
6
7/// Column identity in the native expression IR.
8///
9/// `name` is the physical/logical field name used to resolve the column against
10/// an input schema. `qualifier` disambiguates which input namespace owns that
11/// field when a node has more than one input. `display_name` is deliberately
12/// separate: it is the externally visible or projected name used for output
13/// contracts and selected diagnostics, and it may differ from `name` when the
14/// same source column is projected under a distinct alias. `Display`
15/// formatting uses the qualifier and lookup name.
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct ColumnRef {
18    /// Optional namespace for resolving the column in multi-input or graph contexts.
19    qualifier: Option<ColumnQualifier>,
20    /// Source/input field name used for schema lookup and execution lowering.
21    name: String,
22    /// User-visible or projected field name retained for output contracts.
23    display_name: String,
24}
25
26impl ColumnRef {
27    /// Build a column reference with no namespace qualifier.
28    ///
29    /// Unqualified references resolve against the current single-input schema.
30    /// The display name is initialized to the same value as the lookup name.
31    pub fn unqualified(name: impl Into<String>) -> Self {
32        let name = name.into();
33        Self { qualifier: None, display_name: name.clone(), name }
34    }
35
36    /// Build a column reference scoped to an input ordinal.
37    ///
38    /// This is the preferred qualifier for binary-input nodes such as joins.
39    /// `input` is zero-based and identifies the node input. It does not index fields.
40    /// Equality and hashing include the qualifier and both names, so a join
41    /// key spelled with a different `display_name` is a different key.
42    pub fn input(input: usize, name: impl Into<String>, display_name: impl Into<String>) -> Self {
43        Self {
44            qualifier: Some(ColumnQualifier::Input { index: input }),
45            name: name.into(),
46            display_name: display_name.into(),
47        }
48    }
49
50    /// Input identity, or `None` when the column is unqualified.
51    pub fn qualifier(&self) -> Option<&ColumnQualifier> {
52        self.qualifier.as_ref()
53    }
54
55    /// Column name used for input-schema resolution.
56    pub fn name(&self) -> &str {
57        &self.name
58    }
59
60    /// Frontend spelling retained for diagnostics and expression identity.
61    pub fn display_name(&self) -> &str {
62        &self.display_name
63    }
64
65    /// Whether this reference names an explicit input ordinal.
66    pub fn is_qualified(&self) -> bool {
67        self.qualifier.is_some()
68    }
69}
70
71impl fmt::Display for ColumnRef {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        if let Some(qualifier) = &self.qualifier {
74            write!(f, "{qualifier}.{}", self.name)
75        } else {
76            f.write_str(&self.name)
77        }
78    }
79}
80
81/// Namespace used to disambiguate column references.
82///
83/// The formatted qualifier is part of expression diagnostics and plan display.
84/// It is not necessarily the field name exposed by downstream schemas; use
85/// [`ColumnRef::display_name`] for that.
86///
87/// Only [`Self::Input`] takes part in resolution: join validation, capability
88/// analysis and the optimizer match `Input { index: 0 | 1 }` to a join side and
89/// treat an unqualified reference as single-input. `Node` and `Relation` are
90/// carried for display and are matched nowhere in this crate.
91#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92pub enum ColumnQualifier {
93    /// Positional input namespace, formatted as `input{index}`.
94    Input {
95        /// Zero-based input ordinal for a multi-input node.
96        index: usize,
97    },
98    /// Query-plan node namespace.
99    Node {
100        /// Stable node identifier used by the plan graph.
101        id: String,
102    },
103    /// Named relation namespace.
104    Relation {
105        /// Relation or table name.
106        name: String,
107    },
108}
109
110impl fmt::Display for ColumnQualifier {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            Self::Input { index } => write!(f, "input{index}"),
114            Self::Node { id } => write!(f, "{id}"),
115            Self::Relation { name } => f.write_str(name),
116        }
117    }
118}