1use std::collections::{BTreeMap, BTreeSet};
13
14use mz_ore::str::StrExt;
15use mz_repr::CatalogItemId;
16use mz_sql_parser::ast::CreateTableFromSourceStatement;
17
18use crate::ast::visit::{self, Visit};
19use crate::ast::visit_mut::{self, VisitMut};
20use crate::ast::{
21 AstInfo, CreateConnectionStatement, CreateIndexStatement, CreateMaterializedViewStatement,
22 CreateSecretStatement, CreateSinkStatement, CreateSourceStatement, CreateSubsourceStatement,
23 CreateTableStatement, CreateViewStatement, CreateWebhookSourceStatement, Expr, Ident, Query,
24 Raw, RawDataType, RawItemName, Statement, UnresolvedItemName, ViewDefinition,
25};
26use crate::names::FullItemName;
27
28pub fn create_stmt_rename_schema_refs(
31 create_stmt: &mut Statement<Raw>,
32 database: &str,
33 cur_schema: &str,
34 new_schema: &str,
35) -> Result<(), (String, String)> {
36 match create_stmt {
37 stmt @ Statement::CreateConnection(_)
38 | stmt @ Statement::CreateDatabase(_)
39 | stmt @ Statement::CreateSchema(_)
40 | stmt @ Statement::CreateWebhookSource(_)
41 | stmt @ Statement::CreateSource(_)
42 | stmt @ Statement::CreateSubsource(_)
43 | stmt @ Statement::CreateSink(_)
44 | stmt @ Statement::CreateView(_)
45 | stmt @ Statement::CreateMaterializedView(_)
46 | stmt @ Statement::CreateTable(_)
47 | stmt @ Statement::CreateTableFromSource(_)
48 | stmt @ Statement::CreateIndex(_)
49 | stmt @ Statement::CreateType(_)
50 | stmt @ Statement::CreateSecret(_) => {
51 let mut visitor = CreateSqlRewriteSchema {
52 database,
53 cur_schema,
54 new_schema,
55 error: None,
56 };
57 visitor.visit_statement_mut(stmt);
58
59 if let Some(e) = visitor.error.take() {
60 Err(e)
61 } else {
62 Ok(())
63 }
64 }
65 stmt => {
66 unreachable!("Internal error: only catalog items need to update item refs. {stmt:?}")
67 }
68 }
69}
70
71struct CreateSqlRewriteSchema<'a> {
72 database: &'a str,
73 cur_schema: &'a str,
74 new_schema: &'a str,
75 error: Option<(String, String)>,
76}
77
78impl<'a> CreateSqlRewriteSchema<'a> {
79 fn maybe_rewrite_idents(&mut self, name: &mut [Ident]) {
80 match name {
81 [schema, item] if schema.as_str() == self.cur_schema => {
82 if self.error.is_none() {
86 self.error = Some((schema.to_string(), item.to_string()));
87 }
88 }
89 [database, schema, _item] => {
90 if database.as_str() == self.database && schema.as_str() == self.cur_schema {
91 *schema = Ident::new_unchecked(self.new_schema);
92 }
93 }
94 _ => (),
95 }
96 }
97}
98
99impl<'a, 'ast> VisitMut<'ast, Raw> for CreateSqlRewriteSchema<'a> {
100 fn visit_expr_mut(&mut self, e: &'ast mut Expr<Raw>) {
101 match e {
102 Expr::Identifier(id) => {
103 let i = id.len() - 1;
106 self.maybe_rewrite_idents(&mut id[..i]);
107 }
108 Expr::QualifiedWildcard(id) => {
109 self.maybe_rewrite_idents(id);
110 }
111 _ => visit_mut::visit_expr_mut(self, e),
112 }
113 }
114
115 fn visit_unresolved_item_name_mut(
116 &mut self,
117 unresolved_item_name: &'ast mut UnresolvedItemName,
118 ) {
119 self.maybe_rewrite_idents(&mut unresolved_item_name.0);
120 }
121
122 fn visit_data_type_mut(&mut self, data_type: &'ast mut RawDataType) {
123 match data_type {
130 RawDataType::Array(element_type) | RawDataType::List(element_type) => {
131 self.visit_data_type_mut(element_type)
132 }
133 RawDataType::Map {
134 key_type,
135 value_type,
136 } => {
137 self.visit_data_type_mut(key_type);
138 self.visit_data_type_mut(value_type);
139 }
140 RawDataType::Other { name, .. } => self.visit_item_name_mut(name),
141 }
142 }
143
144 fn visit_item_name_mut(
145 &mut self,
146 item_name: &'ast mut <mz_sql_parser::ast::Raw as AstInfo>::ItemName,
147 ) {
148 match item_name {
149 RawItemName::Name(n) | RawItemName::Id(_, n, _) => self.maybe_rewrite_idents(&mut n.0),
150 }
151 }
152}
153
154pub fn create_stmt_rename(create_stmt: &mut Statement<Raw>, to_item_name: String) {
158 match create_stmt {
160 Statement::CreateIndex(CreateIndexStatement { name, .. }) => {
161 *name = Some(Ident::new_unchecked(to_item_name));
162 }
163 Statement::CreateSink(CreateSinkStatement {
164 name: Some(name), ..
165 })
166 | Statement::CreateSource(CreateSourceStatement { name, .. })
167 | Statement::CreateSubsource(CreateSubsourceStatement { name, .. })
168 | Statement::CreateView(CreateViewStatement {
169 definition: ViewDefinition { name, .. },
170 ..
171 })
172 | Statement::CreateMaterializedView(CreateMaterializedViewStatement { name, .. })
173 | Statement::CreateTable(CreateTableStatement { name, .. })
174 | Statement::CreateTableFromSource(CreateTableFromSourceStatement { name, .. })
175 | Statement::CreateSecret(CreateSecretStatement { name, .. })
176 | Statement::CreateConnection(CreateConnectionStatement { name, .. })
177 | Statement::CreateWebhookSource(CreateWebhookSourceStatement { name, .. }) => {
178 let item_name_len = name.0.len() - 1;
182 name.0[item_name_len] = Ident::new_unchecked(to_item_name);
183 }
184 item => unreachable!("Internal error: only catalog items can be renamed {item:?}"),
185 }
186}
187
188pub fn create_stmt_rename_refs(
201 create_stmt: &mut Statement<Raw>,
202 from_name: FullItemName,
203 to_item_name: String,
204) -> Result<(), String> {
205 let from_item = UnresolvedItemName::from(from_name.clone());
206 let maybe_update_item_name = |item_name: &mut UnresolvedItemName| {
207 if item_name.0 == from_item.0 {
208 let item_name_len = item_name.0.len() - 1;
212 item_name.0[item_name_len] = Ident::new_unchecked(to_item_name.clone());
213 }
214 };
215
216 match create_stmt {
218 Statement::CreateIndex(CreateIndexStatement { on_name, .. }) => {
219 maybe_update_item_name(on_name.name_mut());
220 }
221 Statement::CreateSink(CreateSinkStatement { from, .. }) => {
222 maybe_update_item_name(from.name_mut());
223 }
224 Statement::CreateTableFromSource(CreateTableFromSourceStatement { source, .. }) => {
225 maybe_update_item_name(source.name_mut());
226 }
227 Statement::CreateView(CreateViewStatement {
228 definition: ViewDefinition { query, .. },
229 ..
230 }) => {
231 rewrite_query(from_name, to_item_name, query)?;
232 }
233 Statement::CreateMaterializedView(CreateMaterializedViewStatement {
234 replacement_for,
235 query,
236 ..
237 }) => {
238 if let Some(target) = replacement_for {
239 maybe_update_item_name(target.name_mut());
240 }
241 rewrite_query(from_name, to_item_name, query)?;
242 }
243 Statement::CreateSource(_)
244 | Statement::CreateSubsource(_)
245 | Statement::CreateTable(_)
246 | Statement::CreateSecret(_)
247 | Statement::CreateConnection(_)
248 | Statement::CreateWebhookSource(_) => {}
249 item => {
250 unreachable!("Internal error: only catalog items need to update item refs {item:?}")
251 }
252 }
253
254 Ok(())
255}
256
257fn rewrite_query(from: FullItemName, to: String, query: &mut Query<Raw>) -> Result<(), String> {
259 let from_ident = Ident::new_unchecked(from.item.clone());
260 let to_ident = Ident::new_unchecked(to);
261 let qual_depth =
262 QueryIdentAgg::determine_qual_depth(&from_ident, Some(to_ident.clone()), query)?;
263 CreateSqlRewriter::rewrite_query_with_qual_depth(from, to_ident.clone(), qual_depth, query);
264 match QueryIdentAgg::determine_qual_depth(&to_ident, None, query) {
267 Ok(_) => Ok(()),
268 Err(e) => Err(e),
269 }
270}
271
272fn ambiguous_err(n: &Ident, t: &str) -> String {
273 format!(
274 "{} potentially used ambiguously as item and {}",
275 n.as_str().quoted(),
276 t
277 )
278}
279
280struct QueryIdentAgg<'a> {
282 name: &'a Ident,
284 qualifiers: BTreeMap<Ident, BTreeSet<Ident>>,
288 min_qual_depth: usize,
290 fail_on: Option<Ident>,
292 err: Option<String>,
293}
294
295impl<'a> QueryIdentAgg<'a> {
296 fn determine_qual_depth(
308 name: &Ident,
309 fail_on: Option<Ident>,
310 query: &Query<Raw>,
311 ) -> Result<usize, String> {
312 let mut v = QueryIdentAgg {
313 qualifiers: BTreeMap::new(),
314 min_qual_depth: usize::MAX,
315 err: None,
316 name,
317 fail_on,
318 };
319
320 v.visit_query(query);
322 assert!(v.min_qual_depth > 0);
324
325 if let Some(e) = v.err {
326 return Err(e);
327 }
328
329 let req_depth = if v.qualifiers.values().any(|v| v.len() > 1) {
332 3
333 } else if v.qualifiers.len() > 1 {
336 2
337 } else {
338 1
339 };
340
341 if v.min_qual_depth < req_depth {
342 Err(format!(
343 "{} is not sufficiently qualified to support renaming",
344 name.as_str().quoted()
345 ))
346 } else {
347 Ok(req_depth)
348 }
349 }
350
351 fn check_failure(&mut self, v: &[Ident]) {
353 if let Some(f) = &self.fail_on {
355 if v.iter().any(|i| i == f) {
356 self.err = Some(format!(
357 "found reference to {}; cannot rename {} to any identity \
358 used in any existing view definitions",
359 f.as_str().quoted(),
360 self.name.as_str().quoted()
361 ));
362 }
363 }
364 }
365}
366
367impl<'a, 'ast> Visit<'ast, Raw> for QueryIdentAgg<'a> {
368 fn visit_expr(&mut self, e: &'ast Expr<Raw>) {
369 match e {
370 Expr::Identifier(i) => {
371 self.check_failure(i);
372 if let Some(p) = i.iter().rposition(|e| e == self.name) {
373 if p == i.len() - 1 {
374 self.err = Some(ambiguous_err(self.name, "column"));
377 return;
378 }
379 self.min_qual_depth = std::cmp::min(p + 1, self.min_qual_depth);
380 }
381 }
382 Expr::QualifiedWildcard(i) => {
383 self.check_failure(i);
384 if let Some(p) = i.iter().rposition(|e| e == self.name) {
385 self.min_qual_depth = std::cmp::min(p + 1, self.min_qual_depth);
386 }
387 }
388 _ => visit::visit_expr(self, e),
389 }
390 }
391
392 fn visit_ident(&mut self, ident: &'ast Ident) {
393 self.check_failure(std::slice::from_ref(ident));
394 if ident == self.name {
397 self.err = Some(ambiguous_err(self.name, "alias or column"));
398 }
399 }
400
401 fn visit_unresolved_item_name(&mut self, unresolved_item_name: &'ast UnresolvedItemName) {
402 let names = &unresolved_item_name.0;
403 self.check_failure(names);
404 if let Some(p) = names.iter().rposition(|e| e == self.name) {
407 if p == names.len() - 1 && names.len() == 3 {
409 self.qualifiers
410 .entry(names[1].clone())
411 .or_default()
412 .insert(names[0].clone());
413 self.min_qual_depth = std::cmp::min(3, self.min_qual_depth);
414 } else {
415 self.err = Some(ambiguous_err(self.name, "database, schema, or function"))
417 }
418 }
419 }
420
421 fn visit_item_name(&mut self, item_name: &'ast <Raw as AstInfo>::ItemName) {
422 match item_name {
423 RawItemName::Name(n) | RawItemName::Id(_, n, _) => self.visit_unresolved_item_name(n),
424 }
425 }
426}
427
428struct CreateSqlRewriter {
429 from: Vec<Ident>,
430 to: Ident,
431}
432
433impl CreateSqlRewriter {
434 fn rewrite_query_with_qual_depth(
435 from_name: FullItemName,
436 to_name: Ident,
437 qual_depth: usize,
438 query: &mut Query<Raw>,
439 ) {
440 let from = match qual_depth {
441 1 => vec![Ident::new_unchecked(from_name.item)],
442 2 => vec![
443 Ident::new_unchecked(from_name.schema),
444 Ident::new_unchecked(from_name.item),
445 ],
446 3 => vec![
447 Ident::new_unchecked(from_name.database.to_string()),
448 Ident::new_unchecked(from_name.schema),
449 Ident::new_unchecked(from_name.item),
450 ],
451 _ => unreachable!(),
452 };
453 let mut v = CreateSqlRewriter { from, to: to_name };
454 v.visit_query_mut(query);
455 }
456
457 fn maybe_rewrite_idents(&self, name: &mut [Ident]) {
458 if name.len() > 0 && name.ends_with(&self.from) {
459 name[name.len() - 1] = self.to.clone();
460 }
461 }
462}
463
464impl<'ast> VisitMut<'ast, Raw> for CreateSqlRewriter {
465 fn visit_expr_mut(&mut self, e: &'ast mut Expr<Raw>) {
466 match e {
467 Expr::Identifier(id) => {
468 let i = id.len() - 1;
471 self.maybe_rewrite_idents(&mut id[..i]);
472 }
473 Expr::QualifiedWildcard(id) => {
474 self.maybe_rewrite_idents(id);
475 }
476 _ => visit_mut::visit_expr_mut(self, e),
477 }
478 }
479 fn visit_unresolved_item_name_mut(
480 &mut self,
481 unresolved_item_name: &'ast mut UnresolvedItemName,
482 ) {
483 self.maybe_rewrite_idents(&mut unresolved_item_name.0);
484 }
485 fn visit_item_name_mut(
486 &mut self,
487 item_name: &'ast mut <mz_sql_parser::ast::Raw as AstInfo>::ItemName,
488 ) {
489 match item_name {
490 RawItemName::Name(n) | RawItemName::Id(_, n, _) => self.maybe_rewrite_idents(&mut n.0),
491 }
492 }
493}
494
495pub fn create_stmt_replace_ids(
497 create_stmt: &mut Statement<Raw>,
498 ids: &BTreeMap<CatalogItemId, CatalogItemId>,
499) {
500 let mut id_replacer = CreateSqlIdReplacer { ids };
501 id_replacer.visit_statement_mut(create_stmt);
502}
503
504struct CreateSqlIdReplacer<'a> {
505 ids: &'a BTreeMap<CatalogItemId, CatalogItemId>,
506}
507
508impl<'ast> VisitMut<'ast, Raw> for CreateSqlIdReplacer<'_> {
509 fn visit_item_name_mut(
510 &mut self,
511 item_name: &'ast mut <mz_sql_parser::ast::Raw as AstInfo>::ItemName,
512 ) {
513 match item_name {
514 RawItemName::Id(id, _, _) => {
515 let old_id = match id.parse() {
516 Ok(old_id) => old_id,
517 Err(e) => panic!("invalid persisted global id {id}: {e}"),
518 };
519 if let Some(new_id) = self.ids.get(&old_id) {
520 *id = new_id.to_string();
521 }
522 }
523 RawItemName::Name(_) => {}
524 }
525 }
526}