Skip to main content

mz_sql/
names.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//! Structured name types for SQL objects.
11
12use std::borrow::Cow;
13use std::collections::{BTreeMap, BTreeSet};
14use std::fmt;
15use std::str::FromStr;
16use std::sync::LazyLock;
17
18use anyhow::anyhow;
19use mz_controller_types::{ClusterId, ReplicaId};
20use mz_expr::LocalId;
21use mz_ore::str::StrExt;
22use mz_repr::network_policy_id::NetworkPolicyId;
23use mz_repr::role_id::RoleId;
24use mz_repr::{CatalogItemId, GlobalId, RelationVersion};
25use mz_repr::{ColumnName, RelationVersionSelector};
26use mz_sql_parser::ast::visit_mut::VisitMutNode;
27use mz_sql_parser::ast::{Expr, RawNetworkPolicyName, Version};
28use mz_sql_parser::ident;
29use proptest_derive::Arbitrary;
30use serde::{Deserialize, Serialize};
31use uncased::UncasedStr;
32
33use crate::ast::display::{AstDisplay, AstFormatter};
34use crate::ast::fold::{Fold, FoldNode};
35use crate::ast::visit::{Visit, VisitNode};
36use crate::ast::visit_mut::VisitMut;
37use crate::ast::{
38    self, AstInfo, Cte, CteBlock, CteMutRec, DocOnIdentifier, GrantTargetSpecification,
39    GrantTargetSpecificationInner, Ident, MutRecBlock, ObjectType, Query, Raw, RawClusterName,
40    RawDataType, RawItemName, Statement, UnresolvedItemName, UnresolvedObjectName,
41};
42use crate::catalog::{
43    CatalogError, CatalogItem, CatalogItemType, CatalogType, CatalogTypeDetails, SessionCatalog,
44};
45use crate::normalize;
46use crate::plan::PlanError;
47
48/// A fully-qualified human readable name of an item in the catalog.
49///
50/// Catalog names compare case sensitively. Use
51/// [`normalize::unresolved_item_name`] to
52/// perform proper case folding if converting an [`UnresolvedItemName`] to a
53/// `FullItemName`.
54///
55/// [`normalize::unresolved_item_name`]: crate::normalize::unresolved_item_name
56#[derive(
57    Debug,
58    Clone,
59    Eq,
60    PartialEq,
61    Hash,
62    Ord,
63    PartialOrd,
64    Serialize,
65    Deserialize
66)]
67pub struct FullItemName {
68    /// The database name.
69    pub database: RawDatabaseSpecifier,
70    /// The schema name.
71    pub schema: String,
72    /// The item name.
73    pub item: String,
74}
75
76impl FullItemName {
77    /// Converts the name into a string vector of its constituent parts:
78    /// database (if present), schema, and item.
79    pub fn into_parts(self) -> Vec<String> {
80        let mut parts = vec![];
81        if let RawDatabaseSpecifier::Name(name) = self.database {
82            parts.push(name);
83        }
84        parts.push(self.schema);
85        parts.push(self.item);
86        parts
87    }
88}
89
90impl fmt::Display for FullItemName {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        if let RawDatabaseSpecifier::Name(database) = &self.database {
93            write!(f, "{}.", database)?;
94        }
95        write!(f, "{}.{}", self.schema, self.item)
96    }
97}
98
99impl From<FullItemName> for UnresolvedItemName {
100    fn from(full_name: FullItemName) -> UnresolvedItemName {
101        // TODO(parkmycar): Change UnresolvedItemName to use `Ident` internally.
102        let mut name_parts = Vec::new();
103        if let RawDatabaseSpecifier::Name(database) = full_name.database {
104            name_parts.push(Ident::new_unchecked(database));
105        }
106        name_parts.push(Ident::new_unchecked(full_name.schema));
107        name_parts.push(Ident::new_unchecked(full_name.item));
108        UnresolvedItemName(name_parts)
109    }
110}
111
112/// A fully-qualified non-human readable name of an item in the catalog using IDs for the database
113/// and schema.
114#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
115pub struct QualifiedItemName {
116    pub qualifiers: ItemQualifiers,
117    pub item: String,
118}
119
120// Do not implement [`Display`] for [`QualifiedItemName`]. [`FullItemName`] should always be
121// displayed instead.
122static_assertions::assert_not_impl_any!(QualifiedItemName: fmt::Display);
123
124/// An optionally-qualified human-readable name of an item in the catalog.
125///
126/// This is like a [`FullItemName`], but either the database or schema name may be
127/// omitted.
128#[derive(
129    Clone,
130    Debug,
131    Serialize,
132    Deserialize,
133    PartialEq,
134    Eq,
135    PartialOrd,
136    Ord,
137    Hash
138)]
139pub struct PartialItemName {
140    pub database: Option<String>,
141    pub schema: Option<String>,
142    pub item: String,
143}
144
145impl PartialItemName {
146    // Whether either self or other might be a (possibly differently qualified)
147    // version of the other.
148    pub fn matches(&self, other: &Self) -> bool {
149        match (&self.database, &other.database) {
150            (Some(d1), Some(d2)) if d1 != d2 => return false,
151            _ => (),
152        }
153        match (&self.schema, &other.schema) {
154            (Some(s1), Some(s2)) if s1 != s2 => return false,
155            _ => (),
156        }
157        self.item == other.item
158    }
159}
160
161impl fmt::Display for PartialItemName {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        if let Some(database) = &self.database {
164            write!(f, "{}.", database)?;
165        }
166        if let Some(schema) = &self.schema {
167            write!(f, "{}.", schema)?;
168        }
169        write!(f, "{}", self.item)
170    }
171}
172
173impl From<FullItemName> for PartialItemName {
174    fn from(n: FullItemName) -> PartialItemName {
175        let database = match n.database {
176            RawDatabaseSpecifier::Ambient => None,
177            RawDatabaseSpecifier::Name(name) => Some(name),
178        };
179        PartialItemName {
180            database,
181            schema: Some(n.schema),
182            item: n.item,
183        }
184    }
185}
186
187impl From<String> for PartialItemName {
188    fn from(item: String) -> Self {
189        PartialItemName {
190            database: None,
191            schema: None,
192            item,
193        }
194    }
195}
196
197impl From<PartialItemName> for UnresolvedItemName {
198    fn from(partial_name: PartialItemName) -> UnresolvedItemName {
199        // TODO(parkmycar): Change UnresolvedItemName to use `Ident` internally.
200        let mut name_parts = Vec::new();
201        if let Some(database) = partial_name.database {
202            name_parts.push(Ident::new_unchecked(database));
203        }
204        if let Some(schema) = partial_name.schema {
205            name_parts.push(Ident::new_unchecked(schema));
206        }
207        name_parts.push(Ident::new_unchecked(partial_name.item));
208        UnresolvedItemName(name_parts)
209    }
210}
211
212/// A fully-qualified human readable name of a schema in the catalog.
213#[derive(
214    Debug,
215    Clone,
216    Eq,
217    PartialEq,
218    Hash,
219    PartialOrd,
220    Ord,
221    Serialize,
222    Deserialize
223)]
224pub struct FullSchemaName {
225    /// The database name
226    pub database: RawDatabaseSpecifier,
227    /// The schema name
228    pub schema: String,
229}
230
231impl fmt::Display for FullSchemaName {
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        if let RawDatabaseSpecifier::Name(database) = &self.database {
234            write!(f, "{}.", database)?;
235        }
236        write!(f, "{}", self.schema)
237    }
238}
239
240/// The fully-qualified non-human readable name of a schema in the catalog.
241#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
242pub struct QualifiedSchemaName {
243    pub database: ResolvedDatabaseSpecifier,
244    pub schema: String,
245}
246
247impl fmt::Display for QualifiedSchemaName {
248    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249        match &self.database {
250            ResolvedDatabaseSpecifier::Ambient => f.write_str(&self.schema),
251            ResolvedDatabaseSpecifier::Id(id) => write!(f, "{}.{}", id, self.schema),
252        }
253    }
254}
255
256/// An optionally-qualified name of an schema in the catalog.
257///
258/// This is like a [`FullSchemaName`], but either the database or schema name may be
259/// omitted.
260#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
261pub struct PartialSchemaName {
262    pub database: Option<String>,
263    pub schema: String,
264}
265
266impl fmt::Display for PartialSchemaName {
267    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268        if let Some(database) = &self.database {
269            write!(f, "{}.", database)?;
270        }
271        write!(f, "{}", self.schema)
272    }
273}
274
275/// A human readable name of a database.
276#[derive(
277    Debug,
278    Clone,
279    Eq,
280    PartialEq,
281    Ord,
282    PartialOrd,
283    Hash,
284    Serialize,
285    Deserialize
286)]
287pub enum RawDatabaseSpecifier {
288    /// The "ambient" database, which is always present and is not named
289    /// explicitly, but by omission.
290    Ambient,
291    /// A normal database with a name.
292    Name(String),
293}
294
295impl fmt::Display for RawDatabaseSpecifier {
296    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297        match self {
298            Self::Ambient => f.write_str("<none>"),
299            Self::Name(name) => f.write_str(name),
300        }
301    }
302}
303
304impl From<Option<String>> for RawDatabaseSpecifier {
305    fn from(s: Option<String>) -> RawDatabaseSpecifier {
306        match s {
307            None => Self::Ambient,
308            Some(name) => Self::Name(name),
309        }
310    }
311}
312
313/// An id of a database.
314#[derive(
315    Debug,
316    Clone,
317    Copy,
318    Eq,
319    PartialEq,
320    Hash,
321    PartialOrd,
322    Ord,
323    Serialize,
324    Deserialize,
325    Arbitrary
326)]
327pub enum ResolvedDatabaseSpecifier {
328    /// The "ambient" database, which is always present and is not named
329    /// explicitly, but by omission.
330    Ambient,
331    /// A normal database with a name.
332    Id(DatabaseId),
333}
334
335impl ResolvedDatabaseSpecifier {
336    pub fn id(&self) -> Option<DatabaseId> {
337        match self {
338            ResolvedDatabaseSpecifier::Ambient => None,
339            ResolvedDatabaseSpecifier::Id(id) => Some(*id),
340        }
341    }
342}
343
344impl fmt::Display for ResolvedDatabaseSpecifier {
345    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346        match self {
347            Self::Ambient => f.write_str("<none>"),
348            Self::Id(id) => write!(f, "{}", id),
349        }
350    }
351}
352
353impl AstDisplay for ResolvedDatabaseSpecifier {
354    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
355        f.write_str(format!("{}", self));
356    }
357}
358
359impl From<DatabaseId> for ResolvedDatabaseSpecifier {
360    fn from(id: DatabaseId) -> Self {
361        Self::Id(id)
362    }
363}
364
365impl From<Option<DatabaseId>> for ResolvedDatabaseSpecifier {
366    fn from(id: Option<DatabaseId>) -> Self {
367        match id {
368            Some(id) => Self::Id(id),
369            None => Self::Ambient,
370        }
371    }
372}
373
374/*
375 * TODO(jkosh44) It's possible that in order to fix
376 * https://github.com/MaterializeInc/database-issues/issues/2689 we will need to assign temporary
377 * schemas unique Ids. If/when that happens we can remove this enum and refer to all schemas by
378 * their Id.
379 */
380/// An id of a schema.
381#[derive(
382    Debug,
383    Clone,
384    Copy,
385    Eq,
386    PartialEq,
387    Hash,
388    PartialOrd,
389    Ord,
390    Serialize,
391    Deserialize
392)]
393pub enum SchemaSpecifier {
394    /// A temporary schema
395    Temporary,
396    /// A normal database with a name.
397    Id(SchemaId),
398}
399
400impl SchemaSpecifier {
401    const TEMPORARY_SCHEMA_ID: u64 = 0;
402
403    pub fn is_system(&self) -> bool {
404        match self {
405            SchemaSpecifier::Temporary => false,
406            SchemaSpecifier::Id(id) => id.is_system(),
407        }
408    }
409
410    pub fn is_user(&self) -> bool {
411        match self {
412            SchemaSpecifier::Temporary => true,
413            SchemaSpecifier::Id(id) => id.is_user(),
414        }
415    }
416
417    pub fn is_temporary(&self) -> bool {
418        matches!(self, SchemaSpecifier::Temporary)
419    }
420}
421
422impl fmt::Display for SchemaSpecifier {
423    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424        match self {
425            Self::Temporary => f.write_str(format!("{}", Self::TEMPORARY_SCHEMA_ID).as_str()),
426            Self::Id(id) => write!(f, "{}", id),
427        }
428    }
429}
430
431impl AstDisplay for SchemaSpecifier {
432    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
433        f.write_str(format!("{}", self));
434    }
435}
436
437impl From<SchemaId> for SchemaSpecifier {
438    fn from(id: SchemaId) -> SchemaSpecifier {
439        match id {
440            SchemaId::User(id) if id == SchemaSpecifier::TEMPORARY_SCHEMA_ID => {
441                SchemaSpecifier::Temporary
442            }
443            schema_id => SchemaSpecifier::Id(schema_id),
444        }
445    }
446}
447
448impl From<&SchemaSpecifier> for SchemaId {
449    fn from(schema_spec: &SchemaSpecifier) -> Self {
450        match schema_spec {
451            SchemaSpecifier::Temporary => SchemaId::User(SchemaSpecifier::TEMPORARY_SCHEMA_ID),
452            SchemaSpecifier::Id(id) => id.clone(),
453        }
454    }
455}
456
457impl From<SchemaSpecifier> for SchemaId {
458    fn from(schema_spec: SchemaSpecifier) -> Self {
459        match schema_spec {
460            SchemaSpecifier::Temporary => SchemaId::User(SchemaSpecifier::TEMPORARY_SCHEMA_ID),
461            SchemaSpecifier::Id(id) => id,
462        }
463    }
464}
465
466// Aug is the type variable assigned to an AST that has already been
467// name-resolved. An AST in this state has global IDs populated next to table
468// names, and local IDs assigned to CTE definitions and references.
469#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone, Default)]
470pub struct Aug;
471
472#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)]
473pub struct ItemQualifiers {
474    pub database_spec: ResolvedDatabaseSpecifier,
475    pub schema_spec: SchemaSpecifier,
476}
477
478#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
479pub enum ResolvedItemName {
480    Item {
481        id: CatalogItemId,
482        qualifiers: ItemQualifiers,
483        full_name: FullItemName,
484        // Whether this object, when printed out, should use [id AS name] syntax. We
485        // want this for things like tables and sources, but not for things like
486        // types.
487        print_id: bool,
488        version: RelationVersionSelector,
489    },
490    Cte {
491        id: LocalId,
492        name: String,
493    },
494    Error,
495}
496
497impl ResolvedItemName {
498    pub fn full_name_str(&self) -> String {
499        match self {
500            ResolvedItemName::Item { full_name, .. } => full_name.to_string(),
501            ResolvedItemName::Cte { name, .. } => name.clone(),
502            ResolvedItemName::Error => "error in name resolution".to_string(),
503        }
504    }
505
506    pub fn full_item_name(&self) -> &FullItemName {
507        match self {
508            ResolvedItemName::Item { full_name, .. } => full_name,
509            _ => panic!("cannot call object_full_name on non-object"),
510        }
511    }
512
513    pub fn item_id(&self) -> &CatalogItemId {
514        match self {
515            ResolvedItemName::Item { id, .. } => id,
516            _ => panic!("cannot call item_id on non-object"),
517        }
518    }
519
520    pub fn version(&self) -> &RelationVersionSelector {
521        match self {
522            ResolvedItemName::Item { version, .. } => version,
523            _ => panic!("cannot call version on non-object"),
524        }
525    }
526}
527
528impl AstDisplay for ResolvedItemName {
529    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
530        match self {
531            ResolvedItemName::Item {
532                id,
533                qualifiers: _,
534                full_name,
535                print_id,
536                version,
537            } => {
538                if *print_id {
539                    f.write_str(format!("[{} AS ", id));
540                }
541                if let RawDatabaseSpecifier::Name(database) = &full_name.database {
542                    f.write_node(&Ident::new_unchecked(database));
543                    f.write_str(".");
544                }
545                f.write_node(&Ident::new_unchecked(&full_name.schema));
546                f.write_str(".");
547                f.write_node(&Ident::new_unchecked(&full_name.item));
548
549                if *print_id {
550                    if let RelationVersionSelector::Specific(version) = version {
551                        let version: Version = (*version).into();
552                        f.write_str(" VERSION ");
553                        f.write_node(&version);
554                    }
555                }
556
557                if *print_id {
558                    f.write_str("]");
559                }
560            }
561            ResolvedItemName::Cte { name, .. } => f.write_node(&Ident::new_unchecked(name)),
562            ResolvedItemName::Error => {}
563        }
564    }
565}
566
567impl std::fmt::Display for ResolvedItemName {
568    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
569        f.write_str(self.to_ast_string_simple().as_str())
570    }
571}
572
573#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
574pub enum ResolvedColumnReference {
575    Column { name: ColumnName, index: usize },
576    Error,
577}
578
579impl AstDisplay for ResolvedColumnReference {
580    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
581        match self {
582            ResolvedColumnReference::Column { name, .. } => {
583                f.write_node(&Ident::new_unchecked(name.as_str()));
584            }
585            ResolvedColumnReference::Error => {}
586        }
587    }
588}
589
590impl std::fmt::Display for ResolvedColumnReference {
591    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
592        f.write_str(self.to_ast_string_simple().as_str())
593    }
594}
595
596#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
597pub enum ResolvedSchemaName {
598    Schema {
599        database_spec: ResolvedDatabaseSpecifier,
600        schema_spec: SchemaSpecifier,
601        full_name: FullSchemaName,
602    },
603    Error,
604}
605
606impl ResolvedSchemaName {
607    /// Panics if this is `Self::Error`.
608    pub fn database_spec(&self) -> &ResolvedDatabaseSpecifier {
609        match self {
610            ResolvedSchemaName::Schema { database_spec, .. } => database_spec,
611            ResolvedSchemaName::Error => {
612                unreachable!("should have been handled by name resolution")
613            }
614        }
615    }
616
617    /// Panics if this is `Self::Error`.
618    pub fn schema_spec(&self) -> &SchemaSpecifier {
619        match self {
620            ResolvedSchemaName::Schema { schema_spec, .. } => schema_spec,
621            ResolvedSchemaName::Error => {
622                unreachable!("should have been handled by name resolution")
623            }
624        }
625    }
626}
627
628impl AstDisplay for ResolvedSchemaName {
629    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
630        match self {
631            ResolvedSchemaName::Schema { full_name, .. } => {
632                if let RawDatabaseSpecifier::Name(database) = &full_name.database {
633                    f.write_node(&Ident::new_unchecked(database));
634                    f.write_str(".");
635                }
636                f.write_node(&Ident::new_unchecked(&full_name.schema));
637            }
638            ResolvedSchemaName::Error => {}
639        }
640    }
641}
642
643#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
644pub enum ResolvedDatabaseName {
645    Database { id: DatabaseId, name: String },
646    Error,
647}
648
649impl ResolvedDatabaseName {
650    /// Panics if this is `Self::Error`.
651    pub fn database_id(&self) -> &DatabaseId {
652        match self {
653            ResolvedDatabaseName::Database { id, .. } => id,
654            ResolvedDatabaseName::Error => {
655                unreachable!("should have been handled by name resolution")
656            }
657        }
658    }
659}
660
661impl AstDisplay for ResolvedDatabaseName {
662    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
663        match self {
664            ResolvedDatabaseName::Database { name, .. } => {
665                f.write_node(&Ident::new_unchecked(name))
666            }
667            ResolvedDatabaseName::Error => {}
668        }
669    }
670}
671
672#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
673pub struct ResolvedClusterName {
674    pub id: ClusterId,
675    /// If set, a name to print in the `AstDisplay` implementation instead of
676    /// `None`. This is only meant to be used by the `NameSimplifier`.
677    ///
678    /// NOTE(benesch): it would be much clearer if the `NameSimplifier` folded
679    /// the AST into a different metadata type, to avoid polluting the resolved
680    /// AST with this field.
681    pub print_name: Option<String>,
682}
683
684impl AstDisplay for ResolvedClusterName {
685    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
686        if let Some(print_name) = &self.print_name {
687            f.write_node(&Ident::new_unchecked(print_name))
688        } else {
689            f.write_str(format!("[{}]", self.id))
690        }
691    }
692}
693
694#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
695pub struct ResolvedClusterReplicaName {
696    pub cluster_id: ClusterId,
697    pub replica_id: ReplicaId,
698}
699
700impl AstDisplay for ResolvedClusterReplicaName {
701    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
702        f.write_str(format!("[{}.{}]", self.cluster_id, self.replica_id))
703    }
704}
705
706#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
707pub enum ResolvedDataType {
708    AnonymousList(Box<ResolvedDataType>),
709    AnonymousMap {
710        key_type: Box<ResolvedDataType>,
711        value_type: Box<ResolvedDataType>,
712    },
713    Named {
714        id: CatalogItemId,
715        qualifiers: ItemQualifiers,
716        full_name: FullItemName,
717        modifiers: Vec<i64>,
718        print_id: bool,
719    },
720    Error,
721}
722
723impl AstDisplay for ResolvedDataType {
724    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
725        match self {
726            ResolvedDataType::AnonymousList(element_type) => {
727                element_type.fmt(f);
728                f.write_str(" list");
729            }
730            ResolvedDataType::AnonymousMap {
731                key_type,
732                value_type,
733            } => {
734                f.write_str("map[");
735                key_type.fmt(f);
736                f.write_str("=>");
737                value_type.fmt(f);
738                f.write_str("]");
739            }
740            ResolvedDataType::Named {
741                id,
742                full_name,
743                modifiers,
744                print_id,
745                ..
746            } => {
747                if *print_id {
748                    f.write_str(format!("[{} AS ", id));
749                }
750                if let RawDatabaseSpecifier::Name(database) = &full_name.database {
751                    f.write_node(&Ident::new_unchecked(database));
752                    f.write_str(".");
753                }
754
755                f.write_node(&Ident::new_unchecked(&full_name.schema));
756                f.write_str(".");
757
758                f.write_node(&Ident::new_unchecked(&full_name.item));
759                if *print_id {
760                    f.write_str("]");
761                }
762                if modifiers.len() > 0 {
763                    f.write_str("(");
764                    f.write_node(&ast::display::comma_separated(modifiers));
765                    f.write_str(")");
766                }
767            }
768            ResolvedDataType::Error => {}
769        }
770    }
771}
772
773impl ResolvedDataType {
774    /// Return the name of `self`'s item without qualification or IDs.
775    ///
776    /// This is used to generate default column names for cast operations.
777    pub fn unqualified_item_name(&self) -> String {
778        let mut res = String::new();
779        match self {
780            ResolvedDataType::AnonymousList(element_type) => {
781                res += &element_type.unqualified_item_name();
782                res += " list";
783            }
784            ResolvedDataType::AnonymousMap {
785                key_type,
786                value_type,
787            } => {
788                res += "map[";
789                res += &key_type.unqualified_item_name();
790                res += "=>";
791                res += &value_type.unqualified_item_name();
792                res += "]";
793            }
794            ResolvedDataType::Named { full_name, .. } => {
795                res += &full_name.item;
796            }
797            ResolvedDataType::Error => {}
798        }
799        res
800    }
801
802    /// Return the name of `self`'s without IDs or modifiers.
803    ///
804    /// This is used for error messages.
805    pub fn human_readable_name(&self) -> String {
806        let mut res = String::new();
807        match self {
808            ResolvedDataType::AnonymousList(element_type) => {
809                res += &element_type.human_readable_name();
810                res += " list";
811            }
812            ResolvedDataType::AnonymousMap {
813                key_type,
814                value_type,
815            } => {
816                res += "map[";
817                res += &key_type.human_readable_name();
818                res += "=>";
819                res += &value_type.human_readable_name();
820                res += "]";
821            }
822            ResolvedDataType::Named { full_name, .. } => {
823                if let RawDatabaseSpecifier::Name(database) = &full_name.database {
824                    res += database;
825                    res += ".";
826                }
827                res += &full_name.schema;
828                res += ".";
829                res += &full_name.item;
830            }
831            ResolvedDataType::Error => {}
832        }
833        res
834    }
835}
836
837impl fmt::Display for ResolvedDataType {
838    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
839        f.write_str(self.to_ast_string_simple().as_str())
840    }
841}
842
843#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
844pub struct ResolvedRoleName {
845    pub id: RoleId,
846    pub name: String,
847}
848
849impl AstDisplay for ResolvedRoleName {
850    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
851        f.write_str(format!("[{} AS {}]", self.id, self.name));
852    }
853}
854
855#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
856pub struct ResolvedNetworkPolicyName {
857    pub id: NetworkPolicyId,
858    pub name: String,
859}
860
861impl AstDisplay for ResolvedNetworkPolicyName {
862    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
863        f.write_str(format!("[{} AS {}]", self.id, self.name));
864    }
865}
866
867#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
868pub enum ResolvedObjectName {
869    Cluster(ResolvedClusterName),
870    ClusterReplica(ResolvedClusterReplicaName),
871    Database(ResolvedDatabaseName),
872    Schema(ResolvedSchemaName),
873    Role(ResolvedRoleName),
874    NetworkPolicy(ResolvedNetworkPolicyName),
875    Item(ResolvedItemName),
876}
877
878impl AstDisplay for ResolvedObjectName {
879    fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
880        match self {
881            ResolvedObjectName::Cluster(n) => f.write_node(n),
882            ResolvedObjectName::ClusterReplica(n) => f.write_node(n),
883            ResolvedObjectName::Database(n) => f.write_node(n),
884            ResolvedObjectName::Schema(n) => f.write_node(n),
885            ResolvedObjectName::Role(n) => f.write_node(n),
886            ResolvedObjectName::Item(n) => f.write_node(n),
887            ResolvedObjectName::NetworkPolicy(n) => f.write_node(n),
888        }
889    }
890}
891
892impl AstInfo for Aug {
893    type NestedStatement = Statement<Raw>;
894    type ItemName = ResolvedItemName;
895    type ColumnReference = ResolvedColumnReference;
896    type SchemaName = ResolvedSchemaName;
897    type DatabaseName = ResolvedDatabaseName;
898    type ClusterName = ResolvedClusterName;
899    type DataType = ResolvedDataType;
900    type CteId = LocalId;
901    type RoleName = ResolvedRoleName;
902    type ObjectName = ResolvedObjectName;
903    type NetworkPolicyName = ResolvedNetworkPolicyName;
904}
905
906/// The identifier for a schema.
907#[derive(
908    Clone,
909    Copy,
910    Debug,
911    Eq,
912    PartialEq,
913    Ord,
914    PartialOrd,
915    Hash,
916    Serialize,
917    Deserialize,
918    Arbitrary
919)]
920pub enum SchemaId {
921    User(u64),
922    System(u64),
923}
924
925impl SchemaId {
926    pub fn is_user(&self) -> bool {
927        matches!(self, SchemaId::User(_))
928    }
929
930    pub fn is_system(&self) -> bool {
931        matches!(self, SchemaId::System(_))
932    }
933}
934
935impl fmt::Display for SchemaId {
936    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
937        match self {
938            SchemaId::System(id) => write!(f, "s{}", id),
939            SchemaId::User(id) => write!(f, "u{}", id),
940        }
941    }
942}
943
944impl FromStr for SchemaId {
945    type Err = PlanError;
946
947    fn from_str(s: &str) -> Result<Self, Self::Err> {
948        let err = || PlanError::Unstructured(format!("couldn't parse SchemaId {}", s));
949        // Validate the (single-byte, ASCII) tag before slicing so that a
950        // multi-byte leading character doesn't slice inside a UTF-8 boundary.
951        let variant = match s.chars().next() {
952            Some('s') => SchemaId::System,
953            Some('u') => SchemaId::User,
954            _ => return Err(err()),
955        };
956        let val: u64 = s[1..].parse().map_err(|_| err())?;
957        Ok(variant(val))
958    }
959}
960
961/// The identifier for a database.
962#[derive(
963    Clone,
964    Copy,
965    Debug,
966    Eq,
967    PartialEq,
968    Ord,
969    PartialOrd,
970    Hash,
971    Serialize,
972    Deserialize,
973    Arbitrary
974)]
975pub enum DatabaseId {
976    User(u64),
977    System(u64),
978}
979
980impl DatabaseId {
981    pub fn is_user(&self) -> bool {
982        matches!(self, DatabaseId::User(_))
983    }
984
985    pub fn is_system(&self) -> bool {
986        matches!(self, DatabaseId::System(_))
987    }
988}
989
990impl fmt::Display for DatabaseId {
991    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
992        match self {
993            DatabaseId::System(id) => write!(f, "s{}", id),
994            DatabaseId::User(id) => write!(f, "u{}", id),
995        }
996    }
997}
998
999impl FromStr for DatabaseId {
1000    type Err = PlanError;
1001
1002    fn from_str(s: &str) -> Result<Self, Self::Err> {
1003        let err = || PlanError::Unstructured(format!("couldn't parse DatabaseId {}", s));
1004        // Validate the (single-byte, ASCII) tag before slicing so that a
1005        // multi-byte leading character doesn't slice inside a UTF-8 boundary.
1006        let variant = match s.chars().next() {
1007            Some('s') => DatabaseId::System,
1008            Some('u') => DatabaseId::User,
1009            _ => return Err(err()),
1010        };
1011        let val: u64 = s[1..].parse().map_err(|_| err())?;
1012        Ok(variant(val))
1013    }
1014}
1015
1016pub static PUBLIC_ROLE_NAME: LazyLock<&UncasedStr> = LazyLock::new(|| UncasedStr::new("PUBLIC"));
1017
1018#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1019pub enum ObjectId {
1020    Cluster(ClusterId),
1021    ClusterReplica((ClusterId, ReplicaId)),
1022    Database(DatabaseId),
1023    Schema((ResolvedDatabaseSpecifier, SchemaSpecifier)),
1024    Role(RoleId),
1025    Item(CatalogItemId),
1026    NetworkPolicy(NetworkPolicyId),
1027}
1028
1029impl ObjectId {
1030    pub fn unwrap_cluster_id(self) -> ClusterId {
1031        match self {
1032            ObjectId::Cluster(id) => id,
1033            _ => panic!("ObjectId::unwrap_cluster_id called on {self:?}"),
1034        }
1035    }
1036    pub fn unwrap_cluster_replica_id(self) -> (ClusterId, ReplicaId) {
1037        match self {
1038            ObjectId::ClusterReplica(id) => id,
1039            _ => panic!("ObjectId::unwrap_cluster_replica_id called on {self:?}"),
1040        }
1041    }
1042    pub fn unwrap_database_id(self) -> DatabaseId {
1043        match self {
1044            ObjectId::Database(id) => id,
1045            _ => panic!("ObjectId::unwrap_database_id called on {self:?}"),
1046        }
1047    }
1048    pub fn unwrap_schema_id(self) -> (ResolvedDatabaseSpecifier, SchemaSpecifier) {
1049        match self {
1050            ObjectId::Schema(id) => id,
1051            _ => panic!("ObjectId::unwrap_schema_id called on {self:?}"),
1052        }
1053    }
1054    pub fn unwrap_role_id(self) -> RoleId {
1055        match self {
1056            ObjectId::Role(id) => id,
1057            _ => panic!("ObjectId::unwrap_role_id called on {self:?}"),
1058        }
1059    }
1060    pub fn unwrap_item_id(self) -> CatalogItemId {
1061        match self {
1062            ObjectId::Item(id) => id,
1063            _ => panic!("ObjectId::unwrap_item_id called on {self:?}"),
1064        }
1065    }
1066
1067    pub fn is_system(&self) -> bool {
1068        match self {
1069            ObjectId::Cluster(cluster_id) => cluster_id.is_system(),
1070            ObjectId::ClusterReplica((_cluster_id, replica_id)) => replica_id.is_system(),
1071            ObjectId::Database(database_id) => database_id.is_system(),
1072            ObjectId::Schema((_database_id, schema_id)) => schema_id.is_system(),
1073            ObjectId::Role(role_id) => role_id.is_system(),
1074            ObjectId::Item(global_id) => global_id.is_system(),
1075            ObjectId::NetworkPolicy(network_policy_id) => network_policy_id.is_system(),
1076        }
1077    }
1078
1079    pub fn is_user(&self) -> bool {
1080        match self {
1081            ObjectId::Cluster(cluster_id) => cluster_id.is_user(),
1082            ObjectId::ClusterReplica((_cluster_id, replica_id)) => replica_id.is_user(),
1083            ObjectId::Database(database_id) => database_id.is_user(),
1084            ObjectId::Schema((_database_id, schema_id)) => schema_id.is_user(),
1085            ObjectId::Role(role_id) => role_id.is_user(),
1086            ObjectId::Item(global_id) => global_id.is_user(),
1087            ObjectId::NetworkPolicy(network_policy_id) => network_policy_id.is_user(),
1088        }
1089    }
1090}
1091
1092impl fmt::Display for ObjectId {
1093    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1094        match self {
1095            ObjectId::Cluster(cluster_id) => write!(f, "C{cluster_id}"),
1096            ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1097                write!(f, "CR{cluster_id}.{replica_id}")
1098            }
1099            ObjectId::Database(database_id) => write!(f, "D{database_id}"),
1100            ObjectId::Schema((database_spec, schema_spec)) => {
1101                let database_id = match database_spec {
1102                    ResolvedDatabaseSpecifier::Ambient => "".to_string(),
1103                    ResolvedDatabaseSpecifier::Id(database_id) => format!("{database_id}."),
1104                };
1105                write!(f, "S{database_id}{schema_spec}")
1106            }
1107            ObjectId::Role(role_id) => write!(f, "R{role_id}"),
1108            ObjectId::Item(item_id) => write!(f, "I{item_id}"),
1109            ObjectId::NetworkPolicy(network_policy_id) => write!(f, "NP{network_policy_id}"),
1110        }
1111    }
1112}
1113
1114impl TryFrom<ResolvedObjectName> for ObjectId {
1115    type Error = anyhow::Error;
1116
1117    fn try_from(name: ResolvedObjectName) -> Result<ObjectId, Self::Error> {
1118        match name {
1119            ResolvedObjectName::Cluster(name) => Ok(ObjectId::Cluster(name.id)),
1120            ResolvedObjectName::ClusterReplica(name) => {
1121                Ok(ObjectId::ClusterReplica((name.cluster_id, name.replica_id)))
1122            }
1123            ResolvedObjectName::Database(name) => Ok(ObjectId::Database(*name.database_id())),
1124            ResolvedObjectName::Schema(name) => match name {
1125                ResolvedSchemaName::Schema {
1126                    database_spec,
1127                    schema_spec,
1128                    ..
1129                } => Ok(ObjectId::Schema((database_spec, schema_spec))),
1130                ResolvedSchemaName::Error => Err(anyhow!("error in name resolution")),
1131            },
1132            ResolvedObjectName::Role(name) => Ok(ObjectId::Role(name.id)),
1133            ResolvedObjectName::Item(name) => match name {
1134                ResolvedItemName::Item { id, .. } => Ok(ObjectId::Item(id)),
1135                ResolvedItemName::Cte { .. } => Err(anyhow!("CTE does not correspond to object")),
1136                ResolvedItemName::Error => Err(anyhow!("error in name resolution")),
1137            },
1138            ResolvedObjectName::NetworkPolicy(name) => Ok(ObjectId::NetworkPolicy(name.id)),
1139        }
1140    }
1141}
1142
1143impl From<ClusterId> for ObjectId {
1144    fn from(id: ClusterId) -> Self {
1145        ObjectId::Cluster(id)
1146    }
1147}
1148
1149impl From<&ClusterId> for ObjectId {
1150    fn from(id: &ClusterId) -> Self {
1151        ObjectId::Cluster(*id)
1152    }
1153}
1154
1155impl From<(ClusterId, ReplicaId)> for ObjectId {
1156    fn from(id: (ClusterId, ReplicaId)) -> Self {
1157        ObjectId::ClusterReplica(id)
1158    }
1159}
1160
1161impl From<&(ClusterId, ReplicaId)> for ObjectId {
1162    fn from(id: &(ClusterId, ReplicaId)) -> Self {
1163        ObjectId::ClusterReplica(*id)
1164    }
1165}
1166
1167impl From<DatabaseId> for ObjectId {
1168    fn from(id: DatabaseId) -> Self {
1169        ObjectId::Database(id)
1170    }
1171}
1172
1173impl From<&DatabaseId> for ObjectId {
1174    fn from(id: &DatabaseId) -> Self {
1175        ObjectId::Database(*id)
1176    }
1177}
1178
1179impl From<ItemQualifiers> for ObjectId {
1180    fn from(qualifiers: ItemQualifiers) -> Self {
1181        ObjectId::Schema((qualifiers.database_spec, qualifiers.schema_spec))
1182    }
1183}
1184
1185impl From<&ItemQualifiers> for ObjectId {
1186    fn from(qualifiers: &ItemQualifiers) -> Self {
1187        ObjectId::Schema((qualifiers.database_spec, qualifiers.schema_spec))
1188    }
1189}
1190
1191impl From<(ResolvedDatabaseSpecifier, SchemaSpecifier)> for ObjectId {
1192    fn from(id: (ResolvedDatabaseSpecifier, SchemaSpecifier)) -> Self {
1193        ObjectId::Schema(id)
1194    }
1195}
1196
1197impl From<&(ResolvedDatabaseSpecifier, SchemaSpecifier)> for ObjectId {
1198    fn from(id: &(ResolvedDatabaseSpecifier, SchemaSpecifier)) -> Self {
1199        ObjectId::Schema(*id)
1200    }
1201}
1202
1203impl From<RoleId> for ObjectId {
1204    fn from(id: RoleId) -> Self {
1205        ObjectId::Role(id)
1206    }
1207}
1208
1209impl From<&RoleId> for ObjectId {
1210    fn from(id: &RoleId) -> Self {
1211        ObjectId::Role(*id)
1212    }
1213}
1214
1215impl From<CatalogItemId> for ObjectId {
1216    fn from(id: CatalogItemId) -> Self {
1217        ObjectId::Item(id)
1218    }
1219}
1220
1221impl From<&CatalogItemId> for ObjectId {
1222    fn from(id: &CatalogItemId) -> Self {
1223        ObjectId::Item(*id)
1224    }
1225}
1226
1227impl From<CommentObjectId> for ObjectId {
1228    fn from(id: CommentObjectId) -> Self {
1229        match id {
1230            CommentObjectId::Table(item_id)
1231            | CommentObjectId::View(item_id)
1232            | CommentObjectId::MaterializedView(item_id)
1233            | CommentObjectId::Source(item_id)
1234            | CommentObjectId::Sink(item_id)
1235            | CommentObjectId::MetricSink(item_id)
1236            | CommentObjectId::Index(item_id)
1237            | CommentObjectId::Func(item_id)
1238            | CommentObjectId::Connection(item_id)
1239            | CommentObjectId::Type(item_id)
1240            | CommentObjectId::Secret(item_id) => ObjectId::Item(item_id),
1241            CommentObjectId::Role(id) => ObjectId::Role(id),
1242            CommentObjectId::Database(id) => ObjectId::Database(id),
1243            CommentObjectId::Schema(id) => ObjectId::Schema(id),
1244            CommentObjectId::Cluster(id) => ObjectId::Cluster(id),
1245            CommentObjectId::ClusterReplica(id) => ObjectId::ClusterReplica(id),
1246            CommentObjectId::NetworkPolicy(id) => ObjectId::NetworkPolicy(id),
1247        }
1248    }
1249}
1250
1251#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1252pub enum SystemObjectId {
1253    /// The ID of a specific object.
1254    Object(ObjectId),
1255    /// Identifier for the entire system.
1256    System,
1257}
1258
1259impl SystemObjectId {
1260    pub fn object_id(&self) -> Option<&ObjectId> {
1261        match self {
1262            SystemObjectId::Object(object_id) => Some(object_id),
1263            SystemObjectId::System => None,
1264        }
1265    }
1266
1267    pub fn is_system(&self) -> bool {
1268        matches!(self, SystemObjectId::System)
1269    }
1270}
1271
1272impl From<ObjectId> for SystemObjectId {
1273    fn from(id: ObjectId) -> Self {
1274        SystemObjectId::Object(id)
1275    }
1276}
1277
1278/// Comments can be applied to multiple kinds of objects (e.g. Tables and Role), so we need a way
1279/// to represent these different types and their IDs (e.g. [`CatalogItemId`] and [`RoleId`]), as
1280/// well as the inner kind of object that is represented, e.g. [`CatalogItemId`] is used to
1281/// identify both Tables and Views. No other kind of ID encapsulates all of this, hence this new
1282/// "*Id" type.
1283///
1284/// New variants here also need to be added to the durable counterpart
1285/// `mz_catalog_protos::objects::CommentObject` and to both CASE expressions in
1286/// the `mz_internal.mz_comments` materialized view (`MZ_COMMENTS` in
1287/// `mz-catalog::builtin::mz_internal`). The MV reads `CommentObject` out of
1288/// `mz_catalog_raw` as serde JSON.
1289#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
1290pub enum CommentObjectId {
1291    Table(CatalogItemId),
1292    View(CatalogItemId),
1293    MaterializedView(CatalogItemId),
1294    Source(CatalogItemId),
1295    Sink(CatalogItemId),
1296    MetricSink(CatalogItemId),
1297    Index(CatalogItemId),
1298    Func(CatalogItemId),
1299    Connection(CatalogItemId),
1300    Type(CatalogItemId),
1301    Secret(CatalogItemId),
1302    Role(RoleId),
1303    Database(DatabaseId),
1304    Schema((ResolvedDatabaseSpecifier, SchemaSpecifier)),
1305    Cluster(ClusterId),
1306    ClusterReplica((ClusterId, ReplicaId)),
1307    NetworkPolicy(NetworkPolicyId),
1308}
1309
1310/// Whether to resolve an name in the types namespace, the functions namespace,
1311/// or the relations namespace. It is possible to resolve name in multiple
1312/// namespaces, in which case types are preferred to functions are preferred to
1313/// relations.
1314// NOTE(benesch,sploiselle): The fact that some names are looked up in multiple
1315// namespaces is a bit dubious, and stems from the fact that we don't
1316// automatically create types for relations (see database-issues#7142). It's possible that we
1317// don't allow names to be looked up in multiple namespaces (i.e., this becomes
1318// `enum ItemResolutionNamespace`), but it's also possible that the design of
1319// the `DOC ON TYPE` option means we're forever stuck with this complexity.
1320#[derive(Debug, Clone, Copy)]
1321struct ItemResolutionConfig {
1322    types: bool,
1323    functions: bool,
1324    relations: bool,
1325}
1326
1327#[derive(Debug)]
1328pub struct NameResolver<'a> {
1329    catalog: &'a dyn SessionCatalog,
1330    ctes: BTreeMap<String, LocalId>,
1331    /// The next `LocalId` to allocate for a CTE. Never decremented, so every
1332    /// CTE in the statement gets a unique id, even when CTE names shadow each
1333    /// other. Later phases (e.g., HIR lowering's `CteMap`) key CTEs by
1334    /// `LocalId` and rely on this uniqueness.
1335    next_cte_id: u64,
1336    status: Result<(), PlanError>,
1337    ids: BTreeMap<CatalogItemId, BTreeSet<GlobalId>>,
1338}
1339
1340impl<'a> NameResolver<'a> {
1341    fn new(catalog: &'a dyn SessionCatalog) -> NameResolver<'a> {
1342        NameResolver {
1343            catalog,
1344            ctes: BTreeMap::new(),
1345            next_cte_id: 0,
1346            status: Ok(()),
1347            ids: BTreeMap::new(),
1348        }
1349    }
1350
1351    fn allocate_cte_id(&mut self) -> LocalId {
1352        let id = LocalId::new(self.next_cte_id);
1353        self.next_cte_id += 1;
1354        id
1355    }
1356
1357    fn resolve_data_type(&mut self, data_type: RawDataType) -> Result<ResolvedDataType, PlanError> {
1358        match data_type {
1359            RawDataType::Array(elem_type) => {
1360                let name = elem_type.to_string();
1361                match self.resolve_data_type(*elem_type)? {
1362                    ResolvedDataType::AnonymousList(_) | ResolvedDataType::AnonymousMap { .. } => {
1363                        sql_bail!("type \"{}[]\" does not exist", name)
1364                    }
1365                    ResolvedDataType::Named { id, modifiers, .. } => {
1366                        let element_item = self.catalog.get_item(&id);
1367                        let array_item = match element_item.type_details() {
1368                            Some(CatalogTypeDetails {
1369                                array_id: Some(array_id),
1370                                ..
1371                            }) => self.catalog.get_item(array_id),
1372                            Some(_) => sql_bail!("type \"{}[]\" does not exist", name),
1373                            None => {
1374                                // Resolution should never produce a
1375                                // `ResolvedDataType::Named` with an ID of a
1376                                // non-type, but we error gracefully just in
1377                                // case.
1378                                sql_bail!(
1379                                    "internal error: {} does not refer to a type",
1380                                    self.catalog
1381                                        .resolve_full_name(element_item.name())
1382                                        .to_string()
1383                                        .quoted()
1384                                );
1385                            }
1386                        };
1387                        self.ids.insert(array_item.id(), BTreeSet::new());
1388                        Ok(ResolvedDataType::Named {
1389                            id: array_item.id(),
1390                            qualifiers: array_item.name().qualifiers.clone(),
1391                            full_name: self.catalog.resolve_full_name(array_item.name()),
1392                            modifiers,
1393                            print_id: true,
1394                        })
1395                    }
1396                    ResolvedDataType::Error => sql_bail!("type \"{}[]\" does not exist", name),
1397                }
1398            }
1399            RawDataType::List(elem_type) => {
1400                let elem_type = self.resolve_data_type(*elem_type)?;
1401                Ok(ResolvedDataType::AnonymousList(Box::new(elem_type)))
1402            }
1403            RawDataType::Map {
1404                key_type,
1405                value_type,
1406            } => {
1407                let key_type = self.resolve_data_type(*key_type)?;
1408                let value_type = self.resolve_data_type(*value_type)?;
1409                Ok(ResolvedDataType::AnonymousMap {
1410                    key_type: Box::new(key_type),
1411                    value_type: Box::new(value_type),
1412                })
1413            }
1414            RawDataType::Other { name, typ_mod } => {
1415                let (full_name, item) = match name {
1416                    RawItemName::Name(name) => {
1417                        let name = normalize::unresolved_item_name(name)?;
1418                        let item = self.catalog.resolve_type(&name)?;
1419                        let full_name = self.catalog.resolve_full_name(item.name());
1420                        (full_name, item)
1421                    }
1422                    RawItemName::Id(id, name, version) => {
1423                        let id: CatalogItemId = id.parse()?;
1424                        let item = match self.catalog.try_get_item(&id) {
1425                            Some(item) => item,
1426                            None => return Err(PlanError::InvalidId(id)),
1427                        };
1428                        let full_name = normalize::full_name(name)?;
1429                        if version.is_some() {
1430                            sql_bail!("specifying a version for a type reference is not supported");
1431                        }
1432
1433                        (full_name, item)
1434                    }
1435                };
1436                self.ids.insert(item.id(), BTreeSet::new());
1437                // If this is a named array type, then make sure to include the element reference
1438                // in the resolved IDs. This helps ensure that named array types are resolved the
1439                // same as an array type with the same element type. For example, `int4[]` and
1440                // `_int4` should have the same set of resolved IDs.
1441                if let Some(CatalogTypeDetails {
1442                    typ: CatalogType::Array { element_reference },
1443                    ..
1444                }) = item.type_details()
1445                {
1446                    self.ids.insert(*element_reference, BTreeSet::new());
1447                }
1448                Ok(ResolvedDataType::Named {
1449                    id: item.id(),
1450                    qualifiers: item.name().qualifiers.clone(),
1451                    full_name,
1452                    modifiers: typ_mod,
1453                    print_id: true,
1454                })
1455            }
1456        }
1457    }
1458
1459    fn resolve_item_name(
1460        &mut self,
1461        item_name: RawItemName,
1462        config: ItemResolutionConfig,
1463    ) -> ResolvedItemName {
1464        match item_name {
1465            RawItemName::Name(name) => self.resolve_item_name_name(name, config),
1466            RawItemName::Id(id, raw_name, version) => {
1467                self.resolve_item_name_id(id, raw_name, version)
1468            }
1469        }
1470    }
1471
1472    fn resolve_item_name_name(
1473        &mut self,
1474        raw_name: UnresolvedItemName,
1475        config: ItemResolutionConfig,
1476    ) -> ResolvedItemName {
1477        let raw_name = match normalize::unresolved_item_name(raw_name) {
1478            Ok(raw_name) => raw_name,
1479            Err(e) => {
1480                if self.status.is_ok() {
1481                    self.status = Err(e);
1482                }
1483                return ResolvedItemName::Error;
1484            }
1485        };
1486
1487        let mut r: Result<&dyn CatalogItem, CatalogError> =
1488            Err(CatalogError::UnknownItem(raw_name.to_string()));
1489
1490        if r.is_err() && config.types {
1491            r = self.catalog.resolve_type(&raw_name);
1492        }
1493
1494        if r.is_err() && config.functions {
1495            r = self.catalog.resolve_function(&raw_name);
1496        }
1497
1498        if r.is_err() && config.relations {
1499            // Check if unqualified name refers to a CTE.
1500            //
1501            // Note that this is done in non-function contexts as CTEs
1502            // are treated as relations.
1503            if raw_name.database.is_none() && raw_name.schema.is_none() {
1504                let norm_name = normalize::ident(Ident::new_unchecked(&raw_name.item));
1505                if let Some(id) = self.ctes.get(&norm_name) {
1506                    return ResolvedItemName::Cte {
1507                        id: *id,
1508                        name: norm_name,
1509                    };
1510                }
1511            }
1512            r = self.catalog.resolve_item(&raw_name);
1513        };
1514
1515        match r {
1516            Ok(item) => {
1517                // Record the item at its current version.
1518                let item = item.at_version(RelationVersionSelector::Latest);
1519                self.ids
1520                    .entry(item.id())
1521                    .or_default()
1522                    .insert(item.global_id());
1523                let print_id = !matches!(
1524                    item.item_type(),
1525                    CatalogItemType::Func | CatalogItemType::Type
1526                );
1527                let alter_table_enabled =
1528                    self.catalog.system_vars().enable_alter_table_add_column();
1529                let version = match item.latest_version() {
1530                    // Only track the version of referenced object if the feature is enabled.
1531                    Some(v) if item.id().is_user() && alter_table_enabled => {
1532                        RelationVersionSelector::Specific(v)
1533                    }
1534                    _ => RelationVersionSelector::Latest,
1535                };
1536
1537                ResolvedItemName::Item {
1538                    id: item.id(),
1539                    qualifiers: item.name().qualifiers.clone(),
1540                    full_name: self.catalog.resolve_full_name(item.name()),
1541                    print_id,
1542                    version,
1543                }
1544            }
1545            Err(mut e) => {
1546                if self.status.is_ok() {
1547                    match &mut e {
1548                        CatalogError::UnknownFunction {
1549                            name: _,
1550                            alternative,
1551                        } => {
1552                            // Suggest using the `jsonb_` version of `json_`
1553                            // functions that do not exist.
1554                            if raw_name.database.is_none()
1555                                && (raw_name.schema.is_none()
1556                                    || raw_name.schema.as_deref() == Some("pg_catalog")
1557                                        && raw_name.item.starts_with("json_"))
1558                            {
1559                                let jsonb_name = PartialItemName {
1560                                    item: raw_name.item.replace("json_", "jsonb_"),
1561                                    ..raw_name
1562                                };
1563                                if self.catalog.resolve_function(&jsonb_name).is_ok() {
1564                                    *alternative = Some(jsonb_name.to_string());
1565                                }
1566                            }
1567                        }
1568                        _ => (),
1569                    }
1570
1571                    self.status = Err(e.into());
1572                }
1573                ResolvedItemName::Error
1574            }
1575        }
1576    }
1577
1578    fn resolve_item_name_id(
1579        &mut self,
1580        id: String,
1581        raw_name: UnresolvedItemName,
1582        version: Option<Version>,
1583    ) -> ResolvedItemName {
1584        let id: CatalogItemId = match id.parse() {
1585            Ok(id) => id,
1586            Err(e) => {
1587                if self.status.is_ok() {
1588                    self.status = Err(e.into());
1589                }
1590                return ResolvedItemName::Error;
1591            }
1592        };
1593        let item = match self.catalog.try_get_item(&id) {
1594            Some(item) => item,
1595            None => {
1596                if self.status.is_ok() {
1597                    self.status = Err(PlanError::InvalidId(id));
1598                }
1599                return ResolvedItemName::Error;
1600            }
1601        };
1602        let alter_table_enabled = self.catalog.system_vars().enable_alter_table_add_column();
1603        let version = match version {
1604            // If there isn't a version specified, and this item supports versioning, track the
1605            // latest.
1606            None => match item.latest_version() {
1607                // Only pin a version for user items, and only with the feature on. Mirrors the
1608                // by-name path in `fold_item_name`. Builtins are not user-versioned, so pinning
1609                // one strands the reference if the builtin is ever converted to an item type
1610                // without versions.
1611                Some(v) if id.is_user() && alter_table_enabled => {
1612                    RelationVersionSelector::Specific(v)
1613                }
1614                _ => RelationVersionSelector::Latest,
1615            },
1616            // Note: Return the specific version if one is specified, even if the feature is off.
1617            Some(v) => {
1618                let specified_version = RelationVersion::from(v);
1619                match item.latest_version() {
1620                    Some(latest) if latest >= specified_version => {
1621                        RelationVersionSelector::Specific(specified_version)
1622                    }
1623                    // A version pin on a builtin is meaningless, since builtins are not
1624                    // user-versioned. Such a pin can still sit in a persisted catalog, and if the
1625                    // builtin has been converted to an item type without versions it no longer
1626                    // validates. Resolve to latest instead of failing catalog open. User items
1627                    // keep the strict check so real out-of-range versions still error.
1628                    _ if !id.is_user() => RelationVersionSelector::Latest,
1629                    _ => {
1630                        if self.status.is_ok() {
1631                            self.status = Err(PlanError::InvalidVersion {
1632                                name: item.name().item.clone(),
1633                                version: v.to_string(),
1634                            })
1635                        }
1636                        return ResolvedItemName::Error;
1637                    }
1638                }
1639            }
1640        };
1641        let item = item.at_version(version);
1642        self.ids
1643            .entry(item.id())
1644            .or_default()
1645            .insert(item.global_id());
1646
1647        let full_name = match normalize::full_name(raw_name) {
1648            Ok(full_name) => full_name,
1649            Err(e) => {
1650                if self.status.is_ok() {
1651                    self.status = Err(e);
1652                }
1653                return ResolvedItemName::Error;
1654            }
1655        };
1656        ResolvedItemName::Item {
1657            id,
1658            qualifiers: item.name().qualifiers.clone(),
1659            full_name,
1660            print_id: true,
1661            version,
1662        }
1663    }
1664}
1665
1666impl<'a> Fold<Raw, Aug> for NameResolver<'a> {
1667    fn fold_nested_statement(
1668        &mut self,
1669        stmt: <Raw as AstInfo>::NestedStatement,
1670    ) -> <Aug as AstInfo>::NestedStatement {
1671        stmt
1672    }
1673
1674    fn fold_query(&mut self, q: Query<Raw>) -> Query<Aug> {
1675        // Retain the old values of various CTE names so that we can restore them after we're done
1676        // planning this SELECT.
1677        let mut shadowed_cte_ids = Vec::new();
1678
1679        // A reused identifier indicates a reused name.
1680        use itertools::Itertools;
1681        if let Some(ident) = q.ctes.bound_identifiers().duplicates().next() {
1682            self.status = Err(sql_err!(
1683                "WITH query name \"{}\" specified more than once",
1684                normalize::ident_ref(ident),
1685            ));
1686        }
1687
1688        let ctes: CteBlock<Aug> = match q.ctes {
1689            CteBlock::Simple(ctes) => {
1690                let mut result_ctes = Vec::<Cte<Aug>>::new();
1691
1692                for cte in ctes.into_iter() {
1693                    let cte_name = normalize::ident(cte.alias.name.clone());
1694                    let local_id = self.allocate_cte_id();
1695
1696                    result_ctes.push(Cte {
1697                        alias: cte.alias,
1698                        id: local_id,
1699                        query: self.fold_query(cte.query),
1700                    });
1701
1702                    let shadowed_id = self.ctes.insert(cte_name.clone(), local_id);
1703                    shadowed_cte_ids.push((cte_name, shadowed_id));
1704                }
1705                CteBlock::Simple(result_ctes)
1706            }
1707            CteBlock::MutuallyRecursive(MutRecBlock { options, ctes }) => {
1708                let mut result_ctes = Vec::<CteMutRec<Aug>>::new();
1709
1710                // All bindings go into scope before any definition is walked,
1711                // so that the definitions can refer to each other.
1712                let mut local_ids = Vec::with_capacity(ctes.len());
1713                for cte in ctes.iter() {
1714                    let cte_name = normalize::ident(cte.name.clone());
1715                    let local_id = self.allocate_cte_id();
1716                    let shadowed_id = self.ctes.insert(cte_name.clone(), local_id);
1717                    shadowed_cte_ids.push((cte_name, shadowed_id));
1718                    local_ids.push(local_id);
1719                }
1720
1721                for (cte, local_id) in ctes.into_iter().zip_eq(local_ids) {
1722                    let columns = cte
1723                        .columns
1724                        .into_iter()
1725                        .map(|column| self.fold_cte_mut_rec_column_def(column))
1726                        .collect();
1727                    let query = self.fold_query(cte.query);
1728                    result_ctes.push(CteMutRec {
1729                        name: cte.name,
1730                        columns,
1731                        id: local_id,
1732                        query,
1733                    });
1734                }
1735                CteBlock::MutuallyRecursive(MutRecBlock {
1736                    options: options
1737                        .into_iter()
1738                        .map(|option| self.fold_mut_rec_block_option(option))
1739                        .collect(),
1740                    ctes: result_ctes,
1741                })
1742            }
1743        };
1744
1745        let result = Query {
1746            ctes,
1747            // Queries can be recursive, so need the ability to grow the stack.
1748            body: mz_ore::stack::maybe_grow(|| self.fold_set_expr(q.body)),
1749            limit: q.limit.map(|l| self.fold_limit(l)),
1750            offset: q.offset.map(|l| self.fold_expr(l)),
1751            order_by: q
1752                .order_by
1753                .into_iter()
1754                .map(|c| self.fold_order_by_expr(c))
1755                .collect(),
1756        };
1757
1758        // Restore the old values of the CTEs.
1759        for (name, value) in shadowed_cte_ids.iter() {
1760            match value {
1761                Some(value) => {
1762                    self.ctes.insert(name.to_string(), value.clone());
1763                }
1764                None => {
1765                    self.ctes.remove(name);
1766                }
1767            };
1768        }
1769
1770        result
1771    }
1772
1773    fn fold_cte_id(&mut self, _id: <Raw as AstInfo>::CteId) -> <Aug as AstInfo>::CteId {
1774        panic!("this should have been handled when walking the CTE");
1775    }
1776
1777    fn fold_item_name(
1778        &mut self,
1779        item_name: <Raw as AstInfo>::ItemName,
1780    ) -> <Aug as AstInfo>::ItemName {
1781        self.resolve_item_name(
1782            item_name,
1783            // By default, when resolving an item name, we assume only relations
1784            // should be in scope.
1785            ItemResolutionConfig {
1786                functions: false,
1787                types: false,
1788                relations: true,
1789            },
1790        )
1791    }
1792
1793    fn fold_column_name(&mut self, column_name: ast::ColumnName<Raw>) -> ast::ColumnName<Aug> {
1794        let item_name = self.resolve_item_name(
1795            column_name.relation,
1796            ItemResolutionConfig {
1797                functions: false,
1798                types: true,
1799                relations: true,
1800            },
1801        );
1802
1803        match &item_name {
1804            ResolvedItemName::Item {
1805                id,
1806                full_name,
1807                version,
1808                qualifiers: _,
1809                print_id: _,
1810            } => {
1811                let item = self.catalog.get_item(id).at_version(*version);
1812                let name = normalize::column_name(column_name.column.clone());
1813
1814                let maybe_desc = match item.type_details() {
1815                    Some(details) => match details.typ.desc(self.catalog) {
1816                        Ok(desc) => desc.map(Cow::Owned),
1817                        Err(e) => {
1818                            if self.status.is_ok() {
1819                                self.status = Err(e);
1820                            }
1821                            return ast::ColumnName {
1822                                relation: ResolvedItemName::Error,
1823                                column: ResolvedColumnReference::Error,
1824                            };
1825                        }
1826                    },
1827                    None => item.relation_desc(),
1828                };
1829                let Some(desc) = maybe_desc else {
1830                    if self.status.is_ok() {
1831                        self.status = Err(PlanError::ItemWithoutColumns {
1832                            name: full_name.to_string(),
1833                            item_type: item.item_type(),
1834                        });
1835                    }
1836                    return ast::ColumnName {
1837                        relation: ResolvedItemName::Error,
1838                        column: ResolvedColumnReference::Error,
1839                    };
1840                };
1841
1842                let Some((index, _typ)) = desc.get_by_name(&name) else {
1843                    if self.status.is_ok() {
1844                        let similar = desc.iter_similar_names(&name).cloned().collect();
1845                        self.status = Err(PlanError::UnknownColumn {
1846                            table: Some(full_name.clone().into()),
1847                            column: name,
1848                            similar,
1849                        })
1850                    }
1851                    return ast::ColumnName {
1852                        relation: ResolvedItemName::Error,
1853                        column: ResolvedColumnReference::Error,
1854                    };
1855                };
1856
1857                ast::ColumnName {
1858                    relation: item_name,
1859                    column: ResolvedColumnReference::Column { name, index },
1860                }
1861            }
1862            ResolvedItemName::Cte { .. } | ResolvedItemName::Error => ast::ColumnName {
1863                relation: ResolvedItemName::Error,
1864                column: ResolvedColumnReference::Error,
1865            },
1866        }
1867    }
1868
1869    fn fold_column_reference(
1870        &mut self,
1871        _node: <Raw as AstInfo>::ColumnReference,
1872    ) -> <Aug as AstInfo>::ColumnReference {
1873        // Do not call this function directly; instead resolve through `fold_column_name`
1874        ResolvedColumnReference::Error
1875    }
1876
1877    fn fold_data_type(
1878        &mut self,
1879        data_type: <Raw as AstInfo>::DataType,
1880    ) -> <Aug as AstInfo>::DataType {
1881        match self.resolve_data_type(data_type) {
1882            Ok(data_type) => data_type,
1883            Err(e) => {
1884                if self.status.is_ok() {
1885                    self.status = Err(e);
1886                }
1887                ResolvedDataType::Error
1888            }
1889        }
1890    }
1891
1892    fn fold_schema_name(
1893        &mut self,
1894        name: <Raw as AstInfo>::SchemaName,
1895    ) -> <Aug as AstInfo>::SchemaName {
1896        let norm_name = match normalize::unresolved_schema_name(name) {
1897            Ok(norm_name) => norm_name,
1898            Err(e) => {
1899                if self.status.is_ok() {
1900                    self.status = Err(e);
1901                }
1902                return ResolvedSchemaName::Error;
1903            }
1904        };
1905
1906        // Special case for mz_temp: with lazy temporary schema creation, the temp
1907        // schema may not exist yet. Return a resolved name with SchemaSpecifier::Temporary
1908        // so that downstream code can handle it appropriately (e.g., return a proper error).
1909        if norm_name.database.is_none() && norm_name.schema == mz_repr::namespaces::MZ_TEMP_SCHEMA {
1910            return ResolvedSchemaName::Schema {
1911                database_spec: ResolvedDatabaseSpecifier::Ambient,
1912                schema_spec: SchemaSpecifier::Temporary,
1913                full_name: FullSchemaName {
1914                    database: RawDatabaseSpecifier::Ambient,
1915                    schema: mz_repr::namespaces::MZ_TEMP_SCHEMA.to_string(),
1916                },
1917            };
1918        }
1919
1920        match self
1921            .catalog
1922            .resolve_schema(norm_name.database.as_deref(), norm_name.schema.as_str())
1923        {
1924            Ok(schema) => {
1925                let raw_database_spec = match schema.database() {
1926                    ResolvedDatabaseSpecifier::Ambient => RawDatabaseSpecifier::Ambient,
1927                    ResolvedDatabaseSpecifier::Id(id) => {
1928                        RawDatabaseSpecifier::Name(self.catalog.get_database(id).name().to_string())
1929                    }
1930                };
1931                ResolvedSchemaName::Schema {
1932                    database_spec: schema.database().clone(),
1933                    schema_spec: schema.id().clone(),
1934                    full_name: FullSchemaName {
1935                        database: raw_database_spec,
1936                        schema: schema.name().schema.clone(),
1937                    },
1938                }
1939            }
1940            Err(e) => {
1941                if self.status.is_ok() {
1942                    self.status = Err(e.into());
1943                }
1944                ResolvedSchemaName::Error
1945            }
1946        }
1947    }
1948
1949    fn fold_database_name(
1950        &mut self,
1951        database_name: <Raw as AstInfo>::DatabaseName,
1952    ) -> <Aug as AstInfo>::DatabaseName {
1953        match self.catalog.resolve_database(database_name.0.as_str()) {
1954            Ok(database) => ResolvedDatabaseName::Database {
1955                id: database.id(),
1956                name: database_name.0.into_string(),
1957            },
1958            Err(e) => {
1959                if self.status.is_ok() {
1960                    self.status = Err(e.into());
1961                }
1962                ResolvedDatabaseName::Error
1963            }
1964        }
1965    }
1966
1967    fn fold_cluster_name(
1968        &mut self,
1969        cluster_name: <Raw as AstInfo>::ClusterName,
1970    ) -> <Aug as AstInfo>::ClusterName {
1971        match cluster_name {
1972            RawClusterName::Unresolved(ident) => {
1973                match self.catalog.resolve_cluster(Some(ident.as_str())) {
1974                    Ok(cluster) => ResolvedClusterName {
1975                        id: cluster.id(),
1976                        print_name: None,
1977                    },
1978                    Err(e) => {
1979                        self.status = Err(e.into());
1980                        ResolvedClusterName {
1981                            // The ID is arbitrary here; we just need some dummy
1982                            // value to return.
1983                            id: ClusterId::system(0).expect("0 is a valid ID"),
1984                            print_name: None,
1985                        }
1986                    }
1987                }
1988            }
1989            RawClusterName::Resolved(ident) => match ident.parse() {
1990                Ok(id) => ResolvedClusterName {
1991                    id,
1992                    print_name: None,
1993                },
1994                Err(e) => {
1995                    self.status = Err(e.into());
1996                    ResolvedClusterName {
1997                        // The ID is arbitrary here; we just need some dummy
1998                        // value to return.
1999                        id: ClusterId::system(0).expect("0 is a valid ID"),
2000                        print_name: None,
2001                    }
2002                }
2003            },
2004        }
2005    }
2006
2007    fn fold_with_option_value(
2008        &mut self,
2009        node: mz_sql_parser::ast::WithOptionValue<Raw>,
2010    ) -> mz_sql_parser::ast::WithOptionValue<Aug> {
2011        use mz_sql_parser::ast::WithOptionValue::*;
2012        match node {
2013            Sequence(vs) => Sequence(
2014                vs.into_iter()
2015                    .map(|v| self.fold_with_option_value(v))
2016                    .collect(),
2017            ),
2018            Map(map) => Map(map
2019                .into_iter()
2020                .map(|(k, v)| (k, self.fold_with_option_value(v)))
2021                .collect()),
2022            Value(v) => Value(self.fold_value(v)),
2023            DataType(dt) => DataType(self.fold_data_type(dt)),
2024            Secret(secret) => {
2025                let item_name = self.fold_item_name(secret);
2026                match &item_name {
2027                    ResolvedItemName::Item { id, .. } => {
2028                        let item = self.catalog.get_item(id);
2029                        if item.item_type() != CatalogItemType::Secret {
2030                            self.status =
2031                                Err(PlanError::InvalidSecret(Box::new(item_name.clone())));
2032                        }
2033                    }
2034                    ResolvedItemName::Cte { .. } => {
2035                        self.status = Err(PlanError::InvalidSecret(Box::new(item_name.clone())));
2036                    }
2037                    ResolvedItemName::Error => {}
2038                }
2039                Secret(item_name)
2040            }
2041            Item(obj) => {
2042                let item_name = self.fold_item_name(obj);
2043                match &item_name {
2044                    ResolvedItemName::Item { .. } => {}
2045                    ResolvedItemName::Cte { .. } => {
2046                        self.status = Err(PlanError::InvalidObject(Box::new(item_name.clone())));
2047                    }
2048                    ResolvedItemName::Error => {}
2049                }
2050                Item(item_name)
2051            }
2052            UnresolvedItemName(name) => UnresolvedItemName(self.fold_unresolved_item_name(name)),
2053            Ident(name) => Ident(self.fold_ident(name)),
2054            Expr(e) => Expr(self.fold_expr(e)),
2055            ClusterReplicas(replicas) => ClusterReplicas(
2056                replicas
2057                    .into_iter()
2058                    .map(|r| self.fold_replica_definition(r))
2059                    .collect(),
2060            ),
2061            ConnectionKafkaBroker(broker) => ConnectionKafkaBroker(self.fold_kafka_broker(broker)),
2062            ConnectionAwsPrivatelink(privatelink) => {
2063                ConnectionAwsPrivatelink(self.fold_connection_default_aws_privatelink(privatelink))
2064            }
2065            KafkaMatchingBrokerRule(x) => {
2066                KafkaMatchingBrokerRule(self.fold_kafka_matching_broker_rule(x))
2067            }
2068            RetainHistoryFor(value) => RetainHistoryFor(self.fold_value(value)),
2069            Refresh(refresh) => Refresh(self.fold_refresh_option_value(refresh)),
2070            ClusterScheduleOptionValue(value) => ClusterScheduleOptionValue(value),
2071            ClusterAutoScalingStrategyOptionValue(value) => {
2072                ClusterAutoScalingStrategyOptionValue(value)
2073            }
2074            ClusterAlterStrategy(value) => {
2075                ClusterAlterStrategy(self.fold_cluster_alter_option_value(value))
2076            }
2077            NetworkPolicyRules(rules) => NetworkPolicyRules(
2078                rules
2079                    .into_iter()
2080                    .map(|r| self.fold_network_policy_rule_definition(r))
2081                    .collect(),
2082            ),
2083        }
2084    }
2085
2086    fn fold_role_name(&mut self, name: <Raw as AstInfo>::RoleName) -> <Aug as AstInfo>::RoleName {
2087        match self.catalog.resolve_role(name.as_str()) {
2088            Ok(role) => ResolvedRoleName {
2089                id: role.id(),
2090                name: role.name().to_string(),
2091            },
2092            Err(e) => {
2093                if self.status.is_ok() {
2094                    self.status = Err(e.into());
2095                }
2096                // garbage value that will be ignored since there's an error.
2097                ResolvedRoleName {
2098                    id: RoleId::User(0),
2099                    name: "".to_string(),
2100                }
2101            }
2102        }
2103    }
2104
2105    fn fold_network_policy_name(
2106        &mut self,
2107        name: <Raw as AstInfo>::NetworkPolicyName,
2108    ) -> <Aug as AstInfo>::NetworkPolicyName {
2109        let name_str = match &name {
2110            RawNetworkPolicyName::Unresolved(ident) => ident.as_str(),
2111            RawNetworkPolicyName::Resolved(s) => s.as_str(),
2112        };
2113        match self.catalog.resolve_network_policy(name_str) {
2114            Ok(policy) => ResolvedNetworkPolicyName {
2115                id: policy.id(),
2116                name: policy.name().to_string(),
2117            },
2118            Err(e) => {
2119                if self.status.is_ok() {
2120                    self.status = Err(e.into());
2121                }
2122                // garbage value that will be ignored since there's an error.
2123                ResolvedNetworkPolicyName {
2124                    id: NetworkPolicyId::User(0),
2125                    name: "".to_string(),
2126                }
2127            }
2128        }
2129    }
2130
2131    fn fold_object_name(
2132        &mut self,
2133        name: <Raw as AstInfo>::ObjectName,
2134    ) -> <Aug as AstInfo>::ObjectName {
2135        match name {
2136            UnresolvedObjectName::Cluster(name) => ResolvedObjectName::Cluster(
2137                self.fold_cluster_name(RawClusterName::Unresolved(name)),
2138            ),
2139            UnresolvedObjectName::ClusterReplica(name) => {
2140                match self.catalog.resolve_cluster_replica(&name) {
2141                    Ok(cluster_replica) => {
2142                        ResolvedObjectName::ClusterReplica(ResolvedClusterReplicaName {
2143                            cluster_id: cluster_replica.cluster_id(),
2144                            replica_id: cluster_replica.replica_id(),
2145                        })
2146                    }
2147                    Err(e) => {
2148                        self.status = Err(e.into());
2149                        ResolvedObjectName::ClusterReplica(ResolvedClusterReplicaName {
2150                            // The ID is arbitrary here; we just need some dummy
2151                            // value to return.
2152                            cluster_id: ClusterId::system(0).expect("0 is a valid ID"),
2153                            replica_id: ReplicaId::System(0),
2154                        })
2155                    }
2156                }
2157            }
2158            UnresolvedObjectName::Database(name) => {
2159                ResolvedObjectName::Database(self.fold_database_name(name))
2160            }
2161            UnresolvedObjectName::Schema(name) => {
2162                ResolvedObjectName::Schema(self.fold_schema_name(name))
2163            }
2164            UnresolvedObjectName::Role(name) => ResolvedObjectName::Role(self.fold_role_name(name)),
2165            UnresolvedObjectName::Item(name) => {
2166                ResolvedObjectName::Item(self.fold_item_name(RawItemName::Name(name)))
2167            }
2168            UnresolvedObjectName::NetworkPolicy(name) => ResolvedObjectName::NetworkPolicy(
2169                self.fold_network_policy_name(RawNetworkPolicyName::Unresolved(name)),
2170            ),
2171        }
2172    }
2173
2174    fn fold_function(
2175        &mut self,
2176        node: mz_sql_parser::ast::Function<Raw>,
2177    ) -> mz_sql_parser::ast::Function<Aug> {
2178        // Functions implemented as SQL statements can have very deeply nested
2179        // and recursive structures, so need the ability to grow the stack.
2180        mz_ore::stack::maybe_grow(|| {
2181            mz_sql_parser::ast::Function {
2182                name: self.resolve_item_name(
2183                    node.name,
2184                    // When resolving a function name, only function items should be
2185                    // considered.
2186                    ItemResolutionConfig {
2187                        functions: true,
2188                        types: false,
2189                        relations: false,
2190                    },
2191                ),
2192                args: self.fold_function_args(node.args),
2193                filter: node.filter.map(|expr| Box::new(self.fold_expr(*expr))),
2194                over: node.over.map(|over| self.fold_window_spec(over)),
2195                distinct: node.distinct,
2196            }
2197        })
2198    }
2199
2200    fn fold_table_factor(
2201        &mut self,
2202        node: mz_sql_parser::ast::TableFactor<Raw>,
2203    ) -> mz_sql_parser::ast::TableFactor<Aug> {
2204        use mz_sql_parser::ast::TableFactor::*;
2205        match node {
2206            Table { name, alias } => Table {
2207                name: self.fold_item_name(name),
2208                alias: alias.map(|alias| self.fold_table_alias(alias)),
2209            },
2210            Function {
2211                function,
2212                alias,
2213                with_ordinality,
2214            } => {
2215                match &function.name {
2216                    RawItemName::Name(name) => {
2217                        if *name == UnresolvedItemName::unqualified(ident!("values"))
2218                            && self.status.is_ok()
2219                        {
2220                            self.status = Err(PlanError::FromValueRequiresParen);
2221                        }
2222                    }
2223                    RawItemName::Id(..) => {}
2224                }
2225
2226                Function {
2227                    function: self.fold_function(function),
2228                    alias: alias.map(|alias| self.fold_table_alias(alias)),
2229                    with_ordinality,
2230                }
2231            }
2232            RowsFrom {
2233                functions,
2234                alias,
2235                with_ordinality,
2236            } => RowsFrom {
2237                functions: functions
2238                    .into_iter()
2239                    .map(|f| self.fold_function(f))
2240                    .collect(),
2241                alias: alias.map(|alias| self.fold_table_alias(alias)),
2242                with_ordinality,
2243            },
2244            Derived {
2245                lateral,
2246                subquery,
2247                alias,
2248            } => Derived {
2249                lateral,
2250                subquery: Box::new(self.fold_query(*subquery)),
2251                alias: alias.map(|alias| self.fold_table_alias(alias)),
2252            },
2253            NestedJoin { join, alias } => NestedJoin {
2254                join: Box::new(self.fold_table_with_joins(*join)),
2255                alias: alias.map(|alias| self.fold_table_alias(alias)),
2256            },
2257        }
2258    }
2259
2260    fn fold_grant_target_specification(
2261        &mut self,
2262        node: GrantTargetSpecification<Raw>,
2263    ) -> GrantTargetSpecification<Aug> {
2264        match node {
2265            GrantTargetSpecification::Object {
2266                object_type: ObjectType::Type,
2267                object_spec_inner: GrantTargetSpecificationInner::Objects { names },
2268            } => GrantTargetSpecification::Object {
2269                object_type: ObjectType::Type,
2270                object_spec_inner: GrantTargetSpecificationInner::Objects {
2271                    names: names
2272                        .into_iter()
2273                        .map(|name| match name {
2274                            UnresolvedObjectName::Item(name) => {
2275                                ResolvedObjectName::Item(self.resolve_item_name_name(
2276                                    name,
2277                                    // `{GRANT|REVOKE} ... ON TYPE ...` can only
2278                                    // refer to type names.
2279                                    ItemResolutionConfig {
2280                                        functions: false,
2281                                        types: true,
2282                                        relations: false,
2283                                    },
2284                                ))
2285                            }
2286                            _ => self.fold_object_name(name),
2287                        })
2288                        .collect(),
2289                },
2290            },
2291            _ => mz_sql_parser::ast::fold::fold_grant_target_specification(self, node),
2292        }
2293    }
2294
2295    fn fold_doc_on_identifier(&mut self, node: DocOnIdentifier<Raw>) -> DocOnIdentifier<Aug> {
2296        match node {
2297            DocOnIdentifier::Column(name) => DocOnIdentifier::Column(self.fold_column_name(name)),
2298            DocOnIdentifier::Type(name) => DocOnIdentifier::Type(self.resolve_item_name(
2299                name,
2300                // In `DOC ON TYPE ...`, the type can refer to either a type or
2301                // a relation.
2302                //
2303                // It's possible this will get simpler once database-issues#7142 is fixed. See
2304                // the comment on `ItemResolutionConfig` for details.
2305                ItemResolutionConfig {
2306                    functions: false,
2307                    types: true,
2308                    relations: true,
2309                },
2310            )),
2311        }
2312    }
2313
2314    fn fold_expr(&mut self, node: Expr<Raw>) -> Expr<Aug> {
2315        // Exprs can be recursive, so need the ability to grow the stack.
2316        mz_ore::stack::maybe_grow(|| mz_sql_parser::ast::fold::fold_expr(self, node))
2317    }
2318}
2319
2320/// Resolves names in an AST node using the provided catalog.
2321#[mz_ore::instrument(target = "compiler", level = "trace", name = "ast_resolve_names")]
2322pub fn resolve<N>(
2323    catalog: &dyn SessionCatalog,
2324    node: N,
2325) -> Result<(N::Folded, ResolvedIds), PlanError>
2326where
2327    N: FoldNode<Raw, Aug>,
2328{
2329    let mut resolver = NameResolver::new(catalog);
2330    let result = node.fold(&mut resolver);
2331    resolver.status?;
2332    Ok((result, ResolvedIds::new(resolver.ids)))
2333}
2334
2335/// A set of items and their corresponding collections resolved by name resolution.
2336///
2337/// This is a newtype of a [`BTreeMap`] that is provided to make it harder to confuse a set of
2338/// resolved IDs with other collections of [`CatalogItemId`].
2339#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2340pub struct ResolvedIds {
2341    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
2342    entries: BTreeMap<CatalogItemId, BTreeSet<GlobalId>>,
2343}
2344
2345impl ResolvedIds {
2346    fn new(entries: BTreeMap<CatalogItemId, BTreeSet<GlobalId>>) -> Self {
2347        ResolvedIds { entries }
2348    }
2349
2350    /// Returns an emptry [`ResolvedIds`].
2351    pub fn empty() -> Self {
2352        ResolvedIds {
2353            entries: BTreeMap::new(),
2354        }
2355    }
2356
2357    /// Returns if the set of IDs is empty.
2358    pub fn is_empty(&self) -> bool {
2359        self.entries.is_empty()
2360    }
2361
2362    /// Returns all of the [`GlobalId`]s in this set.
2363    pub fn collections(&self) -> impl Iterator<Item = &GlobalId> {
2364        self.entries.values().flat_map(|gids| gids.into_iter())
2365    }
2366
2367    /// Returns all of the [`CatalogItemId`]s in this set.
2368    pub fn items(&self) -> impl Iterator<Item = &CatalogItemId> {
2369        self.entries.keys()
2370    }
2371
2372    /// Returns if this set of IDs contains the provided [`CatalogItemId`].
2373    pub fn contains_item(&self, item: &CatalogItemId) -> bool {
2374        self.entries.contains_key(item)
2375    }
2376
2377    pub fn add_item(&mut self, item: CatalogItemId) {
2378        self.entries.insert(item, BTreeSet::new());
2379    }
2380
2381    pub fn remove_item(&mut self, item: &CatalogItemId) {
2382        self.entries.remove(item);
2383    }
2384
2385    /// Merges all entries from `other` into `self`.
2386    pub fn extend_from(&mut self, other: &ResolvedIds) {
2387        for (id, gids) in &other.entries {
2388            self.entries
2389                .entry(*id)
2390                .or_default()
2391                .extend(gids.iter().copied());
2392        }
2393    }
2394
2395    /// Create a new [`ResolvedIds`] that contains the elements from `self`
2396    /// where `predicate` returns `true`.
2397    pub fn retain_items<F>(&self, predicate: F) -> Self
2398    where
2399        F: Fn(&CatalogItemId) -> bool,
2400    {
2401        let mut new_ids = self.clone();
2402        new_ids
2403            .entries
2404            .retain(|item_id, _global_ids| predicate(item_id));
2405        new_ids
2406    }
2407}
2408
2409impl FromIterator<(CatalogItemId, GlobalId)> for ResolvedIds {
2410    fn from_iter<T: IntoIterator<Item = (CatalogItemId, GlobalId)>>(iter: T) -> Self {
2411        let mut ids = ResolvedIds::empty();
2412        ids.extend(iter);
2413        ids
2414    }
2415}
2416
2417impl Extend<(CatalogItemId, GlobalId)> for ResolvedIds {
2418    fn extend<T: IntoIterator<Item = (CatalogItemId, GlobalId)>>(&mut self, iter: T) {
2419        for (item_id, global_id) in iter {
2420            self.entries.entry(item_id).or_default().insert(global_id);
2421        }
2422    }
2423}
2424
2425/// A set of IDs references by the `HirRelationExpr` of an object.
2426#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2427pub struct DependencyIds(pub BTreeSet<CatalogItemId>);
2428
2429impl FromIterator<CatalogItemId> for DependencyIds {
2430    fn from_iter<T: IntoIterator<Item = CatalogItemId>>(iter: T) -> Self {
2431        DependencyIds(iter.into_iter().collect())
2432    }
2433}
2434
2435#[derive(Debug)]
2436pub struct DependencyVisitor<'a> {
2437    catalog: &'a dyn SessionCatalog,
2438    ids: BTreeMap<CatalogItemId, BTreeSet<GlobalId>>,
2439}
2440
2441impl<'a> DependencyVisitor<'a> {
2442    pub fn new(catalog: &'a dyn SessionCatalog) -> Self {
2443        DependencyVisitor {
2444            catalog,
2445            ids: Default::default(),
2446        }
2447    }
2448}
2449
2450impl<'a, 'ast> Visit<'ast, Aug> for DependencyVisitor<'a> {
2451    fn visit_item_name(&mut self, item_name: &'ast <Aug as AstInfo>::ItemName) {
2452        if let ResolvedItemName::Item { id, version, .. } = item_name {
2453            let global_ids = self.ids.entry(*id).or_default();
2454            if let Some(item) = self.catalog.try_get_item(id) {
2455                global_ids.insert(item.at_version(*version).global_id());
2456            }
2457        }
2458    }
2459
2460    fn visit_data_type(&mut self, data_type: &'ast <Aug as AstInfo>::DataType) {
2461        match data_type {
2462            ResolvedDataType::AnonymousList(data_type) => self.visit_data_type(data_type),
2463            ResolvedDataType::AnonymousMap {
2464                key_type,
2465                value_type,
2466            } => {
2467                self.visit_data_type(key_type);
2468                self.visit_data_type(value_type);
2469            }
2470            ResolvedDataType::Named { id, .. } => {
2471                self.ids.entry(*id).or_default();
2472            }
2473            ResolvedDataType::Error => {}
2474        }
2475    }
2476}
2477
2478pub fn visit_dependencies<'ast, N>(catalog: &dyn SessionCatalog, node: &'ast N) -> ResolvedIds
2479where
2480    N: VisitNode<'ast, Aug> + 'ast,
2481{
2482    let mut visitor = DependencyVisitor::new(catalog);
2483    node.visit(&mut visitor);
2484    ResolvedIds::new(visitor.ids)
2485}
2486
2487#[derive(Debug)]
2488pub struct ItemDependencyModifier<'a> {
2489    pub modified: bool,
2490    pub id_map: &'a BTreeMap<CatalogItemId, CatalogItemId>,
2491}
2492
2493impl<'ast, 'a> VisitMut<'ast, Raw> for ItemDependencyModifier<'a> {
2494    fn visit_item_name_mut(&mut self, item_name: &mut RawItemName) {
2495        if let RawItemName::Id(id, _, _) = item_name {
2496            let parsed_id = id.parse::<CatalogItemId>().unwrap();
2497            if let Some(new_id) = self.id_map.get(&parsed_id) {
2498                *id = new_id.to_string();
2499                self.modified = true;
2500            }
2501        }
2502    }
2503}
2504
2505/// Updates any references in the provided AST node that are keys in `id_map`.
2506/// If an id is found it will be updated to the value of the key in `id_map`.
2507/// This assumes the names of the reference(s) are unmodified (e.g. each pair of
2508/// ids refer to an item of the same name, whose id has changed).
2509pub fn modify_dependency_item_ids<'ast, N>(
2510    node: &'ast mut N,
2511    id_map: &BTreeMap<CatalogItemId, CatalogItemId>,
2512) -> bool
2513where
2514    N: VisitMutNode<'ast, Raw>,
2515{
2516    let mut modifier = ItemDependencyModifier {
2517        id_map,
2518        modified: false,
2519    };
2520    node.visit_mut(&mut modifier);
2521
2522    modifier.modified
2523}
2524
2525// Used when displaying a view's source for human creation. If the name
2526// specified is the same as the name in the catalog, we don't use the ID format.
2527#[derive(Debug)]
2528pub struct NameSimplifier<'a> {
2529    pub catalog: &'a dyn SessionCatalog,
2530}
2531
2532impl<'ast, 'a> VisitMut<'ast, Aug> for NameSimplifier<'a> {
2533    fn visit_cluster_name_mut(&mut self, node: &mut ResolvedClusterName) {
2534        node.print_name = Some(self.catalog.get_cluster(node.id).name().into());
2535    }
2536
2537    fn visit_item_name_mut(&mut self, name: &mut ResolvedItemName) {
2538        if let ResolvedItemName::Item {
2539            id,
2540            full_name,
2541            print_id,
2542            ..
2543        } = name
2544        {
2545            let item = self.catalog.get_item(id);
2546            let catalog_full_name = self.catalog.resolve_full_name(item.name());
2547            if catalog_full_name == *full_name {
2548                *print_id = false;
2549            }
2550        }
2551    }
2552
2553    fn visit_data_type_mut(&mut self, name: &mut ResolvedDataType) {
2554        if let ResolvedDataType::Named {
2555            id,
2556            full_name,
2557            print_id,
2558            ..
2559        } = name
2560        {
2561            let item = self.catalog.get_item(id);
2562            let catalog_full_name = self.catalog.resolve_full_name(item.name());
2563            if catalog_full_name == *full_name {
2564                *print_id = false;
2565            }
2566        }
2567    }
2568}
2569
2570/// Returns the [`CatalogItemId`] dependencies the provided `node` has.
2571///
2572/// _DOES NOT_ resolve names, simply does a recursive walk through an object to
2573/// find all of the IDs.
2574pub fn dependencies<'ast, N>(node: &'ast N) -> Result<BTreeSet<CatalogItemId>, anyhow::Error>
2575where
2576    N: VisitNode<'ast, Raw>,
2577{
2578    let mut visitor = IdDependencVisitor::default();
2579    node.visit(&mut visitor);
2580    match visitor.error {
2581        Some(error) => Err(error),
2582        None => Ok(visitor.ids),
2583    }
2584}
2585
2586#[derive(Debug, Default)]
2587struct IdDependencVisitor {
2588    ids: BTreeSet<CatalogItemId>,
2589    error: Option<anyhow::Error>,
2590}
2591
2592impl<'ast> Visit<'ast, Raw> for IdDependencVisitor {
2593    fn visit_item_name(&mut self, node: &'ast <Raw as AstInfo>::ItemName) {
2594        // Bail early if we're already in an error state.
2595        if self.error.is_some() {
2596            return;
2597        }
2598
2599        match node {
2600            // Nothing to do! We don't lookup names.
2601            RawItemName::Name(_) => (),
2602            RawItemName::Id(id, _name, _version) => match id.parse::<CatalogItemId>() {
2603                Ok(id) => {
2604                    self.ids.insert(id);
2605                }
2606                Err(e) => {
2607                    self.error = Some(e);
2608                }
2609            },
2610        }
2611    }
2612}
2613
2614#[cfg(test)]
2615mod tests {
2616    use proptest::prelude::*;
2617
2618    use super::*;
2619
2620    #[mz_ore::test]
2621    fn proptest_schema_id_roundtrips() {
2622        fn testcase(og: SchemaId) {
2623            let s = og.to_string();
2624            let rnd: SchemaId = s.parse().unwrap();
2625            assert_eq!(og, rnd);
2626        }
2627
2628        proptest!(|(id in any::<SchemaId>())| {
2629            testcase(id);
2630        })
2631    }
2632
2633    #[mz_ore::test]
2634    fn proptest_database_id_roundtrips() {
2635        fn testcase(og: DatabaseId) {
2636            let s = og.to_string();
2637            let rnd: DatabaseId = s.parse().unwrap();
2638            assert_eq!(og, rnd);
2639        }
2640
2641        proptest!(|(id in any::<DatabaseId>())| {
2642            testcase(id);
2643        })
2644    }
2645
2646    #[mz_ore::test]
2647    fn test_schema_id_from_str() {
2648        assert_eq!("s5".parse::<SchemaId>().unwrap(), SchemaId::System(5));
2649        assert_eq!("u5".parse::<SchemaId>().unwrap(), SchemaId::User(5));
2650
2651        // Regression test for a panic on multi-byte leading characters, where
2652        // slicing off a single byte landed inside a UTF-8 char boundary (SQL-195).
2653        for invalid in ["ü1", "ü", "é42", "🦀7", "", "x1", "s"] {
2654            assert!(
2655                invalid.parse::<SchemaId>().is_err(),
2656                "expected {invalid:?} to fail to parse"
2657            );
2658        }
2659    }
2660
2661    #[mz_ore::test]
2662    fn test_database_id_from_str() {
2663        assert_eq!("s5".parse::<DatabaseId>().unwrap(), DatabaseId::System(5));
2664        assert_eq!("u5".parse::<DatabaseId>().unwrap(), DatabaseId::User(5));
2665
2666        // Regression test for a panic on multi-byte leading characters, where
2667        // slicing off a single byte landed inside a UTF-8 char boundary (SQL-195).
2668        for invalid in ["ü1", "ü", "é42", "🦀7", "", "x1", "u"] {
2669            assert!(
2670                invalid.parse::<DatabaseId>().is_err(),
2671                "expected {invalid:?} to fail to parse"
2672            );
2673        }
2674    }
2675}