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