uqa_sql/schema/
removal.rs1use crate::{ast::DropKind, catalog::resolution::RelationResolution, SQLError};
9
10pub trait RelationDropCatalog {
11 fn resolve_relation_kind(&self, name: &str) -> Result<RelationResolution, SQLError>;
12 fn resolve_age_label_relation_name(&self, name: &str) -> Result<Option<String>, SQLError>;
13}
14pub fn validate_drop_table_label_target(
15 catalog: &dyn RelationDropCatalog,
16 name: &str,
17) -> Result<(), SQLError> {
18 if let Some(canonical) = catalog.resolve_age_label_relation_name(name)? {
19 let relation =
20 uqa_core::RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
21 SQLError::Internal(format!(
22 "resolve AGE label relation `{canonical}` for DROP TABLE: {error}"
23 ))
24 })?;
25 return Err(SQLError::Routine {
26 sqlstate: "2BP01".into(),
27 message: format!(
28 "table \"{}\" is for label \"{}\"",
29 relation.name, relation.name
30 ),
31 });
32 }
33 Ok(())
34}
35
36pub fn drop_relation_kind(kind: DropKind) -> &'static str {
37 match kind {
38 DropKind::Table => "table",
39 DropKind::ForeignTable => "foreign table",
40 DropKind::ForeignServer => "server",
41 DropKind::ForeignWrapper => "foreign-data wrapper",
42 DropKind::View => "view",
43 DropKind::MaterializedView => "materialized view",
44 DropKind::Sequence => "sequence",
45 DropKind::Index => "index",
46 DropKind::Schema => "schema",
47 DropKind::Domain => "domain",
48 DropKind::Type => "type",
49 }
50}
51
52pub fn wrong_drop_kind_error(local: &str, expected: &str, found: &str) -> SQLError {
54 let article = if expected == "index" { "an" } else { "a" };
55 let hint = match found {
56 "table" => Some("Use DROP TABLE to remove a table."),
57 "sequence" => Some("Use DROP SEQUENCE to remove a sequence."),
58 "view" => Some("Use DROP VIEW to remove a view."),
59 "materialized view" => Some("Use DROP MATERIALIZED VIEW to remove a materialized view."),
60 "index" => Some("Use DROP INDEX to remove an index."),
61 "foreign table" => Some("Use DROP FOREIGN TABLE to remove a foreign table."),
62 "type" | "composite type" => Some("Use DROP TYPE to remove a type."),
63 _ => None,
64 };
65 SQLError::Diagnostic {
66 sqlstate: "42809".into(),
67 message: format!("\"{local}\" is not {article} {expected}"),
68 detail: None,
69 hint: hint.map(str::to_string),
70 }
71}
72
73pub fn bind_relation_drop_target(
75 catalog: &dyn RelationDropCatalog,
76 name: &str,
77 kind: DropKind,
78 if_exists: bool,
79 notice: &mut dyn FnMut(&str),
80) -> Result<Option<String>, SQLError> {
81 if kind == DropKind::Table {
82 validate_drop_table_label_target(catalog, name)?;
83 }
84 let expected = drop_relation_kind(kind);
85 let (_, local) =
86 uqa_core::RelationIdentity::parse_reference(name).map_err(SQLError::Internal)?;
87 match catalog.resolve_relation_kind(name)? {
88 RelationResolution::Found(canonical, found) if found == expected => Ok(Some(canonical)),
89 RelationResolution::Found(_, found) => Err(wrong_drop_kind_error(&local, expected, found)),
90 RelationResolution::MissingSchema(schema) if if_exists => {
91 notice(&format!("schema \"{schema}\" does not exist, skipping"));
92 Ok(None)
93 }
94 RelationResolution::MissingRelation if if_exists => {
95 notice(&format!("{expected} \"{local}\" does not exist, skipping"));
96 Ok(None)
97 }
98 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
99 sqlstate: "3F000".into(),
100 message: format!("schema \"{schema}\" does not exist"),
101 }),
102 RelationResolution::MissingRelation => Err(SQLError::Routine {
103 sqlstate: if matches!(kind, DropKind::ForeignTable | DropKind::Index) {
104 "42704"
105 } else {
106 "42P01"
107 }
108 .into(),
109 message: format!("{expected} \"{local}\" does not exist"),
110 }),
111 }
112}
113
114pub mod hierarchy;
115pub mod tables;
116
117#[cfg(test)]
118mod tests;