1#![warn(missing_docs)]
11
12use std::borrow::Cow;
15use std::collections::{BTreeMap, BTreeSet};
16use std::error::Error;
17use std::fmt;
18use std::fmt::{Debug, Display, Formatter};
19use std::str::FromStr;
20use std::sync::LazyLock;
21use std::time::{Duration, Instant};
22
23use chrono::{DateTime, Utc};
24use mz_auth::password::Password;
25use mz_build_info::BuildInfo;
26use mz_cloud_provider::{CloudProvider, InvalidCloudProviderError};
27use mz_controller_types::{ClusterId, ReplicaId};
28use mz_expr::MirScalarExpr;
29use mz_ore::now::{EpochMillis, NowFn};
30use mz_ore::str::StrExt;
31use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap};
32use mz_repr::explain::ExprHumanizer;
33use mz_repr::network_policy_id::NetworkPolicyId;
34use mz_repr::role_id::RoleId;
35use mz_repr::{
36 CatalogItemId, ColumnName, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector,
37};
38use mz_sql_parser::ast::{Expr, QualifiedReplica, UnresolvedItemName};
39use mz_storage_types::connections::inline::{ConnectionResolver, ReferencedConnection};
40use mz_storage_types::connections::{Connection, ConnectionContext};
41use mz_storage_types::sources::{SourceDesc, SourceExportDataConfig, SourceExportDetails};
42use proptest_derive::Arbitrary;
43use regex::Regex;
44use serde::{Deserialize, Serialize};
45use uuid::Uuid;
46
47use crate::func::Func;
48use crate::names::{
49 Aug, CommentObjectId, DatabaseId, FullItemName, FullSchemaName, ObjectId, PartialItemName,
50 QualifiedItemName, QualifiedSchemaName, ResolvedDatabaseSpecifier, ResolvedIds, SchemaId,
51 SchemaSpecifier, SystemObjectId,
52};
53use crate::plan::statement::StatementDesc;
54use crate::plan::statement::ddl::PlannedRoleAttributes;
55use crate::plan::{ClusterSchedule, CreateClusterPlan, PlanError, PlanNotice, query};
56use crate::session::vars::{OwnedVarInput, SystemVars};
57
58pub trait SessionCatalog: fmt::Debug + ExprHumanizer + Send + Sync + ConnectionResolver {
87 fn active_role_id(&self) -> &RoleId;
89
90 fn active_database_name(&self) -> Option<&str> {
92 self.active_database()
93 .map(|id| self.get_database(id))
94 .map(|db| db.name())
95 }
96
97 fn active_database(&self) -> Option<&DatabaseId>;
99
100 fn active_cluster(&self) -> &str;
102
103 fn search_path(&self) -> &[(ResolvedDatabaseSpecifier, SchemaSpecifier)];
105
106 fn get_prepared_statement_desc(&self, name: &str) -> Option<&StatementDesc>;
109
110 fn get_portal_desc_unverified(&self, portal_name: &str) -> Option<&StatementDesc>;
114
115 fn resolve_database(&self, database_name: &str) -> Result<&dyn CatalogDatabase, CatalogError>;
120
121 fn get_database(&self, id: &DatabaseId) -> &dyn CatalogDatabase;
125
126 fn get_databases(&self) -> Vec<&dyn CatalogDatabase>;
128
129 fn resolve_schema(
134 &self,
135 database_name: Option<&str>,
136 schema_name: &str,
137 ) -> Result<&dyn CatalogSchema, CatalogError>;
138
139 fn resolve_schema_in_database(
144 &self,
145 database_spec: &ResolvedDatabaseSpecifier,
146 schema_name: &str,
147 ) -> Result<&dyn CatalogSchema, CatalogError>;
148
149 fn get_schema(
153 &self,
154 database_spec: &ResolvedDatabaseSpecifier,
155 schema_spec: &SchemaSpecifier,
156 ) -> &dyn CatalogSchema;
157
158 fn get_schemas(&self) -> Vec<&dyn CatalogSchema>;
160
161 fn get_mz_internal_schema_id(&self) -> SchemaId;
163
164 fn get_mz_unsafe_schema_id(&self) -> SchemaId;
166
167 fn is_system_schema_specifier(&self, schema: SchemaSpecifier) -> bool;
169
170 fn resolve_role(&self, role_name: &str) -> Result<&dyn CatalogRole, CatalogError>;
172
173 fn resolve_network_policy(
175 &self,
176 network_policy_name: &str,
177 ) -> Result<&dyn CatalogNetworkPolicy, CatalogError>;
178
179 fn try_get_role(&self, id: &RoleId) -> Option<&dyn CatalogRole>;
181
182 fn get_role(&self, id: &RoleId) -> &dyn CatalogRole;
186
187 fn get_roles(&self) -> Vec<&dyn CatalogRole>;
189
190 fn mz_system_role_id(&self) -> RoleId;
192
193 fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId>;
195
196 fn get_network_policy(&self, id: &NetworkPolicyId) -> &dyn CatalogNetworkPolicy;
201
202 fn get_network_policies(&self) -> Vec<&dyn CatalogNetworkPolicy>;
204
205 fn resolve_cluster<'a, 'b>(
208 &'a self,
209 cluster_name: Option<&'b str>,
210 ) -> Result<&'a dyn CatalogCluster<'a>, CatalogError>;
211
212 fn resolve_cluster_replica<'a, 'b>(
214 &'a self,
215 cluster_replica_name: &'b QualifiedReplica,
216 ) -> Result<&'a dyn CatalogClusterReplica<'a>, CatalogError>;
217
218 fn resolve_item(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
233
234 fn resolve_function(
237 &self,
238 item_name: &PartialItemName,
239 ) -> Result<&dyn CatalogItem, CatalogError>;
240
241 fn resolve_type(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
244
245 fn resolve_item_or_type(
247 &self,
248 name: &PartialItemName,
249 ) -> Result<&dyn CatalogItem, CatalogError> {
250 if let Ok(ty) = self.resolve_type(name) {
251 return Ok(ty);
252 }
253 self.resolve_item(name)
254 }
255
256 fn get_system_type(&self, name: &str) -> &dyn CatalogItem;
262
263 fn try_get_item(&self, id: &CatalogItemId) -> Option<&dyn CatalogItem>;
265
266 fn try_get_item_by_global_id<'a>(
271 &'a self,
272 id: &GlobalId,
273 ) -> Option<Box<dyn CatalogCollectionItem + 'a>>;
274
275 fn get_item(&self, id: &CatalogItemId) -> &dyn CatalogItem;
279
280 fn get_item_by_global_id<'a>(&'a self, id: &GlobalId) -> Box<dyn CatalogCollectionItem + 'a>;
286
287 fn get_items(&self) -> Vec<&dyn CatalogItem>;
289
290 fn get_item_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
292
293 fn get_type_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
295
296 fn get_cluster(&self, id: ClusterId) -> &dyn CatalogCluster<'_>;
298
299 fn get_clusters(&self) -> Vec<&dyn CatalogCluster<'_>>;
301
302 fn get_cluster_replica(
304 &self,
305 cluster_id: ClusterId,
306 replica_id: ReplicaId,
307 ) -> &dyn CatalogClusterReplica<'_>;
308
309 fn get_cluster_replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>>;
311
312 fn get_system_privileges(&self) -> &PrivilegeMap;
314
315 fn get_default_privileges(
317 &self,
318 ) -> Vec<(&DefaultPrivilegeObject, Vec<&DefaultPrivilegeAclItem>)>;
319
320 fn find_available_name(&self, name: QualifiedItemName) -> QualifiedItemName;
324
325 fn resolve_full_name(&self, name: &QualifiedItemName) -> FullItemName;
327
328 fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName;
331
332 fn resolve_item_id(&self, global_id: &GlobalId) -> CatalogItemId;
334
335 fn resolve_global_id(
337 &self,
338 item_id: &CatalogItemId,
339 version: RelationVersionSelector,
340 ) -> GlobalId;
341
342 fn config(&self) -> &CatalogConfig;
344
345 fn now(&self) -> EpochMillis;
349
350 fn aws_privatelink_availability_zones(&self) -> Option<BTreeSet<String>>;
352
353 fn system_vars(&self) -> &SystemVars;
355
356 fn system_vars_mut(&mut self) -> &mut SystemVars;
363
364 fn get_owner_id(&self, id: &ObjectId) -> Option<RoleId>;
366
367 fn get_privileges(&self, id: &SystemObjectId) -> Option<&PrivilegeMap>;
369
370 fn object_dependents(&self, ids: &Vec<ObjectId>) -> Vec<ObjectId>;
376
377 fn item_dependents(&self, id: CatalogItemId) -> Vec<ObjectId>;
383
384 fn all_object_privileges(&self, object_type: SystemObjectType) -> AclMode;
386
387 fn get_object_type(&self, object_id: &ObjectId) -> ObjectType;
389
390 fn get_system_object_type(&self, id: &SystemObjectId) -> SystemObjectType;
392
393 fn minimal_qualification(&self, qualified_name: &QualifiedItemName) -> PartialItemName;
396
397 fn add_notice(&self, notice: PlanNotice);
400
401 fn get_item_comments(&self, id: &CatalogItemId) -> Option<&BTreeMap<Option<usize>, String>>;
403
404 fn is_cluster_size_cc(&self, size: &str) -> bool;
407}
408
409#[derive(Debug, Clone)]
411pub struct CatalogConfig {
412 pub start_time: DateTime<Utc>,
414 pub start_instant: Instant,
416 pub nonce: u64,
421 pub environment_id: EnvironmentId,
423 pub session_id: Uuid,
425 pub build_info: &'static BuildInfo,
427 pub timestamp_interval: Duration,
429 pub now: NowFn,
432 pub connection_context: ConnectionContext,
434 pub builtins_cfg: BuiltinsConfig,
436 pub helm_chart_version: Option<String>,
438}
439
440pub trait CatalogDatabase {
442 fn name(&self) -> &str;
444
445 fn id(&self) -> DatabaseId;
447
448 fn has_schemas(&self) -> bool;
450
451 fn schema_ids(&self) -> &BTreeMap<String, SchemaId>;
454
455 fn schemas(&self) -> Vec<&dyn CatalogSchema>;
457
458 fn owner_id(&self) -> RoleId;
460
461 fn privileges(&self) -> &PrivilegeMap;
463}
464
465pub trait CatalogSchema {
467 fn database(&self) -> &ResolvedDatabaseSpecifier;
469
470 fn name(&self) -> &QualifiedSchemaName;
472
473 fn id(&self) -> &SchemaSpecifier;
475
476 fn has_items(&self) -> bool;
478
479 fn item_ids(&self) -> Box<dyn Iterator<Item = CatalogItemId> + '_>;
481
482 fn owner_id(&self) -> RoleId;
484
485 fn privileges(&self) -> &PrivilegeMap;
487}
488
489#[derive(Debug, Clone, Eq, PartialEq, Arbitrary)]
491pub enum PasswordAction {
492 Set(Password),
494 Clear,
496 NoChange,
498}
499
500#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Arbitrary)]
505pub struct RoleAttributesRaw {
506 pub inherit: bool,
508 pub password: Option<Password>,
510 pub superuser: Option<bool>,
512 pub login: Option<bool>,
514 _private: (),
516}
517
518#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialEq, Ord, PartialOrd, Arbitrary)]
520pub struct RoleAttributes {
521 pub inherit: bool,
523 pub superuser: Option<bool>,
525 pub login: Option<bool>,
527 _private: (),
529}
530
531impl RoleAttributesRaw {
532 pub const fn new() -> RoleAttributesRaw {
534 RoleAttributesRaw {
535 inherit: true,
536 password: None,
537 superuser: None,
538 login: None,
539 _private: (),
540 }
541 }
542
543 pub const fn with_all(mut self) -> RoleAttributesRaw {
545 self.inherit = true;
546 self.superuser = Some(true);
547 self.login = Some(true);
548 self
549 }
550}
551
552impl RoleAttributes {
553 pub const fn new() -> RoleAttributes {
555 RoleAttributes {
556 inherit: true,
557 superuser: None,
558 login: None,
559 _private: (),
560 }
561 }
562
563 pub const fn with_all(mut self) -> RoleAttributes {
565 self.inherit = true;
566 self.superuser = Some(true);
567 self.login = Some(true);
568 self
569 }
570
571 pub const fn is_inherit(&self) -> bool {
573 self.inherit
574 }
575}
576
577impl From<RoleAttributesRaw> for RoleAttributes {
578 fn from(
579 RoleAttributesRaw {
580 inherit,
581 superuser,
582 login,
583 ..
584 }: RoleAttributesRaw,
585 ) -> RoleAttributes {
586 RoleAttributes {
587 inherit,
588 superuser,
589 login,
590 _private: (),
591 }
592 }
593}
594
595impl From<RoleAttributes> for RoleAttributesRaw {
596 fn from(
597 RoleAttributes {
598 inherit,
599 superuser,
600 login,
601 ..
602 }: RoleAttributes,
603 ) -> RoleAttributesRaw {
604 RoleAttributesRaw {
605 inherit,
606 password: None,
607 superuser,
608 login,
609 _private: (),
610 }
611 }
612}
613
614impl From<PlannedRoleAttributes> for RoleAttributesRaw {
615 fn from(
616 PlannedRoleAttributes {
617 inherit,
618 password,
619 superuser,
620 login,
621 ..
622 }: PlannedRoleAttributes,
623 ) -> RoleAttributesRaw {
624 let default_attributes = RoleAttributesRaw::new();
625 RoleAttributesRaw {
626 inherit: inherit.unwrap_or(default_attributes.inherit),
627 password,
628 superuser,
629 login,
630 _private: (),
631 }
632 }
633}
634
635#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
637pub struct RoleVars {
638 pub map: BTreeMap<String, OwnedVarInput>,
640}
641
642pub trait CatalogRole {
644 fn name(&self) -> &str;
646
647 fn id(&self) -> RoleId;
649
650 fn membership(&self) -> &BTreeMap<RoleId, RoleId>;
655
656 fn attributes(&self) -> &RoleAttributes;
658
659 fn vars(&self) -> &BTreeMap<String, OwnedVarInput>;
661}
662
663pub trait CatalogNetworkPolicy {
665 fn name(&self) -> &str;
667
668 fn id(&self) -> NetworkPolicyId;
670
671 fn owner_id(&self) -> RoleId;
673
674 fn privileges(&self) -> &PrivilegeMap;
676}
677
678pub trait CatalogCluster<'a> {
680 fn name(&self) -> &str;
682
683 fn id(&self) -> ClusterId;
685
686 fn bound_objects(&self) -> &BTreeSet<CatalogItemId>;
688
689 fn replica_ids(&self) -> &BTreeMap<String, ReplicaId>;
692
693 fn replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>>;
695
696 fn replica(&self, id: ReplicaId) -> &dyn CatalogClusterReplica<'_>;
698
699 fn owner_id(&self) -> RoleId;
701
702 fn privileges(&self) -> &PrivilegeMap;
704
705 fn is_managed(&self) -> bool;
707
708 fn managed_size(&self) -> Option<&str>;
710
711 fn schedule(&self) -> Option<&ClusterSchedule>;
713
714 fn try_to_plan(&self) -> Result<CreateClusterPlan, PlanError>;
717}
718
719pub trait CatalogClusterReplica<'a>: Debug {
721 fn name(&self) -> &str;
723
724 fn cluster_id(&self) -> ClusterId;
726
727 fn replica_id(&self) -> ReplicaId;
729
730 fn owner_id(&self) -> RoleId;
732
733 fn internal(&self) -> bool;
735}
736
737pub trait CatalogItem {
742 fn name(&self) -> &QualifiedItemName;
744
745 fn id(&self) -> CatalogItemId;
747
748 fn global_ids(&self) -> Box<dyn Iterator<Item = GlobalId> + '_>;
750
751 fn oid(&self) -> u32;
753
754 fn func(&self) -> Result<&'static Func, CatalogError>;
759
760 fn source_desc(&self) -> Result<Option<&SourceDesc<ReferencedConnection>>, CatalogError>;
765
766 fn connection(&self) -> Result<Connection<ReferencedConnection>, CatalogError>;
770
771 fn item_type(&self) -> CatalogItemType;
773
774 fn create_sql(&self) -> &str;
777
778 fn references(&self) -> &ResolvedIds;
781
782 fn uses(&self) -> BTreeSet<CatalogItemId>;
785
786 fn referenced_by(&self) -> &[CatalogItemId];
788
789 fn used_by(&self) -> &[CatalogItemId];
791
792 fn subsource_details(
795 &self,
796 ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)>;
797
798 fn source_export_details(
801 &self,
802 ) -> Option<(
803 CatalogItemId,
804 &UnresolvedItemName,
805 &SourceExportDetails,
806 &SourceExportDataConfig<ReferencedConnection>,
807 )>;
808
809 fn is_progress_source(&self) -> bool;
811
812 fn progress_id(&self) -> Option<CatalogItemId>;
814
815 fn index_details(&self) -> Option<(&[MirScalarExpr], GlobalId)>;
818
819 fn writable_table_details(&self) -> Option<&[Expr<Aug>]>;
822
823 fn type_details(&self) -> Option<&CatalogTypeDetails<IdReference>>;
826
827 fn owner_id(&self) -> RoleId;
829
830 fn privileges(&self) -> &PrivilegeMap;
832
833 fn cluster_id(&self) -> Option<ClusterId>;
835
836 fn at_version(&self, version: RelationVersionSelector) -> Box<dyn CatalogCollectionItem>;
839
840 fn latest_version(&self) -> Option<RelationVersion>;
842}
843
844pub trait CatalogCollectionItem: CatalogItem + Send + Sync {
847 fn desc(&self, name: &FullItemName) -> Result<Cow<'_, RelationDesc>, CatalogError>;
852
853 fn global_id(&self) -> GlobalId;
855}
856
857#[derive(Debug, Deserialize, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
859pub enum CatalogItemType {
860 Table,
862 Source,
864 Sink,
866 View,
868 MaterializedView,
870 Index,
872 Type,
874 Func,
876 Secret,
878 Connection,
880 ContinualTask,
882}
883
884impl CatalogItemType {
885 pub fn conflicts_with_type(&self) -> bool {
904 match self {
905 CatalogItemType::Table => true,
906 CatalogItemType::Source => true,
907 CatalogItemType::View => true,
908 CatalogItemType::MaterializedView => true,
909 CatalogItemType::Index => true,
910 CatalogItemType::Type => true,
911 CatalogItemType::Sink => false,
912 CatalogItemType::Func => false,
913 CatalogItemType::Secret => false,
914 CatalogItemType::Connection => false,
915 CatalogItemType::ContinualTask => true,
916 }
917 }
918}
919
920impl fmt::Display for CatalogItemType {
921 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
922 match self {
923 CatalogItemType::Table => f.write_str("table"),
924 CatalogItemType::Source => f.write_str("source"),
925 CatalogItemType::Sink => f.write_str("sink"),
926 CatalogItemType::View => f.write_str("view"),
927 CatalogItemType::MaterializedView => f.write_str("materialized view"),
928 CatalogItemType::Index => f.write_str("index"),
929 CatalogItemType::Type => f.write_str("type"),
930 CatalogItemType::Func => f.write_str("func"),
931 CatalogItemType::Secret => f.write_str("secret"),
932 CatalogItemType::Connection => f.write_str("connection"),
933 CatalogItemType::ContinualTask => f.write_str("continual task"),
934 }
935 }
936}
937
938impl From<CatalogItemType> for ObjectType {
939 fn from(value: CatalogItemType) -> Self {
940 match value {
941 CatalogItemType::Table => ObjectType::Table,
942 CatalogItemType::Source => ObjectType::Source,
943 CatalogItemType::Sink => ObjectType::Sink,
944 CatalogItemType::View => ObjectType::View,
945 CatalogItemType::MaterializedView => ObjectType::MaterializedView,
946 CatalogItemType::Index => ObjectType::Index,
947 CatalogItemType::Type => ObjectType::Type,
948 CatalogItemType::Func => ObjectType::Func,
949 CatalogItemType::Secret => ObjectType::Secret,
950 CatalogItemType::Connection => ObjectType::Connection,
951 CatalogItemType::ContinualTask => ObjectType::ContinualTask,
952 }
953 }
954}
955
956impl From<CatalogItemType> for mz_audit_log::ObjectType {
957 fn from(value: CatalogItemType) -> Self {
958 match value {
959 CatalogItemType::Table => mz_audit_log::ObjectType::Table,
960 CatalogItemType::Source => mz_audit_log::ObjectType::Source,
961 CatalogItemType::View => mz_audit_log::ObjectType::View,
962 CatalogItemType::MaterializedView => mz_audit_log::ObjectType::MaterializedView,
963 CatalogItemType::Index => mz_audit_log::ObjectType::Index,
964 CatalogItemType::Type => mz_audit_log::ObjectType::Type,
965 CatalogItemType::Sink => mz_audit_log::ObjectType::Sink,
966 CatalogItemType::Func => mz_audit_log::ObjectType::Func,
967 CatalogItemType::Secret => mz_audit_log::ObjectType::Secret,
968 CatalogItemType::Connection => mz_audit_log::ObjectType::Connection,
969 CatalogItemType::ContinualTask => mz_audit_log::ObjectType::ContinualTask,
970 }
971 }
972}
973
974#[derive(Clone, Debug, Eq, PartialEq)]
976pub struct CatalogTypeDetails<T: TypeReference> {
977 pub array_id: Option<CatalogItemId>,
979 pub typ: CatalogType<T>,
981 pub pg_metadata: Option<CatalogTypePgMetadata>,
983}
984
985#[derive(Clone, Debug, Eq, PartialEq)]
987pub struct CatalogTypePgMetadata {
988 pub typinput_oid: u32,
990 pub typreceive_oid: u32,
992}
993
994pub trait TypeReference {
996 type Reference: Clone + Debug + Eq + PartialEq;
998}
999
1000#[derive(Clone, Debug, Eq, PartialEq)]
1002pub struct NameReference;
1003
1004impl TypeReference for NameReference {
1005 type Reference = &'static str;
1006}
1007
1008#[derive(Clone, Debug, Eq, PartialEq)]
1010pub struct IdReference;
1011
1012impl TypeReference for IdReference {
1013 type Reference = CatalogItemId;
1014}
1015
1016#[allow(missing_docs)]
1022#[derive(Clone, Debug, Eq, PartialEq)]
1023pub enum CatalogType<T: TypeReference> {
1024 AclItem,
1025 Array {
1026 element_reference: T::Reference,
1027 },
1028 Bool,
1029 Bytes,
1030 Char,
1031 Date,
1032 Float32,
1033 Float64,
1034 Int16,
1035 Int32,
1036 Int64,
1037 UInt16,
1038 UInt32,
1039 UInt64,
1040 MzTimestamp,
1041 Interval,
1042 Jsonb,
1043 List {
1044 element_reference: T::Reference,
1045 element_modifiers: Vec<i64>,
1046 },
1047 Map {
1048 key_reference: T::Reference,
1049 key_modifiers: Vec<i64>,
1050 value_reference: T::Reference,
1051 value_modifiers: Vec<i64>,
1052 },
1053 Numeric,
1054 Oid,
1055 PgLegacyChar,
1056 PgLegacyName,
1057 Pseudo,
1058 Range {
1059 element_reference: T::Reference,
1060 },
1061 Record {
1062 fields: Vec<CatalogRecordField<T>>,
1063 },
1064 RegClass,
1065 RegProc,
1066 RegType,
1067 String,
1068 Time,
1069 Timestamp,
1070 TimestampTz,
1071 Uuid,
1072 VarChar,
1073 Int2Vector,
1074 MzAclItem,
1075}
1076
1077impl CatalogType<IdReference> {
1078 pub fn desc(&self, catalog: &dyn SessionCatalog) -> Result<Option<RelationDesc>, PlanError> {
1081 match &self {
1082 CatalogType::Record { fields } => {
1083 let mut desc = RelationDesc::builder();
1084 for f in fields {
1085 let name = f.name.clone();
1086 let ty = query::scalar_type_from_catalog(
1087 catalog,
1088 f.type_reference,
1089 &f.type_modifiers,
1090 )?;
1091 let ty = ty.nullable(true);
1094 desc = desc.with_column(name, ty);
1095 }
1096 Ok(Some(desc.finish()))
1097 }
1098 _ => Ok(None),
1099 }
1100 }
1101}
1102
1103#[derive(Clone, Debug, Eq, PartialEq)]
1105pub struct CatalogRecordField<T: TypeReference> {
1106 pub name: ColumnName,
1108 pub type_reference: T::Reference,
1110 pub type_modifiers: Vec<i64>,
1112}
1113
1114#[derive(Clone, Debug, Eq, PartialEq)]
1115pub enum TypeCategory {
1123 Array,
1125 BitString,
1127 Boolean,
1129 Composite,
1131 DateTime,
1133 Enum,
1135 Geometric,
1137 List,
1139 NetworkAddress,
1141 Numeric,
1143 Pseudo,
1145 Range,
1147 String,
1149 Timespan,
1151 UserDefined,
1153 Unknown,
1155}
1156
1157impl fmt::Display for TypeCategory {
1158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1159 f.write_str(match self {
1160 TypeCategory::Array => "array",
1161 TypeCategory::BitString => "bit-string",
1162 TypeCategory::Boolean => "boolean",
1163 TypeCategory::Composite => "composite",
1164 TypeCategory::DateTime => "date-time",
1165 TypeCategory::Enum => "enum",
1166 TypeCategory::Geometric => "geometric",
1167 TypeCategory::List => "list",
1168 TypeCategory::NetworkAddress => "network-address",
1169 TypeCategory::Numeric => "numeric",
1170 TypeCategory::Pseudo => "pseudo",
1171 TypeCategory::Range => "range",
1172 TypeCategory::String => "string",
1173 TypeCategory::Timespan => "timespan",
1174 TypeCategory::UserDefined => "user-defined",
1175 TypeCategory::Unknown => "unknown",
1176 })
1177 }
1178}
1179
1180#[derive(Debug, Clone, PartialEq)]
1204pub struct EnvironmentId {
1205 cloud_provider: CloudProvider,
1206 cloud_provider_region: String,
1207 organization_id: Uuid,
1208 ordinal: u64,
1209}
1210
1211impl EnvironmentId {
1212 pub fn for_tests() -> EnvironmentId {
1214 EnvironmentId {
1215 cloud_provider: CloudProvider::Local,
1216 cloud_provider_region: "az1".into(),
1217 organization_id: Uuid::new_v4(),
1218 ordinal: 0,
1219 }
1220 }
1221
1222 pub fn cloud_provider(&self) -> &CloudProvider {
1224 &self.cloud_provider
1225 }
1226
1227 pub fn cloud_provider_region(&self) -> &str {
1229 &self.cloud_provider_region
1230 }
1231
1232 pub fn region(&self) -> String {
1237 format!("{}/{}", self.cloud_provider, self.cloud_provider_region)
1238 }
1239
1240 pub fn organization_id(&self) -> Uuid {
1242 self.organization_id
1243 }
1244
1245 pub fn ordinal(&self) -> u64 {
1247 self.ordinal
1248 }
1249}
1250
1251impl fmt::Display for EnvironmentId {
1257 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1258 write!(
1259 f,
1260 "{}-{}-{}-{}",
1261 self.cloud_provider, self.cloud_provider_region, self.organization_id, self.ordinal
1262 )
1263 }
1264}
1265
1266impl FromStr for EnvironmentId {
1267 type Err = InvalidEnvironmentIdError;
1268
1269 fn from_str(s: &str) -> Result<EnvironmentId, InvalidEnvironmentIdError> {
1270 static MATCHER: LazyLock<Regex> = LazyLock::new(|| {
1271 Regex::new(
1272 "^(?P<cloud_provider>[[:alnum:]]+)-\
1273 (?P<cloud_provider_region>[[:alnum:]\\-]+)-\
1274 (?P<organization_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-\
1275 (?P<ordinal>\\d{1,8})$"
1276 ).unwrap()
1277 });
1278 let captures = MATCHER.captures(s).ok_or(InvalidEnvironmentIdError)?;
1279 Ok(EnvironmentId {
1280 cloud_provider: CloudProvider::from_str(&captures["cloud_provider"])?,
1281 cloud_provider_region: captures["cloud_provider_region"].into(),
1282 organization_id: captures["organization_id"]
1283 .parse()
1284 .map_err(|_| InvalidEnvironmentIdError)?,
1285 ordinal: captures["ordinal"]
1286 .parse()
1287 .map_err(|_| InvalidEnvironmentIdError)?,
1288 })
1289 }
1290}
1291
1292#[derive(Debug, Clone, PartialEq)]
1294pub struct InvalidEnvironmentIdError;
1295
1296impl fmt::Display for InvalidEnvironmentIdError {
1297 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1298 f.write_str("invalid environment ID")
1299 }
1300}
1301
1302impl Error for InvalidEnvironmentIdError {}
1303
1304impl From<InvalidCloudProviderError> for InvalidEnvironmentIdError {
1305 fn from(_: InvalidCloudProviderError) -> Self {
1306 InvalidEnvironmentIdError
1307 }
1308}
1309
1310#[derive(Clone, Debug, Eq, PartialEq)]
1312pub enum CatalogError {
1313 UnknownDatabase(String),
1315 DatabaseAlreadyExists(String),
1317 UnknownSchema(String),
1319 SchemaAlreadyExists(String),
1321 UnknownRole(String),
1323 RoleAlreadyExists(String),
1325 NetworkPolicyAlreadyExists(String),
1327 UnknownCluster(String),
1329 UnexpectedBuiltinCluster(String),
1331 UnexpectedBuiltinClusterType(String),
1333 ClusterAlreadyExists(String),
1335 UnknownClusterReplica(String),
1337 UnknownClusterReplicaSize(String),
1339 DuplicateReplica(String, String),
1341 UnknownItem(String),
1343 ItemAlreadyExists(CatalogItemId, String),
1345 UnknownFunction {
1347 name: String,
1349 alternative: Option<String>,
1351 },
1352 UnknownType {
1354 name: String,
1356 },
1357 UnknownConnection(String),
1359 UnknownNetworkPolicy(String),
1361 UnexpectedType {
1363 name: String,
1365 actual_type: CatalogItemType,
1367 expected_type: CatalogItemType,
1369 },
1370 InvalidDependency {
1372 name: String,
1374 typ: CatalogItemType,
1376 },
1377 IdExhaustion,
1379 OidExhaustion,
1381 TimelineAlreadyExists(String),
1383 IdAllocatorAlreadyExists(String),
1385 ConfigAlreadyExists(String),
1387 FailedBuiltinSchemaMigration(String),
1389 StorageCollectionMetadataAlreadyExists(GlobalId),
1391}
1392
1393impl fmt::Display for CatalogError {
1394 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1395 match self {
1396 Self::UnknownDatabase(name) => write!(f, "unknown database '{}'", name),
1397 Self::DatabaseAlreadyExists(name) => write!(f, "database '{name}' already exists"),
1398 Self::UnknownFunction { name, .. } => write!(f, "function \"{}\" does not exist", name),
1399 Self::UnknownType { name, .. } => write!(f, "type \"{}\" does not exist", name),
1400 Self::UnknownConnection(name) => write!(f, "connection \"{}\" does not exist", name),
1401 Self::UnknownSchema(name) => write!(f, "unknown schema '{}'", name),
1402 Self::SchemaAlreadyExists(name) => write!(f, "schema '{name}' already exists"),
1403 Self::UnknownRole(name) => write!(f, "unknown role '{}'", name),
1404 Self::RoleAlreadyExists(name) => write!(f, "role '{name}' already exists"),
1405 Self::NetworkPolicyAlreadyExists(name) => {
1406 write!(f, "network policy '{name}' already exists")
1407 }
1408 Self::UnknownCluster(name) => write!(f, "unknown cluster '{}'", name),
1409 Self::UnknownNetworkPolicy(name) => write!(f, "unknown network policy '{}'", name),
1410 Self::UnexpectedBuiltinCluster(name) => {
1411 write!(f, "Unexpected builtin cluster '{}'", name)
1412 }
1413 Self::UnexpectedBuiltinClusterType(name) => {
1414 write!(f, "Unexpected builtin cluster type'{}'", name)
1415 }
1416 Self::ClusterAlreadyExists(name) => write!(f, "cluster '{name}' already exists"),
1417 Self::UnknownClusterReplica(name) => {
1418 write!(f, "unknown cluster replica '{}'", name)
1419 }
1420 Self::UnknownClusterReplicaSize(name) => {
1421 write!(f, "unknown cluster replica size '{}'", name)
1422 }
1423 Self::DuplicateReplica(replica_name, cluster_name) => write!(
1424 f,
1425 "cannot create multiple replicas named '{replica_name}' on cluster '{cluster_name}'"
1426 ),
1427 Self::UnknownItem(name) => write!(f, "unknown catalog item '{}'", name),
1428 Self::ItemAlreadyExists(_gid, name) => {
1429 write!(f, "catalog item '{name}' already exists")
1430 }
1431 Self::UnexpectedType {
1432 name,
1433 actual_type,
1434 expected_type,
1435 } => {
1436 write!(f, "\"{name}\" is a {actual_type} not a {expected_type}")
1437 }
1438 Self::InvalidDependency { name, typ } => write!(
1439 f,
1440 "catalog item '{}' is {} {} and so cannot be depended upon",
1441 name,
1442 if matches!(typ, CatalogItemType::Index) {
1443 "an"
1444 } else {
1445 "a"
1446 },
1447 typ,
1448 ),
1449 Self::IdExhaustion => write!(f, "id counter overflows i64"),
1450 Self::OidExhaustion => write!(f, "oid counter overflows u32"),
1451 Self::TimelineAlreadyExists(name) => write!(f, "timeline '{name}' already exists"),
1452 Self::IdAllocatorAlreadyExists(name) => {
1453 write!(f, "ID allocator '{name}' already exists")
1454 }
1455 Self::ConfigAlreadyExists(key) => write!(f, "config '{key}' already exists"),
1456 Self::FailedBuiltinSchemaMigration(objects) => {
1457 write!(f, "failed to migrate schema of builtin objects: {objects}")
1458 }
1459 Self::StorageCollectionMetadataAlreadyExists(key) => {
1460 write!(f, "storage metadata for '{key}' already exists")
1461 }
1462 }
1463 }
1464}
1465
1466impl CatalogError {
1467 pub fn hint(&self) -> Option<String> {
1469 match self {
1470 CatalogError::UnknownFunction { alternative, .. } => {
1471 match alternative {
1472 None => Some("No function matches the given name and argument types. You might need to add explicit type casts.".into()),
1473 Some(alt) => Some(format!("Try using {alt}")),
1474 }
1475 }
1476 _ => None,
1477 }
1478 }
1479}
1480
1481impl Error for CatalogError {}
1482
1483#[allow(missing_docs)]
1485#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Deserialize, Serialize)]
1486pub enum ObjectType {
1488 Table,
1489 View,
1490 MaterializedView,
1491 Source,
1492 Sink,
1493 Index,
1494 Type,
1495 Role,
1496 Cluster,
1497 ClusterReplica,
1498 Secret,
1499 Connection,
1500 Database,
1501 Schema,
1502 Func,
1503 ContinualTask,
1504 NetworkPolicy,
1505}
1506
1507impl ObjectType {
1508 pub fn is_relation(&self) -> bool {
1510 match self {
1511 ObjectType::Table
1512 | ObjectType::View
1513 | ObjectType::MaterializedView
1514 | ObjectType::Source
1515 | ObjectType::ContinualTask => true,
1516 ObjectType::Sink
1517 | ObjectType::Index
1518 | ObjectType::Type
1519 | ObjectType::Secret
1520 | ObjectType::Connection
1521 | ObjectType::Func
1522 | ObjectType::Database
1523 | ObjectType::Schema
1524 | ObjectType::Cluster
1525 | ObjectType::ClusterReplica
1526 | ObjectType::Role
1527 | ObjectType::NetworkPolicy => false,
1528 }
1529 }
1530}
1531
1532impl From<mz_sql_parser::ast::ObjectType> for ObjectType {
1533 fn from(value: mz_sql_parser::ast::ObjectType) -> Self {
1534 match value {
1535 mz_sql_parser::ast::ObjectType::Table => ObjectType::Table,
1536 mz_sql_parser::ast::ObjectType::View => ObjectType::View,
1537 mz_sql_parser::ast::ObjectType::MaterializedView => ObjectType::MaterializedView,
1538 mz_sql_parser::ast::ObjectType::Source => ObjectType::Source,
1539 mz_sql_parser::ast::ObjectType::Subsource => ObjectType::Source,
1540 mz_sql_parser::ast::ObjectType::Sink => ObjectType::Sink,
1541 mz_sql_parser::ast::ObjectType::Index => ObjectType::Index,
1542 mz_sql_parser::ast::ObjectType::Type => ObjectType::Type,
1543 mz_sql_parser::ast::ObjectType::Role => ObjectType::Role,
1544 mz_sql_parser::ast::ObjectType::Cluster => ObjectType::Cluster,
1545 mz_sql_parser::ast::ObjectType::ClusterReplica => ObjectType::ClusterReplica,
1546 mz_sql_parser::ast::ObjectType::Secret => ObjectType::Secret,
1547 mz_sql_parser::ast::ObjectType::Connection => ObjectType::Connection,
1548 mz_sql_parser::ast::ObjectType::Database => ObjectType::Database,
1549 mz_sql_parser::ast::ObjectType::Schema => ObjectType::Schema,
1550 mz_sql_parser::ast::ObjectType::Func => ObjectType::Func,
1551 mz_sql_parser::ast::ObjectType::ContinualTask => ObjectType::ContinualTask,
1552 mz_sql_parser::ast::ObjectType::NetworkPolicy => ObjectType::NetworkPolicy,
1553 }
1554 }
1555}
1556
1557impl From<CommentObjectId> for ObjectType {
1558 fn from(value: CommentObjectId) -> ObjectType {
1559 match value {
1560 CommentObjectId::Table(_) => ObjectType::Table,
1561 CommentObjectId::View(_) => ObjectType::View,
1562 CommentObjectId::MaterializedView(_) => ObjectType::MaterializedView,
1563 CommentObjectId::Source(_) => ObjectType::Source,
1564 CommentObjectId::Sink(_) => ObjectType::Sink,
1565 CommentObjectId::Index(_) => ObjectType::Index,
1566 CommentObjectId::Func(_) => ObjectType::Func,
1567 CommentObjectId::Connection(_) => ObjectType::Connection,
1568 CommentObjectId::Type(_) => ObjectType::Type,
1569 CommentObjectId::Secret(_) => ObjectType::Secret,
1570 CommentObjectId::Role(_) => ObjectType::Role,
1571 CommentObjectId::Database(_) => ObjectType::Database,
1572 CommentObjectId::Schema(_) => ObjectType::Schema,
1573 CommentObjectId::Cluster(_) => ObjectType::Cluster,
1574 CommentObjectId::ClusterReplica(_) => ObjectType::ClusterReplica,
1575 CommentObjectId::ContinualTask(_) => ObjectType::ContinualTask,
1576 CommentObjectId::NetworkPolicy(_) => ObjectType::NetworkPolicy,
1577 }
1578 }
1579}
1580
1581impl Display for ObjectType {
1582 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1583 f.write_str(match self {
1584 ObjectType::Table => "TABLE",
1585 ObjectType::View => "VIEW",
1586 ObjectType::MaterializedView => "MATERIALIZED VIEW",
1587 ObjectType::Source => "SOURCE",
1588 ObjectType::Sink => "SINK",
1589 ObjectType::Index => "INDEX",
1590 ObjectType::Type => "TYPE",
1591 ObjectType::Role => "ROLE",
1592 ObjectType::Cluster => "CLUSTER",
1593 ObjectType::ClusterReplica => "CLUSTER REPLICA",
1594 ObjectType::Secret => "SECRET",
1595 ObjectType::Connection => "CONNECTION",
1596 ObjectType::Database => "DATABASE",
1597 ObjectType::Schema => "SCHEMA",
1598 ObjectType::Func => "FUNCTION",
1599 ObjectType::ContinualTask => "CONTINUAL TASK",
1600 ObjectType::NetworkPolicy => "NETWORK POLICY",
1601 })
1602 }
1603}
1604
1605#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Deserialize, Serialize)]
1606pub enum SystemObjectType {
1608 Object(ObjectType),
1610 System,
1612}
1613
1614impl SystemObjectType {
1615 pub fn is_relation(&self) -> bool {
1617 match self {
1618 SystemObjectType::Object(object_type) => object_type.is_relation(),
1619 SystemObjectType::System => false,
1620 }
1621 }
1622}
1623
1624impl Display for SystemObjectType {
1625 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1626 match self {
1627 SystemObjectType::Object(object_type) => std::fmt::Display::fmt(&object_type, f),
1628 SystemObjectType::System => f.write_str("SYSTEM"),
1629 }
1630 }
1631}
1632
1633#[derive(Debug, Clone, PartialEq, Eq)]
1635pub enum ErrorMessageObjectDescription {
1636 Object {
1638 object_type: ObjectType,
1640 object_name: Option<String>,
1642 },
1643 System,
1645}
1646
1647impl ErrorMessageObjectDescription {
1648 pub fn from_id(
1650 object_id: &ObjectId,
1651 catalog: &dyn SessionCatalog,
1652 ) -> ErrorMessageObjectDescription {
1653 let object_name = match object_id {
1654 ObjectId::Cluster(cluster_id) => catalog.get_cluster(*cluster_id).name().to_string(),
1655 ObjectId::ClusterReplica((cluster_id, replica_id)) => catalog
1656 .get_cluster_replica(*cluster_id, *replica_id)
1657 .name()
1658 .to_string(),
1659 ObjectId::Database(database_id) => catalog.get_database(database_id).name().to_string(),
1660 ObjectId::Schema((database_spec, schema_spec)) => {
1661 let name = catalog.get_schema(database_spec, schema_spec).name();
1662 catalog.resolve_full_schema_name(name).to_string()
1663 }
1664 ObjectId::Role(role_id) => catalog.get_role(role_id).name().to_string(),
1665 ObjectId::Item(id) => {
1666 let name = catalog.get_item(id).name();
1667 catalog.resolve_full_name(name).to_string()
1668 }
1669 ObjectId::NetworkPolicy(network_policy_id) => catalog
1670 .get_network_policy(network_policy_id)
1671 .name()
1672 .to_string(),
1673 };
1674 ErrorMessageObjectDescription::Object {
1675 object_type: catalog.get_object_type(object_id),
1676 object_name: Some(object_name),
1677 }
1678 }
1679
1680 pub fn from_sys_id(
1682 object_id: &SystemObjectId,
1683 catalog: &dyn SessionCatalog,
1684 ) -> ErrorMessageObjectDescription {
1685 match object_id {
1686 SystemObjectId::Object(object_id) => {
1687 ErrorMessageObjectDescription::from_id(object_id, catalog)
1688 }
1689 SystemObjectId::System => ErrorMessageObjectDescription::System,
1690 }
1691 }
1692
1693 pub fn from_object_type(object_type: SystemObjectType) -> ErrorMessageObjectDescription {
1695 match object_type {
1696 SystemObjectType::Object(object_type) => ErrorMessageObjectDescription::Object {
1697 object_type,
1698 object_name: None,
1699 },
1700 SystemObjectType::System => ErrorMessageObjectDescription::System,
1701 }
1702 }
1703}
1704
1705impl Display for ErrorMessageObjectDescription {
1706 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1707 match self {
1708 ErrorMessageObjectDescription::Object {
1709 object_type,
1710 object_name,
1711 } => {
1712 let object_name = object_name
1713 .as_ref()
1714 .map(|object_name| format!(" {}", object_name.quoted()))
1715 .unwrap_or_else(|| "".to_string());
1716 write!(f, "{object_type}{object_name}")
1717 }
1718 ErrorMessageObjectDescription::System => f.write_str("SYSTEM"),
1719 }
1720 }
1721}
1722
1723#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
1724#[serde(into = "BTreeMap<String, RoleId>")]
1727#[serde(try_from = "BTreeMap<String, RoleId>")]
1728pub struct RoleMembership {
1730 pub map: BTreeMap<RoleId, RoleId>,
1736}
1737
1738impl RoleMembership {
1739 pub fn new() -> RoleMembership {
1741 RoleMembership {
1742 map: BTreeMap::new(),
1743 }
1744 }
1745}
1746
1747impl From<RoleMembership> for BTreeMap<String, RoleId> {
1748 fn from(value: RoleMembership) -> Self {
1749 value
1750 .map
1751 .into_iter()
1752 .map(|(k, v)| (k.to_string(), v))
1753 .collect()
1754 }
1755}
1756
1757impl TryFrom<BTreeMap<String, RoleId>> for RoleMembership {
1758 type Error = anyhow::Error;
1759
1760 fn try_from(value: BTreeMap<String, RoleId>) -> Result<Self, Self::Error> {
1761 Ok(RoleMembership {
1762 map: value
1763 .into_iter()
1764 .map(|(k, v)| Ok((RoleId::from_str(&k)?, v)))
1765 .collect::<Result<_, anyhow::Error>>()?,
1766 })
1767 }
1768}
1769
1770#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1772pub struct DefaultPrivilegeObject {
1773 pub role_id: RoleId,
1775 pub database_id: Option<DatabaseId>,
1777 pub schema_id: Option<SchemaId>,
1779 pub object_type: ObjectType,
1781}
1782
1783impl DefaultPrivilegeObject {
1784 pub fn new(
1786 role_id: RoleId,
1787 database_id: Option<DatabaseId>,
1788 schema_id: Option<SchemaId>,
1789 object_type: ObjectType,
1790 ) -> DefaultPrivilegeObject {
1791 DefaultPrivilegeObject {
1792 role_id,
1793 database_id,
1794 schema_id,
1795 object_type,
1796 }
1797 }
1798}
1799
1800impl std::fmt::Display for DefaultPrivilegeObject {
1801 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1802 write!(f, "{self:?}")
1804 }
1805}
1806
1807#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1809pub struct DefaultPrivilegeAclItem {
1810 pub grantee: RoleId,
1812 pub acl_mode: AclMode,
1814}
1815
1816impl DefaultPrivilegeAclItem {
1817 pub fn new(grantee: RoleId, acl_mode: AclMode) -> DefaultPrivilegeAclItem {
1819 DefaultPrivilegeAclItem { grantee, acl_mode }
1820 }
1821
1822 pub fn mz_acl_item(self, grantor: RoleId) -> MzAclItem {
1824 MzAclItem {
1825 grantee: self.grantee,
1826 grantor,
1827 acl_mode: self.acl_mode,
1828 }
1829 }
1830}
1831
1832#[derive(Debug, Clone)]
1838pub struct BuiltinsConfig {
1839 pub include_continual_tasks: bool,
1841}
1842
1843#[cfg(test)]
1844mod tests {
1845 use super::{CloudProvider, EnvironmentId, InvalidEnvironmentIdError};
1846
1847 #[mz_ore::test]
1848 fn test_environment_id() {
1849 for (input, expected) in [
1850 (
1851 "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1852 Ok(EnvironmentId {
1853 cloud_provider: CloudProvider::Local,
1854 cloud_provider_region: "az1".into(),
1855 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1856 ordinal: 452,
1857 }),
1858 ),
1859 (
1860 "aws-us-east-1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1861 Ok(EnvironmentId {
1862 cloud_provider: CloudProvider::Aws,
1863 cloud_provider_region: "us-east-1".into(),
1864 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1865 ordinal: 0,
1866 }),
1867 ),
1868 (
1869 "gcp-us-central1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1870 Ok(EnvironmentId {
1871 cloud_provider: CloudProvider::Gcp,
1872 cloud_provider_region: "us-central1".into(),
1873 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1874 ordinal: 0,
1875 }),
1876 ),
1877 (
1878 "azure-australiaeast-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1879 Ok(EnvironmentId {
1880 cloud_provider: CloudProvider::Azure,
1881 cloud_provider_region: "australiaeast".into(),
1882 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1883 ordinal: 0,
1884 }),
1885 ),
1886 (
1887 "generic-moon-station-11-darkside-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1888 Ok(EnvironmentId {
1889 cloud_provider: CloudProvider::Generic,
1890 cloud_provider_region: "moon-station-11-darkside".into(),
1891 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1892 ordinal: 0,
1893 }),
1894 ),
1895 ("", Err(InvalidEnvironmentIdError)),
1896 (
1897 "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-123456789",
1898 Err(InvalidEnvironmentIdError),
1899 ),
1900 (
1901 "local-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1902 Err(InvalidEnvironmentIdError),
1903 ),
1904 (
1905 "local-az1-1497a3b7-a455-4fc48752-b44a94b5f90a-452",
1906 Err(InvalidEnvironmentIdError),
1907 ),
1908 ] {
1909 let actual = input.parse();
1910 assert_eq!(expected, actual, "input = {}", input);
1911 if let Ok(actual) = actual {
1912 assert_eq!(input, actual.to_string(), "input = {}", input);
1913 }
1914 }
1915 }
1916}