Skip to main content

uqa_sql/catalog/node_tree/expressions/
schema.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Borrowed catalog attributes used by stored expression encoding.
8
9use crate::schema::{ColumnTypeSchema, ScalarTypeSchema};
10use crate::{ColumnType, RowSchema, SQLError};
11
12/// A resolved attribute in the same generation as the expression's type lookup. Attribute numbers are catalog identities, not positions after projection or a dropped column.
13pub struct ColumnReference<'a> {
14    pub attribute_number: i64,
15    pub ty: &'a ColumnType,
16    pub collation_oid: i64,
17}
18
19/// Stored expression encoding needs the original attribute identity and collation as well as its type. Borrow declarations directly when those identities are available; an execution-only schema retains its existing positional contract.
20pub trait ExpressionSchema: ScalarTypeSchema {
21    fn column_reference(
22        &self,
23        name: &str,
24        qualifier: Option<&str>,
25    ) -> Result<ColumnReference<'_>, SQLError>;
26
27    /// Whole-row Vars retain attribute zero and the relation's actual named type, independently of its current live attribute list.
28    fn whole_row_reference(&self, qualifier: &str) -> Result<ColumnReference<'_>, SQLError> {
29        self.whole_row_type(qualifier)
30            .map(|ty| ColumnReference {
31                attribute_number: 0,
32                ty,
33                collation_oid: 0,
34            })
35            .ok_or_else(|| SQLError::UnknownTable(qualifier.into()))
36    }
37}
38
39impl ExpressionSchema for RowSchema {
40    fn column_reference(
41        &self,
42        name: &str,
43        qualifier: Option<&str>,
44    ) -> Result<ColumnReference<'_>, SQLError> {
45        let position = qualifier
46            .map_or_else(
47                || self.unqualified_position(name),
48                |qualifier| self.qualified_position(qualifier, name),
49            )
50            .ok_or_else(|| SQLError::UnknownColumn(name.into()))?;
51        let ty = self
52            .column_type(position)
53            .ok_or_else(|| SQLError::Internal("catalog column has no declared type".into()))?;
54        Ok(ColumnReference {
55            attribute_number: i64::try_from(position + 1)
56                .map_err(|_| SQLError::Internal("column ordinal overflow".into()))?,
57            ty,
58            collation_oid: i64::from(
59                self.column_collation(position)
60                    .expect("typed catalog column collation"),
61            ),
62        })
63    }
64}
65
66impl ExpressionSchema for ColumnTypeSchema<'_> {
67    fn column_reference(
68        &self,
69        name: &str,
70        qualifier: Option<&str>,
71    ) -> Result<ColumnReference<'_>, SQLError> {
72        if qualifier.is_some_and(|qualifier| !self.has_qualifier(qualifier)) {
73            return Err(SQLError::UnknownColumn(name.into()));
74        }
75        let mut matching = self
76            .columns()
77            .iter()
78            .enumerate()
79            .filter(|(_, column)| column.name == name);
80        let Some((position, column)) = matching.next() else {
81            if let Some(ty) = self.system_column_type(name) {
82                return Ok(ColumnReference {
83                    attribute_number: -6,
84                    ty,
85                    collation_oid: 0,
86                });
87            }
88            if qualifier.is_none() && self.has_qualifier(name) {
89                return self.whole_row_reference(name);
90            }
91            return Err(SQLError::UnknownColumn(name.into()));
92        };
93        if matching.next().is_some() {
94            return Err(SQLError::UnknownColumn(name.into()));
95        }
96        let attribute_number = column.attribute_number.map_or_else(
97            || {
98                i64::try_from(position + 1)
99                    .map_err(|_| SQLError::Internal("column ordinal overflow".into()))
100            },
101            |number| Ok(i64::from(number)),
102        )?;
103        if attribute_number <= 0 {
104            return Err(SQLError::Internal(
105                "invalid catalog attribute number".into(),
106            ));
107        }
108        Ok(ColumnReference {
109            attribute_number,
110            ty: &column.ty,
111            collation_oid: crate::catalog::collations::declarations::column_oid(column),
112        })
113    }
114}