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 scx.record_sql_impl_ids(&new_resolved_ids);
745 Ok(ShowColumnsSelect {
746 id: entry.id(),
747 show_select,
748 new_resolved_ids,
749 })
750}
751
752pub fn show_clusters<'a>(
756 scx: &'a StatementContext<'a>,
757 filter: Option<ShowStatementFilter<Aug>>,
758) -> Result<ShowSelect<'a>, PlanError> {
759 let query = "SELECT name, replicas, comment FROM mz_internal.mz_show_clusters".to_string();
760 ShowSelect::new(
761 scx,
762 query,
763 filter,
764 None,
765 Some(&["name", "replicas", "comment"]),
766 )
767}
768
769pub fn show_cluster_replicas<'a>(
770 scx: &'a StatementContext<'a>,
771 filter: Option<ShowStatementFilter<Aug>>,
772) -> Result<ShowSelect<'a>, PlanError> {
773 let query = "
774 SELECT cluster, replica, size, ready, comment
775 FROM mz_internal.mz_show_cluster_replicas
776 "
777 .to_string();
778
779 ShowSelect::new(
780 scx,
781 query,
782 filter,
783 None,
784 Some(&["cluster", "replica", "size", "ready", "comment"]),
785 )
786}
787
788pub fn show_secrets<'a>(
789 scx: &'a StatementContext<'a>,
790 from: Option<ResolvedSchemaName>,
791 filter: Option<ShowStatementFilter<Aug>>,
792) -> Result<ShowSelect<'a>, PlanError> {
793 let schema_spec = scx.resolve_optional_schema(&from)?;
794
795 let query = format!(
796 "SELECT name, comment
797 FROM mz_internal.mz_show_secrets
798 WHERE schema_id = '{schema_spec}'",
799 );
800
801 ShowSelect::new(scx, query, filter, None, Some(&["name", "comment"]))
802}
803
804pub fn show_privileges<'a>(
805 scx: &'a StatementContext<'a>,
806 object_type: Option<SystemObjectType>,
807 role: Option<ResolvedRoleName>,
808 filter: Option<ShowStatementFilter<Aug>>,
809) -> Result<ShowSelect<'a>, PlanError> {
810 let mut query_filter = Vec::new();
811 if let Some(object_type) = object_type {
812 query_filter.push(format!(
813 "object_type = '{}'",
814 object_type.to_string().to_lowercase()
815 ));
816 }
817 if let Some(role) = role {
818 let name = role.name;
819 query_filter.push(format!("CASE WHEN grantee = 'PUBLIC' THEN true ELSE pg_has_role('{name}', grantee, 'USAGE') END"));
820 }
821 let query_filter = if query_filter.len() > 0 {
822 format!("WHERE {}", itertools::join(query_filter, " AND "))
823 } else {
824 "".to_string()
825 };
826
827 let query = format!(
828 "SELECT grantor, grantee, database, schema, name, object_type, privilege_type
829 FROM mz_internal.mz_show_all_privileges
830 {query_filter}",
831 );
832
833 ShowSelect::new(
834 scx,
835 query,
836 filter,
837 None,
838 Some(&[
839 "grantor",
840 "grantee",
841 "database",
842 "schema",
843 "name",
844 "object_type",
845 "privilege_type",
846 ]),
847 )
848}
849
850pub fn show_default_privileges<'a>(
851 scx: &'a StatementContext<'a>,
852 object_type: Option<ObjectType>,
853 role: Option<ResolvedRoleName>,
854 filter: Option<ShowStatementFilter<Aug>>,
855) -> Result<ShowSelect<'a>, PlanError> {
856 let mut query_filter = Vec::new();
857 if let Some(object_type) = object_type {
858 query_filter.push(format!(
859 "object_type = '{}'",
860 object_type.to_string().to_lowercase()
861 ));
862 }
863 if let Some(role) = role {
864 let name = role.name;
865 query_filter.push(format!("CASE WHEN grantee = 'PUBLIC' THEN true ELSE pg_has_role('{name}', grantee, 'USAGE') END"));
866 }
867 let query_filter = if query_filter.len() > 0 {
868 format!("WHERE {}", itertools::join(query_filter, " AND "))
869 } else {
870 "".to_string()
871 };
872
873 let query = format!(
874 "SELECT object_owner, database, schema, object_type, grantee, privilege_type
875 FROM mz_internal.mz_show_default_privileges
876 {query_filter}",
877 );
878
879 ShowSelect::new(
880 scx,
881 query,
882 filter,
883 None,
884 Some(&[
885 "object_owner",
886 "database",
887 "schema",
888 "object_type",
889 "grantee",
890 "privilege_type",
891 ]),
892 )
893}
894
895pub fn show_role_membership<'a>(
896 scx: &'a StatementContext<'a>,
897 role: Option<ResolvedRoleName>,
898 filter: Option<ShowStatementFilter<Aug>>,
899) -> Result<ShowSelect<'a>, PlanError> {
900 let mut query_filter = Vec::new();
901 if let Some(role) = role {
902 let name = role.name;
903 query_filter.push(format!("pg_has_role('{name}', member, 'USAGE')"));
904 }
905 let query_filter = if query_filter.len() > 0 {
906 format!("WHERE {}", itertools::join(query_filter, " AND "))
907 } else {
908 "".to_string()
909 };
910
911 let query = format!(
912 "SELECT role, member, grantor
913 FROM mz_internal.mz_show_role_members
914 {query_filter}",
915 );
916
917 ShowSelect::new(
918 scx,
919 query,
920 filter,
921 None,
922 Some(&["role", "member", "grantor"]),
923 )
924}
925
926pub struct ShowSelect<'a> {
930 scx: &'a StatementContext<'a>,
931 pub(crate) stmt: SelectStatement<Aug>,
932}
933
934impl<'a> ShowSelect<'a> {
935 fn new(
944 scx: &'a StatementContext,
945 query: String,
946 filter: Option<ShowStatementFilter<Aug>>,
947 order: Option<&str>,
948 projection: Option<&[&str]>,
949 ) -> Result<ShowSelect<'a>, PlanError> {
950 let (show_select, new_resolved_ids) =
951 Self::new_with_resolved_ids(scx, query, filter, order, projection)?;
952 scx.sql_impl_resolved_ids
953 .lock()
954 .expect("planning is single-threaded")
955 .extend_from(&new_resolved_ids);
956 Ok(show_select)
957 }
958
959 fn new_with_resolved_ids(
960 scx: &'a StatementContext,
961 query: String,
962 filter: Option<ShowStatementFilter<Aug>>,
963 order: Option<&str>,
964 projection: Option<&[&str]>,
965 ) -> Result<(ShowSelect<'a>, ResolvedIds), PlanError> {
966 let filter = match filter {
967 Some(ShowStatementFilter::Like(like)) => format!("name LIKE {}", Value::String(like)),
968 Some(ShowStatementFilter::Where(expr)) => expr.to_string(),
969 None => "true".to_string(),
970 };
971 let query = format!(
972 "SELECT {} FROM ({}) q WHERE {} ORDER BY {}",
973 projection
974 .map(|ps| ps.join(", "))
975 .unwrap_or_else(|| "*".into()),
976 query,
977 filter,
978 order.unwrap_or("q.*")
979 );
980
981 Self::new_from_bare_query(scx, query)
982 }
983
984 pub fn new_from_bare_query(
985 scx: &'a StatementContext,
986 query: String,
987 ) -> Result<(ShowSelect<'a>, ResolvedIds), PlanError> {
988 let stmts = parse::parse(&query)
989 .map_err(|e| internal_err!("failed to parse generated SHOW query: {}", e))?;
990 let stmt = match stmts.into_element().ast {
991 Statement::Select(select) => select,
992 _ => bail_internal!("generated SHOW query was not a SELECT statement"),
993 };
994 let (mut stmt, new_resolved_ids) = names::resolve(scx.catalog, stmt)?;
995 transform_ast::transform(scx, &mut stmt)?;
996 Ok((ShowSelect { scx, stmt }, new_resolved_ids))
997 }
998
999 pub fn describe(self) -> Result<StatementDesc, PlanError> {
1001 dml::describe_select(self.scx, self.stmt)
1002 }
1003
1004 pub fn plan(self) -> Result<Plan, PlanError> {
1006 dml::plan_select(self.scx, self.stmt, &Params::empty(), None)
1007 }
1008
1009 pub fn plan_hir(self, qcx: &QueryContext) -> Result<(HirRelationExpr, Scope), PlanError> {
1011 query::plan_nested_query(&mut qcx.clone(), &self.stmt.query)
1012 }
1013}
1014
1015pub struct ShowColumnsSelect<'a> {
1016 id: CatalogItemId,
1017 new_resolved_ids: ResolvedIds,
1018 show_select: ShowSelect<'a>,
1019}
1020
1021impl<'a> ShowColumnsSelect<'a> {
1022 pub fn describe(self) -> Result<StatementDesc, PlanError> {
1023 self.show_select.describe()
1024 }
1025
1026 pub fn plan(self) -> Result<Plan, PlanError> {
1027 let select_plan = self.show_select.plan()?;
1028 match select_plan {
1029 Plan::Select(select_plan) => Ok(Plan::ShowColumns(ShowColumnsPlan {
1030 id: self.id,
1031 select_plan,
1032 new_resolved_ids: self.new_resolved_ids,
1033 })),
1034 _ => {
1035 tracing::error!(
1036 "SHOW COLUMNS produced a non select plan. plan: {:?}",
1037 select_plan
1038 );
1039 Err(PlanError::Unstructured(
1040 "SHOW COLUMNS produced an unexpected plan. Please file a bug.".to_string(),
1041 ))
1042 }
1043 }
1044 }
1045
1046 pub fn plan_hir(self, qcx: &QueryContext) -> Result<(HirRelationExpr, Scope), PlanError> {
1047 self.show_select.plan_hir(qcx)
1048 }
1049}
1050
1051fn humanize_sql_for_show_create(
1054 catalog: &dyn SessionCatalog,
1055 id: CatalogItemId,
1056 sql: &str,
1057 redacted: bool,
1058) -> Result<String, PlanError> {
1059 use mz_sql_parser::ast::{CreateSourceConnection, MySqlConfigOptionName, PgConfigOptionName};
1060
1061 let parsed = parse::parse(sql)?.into_element().ast;
1062 let (mut resolved, _) = names::resolve(catalog, parsed)?;
1063
1064 let mut simplifier = NameSimplifier { catalog };
1066 simplifier.visit_statement_mut(&mut resolved);
1067
1068 match &mut resolved {
1069 Statement::CreateMaterializedView(stmt) => stmt.as_of = None,
1071 Statement::CreateSource(stmt) => {
1080 let mut curr_references: BTreeMap<UnresolvedItemName, Vec<UnresolvedItemName>> =
1082 catalog
1083 .get_item(&id)
1084 .used_by()
1085 .into_iter()
1086 .filter_map(|subsource| {
1087 let item = catalog.get_item(subsource);
1088 item.subsource_details().map(|(_id, reference, _details)| {
1089 let name = item.name();
1090 let subsource_name = catalog.resolve_full_name(name);
1091 let subsource_name = UnresolvedItemName::from(subsource_name);
1092 (reference.clone(), subsource_name)
1093 })
1094 })
1095 .fold(BTreeMap::new(), |mut map, (reference, subsource_name)| {
1096 map.entry(reference)
1097 .or_insert_with(Vec::new)
1098 .push(subsource_name);
1099 map
1100 });
1101
1102 match &mut stmt.connection {
1103 CreateSourceConnection::Postgres { options, .. } => {
1104 options.retain_mut(|o| {
1105 match o.name {
1106 PgConfigOptionName::TextColumns => {}
1110 PgConfigOptionName::Details => return false,
1112 _ => return true,
1113 };
1114 match &mut o.value {
1115 Some(WithOptionValue::Sequence(text_cols)) => {
1116 text_cols.retain(|v| match v {
1117 WithOptionValue::UnresolvedItemName(n) => {
1118 let mut name = n.clone();
1119 name.0.truncate(3);
1121 curr_references.contains_key(&name)
1122 }
1123 _ => unreachable!(
1124 "TEXT COLUMNS must be sequence of unresolved item names"
1125 ),
1126 });
1127 !text_cols.is_empty()
1128 }
1129 _ => unreachable!(
1130 "TEXT COLUMNS must be sequence of unresolved item names"
1131 ),
1132 }
1133 });
1134 }
1135 CreateSourceConnection::SqlServer { options, .. } => {
1136 let adjusted_references: BTreeSet<_> = curr_references
1141 .keys()
1142 .map(|name| {
1143 if name.0.len() == 3 {
1144 let adjusted_name = name.0[1..].to_vec();
1146 UnresolvedItemName(adjusted_name)
1147 } else {
1148 name.clone()
1149 }
1150 })
1151 .collect();
1152
1153 options.retain_mut(|o| {
1154 match o.name {
1155 SqlServerConfigOptionName::TextColumns
1159 | SqlServerConfigOptionName::ExcludeColumns => {}
1160 SqlServerConfigOptionName::Details => return false,
1162 };
1163
1164 match &mut o.value {
1165 Some(WithOptionValue::Sequence(seq_unresolved_item_names)) => {
1166 seq_unresolved_item_names.retain(|v| match v {
1167 WithOptionValue::UnresolvedItemName(n) => {
1168 let mut name = n.clone();
1169 name.0.truncate(2);
1171 adjusted_references.contains(&name)
1172 }
1173 _ => unreachable!(
1174 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1175 ),
1176 });
1177 !seq_unresolved_item_names.is_empty()
1178 }
1179 _ => unreachable!(
1180 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1181 ),
1182 }
1183 });
1184 }
1185 CreateSourceConnection::MySql { options, .. } => {
1186 options.retain_mut(|o| {
1187 match o.name {
1188 MySqlConfigOptionName::TextColumns
1192 | MySqlConfigOptionName::ExcludeColumns => {}
1193 MySqlConfigOptionName::Details => return false,
1195 };
1196
1197 match &mut o.value {
1198 Some(WithOptionValue::Sequence(seq_unresolved_item_names)) => {
1199 seq_unresolved_item_names.retain(|v| match v {
1200 WithOptionValue::UnresolvedItemName(n) => {
1201 let mut name = n.clone();
1202 name.0.truncate(2);
1204 curr_references.contains_key(&name)
1205 }
1206 _ => unreachable!(
1207 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1208 ),
1209 });
1210 !seq_unresolved_item_names.is_empty()
1211 }
1212 _ => unreachable!(
1213 "TEXT COLUMNS + EXCLUDE COLUMNS must be sequence of unresolved item names"
1214 ),
1215 }
1216 });
1217 }
1218 CreateSourceConnection::LoadGenerator { .. } if !curr_references.is_empty() => {
1219 curr_references.clear();
1223 stmt.external_references = Some(ExternalReferences::All);
1224 }
1225 CreateSourceConnection::Kafka { .. }
1226 | CreateSourceConnection::LoadGenerator { .. } => {}
1227 }
1228
1229 if !curr_references.is_empty() {
1231 let mut subsources: Vec<_> = curr_references
1232 .into_iter()
1233 .flat_map(|(reference, names)| {
1234 names.into_iter().map(move |name| ExternalReferenceExport {
1235 reference: reference.clone(),
1236 alias: Some(name),
1237 })
1238 })
1239 .collect();
1240 subsources.sort();
1241 stmt.external_references = Some(ExternalReferences::SubsetTables(subsources));
1242 }
1243 }
1244 Statement::CreateSubsource(stmt) => {
1245 stmt.with_options.retain_mut(|o| {
1246 match o.name {
1247 CreateSubsourceOptionName::TextColumns => true,
1248 CreateSubsourceOptionName::RetainHistory => true,
1249 CreateSubsourceOptionName::ExcludeColumns => true,
1250 CreateSubsourceOptionName::Details => false,
1252 CreateSubsourceOptionName::ExternalReference => true,
1253 CreateSubsourceOptionName::Progress => true,
1254 }
1255 });
1256 }
1257
1258 _ => (),
1259 }
1260
1261 Ok(mz_sql_pretty::to_pretty(
1262 &resolved,
1263 PrettyConfig {
1264 width: mz_sql_pretty::DEFAULT_WIDTH,
1265 format_mode: if redacted {
1266 FormatMode::SimpleRedacted
1267 } else {
1268 FormatMode::Simple
1269 },
1270 },
1271 ))
1272}