uqa_sql/schema/
relation_alteration.rs1use crate::{
9 ast::{
10 AlterForeignTableAction, AlterForeignTableStmt, AlterViewAction, AlterViewKind,
11 AlterViewStmt,
12 },
13 catalog::resolution::{resolve_relation_rename_source, RelationResolution},
14 SQLError,
15};
16use uqa_core::RelationIdentity;
17
18pub trait RelationAlterNames {
19 fn resolve_relation_kind(&self, name: &str) -> Result<RelationResolution, SQLError>;
20 fn relation_kind_at(&self, name: &str) -> Result<Option<&'static str>, String>;
21}
22
23pub struct ViewAlterTarget {
24 pub canonical: String,
25 pub relation: RelationIdentity,
26 pub kind: &'static str,
27}
28
29pub fn view_alter_target(
30 resolution: RelationResolution,
31 statement: &AlterViewStmt,
32 notice: &mut dyn FnMut(&str),
33) -> Result<Option<ViewAlterTarget>, SQLError> {
34 let kind = match statement.kind {
35 AlterViewKind::View => "view",
36 AlterViewKind::MaterializedView => "materialized view",
37 };
38 let resolution = if matches!(statement.action, AlterViewAction::RenameTo(_)) {
39 resolve_relation_rename_source(resolution, &statement.name, statement.if_exists, notice)?
40 } else {
41 resolution.into_found()
42 };
43 let Some((canonical, actual_kind)) = resolution else {
44 if statement.if_exists {
45 return Ok(None);
46 }
47 return Err(SQLError::Routine {
48 sqlstate: "42P01".into(),
49 message: format!("relation \"{}\" does not exist", statement.name),
50 });
51 };
52 if actual_kind != kind {
53 return Err(SQLError::Routine {
54 sqlstate: "42809".into(),
55 message: format!("\"{}\" is not a {kind}", statement.name),
56 });
57 }
58 let relation = RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
59 SQLError::Internal(format!("invalid ALTER VIEW target `{canonical}`: {error}"))
60 })?;
61 Ok(Some(ViewAlterTarget {
62 canonical,
63 relation,
64 kind,
65 }))
66}
67
68pub fn foreign_table_alter_target(
69 resolution: RelationResolution,
70 statement: &AlterForeignTableStmt,
71 notice: &mut dyn FnMut(&str),
72) -> Result<Option<String>, SQLError> {
73 let resolution = if matches!(statement.action, AlterForeignTableAction::RenameTo(_)) {
74 let Some((canonical, kind)) = resolve_relation_rename_source(
75 resolution,
76 &statement.name,
77 statement.if_exists,
78 notice,
79 )?
80 else {
81 return Ok(None);
82 };
83 RelationResolution::Found(canonical, kind)
84 } else {
85 resolution
86 };
87 match resolution {
88 RelationResolution::Found(canonical, "foreign table") => Ok(Some(canonical)),
89 RelationResolution::Found(_, _) => Err(SQLError::Routine {
90 sqlstate: "42809".into(),
91 message: format!("\"{}\" is not a foreign table", statement.name),
92 }),
93 RelationResolution::MissingSchema(schema) if statement.if_exists => {
94 notice(&format!("schema \"{schema}\" does not exist, skipping"));
95 Ok(None)
96 }
97 RelationResolution::MissingRelation if statement.if_exists => {
98 notice(&format!(
99 "foreign table \"{}\" does not exist, skipping",
100 statement.name
101 ));
102 Ok(None)
103 }
104 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
105 sqlstate: "3F000".into(),
106 message: format!("schema \"{schema}\" does not exist"),
107 }),
108 RelationResolution::MissingRelation => Err(SQLError::Routine {
109 sqlstate: "42P01".into(),
110 message: format!("foreign table \"{}\" does not exist", statement.name),
111 }),
112 }
113}
114
115pub fn relation_rename_target(
116 catalog: &dyn RelationAlterNames,
117 source: &RelationIdentity,
118 new_name: &str,
119 context: &str,
120) -> Result<RelationIdentity, SQLError> {
121 let (schema, local_name) = RelationIdentity::parse_reference(new_name)
122 .map_err(|error| SQLError::Internal(format!("invalid {context} target: {error}")))?;
123 if schema.is_some() {
124 return Err(SQLError::Internal(format!(
125 "{context} produced a qualified target"
126 )));
127 }
128 let target = RelationIdentity::new(&source.schema, local_name);
129 if target == *source
130 || catalog
131 .relation_kind_at(&target.qualified_name())
132 .map_err(|error| {
133 SQLError::Internal(format!(
134 "check {context} target `{}`: {error}",
135 target.qualified_name()
136 ))
137 })?
138 .is_some()
139 {
140 return Err(SQLError::Routine {
141 sqlstate: "42P07".into(),
142 message: format!("relation \"{}\" already exists", target.name),
143 });
144 }
145 Ok(target)
146}
147
148pub fn set_view_options(options: &mut Vec<(String, String)>, changes: &[(String, String)]) {
149 for (name, value) in changes {
150 options.retain(|(current, _)| current != name);
151 options.push((name.clone(), value.clone()));
152 }
153}
154
155pub fn reset_view_options(options: &mut Vec<(String, String)>, names: &[String]) {
156 options.retain(|(current, _)| !names.contains(current));
157}