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