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