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