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