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    /// The OID of the `typsend` function in PostgreSQL.
1087    pub typsend_oid: u32,
1088}
1089
1090/// Represents a reference to type in the catalog
1091pub trait TypeReference {
1092    /// The actual type used to reference a `CatalogType`
1093    type Reference: Clone + Debug + Eq + PartialEq;
1094}
1095
1096/// Reference to a type by it's name
1097#[derive(Clone, Debug, Eq, PartialEq)]
1098pub struct NameReference;
1099
1100impl TypeReference for NameReference {
1101    type Reference = &'static str;
1102}
1103
1104/// Reference to a type by it's global ID
1105#[derive(Clone, Debug, Eq, PartialEq)]
1106pub struct IdReference;
1107
1108impl TypeReference for IdReference {
1109    type Reference = CatalogItemId;
1110}
1111
1112/// A type stored in the catalog.
1113///
1114/// The variants correspond one-to-one with [`mz_repr::SqlScalarType`], but with type
1115/// modifiers removed and with embedded types replaced with references to other
1116/// types in the catalog.
1117#[allow(missing_docs)]
1118#[derive(Clone, Debug, Eq, PartialEq)]
1119pub enum CatalogType<T: TypeReference> {
1120    AclItem,
1121    Array {
1122        element_reference: T::Reference,
1123    },
1124    Bool,
1125    Bytes,
1126    Char,
1127    Date,
1128    Float32,
1129    Float64,
1130    Int16,
1131    Int32,
1132    Int64,
1133    UInt16,
1134    UInt32,
1135    UInt64,
1136    MzTimestamp,
1137    Interval,
1138    Jsonb,
1139    List {
1140        element_reference: T::Reference,
1141        element_modifiers: Vec<i64>,
1142    },
1143    Map {
1144        key_reference: T::Reference,
1145        key_modifiers: Vec<i64>,
1146        value_reference: T::Reference,
1147        value_modifiers: Vec<i64>,
1148    },
1149    Numeric,
1150    Oid,
1151    PgLegacyChar,
1152    PgLegacyName,
1153    Pseudo,
1154    Range {
1155        element_reference: T::Reference,
1156    },
1157    Record {
1158        fields: Vec<CatalogRecordField<T>>,
1159    },
1160    RegClass,
1161    RegProc,
1162    RegType,
1163    String,
1164    Time,
1165    Timestamp,
1166    TimestampTz,
1167    Uuid,
1168    VarChar,
1169    Int2Vector,
1170    MzAclItem,
1171}
1172
1173impl CatalogType<IdReference> {
1174    /// Returns the relation description for the type, if the type is a record
1175    /// type.
1176    pub fn desc(&self, catalog: &dyn SessionCatalog) -> Result<Option<RelationDesc>, PlanError> {
1177        match &self {
1178            CatalogType::Record { fields } => {
1179                let mut desc = RelationDesc::builder();
1180                // Share one budget across every field. Resolving each field with
1181                // a fresh budget would let a wide record materialize an
1182                // unbounded type tree here even though each field is individually
1183                // within the bound.
1184                let mut budget = query::TypeResolutionBudget::for_root(catalog);
1185                for f in fields {
1186                    let name = f.name.clone();
1187                    let ty = budget.resolve_child(catalog, f.type_reference, &f.type_modifiers)?;
1188                    // TODO: support plumbing `NOT NULL` constraints through
1189                    // `CREATE TYPE`.
1190                    let ty = ty.nullable(true);
1191                    desc = desc.with_column(name, ty);
1192                }
1193                Ok(Some(desc.finish()))
1194            }
1195            _ => Ok(None),
1196        }
1197    }
1198}
1199
1200/// A description of a field in a [`CatalogType::Record`].
1201#[derive(Clone, Debug, Eq, PartialEq)]
1202pub struct CatalogRecordField<T: TypeReference> {
1203    /// The name of the field.
1204    pub name: ColumnName,
1205    /// The ID of the type of the field.
1206    pub type_reference: T::Reference,
1207    /// Modifiers to apply to the type.
1208    pub type_modifiers: Vec<i64>,
1209}
1210
1211#[derive(Clone, Debug, Eq, PartialEq)]
1212/// Mirrored from [PostgreSQL's `typcategory`][typcategory].
1213///
1214/// Note that Materialize also uses a number of pseudotypes when planning, but
1215/// we have yet to need to integrate them with `TypeCategory`.
1216///
1217/// [typcategory]:
1218/// https://www.postgresql.org/docs/9.6/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE
1219pub enum TypeCategory {
1220    /// Array type.
1221    Array,
1222    /// Bit string type.
1223    BitString,
1224    /// Boolean type.
1225    Boolean,
1226    /// Composite type.
1227    Composite,
1228    /// Date/time type.
1229    DateTime,
1230    /// Enum type.
1231    Enum,
1232    /// Geometric type.
1233    Geometric,
1234    /// List type. Materialize specific.
1235    List,
1236    /// Network address type.
1237    NetworkAddress,
1238    /// Numeric type.
1239    Numeric,
1240    /// Pseudo type.
1241    Pseudo,
1242    /// Range type.
1243    Range,
1244    /// String type.
1245    String,
1246    /// Timestamp type.
1247    Timespan,
1248    /// User-defined type.
1249    UserDefined,
1250    /// Unknown type.
1251    Unknown,
1252}
1253
1254impl fmt::Display for TypeCategory {
1255    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1256        f.write_str(match self {
1257            TypeCategory::Array => "array",
1258            TypeCategory::BitString => "bit-string",
1259            TypeCategory::Boolean => "boolean",
1260            TypeCategory::Composite => "composite",
1261            TypeCategory::DateTime => "date-time",
1262            TypeCategory::Enum => "enum",
1263            TypeCategory::Geometric => "geometric",
1264            TypeCategory::List => "list",
1265            TypeCategory::NetworkAddress => "network-address",
1266            TypeCategory::Numeric => "numeric",
1267            TypeCategory::Pseudo => "pseudo",
1268            TypeCategory::Range => "range",
1269            TypeCategory::String => "string",
1270            TypeCategory::Timespan => "timespan",
1271            TypeCategory::UserDefined => "user-defined",
1272            TypeCategory::Unknown => "unknown",
1273        })
1274    }
1275}
1276
1277/// Identifies an environment.
1278///
1279/// Outside of tests, an environment ID can be constructed only from a string of
1280/// the following form:
1281///
1282/// ```text
1283/// <CLOUD PROVIDER>-<CLOUD PROVIDER REGION>-<ORGANIZATION ID>-<ORDINAL>
1284/// ```
1285///
1286/// The fields have the following formats:
1287///
1288/// * The cloud provider consists of one or more alphanumeric characters.
1289/// * The cloud provider region consists of one or more alphanumeric or hyphen
1290///   characters.
1291/// * The organization ID is a UUID in its canonical text format.
1292/// * The ordinal is a decimal number with between one and eight digits.
1293///
1294/// There is no way to construct an environment ID from parts, to ensure that
1295/// the `Display` representation is parseable according to the above rules.
1296// NOTE(benesch): ideally we'd have accepted the components of the environment
1297// ID using separate command-line arguments, or at least a string format that
1298// used a field separator that did not appear in the fields. Alas. We can't
1299// easily change it now, as it's used as the e.g. default sink progress topic.
1300#[derive(Debug, Clone, PartialEq)]
1301pub struct EnvironmentId {
1302    cloud_provider: CloudProvider,
1303    cloud_provider_region: String,
1304    organization_id: Uuid,
1305    ordinal: u64,
1306}
1307
1308impl EnvironmentId {
1309    /// Creates a dummy `EnvironmentId` for use in tests.
1310    pub fn for_tests() -> EnvironmentId {
1311        EnvironmentId {
1312            cloud_provider: CloudProvider::Local,
1313            cloud_provider_region: "az1".into(),
1314            organization_id: Uuid::new_v4(),
1315            ordinal: 0,
1316        }
1317    }
1318
1319    /// Returns the cloud provider associated with this environment ID.
1320    pub fn cloud_provider(&self) -> &CloudProvider {
1321        &self.cloud_provider
1322    }
1323
1324    /// Returns the cloud provider region associated with this environment ID.
1325    pub fn cloud_provider_region(&self) -> &str {
1326        &self.cloud_provider_region
1327    }
1328
1329    /// Returns the name of the region associted with this environment ID.
1330    ///
1331    /// A region is a combination of [`EnvironmentId::cloud_provider`] and
1332    /// [`EnvironmentId::cloud_provider_region`].
1333    pub fn region(&self) -> String {
1334        format!("{}/{}", self.cloud_provider, self.cloud_provider_region)
1335    }
1336
1337    /// Returns the organization ID associated with this environment ID.
1338    pub fn organization_id(&self) -> Uuid {
1339        self.organization_id
1340    }
1341
1342    /// Returns the ordinal associated with this environment ID.
1343    pub fn ordinal(&self) -> u64 {
1344        self.ordinal
1345    }
1346}
1347
1348// *Warning*: once the LaunchDarkly integration is live, our contexts will be
1349// populated using this key. Consequently, any changes to that trait
1350// implementation will also have to be reflected in the existing feature
1351// targeting config in LaunchDarkly, otherwise environments might receive
1352// different configs upon restart.
1353impl fmt::Display for EnvironmentId {
1354    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1355        write!(
1356            f,
1357            "{}-{}-{}-{}",
1358            self.cloud_provider, self.cloud_provider_region, self.organization_id, self.ordinal
1359        )
1360    }
1361}
1362
1363impl FromStr for EnvironmentId {
1364    type Err = InvalidEnvironmentIdError;
1365
1366    fn from_str(s: &str) -> Result<EnvironmentId, InvalidEnvironmentIdError> {
1367        static MATCHER: LazyLock<Regex> = LazyLock::new(|| {
1368            Regex::new(
1369                "^(?P<cloud_provider>[[:alnum:]]+)-\
1370                  (?P<cloud_provider_region>[[:alnum:]\\-]+)-\
1371                  (?P<organization_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-\
1372                  (?P<ordinal>\\d{1,8})$"
1373            ).unwrap()
1374        });
1375        let captures = MATCHER.captures(s).ok_or(InvalidEnvironmentIdError)?;
1376        Ok(EnvironmentId {
1377            cloud_provider: CloudProvider::from_str(&captures["cloud_provider"])?,
1378            cloud_provider_region: captures["cloud_provider_region"].into(),
1379            organization_id: captures["organization_id"]
1380                .parse()
1381                .map_err(|_| InvalidEnvironmentIdError)?,
1382            ordinal: captures["ordinal"]
1383                .parse()
1384                .map_err(|_| InvalidEnvironmentIdError)?,
1385        })
1386    }
1387}
1388
1389/// The error type for [`EnvironmentId::from_str`].
1390#[derive(Debug, Clone, PartialEq)]
1391pub struct InvalidEnvironmentIdError;
1392
1393impl fmt::Display for InvalidEnvironmentIdError {
1394    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1395        f.write_str("invalid environment ID")
1396    }
1397}
1398
1399impl Error for InvalidEnvironmentIdError {}
1400
1401impl From<InvalidCloudProviderError> for InvalidEnvironmentIdError {
1402    fn from(_: InvalidCloudProviderError) -> Self {
1403        InvalidEnvironmentIdError
1404    }
1405}
1406
1407/// An error returned by the catalog.
1408#[derive(Clone, Debug, Eq, PartialEq)]
1409pub enum CatalogError {
1410    /// Unknown database.
1411    UnknownDatabase(String),
1412    /// Database already exists.
1413    DatabaseAlreadyExists(String),
1414    /// Unknown schema.
1415    UnknownSchema(String),
1416    /// Schema already exists.
1417    SchemaAlreadyExists(String),
1418    /// Unknown role.
1419    UnknownRole(String),
1420    /// Role already exists.
1421    RoleAlreadyExists(String),
1422    /// Network Policy already exists.
1423    NetworkPolicyAlreadyExists(String),
1424    /// Unknown cluster.
1425    UnknownCluster(String),
1426    /// Unexpected builtin cluster.
1427    UnexpectedBuiltinCluster(String),
1428    /// Unexpected builtin cluster.
1429    UnexpectedBuiltinClusterType(String),
1430    /// Cluster already exists.
1431    ClusterAlreadyExists(String),
1432    /// Unknown cluster replica.
1433    UnknownClusterReplica(String),
1434    /// Unknown cluster replica size.
1435    UnknownClusterReplicaSize(String),
1436    /// Duplicate Replica. #[error("cannot create multiple replicas named '{0}' on cluster '{1}'")]
1437    DuplicateReplica(String, String),
1438    /// Unknown item.
1439    UnknownItem(String),
1440    /// Item already exists.
1441    ItemAlreadyExists(CatalogItemId, String),
1442    /// Unknown function.
1443    UnknownFunction {
1444        /// The identifier of the function we couldn't find
1445        name: String,
1446        /// A suggested alternative to the named function.
1447        alternative: Option<String>,
1448    },
1449    /// Unknown type.
1450    UnknownType {
1451        /// The identifier of the type we couldn't find.
1452        name: String,
1453    },
1454    /// Unknown connection.
1455    UnknownConnection(String),
1456    /// Unknown network policy.
1457    UnknownNetworkPolicy(String),
1458    /// Expected the catalog item to have the given type, but it did not.
1459    UnexpectedType {
1460        /// The item's name.
1461        name: String,
1462        /// The actual type of the item.
1463        actual_type: CatalogItemType,
1464        /// The expected type of the item.
1465        expected_type: CatalogItemType,
1466    },
1467    /// Ran out of unique IDs.
1468    IdExhaustion,
1469    /// Ran out of unique OIDs.
1470    OidExhaustion,
1471    /// Timeline already exists.
1472    TimelineAlreadyExists(String),
1473    /// Id Allocator already exists.
1474    IdAllocatorAlreadyExists(String),
1475    /// Config already exists.
1476    ConfigAlreadyExists(String),
1477    /// Builtin migrations failed.
1478    FailedBuiltinSchemaMigration(String),
1479    /// StorageCollectionMetadata already exists.
1480    StorageCollectionMetadataAlreadyExists(GlobalId),
1481}
1482
1483impl fmt::Display for CatalogError {
1484    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1485        match self {
1486            Self::UnknownDatabase(name) => write!(f, "unknown database '{}'", name),
1487            Self::DatabaseAlreadyExists(name) => write!(f, "database '{name}' already exists"),
1488            Self::UnknownFunction { name, .. } => write!(f, "function \"{}\" does not exist", name),
1489            Self::UnknownType { name, .. } => write!(f, "type \"{}\" does not exist", name),
1490            Self::UnknownConnection(name) => write!(f, "connection \"{}\" does not exist", name),
1491            Self::UnknownSchema(name) => write!(f, "unknown schema '{}'", name),
1492            Self::SchemaAlreadyExists(name) => write!(f, "schema '{name}' already exists"),
1493            Self::UnknownRole(name) => write!(f, "unknown role '{}'", name),
1494            Self::RoleAlreadyExists(name) => write!(f, "role '{name}' already exists"),
1495            Self::NetworkPolicyAlreadyExists(name) => {
1496                write!(f, "network policy '{name}' already exists")
1497            }
1498            Self::UnknownCluster(name) => write!(f, "unknown cluster '{}'", name),
1499            Self::UnknownNetworkPolicy(name) => write!(f, "unknown network policy '{}'", name),
1500            Self::UnexpectedBuiltinCluster(name) => {
1501                write!(f, "Unexpected builtin cluster '{}'", name)
1502            }
1503            Self::UnexpectedBuiltinClusterType(name) => {
1504                write!(f, "Unexpected builtin cluster type'{}'", name)
1505            }
1506            Self::ClusterAlreadyExists(name) => write!(f, "cluster '{name}' already exists"),
1507            Self::UnknownClusterReplica(name) => {
1508                write!(f, "unknown cluster replica '{}'", name)
1509            }
1510            Self::UnknownClusterReplicaSize(name) => {
1511                write!(f, "unknown cluster replica size '{}'", name)
1512            }
1513            Self::DuplicateReplica(replica_name, cluster_name) => write!(
1514                f,
1515                "cannot create multiple replicas named '{replica_name}' on cluster '{cluster_name}'"
1516            ),
1517            Self::UnknownItem(name) => write!(f, "unknown catalog item '{}'", name),
1518            Self::ItemAlreadyExists(_gid, name) => {
1519                write!(f, "catalog item '{name}' already exists")
1520            }
1521            Self::UnexpectedType {
1522                name,
1523                actual_type,
1524                expected_type,
1525            } => {
1526                write!(f, "\"{name}\" is a {actual_type} not a {expected_type}")
1527            }
1528            Self::IdExhaustion => write!(f, "id counter overflows i64"),
1529            Self::OidExhaustion => write!(f, "oid counter overflows u32"),
1530            Self::TimelineAlreadyExists(name) => write!(f, "timeline '{name}' already exists"),
1531            Self::IdAllocatorAlreadyExists(name) => {
1532                write!(f, "ID allocator '{name}' already exists")
1533            }
1534            Self::ConfigAlreadyExists(key) => write!(f, "config '{key}' already exists"),
1535            Self::FailedBuiltinSchemaMigration(objects) => {
1536                write!(f, "failed to migrate schema of builtin objects: {objects}")
1537            }
1538            Self::StorageCollectionMetadataAlreadyExists(key) => {
1539                write!(f, "storage metadata for '{key}' already exists")
1540            }
1541        }
1542    }
1543}
1544
1545impl CatalogError {
1546    /// Returns any applicable hints for [`CatalogError`].
1547    pub fn hint(&self) -> Option<String> {
1548        match self {
1549            CatalogError::UnknownFunction { alternative, .. } => {
1550                match alternative {
1551                    None => Some("No function matches the given name and argument types. You might need to add explicit type casts.".into()),
1552                    Some(alt) => Some(format!("Try using {alt}")),
1553                }
1554            }
1555            _ => None,
1556        }
1557    }
1558}
1559
1560impl Error for CatalogError {}
1561
1562// Enum variant docs would be useless here.
1563#[allow(missing_docs)]
1564#[derive(
1565    Debug,
1566    Clone,
1567    PartialOrd,
1568    Ord,
1569    PartialEq,
1570    Eq,
1571    Hash,
1572    Copy,
1573    Deserialize,
1574    Serialize
1575)]
1576/// The types of objects stored in the catalog.
1577pub enum ObjectType {
1578    Table,
1579    View,
1580    MaterializedView,
1581    Source,
1582    Sink,
1583    MetricSink,
1584    Index,
1585    Type,
1586    Role,
1587    Cluster,
1588    ClusterReplica,
1589    Secret,
1590    Connection,
1591    Database,
1592    Schema,
1593    Func,
1594    NetworkPolicy,
1595}
1596
1597impl ObjectType {
1598    /// Reports if the object type can be treated as a relation.
1599    pub fn is_relation(&self) -> bool {
1600        match self {
1601            ObjectType::Table
1602            | ObjectType::View
1603            | ObjectType::MaterializedView
1604            | ObjectType::Source => true,
1605            ObjectType::Sink
1606            | ObjectType::MetricSink
1607            | ObjectType::Index
1608            | ObjectType::Type
1609            | ObjectType::Secret
1610            | ObjectType::Connection
1611            | ObjectType::Func
1612            | ObjectType::Database
1613            | ObjectType::Schema
1614            | ObjectType::Cluster
1615            | ObjectType::ClusterReplica
1616            | ObjectType::Role
1617            | ObjectType::NetworkPolicy => false,
1618        }
1619    }
1620}
1621
1622impl From<mz_sql_parser::ast::ObjectType> for ObjectType {
1623    fn from(value: mz_sql_parser::ast::ObjectType) -> Self {
1624        match value {
1625            mz_sql_parser::ast::ObjectType::Table => ObjectType::Table,
1626            mz_sql_parser::ast::ObjectType::View => ObjectType::View,
1627            mz_sql_parser::ast::ObjectType::MaterializedView => ObjectType::MaterializedView,
1628            mz_sql_parser::ast::ObjectType::Source => ObjectType::Source,
1629            mz_sql_parser::ast::ObjectType::Subsource => ObjectType::Source,
1630            mz_sql_parser::ast::ObjectType::Sink => ObjectType::Sink,
1631            mz_sql_parser::ast::ObjectType::MetricSink => ObjectType::MetricSink,
1632            mz_sql_parser::ast::ObjectType::Index => ObjectType::Index,
1633            mz_sql_parser::ast::ObjectType::Type => ObjectType::Type,
1634            mz_sql_parser::ast::ObjectType::Role => ObjectType::Role,
1635            mz_sql_parser::ast::ObjectType::Cluster => ObjectType::Cluster,
1636            mz_sql_parser::ast::ObjectType::ClusterReplica => ObjectType::ClusterReplica,
1637            mz_sql_parser::ast::ObjectType::Secret => ObjectType::Secret,
1638            mz_sql_parser::ast::ObjectType::Connection => ObjectType::Connection,
1639            mz_sql_parser::ast::ObjectType::Database => ObjectType::Database,
1640            mz_sql_parser::ast::ObjectType::Schema => ObjectType::Schema,
1641            mz_sql_parser::ast::ObjectType::Func => ObjectType::Func,
1642            mz_sql_parser::ast::ObjectType::NetworkPolicy => ObjectType::NetworkPolicy,
1643        }
1644    }
1645}
1646
1647impl From<CommentObjectId> for ObjectType {
1648    fn from(value: CommentObjectId) -> ObjectType {
1649        match value {
1650            CommentObjectId::Table(_) => ObjectType::Table,
1651            CommentObjectId::View(_) => ObjectType::View,
1652            CommentObjectId::MaterializedView(_) => ObjectType::MaterializedView,
1653            CommentObjectId::Source(_) => ObjectType::Source,
1654            CommentObjectId::Sink(_) => ObjectType::Sink,
1655            CommentObjectId::MetricSink(_) => ObjectType::MetricSink,
1656            CommentObjectId::Index(_) => ObjectType::Index,
1657            CommentObjectId::Func(_) => ObjectType::Func,
1658            CommentObjectId::Connection(_) => ObjectType::Connection,
1659            CommentObjectId::Type(_) => ObjectType::Type,
1660            CommentObjectId::Secret(_) => ObjectType::Secret,
1661            CommentObjectId::Role(_) => ObjectType::Role,
1662            CommentObjectId::Database(_) => ObjectType::Database,
1663            CommentObjectId::Schema(_) => ObjectType::Schema,
1664            CommentObjectId::Cluster(_) => ObjectType::Cluster,
1665            CommentObjectId::ClusterReplica(_) => ObjectType::ClusterReplica,
1666            CommentObjectId::NetworkPolicy(_) => ObjectType::NetworkPolicy,
1667        }
1668    }
1669}
1670
1671impl Display for ObjectType {
1672    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1673        f.write_str(match self {
1674            ObjectType::Table => "TABLE",
1675            ObjectType::View => "VIEW",
1676            ObjectType::MaterializedView => "MATERIALIZED VIEW",
1677            ObjectType::Source => "SOURCE",
1678            ObjectType::Sink => "SINK",
1679            ObjectType::MetricSink => "METRIC SINK",
1680            ObjectType::Index => "INDEX",
1681            ObjectType::Type => "TYPE",
1682            ObjectType::Role => "ROLE",
1683            ObjectType::Cluster => "CLUSTER",
1684            ObjectType::ClusterReplica => "CLUSTER REPLICA",
1685            ObjectType::Secret => "SECRET",
1686            ObjectType::Connection => "CONNECTION",
1687            ObjectType::Database => "DATABASE",
1688            ObjectType::Schema => "SCHEMA",
1689            ObjectType::Func => "FUNCTION",
1690            ObjectType::NetworkPolicy => "NETWORK POLICY",
1691        })
1692    }
1693}
1694
1695#[derive(
1696    Debug,
1697    Clone,
1698    PartialOrd,
1699    Ord,
1700    PartialEq,
1701    Eq,
1702    Hash,
1703    Copy,
1704    Deserialize,
1705    Serialize
1706)]
1707/// The types of objects in the system.
1708pub enum SystemObjectType {
1709    /// Catalog object type.
1710    Object(ObjectType),
1711    /// Entire system.
1712    System,
1713}
1714
1715impl SystemObjectType {
1716    /// Reports if the object type can be treated as a relation.
1717    pub fn is_relation(&self) -> bool {
1718        match self {
1719            SystemObjectType::Object(object_type) => object_type.is_relation(),
1720            SystemObjectType::System => false,
1721        }
1722    }
1723}
1724
1725impl Display for SystemObjectType {
1726    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1727        match self {
1728            SystemObjectType::Object(object_type) => std::fmt::Display::fmt(&object_type, f),
1729            SystemObjectType::System => f.write_str("SYSTEM"),
1730        }
1731    }
1732}
1733
1734/// Enum used to format object names in error messages.
1735#[derive(Debug, Clone, PartialEq, Eq)]
1736pub enum ErrorMessageObjectDescription {
1737    /// The name of a specific object.
1738    Object {
1739        /// Type of object.
1740        object_type: ObjectType,
1741        /// Name of object.
1742        object_name: Option<String>,
1743    },
1744    /// The name of the entire system.
1745    System,
1746}
1747
1748impl ErrorMessageObjectDescription {
1749    /// Generate a new [`ErrorMessageObjectDescription`] from an [`ObjectId`].
1750    pub fn from_id(
1751        object_id: &ObjectId,
1752        catalog: &dyn SessionCatalog,
1753    ) -> ErrorMessageObjectDescription {
1754        let object_name = match object_id {
1755            ObjectId::Cluster(cluster_id) => catalog.get_cluster(*cluster_id).name().to_string(),
1756            ObjectId::ClusterReplica((cluster_id, replica_id)) => catalog
1757                .get_cluster_replica(*cluster_id, *replica_id)
1758                .name()
1759                .to_string(),
1760            ObjectId::Database(database_id) => catalog.get_database(database_id).name().to_string(),
1761            ObjectId::Schema((database_spec, schema_spec)) => {
1762                let name = catalog.get_schema(database_spec, schema_spec).name();
1763                catalog.resolve_full_schema_name(name).to_string()
1764            }
1765            ObjectId::Role(role_id) => catalog.get_role(role_id).name().to_string(),
1766            ObjectId::Item(id) => {
1767                let name = catalog.get_item(id).name();
1768                catalog.resolve_full_name(name).to_string()
1769            }
1770            ObjectId::NetworkPolicy(network_policy_id) => catalog
1771                .get_network_policy(network_policy_id)
1772                .name()
1773                .to_string(),
1774        };
1775        ErrorMessageObjectDescription::Object {
1776            object_type: catalog.get_object_type(object_id),
1777            object_name: Some(object_name),
1778        }
1779    }
1780
1781    /// Generate a new [`ErrorMessageObjectDescription`] from a [`SystemObjectId`].
1782    pub fn from_sys_id(
1783        object_id: &SystemObjectId,
1784        catalog: &dyn SessionCatalog,
1785    ) -> ErrorMessageObjectDescription {
1786        match object_id {
1787            SystemObjectId::Object(object_id) => {
1788                ErrorMessageObjectDescription::from_id(object_id, catalog)
1789            }
1790            SystemObjectId::System => ErrorMessageObjectDescription::System,
1791        }
1792    }
1793
1794    /// Generate a new [`ErrorMessageObjectDescription`] from a [`SystemObjectType`].
1795    pub fn from_object_type(object_type: SystemObjectType) -> ErrorMessageObjectDescription {
1796        match object_type {
1797            SystemObjectType::Object(object_type) => ErrorMessageObjectDescription::Object {
1798                object_type,
1799                object_name: None,
1800            },
1801            SystemObjectType::System => ErrorMessageObjectDescription::System,
1802        }
1803    }
1804}
1805
1806impl Display for ErrorMessageObjectDescription {
1807    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1808        match self {
1809            ErrorMessageObjectDescription::Object {
1810                object_type,
1811                object_name,
1812            } => {
1813                let object_name = object_name
1814                    .as_ref()
1815                    .map(|object_name| format!(" {}", object_name.quoted()))
1816                    .unwrap_or_else(|| "".to_string());
1817                write!(f, "{object_type}{object_name}")
1818            }
1819            ErrorMessageObjectDescription::System => f.write_str("SYSTEM"),
1820        }
1821    }
1822}
1823
1824#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
1825// These attributes are needed because the key of a map must be a string. We also
1826// get the added benefit of flattening this struct in it's serialized form.
1827#[serde(into = "BTreeMap<String, RoleId>")]
1828#[serde(try_from = "BTreeMap<String, RoleId>")]
1829/// Represents the grantee and a grantor of a role membership.
1830pub struct RoleMembership {
1831    /// Key is the role that some role is a member of, value is the grantor role ID.
1832    // TODO(jkosh44) This structure does not allow a role to have multiple of the same membership
1833    // from different grantors. This isn't a problem now since we don't implement ADMIN OPTION, but
1834    // we should figure this out before implementing ADMIN OPTION. It will likely require a messy
1835    // migration.
1836    pub map: BTreeMap<RoleId, RoleId>,
1837}
1838
1839impl RoleMembership {
1840    /// Creates a new [`RoleMembership`].
1841    pub fn new() -> RoleMembership {
1842        RoleMembership {
1843            map: BTreeMap::new(),
1844        }
1845    }
1846}
1847
1848impl From<RoleMembership> for BTreeMap<String, RoleId> {
1849    fn from(value: RoleMembership) -> Self {
1850        value
1851            .map
1852            .into_iter()
1853            .map(|(k, v)| (k.to_string(), v))
1854            .collect()
1855    }
1856}
1857
1858impl TryFrom<BTreeMap<String, RoleId>> for RoleMembership {
1859    type Error = anyhow::Error;
1860
1861    fn try_from(value: BTreeMap<String, RoleId>) -> Result<Self, Self::Error> {
1862        Ok(RoleMembership {
1863            map: value
1864                .into_iter()
1865                .map(|(k, v)| Ok((RoleId::from_str(&k)?, v)))
1866                .collect::<Result<_, anyhow::Error>>()?,
1867        })
1868    }
1869}
1870
1871/// Specification for objects that will be affected by a default privilege.
1872#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1873pub struct DefaultPrivilegeObject {
1874    /// The role id that created the object.
1875    pub role_id: RoleId,
1876    /// The database that the object is created in if Some, otherwise all databases.
1877    pub database_id: Option<DatabaseId>,
1878    /// The schema that the object is created in if Some, otherwise all databases.
1879    pub schema_id: Option<SchemaId>,
1880    /// The type of object.
1881    pub object_type: ObjectType,
1882}
1883
1884impl DefaultPrivilegeObject {
1885    /// Creates a new [`DefaultPrivilegeObject`].
1886    pub fn new(
1887        role_id: RoleId,
1888        database_id: Option<DatabaseId>,
1889        schema_id: Option<SchemaId>,
1890        object_type: ObjectType,
1891    ) -> DefaultPrivilegeObject {
1892        DefaultPrivilegeObject {
1893            role_id,
1894            database_id,
1895            schema_id,
1896            object_type,
1897        }
1898    }
1899}
1900
1901impl std::fmt::Display for DefaultPrivilegeObject {
1902    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1903        // TODO: Don't just wrap Debug.
1904        write!(f, "{self:?}")
1905    }
1906}
1907
1908/// Specification for the privileges that will be granted from default privileges.
1909#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1910pub struct DefaultPrivilegeAclItem {
1911    /// The role that will receive the privileges.
1912    pub grantee: RoleId,
1913    /// The specific privileges granted.
1914    pub acl_mode: AclMode,
1915}
1916
1917impl DefaultPrivilegeAclItem {
1918    /// Creates a new [`DefaultPrivilegeAclItem`].
1919    pub fn new(grantee: RoleId, acl_mode: AclMode) -> DefaultPrivilegeAclItem {
1920        DefaultPrivilegeAclItem { grantee, acl_mode }
1921    }
1922
1923    /// Converts this [`DefaultPrivilegeAclItem`] into an [`MzAclItem`].
1924    pub fn mz_acl_item(self, grantor: RoleId) -> MzAclItem {
1925        MzAclItem {
1926            grantee: self.grantee,
1927            grantor,
1928            acl_mode: self.acl_mode,
1929        }
1930    }
1931}
1932
1933#[cfg(test)]
1934mod tests {
1935    use super::{CloudProvider, EnvironmentId, InvalidEnvironmentIdError};
1936
1937    #[mz_ore::test]
1938    fn test_environment_id() {
1939        for (input, expected) in [
1940            (
1941                "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1942                Ok(EnvironmentId {
1943                    cloud_provider: CloudProvider::Local,
1944                    cloud_provider_region: "az1".into(),
1945                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1946                    ordinal: 452,
1947                }),
1948            ),
1949            (
1950                "aws-us-east-1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1951                Ok(EnvironmentId {
1952                    cloud_provider: CloudProvider::Aws,
1953                    cloud_provider_region: "us-east-1".into(),
1954                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1955                    ordinal: 0,
1956                }),
1957            ),
1958            (
1959                "gcp-us-central1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1960                Ok(EnvironmentId {
1961                    cloud_provider: CloudProvider::Gcp,
1962                    cloud_provider_region: "us-central1".into(),
1963                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1964                    ordinal: 0,
1965                }),
1966            ),
1967            (
1968                "azure-australiaeast-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1969                Ok(EnvironmentId {
1970                    cloud_provider: CloudProvider::Azure,
1971                    cloud_provider_region: "australiaeast".into(),
1972                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1973                    ordinal: 0,
1974                }),
1975            ),
1976            (
1977                "generic-moon-station-11-darkside-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1978                Ok(EnvironmentId {
1979                    cloud_provider: CloudProvider::Generic,
1980                    cloud_provider_region: "moon-station-11-darkside".into(),
1981                    organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1982                    ordinal: 0,
1983                }),
1984            ),
1985            ("", Err(InvalidEnvironmentIdError)),
1986            (
1987                "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-123456789",
1988                Err(InvalidEnvironmentIdError),
1989            ),
1990            (
1991                "local-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1992                Err(InvalidEnvironmentIdError),
1993            ),
1994            (
1995                "local-az1-1497a3b7-a455-4fc48752-b44a94b5f90a-452",
1996                Err(InvalidEnvironmentIdError),
1997            ),
1998        ] {
1999            let actual = input.parse();
2000            assert_eq!(expected, actual, "input = {}", input);
2001            if let Ok(actual) = actual {
2002                assert_eq!(input, actual.to_string(), "input = {}", input);
2003            }
2004        }
2005    }
2006}