Skip to main content

mz_sql/plan/
statement.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Statement planning.
11//!
12//! This module houses the entry points for planning a SQL statement.
13
14use std::cell::RefCell;
15use std::collections::{BTreeMap, BTreeSet};
16use std::sync::{Arc, Mutex};
17
18use mz_repr::namespaces::is_system_schema;
19use mz_repr::{
20    CatalogItemId, ColumnIndex, RelationDesc, RelationVersionSelector, SqlColumnType, SqlScalarType,
21};
22use mz_sql_parser::ast::{
23    ColumnDef, ColumnName, CreateMaterializedViewStatement, RawItemName, ShowStatement,
24    StatementKind, TableConstraint, UnresolvedDatabaseName, UnresolvedSchemaName,
25};
26use mz_storage_types::connections::Connection;
27
28use crate::ast::{Ident, Statement, UnresolvedItemName};
29use crate::catalog::{
30    CatalogCluster, CatalogCollectionItem, CatalogDatabase, CatalogItem, CatalogItemType,
31    CatalogSchema, ObjectType, SessionCatalog, SystemObjectType,
32};
33use crate::names::{
34    self, Aug, DatabaseId, FullItemName, ItemQualifiers, ObjectId, PartialItemName,
35    QualifiedItemName, RawDatabaseSpecifier, ResolvedColumnReference, ResolvedDataType,
36    ResolvedDatabaseSpecifier, ResolvedIds, ResolvedItemName, ResolvedSchemaName, SchemaSpecifier,
37    SystemObjectId,
38};
39use crate::normalize;
40use crate::plan::error::PlanError;
41use crate::plan::{Params, Plan, PlanContext, PlanKind, query};
42use crate::session::vars::FeatureFlag;
43
44mod acl;
45pub(crate) mod ddl;
46mod dml;
47mod raise;
48mod scl;
49pub(crate) mod show;
50mod tcl;
51mod validate;
52
53use crate::session::vars;
54pub(crate) use ddl::PgConfigOptionExtracted;
55use mz_controller_types::ClusterId;
56use mz_pgrepr::oid::{FIRST_MATERIALIZE_OID, FIRST_USER_OID};
57use mz_repr::role_id::RoleId;
58
59/// Describes the output of a SQL statement.
60#[derive(Debug, Clone, Eq, PartialEq)]
61pub struct StatementDesc {
62    /// The shape of the rows produced by the statement, if the statement
63    /// produces rows.
64    pub relation_desc: Option<RelationDesc>,
65    /// The determined types of the parameters in the statement, if any.
66    pub param_types: Vec<SqlScalarType>,
67    /// Whether the statement is a `COPY` statement.
68    pub is_copy: bool,
69}
70
71impl StatementDesc {
72    pub fn new(relation_desc: Option<RelationDesc>) -> Self {
73        StatementDesc {
74            relation_desc,
75            param_types: vec![],
76            is_copy: false,
77        }
78    }
79
80    /// Reports the number of columns in the statement's result set, or zero if
81    /// the statement does not return rows.
82    pub fn arity(&self) -> usize {
83        self.relation_desc
84            .as_ref()
85            .map(|desc| desc.typ().column_types.len())
86            .unwrap_or(0)
87    }
88
89    fn with_params(mut self, param_types: Vec<SqlScalarType>) -> Self {
90        self.param_types = param_types;
91        self
92    }
93
94    fn with_is_copy(mut self) -> Self {
95        self.is_copy = true;
96        self
97    }
98}
99
100/// Creates a description of the purified statement `stmt`.
101///
102/// See the documentation of [`StatementDesc`] for details.
103pub fn describe(
104    pcx: &PlanContext,
105    catalog: &dyn SessionCatalog,
106    stmt: Statement<Aug>,
107    param_types_in: &[Option<SqlScalarType>],
108) -> Result<StatementDesc, PlanError> {
109    let mut param_types = BTreeMap::new();
110    for (i, ty) in param_types_in.iter().enumerate() {
111        if let Some(ty) = ty {
112            param_types.insert(i + 1, ty.clone());
113        }
114    }
115
116    let scx = StatementContext {
117        pcx: Some(pcx),
118        catalog,
119        param_types: RefCell::new(param_types),
120        ambiguous_columns: RefCell::new(false),
121        sql_impl_resolved_ids: Arc::new(Mutex::new(ResolvedIds::empty())),
122    };
123
124    let desc = match stmt {
125        // DDL statements.
126        Statement::AlterCluster(stmt) => ddl::describe_alter_cluster_set_options(&scx, stmt)?,
127        Statement::AlterConnection(stmt) => ddl::describe_alter_connection(&scx, stmt)?,
128        Statement::AlterIndex(stmt) => ddl::describe_alter_index_options(&scx, stmt)?,
129        Statement::AlterMaterializedViewApplyReplacement(stmt) => {
130            ddl::describe_alter_materialized_view_apply_replacement(&scx, stmt)?
131        }
132        Statement::AlterObjectRename(stmt) => ddl::describe_alter_object_rename(&scx, stmt)?,
133        Statement::AlterObjectSwap(stmt) => ddl::describe_alter_object_swap(&scx, stmt)?,
134        Statement::AlterRetainHistory(stmt) => ddl::describe_alter_retain_history(&scx, stmt)?,
135        Statement::AlterRole(stmt) => ddl::describe_alter_role(&scx, stmt)?,
136        Statement::AlterSecret(stmt) => ddl::describe_alter_secret_options(&scx, stmt)?,
137        Statement::AlterSetCluster(stmt) => ddl::describe_alter_set_cluster(&scx, stmt)?,
138        Statement::AlterSink(stmt) => ddl::describe_alter_sink(&scx, stmt)?,
139        Statement::AlterSource(stmt) => ddl::describe_alter_source(&scx, stmt)?,
140        Statement::AlterSystemSet(stmt) => ddl::describe_alter_system_set(&scx, stmt)?,
141        Statement::AlterSystemReset(stmt) => ddl::describe_alter_system_reset(&scx, stmt)?,
142        Statement::AlterSystemResetAll(stmt) => ddl::describe_alter_system_reset_all(&scx, stmt)?,
143        Statement::AlterTableAddColumn(stmt) => ddl::describe_alter_table_add_column(&scx, stmt)?,
144        Statement::AlterNetworkPolicy(stmt) => ddl::describe_alter_network_policy(&scx, stmt)?,
145        Statement::Comment(stmt) => ddl::describe_comment(&scx, stmt)?,
146        Statement::CreateCluster(stmt) => ddl::describe_create_cluster(&scx, stmt)?,
147        Statement::CreateClusterReplica(stmt) => ddl::describe_create_cluster_replica(&scx, stmt)?,
148        Statement::CreateConnection(stmt) => ddl::describe_create_connection(&scx, stmt)?,
149        Statement::CreateDatabase(stmt) => ddl::describe_create_database(&scx, stmt)?,
150        Statement::CreateIndex(stmt) => ddl::describe_create_index(&scx, stmt)?,
151        Statement::CreateRole(stmt) => ddl::describe_create_role(&scx, stmt)?,
152        Statement::CreateSchema(stmt) => ddl::describe_create_schema(&scx, stmt)?,
153        Statement::CreateSecret(stmt) => ddl::describe_create_secret(&scx, stmt)?,
154        Statement::CreateSink(stmt) => ddl::describe_create_sink(&scx, stmt)?,
155        Statement::CreateMetricSink(stmt) => ddl::describe_create_metric_sink(&scx, stmt)?,
156        Statement::CreateWebhookSource(stmt) => ddl::describe_create_webhook_source(&scx, stmt)?,
157        Statement::CreateSource(stmt) => ddl::describe_create_source(&scx, stmt)?,
158        Statement::CreateSubsource(stmt) => ddl::describe_create_subsource(&scx, stmt)?,
159        Statement::CreateTable(stmt) => ddl::describe_create_table(&scx, stmt)?,
160        Statement::CreateTableFromSource(stmt) => {
161            ddl::describe_create_table_from_source(&scx, stmt)?
162        }
163        Statement::CreateType(stmt) => ddl::describe_create_type(&scx, stmt)?,
164        Statement::CreateView(stmt) => ddl::describe_create_view(&scx, stmt)?,
165        Statement::CreateMaterializedView(stmt) => {
166            ddl::describe_create_materialized_view(&scx, stmt)?
167        }
168        Statement::CreateNetworkPolicy(stmt) => ddl::describe_create_network_policy(&scx, stmt)?,
169        Statement::DropObjects(stmt) => ddl::describe_drop_objects(&scx, stmt)?,
170        Statement::DropOwned(stmt) => ddl::describe_drop_owned(&scx, stmt)?,
171
172        // `ACL` statements.
173        Statement::AlterOwner(stmt) => acl::describe_alter_owner(&scx, stmt)?,
174        Statement::GrantRole(stmt) => acl::describe_grant_role(&scx, stmt)?,
175        Statement::RevokeRole(stmt) => acl::describe_revoke_role(&scx, stmt)?,
176        Statement::GrantPrivileges(stmt) => acl::describe_grant_privileges(&scx, stmt)?,
177        Statement::RevokePrivileges(stmt) => acl::describe_revoke_privileges(&scx, stmt)?,
178        Statement::AlterDefaultPrivileges(stmt) => {
179            acl::describe_alter_default_privileges(&scx, stmt)?
180        }
181        Statement::ReassignOwned(stmt) => acl::describe_reassign_owned(&scx, stmt)?,
182
183        // `SHOW` statements.
184        Statement::Show(ShowStatement::ShowColumns(stmt)) => {
185            show::show_columns(&scx, stmt)?.describe()?
186        }
187        Statement::Show(ShowStatement::ShowCreateConnection(stmt)) => {
188            show::describe_show_create_connection(&scx, stmt)?
189        }
190        Statement::Show(ShowStatement::ShowCreateCluster(stmt)) => {
191            show::describe_show_create_cluster(&scx, stmt)?
192        }
193        Statement::Show(ShowStatement::ShowCreateIndex(stmt)) => {
194            show::describe_show_create_index(&scx, stmt)?
195        }
196        Statement::Show(ShowStatement::ShowCreateSink(stmt)) => {
197            show::describe_show_create_sink(&scx, stmt)?
198        }
199        Statement::Show(ShowStatement::ShowCreateMetricSink(stmt)) => {
200            show::describe_show_create_metric_sink(&scx, stmt)?
201        }
202        Statement::Show(ShowStatement::ShowCreateSource(stmt)) => {
203            show::describe_show_create_source(&scx, stmt)?
204        }
205        Statement::Show(ShowStatement::ShowCreateTable(stmt)) => {
206            show::describe_show_create_table(&scx, stmt)?
207        }
208        Statement::Show(ShowStatement::ShowCreateView(stmt)) => {
209            show::describe_show_create_view(&scx, stmt)?
210        }
211        Statement::Show(ShowStatement::ShowCreateMaterializedView(stmt)) => {
212            show::describe_show_create_materialized_view(&scx, stmt)?
213        }
214        Statement::Show(ShowStatement::ShowCreateType(stmt)) => {
215            show::describe_show_create_type(&scx, stmt)?
216        }
217        Statement::Show(ShowStatement::ShowObjects(stmt)) => {
218            show::show_objects(&scx, stmt)?.describe()?
219        }
220
221        // SCL statements.
222        Statement::Close(stmt) => scl::describe_close(&scx, stmt)?,
223        Statement::Deallocate(stmt) => scl::describe_deallocate(&scx, stmt)?,
224        Statement::Declare(stmt) => scl::describe_declare(&scx, stmt, param_types_in)?,
225        Statement::Discard(stmt) => scl::describe_discard(&scx, stmt)?,
226        Statement::Execute(stmt) => scl::describe_execute(&scx, stmt)?,
227        Statement::Fetch(stmt) => scl::describe_fetch(&scx, stmt)?,
228        Statement::Prepare(stmt) => scl::describe_prepare(&scx, stmt)?,
229        Statement::ResetVariable(stmt) => scl::describe_reset_variable(&scx, stmt)?,
230        Statement::SetVariable(stmt) => scl::describe_set_variable(&scx, stmt)?,
231        Statement::Show(ShowStatement::ShowVariable(stmt)) => {
232            scl::describe_show_variable(&scx, stmt)?
233        }
234
235        // DML statements.
236        Statement::Copy(stmt) => dml::describe_copy(&scx, stmt)?,
237        Statement::Delete(stmt) => dml::describe_delete(&scx, stmt)?,
238        Statement::ExplainPlan(stmt) => dml::describe_explain_plan(&scx, stmt)?,
239        Statement::ExplainPushdown(stmt) => dml::describe_explain_pushdown(&scx, stmt)?,
240        Statement::ExplainAnalyzeObject(stmt) => dml::describe_explain_analyze_object(&scx, stmt)?,
241        Statement::ExplainAnalyzeCluster(stmt) => {
242            dml::describe_explain_analyze_cluster(&scx, stmt)?
243        }
244        Statement::ExplainTimestamp(stmt) => dml::describe_explain_timestamp(&scx, stmt)?,
245        Statement::ExplainSinkSchema(stmt) => dml::describe_explain_schema(&scx, stmt)?,
246        Statement::Insert(stmt) => dml::describe_insert(&scx, stmt)?,
247        Statement::Select(stmt) => dml::describe_select(&scx, stmt)?,
248        Statement::Subscribe(stmt) => dml::describe_subscribe(&scx, stmt)?,
249        Statement::Update(stmt) => dml::describe_update(&scx, stmt)?,
250
251        // TCL statements.
252        Statement::Commit(stmt) => tcl::describe_commit(&scx, stmt)?,
253        Statement::Rollback(stmt) => tcl::describe_rollback(&scx, stmt)?,
254        Statement::SetTransaction(stmt) => tcl::describe_set_transaction(&scx, stmt)?,
255        Statement::StartTransaction(stmt) => tcl::describe_start_transaction(&scx, stmt)?,
256
257        // Other statements.
258        Statement::Raise(stmt) => raise::describe_raise(&scx, stmt)?,
259        Statement::Show(ShowStatement::InspectShard(stmt)) => {
260            scl::describe_inspect_shard(&scx, stmt)?
261        }
262        Statement::ValidateConnection(stmt) => validate::describe_validate_connection(&scx, stmt)?,
263        Statement::ExecuteUnitTest(_) => {
264            return Err(PlanError::Unsupported {
265                feature: "EXECUTE UNIT TEST statement".to_string(),
266                discussion_no: None,
267            });
268        }
269    };
270
271    let desc = desc.with_params(scx.finalize_param_types()?);
272    Ok(desc)
273}
274
275/// Produces a [`Plan`] from the purified statement `stmt`.
276///
277/// Planning is a pure, synchronous function and so requires that the provided
278/// `stmt` does does not depend on any external state. Statements that rely on
279/// external state must remove that state prior to calling this function via
280/// [`crate::pure::purify_statement`] or
281/// [`crate::pure::purify_create_materialized_view_options`].
282///
283/// The returned plan is tied to the state of the provided catalog. If the state
284/// of the catalog changes after planning, the validity of the plan is not
285/// guaranteed.
286///
287/// Note that if you want to do something else asynchronously (e.g. validating
288/// connections), these might want to take different code paths than
289/// `purify_statement`. Feel free to rationalize this by thinking of those
290/// statements as not necessarily depending on external state.
291#[mz_ore::instrument(level = "debug")]
292pub fn plan(
293    pcx: Option<&PlanContext>,
294    catalog: &dyn SessionCatalog,
295    stmt: Statement<Aug>,
296    params: &Params,
297    resolved_ids: &ResolvedIds,
298) -> Result<(Plan, ResolvedIds), PlanError> {
299    let param_types = params
300        // We need the `expected_types` here, not the `actual_types`! This is because
301        // `expected_types` is how the parameter expression (e.g. `$1`) looks "from the outside":
302        // `bind_parameters` will insert a cast from the actual type to the expected type.
303        .expected_types
304        .iter()
305        .enumerate()
306        .map(|(i, ty)| (i + 1, ty.clone()))
307        .collect();
308
309    let kind: StatementKind = (&stmt).into();
310    let permitted_plans = Plan::generated_from(&kind);
311
312    let scx = &mut StatementContext {
313        pcx,
314        catalog,
315        param_types: RefCell::new(param_types),
316        ambiguous_columns: RefCell::new(false),
317        sql_impl_resolved_ids: Arc::new(Mutex::new(ResolvedIds::empty())),
318    };
319
320    if resolved_ids
321        .items()
322        // Filter out items that may not have been created yet, such as sub-sources.
323        .filter_map(|id| catalog.try_get_item(id))
324        .any(|item| {
325            item.func().is_ok()
326                && item.name().qualifiers.schema_spec
327                    == SchemaSpecifier::Id(catalog.get_mz_unsafe_schema_id())
328        })
329    {
330        scx.require_feature_flag(&vars::UNSAFE_ENABLE_UNSAFE_FUNCTIONS)?;
331    }
332
333    let plan = match stmt {
334        // DDL statements.
335        Statement::AlterCluster(stmt) => ddl::plan_alter_cluster(scx, stmt),
336        Statement::AlterConnection(stmt) => ddl::plan_alter_connection(scx, stmt),
337        Statement::AlterIndex(stmt) => ddl::plan_alter_index_options(scx, stmt),
338        Statement::AlterMaterializedViewApplyReplacement(stmt) => {
339            ddl::plan_alter_materialized_view_apply_replacement(scx, stmt)
340        }
341        Statement::AlterObjectRename(stmt) => ddl::plan_alter_object_rename(scx, stmt),
342        Statement::AlterObjectSwap(stmt) => ddl::plan_alter_object_swap(scx, stmt),
343        Statement::AlterRetainHistory(stmt) => ddl::plan_alter_retain_history(scx, stmt),
344        Statement::AlterRole(stmt) => ddl::plan_alter_role(scx, stmt),
345        Statement::AlterSecret(stmt) => ddl::plan_alter_secret(scx, stmt),
346        Statement::AlterSetCluster(stmt) => ddl::plan_alter_item_set_cluster(scx, stmt),
347        Statement::AlterSink(stmt) => ddl::plan_alter_sink(scx, stmt),
348        Statement::AlterSource(stmt) => ddl::plan_alter_source(scx, stmt),
349        Statement::AlterSystemSet(stmt) => ddl::plan_alter_system_set(scx, stmt),
350        Statement::AlterSystemReset(stmt) => ddl::plan_alter_system_reset(scx, stmt),
351        Statement::AlterSystemResetAll(stmt) => ddl::plan_alter_system_reset_all(scx, stmt),
352        Statement::AlterTableAddColumn(stmt) => ddl::plan_alter_table_add_column(scx, stmt),
353        Statement::AlterNetworkPolicy(stmt) => ddl::plan_alter_network_policy(scx, stmt),
354        Statement::Comment(stmt) => ddl::plan_comment(scx, stmt),
355        Statement::CreateCluster(stmt) => ddl::plan_create_cluster(scx, stmt),
356        Statement::CreateClusterReplica(stmt) => ddl::plan_create_cluster_replica(scx, stmt),
357        Statement::CreateConnection(stmt) => ddl::plan_create_connection(scx, stmt),
358        Statement::CreateDatabase(stmt) => ddl::plan_create_database(scx, stmt),
359        Statement::CreateIndex(stmt) => ddl::plan_create_index(scx, stmt),
360        Statement::CreateRole(stmt) => ddl::plan_create_role(scx, stmt),
361        Statement::CreateSchema(stmt) => ddl::plan_create_schema(scx, stmt),
362        Statement::CreateSecret(stmt) => ddl::plan_create_secret(scx, stmt),
363        Statement::CreateSink(stmt) => ddl::plan_create_sink(scx, stmt),
364        Statement::CreateMetricSink(stmt) => ddl::plan_create_metric_sink(scx, stmt),
365        Statement::CreateWebhookSource(stmt) => ddl::plan_create_webhook_source(scx, stmt),
366        Statement::CreateSource(stmt) => ddl::plan_create_source(scx, stmt),
367        Statement::CreateSubsource(stmt) => ddl::plan_create_subsource(scx, stmt),
368        Statement::CreateTable(stmt) => ddl::plan_create_table(scx, stmt),
369        Statement::CreateTableFromSource(stmt) => ddl::plan_create_table_from_source(scx, stmt),
370        Statement::CreateType(stmt) => ddl::plan_create_type(scx, stmt),
371        Statement::CreateView(stmt) => ddl::plan_create_view(scx, stmt),
372        Statement::CreateMaterializedView(stmt) => ddl::plan_create_materialized_view(scx, stmt),
373        Statement::CreateNetworkPolicy(stmt) => ddl::plan_create_network_policy(scx, stmt),
374        Statement::DropObjects(stmt) => ddl::plan_drop_objects(scx, stmt),
375        Statement::DropOwned(stmt) => ddl::plan_drop_owned(scx, stmt),
376
377        // `ACL` statements.
378        Statement::AlterOwner(stmt) => acl::plan_alter_owner(scx, stmt),
379        Statement::GrantRole(stmt) => acl::plan_grant_role(scx, stmt),
380        Statement::RevokeRole(stmt) => acl::plan_revoke_role(scx, stmt),
381        Statement::GrantPrivileges(stmt) => acl::plan_grant_privileges(scx, stmt),
382        Statement::RevokePrivileges(stmt) => acl::plan_revoke_privileges(scx, stmt),
383        Statement::AlterDefaultPrivileges(stmt) => acl::plan_alter_default_privileges(scx, stmt),
384        Statement::ReassignOwned(stmt) => acl::plan_reassign_owned(scx, stmt),
385
386        // DML statements.
387        Statement::Copy(stmt) => dml::plan_copy(scx, stmt),
388        Statement::Delete(stmt) => dml::plan_delete(scx, stmt, params),
389        Statement::ExplainPlan(stmt) => dml::plan_explain_plan(scx, stmt, params),
390        Statement::ExplainPushdown(stmt) => dml::plan_explain_pushdown(scx, stmt, params),
391        Statement::ExplainAnalyzeObject(stmt) => {
392            dml::plan_explain_analyze_object(scx, stmt, params)
393        }
394        Statement::ExplainAnalyzeCluster(stmt) => {
395            dml::plan_explain_analyze_cluster(scx, stmt, params)
396        }
397        Statement::ExplainTimestamp(stmt) => dml::plan_explain_timestamp(scx, stmt),
398        Statement::ExplainSinkSchema(stmt) => dml::plan_explain_schema(scx, stmt),
399        Statement::Insert(stmt) => dml::plan_insert(scx, stmt, params),
400        Statement::Select(stmt) => dml::plan_select(scx, stmt, params, None),
401        Statement::Subscribe(stmt) => dml::plan_subscribe(scx, stmt, params, None),
402        Statement::Update(stmt) => dml::plan_update(scx, stmt, params),
403
404        // `SHOW` statements.
405        Statement::Show(ShowStatement::ShowColumns(stmt)) => show::show_columns(scx, stmt)?.plan(),
406        Statement::Show(ShowStatement::ShowCreateConnection(stmt)) => {
407            show::plan_show_create_connection(scx, stmt).map(Plan::ShowCreate)
408        }
409        Statement::Show(ShowStatement::ShowCreateCluster(stmt)) => {
410            show::plan_show_create_cluster(scx, stmt).map(Plan::ShowCreate)
411        }
412        Statement::Show(ShowStatement::ShowCreateIndex(stmt)) => {
413            show::plan_show_create_index(scx, stmt).map(Plan::ShowCreate)
414        }
415        Statement::Show(ShowStatement::ShowCreateSink(stmt)) => {
416            show::plan_show_create_sink(scx, stmt).map(Plan::ShowCreate)
417        }
418        Statement::Show(ShowStatement::ShowCreateMetricSink(stmt)) => {
419            show::plan_show_create_metric_sink(scx, stmt).map(Plan::ShowCreate)
420        }
421        Statement::Show(ShowStatement::ShowCreateSource(stmt)) => {
422            show::plan_show_create_source(scx, stmt).map(Plan::ShowCreate)
423        }
424        Statement::Show(ShowStatement::ShowCreateTable(stmt)) => {
425            show::plan_show_create_table(scx, stmt).map(Plan::ShowCreate)
426        }
427        Statement::Show(ShowStatement::ShowCreateView(stmt)) => {
428            show::plan_show_create_view(scx, stmt).map(Plan::ShowCreate)
429        }
430        Statement::Show(ShowStatement::ShowCreateMaterializedView(stmt)) => {
431            show::plan_show_create_materialized_view(scx, stmt).map(Plan::ShowCreate)
432        }
433        Statement::Show(ShowStatement::ShowCreateType(stmt)) => {
434            show::plan_show_create_type(scx, stmt).map(Plan::ShowCreate)
435        }
436        Statement::Show(ShowStatement::ShowObjects(stmt)) => show::show_objects(scx, stmt)?.plan(),
437
438        // SCL statements.
439        Statement::Close(stmt) => scl::plan_close(scx, stmt),
440        Statement::Deallocate(stmt) => scl::plan_deallocate(scx, stmt),
441        Statement::Declare(stmt) => scl::plan_declare(scx, stmt, params),
442        Statement::Discard(stmt) => scl::plan_discard(scx, stmt),
443        Statement::Execute(stmt) => scl::plan_execute(scx, stmt),
444        Statement::Fetch(stmt) => scl::plan_fetch(scx, stmt),
445        Statement::Prepare(stmt) => scl::plan_prepare(scx, stmt),
446        Statement::ResetVariable(stmt) => scl::plan_reset_variable(scx, stmt),
447        Statement::SetVariable(stmt) => scl::plan_set_variable(scx, stmt),
448        Statement::Show(ShowStatement::ShowVariable(stmt)) => scl::plan_show_variable(scx, stmt),
449
450        // TCL statements.
451        Statement::Commit(stmt) => tcl::plan_commit(scx, stmt),
452        Statement::Rollback(stmt) => tcl::plan_rollback(scx, stmt),
453        Statement::SetTransaction(stmt) => tcl::plan_set_transaction(scx, stmt),
454        Statement::StartTransaction(stmt) => tcl::plan_start_transaction(scx, stmt),
455
456        // Other statements.
457        Statement::Raise(stmt) => raise::plan_raise(scx, stmt),
458        Statement::Show(ShowStatement::InspectShard(stmt)) => scl::plan_inspect_shard(scx, stmt),
459        Statement::ValidateConnection(stmt) => validate::plan_validate_connection(scx, stmt),
460        Statement::ExecuteUnitTest(_) => {
461            return Err(PlanError::Unsupported {
462                feature: "EXECUTE UNIT TEST statement".to_string(),
463                discussion_no: None,
464            });
465        }
466    };
467
468    if let Ok(plan) = &plan {
469        mz_ore::soft_assert_no_log!(
470            permitted_plans.contains(&PlanKind::from(plan)),
471            "plan {:?}, permitted plans {:?}",
472            plan,
473            permitted_plans
474        );
475    }
476
477    // Return the plan along with any resolved IDs accumulated from sql_impl
478    // function bodies. These are kept separate from the main resolved_ids
479    // because they are implementation details of the functions, not real
480    // dependencies of the statement. They should only be used for the
481    // restrict_to_user_objects RBAC check.
482    let sql_impl_ids = scx
483        .sql_impl_resolved_ids
484        .lock()
485        .expect("planning is single-threaded")
486        .clone();
487    plan.map(|p| (p, sql_impl_ids))
488}
489
490pub fn plan_copy_from(
491    pcx: &PlanContext,
492    catalog: &dyn SessionCatalog,
493    target_id: CatalogItemId,
494    target_name: String,
495    columns: Vec<ColumnIndex>,
496    rows: Vec<mz_repr::Row>,
497) -> Result<super::HirRelationExpr, PlanError> {
498    query::plan_copy_from_rows(pcx, catalog, target_id, target_name, columns, rows)
499}
500
501/// Whether a SQL object type can be interpreted as matching the type of the given catalog item.
502/// For example, if `v` is a view, `DROP SOURCE v` should not work, since Source and View
503/// are non-matching types.
504///
505/// For now tables are treated as a special kind of source in Materialize, so just
506/// allow `TABLE` to refer to either.
507impl PartialEq<ObjectType> for CatalogItemType {
508    fn eq(&self, other: &ObjectType) -> bool {
509        match (self, other) {
510            (CatalogItemType::Source, ObjectType::Source)
511            | (CatalogItemType::Table, ObjectType::Table)
512            | (CatalogItemType::Sink, ObjectType::Sink)
513            | (CatalogItemType::MetricSink, ObjectType::MetricSink)
514            | (CatalogItemType::View, ObjectType::View)
515            | (CatalogItemType::MaterializedView, ObjectType::MaterializedView)
516            | (CatalogItemType::Index, ObjectType::Index)
517            | (CatalogItemType::Type, ObjectType::Type)
518            | (CatalogItemType::Secret, ObjectType::Secret)
519            | (CatalogItemType::Connection, ObjectType::Connection) => true,
520            (_, _) => false,
521        }
522    }
523}
524
525impl PartialEq<CatalogItemType> for ObjectType {
526    fn eq(&self, other: &CatalogItemType) -> bool {
527        other == self
528    }
529}
530
531/// Immutable state that applies to the planning of an entire `Statement`.
532#[derive(Debug, Clone)]
533pub struct StatementContext<'a> {
534    /// The optional PlanContext, which will be present for statements that execute
535    /// within the OneShot QueryLifetime and None otherwise (views). This is an
536    /// awkward field and should probably be relocated to a place that fits our
537    /// execution model more closely.
538    pcx: Option<&'a PlanContext>,
539    pub catalog: &'a dyn SessionCatalog,
540    /// The types of the parameters in the query. This is filled in as planning
541    /// occurs.
542    pub param_types: RefCell<BTreeMap<usize, SqlScalarType>>,
543    /// Whether the statement contains an expression that can make the exact column list
544    /// ambiguous. For example `NATURAL JOIN` or `SELECT *`. This is filled in as planning occurs.
545    pub ambiguous_columns: RefCell<bool>,
546    /// Accumulates resolved IDs from SQL-implemented function bodies (`sql_impl_func`,
547    /// `sql_impl_table_func`). These are kept separate from the statement's main
548    /// `resolved_ids` because they are implementation details of the functions, not
549    /// real dependencies of the statement. They are only used for the
550    /// `restrict_to_user_objects` RBAC check.
551    ///
552    /// Uses `Arc<Mutex<_>>` so that cloned `StatementContext`s (as in `sql_impl`)
553    /// share the same underlying storage. `Arc` (vs `Rc`) is needed because
554    /// `StatementContext` must be `Send`.
555    pub sql_impl_resolved_ids: Arc<Mutex<ResolvedIds>>,
556}
557
558impl<'a> StatementContext<'a> {
559    pub fn new(
560        pcx: Option<&'a PlanContext>,
561        catalog: &'a dyn SessionCatalog,
562    ) -> StatementContext<'a> {
563        StatementContext {
564            pcx,
565            catalog,
566            param_types: Default::default(),
567            ambiguous_columns: RefCell::new(false),
568            sql_impl_resolved_ids: Arc::new(Mutex::new(ResolvedIds::empty())),
569        }
570    }
571
572    /// Returns the schemas in order of search_path that exist in the catalog.
573    pub fn current_schemas(&self) -> &[(ResolvedDatabaseSpecifier, SchemaSpecifier)] {
574        self.catalog.search_path()
575    }
576
577    /// Returns the first schema from the search_path that exist in the catalog,
578    /// or None if there are none.
579    pub fn current_schema(&self) -> Option<&(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
580        self.current_schemas().into_iter().next()
581    }
582
583    pub fn pcx(&self) -> Result<&PlanContext, PlanError> {
584        self.pcx.ok_or_else(|| sql_err!("no plan context"))
585    }
586
587    /// Records resolved IDs from a SQL-implemented expression body (e.g. a SHOW
588    /// command's inner query or an EXPLAIN ANALYZE query) into the accumulator
589    /// checked by `restrict_to_user_objects`. These are kept separate from the
590    /// statement's main `resolved_ids` because they are implementation details,
591    /// not real dependencies.
592    pub(crate) fn record_sql_impl_ids(&self, ids: &ResolvedIds) {
593        self.sql_impl_resolved_ids
594            .lock()
595            .expect("planning is single-threaded")
596            .extend_from(ids);
597    }
598
599    pub fn allocate_full_name(&self, name: PartialItemName) -> Result<FullItemName, PlanError> {
600        let (database, schema): (RawDatabaseSpecifier, String) = match (name.database, name.schema)
601        {
602            (None, None) => {
603                let Some((database, schema)) = self.current_schema() else {
604                    return Err(PlanError::InvalidSchemaName);
605                };
606                let schema = self.get_schema(database, schema);
607                let database = match schema.database() {
608                    ResolvedDatabaseSpecifier::Ambient => RawDatabaseSpecifier::Ambient,
609                    ResolvedDatabaseSpecifier::Id(id) => {
610                        RawDatabaseSpecifier::Name(self.catalog.get_database(id).name().to_string())
611                    }
612                };
613                (database, schema.name().schema.clone())
614            }
615            (None, Some(schema)) => {
616                if is_system_schema(&schema) {
617                    (RawDatabaseSpecifier::Ambient, schema)
618                } else {
619                    match self.catalog.active_database_name() {
620                        Some(name) => (RawDatabaseSpecifier::Name(name.to_string()), schema),
621                        None => {
622                            sql_bail!(
623                                "no database specified for non-system schema and no active database"
624                            )
625                        }
626                    }
627                }
628            }
629            (Some(_database), None) => {
630                // This shouldn't be possible. Refactor the datastructure to
631                // make it not exist.
632                sql_bail!("unreachable: specified the database but no schema")
633            }
634            (Some(database), Some(schema)) => (RawDatabaseSpecifier::Name(database), schema),
635        };
636        let item = name.item;
637        Ok(FullItemName {
638            database,
639            schema,
640            item,
641        })
642    }
643
644    pub fn allocate_qualified_name(
645        &self,
646        name: PartialItemName,
647    ) -> Result<QualifiedItemName, PlanError> {
648        let full_name = self.allocate_full_name(name)?;
649        let database_spec = match full_name.database {
650            RawDatabaseSpecifier::Ambient => ResolvedDatabaseSpecifier::Ambient,
651            RawDatabaseSpecifier::Name(name) => ResolvedDatabaseSpecifier::Id(
652                self.resolve_database(&UnresolvedDatabaseName(Ident::new(name)?))?
653                    .id(),
654            ),
655        };
656        let schema_spec = self
657            .resolve_schema_in_database(&database_spec, &Ident::new(full_name.schema)?)?
658            .id()
659            .clone();
660        Ok(QualifiedItemName {
661            qualifiers: ItemQualifiers {
662                database_spec,
663                schema_spec,
664            },
665            item: full_name.item,
666        })
667    }
668
669    pub fn allocate_temporary_full_name(&self, name: PartialItemName) -> FullItemName {
670        FullItemName {
671            database: RawDatabaseSpecifier::Ambient,
672            schema: name
673                .schema
674                .unwrap_or_else(|| mz_repr::namespaces::MZ_TEMP_SCHEMA.to_owned()),
675            item: name.item,
676        }
677    }
678
679    pub fn allocate_temporary_qualified_name(
680        &self,
681        name: PartialItemName,
682    ) -> Result<QualifiedItemName, PlanError> {
683        // Compare against the MZ_TEMP_SCHEMA constant directly instead of calling
684        // get_schema(), because with lazy temporary schema creation, the temp
685        // schema may not exist yet. (This is similar to what `allocate_temporary_full_name` was
686        // doing already before making temporary schemas lazy.)
687        if let Some(schema_name) = name.schema {
688            if schema_name != mz_repr::namespaces::MZ_TEMP_SCHEMA {
689                return Err(PlanError::InvalidTemporarySchema);
690            }
691        }
692
693        Ok(QualifiedItemName {
694            qualifiers: ItemQualifiers {
695                database_spec: ResolvedDatabaseSpecifier::Ambient,
696                schema_spec: SchemaSpecifier::Temporary,
697            },
698            item: name.item,
699        })
700    }
701
702    // Creates a `ResolvedItemName::Item` from a `GlobalId` and an
703    // `UnresolvedItemName`.
704    pub fn allocate_resolved_item_name(
705        &self,
706        id: CatalogItemId,
707        name: UnresolvedItemName,
708    ) -> Result<ResolvedItemName, PlanError> {
709        let partial = normalize::unresolved_item_name(name)?;
710        let qualified = self.allocate_qualified_name(partial.clone())?;
711        let full_name = self.allocate_full_name(partial)?;
712        Ok(ResolvedItemName::Item {
713            id,
714            qualifiers: qualified.qualifiers,
715            full_name,
716            print_id: true,
717            version: RelationVersionSelector::Latest,
718        })
719    }
720
721    pub fn active_database(&self) -> Option<&DatabaseId> {
722        self.catalog.active_database()
723    }
724
725    pub fn resolve_optional_schema(
726        &self,
727        schema_name: &Option<ResolvedSchemaName>,
728    ) -> Result<SchemaSpecifier, PlanError> {
729        match schema_name {
730            Some(ResolvedSchemaName::Schema { schema_spec, .. }) => Ok(schema_spec.clone()),
731            None => self.resolve_active_schema().map(|spec| spec.clone()),
732            Some(ResolvedSchemaName::Error) => {
733                unreachable!("should have been handled by name resolution")
734            }
735        }
736    }
737
738    pub fn resolve_active_schema(&self) -> Result<&SchemaSpecifier, PlanError> {
739        match self.current_schema() {
740            Some((_db, schema)) => Ok(schema),
741            None => Err(PlanError::InvalidSchemaName),
742        }
743    }
744
745    pub fn get_cluster(&self, id: &ClusterId) -> &dyn CatalogCluster<'_> {
746        self.catalog.get_cluster(*id)
747    }
748
749    pub fn resolve_database(
750        &self,
751        name: &UnresolvedDatabaseName,
752    ) -> Result<&dyn CatalogDatabase, PlanError> {
753        let name = normalize::ident_ref(&name.0);
754        Ok(self.catalog.resolve_database(name)?)
755    }
756
757    pub fn get_database(&self, id: &DatabaseId) -> &dyn CatalogDatabase {
758        self.catalog.get_database(id)
759    }
760
761    pub fn resolve_schema_in_database(
762        &self,
763        database_spec: &ResolvedDatabaseSpecifier,
764        schema: &Ident,
765    ) -> Result<&dyn CatalogSchema, PlanError> {
766        let schema = normalize::ident_ref(schema);
767        Ok(self
768            .catalog
769            .resolve_schema_in_database(database_spec, schema)?)
770    }
771
772    pub fn resolve_schema(
773        &self,
774        name: UnresolvedSchemaName,
775    ) -> Result<&dyn CatalogSchema, PlanError> {
776        let name = normalize::unresolved_schema_name(name)?;
777        Ok(self
778            .catalog
779            .resolve_schema(name.database.as_deref(), &name.schema)?)
780    }
781
782    pub fn get_schema(
783        &self,
784        database_spec: &ResolvedDatabaseSpecifier,
785        schema_spec: &SchemaSpecifier,
786    ) -> &dyn CatalogSchema {
787        self.catalog.get_schema(database_spec, schema_spec)
788    }
789
790    pub fn resolve_item(&self, name: RawItemName) -> Result<&dyn CatalogItem, PlanError> {
791        match name {
792            RawItemName::Name(name) => {
793                let name = normalize::unresolved_item_name(name)?;
794                Ok(self.catalog.resolve_item(&name)?)
795            }
796            RawItemName::Id(id, _, _) => {
797                let gid = id.parse()?;
798                Ok(self.catalog.get_item(&gid))
799            }
800        }
801    }
802
803    pub fn get_item(&self, id: &CatalogItemId) -> &dyn CatalogItem {
804        self.catalog.get_item(id)
805    }
806
807    pub fn get_item_by_resolved_name(
808        &self,
809        name: &ResolvedItemName,
810    ) -> Result<Box<dyn CatalogCollectionItem + '_>, PlanError> {
811        match name {
812            ResolvedItemName::Item { id, version, .. } => {
813                Ok(self.get_item(id).at_version(*version))
814            }
815            ResolvedItemName::Cte { .. } => sql_bail!("non-user item"),
816            ResolvedItemName::Error => unreachable!("should have been caught in name resolution"),
817        }
818    }
819
820    pub fn get_column_by_resolved_name(
821        &self,
822        name: &ColumnName<Aug>,
823    ) -> Result<(Box<dyn CatalogCollectionItem + '_>, usize), PlanError> {
824        match (&name.relation, &name.column) {
825            (
826                ResolvedItemName::Item { id, version, .. },
827                ResolvedColumnReference::Column { index, .. },
828            ) => {
829                let item = self.get_item(id).at_version(*version);
830                Ok((item, *index))
831            }
832            _ => unreachable!(
833                "get_column_by_resolved_name errors should have been caught in name resolution"
834            ),
835        }
836    }
837
838    pub fn resolve_function(
839        &self,
840        name: UnresolvedItemName,
841    ) -> Result<&dyn CatalogItem, PlanError> {
842        let name = normalize::unresolved_item_name(name)?;
843        Ok(self.catalog.resolve_function(&name)?)
844    }
845
846    pub fn resolve_cluster(
847        &self,
848        name: Option<&Ident>,
849    ) -> Result<&dyn CatalogCluster<'_>, PlanError> {
850        let name = name.map(|name| name.as_str());
851        Ok(self.catalog.resolve_cluster(name)?)
852    }
853
854    pub fn resolve_type(&self, mut ty: mz_pgrepr::Type) -> Result<ResolvedDataType, PlanError> {
855        // Ignore precision constraints on date/time types until we support
856        // it. This should be safe enough because our types are wide enough
857        // to support the maximum possible precision.
858        //
859        // See: https://github.com/MaterializeInc/database-issues/issues/3179
860        match &mut ty {
861            mz_pgrepr::Type::Interval { constraints } => *constraints = None,
862            mz_pgrepr::Type::Time { precision } => *precision = None,
863            mz_pgrepr::Type::TimeTz { precision } => *precision = None,
864            mz_pgrepr::Type::Timestamp { precision } => *precision = None,
865            mz_pgrepr::Type::TimestampTz { precision } => *precision = None,
866            _ => (),
867        }
868        // NOTE(benesch): this *looks* gross, but it is
869        // safe enough. The `fmt::Display`
870        // representation on `pgrepr::Type` promises to
871        // produce an unqualified type name that does
872        // not require quoting.
873        let mut ty = if ty.oid() >= FIRST_USER_OID {
874            sql_bail!("internal error, unexpected user type: {ty:?} ");
875        } else if ty.oid() < FIRST_MATERIALIZE_OID {
876            format!("pg_catalog.{}", ty)
877        } else {
878            // This relies on all non-PG types existing in `mz_catalog`, which is annoying.
879            format!("mz_catalog.{}", ty)
880        };
881        // TODO(benesch): converting `json` to `jsonb`
882        // is wrong. We ought to support the `json` type
883        // directly.
884        if ty == "pg_catalog.json" {
885            ty = "pg_catalog.jsonb".into();
886        }
887        let data_type = mz_sql_parser::parser::parse_data_type(&ty)?;
888        let (data_type, _) = names::resolve(self.catalog, data_type)?;
889        Ok(data_type)
890    }
891
892    pub fn get_object_type(&self, id: &ObjectId) -> ObjectType {
893        self.catalog.get_object_type(id)
894    }
895
896    pub fn get_system_object_type(&self, id: &SystemObjectId) -> SystemObjectType {
897        match id {
898            SystemObjectId::Object(id) => SystemObjectType::Object(self.get_object_type(id)),
899            SystemObjectId::System => SystemObjectType::System,
900        }
901    }
902
903    /// Returns an error if the named `FeatureFlag` is not set to `on`.
904    pub fn require_feature_flag(&self, flag: &'static FeatureFlag) -> Result<(), PlanError> {
905        flag.require(self.catalog.system_vars())?;
906        Ok(())
907    }
908
909    /// Returns true if the named [`FeatureFlag`] is set to `on`, returns false otherwise.
910    pub fn is_feature_flag_enabled(&self, flag: &'static FeatureFlag) -> bool {
911        self.require_feature_flag(flag).is_ok()
912    }
913
914    pub fn finalize_param_types(self) -> Result<Vec<SqlScalarType>, PlanError> {
915        let param_types = self.param_types.into_inner();
916        let mut out = vec![];
917        for (i, (n, typ)) in param_types.into_iter().enumerate() {
918            if n != i + 1 {
919                sql_bail!("unable to infer type for parameter ${}", i + 1);
920            }
921            out.push(typ);
922        }
923        Ok(out)
924    }
925
926    /// The returned String is more detailed when the `postgres_compat` flag is not set. However,
927    /// the flag should be set in, e.g., the implementation of the `pg_typeof` function.
928    pub fn humanize_sql_scalar_type(&self, typ: &SqlScalarType, postgres_compat: bool) -> String {
929        self.catalog.humanize_sql_scalar_type(typ, postgres_compat)
930    }
931
932    /// The returned String is more detailed when the `postgres_compat` flag is not set. However,
933    /// the flag should be set in, e.g., the implementation of the `pg_typeof` function.
934    pub fn humanize_column_type(&self, typ: &SqlColumnType, postgres_compat: bool) -> String {
935        self.catalog.humanize_sql_column_type(typ, postgres_compat)
936    }
937
938    pub fn relation_desc_into_table_defs(
939        &self,
940        desc: &RelationDesc,
941    ) -> Result<(Vec<ColumnDef<Aug>>, Vec<TableConstraint<Aug>>), PlanError> {
942        let mut columns = vec![];
943        let mut null_cols = BTreeSet::new();
944        for (column_name, column_type) in desc.iter() {
945            let name = Ident::new(column_name.as_str().to_owned())?;
946
947            let ty = mz_pgrepr::Type::from(&column_type.scalar_type);
948            let data_type = self.resolve_type(ty)?;
949
950            let options = if !column_type.nullable {
951                null_cols.insert(columns.len());
952                vec![mz_sql_parser::ast::ColumnOptionDef {
953                    name: None,
954                    option: mz_sql_parser::ast::ColumnOption::NotNull,
955                }]
956            } else {
957                vec![]
958            };
959
960            columns.push(ColumnDef {
961                name,
962                data_type,
963                collation: None,
964                options,
965            });
966        }
967
968        let mut table_constraints = vec![];
969        for key in desc.typ().keys.iter() {
970            let mut col_names = vec![];
971            for col_idx in key {
972                if !null_cols.contains(col_idx) {
973                    // Note that alternatively we could support NULL values in keys with `NULLS NOT
974                    // DISTINCT` semantics, which treats `NULL` as a distinct value.
975                    sql_bail!(
976                        "[internal error] key columns must be NOT NULL when generating table constraints"
977                    );
978                }
979                col_names.push(columns[*col_idx].name.clone());
980            }
981            table_constraints.push(TableConstraint::Unique {
982                name: None,
983                columns: col_names,
984                is_primary: false,
985                nulls_not_distinct: false,
986            });
987        }
988
989        Ok((columns, table_constraints))
990    }
991
992    pub fn get_owner_id(&self, id: &ObjectId) -> Option<RoleId> {
993        self.catalog.get_owner_id(id)
994    }
995
996    pub fn humanize_resolved_name(
997        &self,
998        name: &ResolvedItemName,
999    ) -> Result<PartialItemName, PlanError> {
1000        let item = self.get_item_by_resolved_name(name)?;
1001        Ok(self.catalog.minimal_qualification(item.name()))
1002    }
1003
1004    /// WARNING! This style of name resolution assumes the referred-to objects exists (i.e. panics
1005    /// if objects do not exist) so should never be used to handle user input.
1006    pub fn dangerous_resolve_name(&self, name: Vec<&str>) -> ResolvedItemName {
1007        tracing::trace!("dangerous_resolve_name {:?}", name);
1008        // Note: Using unchecked here is okay because this function is already dangerous.
1009        let name: Vec<_> = name.into_iter().map(Ident::new_unchecked).collect();
1010        let name = UnresolvedItemName::qualified(&name);
1011        let entry = match self.resolve_item(RawItemName::Name(name.clone())) {
1012            Ok(entry) => entry,
1013            Err(_) => self
1014                .resolve_function(name.clone())
1015                .expect("name referred to an existing object"),
1016        };
1017
1018        let partial = normalize::unresolved_item_name(name).unwrap();
1019        let full_name = self.allocate_full_name(partial).unwrap();
1020
1021        ResolvedItemName::Item {
1022            id: entry.id(),
1023            qualifiers: entry.name().qualifiers.clone(),
1024            full_name,
1025            print_id: true,
1026            version: RelationVersionSelector::Latest,
1027        }
1028    }
1029}
1030
1031pub fn resolve_cluster_for_materialized_view<'a>(
1032    catalog: &'a dyn SessionCatalog,
1033    stmt: &CreateMaterializedViewStatement<Aug>,
1034) -> Result<ClusterId, PlanError> {
1035    Ok(match &stmt.in_cluster {
1036        None => catalog.resolve_cluster(None)?.id(),
1037        Some(in_cluster) => in_cluster.id,
1038    })
1039}
1040
1041/// Statement classification as documented by [`plan`].
1042#[derive(Debug, Clone, Copy)]
1043pub enum StatementClassification {
1044    ACL,
1045    DDL,
1046    DML,
1047    Other,
1048    SCL,
1049    Show,
1050    TCL,
1051}
1052
1053impl StatementClassification {
1054    pub fn is_ddl(&self) -> bool {
1055        matches!(self, StatementClassification::DDL)
1056    }
1057}
1058
1059impl<T: mz_sql_parser::ast::AstInfo> From<&Statement<T>> for StatementClassification {
1060    fn from(value: &Statement<T>) -> Self {
1061        use StatementClassification::*;
1062
1063        match value {
1064            // DDL statements.
1065            Statement::AlterCluster(_) => DDL,
1066            Statement::AlterConnection(_) => DDL,
1067            Statement::AlterIndex(_) => DDL,
1068            Statement::AlterMaterializedViewApplyReplacement(_) => DDL,
1069            Statement::AlterObjectRename(_) => DDL,
1070            Statement::AlterObjectSwap(_) => DDL,
1071            Statement::AlterNetworkPolicy(_) => DDL,
1072            Statement::AlterRetainHistory(_) => DDL,
1073            Statement::AlterRole(_) => DDL,
1074            Statement::AlterSecret(_) => DDL,
1075            Statement::AlterSetCluster(_) => DDL,
1076            Statement::AlterSink(_) => DDL,
1077            Statement::AlterSource(_) => DDL,
1078            Statement::AlterSystemSet(_) => DDL,
1079            Statement::AlterSystemReset(_) => DDL,
1080            Statement::AlterSystemResetAll(_) => DDL,
1081            Statement::AlterTableAddColumn(_) => DDL,
1082            Statement::Comment(_) => DDL,
1083            Statement::CreateCluster(_) => DDL,
1084            Statement::CreateClusterReplica(_) => DDL,
1085            Statement::CreateConnection(_) => DDL,
1086            Statement::CreateDatabase(_) => DDL,
1087            Statement::CreateIndex(_) => DDL,
1088            Statement::CreateRole(_) => DDL,
1089            Statement::CreateSchema(_) => DDL,
1090            Statement::CreateSecret(_) => DDL,
1091            Statement::CreateSink(_) => DDL,
1092            Statement::CreateMetricSink(_) => DDL,
1093            Statement::CreateWebhookSource(_) => DDL,
1094            Statement::CreateSource(_) => DDL,
1095            Statement::CreateSubsource(_) => DDL,
1096            Statement::CreateTable(_) => DDL,
1097            Statement::CreateTableFromSource(_) => DDL,
1098            Statement::CreateType(_) => DDL,
1099            Statement::CreateView(_) => DDL,
1100            Statement::CreateMaterializedView(_) => DDL,
1101            Statement::CreateNetworkPolicy(_) => DDL,
1102            Statement::DropObjects(_) => DDL,
1103            Statement::DropOwned(_) => DDL,
1104
1105            // `ACL` statements.
1106            Statement::AlterOwner(_) => ACL,
1107            Statement::GrantRole(_) => ACL,
1108            Statement::RevokeRole(_) => ACL,
1109            Statement::GrantPrivileges(_) => ACL,
1110            Statement::RevokePrivileges(_) => ACL,
1111            Statement::AlterDefaultPrivileges(_) => ACL,
1112            Statement::ReassignOwned(_) => ACL,
1113
1114            // DML statements.
1115            Statement::Copy(_) => DML,
1116            Statement::Delete(_) => DML,
1117            Statement::ExplainPlan(_) => DML,
1118            Statement::ExplainPushdown(_) => DML,
1119            Statement::ExplainAnalyzeObject(_) => DML,
1120            Statement::ExplainAnalyzeCluster(_) => DML,
1121            Statement::ExplainTimestamp(_) => DML,
1122            Statement::ExplainSinkSchema(_) => DML,
1123            Statement::Insert(_) => DML,
1124            Statement::Select(_) => DML,
1125            Statement::Subscribe(_) => DML,
1126            Statement::Update(_) => DML,
1127
1128            // `SHOW` statements.
1129            Statement::Show(ShowStatement::ShowColumns(_)) => Show,
1130            Statement::Show(ShowStatement::ShowCreateConnection(_)) => Show,
1131            Statement::Show(ShowStatement::ShowCreateCluster(_)) => Show,
1132            Statement::Show(ShowStatement::ShowCreateIndex(_)) => Show,
1133            Statement::Show(ShowStatement::ShowCreateSink(_)) => Show,
1134            Statement::Show(ShowStatement::ShowCreateMetricSink(_)) => Show,
1135            Statement::Show(ShowStatement::ShowCreateSource(_)) => Show,
1136            Statement::Show(ShowStatement::ShowCreateTable(_)) => Show,
1137            Statement::Show(ShowStatement::ShowCreateView(_)) => Show,
1138            Statement::Show(ShowStatement::ShowCreateMaterializedView(_)) => Show,
1139            Statement::Show(ShowStatement::ShowCreateType(_)) => Show,
1140            Statement::Show(ShowStatement::ShowObjects(_)) => Show,
1141
1142            // SCL statements.
1143            Statement::Close(_) => SCL,
1144            Statement::Deallocate(_) => SCL,
1145            Statement::Declare(_) => SCL,
1146            Statement::Discard(_) => SCL,
1147            Statement::Execute(_) => SCL,
1148            Statement::Fetch(_) => SCL,
1149            Statement::Prepare(_) => SCL,
1150            Statement::ResetVariable(_) => SCL,
1151            Statement::SetVariable(_) => SCL,
1152            Statement::Show(ShowStatement::ShowVariable(_)) => SCL,
1153
1154            // TCL statements.
1155            Statement::Commit(_) => TCL,
1156            Statement::Rollback(_) => TCL,
1157            Statement::SetTransaction(_) => TCL,
1158            Statement::StartTransaction(_) => TCL,
1159
1160            // Other statements.
1161            Statement::Raise(_) => Other,
1162            Statement::Show(ShowStatement::InspectShard(_)) => Other,
1163            Statement::ValidateConnection(_) => Other,
1164            Statement::ExecuteUnitTest(_) => Other,
1165        }
1166    }
1167}