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(
34                crate::catalog::relation_oids::RelationOidKind::View,
35                &view.object_id,
36            ) || !recorded.claimed().all(|oid| oids.insert(oid))
37            {
38                return Err(format!(
39                    "view `{}` records invalid catalog OIDs",
40                    relation.qualified_name()
41                ));
42            }
43        }
44    }
45    Ok(())
46}
47
48pub fn validate_restored_view_security(
49    roles: &dyn RoleCatalogGuards,
50    view_name: &str,
51    view: &StoredView,
52) -> Result<(), String> {
53    view.security
54        .validate(view.output_columns.as_deref(), &roles.role_definitions())
55        .map_err(|error| format!("view `{view_name}` has invalid privilege metadata: {error}"))?;
56    Ok(())
57}
58pub fn validate_migrated_view_security(
59    roles: &dyn RoleCatalogGuards,
60    views: &BTreeMap<RelationIdentity, StoredView>,
61) -> Result<(), String> {
62    for (relation, view) in views {
63        let output_columns = view.output_columns.as_deref().ok_or_else(|| {
64            format!(
65                "view `{}` has no public column metadata after catalog migration",
66                relation.qualified_name()
67            )
68        })?;
69        view.security
70            .validate(Some(output_columns), &roles.role_definitions())
71            .map_err(|error| {
72                format!(
73                    "view `{}` has invalid privilege metadata after migration: {error}",
74                    relation.qualified_name()
75                )
76            })?;
77    }
78    Ok(())
79}
80pub fn restored_view_output_columns(schema: &RowSchema) -> Vec<String> {
81    schema
82        .columns()
83        .iter()
84        .enumerate()
85        .map(|(position, column)| schema.public_name(position).unwrap_or(column).to_string())
86        .collect()
87}
88
89#[cfg(test)]
90mod tests;