Skip to main content

uqa_sql/catalog/stored_view/
restoration.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Decode and validate durable view definitions and migrated public metadata.
8use super::StoredView;
9use crate::{catalog::roles::guards::RoleCatalogGuards, plan::QueryPlan, RowSchema};
10use std::collections::{BTreeMap, BTreeSet};
11use uqa_core::RelationIdentity;
12
13mod collations;
14pub use collations::view_metadata_dependency_order;
15
16#[derive(serde::Deserialize)]
17#[serde(untagged)]
18pub enum RestoredView {
19    Current(super::StoredViewDefinition),
20    Legacy(QueryPlan),
21}
22
23pub fn validate_restored_view_object_ids(
24    views: &BTreeMap<RelationIdentity, StoredView>,
25) -> Result<(), String> {
26    let mut object_ids = BTreeSet::new();
27    let mut oids = BTreeSet::new();
28    for (relation, view) in views {
29        if !object_ids.insert(view.object_id) {
30            return Err(format!(
31                "view `{}` has a duplicate object identity",
32                relation.qualified_name()
33            ));
34        }
35        if let Some(recorded) = view.catalog_oids {
36            if !recorded.is_valid_for(
37                crate::catalog::relation_oids::RelationOidKind::View,
38                &view.object_id,
39            ) || !recorded.claimed().all(|oid| oids.insert(oid))
40            {
41                return Err(format!(
42                    "view `{}` records invalid catalog OIDs",
43                    relation.qualified_name()
44                ));
45            }
46        }
47    }
48    Ok(())
49}
50
51pub fn validate_restored_view_security(
52    roles: &dyn RoleCatalogGuards,
53    view_name: &str,
54    view: &StoredView,
55) -> Result<(), String> {
56    view.security
57        .validate(view.output_columns.as_deref(), &roles.role_definitions())
58        .map_err(|error| format!("view `{view_name}` has invalid privilege metadata: {error}"))?;
59    Ok(())
60}
61pub fn validate_migrated_view_security(
62    roles: &dyn RoleCatalogGuards,
63    views: &BTreeMap<RelationIdentity, StoredView>,
64) -> Result<(), String> {
65    for (relation, view) in views {
66        let output_columns = view.output_columns.as_deref().ok_or_else(|| {
67            format!(
68                "view `{}` has no public column metadata after catalog migration",
69                relation.qualified_name()
70            )
71        })?;
72        view.security
73            .validate(Some(output_columns), &roles.role_definitions())
74            .map_err(|error| {
75                format!(
76                    "view `{}` has invalid privilege metadata after migration: {error}",
77                    relation.qualified_name()
78                )
79            })?;
80    }
81    Ok(())
82}
83pub fn restored_view_output_columns(schema: &RowSchema) -> Vec<String> {
84    schema
85        .columns()
86        .iter()
87        .enumerate()
88        .map(|(position, column)| schema.public_name(position).unwrap_or(column).to_string())
89        .collect()
90}
91
92#[cfg(test)]
93mod tests;