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}