Skip to main content

uqa_sql/schema/
type_lookup.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Scalar type lookup borrows declared columns without constructing physical row indexes.
8
9use super::RowSchema;
10use crate::ast::{ColumnDef, ColumnType, InternalColumnRef};
11
12/// Borrowed schema operations used by scalar type propagation and binding. Physical schemas retain their complete alias, ambiguity, open-column and executor-attribute behavior; declared column slices supply an unqualified positional namespace without allocating another schema generation.
13pub trait ScalarTypeSchema {
14    fn has_unqualified_column(&self, name: &str) -> bool;
15    fn column_is_ambiguous(&self, name: &str) -> bool;
16    fn type_of(&self, name: &str) -> Option<&ColumnType>;
17    fn column_type(&self, position: usize) -> Option<&ColumnType>;
18    fn internal_type(&self, column: InternalColumnRef) -> Option<&ColumnType>;
19    fn has_qualifier(&self, qualifier: &str) -> bool;
20    fn has_qualified_column(&self, qualifier: &str, name: &str) -> bool;
21    fn qualified_column_is_ambiguous(&self, qualifier: &str, name: &str) -> bool;
22    fn qualified_type(&self, qualifier: &str, name: &str) -> Option<&ColumnType>;
23    fn columns_are_open(&self, qualifier: Option<&str>) -> bool;
24
25    /// Preserve the physical outer-row interface used by existing custom scalar-subquery resolvers. Declared column views have no query arena or physical outer row.
26    fn physical_schema(&self) -> Option<&RowSchema>;
27}
28
29impl ScalarTypeSchema for RowSchema {
30    fn has_unqualified_column(&self, name: &str) -> bool {
31        Self::has_unqualified_column(self, name)
32    }
33    fn column_is_ambiguous(&self, name: &str) -> bool {
34        Self::column_is_ambiguous(self, name)
35    }
36    fn type_of(&self, name: &str) -> Option<&ColumnType> {
37        Self::type_of(self, name)
38    }
39    fn column_type(&self, position: usize) -> Option<&ColumnType> {
40        Self::column_type(self, position)
41    }
42    fn internal_type(&self, column: InternalColumnRef) -> Option<&ColumnType> {
43        Self::internal_type(self, column)
44    }
45    fn has_qualifier(&self, qualifier: &str) -> bool {
46        Self::has_qualifier(self, qualifier)
47    }
48    fn has_qualified_column(&self, qualifier: &str, name: &str) -> bool {
49        Self::has_qualified_column(self, qualifier, name)
50    }
51    fn qualified_column_is_ambiguous(&self, qualifier: &str, name: &str) -> bool {
52        Self::qualified_column_is_ambiguous(self, qualifier, name)
53    }
54    fn qualified_type(&self, qualifier: &str, name: &str) -> Option<&ColumnType> {
55        Self::qualified_type(self, qualifier, name)
56    }
57    fn columns_are_open(&self, qualifier: Option<&str>) -> bool {
58        Self::columns_are_open(self, qualifier)
59    }
60    fn physical_schema(&self) -> Option<&RowSchema> {
61        Some(self)
62    }
63}
64
65/// Zero-allocation view matching `RowSchema::with_types` for the supplied unqualified column definitions. Names and declared types, including domains and nested arrays, stay borrowed from their existing owner. Duplicate exact names remain ambiguous; dots in an identifier do not create a relation qualifier.
66#[derive(Clone, Copy)]
67pub struct ColumnTypeSchema<'a> {
68    columns: &'a [ColumnDef],
69}
70
71impl<'a> ColumnTypeSchema<'a> {
72    pub fn new(columns: &'a [ColumnDef]) -> Self {
73        Self { columns }
74    }
75}
76
77impl ScalarTypeSchema for ColumnTypeSchema<'_> {
78    fn has_unqualified_column(&self, name: &str) -> bool {
79        self.columns.iter().any(|column| column.name == name)
80    }
81    fn column_is_ambiguous(&self, name: &str) -> bool {
82        self.columns
83            .iter()
84            .filter(|column| column.name == name)
85            .nth(1)
86            .is_some()
87    }
88    fn type_of(&self, name: &str) -> Option<&ColumnType> {
89        let mut matching = self.columns.iter().filter(|column| column.name == name);
90        let first = matching.next()?;
91        matching.next().is_none().then_some(&first.ty)
92    }
93    fn column_type(&self, position: usize) -> Option<&ColumnType> {
94        self.columns.get(position).map(|column| &column.ty)
95    }
96    fn internal_type(&self, _: InternalColumnRef) -> Option<&ColumnType> {
97        None
98    }
99    fn has_qualifier(&self, _: &str) -> bool {
100        false
101    }
102    fn has_qualified_column(&self, _: &str, _: &str) -> bool {
103        false
104    }
105    fn qualified_column_is_ambiguous(&self, _: &str, _: &str) -> bool {
106        false
107    }
108    fn qualified_type(&self, _: &str, _: &str) -> Option<&ColumnType> {
109        None
110    }
111    fn columns_are_open(&self, _: Option<&str>) -> bool {
112        false
113    }
114    fn physical_schema(&self) -> Option<&RowSchema> {
115        None
116    }
117}
118
119#[cfg(test)]
120mod tests;