1use crate::{
9 ast::{
10 AlterForeignTableAction, AlterForeignTableStmt, AlterViewAction, AlterViewKind,
11 AlterViewStmt, TableLockMode,
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 RelationAlterTarget {
24 pub canonical: String,
25 pub relation: RelationIdentity,
26 pub kind: &'static str,
27}
28
29impl RelationAlterTarget {
30 pub fn resolve(
31 resolution: RelationResolution,
32 name: &str,
33 if_exists: bool,
34 notice: &mut dyn FnMut(&str),
35 ) -> Result<Option<Self>, SQLError> {
36 resolve_relation_rename_source(resolution, name, if_exists, notice)?
37 .map(|(canonical, kind)| Self::from_name(canonical, kind))
38 .transpose()
39 }
40
41 pub(crate) fn from_name(canonical: String, kind: &'static str) -> Result<Self, SQLError> {
42 let relation = RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
43 SQLError::Internal(format!(
44 "invalid relation ALTER target `{canonical}`: {error}"
45 ))
46 })?;
47 Ok(Self {
48 canonical,
49 relation,
50 kind,
51 })
52 }
53
54 pub fn require_kind(&self, expected: &str) -> Result<(), SQLError> {
56 if self.kind == expected {
57 Ok(())
58 } else {
59 Err(SQLError::Routine {
60 sqlstate: "42809".into(),
61 message: format!("\"{}\" is not a {expected}", self.relation.name),
62 })
63 }
64 }
65}
66
67pub fn view_alter_lock_mode(statement: &AlterViewStmt) -> TableLockMode {
68 match (&statement.kind, &statement.action) {
69 (AlterViewKind::MaterializedView, AlterViewAction::Set(_) | AlterViewAction::Reset(_)) => {
70 TableLockMode::ShareUpdateExclusive
71 }
72 _ => TableLockMode::AccessExclusive,
73 }
74}
75
76#[cfg(test)]
77mod tests;
78
79pub fn view_alter_target(
80 resolution: RelationResolution,
81 statement: &AlterViewStmt,
82 notice: &mut dyn FnMut(&str),
83) -> Result<Option<RelationAlterTarget>, SQLError> {
84 let resolution = if matches!(
85 statement.action,
86 AlterViewAction::RenameTo(_) | AlterViewAction::SetSchema(_)
87 ) {
88 resolve_relation_rename_source(resolution, &statement.name, statement.if_exists, notice)?
89 } else {
90 resolution.into_found()
91 };
92 let Some((canonical, actual_kind)) = resolution else {
93 if statement.if_exists {
94 return Ok(None);
95 }
96 return Err(SQLError::Routine {
97 sqlstate: "42P01".into(),
98 message: format!("relation \"{}\" does not exist", statement.name),
99 });
100 };
101 RelationAlterTarget::from_name(canonical, actual_kind).map(Some)
102}
103
104pub fn foreign_table_alter_target(
105 resolution: RelationResolution,
106 statement: &AlterForeignTableStmt,
107 notice: &mut dyn FnMut(&str),
108) -> Result<Option<RelationAlterTarget>, SQLError> {
109 let resolution = if matches!(
110 statement.action,
111 AlterForeignTableAction::RenameTo(_)
112 | AlterForeignTableAction::SetSchema(_)
113 | AlterForeignTableAction::DropColumns(_)
114 ) {
115 let Some((canonical, kind)) = resolve_relation_rename_source(
116 resolution,
117 &statement.name,
118 statement.if_exists,
119 notice,
120 )?
121 else {
122 return Ok(None);
123 };
124 RelationResolution::Found(canonical, kind)
125 } else {
126 resolution
127 };
128 match resolution {
129 RelationResolution::Found(canonical, kind) => {
130 RelationAlterTarget::from_name(canonical, kind).map(Some)
131 }
132 RelationResolution::MissingSchema(schema) if statement.if_exists => {
133 notice(&format!("schema \"{schema}\" does not exist, skipping"));
134 Ok(None)
135 }
136 RelationResolution::MissingRelation if statement.if_exists => {
137 notice(&format!(
138 "foreign table \"{}\" does not exist, skipping",
139 statement.name
140 ));
141 Ok(None)
142 }
143 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
144 sqlstate: "3F000".into(),
145 message: format!("schema \"{schema}\" does not exist"),
146 }),
147 RelationResolution::MissingRelation => Err(SQLError::Routine {
148 sqlstate: "42P01".into(),
149 message: format!("foreign table \"{}\" does not exist", statement.name),
150 }),
151 }
152}
153
154pub fn relation_rename_target(
155 catalog: &dyn RelationAlterNames,
156 source: &RelationIdentity,
157 new_name: &str,
158 context: &str,
159) -> Result<RelationIdentity, SQLError> {
160 let (schema, local_name) = RelationIdentity::parse_reference(new_name)
161 .map_err(|error| SQLError::Internal(format!("invalid {context} target: {error}")))?;
162 if schema.is_some() {
163 return Err(SQLError::Internal(format!(
164 "{context} produced a qualified target"
165 )));
166 }
167 let target = RelationIdentity::new(&source.schema, local_name);
168 if target == *source
169 || catalog
170 .relation_kind_at(&target.qualified_name())
171 .map_err(|error| {
172 SQLError::Internal(format!(
173 "check {context} target `{}`: {error}",
174 target.qualified_name()
175 ))
176 })?
177 .is_some()
178 {
179 return Err(SQLError::Routine {
180 sqlstate: "42P07".into(),
181 message: format!("relation \"{}\" already exists", target.name),
182 });
183 }
184 Ok(target)
185}
186
187pub fn set_view_options(options: &mut Vec<(String, String)>, changes: &[(String, String)]) {
188 for (name, value) in changes {
189 options.retain(|(current, _)| current != name);
190 options.push((name.clone(), value.clone()));
191 }
192}
193
194pub fn reset_view_options(options: &mut Vec<(String, String)>, names: &[String]) {
195 options.retain(|(current, _)| !names.contains(current));
196}
197
198pub mod relocation;