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}
29
30/// Immutable namespace-aware lookup for SQL analysis. This contract exposes definitions only; it grants no row access, mutation, locking, or transaction services.
31pub trait AnalysisCatalog: Send + Sync {
32    /// 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.
33    fn prepared_dependency_snapshot(
34        &self,
35        _dependencies: &crate::prepared::dependencies::PreparedAnalysisDependencies,
36    ) -> Result<Option<crate::prepared::dependencies::PreparedDependencySnapshot>, SQLError> {
37        Ok(None)
38    }
39
40    /// 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.
41    fn relation_dependency(
42        &self,
43        _resolution: &RelationNameResolution,
44        _name: &str,
45    ) -> Result<Option<u32>, SQLError> {
46        Ok(None)
47    }
48
49    /// 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.
50    fn effective_search_path(
51        &self,
52        _resolution: &RelationNameResolution,
53    ) -> Result<Option<super::resolution::EffectiveSearchPath>, SQLError> {
54        Ok(None)
55    }
56
57    fn table_resolved(
58        &self,
59        resolution: &RelationNameResolution,
60        name: &str,
61    ) -> Result<Option<TableDefinition>, SQLError>;
62    fn table_name_resolved(
63        &self,
64        resolution: &RelationNameResolution,
65        name: &str,
66    ) -> Result<Option<String>, SQLError>;
67    fn view_resolved(
68        &self,
69        resolution: &RelationNameResolution,
70        name: &str,
71    ) -> Result<Option<ViewDefinition>, SQLError>;
72    fn foreign_table_resolved(
73        &self,
74        resolution: &RelationNameResolution,
75        name: &str,
76    ) -> Result<Option<TableDefinition>, SQLError>;
77    fn sequence_exists(
78        &self,
79        resolution: &RelationNameResolution,
80        name: &str,
81    ) -> Result<bool, SQLError>;
82    fn virtual_relation_schema(
83        &self,
84        resolution: &RelationNameResolution,
85        name: &str,
86    ) -> Result<Option<Vec<(String, ColumnType)>>, SQLError>;
87    fn sql_functions(
88        &self,
89        resolution: &RelationNameResolution,
90        name: &str,
91    ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError>;
92
93    /// A relation that a query cannot open, such as a composite type's relation: its unqualified name and the kind `PostgreSQL`'s detail names.
94    fn unopenable_relation(
95        &self,
96        _resolution: &RelationNameResolution,
97        _name: &str,
98    ) -> Result<Option<UnopenableRelation>, SQLError> {
99        Ok(None)
100    }
101}
102
103/// `errdetail_relkind_not_supported`: the detail naming a relation kind that an operation refuses.
104#[must_use]
105pub fn relkind_not_supported_detail(kind: &str) -> Option<String> {
106    let plural = match kind {
107        "table" => "tables",
108        "index" => "indexes",
109        "sequence" => "sequences",
110        "view" => "views",
111        "materialized view" => "materialized views",
112        "composite type" => "composite types",
113        "foreign table" => "foreign tables",
114        "partitioned table" => "partitioned tables",
115        _ => return None,
116    };
117    Some(format!("This operation is not supported for {plural}."))
118}
119
120/// A relation that holds no rows a query or command can read or change.
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct UnopenableRelation {
123    pub name: String,
124    /// The relation kind in the plural, as `errdetail_relkind_not_supported` names it.
125    pub kinds: &'static str,
126}
127
128impl UnopenableRelation {
129    /// `table_open`'s refusal of a relation that is not a table.
130    #[must_use]
131    pub fn error(&self) -> SQLError {
132        SQLError::Diagnostic {
133            sqlstate: "42809".into(),
134            message: format!("cannot open relation \"{}\"", self.name),
135            detail: Some(format!(
136                "This operation is not supported for {}.",
137                self.kinds
138            )),
139            hint: None,
140        }
141    }
142}
143
144pub type CatalogReadView = Arc<dyn AnalysisCatalog>;