Skip to main content

mz_sql/
normalize.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//! SQL normalization routines.
11//!
12//! Normalization is the process of taking relatively unstructured types from
13//! the [`ast`] module and converting them to more structured types.
14//!
15//! [`ast`]: crate::ast
16
17use std::fmt;
18
19use itertools::Itertools;
20use mz_repr::{ColumnName, GlobalId};
21use mz_sql_parser::ast::display::AstDisplay;
22use mz_sql_parser::ast::visit_mut::{self, VisitMut};
23use mz_sql_parser::ast::{
24    CreateConnectionStatement, CreateIndexStatement, CreateMaterializedViewStatement,
25    CreateMetricSinkStatement, CreateSecretStatement, CreateSinkStatement, CreateSourceStatement,
26    CreateSubsourceStatement, CreateTableFromSourceStatement, CreateTableStatement,
27    CreateTypeStatement, CreateViewStatement, CreateWebhookSourceStatement, CteBlock, Function,
28    FunctionArgs, Ident, IfExistsBehavior, MutRecBlock, Op, Query, Statement, TableFactor,
29    TableFromSourceColumns, UnresolvedItemName, UnresolvedSchemaName, Value, ViewDefinition,
30};
31
32use crate::names::{Aug, FullItemName, PartialItemName, PartialSchemaName, RawDatabaseSpecifier};
33use crate::plan::error::PlanError;
34use crate::plan::statement::StatementContext;
35
36/// Normalizes a single identifier.
37pub fn ident(ident: Ident) -> String {
38    ident.into_string()
39}
40
41/// Normalizes a single identifier.
42pub fn ident_ref(ident: &Ident) -> &str {
43    ident.as_str()
44}
45
46/// Normalizes an identifier that represents a column name.
47pub fn column_name(id: Ident) -> ColumnName {
48    ColumnName::from(ident(id))
49}
50
51/// Normalizes an unresolved object name.
52pub fn unresolved_item_name(mut name: UnresolvedItemName) -> Result<PartialItemName, PlanError> {
53    if name.0.len() < 1 || name.0.len() > 3 {
54        return Err(PlanError::MisqualifiedName(name.to_string()));
55    }
56    let out = PartialItemName {
57        item: ident(
58            name.0
59                .pop()
60                .expect("name checked to have at least one component"),
61        ),
62        schema: name.0.pop().map(ident),
63        database: name.0.pop().map(ident),
64    };
65    assert!(name.0.is_empty());
66    Ok(out)
67}
68
69/// Normalizes an unresolved schema name.
70pub fn unresolved_schema_name(
71    mut name: UnresolvedSchemaName,
72) -> Result<PartialSchemaName, PlanError> {
73    if name.0.len() < 1 || name.0.len() > 2 {
74        return Err(PlanError::MisqualifiedName(name.to_string()));
75    }
76    let out = PartialSchemaName {
77        schema: ident(
78            name.0
79                .pop()
80                .expect("name checked to have at least one component"),
81        ),
82        database: name.0.pop().map(ident),
83    };
84    assert!(name.0.is_empty());
85    Ok(out)
86}
87
88/// Normalizes an operator reference.
89///
90/// Qualified operators outside of the pg_catalog schema are rejected.
91pub fn op(op: &Op) -> Result<&str, PlanError> {
92    if let Some(namespace) = &op.namespace {
93        if namespace.len() != 0
94            && (namespace.len() != 1
95                || namespace[0].as_str() != mz_repr::namespaces::PG_CATALOG_SCHEMA)
96        {
97            sql_bail!(
98                "operator does not exist: {}.{}",
99                namespace.iter().map(|n| n.to_string()).join("."),
100                op.op,
101            )
102        }
103    }
104    Ok(&op.op)
105}
106
107#[derive(Debug, Clone)]
108pub enum SqlValueOrSecret {
109    Value(Value),
110    Secret(GlobalId),
111}
112
113impl fmt::Display for SqlValueOrSecret {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        match self {
116            SqlValueOrSecret::Value(v) => write!(f, "{}", v),
117            SqlValueOrSecret::Secret(id) => write!(f, "{}", id),
118        }
119    }
120}
121
122impl From<SqlValueOrSecret> for Option<Value> {
123    fn from(s: SqlValueOrSecret) -> Self {
124        match s {
125            SqlValueOrSecret::Value(v) => Some(v),
126            SqlValueOrSecret::Secret(_id) => None,
127        }
128    }
129}
130
131/// Unnormalizes an item name.
132///
133/// This is the inverse of the [`unresolved_item_name`] function.
134pub fn unresolve(name: FullItemName) -> UnresolvedItemName {
135    // TODO(parkmycar): Refactor FullItemName to use `Ident`.
136    let mut out = vec![];
137    if let RawDatabaseSpecifier::Name(n) = name.database {
138        out.push(Ident::new_unchecked(n));
139    }
140    out.push(Ident::new_unchecked(name.schema));
141    out.push(Ident::new_unchecked(name.item));
142    UnresolvedItemName(out)
143}
144
145/// Converts an `UnresolvedItemName` to a `FullItemName` if the
146/// `UnresolvedItemName` is fully specified. Otherwise returns an error.
147pub fn full_name(mut raw_name: UnresolvedItemName) -> Result<FullItemName, PlanError> {
148    match raw_name.0.len() {
149        3 => Ok(FullItemName {
150            item: ident(raw_name.0.pop().unwrap()),
151            schema: ident(raw_name.0.pop().unwrap()),
152            database: RawDatabaseSpecifier::Name(ident(raw_name.0.pop().unwrap())),
153        }),
154        2 => Ok(FullItemName {
155            item: ident(raw_name.0.pop().unwrap()),
156            schema: ident(raw_name.0.pop().unwrap()),
157            database: RawDatabaseSpecifier::Ambient,
158        }),
159        _ => sql_bail!("unresolved name {} not fully qualified", raw_name),
160    }
161}
162
163/// Normalizes a `CREATE` statement.
164///
165/// The resulting statement will not depend upon any session parameters, nor
166/// specify any non-default options (like `TEMPORARY`, `IF NOT EXISTS`, etc).
167///
168/// The goal is to construct a backwards-compatible description of the item.
169/// SQL is the most stable part of Materialize, so SQL is used to describe the
170/// items that are persisted in the catalog.
171pub fn create_statement(
172    scx: &StatementContext,
173    mut stmt: Statement<Aug>,
174) -> Result<String, PlanError> {
175    let allocate_name = |name: &UnresolvedItemName| -> Result<_, PlanError> {
176        Ok(unresolve(
177            scx.allocate_full_name(unresolved_item_name(name.clone())?)?,
178        ))
179    };
180
181    let allocate_temporary_name = |name: &UnresolvedItemName| -> Result<_, PlanError> {
182        Ok(unresolve(scx.allocate_temporary_full_name(
183            unresolved_item_name(name.clone())?,
184        )))
185    };
186
187    struct QueryNormalizer {
188        ctes: Vec<Ident>,
189        err: Option<PlanError>,
190    }
191
192    impl QueryNormalizer {
193        fn new() -> QueryNormalizer {
194            QueryNormalizer {
195                ctes: vec![],
196                err: None,
197            }
198        }
199    }
200
201    impl<'ast> VisitMut<'ast, Aug> for QueryNormalizer {
202        fn visit_query_mut(&mut self, query: &'ast mut Query<Aug>) {
203            let n = self.ctes.len();
204            match &query.ctes {
205                CteBlock::Simple(ctes) => {
206                    for cte in ctes.iter() {
207                        self.ctes.push(cte.alias.name.clone());
208                    }
209                }
210                CteBlock::MutuallyRecursive(MutRecBlock { options: _, ctes }) => {
211                    for cte in ctes.iter() {
212                        self.ctes.push(cte.name.clone());
213                    }
214                }
215            }
216            visit_mut::visit_query_mut(self, query);
217            self.ctes.truncate(n);
218        }
219
220        fn visit_function_mut(&mut self, func: &'ast mut Function<Aug>) {
221            match &mut func.args {
222                FunctionArgs::Star => (),
223                FunctionArgs::Args { args, order_by } => {
224                    for arg in args {
225                        self.visit_expr_mut(arg);
226                    }
227                    for expr in order_by {
228                        self.visit_order_by_expr_mut(expr);
229                    }
230                }
231            }
232            if let Some(over) = &mut func.over {
233                self.visit_window_spec_mut(over);
234            }
235        }
236
237        fn visit_table_factor_mut(&mut self, table_factor: &'ast mut TableFactor<Aug>) {
238            match table_factor {
239                TableFactor::Table { name, alias, .. } => {
240                    self.visit_item_name_mut(name);
241                    if let Some(alias) = alias {
242                        self.visit_table_alias_mut(alias);
243                    }
244                }
245                // We only need special behavior for `TableFactor::Table`.
246                // Just visit the other types of table factors like normal.
247                _ => visit_mut::visit_table_factor_mut(self, table_factor),
248            }
249        }
250    }
251
252    // Think very hard before changing any of the branches in this match
253    // statement. All identifiers must be quoted. All object names must be
254    // allocated or resolved, depending on whether they are the object created
255    // by the statement or an object depended upon by the statement. All
256    // non-default options must be disabled.
257    //
258    // Wildcard matches are explicitly avoided so that future additions to the
259    // syntax cause compile errors here. Before you ignore a new field, triple
260    // check that it does not need to be normalized according to the rules
261    // above.
262    match &mut stmt {
263        Statement::CreateSource(CreateSourceStatement {
264            name,
265            in_cluster: _,
266            col_names: _,
267            connection: _,
268            format: _,
269            include_metadata: _,
270            envelope: _,
271            if_not_exists,
272            key_constraint: _,
273            with_options: _,
274            external_references: _,
275            progress_subsource: _,
276        }) => {
277            *name = allocate_name(name)?;
278            *if_not_exists = false;
279        }
280
281        Statement::CreateSubsource(CreateSubsourceStatement {
282            name,
283            columns,
284            constraints: _,
285            of_source: _,
286            if_not_exists,
287            with_options: _,
288        }) => {
289            *name = allocate_name(name)?;
290            let mut normalizer = QueryNormalizer::new();
291            for c in columns {
292                normalizer.visit_column_def_mut(c);
293            }
294            if let Some(err) = normalizer.err {
295                return Err(err);
296            }
297            *if_not_exists = false;
298        }
299
300        Statement::CreateTableFromSource(CreateTableFromSourceStatement {
301            name,
302            columns,
303            constraints: _,
304            external_reference: _,
305            source: _,
306            if_not_exists,
307            format: _,
308            include_metadata: _,
309            envelope: _,
310            with_options: _,
311        }) => {
312            *name = allocate_name(name)?;
313            let mut normalizer = QueryNormalizer::new();
314            if let TableFromSourceColumns::Defined(columns) = columns {
315                for c in columns {
316                    normalizer.visit_column_def_mut(c);
317                }
318            }
319            if let Some(err) = normalizer.err {
320                return Err(err);
321            }
322            *if_not_exists = false;
323        }
324
325        Statement::CreateTable(CreateTableStatement {
326            name,
327            columns,
328            constraints: _,
329            if_not_exists,
330            temporary,
331            with_options: _,
332        }) => {
333            *name = if *temporary {
334                allocate_temporary_name(name)?
335            } else {
336                allocate_name(name)?
337            };
338            let mut normalizer = QueryNormalizer::new();
339            for c in columns {
340                normalizer.visit_column_def_mut(c);
341            }
342            if let Some(err) = normalizer.err {
343                return Err(err);
344            }
345            *if_not_exists = false;
346        }
347
348        Statement::CreateWebhookSource(CreateWebhookSourceStatement {
349            name,
350            is_table: _,
351            if_not_exists,
352            include_headers: _,
353            body_format: _,
354            validate_using: _,
355            in_cluster: _,
356        }) => {
357            *name = allocate_name(name)?;
358            *if_not_exists = false;
359        }
360
361        Statement::CreateSink(CreateSinkStatement {
362            name,
363            in_cluster: _,
364            from: _,
365            connection: _,
366            format: _,
367            envelope: _,
368            mode: _,
369            with_options: _,
370            if_not_exists,
371        }) => {
372            if let Some(name) = name {
373                *name = allocate_name(name)?;
374            }
375            *if_not_exists = false;
376        }
377
378        Statement::CreateMetricSink(CreateMetricSinkStatement {
379            name,
380            in_cluster: _,
381            if_not_exists,
382            from: _,
383            with_options: _,
384        }) => {
385            *name = allocate_name(name)?;
386            *if_not_exists = false;
387        }
388
389        Statement::CreateView(CreateViewStatement {
390            temporary,
391            if_exists,
392            definition:
393                ViewDefinition {
394                    name,
395                    query,
396                    columns: _,
397                },
398        }) => {
399            *name = if *temporary {
400                allocate_temporary_name(name)?
401            } else {
402                allocate_name(name)?
403            };
404            {
405                let mut normalizer = QueryNormalizer::new();
406                normalizer.visit_query_mut(query);
407                if let Some(err) = normalizer.err {
408                    return Err(err);
409                }
410            }
411            *if_exists = IfExistsBehavior::Error;
412        }
413
414        Statement::CreateMaterializedView(CreateMaterializedViewStatement {
415            if_exists,
416            name,
417            columns: _,
418            replacement_for: _,
419            in_cluster: _,
420            in_cluster_replica: _,
421            query,
422            with_options: _,
423            as_of: _,
424        }) => {
425            *name = allocate_name(name)?;
426            {
427                let mut normalizer = QueryNormalizer::new();
428                normalizer.visit_query_mut(query);
429                if let Some(err) = normalizer.err {
430                    return Err(err);
431                }
432            }
433            *if_exists = IfExistsBehavior::Error;
434        }
435
436        Statement::CreateIndex(CreateIndexStatement {
437            name: _,
438            in_cluster: _,
439            on_name: _,
440            key_parts,
441            with_options: _,
442            if_not_exists,
443        }) => {
444            let mut normalizer = QueryNormalizer::new();
445            if let Some(key_parts) = key_parts {
446                for key_part in key_parts {
447                    normalizer.visit_expr_mut(key_part);
448                    if let Some(err) = normalizer.err {
449                        return Err(err);
450                    }
451                }
452            }
453            *if_not_exists = false;
454        }
455
456        Statement::CreateType(CreateTypeStatement { name, as_type }) => {
457            *name = allocate_name(name)?;
458            let mut normalizer = QueryNormalizer::new();
459            normalizer.visit_create_type_as_mut(as_type);
460            if let Some(err) = normalizer.err {
461                return Err(err);
462            }
463        }
464        Statement::CreateSecret(CreateSecretStatement {
465            name,
466            if_not_exists,
467            value: _,
468        }) => {
469            *name = allocate_name(name)?;
470            *if_not_exists = false;
471        }
472        Statement::CreateConnection(CreateConnectionStatement {
473            name,
474            connection_type: _,
475            values,
476            with_options,
477            if_not_exists,
478        }) => {
479            *name = allocate_name(name)?;
480            *if_not_exists = false;
481
482            values.sort();
483
484            // Validation only occurs once during planning and should not be
485            // considered part of the statement's AST/canonical representation.
486            with_options
487                .retain(|o| o.name != mz_sql_parser::ast::CreateConnectionOptionName::Validate);
488        }
489
490        _ => bail_internal!("unexpected statement type for normalization"),
491    }
492
493    Ok(stmt.to_ast_string_stable())
494}
495
496/// Generates a struct capable of taking a `Vec` of types commonly used to
497/// represent `WITH` options into useful data types, such as strings.
498/// Additionally, it is able to convert the useful data types back to the `Vec`
499/// of options.
500///
501/// # Parameters
502/// - `$option_ty`: Accepts a struct representing a set of `WITH` options, which
503///     must contain the fields `name` and `value`.
504///     - `name` must be of type `$option_tyName`, e.g. if `$option_ty` is
505///       `FooOption`, then `name` must be of type `FooOptionName`.
506///       `$option_tyName` must be an enum representing `WITH` option keys.
507///     - `TryFromValue<value>` must be implemented for the type you want to
508///       take the option to. The `sql::plan::with_option` module contains these
509///       implementations.
510/// - `$option_name` must be an element of `$option_tyName`
511/// - `$t` is the type you want to convert the option's value to. If the
512///   option's value is absent (i.e. the user only entered the option's key),
513///   you can also define a default value.
514/// - `Default($v)` is an optional parameter that sets the default value of the
515///   field to `$v`. `$v` must be convertible to `$t` using `.into`. This also
516///   converts the struct's type from `Option<$t>` to `<$t>`.
517/// - `AllowMultiple` is an optional parameter that, when specified, allows
518///   the given option to appear multiple times in the `WITH` clause. This
519///   also converts the struct's type from `$t` to `Vec<$t>`.
520macro_rules! generate_extracted_config {
521    // No default specified, have remaining options.
522    (
523        $option_ty:ty, [$($processed:tt)*],
524        ($option_name:path, $t:ty), $($tail:tt),*
525    ) => {
526        generate_extracted_config!(
527            $option_ty,
528            [$($processed)* ($option_name, Option::<$t>, None, false)],
529            $($tail),*
530        );
531    };
532    // No default specified, no remaining options.
533    (
534        $option_ty:ty, [$($processed:tt)*],
535        ($option_name:path, $t:ty)
536    ) => {
537        generate_extracted_config!(
538            $option_ty,
539            [$($processed)* ($option_name, Option::<$t>, None, false)]
540        );
541    };
542    // Default specified, have remaining options.
543    (
544        $option_ty:ty, [$($processed:tt)*],
545        ($option_name:path, $t:ty, Default($v:expr)), $($tail:tt),*
546    ) => {
547        generate_extracted_config!(
548            $option_ty,
549            [$($processed)* ($option_name, $t, $v, false)],
550            $($tail),*
551        );
552    };
553    // Default specified, no remaining options.
554    (
555        $option_ty:ty, [$($processed:tt)*],
556        ($option_name:path, $t:ty, Default($v:expr))
557    ) => {
558        generate_extracted_config!(
559            $option_ty,
560            [$($processed)* ($option_name, $t, $v, false)]
561        );
562    };
563    // AllowMultiple specified, have remaining options.
564    (
565        $option_ty:ty, [$($processed:tt)*],
566        ($option_name:path, $t:ty, AllowMultiple), $($tail:tt),*
567    ) => {
568        generate_extracted_config!(
569            $option_ty,
570            [$($processed)* ($option_name, $t, vec![], true)],
571            $($tail),*
572        );
573    };
574    // AllowMultiple specified, no remaining options.
575    (
576        $option_ty:ty, [$($processed:tt)*],
577        ($option_name:path, $t:ty, AllowMultiple)
578    ) => {
579        generate_extracted_config!(
580            $option_ty,
581            [$($processed)* ($option_name, $t, vec![], true)]
582        );
583    };
584    ($option_ty:ty, [$(($option_name:path, $t:ty, $v:expr, $allow_multiple:literal))+]) => {
585        paste::paste! {
586            #[derive(Debug)]
587            pub struct [<$option_ty Extracted>] {
588                pub(crate) seen: ::std::collections::BTreeSet::<[<$option_ty Name>]>,
589                $(
590                    pub [<$option_name:snake>]: generate_extracted_config!(
591                        @ifty $allow_multiple,
592                        Vec::<$t>,
593                        $t
594                    ),
595                )*
596            }
597
598            impl std::default::Default for [<$option_ty Extracted>] {
599                fn default() -> Self {
600                    [<$option_ty Extracted>] {
601                        seen: ::std::collections::BTreeSet::<[<$option_ty Name>]>::new(),
602                        $(
603                            [<$option_name:snake>]: <generate_extracted_config!(
604                                @ifty $allow_multiple,
605                                Vec::<$t>,
606                                $t
607                            )>::from($v),
608                        )*
609                    }
610                }
611            }
612
613            impl std::convert::TryFrom<Vec<$option_ty<Aug>>>
614                for [<$option_ty Extracted>]
615            {
616                type Error = $crate::plan::PlanError;
617                fn try_from(
618                    v: Vec<$option_ty<Aug>>,
619                ) -> Result<[<$option_ty Extracted>], Self::Error> {
620                    use [<$option_ty Name>]::*;
621                    let mut extracted = [<$option_ty Extracted>]::default();
622                    for option in v {
623                        match option.name {
624                            $(
625                                $option_name => {
626                                    if !$allow_multiple
627                                        && !extracted.seen.insert(option.name.clone())
628                                    {
629                                        sql_bail!(
630                                            "{} specified more than once",
631                                            option.name.to_ast_string_simple(),
632                                        );
633                                    }
634                                    let val: $t = $crate::plan::with_options
635                                        ::TryFromValue::try_from_value(option.value)
636                                        .map_err(|e| sql_err!(
637                                            "invalid {}: {}",
638                                            option.name.to_ast_string_simple(),
639                                            e,
640                                        ))?;
641                                    generate_extracted_config!(
642                                        @ifexpr $allow_multiple,
643                                        extracted.[<$option_name:snake>].push(val),
644                                        extracted.[<$option_name:snake>] = val
645                                    );
646                                }
647                            )*
648                        }
649                    }
650                    Ok(extracted)
651                }
652            }
653
654            impl [<$option_ty Extracted>] {
655                #[allow(unused)]
656                fn into_values(
657                    self,
658                    catalog: &dyn crate::catalog::SessionCatalog,
659                ) -> Vec<$option_ty<Aug>> {
660                    use [<$option_ty Name>]::*;
661                    let mut options = Vec::new();
662                    $(
663                        let value = self.[<$option_name:snake>];
664                        let values: Vec<_> = generate_extracted_config!(
665                            @ifexpr $allow_multiple,
666                            value,
667                            Vec::from([value])
668                        );
669                        for value in values {
670                            // If `try_into_value` returns `None`, then there was no option that
671                            // generated this value. For example, this can happen when `value` is
672                            // `None`.
673                            let maybe_value = <$t as $crate::plan::with_options::TryFromValue<
674                                Option<mz_sql_parser::ast::WithOptionValue<$crate::names::Aug>>
675                            >>::try_into_value(value, catalog);
676                            match maybe_value {
677                                Some(value) => {
678                                    let option = $option_ty {name: $option_name, value};
679                                    options.push(option);
680                                },
681                                None => (),
682                            }
683                        }
684                    )*
685                    options
686                }
687            }
688        }
689    };
690    ($option_ty:ty, $($h:tt),+) => {
691        generate_extracted_config!{$option_ty, [], $($h),+}
692    };
693    // Helper `if` constructs to conditionally generate expressions and types
694    // based on the value of $allow_multiple.
695    (@ifexpr false, $lhs:expr, $rhs:expr) => {
696        $rhs
697    };
698    (@ifexpr true, $lhs:expr, $rhs:expr) => {
699        $lhs
700    };
701    (@ifty false, $lhs:ty, $rhs:ty) => {
702        $rhs
703    };
704    (@ifty true, $lhs:ty, $rhs:ty) => {
705        $lhs
706    };
707}
708
709pub(crate) use generate_extracted_config;