uqa_sql/catalog/stored_view/
removal.rs1use super::StoredView;
9use crate::{
10 binding::view_dependencies::query_plan_references_relation,
11 catalog::security::view_ownership::{self, ViewOwnershipContext},
12 SQLError,
13};
14use std::collections::BTreeMap;
15use uqa_core::RelationIdentity;
16
17pub fn direct_view_drop_target(target: Option<(String, &str)>) -> Result<Option<String>, SQLError> {
18 match target {
19 Some((canonical, "view")) => Ok(Some(canonical)),
20 Some((canonical, kind)) => Err(SQLError::Unsupported(format!(
21 "DROP VIEW: relation `{canonical}` is a {kind}, not a view"
22 ))),
23 None => Ok(None),
24 }
25}
26
27pub fn ensure_view_drop_authorities(
28 ownership: ViewOwnershipContext<'_>,
29 names: &[String],
30 views: &BTreeMap<RelationIdentity, StoredView>,
31) -> Result<(), SQLError> {
32 for name in names {
33 let relation = RelationIdentity::from_legacy_name(name).map_err(|error| {
34 SQLError::Internal(format!("resolve DROP VIEW target `{name}`: {error}"))
35 })?;
36 let view = views.get(&relation).ok_or_else(|| {
37 SQLError::Internal(format!("view `{name}` disappeared before owner check"))
38 })?;
39 view_ownership::ensure_view_drop_authority(ownership, name, view)?;
40 }
41 Ok(())
42}
43
44pub fn ensure_no_rule_dependents(
45 names: &[String],
46 dependent_rules: Vec<(RelationIdentity, String)>,
47) -> Result<(), SQLError> {
48 if !dependent_rules.is_empty() {
49 return Err(SQLError::Routine {
50 sqlstate: "2BP01".into(),
51 message: format!(
52 "cannot drop view {} because other objects depend on it: {}",
53 names.join(", "),
54 dependent_rules
55 .into_iter()
56 .map(|(table, rule)| format!("rule {rule} on table {}", table.qualified_name()))
57 .collect::<Vec<_>>()
58 .join(", ")
59 ),
60 });
61 }
62 Ok(())
63}
64
65pub fn ensure_no_view_dependents(name: &str, dependents: &[String]) -> Result<(), SQLError> {
66 if !dependents.is_empty() {
67 return Err(SQLError::Unsupported(format!(
68 "DROP VIEW `{name}` rejected: dependent view(s) `{}` still reference it",
69 dependents.join("`, `")
70 )));
71 }
72 Ok(())
73}
74
75pub fn temporary_view_dependency_layers(
76 canonical_name: &str,
77 target: RelationIdentity,
78 views: &BTreeMap<RelationIdentity, StoredView>,
79) -> Result<Vec<Vec<RelationIdentity>>, String> {
80 let empty_ctes = std::collections::BTreeSet::new();
81 let mut targets = std::collections::BTreeSet::from([target]);
82 let mut layers = Vec::new();
83 loop {
84 let layer = views
85 .iter()
86 .filter(|(relation, _)| !targets.contains(*relation))
87 .filter(|(_, view)| {
88 targets.iter().any(|target| {
89 query_plan_references_relation(&view.query, target, &empty_ctes)
90 })
91 })
92 .map(|(relation, view)| {
93 if view.persistence != crate::ast::RelationPersistence::Temporary {
94 return Err(format!(
95 "temporary relation `{canonical_name}` has non-temporary dependent view `{}`",
96 relation.qualified_name()
97 ));
98 }
99 Ok(relation.clone())
100 })
101 .collect::<Result<Vec<_>, String>>()?;
102 if layer.is_empty() {
103 break;
104 }
105 targets.extend(layer.iter().cloned());
106 layers.push(layer);
107 }
108 Ok(layers)
109}
110
111#[cfg(test)]
112mod tests;