Skip to main content

mz_sql/plan/statement/
scl.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//! Session control language (SCL).
11//!
12//! This module houses the handlers for statements that manipulate the session,
13//! like `DISCARD` and `SET`.
14
15use mz_ore::str::StrExt;
16use mz_repr::{CatalogItemId, RelationDesc, RelationVersionSelector, SqlScalarType};
17use mz_sql_parser::ast::InspectShardStatement;
18use std::time::Duration;
19use uncased::UncasedStr;
20
21use crate::ast::display::AstDisplay;
22use crate::ast::{
23    CloseStatement, DeallocateStatement, DeclareStatement, DiscardStatement, DiscardTarget,
24    ExecuteStatement, FetchOption, FetchOptionName, FetchStatement, Ident, PrepareStatement,
25    ResetVariableStatement, SetVariableStatement, SetVariableTo, ShowVariableStatement,
26};
27use crate::names::{self, Aug};
28use crate::plan::statement::{StatementContext, StatementDesc};
29use crate::plan::{
30    ClosePlan, DeallocatePlan, DeclarePlan, ExecutePlan, ExecuteTimeout, FetchPlan,
31    InspectShardPlan, Params, Plan, PlanError, PreparePlan, ResetVariablePlan, SetVariablePlan,
32    ShowVariablePlan, VariableValue, describe, query,
33};
34use crate::session::vars;
35use crate::session::vars::{SCHEMA_ALIAS, VarInput};
36
37pub fn describe_set_variable(
38    _: &StatementContext,
39    _: SetVariableStatement,
40) -> Result<StatementDesc, PlanError> {
41    Ok(StatementDesc::new(None))
42}
43
44pub fn plan_set_variable(
45    scx: &StatementContext,
46    SetVariableStatement {
47        local,
48        variable,
49        to,
50    }: SetVariableStatement,
51) -> Result<Plan, PlanError> {
52    let value = plan_set_variable_to(to)?;
53    let name = variable.into_string();
54
55    // Gate feature-flagged isolation levels at plan time. The same check runs in
56    // `SessionVars::set`, which also covers `ALTER ROLE ... SET` and connection
57    // options; running it here too surfaces the error before sequencing.
58    if let VariableValue::Values(values) = &value {
59        vars::check_transaction_isolation_feature_flag(
60            &name,
61            VarInput::SqlSet(values),
62            scx.catalog.system_vars(),
63        )?;
64    }
65
66    Ok(Plan::SetVariable(SetVariablePlan { name, value, local }))
67}
68
69pub fn plan_set_variable_to(to: SetVariableTo) -> Result<VariableValue, PlanError> {
70    match to {
71        SetVariableTo::Default => Ok(VariableValue::Default),
72        SetVariableTo::Values(values) => {
73            // Per PostgreSQL, string literals and identifiers are treated
74            // equivalently during `SET`. We retain only the underlying string
75            // value of each element in the list. It's our caller's
76            // responsibility to figure out how to set the variable to the
77            // provided list of values using variable-specific logic.
78            let values = values
79                .into_iter()
80                .map(|v| v.into_unquoted_value())
81                .collect();
82            Ok(VariableValue::Values(values))
83        }
84    }
85}
86
87pub fn describe_reset_variable(
88    _: &StatementContext,
89    _: ResetVariableStatement,
90) -> Result<StatementDesc, PlanError> {
91    Ok(StatementDesc::new(None))
92}
93
94pub fn plan_reset_variable(
95    _: &StatementContext,
96    ResetVariableStatement { variable }: ResetVariableStatement,
97) -> Result<Plan, PlanError> {
98    Ok(Plan::ResetVariable(ResetVariablePlan {
99        name: variable.to_string(),
100    }))
101}
102
103pub fn describe_show_variable(
104    _: &StatementContext,
105    ShowVariableStatement { variable, .. }: ShowVariableStatement,
106) -> Result<StatementDesc, PlanError> {
107    let desc = if variable.as_str() == UncasedStr::new("ALL") {
108        RelationDesc::builder()
109            .with_column("name", SqlScalarType::String.nullable(false))
110            .with_column("setting", SqlScalarType::String.nullable(false))
111            .with_column("description", SqlScalarType::String.nullable(false))
112            .finish()
113    } else if variable.as_str() == SCHEMA_ALIAS {
114        RelationDesc::builder()
115            .with_column(variable.as_str(), SqlScalarType::String.nullable(true))
116            .finish()
117    } else {
118        RelationDesc::builder()
119            .with_column(variable.as_str(), SqlScalarType::String.nullable(false))
120            .finish()
121    };
122    Ok(StatementDesc::new(Some(desc)))
123}
124
125pub fn plan_show_variable(
126    _: &StatementContext,
127    ShowVariableStatement { variable }: ShowVariableStatement,
128) -> Result<Plan, PlanError> {
129    if variable.as_str() == UncasedStr::new("ALL") {
130        Ok(Plan::ShowAllVariables)
131    } else {
132        Ok(Plan::ShowVariable(ShowVariablePlan {
133            name: variable.to_string(),
134        }))
135    }
136}
137
138pub fn describe_inspect_shard(
139    _: &StatementContext,
140    InspectShardStatement { .. }: InspectShardStatement,
141) -> Result<StatementDesc, PlanError> {
142    let desc = RelationDesc::builder()
143        .with_column("state", SqlScalarType::Jsonb.nullable(false))
144        .finish();
145    Ok(StatementDesc::new(Some(desc)))
146}
147
148pub fn plan_inspect_shard(
149    scx: &StatementContext,
150    InspectShardStatement { id }: InspectShardStatement,
151) -> Result<Plan, PlanError> {
152    let id: CatalogItemId = id.parse().map_err(|_| sql_err!("invalid shard id"))?;
153    // Always inspect the shard at the latest GlobalId.
154    let gid = scx
155        .catalog
156        .try_get_item(&id)
157        .ok_or_else(|| sql_err!("item doesn't exist"))?
158        .at_version(RelationVersionSelector::Latest)
159        .global_id();
160    Ok(Plan::InspectShard(InspectShardPlan { id: gid }))
161}
162
163pub fn describe_discard(
164    _: &StatementContext,
165    _: DiscardStatement,
166) -> Result<StatementDesc, PlanError> {
167    Ok(StatementDesc::new(None))
168}
169
170pub fn plan_discard(
171    _: &StatementContext,
172    DiscardStatement { target }: DiscardStatement,
173) -> Result<Plan, PlanError> {
174    match target {
175        DiscardTarget::All => Ok(Plan::DiscardAll),
176        DiscardTarget::Temp => Ok(Plan::DiscardTemp),
177        DiscardTarget::Sequences => bail_unsupported!("DISCARD SEQUENCES"),
178        DiscardTarget::Plans => bail_unsupported!("DISCARD PLANS"),
179    }
180}
181
182pub fn describe_declare(
183    scx: &StatementContext,
184    DeclareStatement { stmt, .. }: DeclareStatement<Aug>,
185    param_types_in: &[Option<SqlScalarType>],
186) -> Result<StatementDesc, PlanError> {
187    let (stmt_resolved, _) = names::resolve(scx.catalog, *stmt)?;
188    // Get the desc for the inner statement, but only for its parameters. The outer DECLARE doesn't
189    // return any rows itself when executed.
190    let desc = describe(scx.pcx()?, scx.catalog, stmt_resolved, param_types_in)?;
191    // The outer describe fn calls scx.finalize_param_types, so we need to transfer the inner desc's
192    // params to this scx.
193    for (i, ty) in desc.param_types.into_iter().enumerate() {
194        scx.param_types.borrow_mut().insert(i + 1, ty);
195    }
196    Ok(StatementDesc::new(None))
197}
198
199/// The lookup key for the prepared statement or portal that `name` refers to.
200///
201/// Both namespaces are shared with the extended protocol, which stores names
202/// exactly as they arrive on the wire, so the key is the identifier's raw value
203/// rather than its SQL rendering, which re-quotes anything not printable bare.
204fn statement_or_portal_name(name: Ident) -> String {
205    name.into_string()
206}
207
208pub fn plan_declare(
209    _: &StatementContext,
210    DeclareStatement { name, stmt, sql }: DeclareStatement<Aug>,
211    params: &Params,
212) -> Result<Plan, PlanError> {
213    Ok(Plan::Declare(DeclarePlan {
214        name: statement_or_portal_name(name),
215        stmt: *stmt,
216        sql,
217        params: params.clone(),
218    }))
219}
220
221pub fn describe_fetch(
222    scx: &StatementContext,
223    FetchStatement {
224        name,
225        count: _,
226        options: _,
227    }: FetchStatement<Aug>,
228) -> Result<StatementDesc, PlanError> {
229    let name = statement_or_portal_name(name);
230    if let Some(mut desc) = scx.catalog.get_portal_desc_unverified(&name).cloned() {
231        // Parameters are already bound to the portal and will not be accepted through
232        // FETCH.
233        desc.param_types = Vec::new();
234        Ok(desc)
235    } else {
236        Err(PlanError::UnknownCursor(name))
237    }
238}
239
240generate_extracted_config!(FetchOption, (Timeout, Duration));
241
242pub fn plan_fetch(
243    _: &StatementContext,
244    FetchStatement {
245        name,
246        count,
247        options,
248    }: FetchStatement<Aug>,
249) -> Result<Plan, PlanError> {
250    let FetchOptionExtracted { timeout, .. } = options.try_into()?;
251    let timeout = match timeout {
252        Some(timeout) => {
253            // Limit FETCH timeouts to 1 day. If users have a legitimate need it can be
254            // bumped. If we do bump it, ensure that the new upper limit is within the
255            // bounds of a tokio time future, otherwise it'll panic.
256            const DAY: Duration = Duration::from_secs(60 * 60 * 24);
257            if timeout > DAY {
258                sql_bail!("timeout out of range: {}s", timeout.as_secs_f64());
259            }
260            ExecuteTimeout::Seconds(timeout.as_secs_f64())
261        }
262        // FETCH defaults to WaitOnce.
263        None => ExecuteTimeout::WaitOnce,
264    };
265    Ok(Plan::Fetch(FetchPlan {
266        name: statement_or_portal_name(name),
267        count,
268        timeout,
269    }))
270}
271
272pub fn describe_close(_: &StatementContext, _: CloseStatement) -> Result<StatementDesc, PlanError> {
273    Ok(StatementDesc::new(None))
274}
275
276pub fn plan_close(
277    _: &StatementContext,
278    CloseStatement { name }: CloseStatement,
279) -> Result<Plan, PlanError> {
280    Ok(Plan::Close(ClosePlan {
281        name: statement_or_portal_name(name),
282    }))
283}
284
285pub fn describe_prepare(
286    _: &StatementContext,
287    _: PrepareStatement<Aug>,
288) -> Result<StatementDesc, PlanError> {
289    Ok(StatementDesc::new(None))
290}
291
292pub fn plan_prepare(
293    scx: &StatementContext,
294    PrepareStatement { name, stmt, sql }: PrepareStatement<Aug>,
295) -> Result<Plan, PlanError> {
296    // TODO: PREPARE supports specifying param types.
297    let param_types = [];
298    let (stmt_resolved, _) = names::resolve(scx.catalog, *stmt.clone())?;
299    let desc = describe(scx.pcx()?, scx.catalog, stmt_resolved, &param_types)?;
300    Ok(Plan::Prepare(PreparePlan {
301        name: statement_or_portal_name(name),
302        stmt: *stmt,
303        desc,
304        sql,
305    }))
306}
307
308pub fn describe_execute(
309    scx: &StatementContext,
310    stmt: ExecuteStatement<Aug>,
311) -> Result<StatementDesc, PlanError> {
312    // The evaluation of the statement doesn't happen until it gets to coord. That
313    // means if the statement is now invalid due to an object having been dropped,
314    // describe is unable to notice that. This is currently an existing problem
315    // with prepared statements over pgwire as well, so we can leave this for now.
316    // See database-issues#2563.
317    Ok(plan_execute_desc(scx, stmt)?.0.clone())
318}
319
320pub fn plan_execute(
321    scx: &StatementContext,
322    stmt: ExecuteStatement<Aug>,
323) -> Result<Plan, PlanError> {
324    Ok(plan_execute_desc(scx, stmt)?.1)
325}
326
327fn plan_execute_desc<'a>(
328    scx: &'a StatementContext,
329    ExecuteStatement { name, params }: ExecuteStatement<Aug>,
330) -> Result<(&'a StatementDesc, Plan), PlanError> {
331    let name = statement_or_portal_name(name);
332    let desc = match scx.catalog.get_prepared_statement_desc(&name) {
333        Some(desc) => desc,
334        // TODO(mjibson): use CoordError::UnknownPreparedStatement.
335        None => sql_bail!("unknown prepared statement {}", name.quoted()),
336    };
337    Ok((
338        desc,
339        Plan::Execute(ExecutePlan {
340            name,
341            params: query::plan_params(scx, params, desc)?,
342        }),
343    ))
344}
345
346pub fn describe_deallocate(
347    _: &StatementContext,
348    _: DeallocateStatement,
349) -> Result<StatementDesc, PlanError> {
350    Ok(StatementDesc::new(None))
351}
352
353pub fn plan_deallocate(
354    _: &StatementContext,
355    DeallocateStatement { name }: DeallocateStatement,
356) -> Result<Plan, PlanError> {
357    Ok(Plan::Deallocate(DeallocatePlan {
358        name: name.map(statement_or_portal_name),
359    }))
360}