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!(statement.action, AlterViewAction::RenameTo(_)) {
85 resolve_relation_rename_source(resolution, &statement.name, statement.if_exists, notice)?
86 } else {
87 resolution.into_found()
88 };
89 let Some((canonical, actual_kind)) = resolution else {
90 if statement.if_exists {
91 return Ok(None);
92 }
93 return Err(SQLError::Routine {
94 sqlstate: "42P01".into(),
95 message: format!("relation \"{}\" does not exist", statement.name),
96 });
97 };
98 RelationAlterTarget::from_name(canonical, actual_kind).map(Some)
99}
100
101pub fn foreign_table_alter_target(
102 resolution: RelationResolution,
103 statement: &AlterForeignTableStmt,
104 notice: &mut dyn FnMut(&str),
105) -> Result<Option<RelationAlterTarget>, SQLError> {
106 let resolution = if matches!(statement.action, AlterForeignTableAction::RenameTo(_)) {
107 let Some((canonical, kind)) = resolve_relation_rename_source(
108 resolution,
109 &statement.name,
110 statement.if_exists,
111 notice,
112 )?
113 else {
114 return Ok(None);
115 };
116 RelationResolution::Found(canonical, kind)
117 } else {
118 resolution
119 };
120 match resolution {
121 RelationResolution::Found(canonical, kind) => {
122 RelationAlterTarget::from_name(canonical, kind).map(Some)
123 }
124 RelationResolution::MissingSchema(schema) if statement.if_exists => {
125 notice(&format!("schema \"{schema}\" does not exist, skipping"));
126 Ok(None)
127 }
128 RelationResolution::MissingRelation if statement.if_exists => {
129 notice(&format!(
130 "foreign table \"{}\" does not exist, skipping",
131 statement.name
132 ));
133 Ok(None)
134 }
135 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
136 sqlstate: "3F000".into(),
137 message: format!("schema \"{schema}\" does not exist"),
138 }),
139 RelationResolution::MissingRelation => Err(SQLError::Routine {
140 sqlstate: "42P01".into(),
141 message: format!("foreign table \"{}\" does not exist", statement.name),
142 }),
143 }
144}
145
146pub fn relation_rename_target(
147 catalog: &dyn RelationAlterNames,
148 source: &RelationIdentity,
149 new_name: &str,
150 context: &str,
151) -> Result<RelationIdentity, SQLError> {
152 let (schema, local_name) = RelationIdentity::parse_reference(new_name)
153 .map_err(|error| SQLError::Internal(format!("invalid {context} target: {error}")))?;
154 if schema.is_some() {
155 return Err(SQLError::Internal(format!(
156 "{context} produced a qualified target"
157 )));
158 }
159 let target = RelationIdentity::new(&source.schema, local_name);
160 if target == *source
161 || catalog
162 .relation_kind_at(&target.qualified_name())
163 .map_err(|error| {
164 SQLError::Internal(format!(
165 "check {context} target `{}`: {error}",
166 target.qualified_name()
167 ))
168 })?
169 .is_some()
170 {
171 return Err(SQLError::Routine {
172 sqlstate: "42P07".into(),
173 message: format!("relation \"{}\" already exists", target.name),
174 });
175 }
176 Ok(target)
177}
178
179pub fn set_view_options(options: &mut Vec<(String, String)>, changes: &[(String, String)]) {
180 for (name, value) in changes {
181 options.retain(|(current, _)| current != name);
182 options.push((name.clone(), value.clone()));
183 }
184}
185
186pub fn reset_view_options(options: &mut Vec<(String, String)>, names: &[String]) {
187 options.retain(|(current, _)| !names.contains(current));
188}