uqa_sql/binding/
catalog_sources.rs1use crate::ast::OperatorJoinRelations;
10use crate::RowSchema;
11use crate::SQLError;
12
13use crate::catalog::analysis::CatalogReadView;
14use crate::catalog::resolution::RelationNameResolution;
15
16use super::analysis;
17
18pub(super) fn operator_join_relation_schemas(
20 catalog: &CatalogReadView,
21 resolution: &RelationNameResolution,
22 relations: Option<&OperatorJoinRelations>,
23) -> Result<(RowSchema, RowSchema), SQLError> {
24 let relations = relations.ok_or_else(|| {
25 SQLError::TypeMismatch("operator join requires left and right table identifiers".into())
26 })?;
27 Ok((
28 relation_schema(catalog, resolution, &relations.left, "left")?,
29 relation_schema(catalog, resolution, &relations.right, "right")?,
30 ))
31}
32
33fn relation_schema(
34 catalog: &CatalogReadView,
35 resolution: &RelationNameResolution,
36 relation: &str,
37 side: &str,
38) -> Result<RowSchema, SQLError> {
39 let resolved = catalog
40 .table_name_resolved(resolution, relation)?
41 .ok_or_else(|| SQLError::UnknownTable(relation.to_string()))?;
42 let identity = crate::RelationIdentity::from_legacy_name(&resolved).map_err(|error| {
43 SQLError::Internal(format!(
44 "decode operator join {side} relation `{resolved}` schema: {error}"
45 ))
46 })?;
47 let table = catalog
48 .table_resolved(resolution, &resolved)?
49 .ok_or_else(|| SQLError::UnknownTable(relation.to_string()))?;
50 let columns = table
51 .columns
52 .iter()
53 .map(|column| column.name.clone())
54 .collect();
55 let types = table
56 .columns
57 .iter()
58 .map(|column| Some(column.ty.clone()))
59 .collect();
60 let schema = RowSchema::with_qualified_types(&identity.name, columns, types);
61 Ok(analysis::with_table_pseudo_columns(&schema, &identity.name))
62}
63
64pub fn user_function_output_columns(
66 catalog: &dyn crate::catalog::analysis::AnalysisCatalog,
67 resolution: &RelationNameResolution,
68 name: &str,
69) -> Result<Option<Vec<String>>, SQLError> {
70 let Some(overloads) = catalog.sql_functions(resolution, name)? else {
71 return Ok(None);
72 };
73 for function in &overloads {
74 if let Some(columns) = crate::semantics::user_function_output_columns_for(function) {
75 return Ok(Some(columns));
76 }
77 }
78 Ok(None)
79}
80
81#[cfg(test)]
82mod tests {
83 use std::collections::BTreeMap;
84
85 use crate::ast::{ColumnDef, ColumnType};
86
87 use super::operator_join_relation_schemas;
88 use crate::RelationIdentity;
89
90 #[test]
91 fn relation_schema_binds_against_catalog_fixture_without_engine() {
92 let column = ColumnDef {
93 name: "id".into(),
94 ty: ColumnType::BigInteger,
95 object_id: None,
96 missing_value: None,
97 primary_key: true,
98 not_null: true,
99 not_null_explicit: false,
100 not_null_name: None,
101 not_null_validated: true,
102 not_null_no_inherit: false,
103 not_null_is_local: true,
104 auto_increment: None,
105 unique: false,
106 default: None,
107 generated: None,
108 check: None,
109 check_name: None,
110 check_enforced: true,
111 check_validated: true,
112 check_no_inherit: false,
113 check_is_local: true,
114 check_object_id: None,
115 references: None,
116 };
117 let catalog = crate::binding::fixture::catalog(BTreeMap::from([
118 (
119 RelationIdentity::new("app", "documents"),
120 crate::binding::fixture::table_definition(vec![column.clone()]),
121 ),
122 (
123 RelationIdentity::new("app", "archive"),
124 crate::binding::fixture::table_definition(vec![column]),
125 ),
126 ]));
127 let resolution =
128 crate::binding::fixture::resolution(vec!["app".into()], "pg_temp_fixture".into());
129 let relations = crate::ast::OperatorJoinRelations {
130 left: "documents".into(),
131 right: "archive".into(),
132 };
133 let (left, right) =
134 operator_join_relation_schemas(&catalog, &resolution, Some(&relations)).unwrap();
135 assert!(left.has_qualified_column("documents", "id"));
136 assert!(right.has_qualified_column("archive", "id"));
137 assert_eq!(left.column_type(0), Some(&ColumnType::BigInteger));
138 assert_eq!(right.column_type(0), Some(&ColumnType::BigInteger));
139 }
140}