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