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