Skip to main content

uqa_sql/catalog/
analysis.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Catalog descriptions required by static SQL binding.
8
9use super::resolution::RelationNameResolution;
10use crate::ast::ColumnDef;
11use crate::plan::QueryPlan;
12use crate::routines::SQLUserFunction;
13use crate::{ColumnType, SQLError};
14use std::sync::Arc;
15
16#[derive(Clone)]
17pub struct TableDefinition {
18    pub columns: Arc<Vec<ColumnDef>>,
19    pub columns_declared: bool,
20}
21
22#[derive(Clone)]
23pub struct ViewDefinition {
24    pub query: QueryPlan,
25    pub output_columns: Option<Vec<String>>,
26    pub materialized: bool,
27    pub materialized_column_types: Vec<Option<ColumnType>>,
28    pub materialized_column_collations: Option<Vec<u32>>,
29}
30
31/// Immutable namespace-aware lookup for SQL analysis. This contract exposes definitions only; it grants no row access, mutation, locking, or transaction services.
32pub trait AnalysisCatalog: Send + Sync {
33    /// The actual row type of a selected catalog relation. Anonymous query sources and catalog fixtures without identities leave this unavailable.
34    fn relation_row_type(
35        &self,
36        _resolution: &RelationNameResolution,
37        _name: &str,
38    ) -> Result<Option<crate::ast::CompositeTypeReference>, SQLError> {
39        Ok(None)
40    }
41
42    /// Exact revisions of the requested identities in this catalog view. Return `None` only when the catalog has no revision capability; within an available snapshot, include every requested identity and use a missing value for an absent object.
43    fn prepared_dependency_snapshot(
44        &self,
45        _dependencies: &crate::prepared::dependencies::PreparedAnalysisDependencies,
46    ) -> Result<Option<crate::prepared::dependencies::PreparedDependencySnapshot>, SQLError> {
47        Ok(None)
48    }
49
50    /// The OID of a relation successfully selected in this same immutable namespace. The binder calls this after selecting a catalog relation, never for a CTE or an unsuccessful lookup candidate. Catalog fixtures without identities may leave this unavailable.
51    fn relation_dependency(
52        &self,
53        _resolution: &RelationNameResolution,
54        _name: &str,
55    ) -> Result<Option<u32>, SQLError> {
56        Ok(None)
57    }
58
59    /// The schemas that exist and that the analyzing role may use, including implicit namespaces, and its creation namespace. Catalogs without namespace state leave this unavailable.
60    fn effective_search_path(
61        &self,
62        _resolution: &RelationNameResolution,
63    ) -> Result<Option<super::resolution::EffectiveSearchPath>, SQLError> {
64        Ok(None)
65    }
66
67    fn table_resolved(
68        &self,
69        resolution: &RelationNameResolution,
70        name: &str,
71    ) -> Result<Option<TableDefinition>, SQLError>;
72    fn table_name_resolved(
73        &self,
74        resolution: &RelationNameResolution,
75        name: &str,
76    ) -> Result<Option<String>, SQLError>;
77    fn view_resolved(
78        &self,
79        resolution: &RelationNameResolution,
80        name: &str,
81    ) -> Result<Option<ViewDefinition>, SQLError>;
82    fn foreign_table_resolved(
83        &self,
84        resolution: &RelationNameResolution,
85        name: &str,
86    ) -> Result<Option<TableDefinition>, SQLError>;
87    fn sequence_exists(
88        &self,
89        resolution: &RelationNameResolution,
90        name: &str,
91    ) -> Result<bool, SQLError>;
92    fn virtual_relation_schema(
93        &self,
94        resolution: &RelationNameResolution,
95        name: &str,
96    ) -> Result<Option<Vec<(String, ColumnType)>>, SQLError>;
97    fn sql_functions(
98        &self,
99        resolution: &RelationNameResolution,
100        name: &str,
101    ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError>;
102
103    /// A relation that a query cannot open, such as a composite type's relation: its unqualified name and the kind `PostgreSQL`'s detail names.
104    fn unopenable_relation(
105        &self,
106        _resolution: &RelationNameResolution,
107        _name: &str,
108    ) -> Result<Option<UnopenableRelation>, SQLError> {
109        Ok(None)
110    }
111}
112
113/// `errdetail_relkind_not_supported`: the detail naming a relation kind that an operation refuses.
114#[must_use]
115pub fn relkind_not_supported_detail(kind: &str) -> Option<String> {
116    let plural = match kind {
117        "table" => "tables",
118        "index" => "indexes",
119        "sequence" => "sequences",
120        "view" => "views",
121        "materialized view" => "materialized views",
122        "composite type" => "composite types",
123        "foreign table" => "foreign tables",
124        "partitioned table" => "partitioned tables",
125        _ => return None,
126    };
127    Some(format!("This operation is not supported for {plural}."))
128}
129
130/// A relation that holds no rows a query or command can read or change.
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct UnopenableRelation {
133    pub name: String,
134    /// The relation kind in the plural, as `errdetail_relkind_not_supported` names it.
135    pub kinds: &'static str,
136}
137
138impl UnopenableRelation {
139    /// `table_open`'s refusal of a relation that is not a table.
140    #[must_use]
141    pub fn error(&self) -> SQLError {
142        SQLError::Diagnostic {
143            sqlstate: "42809".into(),
144            message: format!("cannot open relation \"{}\"", self.name),
145            detail: Some(format!(
146                "This operation is not supported for {}.",
147                self.kinds
148            )),
149            hint: None,
150        }
151    }
152}
153
154pub type CatalogReadView = Arc<dyn AnalysisCatalog>;