1use 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_targets(
32 catalog: &dyn RelationDropCatalog,
33 stmt: &DropStmt,
34) -> Result<(), SQLError> {
35 if stmt.kind == DropKind::Table {
36 for name in &stmt.names {
37 if let Some(canonical) = catalog.resolve_age_label_relation_name(name)? {
38 let relation =
39 uqa_core::RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
40 SQLError::Internal(format!(
41 "resolve AGE label relation `{canonical}` for DROP TABLE: {error}"
42 ))
43 })?;
44 return Err(SQLError::Routine {
45 sqlstate: "2BP01".into(),
46 message: format!(
47 "table \"{}\" is for label \"{}\"",
48 relation.name, relation.name
49 ),
50 });
51 }
52 }
53 }
54 Ok(())
55}
56pub fn bind_table_drop_targets(
57 catalog: &dyn RelationDropCatalog,
58 stmt: &DropStmt,
59 notice: &mut dyn FnMut(&str),
60) -> Result<Vec<String>, SQLError> {
61 let mut tables = Vec::new();
62 for name in &stmt.names {
63 let (_, local) =
64 uqa_core::RelationIdentity::parse_reference(name).map_err(SQLError::Internal)?;
65 match catalog.resolve_relation_kind(name)? {
66 RelationResolution::Found(canonical, "table") => tables.push(canonical),
67 RelationResolution::Found(_, _) => {
68 return Err(SQLError::Routine {
69 sqlstate: "42809".into(),
70 message: format!("\"{local}\" is not a table"),
71 });
72 }
73 RelationResolution::MissingSchema(schema) if stmt.if_exists => {
74 notice(&format!("schema \"{schema}\" does not exist, skipping"));
75 }
76 RelationResolution::MissingRelation if stmt.if_exists => {
77 notice(&format!("table \"{local}\" does not exist, skipping"));
78 }
79 RelationResolution::MissingSchema(schema) => {
80 return Err(SQLError::Routine {
81 sqlstate: "3F000".into(),
82 message: format!("schema \"{schema}\" does not exist"),
83 });
84 }
85 RelationResolution::MissingRelation => {
86 return Err(SQLError::Routine {
87 sqlstate: "42P01".into(),
88 message: format!("table \"{local}\" does not exist"),
89 });
90 }
91 }
92 }
93 Ok(tables)
94}
95
96pub fn bind_foreign_table_drop_targets(
97 catalog: &dyn RelationDropCatalog,
98 stmt: &DropStmt,
99 notice: &mut dyn FnMut(&str),
100) -> Result<Vec<String>, SQLError> {
101 let mut foreign_tables = Vec::new();
102 let mut seen = std::collections::BTreeSet::new();
103 for name in &stmt.names {
104 match catalog.resolve_relation_kind(name)? {
105 RelationResolution::Found(canonical, "foreign table") => {
106 if seen.insert(canonical.clone()) {
107 foreign_tables.push(canonical);
108 }
109 }
110 RelationResolution::Found(_, _) => {
111 return Err(SQLError::Routine {
112 sqlstate: "42809".into(),
113 message: format!("\"{name}\" is not a foreign table"),
114 });
115 }
116 RelationResolution::MissingSchema(schema) if stmt.if_exists => {
117 notice(&format!("schema \"{schema}\" does not exist, skipping"));
118 }
119 RelationResolution::MissingRelation if stmt.if_exists => {
120 notice(&format!(
121 "foreign table \"{name}\" does not exist, skipping"
122 ));
123 }
124 RelationResolution::MissingSchema(schema) => {
125 return Err(SQLError::Routine {
126 sqlstate: "3F000".into(),
127 message: format!("schema \"{schema}\" does not exist"),
128 });
129 }
130 RelationResolution::MissingRelation => {
131 return Err(SQLError::Routine {
132 sqlstate: "42P01".into(),
133 message: format!("foreign table \"{name}\" does not exist"),
134 });
135 }
136 }
137 }
138 Ok(foreign_tables)
139}
140
141pub fn bind_view_drop_targets(
142 catalog: &dyn RelationDropCatalog,
143 stmt: &DropStmt,
144) -> Result<(Vec<String>, &'static str), SQLError> {
145 let expected_kind = if stmt.kind == DropKind::View {
146 "view"
147 } else {
148 "materialized view"
149 };
150 let command = if stmt.kind == DropKind::View {
151 "DROP VIEW"
152 } else {
153 "DROP MATERIALIZED VIEW"
154 };
155 let mut views = Vec::new();
156 for name in &stmt.names {
157 match catalog.resolve_relation_kind(name)?.into_found() {
158 Some((canonical, kind)) if kind == expected_kind => views.push(canonical),
159 Some((canonical, kind)) => {
160 return Err(SQLError::Routine {
161 sqlstate: "42809".into(),
162 message: format!(
163 "{command}: relation `{canonical}` is a {kind}, not a {expected_kind}"
164 ),
165 });
166 }
167 None if stmt.if_exists => {}
168 None => {
169 return Err(SQLError::Routine {
170 sqlstate: "42P01".into(),
171 message: format!("{command}: relation `{name}` does not exist"),
172 });
173 }
174 }
175 }
176 Ok((views, expected_kind))
177}
178
179pub fn bind_sequence_drop_targets(
180 catalog: &dyn RelationDropCatalog,
181 stmt: &DropStmt,
182 notice: &mut dyn FnMut(&str),
183) -> Result<Vec<String>, SQLError> {
184 let mut sequences = Vec::new();
185 let mut seen = std::collections::BTreeSet::new();
186 for name in &stmt.names {
187 match catalog.resolve_relation_kind(name)? {
188 RelationResolution::Found(canonical, "sequence") => {
189 if seen.insert(canonical.clone()) {
190 sequences.push(canonical);
191 }
192 }
193 RelationResolution::Found(_canonical, _kind) => {
194 return Err(SQLError::Routine {
195 sqlstate: "42809".into(),
196 message: format!("\"{name}\" is not a sequence"),
197 });
198 }
199 RelationResolution::MissingRelation | RelationResolution::MissingSchema(_)
200 if stmt.if_exists =>
201 {
202 notice(&format!("sequence \"{name}\" does not exist, skipping"));
203 }
204 RelationResolution::MissingSchema(schema) => {
205 return Err(SQLError::Routine {
206 sqlstate: "3F000".into(),
207 message: format!("schema \"{schema}\" does not exist"),
208 });
209 }
210 RelationResolution::MissingRelation => {
211 return Err(SQLError::Routine {
212 sqlstate: "42P01".into(),
213 message: format!("sequence \"{name}\" does not exist"),
214 });
215 }
216 }
217 }
218 Ok(sequences)
219}
220pub fn foreign_table_drop_dependents(
221 catalog: &dyn ForeignTableDropDependencies,
222 foreign_tables: &[String],
223 owned_sequences: &BTreeSet<String>,
224 target_names: &BTreeSet<String>,
225) -> Result<BTreeSet<String>, SQLError> {
226 let mut dependents = std::collections::BTreeSet::new();
227 for table in foreign_tables {
228 dependents.extend(
229 catalog
230 .views_depending_on_relation(table)?
231 .into_iter()
232 .map(|view| format!("view {view}")),
233 );
234 }
235 dependents.extend(
236 catalog
237 .rules_depending_on_relations(foreign_tables)?
238 .into_iter()
239 .map(|(table, rule)| format!("rule {rule} on table {}", table.qualified_name())),
240 );
241 for sequence in owned_sequences {
242 dependents
243 .extend(catalog.sequence_external_dependents_for_owner_drop(sequence, target_names)?);
244 }
245 Ok(dependents)
246}
247
248pub mod hierarchy;
249pub mod tables;