uqa_sql/schema/
removal.rs1use crate::{
9 ast::{DropKind, DropStmt},
10 catalog::resolution::RelationResolution,
11 SQLError,
12};
13use std::collections::BTreeSet;
14
15pub trait RelationDropCatalog {
16 fn resolve_relation_kind(&self, name: &str) -> Result<RelationResolution, SQLError>;
17 fn resolve_age_label_relation_name(&self, name: &str) -> Result<Option<String>, SQLError>;
18}
19pub trait ForeignTableDropDependencies {
20 fn views_depending_on_relation(&self, name: &str) -> Result<Vec<String>, SQLError>;
21 fn rules_depending_on_relations(
22 &self,
23 names: &[String],
24 ) -> Result<Vec<(uqa_core::RelationIdentity, String)>, SQLError>;
25 fn sequence_external_dependents_for_owner_drop(
26 &self,
27 name: &str,
28 targets: &BTreeSet<String>,
29 ) -> Result<Vec<String>, SQLError>;
30}
31pub fn validate_drop_table_label_target(
32 catalog: &dyn RelationDropCatalog,
33 name: &str,
34) -> Result<(), SQLError> {
35 if let Some(canonical) = catalog.resolve_age_label_relation_name(name)? {
36 let relation =
37 uqa_core::RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
38 SQLError::Internal(format!(
39 "resolve AGE label relation `{canonical}` for DROP TABLE: {error}"
40 ))
41 })?;
42 return Err(SQLError::Routine {
43 sqlstate: "2BP01".into(),
44 message: format!(
45 "table \"{}\" is for label \"{}\"",
46 relation.name, relation.name
47 ),
48 });
49 }
50 Ok(())
51}
52
53pub fn drop_relation_kind(kind: DropKind) -> &'static str {
54 match kind {
55 DropKind::Table => "table",
56 DropKind::ForeignTable => "foreign table",
57 DropKind::View => "view",
58 DropKind::MaterializedView => "materialized view",
59 DropKind::Sequence => "sequence",
60 DropKind::Index => "index",
61 DropKind::Schema => "schema",
62 DropKind::Domain => "domain",
63 }
64}
65
66pub fn bind_relation_drop_target(
68 catalog: &dyn RelationDropCatalog,
69 name: &str,
70 kind: DropKind,
71 if_exists: bool,
72 notice: &mut dyn FnMut(&str),
73) -> Result<Option<String>, SQLError> {
74 if kind == DropKind::Table {
75 validate_drop_table_label_target(catalog, name)?;
76 }
77 let expected = drop_relation_kind(kind);
78 let (_, local) =
79 uqa_core::RelationIdentity::parse_reference(name).map_err(SQLError::Internal)?;
80 match catalog.resolve_relation_kind(name)? {
81 RelationResolution::Found(canonical, found) if found == expected => Ok(Some(canonical)),
82 RelationResolution::Found(_, _) => Err(SQLError::Routine {
83 sqlstate: "42809".into(),
84 message: format!("\"{local}\" is not a {expected}"),
85 }),
86 RelationResolution::MissingSchema(schema) if if_exists => {
87 notice(&format!("schema \"{schema}\" does not exist, skipping"));
88 Ok(None)
89 }
90 RelationResolution::MissingRelation if if_exists => {
91 notice(&format!("{expected} \"{local}\" does not exist, skipping"));
92 Ok(None)
93 }
94 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
95 sqlstate: "3F000".into(),
96 message: format!("schema \"{schema}\" does not exist"),
97 }),
98 RelationResolution::MissingRelation => Err(SQLError::Routine {
99 sqlstate: if matches!(kind, DropKind::ForeignTable | DropKind::Index) {
100 "42704"
101 } else {
102 "42P01"
103 }
104 .into(),
105 message: format!("{expected} \"{local}\" does not exist"),
106 }),
107 }
108}
109
110pub fn bind_relation_drop_targets(
111 catalog: &dyn RelationDropCatalog,
112 stmt: &DropStmt,
113 notice: &mut dyn FnMut(&str),
114) -> Result<Vec<String>, SQLError> {
115 let mut targets = Vec::new();
116 let mut seen = BTreeSet::new();
117 for name in &stmt.names {
118 if let Some(canonical) =
119 bind_relation_drop_target(catalog, name, stmt.kind, stmt.if_exists, notice)?
120 {
121 if seen.insert(canonical.clone()) {
122 targets.push(canonical);
123 }
124 }
125 }
126 Ok(targets)
127}
128
129pub fn foreign_table_drop_dependents(
130 catalog: &dyn ForeignTableDropDependencies,
131 foreign_tables: &[String],
132 owned_sequences: &BTreeSet<String>,
133 target_names: &BTreeSet<String>,
134) -> Result<BTreeSet<String>, SQLError> {
135 let mut dependents = std::collections::BTreeSet::new();
136 for table in foreign_tables {
137 dependents.extend(
138 catalog
139 .views_depending_on_relation(table)?
140 .into_iter()
141 .map(|view| format!("view {view}")),
142 );
143 }
144 dependents.extend(
145 catalog
146 .rules_depending_on_relations(foreign_tables)?
147 .into_iter()
148 .map(|(table, rule)| format!("rule {rule} on table {}", table.qualified_name())),
149 );
150 for sequence in owned_sequences {
151 dependents
152 .extend(catalog.sequence_external_dependents_for_owner_drop(sequence, target_names)?);
153 }
154 Ok(dependents)
155}
156
157pub mod hierarchy;
158pub mod tables;
159
160#[cfg(test)]
161mod tests;