1use std::collections::{BTreeMap, BTreeSet};
17use std::fmt::Write;
18
19use mz_ore::assert_none;
20use mz_ore::collections::CollectionExt;
21use mz_repr::{CatalogItemId, Datum, RelationDesc, Row, SqlScalarType};
22use mz_sql_parser::ast::display::{AstDisplay, FormatMode};
23use mz_sql_parser::ast::{
24 CreateSubsourceOptionName, ExternalReferenceExport, ExternalReferences, ObjectType,
25 ShowCreateClusterStatement, ShowCreateConnectionStatement, ShowCreateMaterializedViewStatement,
26 ShowCreateTypeStatement, ShowObjectType, SqlServerConfigOptionName, SystemObjectType,
27 UnresolvedItemName, WithOptionValue,
28};
29use mz_sql_pretty::PrettyConfig;
30use query::QueryContext;
31
32use crate::ast::visit_mut::VisitMut;
33use crate::ast::{
34 SelectStatement, ShowColumnsStatement, ShowCreateIndexStatement, ShowCreateSinkStatement,
35 ShowCreateSourceStatement, ShowCreateTableStatement, ShowCreateViewStatement,
36 ShowObjectsStatement, ShowStatementFilter, Statement, Value,
37};
38use crate::catalog::{CatalogItemType, SessionCatalog};
39use crate::names::{
40 self, Aug, NameSimplifier, ObjectId, ResolvedClusterName, ResolvedDataType,
41 ResolvedDatabaseName, ResolvedIds, ResolvedItemName, ResolvedRoleName, ResolvedSchemaName,
42};
43use crate::parse;
44use crate::plan::scope::Scope;
45use crate::plan::statement::ddl::unplan_create_cluster;
46use crate::plan::statement::{StatementContext, StatementDesc, dml};
47use crate::plan::{
48 HirRelationExpr, Params, Plan, PlanError, ShowColumnsPlan, ShowCreatePlan, query, transform_ast,
49};
50
51pub fn describe_show_create_view(
52 _: &StatementContext,
53 _: ShowCreateViewStatement<Aug>,
54) -> Result<StatementDesc, PlanError> {
55 Ok(StatementDesc::new(Some(
56 RelationDesc::builder()
57 .with_column("name", SqlScalarType::String.nullable(false))
58 .with_column("create_sql", SqlScalarType::String.nullable(false))
59 .finish(),
60 )))
61}
62
63pub fn plan_show_create_view(
64 scx: &StatementContext,
65 ShowCreateViewStatement {
66 view_name,
67 redacted,
68 }: ShowCreateViewStatement<Aug>,
69) -> Result<ShowCreatePlan, PlanError> {
70 plan_show_create_item(scx, &view_name, CatalogItemType::View, redacted)
71}
72
73pub fn describe_show_create_materialized_view(
74 _: &StatementContext,
75 _: ShowCreateMaterializedViewStatement<Aug>,
76) -> Result<StatementDesc, PlanError> {
77 Ok(StatementDesc::new(Some(
78 RelationDesc::builder()
79 .with_column("name", SqlScalarType::String.nullable(false))
80 .with_column("create_sql", SqlScalarType::String.nullable(false))
81 .finish(),
82 )))
83}
84
85pub fn plan_show_create_materialized_view(
86 scx: &StatementContext,
87 ShowCreateMaterializedViewStatement {
88 materialized_view_name,
89 redacted,
90 }: ShowCreateMaterializedViewStatement<Aug>,
91) -> Result<ShowCreatePlan, PlanError> {
92 plan_show_create_item(
93 scx,
94 &materialized_view_name,
95 CatalogItemType::MaterializedView,
96 redacted,
97 )
98}
99
100pub fn describe_show_create_table(
101 _: &StatementContext,
102 _: ShowCreateTableStatement<Aug>,
103) -> Result<StatementDesc, PlanError> {
104 Ok(StatementDesc::new(Some(
105 RelationDesc::builder()
106 .with_column("name", SqlScalarType::String.nullable(false))
107 .with_column("create_sql", SqlScalarType::String.nullable(false))
108 .finish(),
109 )))
110}
111
112fn plan_show_create_item(
113 scx: &StatementContext,
114 name: &ResolvedItemName,
115 expect_type: CatalogItemType,
116 redacted: bool,
117) -> Result<ShowCreatePlan, PlanError> {
118 let item = scx.get_item_by_resolved_name(name)?;
119 let name = name.full_name_str();
120 if item.id().is_system()
121 && matches!(
122 expect_type,
123 CatalogItemType::Table | CatalogItemType::Source
124 )
125 {
126 sql_bail!("cannot show create for system object {name}");
127 }
128 if item.item_type() == CatalogItemType::MaterializedView && expect_type == CatalogItemType::View
129 {
130 return Err(PlanError::ShowCreateViewOnMaterializedView(name));
131 }
132 if item.item_type() != expect_type {
133 sql_bail!("{name} is not a {expect_type}");
134 }
135 let create_sql =
136 humanize_sql_for_show_create(scx.catalog, item.id(), item.create_sql(), redacted)?;
137 Ok(ShowCreatePlan {
138 id: ObjectId::Item(item.id()),
139 row: Row::pack_slice(&[Datum::String(&name), Datum::String(&create_sql)]),
140 })
141}
142
143pub fn plan_show_create_table(
144 scx: &StatementContext,
145 ShowCreateTableStatement {
146 table_name,
147 redacted,
148 }: ShowCreateTableStatement<Aug>,
149) -> Result<ShowCreatePlan, PlanError> {
150 plan_show_create_item(scx, &table_name, CatalogItemType::Table, redacted)
151}
152
153pub fn describe_show_create_source(
154 _: &StatementContext,
155 _: ShowCreateSourceStatement<Aug>,
156) -> Result<StatementDesc, PlanError> {
157 Ok(StatementDesc::new(Some(
158 RelationDesc::builder()
159 .with_column("name", SqlScalarType::String.nullable(false))
160 .with_column("create_sql", SqlScalarType::String.nullable(false))
161 .finish(),
162 )))
163}
164
165pub fn plan_show_create_source(
166 scx: &StatementContext,
167 ShowCreateSourceStatement {
168 source_name,
169 redacted,
170 }: ShowCreateSourceStatement<Aug>,
171) -> Result<ShowCreatePlan, PlanError> {
172 plan_show_create_item(scx, &source_name, CatalogItemType::Source, redacted)
173}
174
175pub fn describe_show_create_sink(
176 _: &StatementContext,
177 _: ShowCreateSinkStatement<Aug>,
178) -> Result<StatementDesc, PlanError> {
179 Ok(StatementDesc::new(Some(
180 RelationDesc::builder()
181 .with_column("name", SqlScalarType::String.nullable(false))
182 .with_column("create_sql", SqlScalarType::String.nullable(false))
183 .finish(),
184 )))
185}
186
187pub fn plan_show_create_sink(
188 scx: &StatementContext,
189 ShowCreateSinkStatement {
190 sink_name,
191 redacted,
192 }: ShowCreateSinkStatement<Aug>,
193) -> Result<ShowCreatePlan, PlanError> {
194 plan_show_create_item(scx, &sink_name, CatalogItemType::Sink, redacted)
195}
196
197pub fn describe_show_create_index(
198 _: &StatementContext,
199 _: ShowCreateIndexStatement<Aug>,
200) -> Result<StatementDesc, PlanError> {
201 Ok(StatementDesc::new(Some(
202 RelationDesc::builder()
203 .with_column("name", SqlScalarType::String.nullable(false))
204 .with_column("create_sql", SqlScalarType::String.nullable(false))
205 .finish(),
206 )))
207}
208
209pub fn plan_show_create_index(
210 scx: &StatementContext,
211 ShowCreateIndexStatement {
212 index_name,
213 redacted,
214 }: ShowCreateIndexStatement<Aug>,
215) -> Result<ShowCreatePlan, PlanError> {
216 plan_show_create_item(scx, &index_name, CatalogItemType::Index, redacted)
217}
218
219pub fn describe_show_create_connection(
220 _: &StatementContext,
221 _: ShowCreateConnectionStatement<Aug>,
222) -> Result<StatementDesc, PlanError> {
223 Ok(StatementDesc::new(Some(
224 RelationDesc::builder()
225 .with_column("name", SqlScalarType::String.nullable(false))
226 .with_column("create_sql", SqlScalarType::String.nullable(false))
227 .finish(),
228 )))
229}
230
231pub fn plan_show_create_cluster(
232 scx: &StatementContext,
233 ShowCreateClusterStatement { cluster_name }: ShowCreateClusterStatement<Aug>,
234) -> Result<ShowCreatePlan, PlanError> {
235 let cluster = scx.get_cluster(&cluster_name.id);
236 let name = cluster.name().to_string();
237 let plan = cluster.try_to_plan()?;
238 let stmt = unplan_create_cluster(scx, plan)?;
239 let create_sql = stmt.to_ast_string_stable();
240 Ok(ShowCreatePlan {
241 id: ObjectId::Cluster(cluster_name.id),
242 row: Row::pack_slice(&[Datum::String(&name), Datum::String(&create_sql)]),
243 })
244}
245
246pub fn describe_show_create_cluster(
247 _: &StatementContext,
248 _: ShowCreateClusterStatement<Aug>,
249) -> Result<StatementDesc, PlanError> {
250 Ok(StatementDesc::new(Some(
251 RelationDesc::builder()
252 .with_column("name", SqlScalarType::String.nullable(false))
253 .with_column("create_sql", SqlScalarType::String.nullable(false))
254 .finish(),
255 )))
256}
257
258pub fn plan_show_create_type(
259 scx: &StatementContext,
260 ShowCreateTypeStatement {
261 type_name,
262 redacted,
263 }: ShowCreateTypeStatement<Aug>,
264) -> Result<ShowCreatePlan, PlanError> {
265 let ResolvedDataType::Named { id, full_name, .. } = type_name else {
266 sql_bail!("{type_name} is not a named type");
267 };
268
269 let type_item = scx.get_item(&id);
270
271 if id.is_system() {
273 sql_bail!("cannot show create for system type {full_name}");
275 }
276
277 let name = type_item.name().item.as_ref();
278
279 let create_sql = humanize_sql_for_show_create(
281 scx.catalog,
282 type_item.id(),
283 type_item.create_sql(),
284 redacted,
285 )?;
286
287 Ok(ShowCreatePlan {
288 id: ObjectId::Item(id),
289 row: Row::pack_slice(&[Datum::String(name), Datum::String(&create_sql)]),
290 })
291}
292
293pub fn describe_show_create_type(
294 _: &StatementContext,
295 _: ShowCreateTypeStatement<Aug>,
296) -> Result<StatementDesc, PlanError> {
297 Ok(StatementDesc::new(Some(
298 RelationDesc::builder()
299 .with_column("name", SqlScalarType::String.nullable(false))
300 .with_column("create_sql", SqlScalarType::String.nullable(false))
301 .finish(),
302 )))
303}
304
305pub fn plan_show_create_connection(
306 scx: &StatementContext,
307 ShowCreateConnectionStatement {
308 connection_name,
309 redacted,
310 }: ShowCreateConnectionStatement<Aug>,
311) -> Result<ShowCreatePlan, PlanError> {
312 plan_show_create_item(scx, &connection_name, CatalogItemType::Connection, redacted)
313}
314
315pub fn show_databases<'a>(
316 scx: &'a StatementContext<'a>,
317 filter: Option<ShowStatementFilter<Aug>>,
318) -> Result<ShowSelect<'a>, PlanError> {
319 let query = "SELECT name, comment FROM mz_internal.mz_show_databases".to_string();
320 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
321}
322
323pub fn show_schemas<'a>(
324 scx: &'a StatementContext<'a>,
325 from: Option<ResolvedDatabaseName>,
326 filter: Option<ShowStatementFilter<Aug>>,
327) -> Result<ShowSelect<'a>, PlanError> {
328 let database_id = match from {
329 Some(ResolvedDatabaseName::Database { id, .. }) => id.to_string(),
330 None => match scx.active_database() {
331 Some(id) => id.to_string(),
332 None => sql_bail!("no database specified and no active database"),
333 },
334 Some(ResolvedDatabaseName::Error) => {
335 unreachable!("should have been handled in name resolution")
336 }
337 };
338 let query = format!(
339 "SELECT name, comment
340 FROM mz_internal.mz_show_schemas
341 WHERE database_id IS NULL OR database_id = '{database_id}'",
342 );
343 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
344}
345
346pub fn show_roles<'a>(
347 scx: &'a StatementContext<'a>,
348 filter: Option<ShowStatementFilter<Aug>>,
349) -> Result<ShowSelect<'a>, PlanError> {
350 let query = "SELECT name, comment FROM mz_internal.mz_show_roles".to_string();
351 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
352}
353
354pub fn show_network_policies<'a>(
355 scx: &'a StatementContext<'a>,
356 filter: Option<ShowStatementFilter<Aug>>,
357) -> Result<ShowSelect<'a>, PlanError> {
358 let query = "SELECT name, rules, comment FROM mz_internal.mz_show_network_policies".to_string();
359 ShowSelect::new(
360 scx,
361 query,
362 filter,
363 None,
364 Some(&["name", "rules", "comment"]),
365 )
366}
367
368pub fn show_objects<'a>(
369 scx: &'a StatementContext<'a>,
370 ShowObjectsStatement {
371 object_type,
372 from,
373 filter,
374 }: ShowObjectsStatement<Aug>,
375) -> Result<ShowSelect<'a>, PlanError> {
376 match object_type {
377 ShowObjectType::Table { on_source } => show_tables(scx, from, on_source, filter),
378 ShowObjectType::Source { in_cluster } => show_sources(scx, from, in_cluster, filter),
379 ShowObjectType::Subsource { on_source } => show_subsources(scx, from, on_source, filter),
380 ShowObjectType::View => show_views(scx, from, filter),
381 ShowObjectType::Sink { in_cluster } => show_sinks(scx, from, in_cluster, filter),
382 ShowObjectType::Type => show_types(scx, from, filter),
383 ShowObjectType::Object => show_all_objects(scx, from, filter),
384 ShowObjectType::Role => {
385 assert_none!(from, "parser should reject from");
386 show_roles(scx, filter)
387 }
388 ShowObjectType::Cluster => {
389 assert_none!(from, "parser should reject from");
390 show_clusters(scx, filter)
391 }
392 ShowObjectType::ClusterReplica => {
393 assert_none!(from, "parser should reject from");
394 show_cluster_replicas(scx, filter)
395 }
396 ShowObjectType::Secret => show_secrets(scx, from, filter),
397 ShowObjectType::Connection => show_connections(scx, from, filter),
398 ShowObjectType::MaterializedView { in_cluster } => {
399 show_materialized_views(scx, from, in_cluster, filter)
400 }
401 ShowObjectType::Index {
402 in_cluster,
403 on_object,
404 } => show_indexes(scx, from, on_object, in_cluster, filter),
405 ShowObjectType::Database => {
406 assert_none!(from, "parser should reject from");
407 show_databases(scx, filter)
408 }
409 ShowObjectType::Schema { from: db_from } => {
410 assert_none!(from, "parser should reject from");
411 show_schemas(scx, db_from, filter)
412 }
413 ShowObjectType::Privileges { object_type, role } => {
414 assert_none!(from, "parser should reject from");
415 show_privileges(scx, object_type, role, filter)
416 }
417 ShowObjectType::DefaultPrivileges { object_type, role } => {
418 assert_none!(from, "parser should reject from");
419 show_default_privileges(scx, object_type, role, filter)
420 }
421 ShowObjectType::RoleMembership { role } => {
422 assert_none!(from, "parser should reject from");
423 show_role_membership(scx, role, filter)
424 }
425 ShowObjectType::ContinualTask { in_cluster } => {
426 show_continual_tasks(scx, from, in_cluster, filter)
427 }
428 ShowObjectType::NetworkPolicy => {
429 assert_none!(from, "parser should reject from");
430 show_network_policies(scx, filter)
431 }
432 }
433}
434
435fn show_connections<'a>(
436 scx: &'a StatementContext<'a>,
437 from: Option<ResolvedSchemaName>,
438 filter: Option<ShowStatementFilter<Aug>>,
439) -> Result<ShowSelect<'a>, PlanError> {
440 let schema_spec = scx.resolve_optional_schema(&from)?;
441 let query = format!(
442 "SELECT name, type, comment
443 FROM mz_internal.mz_show_connections connections
444 WHERE schema_id = '{schema_spec}'",
445 );
446 ShowSelect::new(scx, query, filter, None, Some(&["name", "type", "comment"]))
447}
448
449fn show_tables<'a>(
450 scx: &'a StatementContext<'a>,
451 from: Option<ResolvedSchemaName>,
452 on_source: Option<ResolvedItemName>,
453 filter: Option<ShowStatementFilter<Aug>>,
454) -> Result<ShowSelect<'a>, PlanError> {
455 let schema_spec = scx.resolve_optional_schema(&from)?;
456 let mut query = format!(
457 "SELECT name, comment
458 FROM mz_internal.mz_show_tables tables
459 WHERE tables.schema_id = '{schema_spec}'",
460 );
461 if let Some(on_source) = &on_source {
462 let on_item = scx.get_item_by_resolved_name(on_source)?;
463 if on_item.item_type() != CatalogItemType::Source {
464 sql_bail!(
465 "cannot show tables on {} because it is a {}",
466 on_source.full_name_str(),
467 on_item.item_type(),
468 );
469 }
470 query += &format!(" AND tables.source_id = '{}'", on_item.id());
471 }
472 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
473}
474
475fn show_sources<'a>(
476 scx: &'a StatementContext<'a>,
477 from: Option<ResolvedSchemaName>,
478 in_cluster: Option<ResolvedClusterName>,
479 filter: Option<ShowStatementFilter<Aug>>,
480) -> Result<ShowSelect<'a>, PlanError> {
481 let schema_spec = scx.resolve_optional_schema(&from)?;
482 let mut where_clause = format!("schema_id = '{schema_spec}'");
483
484 if let Some(cluster) = in_cluster {
485 write!(where_clause, " AND cluster_id = '{}'", cluster.id)
486 .expect("write on string cannot fail");
487 }
488
489 let query = format!(
490 "SELECT name, type, cluster, comment
491 FROM mz_internal.mz_show_sources
492 WHERE {where_clause}"
493 );
494 ShowSelect::new(
495 scx,
496 query,
497 filter,
498 None,
499 Some(&["name", "type", "cluster", "comment"]),
500 )
501}
502
503fn show_subsources<'a>(
504 scx: &'a StatementContext<'a>,
505 from_schema: Option<ResolvedSchemaName>,
506 on_source: Option<ResolvedItemName>,
507 filter: Option<ShowStatementFilter<Aug>>,
508) -> Result<ShowSelect<'a>, PlanError> {
509 let mut query_filter = Vec::new();
510
511 if on_source.is_none() && from_schema.is_none() {
512 query_filter.push("subsources.id NOT LIKE 's%'".into());
513 let schema_spec = scx.resolve_active_schema().map(|spec| spec.clone())?;
514 query_filter.push(format!("subsources.schema_id = '{schema_spec}'"));
515 }
516
517 if let Some(on_source) = &on_source {
518 let on_item = scx.get_item_by_resolved_name(on_source)?;
519 if on_item.item_type() != CatalogItemType::Source {
520 sql_bail!(
521 "cannot show subsources on {} because it is a {}",
522 on_source.full_name_str(),
523 on_item.item_type(),
524 );
525 }
526 query_filter.push(format!("sources.id = '{}'", on_item.id()));
527 }
528
529 if let Some(schema) = from_schema {
530 let schema_spec = schema.schema_spec();
531 query_filter.push(format!("subsources.schema_id = '{schema_spec}'"));
532 }
533
534 let query = format!(
537 "SELECT DISTINCT
538 subsources.name AS name,
539 subsources.type AS type
540 FROM
541 mz_sources AS subsources
542 JOIN mz_internal.mz_object_dependencies deps ON (subsources.id = deps.object_id OR subsources.id = deps.referenced_object_id)
543 JOIN mz_sources AS sources ON (sources.id = deps.object_id OR sources.id = deps.referenced_object_id)
544 WHERE (subsources.type = 'subsource' OR subsources.type = 'progress') AND {}",
545 itertools::join(query_filter, " AND "),
546 );
547 ShowSelect::new(scx, query, filter, None, None)
548}
549
550fn show_views<'a>(
551 scx: &'a StatementContext<'a>,
552 from: Option<ResolvedSchemaName>,
553 filter: Option<ShowStatementFilter<Aug>>,
554) -> Result<ShowSelect<'a>, PlanError> {
555 let schema_spec = scx.resolve_optional_schema(&from)?;
556 let query = format!(
557 "SELECT name, comment
558 FROM mz_internal.mz_show_views
559 WHERE schema_id = '{schema_spec}'"
560 );
561 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
562}
563
564fn show_materialized_views<'a>(
565 scx: &'a StatementContext<'a>,
566 from: Option<ResolvedSchemaName>,
567 in_cluster: Option<ResolvedClusterName>,
568 filter: Option<ShowStatementFilter<Aug>>,
569) -> Result<ShowSelect<'a>, PlanError> {
570 let schema_spec = scx.resolve_optional_schema(&from)?;
571 let mut where_clause = format!("schema_id = '{schema_spec}'");
572
573 if let Some(cluster) = in_cluster {
574 write!(where_clause, " AND cluster_id = '{}'", cluster.id)
575 .expect("write on string cannot fail");
576 }
577
578 let query = format!(
579 "SELECT name, cluster, comment
580 FROM mz_internal.mz_show_materialized_views
581 WHERE {where_clause}"
582 );
583
584 let projection = vec!["name", "cluster", "comment"];
585
586 ShowSelect::new(scx, query, filter, None, Some(&projection))
587}
588
589fn show_sinks<'a>(
590 scx: &'a StatementContext<'a>,
591 from: Option<ResolvedSchemaName>,
592 in_cluster: Option<ResolvedClusterName>,
593 filter: Option<ShowStatementFilter<Aug>>,
594) -> Result<ShowSelect<'a>, PlanError> {
595 let schema_spec = if let Some(ResolvedSchemaName::Schema { schema_spec, .. }) = from {
596 schema_spec.to_string()
597 } else {
598 scx.resolve_active_schema()?.to_string()
599 };
600
601 let mut where_clause = format!("schema_id = '{schema_spec}'");
602
603 if let Some(cluster) = in_cluster {
604 write!(where_clause, " AND cluster_id = '{}'", cluster.id)
605 .expect("write on string cannot fail");
606 }
607
608 let query = format!(
609 "SELECT name, type, cluster, comment
610 FROM mz_internal.mz_show_sinks sinks
611 WHERE {where_clause}"
612 );
613 ShowSelect::new(
614 scx,
615 query,
616 filter,
617 None,
618 Some(&["name", "type", "cluster", "comment"]),
619 )
620}
621
622fn show_types<'a>(
623 scx: &'a StatementContext<'a>,
624 from: Option<ResolvedSchemaName>,
625 filter: Option<ShowStatementFilter<Aug>>,
626) -> Result<ShowSelect<'a>, PlanError> {
627 let schema_spec = scx.resolve_optional_schema(&from)?;
628 let query = format!(
629 "SELECT name, comment
630 FROM mz_internal.mz_show_types
631 WHERE schema_id = '{schema_spec}'"
632 );
633 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
634}
635
636fn show_all_objects<'a>(
637 scx: &'a StatementContext<'a>,
638 from: Option<ResolvedSchemaName>,
639 filter: Option<ShowStatementFilter<Aug>>,
640) -> Result<ShowSelect<'a>, PlanError> {
641 let schema_spec = scx.resolve_optional_schema(&from)?;
642 let query = format!(
643 "SELECT name, type, comment
644 FROM mz_internal.mz_show_all_objects
645 WHERE schema_id = '{schema_spec}'",
646 );
647 ShowSelect::new(scx, query, filter, None, Some(&["name", "type", "comment"]))
648}
649
650pub fn show_indexes<'a>(
651 scx: &'a StatementContext<'a>,
652 from_schema: Option<ResolvedSchemaName>,
653 on_object: Option<ResolvedItemName>,
654 in_cluster: Option<ResolvedClusterName>,
655 filter: Option<ShowStatementFilter<Aug>>,
656) -> Result<ShowSelect<'a>, PlanError> {
657 let mut query_filter = Vec::new();
658
659 if on_object.is_none() && from_schema.is_none() && in_cluster.is_none() {
660 query_filter.push("on_id NOT LIKE 's%'".into());
661 let schema_spec = scx.resolve_active_schema().map(|spec| spec.clone())?;
662 query_filter.push(format!("schema_id = '{schema_spec}'"));
663 }
664
665 if let Some(on_object) = &on_object {
666 let on_item = scx.get_item_by_resolved_name(on_object)?;
667 if on_item.item_type() != CatalogItemType::View
668 && on_item.item_type() != CatalogItemType::MaterializedView
669 && on_item.item_type() != CatalogItemType::Source
670 && on_item.item_type() != CatalogItemType::Table
671 {
672 sql_bail!(
673 "cannot show indexes on {} because it is a {}",
674 on_object.full_name_str(),
675 on_item.item_type(),
676 );
677 }
678 query_filter.push(format!("on_id = '{}'", on_item.id()));
679 }
680
681 if let Some(schema) = from_schema {
682 let schema_spec = schema.schema_spec();
683 query_filter.push(format!("schema_id = '{schema_spec}'"));
684 }
685
686 if let Some(cluster) = in_cluster {
687 query_filter.push(format!("cluster_id = '{}'", cluster.id))
688 };
689
690 let query = format!(
691 "SELECT name, on, cluster, key, comment
692 FROM mz_internal.mz_show_indexes
693 WHERE {}",
694 itertools::join(query_filter.iter(), " AND ")
695 );
696
697 ShowSelect::new(
698 scx,
699 query,
700 filter,
701 None,
702 Some(&["name", "on", "cluster", "key", "comment"]),
703 )
704}
705
706pub fn show_columns<'a>(
707 scx: &'a StatementContext<'a>,
708 ShowColumnsStatement { table_name, filter }: ShowColumnsStatement<Aug>,
709) -> Result<ShowColumnsSelect<'a>, PlanError> {
710 let entry = scx.get_item_by_resolved_name(&table_name)?;
711 let full_name = scx.catalog.resolve_full_name(entry.name());
712
713 match entry.item_type() {
714 CatalogItemType::Source
715 | CatalogItemType::Table
716 | CatalogItemType::View
717 | CatalogItemType::MaterializedView
718 | CatalogItemType::ContinualTask => (),
719 ty @ CatalogItemType::Connection
720 | ty @ CatalogItemType::Index
721 | ty @ CatalogItemType::Func
722 | ty @ CatalogItemType::Secret
723 | ty @ CatalogItemType::Type
724 | ty @ CatalogItemType::Sink => {
725 sql_bail!("{full_name} is a {ty} and so does not have columns");
726 }
727 }
728
729 let query = format!(
730 "SELECT name, nullable, type, position, comment
731 FROM mz_internal.mz_show_columns columns
732 WHERE columns.id = '{}'",
733 entry.id(),
734 );
735 let (show_select, new_resolved_ids) = ShowSelect::new_with_resolved_ids(
736 scx,
737 query,
738 filter,
739 Some("position"),
740 Some(&["name", "nullable", "type", "comment"]),
741 )?;
742 Ok(ShowColumnsSelect {
743 id: entry.id(),
744 show_select,
745 new_resolved_ids,
746 })
747}
748
749pub fn show_clusters<'a>(
753 scx: &'a StatementContext<'a>,
754 filter: Option<ShowStatementFilter<Aug>>,
755) -> Result<ShowSelect<'a>, PlanError> {
756 let query = "SELECT name, replicas, comment FROM mz_internal.mz_show_clusters".to_string();
757 ShowSelect::new(
758 scx,
759 query,
760 filter,
761 None,
762 Some(&["name", "replicas", "comment"]),
763 )
764}
765
766pub fn show_cluster_replicas<'a>(
767 scx: &'a StatementContext<'a>,
768 filter: Option<ShowStatementFilter<Aug>>,
769) -> Result<ShowSelect<'a>, PlanError> {
770 let query = "
771 SELECT cluster, replica, size, ready, comment
772 FROM mz_internal.mz_show_cluster_replicas
773 "
774 .to_string();
775
776 ShowSelect::new(
777 scx,
778 query,
779 filter,
780 None,
781 Some(&["cluster", "replica", "size", "ready", "comment"]),
782 )
783}
784
785pub fn show_secrets<'a>(
786 scx: &'a StatementContext<'a>,
787 from: Option<ResolvedSchemaName>,
788 filter: Option<ShowStatementFilter<Aug>>,
789) -> Result<ShowSelect<'a>, PlanError> {
790 let schema_spec = scx.resolve_optional_schema(&from)?;
791
792 let query = format!(
793 "SELECT name, comment
794 FROM mz_internal.mz_show_secrets
795 WHERE schema_id = '{schema_spec}'",
796 );
797
798 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
799}
800
801pub fn show_privileges<'a>(
802 scx: &'a StatementContext<'a>,
803 object_type: Option<SystemObjectType>,
804 role: Option<ResolvedRoleName>,
805 filter: Option<ShowStatementFilter<Aug>>,
806) -> Result<ShowSelect<'a>, PlanError> {
807 let mut query_filter = Vec::new();
808 if let Some(object_type) = object_type {
809 query_filter.push(format!(
810 "object_type = '{}'",
811 object_type.to_string().to_lowercase()
812 ));
813 }
814 if let Some(role) = role {
815 let name = role.name;
816 query_filter.push(format!("CASE WHEN grantee = 'PUBLIC' THEN true ELSE pg_has_role('{name}', grantee, 'USAGE') END"));
817 }
818 let query_filter = if query_filter.len() > 0 {
819 format!("WHERE {}", itertools::join(query_filter, " AND "))
820 } else {
821 "".to_string()
822 };
823
824 let query = format!(
825 "SELECT grantor, grantee, database, schema, name, object_type, privilege_type
826 FROM mz_internal.mz_show_all_privileges
827 {query_filter}",
828 );
829
830 ShowSelect::new(
831 scx,
832 query,
833 filter,
834 None,
835 Some(&[
836 "grantor",
837 "grantee",
838 "database",
839 "schema",
840 "name",
841 "object_type",
842 "privilege_type",
843 ]),
844 )
845}
846
847pub fn show_default_privileges<'a>(
848 scx: &'a StatementContext<'a>,
849 object_type: Option<ObjectType>,
850 role: Option<ResolvedRoleName>,
851 filter: Option<ShowStatementFilter<Aug>>,
852) -> Result<ShowSelect<'a>, PlanError> {
853 let mut query_filter = Vec::new();
854 if let Some(object_type) = object_type {
855 query_filter.push(format!(
856 "object_type = '{}'",
857 object_type.to_string().to_lowercase()
858 ));
859 }
860 if let Some(role) = role {
861 let name = role.name;
862 query_filter.push(format!("CASE WHEN grantee = 'PUBLIC' THEN true ELSE pg_has_role('{name}', grantee, 'USAGE') END"));
863 }
864 let query_filter = if query_filter.len() > 0 {
865 format!("WHERE {}", itertools::join(query_filter, " AND "))
866 } else {
867 "".to_string()
868 };
869
870 let query = format!(
871 "SELECT object_owner, database, schema, object_type, grantee, privilege_type
872 FROM mz_internal.mz_show_default_privileges
873 {query_filter}",
874 );
875
876 ShowSelect::new(
877 scx,
878 query,
879 filter,
880 None,
881 Some(&[
882 "object_owner",
883 "database",
884 "schema",
885 "object_type",
886 "grantee",
887 "privilege_type",
888 ]),
889 )
890}
891
892pub fn show_role_membership<'a>(
893 scx: &'a StatementContext<'a>,
894 role: Option<ResolvedRoleName>,
895 filter: Option<ShowStatementFilter<Aug>>,
896) -> Result<ShowSelect<'a>, PlanError> {
897 let mut query_filter = Vec::new();
898 if let Some(role) = role {
899 let name = role.name;
900 query_filter.push(format!("pg_has_role('{name}', member, 'USAGE')"));
901 }
902 let query_filter = if query_filter.len() > 0 {
903 format!("WHERE {}", itertools::join(query_filter, " AND "))
904 } else {
905 "".to_string()
906 };
907
908 let query = format!(
909 "SELECT role, member, grantor
910 FROM mz_internal.mz_show_role_members
911 {query_filter}",
912 );
913
914 ShowSelect::new(
915 scx,
916 query,
917 filter,
918 None,
919 Some(&["role", "member", "grantor"]),
920 )
921}
922
923fn show_continual_tasks<'a>(
924 scx: &'a StatementContext<'a>,
925 from: Option<ResolvedSchemaName>,
926 in_cluster: Option<ResolvedClusterName>,
927 filter: Option<ShowStatementFilter<Aug>>,
928) -> Result<ShowSelect<'a>, PlanError> {
929 let schema_spec = scx.resolve_optional_schema(&from)?;
930 let mut where_clause = format!("schema_id = '{schema_spec}'");
931
932 if let Some(cluster) = in_cluster {
933 write!(where_clause, " AND cluster_id = '{}'", cluster.id)
934 .expect("write on string cannot fail");
935 }
936
937 let query = format!(
938 "SELECT name, cluster, comment
939 FROM mz_internal.mz_show_continual_tasks
940 WHERE {where_clause}"
941 );
942
943 ShowSelect::new(
944 scx,
945 query,
946 filter,
947 None,
948 Some(&["name", "cluster", "comment"]),
949 )
950}
951
952pub struct ShowSelect<'a> {
956 scx: &'a StatementContext<'a>,
957 pub(crate) stmt: SelectStatement<Aug>,
958}
959
960impl<'a> ShowSelect<'a> {
961 fn new(
970 scx: &'a StatementContext,
971 query: String,
972 filter: Option<ShowStatementFilter<Aug>>,
973 order: Option<&str>,
974 projection: Option<&[&str]>,
975 ) -> Result<ShowSelect<'a>, PlanError> {
976 Self::new_with_resolved_ids(scx, query, filter, order, projection)
977 .map(|(show_select, _)| show_select)
978 }
979
980 fn new_with_resolved_ids(
981 scx: &'a StatementContext,
982 query: String,
983 filter: Option<ShowStatementFilter<Aug>>,
984 order: Option<&str>,
985 projection: Option<&[&str]>,
986 ) -> Result<(ShowSelect<'a>, ResolvedIds), PlanError> {
987 let filter = match filter {
988 Some(ShowStatementFilter::Like(like)) => format!("name LIKE {}", Value::String(like)),
989 Some(ShowStatementFilter::Where(expr)) => expr.to_string(),
990 None => "true".to_string(),
991 };
992 let query = format!(
993 "SELECT {} FROM ({}) q WHERE {} ORDER BY {}",
994 projection
995 .map(|ps| ps.join(", "))
996 .unwrap_or_else(|| "*".into()),
997 query,
998 filter,
999 order.unwrap_or("q.*")
1000 );
1001
1002 Self::new_from_bare_query(scx, query)
1003 }
1004
1005 pub fn new_from_bare_query(
1006 scx: &'a StatementContext,
1007 query: String,
1008 ) -> Result<(ShowSelect<'a>, ResolvedIds), PlanError> {
1009 let stmts = parse::parse(&query).expect("ShowSelect::new called with invalid SQL");
1010 let stmt = match stmts.into_element().ast {
1011 Statement::Select(select) => select,
1012 _ => panic!("ShowSelect::new called with non-SELECT statement"),
1013 };
1014 let (mut stmt, new_resolved_ids) = names::resolve(scx.catalog, stmt)?;
1015 transform_ast::transform(scx, &mut stmt)?;
1016 Ok((ShowSelect { scx, stmt }, new_resolved_ids))
1017 }
1018
1019 pub fn describe(self) -> Result<StatementDesc, PlanError> {
1021 dml::describe_select(self.scx, self.stmt)
1022 }
1023
1024 pub fn plan(self) -> Result<Plan, PlanError> {
1026 dml::plan_select(self.scx, self.stmt, &Params::empty(), None)
1027 }
1028
1029 pub fn plan_hir(self, qcx: &QueryContext) -> Result<(HirRelationExpr, Scope), PlanError> {
1031 query::plan_nested_query(&mut qcx.clone(), &self.stmt.query)
1032 }
1033}
1034
1035pub struct ShowColumnsSelect<'a> {
1036 id: CatalogItemId,
1037 new_resolved_ids: ResolvedIds,
1038 show_select: ShowSelect<'a>,
1039}
1040
1041impl<'a> ShowColumnsSelect<'a> {
1042 pub fn describe(self) -> Result<StatementDesc, PlanError> {
1043 self.show_select.describe()
1044 }
1045
1046 pub fn plan(self) -> Result<Plan, PlanError> {
1047 let select_plan = self.show_select.plan()?;
1048 match select_plan {
1049 Plan::Select(select_plan) => Ok(Plan::ShowColumns(ShowColumnsPlan {
1050 id: self.id,
1051 select_plan,
1052 new_resolved_ids: self.new_resolved_ids,
1053 })),
1054 _ => {
1055 tracing::error!(
1056 "SHOW COLUMNS produced a non select plan. plan: {:?}",
1057 select_plan
1058 );
1059 Err(PlanError::Unstructured(
1060 "SHOW COLUMNS produced an unexpected plan. Please file a bug.".to_string(),
1061 ))
1062 }
1063 }
1064 }
1065
1066 pub fn plan_hir(self, qcx: &QueryContext) -> Result<(HirRelationExpr, Scope), PlanError> {
1067 self.show_select.plan_hir(qcx)
1068 }
1069}
1070
1071fn humanize_sql_for_show_create(
1074 catalog: &dyn SessionCatalog,
1075 id: CatalogItemId,
1076 sql: &str,
1077 redacted: bool,
1078) -> Result<String, PlanError> {
1079 use mz_sql_parser::ast::{CreateSourceConnection, MySqlConfigOptionName, PgConfigOptionName};
1080
1081 let parsed = parse::parse(sql)?.into_element().ast;
1082 let (mut resolved, _) = names::resolve(catalog, parsed)?;
1083
1084 let mut simplifier = NameSimplifier { catalog };
1086 simplifier.visit_statement_mut(&mut resolved);
1087
1088 match &mut resolved {
1089 Statement::CreateMaterializedView(stmt) => stmt.as_of = None,
1091 Statement::CreateContinualTask(stmt) => stmt.as_of = None,
1092 Statement::CreateSource(stmt) => {
1101 let mut curr_references: BTreeMap<UnresolvedItemName, Vec<UnresolvedItemName>> =
1103 catalog
1104 .get_item(&id)
1105 .used_by()
1106 .into_iter()
1107 .filter_map(|subsource| {
1108 let item = catalog.get_item(subsource);
1109 item.subsource_details().map(|(_id, reference, _details)| {
1110 let name = item.name();
1111 let subsource_name = catalog.resolve_full_name(name);
1112 let subsource_name = UnresolvedItemName::from(subsource_name);
1113 (reference.clone(), subsource_name)
1114 })
1115 })
1116 .fold(BTreeMap::new(), |mut map, (reference, subsource_name)| {
1117 map.entry(reference)
1118 .or_insert_with(Vec::new)
1119 .push(subsource_name);
1120 map
1121 });
1122
1123 match &mut stmt.connection {
1124 CreateSourceConnection::Postgres { options, .. } => {
1125 options.retain_mut(|o| {
1126 match o.name {
1127 PgConfigOptionName::TextColumns => {}
1131 PgConfigOptionName::Details => return false,
1133 _ => return true,
1134 };
1135 match &mut o.value {
1136 Some(WithOptionValue::Sequence(text_cols)) => {
1137 text_cols.retain(|v| match v {
1138 WithOptionValue::UnresolvedItemName(n) => {
1139 let mut name = n.clone();
1140 name.0.truncate(3);
1142 curr_references.contains_key(&name)
1143 }
1144 _ => unreachable!(
1145 "TEXT COLUMNS must be sequence of unresolved item names"
1146 ),
1147 });
1148 !text_cols.is_empty()
1149 }
1150 _ => unreachable!(
1151 "TEXT COLUMNS must be sequence of unresolved item names"
1152 ),
1153 }
1154 });
1155 }
1156 CreateSourceConnection::SqlServer { options, .. } => {
1157 let adjusted_references: BTreeSet<_> = curr_references
1162 .keys()
1163 .map(|name| {
1164 if name.0.len() == 3 {
1165 let adjusted_name = name.0[1..].to_vec();
1167 UnresolvedItemName(adjusted_name)
1168 } else {
1169 name.clone()
1170 }
1171 })
1172 .collect();
1173
1174 options.retain_mut(|o| {
1175 match o.name {
1176 SqlServerConfigOptionName::TextColumns
1180 | SqlServerConfigOptionName::ExcludeColumns => {}
1181 SqlServerConfigOptionName::Details => return false,
1183 };
1184
1185 match &mut o.value {
1186 Some(WithOptionValue::Sequence(seq_unresolved_item_names)) => {
1187 seq_unresolved_item_names.retain(|v| match v {
1188 WithOptionValue::UnresolvedItemName(n) => {
1189 let mut name = n.clone();
1190 name.0.truncate(2);
1192 adjusted_references.contains(&name)
1193 }
1194 _ => unreachable!(
1195 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1196 ),
1197 });
1198 !seq_unresolved_item_names.is_empty()
1199 }
1200 _ => unreachable!(
1201 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1202 ),
1203 }
1204 });
1205 }
1206 CreateSourceConnection::MySql { options, .. } => {
1207 options.retain_mut(|o| {
1208 match o.name {
1209 MySqlConfigOptionName::TextColumns
1213 | MySqlConfigOptionName::ExcludeColumns => {}
1214 MySqlConfigOptionName::Details => return false,
1216 };
1217
1218 match &mut o.value {
1219 Some(WithOptionValue::Sequence(seq_unresolved_item_names)) => {
1220 seq_unresolved_item_names.retain(|v| match v {
1221 WithOptionValue::UnresolvedItemName(n) => {
1222 let mut name = n.clone();
1223 name.0.truncate(2);
1225 curr_references.contains_key(&name)
1226 }
1227 _ => unreachable!(
1228 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1229 ),
1230 });
1231 !seq_unresolved_item_names.is_empty()
1232 }
1233 _ => unreachable!(
1234 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1235 ),
1236 }
1237 });
1238 }
1239 CreateSourceConnection::LoadGenerator { .. } if !curr_references.is_empty() => {
1240 curr_references.clear();
1244 stmt.external_references = Some(ExternalReferences::All);
1245 }
1246 CreateSourceConnection::Kafka { .. }
1247 | CreateSourceConnection::LoadGenerator { .. } => {}
1248 }
1249
1250 if !curr_references.is_empty() {
1252 let mut subsources: Vec<_> = curr_references
1253 .into_iter()
1254 .flat_map(|(reference, names)| {
1255 names.into_iter().map(move |name| ExternalReferenceExport {
1256 reference: reference.clone(),
1257 alias: Some(name),
1258 })
1259 })
1260 .collect();
1261 subsources.sort();
1262 stmt.external_references = Some(ExternalReferences::SubsetTables(subsources));
1263 }
1264 }
1265 Statement::CreateSubsource(stmt) => {
1266 stmt.with_options.retain_mut(|o| {
1267 match o.name {
1268 CreateSubsourceOptionName::TextColumns => true,
1269 CreateSubsourceOptionName::RetainHistory => true,
1270 CreateSubsourceOptionName::ExcludeColumns => true,
1271 CreateSubsourceOptionName::Details => false,
1273 CreateSubsourceOptionName::ExternalReference => true,
1274 CreateSubsourceOptionName::Progress => true,
1275 }
1276 });
1277 }
1278
1279 _ => (),
1280 }
1281
1282 Ok(mz_sql_pretty::to_pretty(
1283 &resolved,
1284 PrettyConfig {
1285 width: mz_sql_pretty::DEFAULT_WIDTH,
1286 format_mode: if redacted {
1287 FormatMode::SimpleRedacted
1288 } else {
1289 FormatMode::Simple
1290 },
1291 },
1292 ))
1293}