Skip to main content

uqa_sql/catalog/
stored_view.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use super::security::BoundTableSecurity;
8use std::ops::{Deref, DerefMut};
9
10/// One bound view query together with the fixed public column names captured when the view was created. `None` exists only while the catalog-opening migration reads formats written before column metadata was persisted.
11#[derive(Debug, Clone, serde::Serialize)]
12pub struct StoredView {
13    /// Security is loaded independently from the durable catalog row.
14    #[serde(skip)]
15    pub security: BoundTableSecurity,
16    #[serde(flatten)]
17    pub definition: StoredViewDefinition,
18}
19
20/// Query-definition JSON carries no authorization state. Restoration supplies validated security before publishing a view.
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
22pub struct StoredViewDefinition {
23    /// Stable logical relation identity. Renames and replacement preserve it; a zero value marks a legacy catalog row upgraded during initial open.
24    #[serde(default)]
25    pub object_id: [u8; 16],
26    pub query: crate::plan::QueryPlan,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub output_columns: Option<Vec<String>>,
29    #[serde(default)]
30    pub persistence: crate::ast::RelationPersistence,
31    #[serde(default, skip_serializing_if = "Vec::is_empty")]
32    pub options: Vec<(String, String)>,
33    #[serde(default)]
34    pub kind: StoredViewKind,
35    #[serde(default, skip_serializing_if = "Vec::is_empty")]
36    pub materialized_rows: Vec<crate::ResultRow>,
37    #[serde(default, skip_serializing_if = "Vec::is_empty")]
38    pub materialized_column_types: Vec<Option<crate::ast::ColumnType>>,
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub materialized_column_collations: Option<Vec<u32>>,
41    #[serde(default = "default_view_populated")]
42    pub populated: bool,
43    /// The view's public OIDs, allocated when it was created; replacement keeps them. Views created before OIDs were recorded derive them from their identity.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub catalog_oids: Option<super::relation_oids::RelationCatalogOids>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub row_type_array_name: Option<String>,
48}
49
50impl StoredViewDefinition {
51    /// Initial-open migration recovers missing collations from the stored query without changing its fixed column types or values.
52    pub fn restore_materialized_collations(
53        &mut self,
54        query_schema: &crate::RowSchema,
55    ) -> Result<(), crate::SQLError> {
56        if self.materialized_column_collations.is_some() {
57            return self.materialized_schema().map(|_| ());
58        }
59        if query_schema.column_types() != self.materialized_column_types {
60            return Err(crate::SQLError::Internal("materialized view query types differ from its stored column types during migration".into()));
61        }
62        crate::schema::table_creation::create_table_as_columns(
63            query_schema,
64            self.output_columns.as_deref().unwrap_or_default(),
65        )?;
66        let collations = (0..query_schema.len())
67            .map(|index| query_schema.column_collation(index).unwrap_or(0))
68            .collect::<Vec<_>>();
69        super::view::materialized_view_schema(
70            self.output_columns.as_deref().unwrap_or_default(),
71            &self.materialized_column_types,
72            Some(&collations),
73        )?;
74        self.materialized_column_collations = Some(collations);
75        Ok(())
76    }
77
78    pub fn materialized_schema(&self) -> Result<crate::RowSchema, crate::SQLError> {
79        super::view::materialized_view_schema(
80            self.output_columns.as_deref().unwrap_or_default(),
81            &self.materialized_column_types,
82            self.materialized_column_collations.as_deref(),
83        )
84    }
85
86    /// The view's public OIDs: the recorded ones, or those its identity derives.
87    #[must_use]
88    pub fn relation_oids(&self) -> super::relation_oids::RelationCatalogOids {
89        self.catalog_oids.unwrap_or_else(|| {
90            super::relation_oids::RelationCatalogOids::legacy(
91                super::relation_oids::RelationOidKind::View,
92                &self.object_id,
93            )
94        })
95    }
96}
97
98use super::view::StoredViewKind;
99
100const fn default_view_populated() -> bool {
101    true
102}
103
104impl Deref for StoredView {
105    type Target = StoredViewDefinition;
106    fn deref(&self) -> &Self::Target {
107        &self.definition
108    }
109}
110
111impl DerefMut for StoredView {
112    fn deref_mut(&mut self) -> &mut Self::Target {
113        &mut self.definition
114    }
115}
116
117impl StoredView {
118    pub fn security(&self) -> BoundTableSecurity {
119        self.security.clone()
120    }
121
122    pub fn set_security(&mut self, security: BoundTableSecurity) {
123        self.security = security;
124    }
125
126    pub fn security_invoker(&self) -> bool {
127        self.options.iter().any(|(name, value)| {
128            name == "security_invoker" && matches!(value.as_str(), "true" | "on" | "yes" | "1")
129        })
130    }
131}
132
133impl StoredView {
134    pub fn rewrite_definition(&self) -> crate::catalog::view::ViewRewriteDefinition {
135        crate::catalog::view::ViewRewriteDefinition {
136            query: self.query.clone(),
137            output_columns: self.output_columns.clone(),
138            options: self.options.clone(),
139            kind: self.kind,
140            materialized_column_types: self.materialized_column_types.clone(),
141            materialized_column_collations: self.materialized_column_collations.clone(),
142        }
143    }
144    pub fn row_schema(
145        &self,
146        routines: &dyn crate::routines::RoutineResolution,
147        catalog: super::analysis::CatalogReadView,
148        resolution: super::resolution::RelationNameResolution,
149    ) -> Result<crate::RowSchema, crate::SQLError> {
150        self.rewrite_definition()
151            .row_schema(routines, catalog, resolution)
152    }
153}
154
155pub mod dependencies;
156
157pub mod restoration;
158
159pub mod references;
160pub mod removal;