Skip to main content

mz_sql/
catalog.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#![warn(missing_docs)]
11
12//! Catalog abstraction layer.
13
14use std::borrow::Cow;
15use std::collections::{BTreeMap, BTreeSet};
16use std::error::Error;
17use std::fmt;
18use std::fmt::{Debug, Display, Formatter};
19use std::num::NonZeroU32;
20use std::str::FromStr;
21use std::sync::LazyLock;
22use std::time::Instant;
23
24use chrono::{DateTime, Utc};
25use mz_auth::password::Password;
26use mz_build_info::BuildInfo;
27use mz_cloud_provider::{CloudProvider, InvalidCloudProviderError};
28use mz_controller_types::{ClusterId, ReplicaId};
29use mz_expr::MirScalarExpr;
30use mz_ore::now::{EpochMillis, NowFn};
31use mz_ore::str::StrExt;
32use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap};
33use mz_repr::explain::ExprHumanizer;
34use mz_repr::network_policy_id::NetworkPolicyId;
35use mz_repr::role_id::RoleId;
36use mz_repr::{
37    CatalogItemId, ColumnName, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector,
38};
39use mz_sql_parser::ast::{Expr, QualifiedReplica, UnresolvedItemName};
40use mz_storage_types::connections::inline::{ConnectionResolver, ReferencedConnection};
41use mz_storage_types::connections::{Connection, ConnectionContext};
42use mz_storage_types::sources::{SourceDesc, SourceExportDataConfig, SourceExportDetails};
43use proptest_derive::Arbitrary;
44use regex::Regex;
45use serde::{Deserialize, Serialize};
46use uuid::Uuid;
47
48use crate::func::Func;
49use crate::names::{
50    Aug, CommentObjectId, DatabaseId, FullItemName, FullSchemaName, ObjectId, PartialItemName,
51    QualifiedItemName, QualifiedSchemaName, ResolvedDatabaseSpecifier, ResolvedIds, SchemaId,
52    SchemaSpecifier, SystemObjectId,
53};
54use crate::plan::statement::StatementDesc;
55use crate::plan::statement::ddl::PlannedRoleAttributes;
56use crate::plan::{ClusterSchedule, CreateClusterPlan, PlanError, PlanNotice, query};
57use crate::session::vars::{OwnedVarInput, SystemVars};
58
59/// A catalog keeps track of SQL objects and session state available to the
60/// planner.
61///
62/// The `sql` crate is agnostic to any particular catalog implementation. This
63/// trait describes the required interface.
64///
65/// The SQL standard mandates a catalog hierarchy of exactly three layers. A
66/// catalog contains databases, databases contain schemas, and schemas contain
67/// catalog items, like sources, sinks, view, and indexes.
68///
69/// There are two classes of operations provided by a catalog:
70///
71///   * Resolution operations, like [`resolve_item`]. These fill in missing name
72///     components based upon connection defaults, e.g., resolving the partial
73///     name `view42` to the fully-specified name `materialize.public.view42`.
74///
75///   * Lookup operations, like [`SessionCatalog::get_item`]. These retrieve
76///     metadata about a catalog entity based on a fully-specified name that is
77///     known to be valid (i.e., because the name was successfully resolved, or
78///     was constructed based on the output of a prior lookup operation). These
79///     functions panic if called with invalid input.
80///
81///   * Session management, such as managing variables' states and adding
82///     notices to the session.
83///
84/// [`get_databases`]: SessionCatalog::get_databases
85/// [`get_item`]: SessionCatalog::get_item
86/// [`resolve_item`]: SessionCatalog::resolve_item
87pub trait SessionCatalog: fmt::Debug + ExprHumanizer + Send + Sync + ConnectionResolver {
88    /// Returns the id of the role that is issuing the query.
89    fn active_role_id(&self) -> &RoleId;
90
91    /// Returns the database to use if one is not explicitly specified.
92    fn active_database_name(&self) -> Option<&str> {
93        self.active_database()
94            .map(|id| self.get_database(id))
95            .map(|db| db.name())
96    }
97
98    /// Returns the database to use if one is not explicitly specified.
99    fn active_database(&self) -> Option<&DatabaseId>;
100
101    /// Returns the cluster to use if one is not explicitly specified.
102    fn active_cluster(&self) -> &str;
103
104    /// Returns the resolved search paths for the current user. (Invalid search paths are skipped.)
105    fn search_path(&self) -> &[(ResolvedDatabaseSpecifier, SchemaSpecifier)];
106
107    /// Returns the descriptor of the named prepared statement on the session, or
108    /// None if the prepared statement does not exist.
109    fn get_prepared_statement_desc(&self, name: &str) -> Option<&StatementDesc>;
110
111    /// Retrieves a reference to the specified portal's descriptor.
112    ///
113    /// If there is no such portal, returns `None`.
114    fn get_portal_desc_unverified(&self, portal_name: &str) -> Option<&StatementDesc>;
115
116    /// Resolves the named database.
117    ///
118    /// If `database_name` exists in the catalog, it returns a reference to the
119    /// resolved database; otherwise it returns an error.
120    fn resolve_database(&self, database_name: &str) -> Result<&dyn CatalogDatabase, CatalogError>;
121
122    /// Gets a database by its ID.
123    ///
124    /// Panics if `id` does not specify a valid database.
125    fn get_database(&self, id: &DatabaseId) -> &dyn CatalogDatabase;
126
127    /// Gets all databases.
128    fn get_databases(&self) -> Vec<&dyn CatalogDatabase>;
129
130    /// Resolves a partially-specified schema name.
131    ///
132    /// If the schema exists in the catalog, it returns a reference to the
133    /// resolved schema; otherwise it returns an error.
134    fn resolve_schema(
135        &self,
136        database_name: Option<&str>,
137        schema_name: &str,
138    ) -> Result<&dyn CatalogSchema, CatalogError>;
139
140    /// Resolves a schema name within a specified database.
141    ///
142    /// If the schema exists in the database, it returns a reference to the
143    /// resolved schema; otherwise it returns an error.
144    fn resolve_schema_in_database(
145        &self,
146        database_spec: &ResolvedDatabaseSpecifier,
147        schema_name: &str,
148    ) -> Result<&dyn CatalogSchema, CatalogError>;
149
150    /// Gets a schema by its ID.
151    ///
152    /// Panics if `id` does not specify a valid schema.
153    fn get_schema(
154        &self,
155        database_spec: &ResolvedDatabaseSpecifier,
156        schema_spec: &SchemaSpecifier,
157    ) -> &dyn CatalogSchema;
158
159    /// Gets all schemas.
160    fn get_schemas(&self) -> Vec<&dyn CatalogSchema>;
161
162    /// Gets the mz_internal schema id.
163    fn get_mz_internal_schema_id(&self) -> SchemaId;
164
165    /// Gets the mz_unsafe schema id.
166    fn get_mz_unsafe_schema_id(&self) -> SchemaId;
167
168    /// Returns true if `schema` is an internal system schema, false otherwise
169    fn is_system_schema_specifier(&self, schema: SchemaSpecifier) -> bool;
170
171    /// Resolves the named role.
172    fn resolve_role(&self, role_name: &str) -> Result<&dyn CatalogRole, CatalogError>;
173
174    /// Resolves the named network policy.
175    fn resolve_network_policy(
176        &self,
177        network_policy_name: &str,
178    ) -> Result<&dyn CatalogNetworkPolicy, CatalogError>;
179
180    /// Gets a role by its ID.
181    fn try_get_role(&self, id: &RoleId) -> Option<&dyn CatalogRole>;
182
183    /// Gets a role by its ID.
184    ///
185    /// Panics if `id` does not specify a valid role.
186    fn get_role(&self, id: &RoleId) -> &dyn CatalogRole;
187
188    /// Gets all roles.
189    fn get_roles(&self) -> Vec<&dyn CatalogRole>;
190
191    /// Gets the id of the `mz_system` role.
192    fn mz_system_role_id(&self) -> RoleId;
193
194    /// Collects all role IDs that `id` is transitively a member of.
195    fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId>;
196
197    /// Resolves the named cluster.
198    /// Gets a network_policy by its ID.
199    ///
200    /// Panics if `id` does not specify a valid role.
201    fn get_network_policy(&self, id: &NetworkPolicyId) -> &dyn CatalogNetworkPolicy;
202
203    /// Gets all roles.
204    fn get_network_policies(&self) -> Vec<&dyn CatalogNetworkPolicy>;
205
206    ///
207    /// If the provided name is `None`, resolves the currently active cluster.
208    fn resolve_cluster<'a, 'b>(
209        &'a self,
210        cluster_name: Option<&'b str>,
211    ) -> Result<&'a dyn CatalogCluster<'a>, CatalogError>;
212
213    /// Resolves the named cluster replica.
214    fn resolve_cluster_replica<'a, 'b>(
215        &'a self,
216        cluster_replica_name: &'b QualifiedReplica,
217    ) -> Result<&'a dyn CatalogClusterReplica<'a>, CatalogError>;
218
219    /// Resolves a partially-specified item name, that is NOT a function or
220    /// type. (For resolving functions or types, please use
221    /// [SessionCatalog::resolve_function] or [SessionCatalog::resolve_type].)
222    ///
223    /// If the partial name has a database component, it searches only the
224    /// specified database; otherwise, it searches the active database. If the
225    /// partial name has a schema component, it searches only the specified
226    /// schema; otherwise, it searches a default set of schemas within the
227    /// selected database. It returns an error if none of the searched schemas
228    /// contain an item whose name matches the item component of the partial
229    /// name.
230    ///
231    /// Note that it is not an error if the named item appears in more than one
232    /// of the search schemas. The catalog implementation must choose one.
233    fn resolve_item(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
234
235    /// Performs the same operation as [`SessionCatalog::resolve_item`] but for
236    /// functions within the catalog.
237    fn resolve_function(
238        &self,
239        item_name: &PartialItemName,
240    ) -> Result<&dyn CatalogItem, CatalogError>;
241
242    /// Performs the same operation as [`SessionCatalog::resolve_item`] but for
243    /// types within the catalog.
244    fn resolve_type(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
245
246    /// Resolves `name` to a type or item, preferring the type if both exist.
247    fn resolve_item_or_type(
248        &self,
249        name: &PartialItemName,
250    ) -> Result<&dyn CatalogItem, CatalogError> {
251        if let Ok(ty) = self.resolve_type(name) {
252            return Ok(ty);
253        }
254        self.resolve_item(name)
255    }
256
257    /// Gets a type named `name` from exactly one of the system schemas.
258    ///
259    /// # Panics
260    /// - If `name` is not an entry in any system schema
261    /// - If more than one system schema has an entry named `name`.
262    fn get_system_type(&self, name: &str) -> &dyn CatalogItem;
263
264    /// Gets an item by its ID.
265    fn try_get_item(&self, id: &CatalogItemId) -> Option<&dyn CatalogItem>;
266
267    /// Tries to get an item by a [`GlobalId`], returning `None` if the [`GlobalId`] does not
268    /// exist.
269    ///
270    /// Note: A single Catalog Item can have multiple [`GlobalId`]s associated with it.
271    fn try_get_item_by_global_id<'a>(
272        &'a self,
273        id: &GlobalId,
274    ) -> Option<Box<dyn CatalogCollectionItem + 'a>>;
275
276    /// Gets an item by its ID.
277    ///
278    /// Panics if `id` does not specify a valid item.
279    fn get_item(&self, id: &CatalogItemId) -> &dyn CatalogItem;
280
281    /// Gets an item by a [`GlobalId`].
282    ///
283    /// Panics if `id` does not specify a valid item.
284    ///
285    /// Note: A single Catalog Item can have multiple [`GlobalId`]s associated with it.
286    fn get_item_by_global_id<'a>(&'a self, id: &GlobalId) -> Box<dyn CatalogCollectionItem + 'a>;
287
288    /// Gets all items.
289    fn get_items(&self) -> Vec<&dyn CatalogItem>;
290
291    /// Looks up an item by its name.
292    fn get_item_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
293
294    /// Looks up a type by its name.
295    fn get_type_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
296
297    /// Gets a cluster by ID.
298    fn get_cluster(&self, id: ClusterId) -> &dyn CatalogCluster<'_>;
299
300    /// Gets all clusters.
301    fn get_clusters(&self) -> Vec<&dyn CatalogCluster<'_>>;
302
303    /// Gets a cluster replica by ID.
304    fn get_cluster_replica(
305        &self,
306        cluster_id: ClusterId,
307        replica_id: ReplicaId,
308    ) -> &dyn CatalogClusterReplica<'_>;
309
310    /// Gets all cluster replicas.
311    fn get_cluster_replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>>;
312
313    /// Gets all system privileges.
314    fn get_system_privileges(&self) -> &PrivilegeMap;
315
316    /// Gets all default privileges.
317    fn get_default_privileges(
318        &self,
319    ) -> Vec<(&DefaultPrivilegeObject, Vec<&DefaultPrivilegeAclItem>)>;
320
321    /// Finds a name like `name` that is not already in use.
322    ///
323    /// If `name` itself is available, it is returned unchanged.
324    fn find_available_name(&self, name: QualifiedItemName) -> QualifiedItemName;
325
326    /// Returns a fully qualified human readable name from fully qualified non-human readable name
327    fn resolve_full_name(&self, name: &QualifiedItemName) -> FullItemName;
328
329    /// Returns a fully qualified human readable schema name from fully qualified non-human
330    /// readable schema name
331    fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName;
332
333    /// Returns the [`CatalogItemId`] for from a [`GlobalId`].
334    fn resolve_item_id(&self, global_id: &GlobalId) -> CatalogItemId;
335
336    /// Returns the [`GlobalId`] for the specificed Catalog Item, at the specified version.
337    fn resolve_global_id(
338        &self,
339        item_id: &CatalogItemId,
340        version: RelationVersionSelector,
341    ) -> GlobalId;
342
343    /// Returns the configuration of the catalog.
344    fn config(&self) -> &CatalogConfig;
345
346    /// Returns the number of milliseconds since the system epoch. For normal use
347    /// this means the Unix epoch. This can safely be mocked in tests and start
348    /// at 0.
349    fn now(&self) -> EpochMillis;
350
351    /// Returns the set of supported AWS PrivateLink availability zone ids.
352    fn aws_privatelink_availability_zones(&self) -> Option<BTreeSet<String>>;
353
354    /// Returns system vars
355    fn system_vars(&self) -> &SystemVars;
356
357    /// Returns mutable system vars
358    ///
359    /// Clients should use this this method carefully, as changes to the backing
360    /// state here are not guarateed to be persisted. The motivating use case
361    /// for this method was ensuring that features are temporary turned on so
362    /// catalog rehydration does not break due to unsupported SQL syntax.
363    fn system_vars_mut(&mut self) -> &mut SystemVars;
364
365    /// Returns the [`RoleId`] of the owner of an object by its ID.
366    fn get_owner_id(&self, id: &ObjectId) -> Option<RoleId>;
367
368    /// Returns the [`PrivilegeMap`] of the object.
369    fn get_privileges(&self, id: &SystemObjectId) -> Option<&PrivilegeMap>;
370
371    /// Returns all the IDs of all objects that depend on `ids`, including `ids` themselves.
372    ///
373    /// The order is guaranteed to be in reverse dependency order, i.e. the leafs will appear
374    /// earlier in the list than the roots. This is particularly userful for the order to drop
375    /// objects.
376    fn object_dependents(&self, ids: &Vec<ObjectId>) -> Vec<ObjectId>;
377
378    /// Returns all the IDs of all objects that depend on `id`, including `id` themselves.
379    ///
380    /// The order is guaranteed to be in reverse dependency order, i.e. the leafs will appear
381    /// earlier in the list than `id`. This is particularly userful for the order to drop
382    /// objects.
383    fn item_dependents(&self, id: CatalogItemId) -> Vec<ObjectId>;
384
385    /// Returns all possible privileges associated with an object type.
386    fn all_object_privileges(&self, object_type: SystemObjectType) -> AclMode;
387
388    /// Returns the object type of `object_id`.
389    fn get_object_type(&self, object_id: &ObjectId) -> ObjectType;
390
391    /// Returns the system object type of `id`.
392    fn get_system_object_type(&self, id: &SystemObjectId) -> SystemObjectType;
393
394    /// Returns the minimal qualification required to unambiguously specify
395    /// `qualified_name`.
396    fn minimal_qualification(&self, qualified_name: &QualifiedItemName) -> PartialItemName;
397
398    /// Adds a [`PlanNotice`] that will be displayed to the user if the plan
399    /// successfully executes.
400    fn add_notice(&self, notice: PlanNotice);
401
402    /// Returns the associated comments for the given `id`
403    fn get_item_comments(&self, id: &CatalogItemId) -> Option<&BTreeMap<Option<usize>, String>>;
404
405    /// Reports whether the specified cluster size is a modern "cc" size rather
406    /// than a legacy T-shirt size.
407    fn is_cluster_size_cc(&self, size: &str) -> bool;
408}
409
410/// Configuration associated with a catalog.
411#[derive(Debug, Clone)]
412pub struct CatalogConfig {
413    /// Returns the time at which the catalog booted.
414    pub start_time: DateTime<Utc>,
415    /// Returns the instant at which the catalog booted.
416    pub start_instant: Instant,
417    /// A random integer associated with this instance of the catalog.
418    ///
419    /// NOTE(benesch): this is only necessary for producing unique Kafka sink
420    /// topics. Perhaps we can remove this when database-issues#977 is complete.
421    pub nonce: u64,
422    /// A persistent ID associated with the environment.
423    pub environment_id: EnvironmentId,
424    /// A transient UUID associated with this process.
425    pub session_id: Uuid,
426    /// Information about this build of Materialize.
427    pub build_info: &'static BuildInfo,
428    /// Function that returns a wall clock now time; can safely be mocked to return
429    /// 0.
430    pub now: NowFn,
431    /// Context for source and sink connections.
432    pub connection_context: ConnectionContext,
433    /// Which system builtins to include. Not allowed to change dynamically.
434    pub builtins_cfg: BuiltinsConfig,
435    /// Helm chart version
436    pub helm_chart_version: Option<String>,
437}
438
439/// A database in a [`SessionCatalog`].
440pub trait CatalogDatabase {
441    /// Returns a fully-specified name of the database.
442    fn name(&self) -> &str;
443
444    /// Returns a stable ID for the database.
445    fn id(&self) -> DatabaseId;
446
447    /// Returns whether the database contains schemas.
448    fn has_schemas(&self) -> bool;
449
450    /// Returns the schemas of the database as a map from schema name to
451    /// schema ID.
452    fn schema_ids(&self) -> &BTreeMap<String, SchemaId>;
453
454    /// Returns the schemas of the database.
455    fn schemas(&self) -> Vec<&dyn CatalogSchema>;
456
457    /// Returns the ID of the owning role.
458    fn owner_id(&self) -> RoleId;
459
460    /// Returns the privileges associated with the database.
461    fn privileges(&self) -> &PrivilegeMap;
462}
463
464/// A schema in a [`SessionCatalog`].
465pub trait CatalogSchema {
466    /// Returns a fully-specified id of the database
467    fn database(&self) -> &ResolvedDatabaseSpecifier;
468
469    /// Returns a fully-specified name of the schema.
470    fn name(&self) -> &QualifiedSchemaName;
471
472    /// Returns a stable ID for the schema.
473    fn id(&self) -> &SchemaSpecifier;
474
475    /// Lists the `CatalogItem`s for the schema.
476    fn has_items(&self) -> bool;
477
478    /// Returns the IDs of the items in the schema.
479    fn item_ids(&self) -> Box<dyn Iterator<Item = CatalogItemId> + '_>;
480
481    /// Returns the ID of the owning role.
482    fn owner_id(&self) -> RoleId;
483
484    /// Returns the privileges associated with the schema.
485    fn privileges(&self) -> &PrivilegeMap;
486}
487
488/// Parameters used to modify password
489#[derive(Debug, Clone, Eq, PartialEq, Arbitrary)]
490pub struct PasswordConfig {
491    /// The Password.
492    pub password: Password,
493    /// a non default iteration count for hashing the password.
494    pub scram_iterations: NonZeroU32,
495}
496
497/// A modification of a role password in the catalog
498#[derive(Debug, Clone, Eq, PartialEq, Arbitrary)]
499pub enum PasswordAction {
500    /// Set a new password.
501    Set(PasswordConfig),
502    /// Remove the existing password.
503    Clear,
504    /// Leave the existing password unchanged.
505    NoChange,
506}
507
508/// The authenticator that auto-provisioned a role on first login.
509#[derive(
510    Debug,
511    Copy,
512    Clone,
513    Eq,
514    PartialEq,
515    Ord,
516    PartialOrd,
517    Serialize,
518    Deserialize,
519    Arbitrary
520)]
521pub enum AutoProvisionSource {
522    /// Role was auto-provisioned by [`mz_auth::AuthenticatorKind::Oidc`].
523    Oidc,
524    /// Role was auto-provisioned by [`mz_auth::AuthenticatorKind::Frontegg`].
525    Frontegg,
526    /// Role was auto-provisioned by [`mz_auth::AuthenticatorKind::None`]
527    None,
528}
529
530/// A raw representation of attributes belonging to a [`CatalogRole`] that we might
531/// get as input from the user. This includes the password.
532/// This struct explicitly does not implement `Serialize` or `Deserialize` to avoid
533/// accidentally serializing passwords.
534#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Arbitrary)]
535pub struct RoleAttributesRaw {
536    /// Indicates whether the role has inheritance of privileges.
537    pub inherit: bool,
538    /// The raw password of the role. This is for self managed auth, not cloud.
539    pub password: Option<Password>,
540    /// Hash iterations used to securely store passwords. This is for self-managed auth
541    pub scram_iterations: Option<NonZeroU32>,
542    /// Whether or not this user is a superuser.
543    pub superuser: Option<bool>,
544    /// Whether this role is login
545    pub login: Option<bool>,
546    /// The authenticator that auto-provisioned this role, if any.
547    pub auto_provision_source: Option<AutoProvisionSource>,
548    // Force use of constructor.
549    _private: (),
550}
551
552/// Attributes belonging to a [`CatalogRole`].
553#[derive(
554    Debug,
555    Clone,
556    Eq,
557    Serialize,
558    Deserialize,
559    PartialEq,
560    Ord,
561    PartialOrd,
562    Arbitrary
563)]
564pub struct RoleAttributes {
565    /// Indicates whether the role has inheritance of privileges.
566    pub inherit: bool,
567    /// Whether or not this user is a superuser.
568    pub superuser: Option<bool>,
569    /// Whether this role is login
570    pub login: Option<bool>,
571    /// The authenticator that auto-provisioned this role, if any.
572    pub auto_provision_source: Option<AutoProvisionSource>,
573    // Force use of constructor.
574    _private: (),
575}
576
577impl RoleAttributesRaw {
578    /// Creates a new [`RoleAttributesRaw`] with default attributes.
579    pub const fn new() -> RoleAttributesRaw {
580        RoleAttributesRaw {
581            inherit: true,
582            password: None,
583            scram_iterations: None,
584            superuser: None,
585            login: None,
586            auto_provision_source: None,
587            _private: (),
588        }
589    }
590
591    /// Adds all attributes excluding password.
592    pub const fn with_all(mut self) -> RoleAttributesRaw {
593        self.inherit = true;
594        self.superuser = Some(true);
595        self.login = Some(true);
596        self
597    }
598}
599
600impl RoleAttributes {
601    /// Creates a new [`RoleAttributes`] with default attributes.
602    pub const fn new() -> RoleAttributes {
603        RoleAttributes {
604            inherit: true,
605            superuser: None,
606            login: None,
607            auto_provision_source: None,
608            _private: (),
609        }
610    }
611
612    /// Adds all attributes except password and auto_provision_source.
613    pub const fn with_all(mut self) -> RoleAttributes {
614        self.inherit = true;
615        self.superuser = Some(true);
616        self.login = Some(true);
617        self
618    }
619
620    /// Returns whether or not the role has inheritence of privileges.
621    pub const fn is_inherit(&self) -> bool {
622        self.inherit
623    }
624}
625
626impl From<RoleAttributesRaw> for RoleAttributes {
627    fn from(
628        RoleAttributesRaw {
629            inherit,
630            superuser,
631            login,
632            auto_provision_source,
633            ..
634        }: RoleAttributesRaw,
635    ) -> RoleAttributes {
636        RoleAttributes {
637            inherit,
638            superuser,
639            login,
640            auto_provision_source,
641            _private: (),
642        }
643    }
644}
645
646impl From<RoleAttributes> for RoleAttributesRaw {
647    fn from(
648        RoleAttributes {
649            inherit,
650            superuser,
651            login,
652            auto_provision_source,
653            ..
654        }: RoleAttributes,
655    ) -> RoleAttributesRaw {
656        RoleAttributesRaw {
657            inherit,
658            password: None,
659            scram_iterations: None,
660            superuser,
661            login,
662            auto_provision_source,
663            _private: (),
664        }
665    }
666}
667
668impl From<PlannedRoleAttributes> for RoleAttributesRaw {
669    fn from(
670        PlannedRoleAttributes {
671            inherit,
672            password,
673            scram_iterations,
674            superuser,
675            login,
676            ..
677        }: PlannedRoleAttributes,
678    ) -> RoleAttributesRaw {
679        let default_attributes = RoleAttributesRaw::new();
680        RoleAttributesRaw {
681            inherit: inherit.unwrap_or(default_attributes.inherit),
682            password,
683            scram_iterations,
684            superuser,
685            login,
686            auto_provision_source: None,
687            _private: (),
688        }
689    }
690}
691
692/// Default variable values for a [`CatalogRole`].
693#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
694pub struct RoleVars {
695    /// Map of variable names to their value.
696    pub map: BTreeMap<String, OwnedVarInput>,
697}
698
699/// A role in a [`SessionCatalog`].
700pub trait CatalogRole {
701    /// Returns a fully-specified name of the role.
702    fn name(&self) -> &str;
703
704    /// Returns a stable ID for the role.
705    fn id(&self) -> RoleId;
706
707    /// Returns all role IDs that this role is an immediate a member of, and the grantor of that
708    /// membership.
709    ///
710    /// Key is the role that some role is a member of, value is the grantor role ID.
711    fn membership(&self) -> &BTreeMap<RoleId, RoleId>;
712
713    /// Returns the attributes associated with this role.
714    fn attributes(&self) -> &RoleAttributes;
715
716    /// Returns all variables that this role has a default value stored for.
717    fn vars(&self) -> &BTreeMap<String, OwnedVarInput>;
718}
719
720/// A network policy in a [`SessionCatalog`].
721pub trait CatalogNetworkPolicy {
722    /// Returns a fully-specified name of the NetworkPolicy.
723    fn name(&self) -> &str;
724
725    /// Returns a stable ID for the NetworkPolicy.
726    fn id(&self) -> NetworkPolicyId;
727
728    /// Returns the ID of the owning NetworkPolicy.
729    fn owner_id(&self) -> RoleId;
730
731    /// Returns the privileges associated with the NetworkPolicy.
732    fn privileges(&self) -> &PrivilegeMap;
733}
734
735/// A cluster in a [`SessionCatalog`].
736pub trait CatalogCluster<'a> {
737    /// Returns a fully-specified name of the cluster.
738    fn name(&self) -> &str;
739
740    /// Returns a stable ID for the cluster.
741    fn id(&self) -> ClusterId;
742
743    /// Returns the objects that are bound to this cluster.
744    fn bound_objects(&self) -> &BTreeSet<CatalogItemId>;
745
746    /// Returns the replicas of the cluster as a map from replica name to
747    /// replica ID.
748    fn replica_ids(&self) -> &BTreeMap<String, ReplicaId>;
749
750    /// Returns the replicas of the cluster.
751    fn replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>>;
752
753    /// Returns the replica belonging to the cluster with replica ID `id`.
754    fn replica(&self, id: ReplicaId) -> &dyn CatalogClusterReplica<'_>;
755
756    /// Returns the ID of the owning role.
757    fn owner_id(&self) -> RoleId;
758
759    /// Returns the privileges associated with the cluster.
760    fn privileges(&self) -> &PrivilegeMap;
761
762    /// Returns true if this cluster is a managed cluster.
763    fn is_managed(&self) -> bool;
764
765    /// Returns the size of the cluster, if the cluster is a managed cluster.
766    fn managed_size(&self) -> Option<&str>;
767
768    /// Returns the schedule of the cluster, if the cluster is a managed cluster.
769    fn schedule(&self) -> Option<&ClusterSchedule>;
770
771    /// Try to convert this cluster into a [`CreateClusterPlan`].
772    // TODO(jkosh44) Make this infallible and convert to `to_plan`.
773    fn try_to_plan(&self) -> Result<CreateClusterPlan, PlanError>;
774}
775
776/// A cluster replica in a [`SessionCatalog`]
777pub trait CatalogClusterReplica<'a>: Debug {
778    /// Returns the name of the cluster replica.
779    fn name(&self) -> &str;
780
781    /// Returns a stable ID for the cluster that the replica belongs to.
782    fn cluster_id(&self) -> ClusterId;
783
784    /// Returns a stable ID for the replica.
785    fn replica_id(&self) -> ReplicaId;
786
787    /// Returns the ID of the owning role.
788    fn owner_id(&self) -> RoleId;
789
790    /// Returns whether or not the replica is internal
791    fn internal(&self) -> bool;
792}
793
794/// An item in a [`SessionCatalog`].
795///
796/// Note that "item" has a very specific meaning in the context of a SQL
797/// catalog, and refers to the various entities that belong to a schema.
798pub trait CatalogItem {
799    /// Returns the fully qualified name of the catalog item.
800    fn name(&self) -> &QualifiedItemName;
801
802    /// Returns the [`CatalogItemId`] for the item.
803    fn id(&self) -> CatalogItemId;
804
805    /// Returns the [`GlobalId`]s associated with this item.
806    fn global_ids(&self) -> Box<dyn Iterator<Item = GlobalId> + '_>;
807
808    /// Returns the catalog item's OID.
809    fn oid(&self) -> u32;
810
811    /// Returns the resolved function.
812    ///
813    /// If the catalog item is not of a type that produces functions (i.e.,
814    /// anything other than a function), it returns an error.
815    fn func(&self) -> Result<&'static Func, CatalogError>;
816
817    /// Returns the resolved source connection.
818    ///
819    /// If the catalog item is not of a type that contains a `SourceDesc`
820    /// (i.e., anything other than sources), it returns an error.
821    fn source_desc(&self) -> Result<Option<&SourceDesc<ReferencedConnection>>, CatalogError>;
822
823    /// Returns the resolved connection.
824    ///
825    /// If the catalog item is not a connection, it returns an error.
826    fn connection(&self) -> Result<Connection<ReferencedConnection>, CatalogError>;
827
828    /// Returns the type of the catalog item.
829    fn item_type(&self) -> CatalogItemType;
830
831    /// A normalized SQL statement that describes how to create the catalog
832    /// item.
833    fn create_sql(&self) -> &str;
834
835    /// Returns the IDs of the catalog items upon which this catalog item
836    /// directly references.
837    fn references(&self) -> &ResolvedIds;
838
839    /// Returns the IDs of the catalog items upon which this catalog item
840    /// depends.
841    fn uses(&self) -> BTreeSet<CatalogItemId>;
842
843    /// Returns the IDs of the catalog items that directly reference this catalog item.
844    fn referenced_by(&self) -> &[CatalogItemId];
845
846    /// Returns the IDs of the catalog items that depend upon this catalog item.
847    fn used_by(&self) -> &[CatalogItemId];
848
849    /// Reports whether this catalog entry is a subsource and, if it is, the
850    /// ingestion it is an export of, as well as the item it exports.
851    fn subsource_details(
852        &self,
853    ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)>;
854
855    /// Reports whether this catalog entry is a source export and, if it is, the
856    /// ingestion it is an export of, as well as the item it exports.
857    fn source_export_details(
858        &self,
859    ) -> Option<(
860        CatalogItemId,
861        &UnresolvedItemName,
862        &SourceExportDetails,
863        &SourceExportDataConfig<ReferencedConnection>,
864    )>;
865
866    /// Reports whether this catalog item is a progress source.
867    fn is_progress_source(&self) -> bool;
868
869    /// If this catalog item is a source, it return the IDs of its progress collection.
870    fn progress_id(&self) -> Option<CatalogItemId>;
871
872    /// Returns the index details associated with the catalog item, if the
873    /// catalog item is an index.
874    fn index_details(&self) -> Option<(&[MirScalarExpr], GlobalId)>;
875
876    /// Returns the column defaults associated with the catalog item, if the
877    /// catalog item is a table that accepts writes.
878    fn writable_table_details(&self) -> Option<&[Expr<Aug>]>;
879
880    /// The item this catalog item replaces, if any.
881    fn replacement_target(&self) -> Option<CatalogItemId>;
882
883    /// Returns the type information associated with the catalog item, if the
884    /// catalog item is a type.
885    fn type_details(&self) -> Option<&CatalogTypeDetails<IdReference>>;
886
887    /// Returns the ID of the owning role.
888    fn owner_id(&self) -> RoleId;
889
890    /// Returns the privileges associated with the item.
891    fn privileges(&self) -> &PrivilegeMap;
892
893    /// Returns the cluster the item belongs to.
894    fn cluster_id(&self) -> Option<ClusterId>;
895
896    /// Returns the [`CatalogCollectionItem`] for a specific version of this
897    /// [`CatalogItem`].
898    fn at_version(&self, version: RelationVersionSelector) -> Box<dyn CatalogCollectionItem>;
899
900    /// The latest version of this item, if it's version-able.
901    fn latest_version(&self) -> Option<RelationVersion>;
902}
903
904/// An item in a [`SessionCatalog`] and the specific "collection"/pTVC that it
905/// refers to.
906pub trait CatalogCollectionItem: CatalogItem + Send + Sync {
907    /// Returns a description of the result set produced by the catalog item.
908    ///
909    /// If the catalog item is not of a type that produces data (e.g., a sink or
910    /// an index), it returns `None`.
911    fn relation_desc(&self) -> Option<Cow<'_, RelationDesc>>;
912
913    /// The [`GlobalId`] for this item.
914    fn global_id(&self) -> GlobalId;
915}
916
917/// The type of a [`CatalogItem`].
918#[derive(
919    Debug,
920    Deserialize,
921    Clone,
922    Copy,
923    Eq,
924    Hash,
925    Ord,
926    PartialEq,
927    PartialOrd,
928    Serialize
929)]
930pub enum CatalogItemType {
931    /// A table.
932    Table,
933    /// A source.
934    Source,
935    /// A sink.
936    Sink,
937    /// A view.
938    View,
939    /// A materialized view.
940    MaterializedView,
941    /// An index.
942    Index,
943    /// A type.
944    Type,
945    /// A func.
946    Func,
947    /// A secret.
948    Secret,
949    /// A connection.
950    Connection,
951    /// A continual task.
952    ContinualTask,
953}
954
955impl CatalogItemType {
956    /// Reports whether the given type of item conflicts with items of type
957    /// `CatalogItemType::Type`.
958    ///
959    /// In PostgreSQL, even though types live in a separate namespace from other
960    /// schema objects, creating a table, view, or materialized view creates a
961    /// type named after that relation. This prevents creating a type with the
962    /// same name as a relational object, even though types and relational
963    /// objects live in separate namespaces. (Indexes are even weirder; while
964    /// they don't get a type with the same name, they get an entry in
965    /// `pg_class` that prevents *record* types of the same name as the index,
966    /// but not other types of types, like enums.)
967    ///
968    /// We don't presently construct types that mirror relational objects,
969    /// though we likely will need to in the future for full PostgreSQL
970    /// compatibility (see database-issues#7142). For now, we use this method to
971    /// prevent creating types and relational objects that have the same name, so
972    /// that it is a backwards compatible change in the future to introduce a
973    /// type named after each relational object in the system.
974    pub fn conflicts_with_type(&self) -> bool {
975        match self {
976            CatalogItemType::Table => true,
977            CatalogItemType::Source => true,
978            CatalogItemType::View => true,
979            CatalogItemType::MaterializedView => true,
980            CatalogItemType::Index => true,
981            CatalogItemType::Type => true,
982            CatalogItemType::Sink => false,
983            CatalogItemType::Func => false,
984            CatalogItemType::Secret => false,
985            CatalogItemType::Connection => false,
986            CatalogItemType::ContinualTask => true,
987        }
988    }
989}
990
991impl fmt::Display for CatalogItemType {
992    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
993        match self {
994            CatalogItemType::Table => f.write_str("table"),
995            CatalogItemType::Source => f.write_str("source"),
996            CatalogItemType::Sink => f.write_str("sink"),
997            CatalogItemType::View => f.write_str("view"),
998            CatalogItemType::MaterializedView => f.write_str("materialized view"),
999            CatalogItemType::Index => f.write_str("index"),
1000            CatalogItemType::Type => f.write_str("type"),
1001            CatalogItemType::Func => f.write_str("func"),
1002            CatalogItemType::Secret => f.write_str("secret"),
1003            CatalogItemType::Connection => f.write_str("connection"),
1004            CatalogItemType::ContinualTask => f.write_str("continual task"),
1005        }
1006    }
1007}
1008
1009impl From<CatalogItemType> for ObjectType {
1010    fn from(value: CatalogItemType) -> Self {
1011        match value {
1012            CatalogItemType::Table => ObjectType::Table,
1013            CatalogItemType::Source => ObjectType::Source,
1014            CatalogItemType::Sink => ObjectType::Sink,
1015            CatalogItemType::View => ObjectType::View,
1016            CatalogItemType::MaterializedView => ObjectType::MaterializedView,
1017            CatalogItemType::Index => ObjectType::Index,
1018            CatalogItemType::Type => ObjectType::Type,
1019            CatalogItemType::Func => ObjectType::Func,
1020            CatalogItemType::Secret => ObjectType::Secret,
1021            CatalogItemType::Connection => ObjectType::Connection,
1022            CatalogItemType::ContinualTask => ObjectType::ContinualTask,
1023        }
1024    }
1025}
1026
1027impl From<CatalogItemType> for mz_audit_log::ObjectType {
1028    fn from(value: CatalogItemType) -> Self {
1029        match value {
1030            CatalogItemType::Table => mz_audit_log::ObjectType::Table,
1031            CatalogItemType::Source => mz_audit_log::ObjectType::Source,
1032            CatalogItemType::View => mz_audit_log::ObjectType::View,
1033            CatalogItemType::MaterializedView => mz_audit_log::ObjectType::MaterializedView,
1034            CatalogItemType::Index => mz_audit_log::ObjectType::Index,
1035            CatalogItemType::Type => mz_audit_log::ObjectType::Type,
1036            CatalogItemType::Sink => mz_audit_log::ObjectType::Sink,
1037            CatalogItemType::Func => mz_audit_log::ObjectType::Func,
1038            CatalogItemType::Secret => mz_audit_log::ObjectType::Secret,
1039            CatalogItemType::Connection => mz_audit_log::ObjectType::Connection,
1040            CatalogItemType::ContinualTask => mz_audit_log::ObjectType::ContinualTask,
1041        }
1042    }
1043}
1044
1045/// Details about a type in the catalog.
1046#[derive(Clone, Debug, Eq, PartialEq)]
1047pub struct CatalogTypeDetails<T: TypeReference> {
1048    /// The ID of the type with this type as the array element, if available.
1049    pub array_id: Option<CatalogItemId>,
1050    /// The description of this type.
1051    pub typ: CatalogType<T>,
1052    /// Additional metadata about the type in PostgreSQL, if relevant.
1053    pub pg_metadata: Option<CatalogTypePgMetadata>,
1054}
1055
1056/// Additional PostgreSQL metadata about a type.
1057#[derive(Clone, Debug, Eq, PartialEq)]
1058pub struct CatalogTypePgMetadata {
1059    /// The OID of the `typinput` function in PostgreSQL.
1060    pub typinput_oid: u32,
1061    /// The OID of the `typreceive` function in PostgreSQL.
1062    pub typreceive_oid: u32,
1063}
1064
1065/// Represents a reference to type in the catalog
1066pub trait TypeReference {
1067    /// The actual type used to reference a `CatalogType`
1068    type Reference: Clone + Debug + Eq + PartialEq;
1069}
1070
1071/// Reference to a type by it's name
1072#[derive(Clone, Debug, Eq, PartialEq)]
1073pub struct NameReference;
1074
1075impl TypeReference for NameReference {
1076    type Reference = &'static str;
1077}
1078
1079/// Reference to a type by it's global ID
1080#[derive(Clone, Debug, Eq, PartialEq)]
1081pub struct IdReference;
1082
1083impl TypeReference for IdReference {
1084    type Reference = CatalogItemId;
1085}
1086
1087/// A type stored in the catalog.
1088///
1089/// The variants correspond one-to-one with [`mz_repr::SqlScalarType`], but with type
1090/// modifiers removed and with embedded types replaced with references to other
1091/// types in the catalog.
1092#[allow(missing_docs)]
1093#[derive(Clone, Debug, Eq, PartialEq)]
1094pub enum CatalogType<T: TypeReference> {
1095    AclItem,
1096    Array {
1097        element_reference: T::Reference,
1098    },
1099    Bool,
1100    Bytes,
1101    Char,
1102    Date,
1103    Float32,
1104    Float64,
1105    Int16,
1106    Int32,
1107    Int64,
1108    UInt16,
1109    UInt32,
1110    UInt64,
1111    MzTimestamp,
1112    Interval,
1113    Jsonb,
1114    List {
1115        element_reference: T::Reference,
1116        element_modifiers: Vec<i64>,
1117    },
1118    Map {
1119        key_reference: T::Reference,
1120        key_modifiers: Vec<i64>,
1121        value_reference: T::Reference,
1122        value_modifiers: Vec<i64>,
1123    },
1124    Numeric,
1125    Oid,
1126    PgLegacyChar,
1127    PgLegacyName,
1128    Pseudo,
1129    Range {
1130        element_reference: T::Reference,
1131    },
1132    Record {
1133        fields: Vec<CatalogRecordField<T>>,
1134    },
1135    RegClass,
1136    RegProc,
1137    RegType,
1138    String,
1139    Time,
1140    Timestamp,
1141    TimestampTz,
1142    Uuid,
1143    VarChar,
1144    Int2Vector,
1145    MzAclItem,
1146}
1147
1148impl CatalogType<IdReference> {
1149    /// Returns the relation description for the type, if the type is a record
1150    /// type.
1151    pub fn desc(&self, catalog: &dyn SessionCatalog) -> Result<Option<RelationDesc>, PlanError> {
1152        match &self {
1153            CatalogType::Record { fields } => {
1154                let mut desc = RelationDesc::builder();
1155                for f in fields {
1156                    let name = f.name.clone();
1157                    let ty = query::scalar_type_from_catalog(
1158                        catalog,
1159                        f.type_reference,
1160                        &f.type_modifiers,
1161                    )?;
1162                    // TODO: support plumbing `NOT NULL` constraints through
1163                    // `CREATE TYPE`.
1164                    let ty = ty.nullable(true);
1165                    desc = desc.with_column(name, ty);
1166                }
1167                Ok(Some(desc.finish()))
1168            }
1169            _ => Ok(None),
1170        }
1171    }
1172}
1173
1174/// A description of a field in a [`CatalogType::Record`].
1175#[derive(Clone, Debug, Eq, PartialEq)]
1176pub struct CatalogRecordField<T: TypeReference> {
1177    /// The name of the field.
1178    pub name: ColumnName,
1179    /// The ID of the type of the field.
1180    pub type_reference: T::Reference,
1181    /// Modifiers to apply to the type.
1182    pub type_modifiers: Vec<i64>,
1183}
1184
1185#[derive(Clone, Debug, Eq, PartialEq)]
1186/// Mirrored from [PostgreSQL's `typcategory`][typcategory].
1187///
1188/// Note that Materialize also uses a number of pseudotypes when planning, but
1189/// we have yet to need to integrate them with `TypeCategory`.
1190///
1191/// [typcategory]:
1192/// https://www.postgresql.org/docs/9.6/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE
1193pub enum TypeCategory {
1194    /// Array type.
1195    Array,
1196    /// Bit string type.
1197    BitString,
1198    /// Boolean type.
1199    Boolean,
1200    /// Composite type.
1201    Composite,
1202    /// Date/time type.
1203    DateTime,
1204    /// Enum type.
1205    Enum,
1206    /// Geometric type.
1207    Geometric,
1208    /// List type. Materialize specific.
1209    List,
1210    /// Network address type.
1211    NetworkAddress,
1212    /// Numeric type.
1213    Numeric,
1214    /// Pseudo type.
1215    Pseudo,
1216    /// Range type.
1217    Range,
1218    /// String type.
1219    String,
1220    /// Timestamp type.
1221    Timespan,
1222    /// User-defined type.
1223    UserDefined,
1224    /// Unknown type.
1225    Unknown,
1226}
1227
1228impl fmt::Display for TypeCategory {
1229    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1230        f.write_str(match self {
1231            TypeCategory::Array => "array",
1232            TypeCategory::BitString => "bit-string",
1233            TypeCategory::Boolean => "boolean",
1234            TypeCategory::Composite => "composite",
1235            TypeCategory::DateTime => "date-time",
1236            TypeCategory::Enum => "enum",
1237            TypeCategory::Geometric => "geometric",
1238            TypeCategory::List => "list",
1239            TypeCategory::NetworkAddress => "network-address",
1240            TypeCategory::Numeric => "numeric",
1241            TypeCategory::Pseudo => "pseudo",
1242            TypeCategory::Range => "range",
1243            TypeCategory::String => "string",
1244            TypeCategory::Timespan => "timespan",
1245            TypeCategory::UserDefined => "user-defined",
1246            TypeCategory::Unknown => "unknown",
1247        })
1248    }
1249}
1250
1251/// Identifies an environment.
1252///
1253/// Outside of tests, an environment ID can be constructed only from a string of
1254/// the following form:
1255///
1256/// ```text
1257/// <CLOUD PROVIDER>-<CLOUD PROVIDER REGION>-<ORGANIZATION ID>-<ORDINAL>
1258/// ```
1259///
1260/// The fields have the following formats:
1261///
1262/// * The cloud provider consists of one or more alphanumeric characters.
1263/// * The cloud provider region consists of one or more alphanumeric or hyphen
1264///   characters.
1265/// * The organization ID is a UUID in its canonical text format.
1266/// * The ordinal is a decimal number with between one and eight digits.
1267///
1268/// There is no way to construct an environment ID from parts, to ensure that
1269/// the `Display` representation is parseable according to the above rules.
1270// NOTE(benesch): ideally we'd have accepted the components of the environment
1271// ID using separate command-line arguments, or at least a string format that
1272// used a field separator that did not appear in the fields. Alas. We can't
1273// easily change it now, as it's used as the e.g. default sink progress topic.
1274#[derive(Debug, Clone, PartialEq)]
1275pub struct EnvironmentId {
1276    cloud_provider: CloudProvider,
1277    cloud_provider_region: String,
1278    organization_id: Uuid,
1279    ordinal: u64,
1280}
1281
1282impl EnvironmentId {
1283    /// Creates a dummy `EnvironmentId` for use in tests.
1284    pub fn for_tests() -> EnvironmentId {
1285        EnvironmentId {
1286            cloud_provider: CloudProvider::Local,
1287            cloud_provider_region: "az1".into(),
1288            organization_id: Uuid::new_v4(),
1289            ordinal: 0,
1290        }
1291    }
1292
1293    /// Returns the cloud provider associated with this environment ID.
1294    pub fn cloud_provider(&self) -> &CloudProvider {
1295        &self.cloud_provider
1296    }
1297
1298    /// Returns the cloud provider region associated with this environment ID.
1299    pub fn cloud_provider_region(&self) -> &str {
1300        &self.cloud_provider_region
1301    }
1302
1303    /// Returns the name of the region associted with this environment ID.
1304    ///
1305    /// A region is a combination of [`EnvironmentId::cloud_provider`] and
1306    /// [`EnvironmentId::cloud_provider_region`].
1307    pub fn region(&self) -> String {
1308        format!("{}/{}", self.cloud_provider, self.cloud_provider_region)
1309    }
1310
1311    /// Returns the organization ID associated with this environment ID.
1312    pub fn organization_id(&self) -> Uuid {
1313        self.organization_id
1314    }
1315
1316    /// Returns the ordinal associated with this environment ID.
1317    pub fn ordinal(&self) -> u64 {
1318        self.ordinal
1319    }
1320}
1321
1322// *Warning*: once the LaunchDarkly integration is live, our contexts will be
1323// populated using this key. Consequently, any changes to that trait
1324// implementation will also have to be reflected in the existing feature
1325// targeting config in LaunchDarkly, otherwise environments might receive
1326// different configs upon restart.
1327impl fmt::Display for EnvironmentId {
1328    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1329        write!(
1330            f,
1331            "{}-{}-{}-{}",
1332            self.cloud_provider, self.cloud_provider_region, self.organization_id, self.ordinal
1333        )
1334    }
1335}
1336
1337impl FromStr for EnvironmentId {
1338    type Err = InvalidEnvironmentIdError;
1339
1340    fn from_str(s: &str) -> Result<EnvironmentId, InvalidEnvironmentIdError> {
1341        static MATCHER: LazyLock<Regex> = LazyLock::new(|| {
1342            Regex::new(
1343                "^(?P<cloud_provider>[[:alnum:]]+)-\
1344                  (?P<cloud_provider_region>[[:alnum:]\\-]+)-\
1345                  (?P<organization_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-\
1346                  (?P<ordinal>\\d{1,8})$"
1347            ).unwrap()
1348        });
1349        let captures = MATCHER.captures(s).ok_or(InvalidEnvironmentIdError)?;
1350        Ok(EnvironmentId {
1351            cloud_provider: CloudProvider::from_str(&captures["cloud_provider"])?,
1352            cloud_provider_region: captures["cloud_provider_region"].into(),
1353            organization_id: captures["organization_id"]
1354                .parse()
1355                .map_err(|_| InvalidEnvironmentIdError)?,
1356            ordinal: captures["ordinal"]
1357                .parse()
1358                .map_err(|_| InvalidEnvironmentIdError)?,
1359        })
1360    }
1361}
1362
1363/// The error type for [`EnvironmentId::from_str`].
1364#[derive(Debug, Clone, PartialEq)]
1365pub struct InvalidEnvironmentIdError;
1366
1367impl fmt::Display for InvalidEnvironmentIdError {
1368    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1369        f.write_str("invalid environment ID")
1370    }
1371}
1372
1373impl Error for InvalidEnvironmentIdError {}
1374
1375impl From<InvalidCloudProviderError> for InvalidEnvironmentIdError {
1376    fn from(_: InvalidCloudProviderError) -> Self {
1377        InvalidEnvironmentIdError
1378    }
1379}
1380
1381/// An error returned by the catalog.
1382#[derive(Clone, Debug, Eq, PartialEq)]
1383pub enum CatalogError {
1384    /// Unknown database.
1385    UnknownDatabase(String),
1386    /// Database already exists.
1387    DatabaseAlreadyExists(String),
1388    /// Unknown schema.
1389    UnknownSchema(String),
1390    /// Schema already exists.
1391    SchemaAlreadyExists(String),
1392    /// Unknown role.
1393    UnknownRole(String),
1394    /// Role already exists.
1395    RoleAlreadyExists(String),
1396    /// Network Policy already exists.
1397    NetworkPolicyAlreadyExists(String),
1398    /// Unknown cluster.
1399    UnknownCluster(String),
1400    /// Unexpected builtin cluster.
1401    UnexpectedBuiltinCluster(String),
1402    /// Unexpected builtin cluster.
1403    UnexpectedBuiltinClusterType(String),
1404    /// Cluster already exists.
1405    ClusterAlreadyExists(String),
1406    /// Unknown cluster replica.
1407    UnknownClusterReplica(String),
1408    /// Unknown cluster replica size.
1409    UnknownClusterReplicaSize(String),
1410    /// Duplicate Replica. #[error("cannot create multiple replicas named '{0}' on cluster '{1}'")]
1411    DuplicateReplica(String, String),
1412    /// Unknown item.
1413    UnknownItem(String),
1414    /// Item already exists.
1415    ItemAlreadyExists(CatalogItemId, String),
1416    /// Unknown function.
1417    UnknownFunction {
1418        /// The identifier of the function we couldn't find
1419        name: String,
1420        /// A suggested alternative to the named function.
1421        alternative: Option<String>,
1422    },
1423    /// Unknown type.
1424    UnknownType {
1425        /// The identifier of the type we couldn't find.
1426        name: String,
1427    },
1428    /// Unknown connection.
1429    UnknownConnection(String),
1430    /// Unknown network policy.
1431    UnknownNetworkPolicy(String),
1432    /// Expected the catalog item to have the given type, but it did not.
1433    UnexpectedType {
1434        /// The item's name.
1435        name: String,
1436        /// The actual type of the item.
1437        actual_type: CatalogItemType,
1438        /// The expected type of the item.
1439        expected_type: CatalogItemType,
1440    },
1441    /// Ran out of unique IDs.
1442    IdExhaustion,
1443    /// Ran out of unique OIDs.
1444    OidExhaustion,
1445    /// Timeline already exists.
1446    TimelineAlreadyExists(String),
1447    /// Id Allocator already exists.
1448    IdAllocatorAlreadyExists(String),
1449    /// Config already exists.
1450    ConfigAlreadyExists(String),
1451    /// Builtin migrations failed.
1452    FailedBuiltinSchemaMigration(String),
1453    /// StorageCollectionMetadata already exists.
1454    StorageCollectionMetadataAlreadyExists(GlobalId),
1455}
1456
1457impl fmt::Display for CatalogError {
1458    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1459        match self {
1460            Self::UnknownDatabase(name) => write!(f, "unknown database '{}'", name),
1461            Self::DatabaseAlreadyExists(name) => write!(f, "database '{name}' already exists"),
1462            Self::UnknownFunction { name, .. } => write!(f, "function \"{}\" does not exist", name),
1463            Self::UnknownType { name, .. } => write!(f, "type \"{}\" does not exist", name),
1464            Self::UnknownConnection(name) => write!(f, "connection \"{}\" does not exist", name),
1465            Self::UnknownSchema(name) => write!(f, "unknown schema '{}'", name),
1466            Self::SchemaAlreadyExists(name) => write!(f, "schema '{name}' already exists"),
1467            Self::UnknownRole(name) => write!(f, "unknown role '{}'", name),
1468            Self::RoleAlreadyExists(name) => write!(f, "role '{name}' already exists"),
1469            Self::NetworkPolicyAlreadyExists(name) => {
1470                write!(f, "network policy '{name}' already exists")
1471            }
1472            Self::UnknownCluster(name) => write!(f, "unknown cluster '{}'", name),
1473            Self::UnknownNetworkPolicy(name) => write!(f, "unknown network policy '{}'", name),
1474            Self::UnexpectedBuiltinCluster(name) => {
1475                write!(f, "Unexpected builtin cluster '{}'", name)
1476            }
1477            Self::UnexpectedBuiltinClusterType(name) => {
1478                write!(f, "Unexpected builtin cluster type'{}'", name)
1479            }
1480            Self::ClusterAlreadyExists(name) => write!(f, "cluster '{name}' already exists"),
1481            Self::UnknownClusterReplica(name) => {
1482                write!(f, "unknown cluster replica '{}'", name)
1483            }
1484            Self::UnknownClusterReplicaSize(name) => {
1485                write!(f, "unknown cluster replica size '{}'", name)
1486            }
1487            Self::DuplicateReplica(replica_name, cluster_name) => write!(
1488                f,
1489                "cannot create multiple replicas named '{replica_name}' on cluster '{cluster_name}'"
1490            ),
1491            Self::UnknownItem(name) => write!(f, "unknown catalog item '{}'", name),
1492            Self::ItemAlreadyExists(_gid, name) => {
1493                write!(f, "catalog item '{name}' already exists")
1494            }
1495            Self::UnexpectedType {
1496                name,
1497                actual_type,
1498                expected_type,
1499            } => {
1500                write!(f, "\"{name}\" is a {actual_type} not a {expected_type}")
1501            }
1502            Self::IdExhaustion => write!(f, "id counter overflows i64"),
1503            Self::OidExhaustion => write!(f, "oid counter overflows u32"),
1504            Self::TimelineAlreadyExists(name) => write!(f, "timeline '{name}' already exists"),
1505            Self::IdAllocatorAlreadyExists(name) => {
1506                write!(f, "ID allocator '{name}' already exists")
1507            }
1508            Self::ConfigAlreadyExists(key) => write!(f, "config '{key}' already exists"),
1509            Self::FailedBuiltinSchemaMigration(objects) => {
1510                write!(f, "failed to migrate schema of builtin objects: {objects}")
1511            }
1512            Self::StorageCollectionMetadataAlreadyExists(key) => {
1513                write!(f, "storage metadata for '{key}' already exists")
1514            }
1515        }
1516    }
1517}
1518
1519impl CatalogError {
1520    /// Returns any applicable hints for [`CatalogError`].
1521    pub fn hint(&self) -> Option<String> {
1522        match self {
1523            CatalogError::UnknownFunction { alternative, .. } => {
1524                match alternative {
1525                    None => Some("No function matches the given name and argument types. You might need to add explicit type casts.".into()),
1526                    Some(alt) => Some(format!("Try using {alt}")),
1527                }
1528            }
1529            _ => None,
1530        }
1531    }
1532}
1533
1534impl Error for CatalogError {}
1535
1536// Enum variant docs would be useless here.
1537#[allow(missing_docs)]
1538#[derive(
1539    Debug,
1540    Clone,
1541    PartialOrd,
1542    Ord,
1543    PartialEq,
1544    Eq,
1545    Hash,
1546    Copy,
1547    Deserialize,
1548    Serialize
1549)]
1550/// The types of objects stored in the catalog.
1551pub enum ObjectType {
1552    Table,
1553    View,
1554    MaterializedView,
1555    Source,
1556    Sink,
1557    Index,
1558    Type,
1559    Role,
1560    Cluster,
1561    ClusterReplica,
1562    Secret,
1563    Connection,
1564    Database,
1565    Schema,
1566    Func,
1567    ContinualTask,
1568    NetworkPolicy,
1569}
1570
1571impl ObjectType {
1572    /// Reports if the object type can be treated as a relation.
1573    pub fn is_relation(&self) -> bool {
1574        match self {
1575            ObjectType::Table
1576            | ObjectType::View
1577            | ObjectType::MaterializedView
1578            | ObjectType::Source
1579            | ObjectType::ContinualTask => true,
1580            ObjectType::Sink
1581            | ObjectType::Index
1582            | ObjectType::Type
1583            | ObjectType::Secret
1584            | ObjectType::Connection
1585            | ObjectType::Func
1586            | ObjectType::Database
1587            | ObjectType::Schema
1588            | ObjectType::Cluster
1589            | ObjectType::ClusterReplica
1590            | ObjectType::Role
1591            | ObjectType::NetworkPolicy => false,
1592        }
1593    }
1594}
1595
1596impl From<mz_sql_parser::ast::ObjectType> for ObjectType {
1597    fn from(value: mz_sql_parser::ast::ObjectType) -> Self {
1598        match value {
1599            mz_sql_parser::ast::ObjectType::Table => ObjectType::Table,
1600            mz_sql_parser::ast::ObjectType::View => ObjectType::View,
1601            mz_sql_parser::ast::ObjectType::MaterializedView => ObjectType::MaterializedView,
1602            mz_sql_parser::ast::ObjectType::Source => ObjectType::Source,
1603            mz_sql_parser::ast::ObjectType::Subsource => ObjectType::Source,
1604            mz_sql_parser::ast::ObjectType::Sink => ObjectType::Sink,
1605            mz_sql_parser::ast::ObjectType::Index => ObjectType::Index,
1606            mz_sql_parser::ast::ObjectType::Type => ObjectType::Type,
1607            mz_sql_parser::ast::ObjectType::Role => ObjectType::Role,
1608            mz_sql_parser::ast::ObjectType::Cluster => ObjectType::Cluster,
1609            mz_sql_parser::ast::ObjectType::ClusterReplica => ObjectType::ClusterReplica,
1610            mz_sql_parser::ast::ObjectType::Secret => ObjectType::Secret,
1611            mz_sql_parser::ast::ObjectType::Connection => ObjectType::Connection,
1612            mz_sql_parser::ast::ObjectType::Database => ObjectType::Database,
1613            mz_sql_parser::ast::ObjectType::Schema => ObjectType::Schema,
1614            mz_sql_parser::ast::ObjectType::Func => ObjectType::Func,
1615            mz_sql_parser::ast::ObjectType::ContinualTask => ObjectType::ContinualTask,
1616            mz_sql_parser::ast::ObjectType::NetworkPolicy => ObjectType::NetworkPolicy,
1617        }
1618    }
1619}
1620
1621impl From<CommentObjectId> for ObjectType {
1622    fn from(value: CommentObjectId) -> ObjectType {
1623        match value {
1624            CommentObjectId::Table(_) => ObjectType::Table,
1625            CommentObjectId::View(_) => ObjectType::View,
1626            CommentObjectId::MaterializedView(_) => ObjectType::MaterializedView,
1627            CommentObjectId::Source(_) => ObjectType::Source,
1628            CommentObjectId::Sink(_) => ObjectType::Sink,
1629            CommentObjectId::Index(_) => ObjectType::Index,
1630            CommentObjectId::Func(_) => ObjectType::Func,
1631            CommentObjectId::Connection(_) => ObjectType::Connection,
1632            CommentObjectId::Type(_) => ObjectType::Type,
1633            CommentObjectId::Secret(_) => ObjectType::Secret,
1634            CommentObjectId::Role(_) => ObjectType::Role,
1635            CommentObjectId::Database(_) => ObjectType::Database,
1636            CommentObjectId::Schema(_) => ObjectType::Schema,
1637            CommentObjectId::Cluster(_) => ObjectType::Cluster,
1638            CommentObjectId::ClusterReplica(_) => ObjectType::ClusterReplica,
1639            CommentObjectId::ContinualTask(_) => ObjectType::ContinualTask,
1640            CommentObjectId::NetworkPolicy(_) => ObjectType::NetworkPolicy,
1641        }
1642    }
1643}
1644
1645impl Display for ObjectType {
1646    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1647        f.write_str(match self {
1648            ObjectType::Table => "TABLE",
1649            ObjectType::View => "VIEW",
1650            ObjectType::MaterializedView => "MATERIALIZED VIEW",
1651            ObjectType::Source => "SOURCE",
1652            ObjectType::Sink => "SINK",
1653            ObjectType::Index => "INDEX",
1654            ObjectType::Type => "TYPE",
1655            ObjectType::Role => "ROLE",
1656            ObjectType::Cluster => "CLUSTER",
1657            ObjectType::ClusterReplica => "CLUSTER REPLICA",
1658            ObjectType::Secret => "SECRET",
1659            ObjectType::Connection => "CONNECTION",
1660            ObjectType::Database => "DATABASE",
1661            ObjectType::Schema => "SCHEMA",
1662            ObjectType::Func => "FUNCTION",
1663            ObjectType::ContinualTask => "CONTINUAL TASK",
1664            ObjectType::NetworkPolicy => "NETWORK POLICY",
1665        })
1666    }
1667}
1668
1669#[derive(
1670    Debug,
1671    Clone,
1672    PartialOrd,
1673    Ord,
1674    PartialEq,
1675    Eq,
1676    Hash,
1677    Copy,
1678    Deserialize,
1679    Serialize
1680)]
1681/// The types of objects in the system.
1682pub enum SystemObjectType {
1683    /// Catalog object type.
1684    Object(ObjectType),
1685    /// Entire system.
1686    System,
1687}
1688
1689impl SystemObjectType {
1690    /// Reports if the object type can be treated as a relation.
1691    pub fn is_relation(&self) -> bool {
1692        match self {
1693            SystemObjectType::Object(object_type) => object_type.is_relation(),
1694            SystemObjectType::System => false,
1695        }
1696    }
1697}
1698
1699impl Display for SystemObjectType {
1700    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1701        match self {
1702            SystemObjectType::Object(object_type) => std::fmt::Display::fmt(&object_type, f),
1703            SystemObjectType::System => f.write_str("SYSTEM"),
1704        }
1705    }
1706}
1707
1708/// Enum used to format object names in error messages.
1709#[derive(Debug, Clone, PartialEq, Eq)]
1710pub enum ErrorMessageObjectDescription {
1711    /// The name of a specific object.
1712    Object {
1713        /// Type of object.
1714        object_type: ObjectType,
1715        /// Name of object.
1716        object_name: Option<String>,
1717    },
1718    /// The name of the entire system.
1719    System,
1720}
1721
1722impl ErrorMessageObjectDescription {
1723    /// Generate a new [`ErrorMessageObjectDescription`] from an [`ObjectId`].
1724    pub fn from_id(
1725        object_id: &ObjectId,
1726        catalog: &dyn SessionCatalog,
1727    ) -> ErrorMessageObjectDescription {
1728        let object_name = match object_id {
1729            ObjectId::Cluster(cluster_id) => catalog.get_cluster(*cluster_id).name().to_string(),
1730            ObjectId::ClusterReplica((cluster_id, replica_id)) => catalog
1731                .get_cluster_replica(*cluster_id, *replica_id)
1732                .name()
1733                .to_string(),
1734            ObjectId::Database(database_id) => catalog.get_database(database_id).name().to_string(),
1735            ObjectId::Schema((database_spec, schema_spec)) => {
1736                let name = catalog.get_schema(database_spec, schema_spec).name();
1737                catalog.resolve_full_schema_name(name).to_string()
1738            }
1739            ObjectId::Role(role_id) => catalog.get_role(role_id).name().to_string(),
1740            ObjectId::Item(id) => {
1741                let name = catalog.get_item(id).name();
1742                catalog.resolve_full_name(name).to_string()
1743            }
1744            ObjectId::NetworkPolicy(network_policy_id) => catalog
1745                .get_network_policy(network_policy_id)
1746                .name()
1747                .to_string(),
1748        };
1749        ErrorMessageObjectDescription::Object {
1750            object_type: catalog.get_object_type(object_id),
1751            object_name: Some(object_name),
1752        }
1753    }
1754
1755    /// Generate a new [`ErrorMessageObjectDescription`] from a [`SystemObjectId`].
1756    pub fn from_sys_id(
1757        object_id: &SystemObjectId,
1758        catalog: &dyn SessionCatalog,
1759    ) -> ErrorMessageObjectDescription {
1760        match object_id {
1761            SystemObjectId::Object(object_id) => {
1762                ErrorMessageObjectDescription::from_id(object_id, catalog)
1763            }
1764            SystemObjectId::System => ErrorMessageObjectDescription::System,
1765        }
1766    }
1767
1768    /// Generate a new [`ErrorMessageObjectDescription`] from a [`SystemObjectType`].
1769    pub fn from_object_type(object_type: SystemObjectType) -> ErrorMessageObjectDescription {
1770        match object_type {
1771            SystemObjectType::Object(object_type) => ErrorMessageObjectDescription::Object {
1772                object_type,
1773                object_name: None,
1774            },
1775            SystemObjectType::System => ErrorMessageObjectDescription::System,
1776        }
1777    }
1778}
1779
1780impl Display for ErrorMessageObjectDescription {
1781    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1782        match self {
1783            ErrorMessageObjectDescription::Object {
1784                object_type,
1785                object_name,
1786            } => {
1787                let object_name = object_name
1788                    .as_ref()
1789                    .map(|object_name| format!(" {}", object_name.quoted()))
1790                    .unwrap_or_else(|| "".to_string());
1791                write!(f, "{object_type}{object_name}")
1792            }
1793            ErrorMessageObjectDescription::System => f.write_str("SYSTEM"),
1794        }
1795    }
1796}
1797
1798#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
1799// These attributes are needed because the key of a map must be a string. We also
1800// get the added benefit of flattening this struct in it's serialized form.
1801#[serde(into = "BTreeMap<String, RoleId>")]
1802#[serde(try_from = "BTreeMap<String, RoleId>")]
1803/// Represents the grantee and a grantor of a role membership.
1804pub struct RoleMembership {
1805    /// Key is the role that some role is a member of, value is the grantor role ID.
1806    // TODO(jkosh44) This structure does not allow a role to have multiple of the same membership
1807    // from different grantors. This isn't a problem now since we don't implement ADMIN OPTION, but
1808    // we should figure this out before implementing ADMIN OPTION. It will likely require a messy
1809    // migration.
1810    pub map: BTreeMap<RoleId, RoleId>,
1811}
1812
1813impl RoleMembership {
1814    /// Creates a new [`RoleMembership`].
1815    pub fn new() -> RoleMembership {
1816        RoleMembership {
1817            map: BTreeMap::new(),
1818        }
1819    }
1820}
1821
1822impl From<RoleMembership> for BTreeMap<String, RoleId> {
1823    fn from(value: RoleMembership) -> Self {
1824        value
1825            .map
1826            .into_iter()
1827            .map(|(k, v)| (k.to_string(), v))
1828            .collect()
1829    }
1830}
1831
1832impl TryFrom<BTreeMap<String, RoleId>> for RoleMembership {
1833    type Error = anyhow::Error;
1834
1835    fn try_from(value: BTreeMap<String, RoleId>) -> Result<Self, Self::Error> {
1836        Ok(RoleMembership {
1837            map: value
1838                .into_iter()
1839                .map(|(k, v)| Ok((RoleId::from_str(&k)?, v)))
1840                .collect::<Result<_, anyhow::Error>>()?,
1841        })
1842    }
1843}
1844
1845/// Specification for objects that will be affected by a default privilege.
1846#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1847pub struct DefaultPrivilegeObject {
1848    /// The role id that created the object.
1849    pub role_id: RoleId,
1850    /// The database that the object is created in if Some, otherwise all databases.
1851    pub database_id: Option<DatabaseId>,
1852    /// The schema that the object is created in if Some, otherwise all databases.
1853    pub schema_id: Option<SchemaId>,
1854    /// The type of object.
1855    pub object_type: ObjectType,
1856}
1857
1858impl DefaultPrivilegeObject {
1859    /// Creates a new [`DefaultPrivilegeObject`].
1860    pub fn new(
1861        role_id: RoleId,
1862        database_id: Option<DatabaseId>,
1863        schema_id: Option<SchemaId>,
1864        object_type: ObjectType,
1865    ) -> DefaultPrivilegeObject {
1866        DefaultPrivilegeObject {
1867            role_id,
1868            database_id,
1869            schema_id,
1870            object_type,
1871        }
1872    }
1873}
1874
1875impl std::fmt::Display for DefaultPrivilegeObject {
1876    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1877        // TODO: Don't just wrap Debug.
1878        write!(f, "{self:?}")
1879    }
1880}
1881
1882/// Specification for the privileges that will be granted from default privileges.
1883#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1884pub struct DefaultPrivilegeAclItem {
1885    /// The role that will receive the privileges.
1886    pub grantee: RoleId,
1887    /// The specific privileges granted.
1888    pub acl_mode: AclMode,
1889}
1890
1891impl DefaultPrivilegeAclItem {
1892    /// Creates a new [`DefaultPrivilegeAclItem`].
1893    pub fn new(grantee: RoleId, acl_mode: AclMode) -> DefaultPrivilegeAclItem {
1894        DefaultPrivilegeAclItem { grantee, acl_mode }
1895    }
1896
1897    /// Converts this [`DefaultPrivilegeAclItem`] into an [`MzAclItem`].
1898    pub fn mz_acl_item(self, grantor: RoleId) -> MzAclItem {
1899        MzAclItem {
1900            grantee: self.grantee,
1901            grantor,
1902            acl_mode: self.acl_mode,
1903        }
1904    }
1905}
1906
1907/// Which builtins to return in `BUILTINS::iter`.
1908///
1909/// All calls to `BUILTINS::iter` within the lifetime of an environmentd process
1910/// must provide an equal `BuiltinsConfig`. It is not allowed to change
1911/// dynamically.
1912#[derive(Debug, Clone)]
1913pub struct BuiltinsConfig {
1914    /// If true, include system builtin continual tasks.
1915    pub include_continual_tasks: bool,
1916}
1917
1918#[cfg(test)]
1919mod tests {
1920    use super::{CloudProvider, EnvironmentId, InvalidEnvironmentIdError};
1921
1922    #[mz_ore::test]
1923    fn test_environment_id() {
1924        for (input, expected) in [
1925            (
1926                "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1927                Ok(EnvironmentId {
1928                    cloud_provider: CloudProvider::Local,
1929                    cloud_provider_region: "az1".into(),
1930                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1931                    ordinal: 452,
1932                }),
1933            ),
1934            (
1935                "aws-us-east-1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1936                Ok(EnvironmentId {
1937                    cloud_provider: CloudProvider::Aws,
1938                    cloud_provider_region: "us-east-1".into(),
1939                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1940                    ordinal: 0,
1941                }),
1942            ),
1943            (
1944                "gcp-us-central1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1945                Ok(EnvironmentId {
1946                    cloud_provider: CloudProvider::Gcp,
1947                    cloud_provider_region: "us-central1".into(),
1948                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1949                    ordinal: 0,
1950                }),
1951            ),
1952            (
1953                "azure-australiaeast-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1954                Ok(EnvironmentId {
1955                    cloud_provider: CloudProvider::Azure,
1956                    cloud_provider_region: "australiaeast".into(),
1957                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1958                    ordinal: 0,
1959                }),
1960            ),
1961            (
1962                "generic-moon-station-11-darkside-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1963                Ok(EnvironmentId {
1964                    cloud_provider: CloudProvider::Generic,
1965                    cloud_provider_region: "moon-station-11-darkside".into(),
1966                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1967                    ordinal: 0,
1968                }),
1969            ),
1970            ("", Err(InvalidEnvironmentIdError)),
1971            (
1972                "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-123456789",
1973                Err(InvalidEnvironmentIdError),
1974            ),
1975            (
1976                "local-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1977                Err(InvalidEnvironmentIdError),
1978            ),
1979            (
1980                "local-az1-1497a3b7-a455-4fc48752-b44a94b5f90a-452",
1981                Err(InvalidEnvironmentIdError),
1982            ),
1983        ] {
1984            let actual = input.parse();
1985            assert_eq!(expected, actual, "input = {}", input);
1986            if let Ok(actual) = actual {
1987                assert_eq!(input, actual.to_string(), "input = {}", input);
1988            }
1989        }
1990    }
1991}