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