Skip to main content

mz_sql_parser/ast/
metadata.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use std::fmt::{self, Debug};
17use std::hash::Hash;
18
19use crate::ast::display::{self, AstDisplay, AstFormatter};
20use crate::ast::fold::{Fold, FoldNode};
21use crate::ast::{
22    Ident, Statement, UnresolvedDatabaseName, UnresolvedItemName, UnresolvedObjectName,
23    UnresolvedSchemaName, Version,
24};
25
26/// This represents the metadata that lives next to an AST, as we take it through
27/// various stages in the planning process.
28///
29/// Conceptually, when we first receive an AST from the parsing process, it only
30/// represents the syntax that the user input, and has no semantic information
31/// embedded in it. Later in this process, we want to be able to walk the tree
32/// and add additional information to it piecemeal, perhaps without going down
33/// the full planning pipeline. AstInfo represents various bits of information
34/// that get stored in the tree: for instance, at first, table names are only
35/// represented by the names the user input (in the `Raw` implementor of this
36/// trait), but later on, we replace them with both the name along with the ID
37/// that it gets resolved to.
38///
39/// Currently this process brings an `Ast<Raw>` to `Ast<Aug>`, and lives in
40/// sql/src/names.rs:resolve.
41pub trait AstInfo: Clone {
42    /// The type used for nested statements.
43    type NestedStatement: AstDisplay + Clone + Hash + Debug + Eq;
44    /// The type used for item references. Items are the subset of objects that are namespaced by a
45    /// database and schema.
46    type ItemName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
47    /// The type used to specify a column.
48    ///
49    /// n.b. when implementing visitors, you likely want to build the visitor to
50    /// visit [`crate::ast::ColumnName`] instead of visiting this struct
51    /// directly. The visitor on this should usually just return an error.
52    type ColumnReference: AstDisplay + Clone + Hash + Debug + Eq + Ord;
53    /// The type used for schema names.
54    type SchemaName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
55    /// The type used for database names.
56    type DatabaseName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
57    /// The type used for cluster names.
58    type ClusterName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
59    /// The type used for data types.
60    type DataType: AstDisplay + Clone + Hash + Debug + Eq + Ord;
61    /// The type stored next to CTEs for their assigned ID.
62    type CteId: Clone + Hash + Debug + Eq + Ord;
63    /// The type used for role references.
64    type RoleName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
65    /// The type used for network policy references.
66    type NetworkPolicyName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
67    /// They type used for any object names. Objects are the superset of all objects in Materialize.
68    type ObjectName: AstDisplay + Clone + Hash + Debug + Eq + Ord;
69}
70
71#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Default)]
72pub struct Raw;
73
74impl AstInfo for Raw {
75    type NestedStatement = Statement<Raw>;
76    type ItemName = RawItemName;
77    type ColumnReference = Ident;
78    type SchemaName = UnresolvedSchemaName;
79    type DatabaseName = UnresolvedDatabaseName;
80    type ClusterName = RawClusterName;
81    type DataType = RawDataType;
82    type CteId = ();
83    type RoleName = Ident;
84    type NetworkPolicyName = RawNetworkPolicyName;
85    type ObjectName = UnresolvedObjectName;
86}
87
88#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
89pub enum RawItemName {
90    Name(UnresolvedItemName),
91    Id(String, UnresolvedItemName, Option<Version>),
92}
93
94impl RawItemName {
95    pub fn name(&self) -> &UnresolvedItemName {
96        match self {
97            RawItemName::Name(name) => name,
98            RawItemName::Id(_, name, _) => name,
99        }
100    }
101
102    pub fn name_mut(&mut self) -> &mut UnresolvedItemName {
103        match self {
104            RawItemName::Name(name) => name,
105            RawItemName::Id(_, name, _) => name,
106        }
107    }
108}
109
110/// Writes an ID so it reparses in a bracketed reference.
111fn write_bracketed_id<W: fmt::Write>(f: &mut AstFormatter<W>, id: &str) {
112    let ident = Ident::new_unchecked(id);
113    // Bracketed IDs require an identifier token, so even non-reserved keywords
114    // need quoting. Keep ordinary IDs bare in the stable mode used by pg_get_viewdef.
115    if ident.has_only_bare_chars() && ident.as_keyword().is_none() {
116        f.write_str(id);
117    } else {
118        f.write_str(ident.to_ast_string_stable());
119    }
120}
121
122impl AstDisplay for RawItemName {
123    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
124        match self {
125            RawItemName::Name(o) => f.write_node(o),
126            RawItemName::Id(id, o, v) => {
127                f.write_str("[");
128                write_bracketed_id(f, id);
129                f.write_str(" AS ");
130                f.write_node(o);
131                if let Some(v) = v {
132                    f.write_str(" VERSION ");
133                    f.write_node(v);
134                }
135                f.write_str("]");
136            }
137        }
138    }
139}
140impl_display!(RawItemName);
141
142impl<T> FoldNode<Raw, T> for RawItemName
143where
144    T: AstInfo,
145{
146    type Folded = T::ItemName;
147
148    fn fold<F>(self, f: &mut F) -> Self::Folded
149    where
150        F: Fold<Raw, T>,
151    {
152        f.fold_item_name(self)
153    }
154}
155
156#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
157pub enum RawClusterName {
158    Unresolved(Ident),
159    Resolved(String),
160}
161
162impl AstDisplay for RawClusterName {
163    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
164        match self {
165            RawClusterName::Unresolved(id) => f.write_node(id),
166            RawClusterName::Resolved(id) => {
167                f.write_str("[");
168                write_bracketed_id(f, id);
169                f.write_str("]");
170            }
171        }
172    }
173}
174impl_display!(RawClusterName);
175
176impl<T> FoldNode<Raw, T> for RawClusterName
177where
178    T: AstInfo,
179{
180    type Folded = T::ClusterName;
181
182    fn fold<F>(self, f: &mut F) -> Self::Folded
183    where
184        F: Fold<Raw, T>,
185    {
186        f.fold_cluster_name(self)
187    }
188}
189
190#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
191pub enum RawNetworkPolicyName {
192    Unresolved(Ident),
193    Resolved(String),
194}
195
196impl AstDisplay for RawNetworkPolicyName {
197    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
198        match self {
199            RawNetworkPolicyName::Unresolved(id) => f.write_node(id),
200            RawNetworkPolicyName::Resolved(id) => {
201                f.write_str("[");
202                write_bracketed_id(f, id);
203                f.write_str("]");
204            }
205        }
206    }
207}
208impl_display!(RawNetworkPolicyName);
209
210impl<T> FoldNode<Raw, T> for RawNetworkPolicyName
211where
212    T: AstInfo,
213{
214    type Folded = T::NetworkPolicyName;
215
216    fn fold<F>(self, f: &mut F) -> Self::Folded
217    where
218        F: Fold<Raw, T>,
219    {
220        f.fold_network_policy_name(self)
221    }
222}
223
224/// SQL data types
225#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
226pub enum RawDataType {
227    /// Array
228    Array(Box<RawDataType>),
229    /// List
230    List(Box<RawDataType>),
231    /// Map
232    Map {
233        key_type: Box<RawDataType>,
234        value_type: Box<RawDataType>,
235    },
236    /// Types who don't embed other types, e.g. INT
237    Other {
238        name: RawItemName,
239        /// Typ modifiers appended to the type name, e.g. `numeric(38,0)`.
240        typ_mod: Vec<i64>,
241    },
242}
243
244impl AstDisplay for RawDataType {
245    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
246        match self {
247            RawDataType::Array(ty) => {
248                f.write_node(&ty);
249                f.write_str("[]");
250            }
251            RawDataType::List(ty) => {
252                f.write_node(&ty);
253                f.write_str(" list");
254            }
255            RawDataType::Map {
256                key_type,
257                value_type,
258            } => {
259                f.write_str("map[");
260                f.write_node(&key_type);
261                f.write_str("=>");
262                f.write_node(&value_type);
263                f.write_str("]");
264            }
265            RawDataType::Other { name, typ_mod } => {
266                // If the first component of the type name clashes with a
267                // keyword that `parse_data_type` either dispatches into a
268                // special grammar (`map[...]`) or canonicalizes to a
269                // different spelling (`string` → `text`, `bigint` → `int8`,
270                // …), an unquoted emit would reparse to a different AST —
271                // either the canonicalizing branch fires and replaces the
272                // name outright, or (for a qualified name) the dispatch
273                // consumes the first part before the rest reaches the
274                // type-name parser. Force the always-quoted stable form in
275                // those cases. Keywords whose canonicalized name matches
276                // the keyword text itself (`bpchar`, `varchar`, `time`,
277                // `timestamp`, `timestamptz`) round-trip unquoted via the
278                // keyword path, so they're omitted here.
279                let first_ident_clashes = name
280                    .name()
281                    .0
282                    .first()
283                    .and_then(|id| id.as_keyword())
284                    .map(data_type_keyword_needs_quoting)
285                    .unwrap_or(false);
286                if first_ident_clashes {
287                    f.write_str(&name.to_ast_string_stable());
288                } else {
289                    f.write_node(name);
290                }
291                if typ_mod.len() > 0 {
292                    f.write_str("(");
293                    f.write_node(&display::comma_separated(typ_mod));
294                    f.write_str(")");
295                }
296            }
297        }
298    }
299}
300impl_display!(RawDataType);
301
302/// Reports whether `kw`, as the first component of a data type name, would be
303/// reparsed differently by `Parser::parse_data_type` — either
304/// dispatched into a special grammar (`map[...]`) or canonicalized to a
305/// different spelling (`string` → `text`, `bigint` → `int8`, …). Such names
306/// must be emitted always-quoted to round-trip.
307///
308/// Keywords that `parse_data_type` parses back to themselves verbatim
309/// (`bpchar`, `varchar`, `time`, `timestamp`, `timestamptz`) round-trip
310/// unquoted and are intentionally excluded. Keep this in sync with the keyword
311/// arms of `parse_data_type`.
312fn data_type_keyword_needs_quoting(kw: mz_sql_lexer::keywords::Keyword) -> bool {
313    use mz_sql_lexer::keywords::*;
314    matches!(
315        kw,
316        MAP | STRING
317            | BIGINT
318            | SMALLINT
319            | DEC
320            | DECIMAL
321            | DOUBLE
322            | FLOAT
323            | INT
324            | INTEGER
325            | REAL
326            | BOOLEAN
327            | BYTES
328            | JSON
329            | CHAR
330            | CHARACTER
331    )
332}
333
334impl<T> FoldNode<Raw, T> for RawDataType
335where
336    T: AstInfo,
337{
338    type Folded = T::DataType;
339
340    fn fold<F>(self, f: &mut F) -> Self::Folded
341    where
342        F: Fold<Raw, T>,
343    {
344        f.fold_data_type(self)
345    }
346}