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 resolve_database(&self, database_name: &str) -> Result<&dyn CatalogDatabase, CatalogError>;
115
116 fn get_database(&self, id: &DatabaseId) -> &dyn CatalogDatabase;
120
121 fn get_databases(&self) -> Vec<&dyn CatalogDatabase>;
123
124 fn resolve_schema(
129 &self,
130 database_name: Option<&str>,
131 schema_name: &str,
132 ) -> Result<&dyn CatalogSchema, CatalogError>;
133
134 fn resolve_schema_in_database(
139 &self,
140 database_spec: &ResolvedDatabaseSpecifier,
141 schema_name: &str,
142 ) -> Result<&dyn CatalogSchema, CatalogError>;
143
144 fn get_schema(
148 &self,
149 database_spec: &ResolvedDatabaseSpecifier,
150 schema_spec: &SchemaSpecifier,
151 ) -> &dyn CatalogSchema;
152
153 fn get_schemas(&self) -> Vec<&dyn CatalogSchema>;
155
156 fn get_mz_internal_schema_id(&self) -> SchemaId;
158
159 fn get_mz_unsafe_schema_id(&self) -> SchemaId;
161
162 fn is_system_schema_specifier(&self, schema: SchemaSpecifier) -> bool;
164
165 fn resolve_role(&self, role_name: &str) -> Result<&dyn CatalogRole, CatalogError>;
167
168 fn resolve_network_policy(
170 &self,
171 network_policy_name: &str,
172 ) -> Result<&dyn CatalogNetworkPolicy, CatalogError>;
173
174 fn try_get_role(&self, id: &RoleId) -> Option<&dyn CatalogRole>;
176
177 fn get_role(&self, id: &RoleId) -> &dyn CatalogRole;
181
182 fn get_roles(&self) -> Vec<&dyn CatalogRole>;
184
185 fn mz_system_role_id(&self) -> RoleId;
187
188 fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId>;
190
191 fn get_network_policy(&self, id: &NetworkPolicyId) -> &dyn CatalogNetworkPolicy;
196
197 fn get_network_policies(&self) -> Vec<&dyn CatalogNetworkPolicy>;
199
200 fn resolve_cluster<'a, 'b>(
203 &'a self,
204 cluster_name: Option<&'b str>,
205 ) -> Result<&'a dyn CatalogCluster<'a>, CatalogError>;
206
207 fn resolve_cluster_replica<'a, 'b>(
209 &'a self,
210 cluster_replica_name: &'b QualifiedReplica,
211 ) -> Result<&'a dyn CatalogClusterReplica<'a>, CatalogError>;
212
213 fn resolve_item(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
228
229 fn resolve_function(
232 &self,
233 item_name: &PartialItemName,
234 ) -> Result<&dyn CatalogItem, CatalogError>;
235
236 fn resolve_type(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
239
240 fn resolve_item_or_type(
242 &self,
243 name: &PartialItemName,
244 ) -> Result<&dyn CatalogItem, CatalogError> {
245 if let Ok(ty) = self.resolve_type(name) {
246 return Ok(ty);
247 }
248 self.resolve_item(name)
249 }
250
251 fn get_system_type(&self, name: &str) -> &dyn CatalogItem;
257
258 fn try_get_item(&self, id: &CatalogItemId) -> Option<&dyn CatalogItem>;
260
261 fn try_get_item_by_global_id<'a>(
266 &'a self,
267 id: &GlobalId,
268 ) -> Option<Box<dyn CatalogCollectionItem + 'a>>;
269
270 fn get_item(&self, id: &CatalogItemId) -> &dyn CatalogItem;
274
275 fn get_item_by_global_id<'a>(&'a self, id: &GlobalId) -> Box<dyn CatalogCollectionItem + 'a>;
281
282 fn get_items(&self) -> Vec<&dyn CatalogItem>;
284
285 fn get_item_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
287
288 fn get_type_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
290
291 fn get_cluster(&self, id: ClusterId) -> &dyn CatalogCluster;
293
294 fn get_clusters(&self) -> Vec<&dyn CatalogCluster>;
296
297 fn get_cluster_replica(
299 &self,
300 cluster_id: ClusterId,
301 replica_id: ReplicaId,
302 ) -> &dyn CatalogClusterReplica;
303
304 fn get_cluster_replicas(&self) -> Vec<&dyn CatalogClusterReplica>;
306
307 fn get_system_privileges(&self) -> &PrivilegeMap;
309
310 fn get_default_privileges(
312 &self,
313 ) -> Vec<(&DefaultPrivilegeObject, Vec<&DefaultPrivilegeAclItem>)>;
314
315 fn find_available_name(&self, name: QualifiedItemName) -> QualifiedItemName;
319
320 fn resolve_full_name(&self, name: &QualifiedItemName) -> FullItemName;
322
323 fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName;
326
327 fn resolve_item_id(&self, global_id: &GlobalId) -> CatalogItemId;
329
330 fn resolve_global_id(
332 &self,
333 item_id: &CatalogItemId,
334 version: RelationVersionSelector,
335 ) -> GlobalId;
336
337 fn config(&self) -> &CatalogConfig;
339
340 fn now(&self) -> EpochMillis;
344
345 fn aws_privatelink_availability_zones(&self) -> Option<BTreeSet<String>>;
347
348 fn system_vars(&self) -> &SystemVars;
350
351 fn system_vars_mut(&mut self) -> &mut SystemVars;
358
359 fn get_owner_id(&self, id: &ObjectId) -> Option<RoleId>;
361
362 fn get_privileges(&self, id: &SystemObjectId) -> Option<&PrivilegeMap>;
364
365 fn object_dependents(&self, ids: &Vec<ObjectId>) -> Vec<ObjectId>;
371
372 fn item_dependents(&self, id: CatalogItemId) -> Vec<ObjectId>;
378
379 fn all_object_privileges(&self, object_type: SystemObjectType) -> AclMode;
381
382 fn get_object_type(&self, object_id: &ObjectId) -> ObjectType;
384
385 fn get_system_object_type(&self, id: &SystemObjectId) -> SystemObjectType;
387
388 fn minimal_qualification(&self, qualified_name: &QualifiedItemName) -> PartialItemName;
391
392 fn add_notice(&self, notice: PlanNotice);
395
396 fn get_item_comments(&self, id: &CatalogItemId) -> Option<&BTreeMap<Option<usize>, String>>;
398
399 fn is_cluster_size_cc(&self, size: &str) -> bool;
402}
403
404#[derive(Debug, Clone)]
406pub struct CatalogConfig {
407 pub start_time: DateTime<Utc>,
409 pub start_instant: Instant,
411 pub nonce: u64,
416 pub environment_id: EnvironmentId,
418 pub session_id: Uuid,
420 pub build_info: &'static BuildInfo,
422 pub timestamp_interval: Duration,
424 pub now: NowFn,
427 pub connection_context: ConnectionContext,
429 pub builtins_cfg: BuiltinsConfig,
431 pub helm_chart_version: Option<String>,
433}
434
435pub trait CatalogDatabase {
437 fn name(&self) -> &str;
439
440 fn id(&self) -> DatabaseId;
442
443 fn has_schemas(&self) -> bool;
445
446 fn schema_ids(&self) -> &BTreeMap<String, SchemaId>;
449
450 fn schemas(&self) -> Vec<&dyn CatalogSchema>;
452
453 fn owner_id(&self) -> RoleId;
455
456 fn privileges(&self) -> &PrivilegeMap;
458}
459
460pub trait CatalogSchema {
462 fn database(&self) -> &ResolvedDatabaseSpecifier;
464
465 fn name(&self) -> &QualifiedSchemaName;
467
468 fn id(&self) -> &SchemaSpecifier;
470
471 fn has_items(&self) -> bool;
473
474 fn item_ids(&self) -> Box<dyn Iterator<Item = CatalogItemId> + '_>;
476
477 fn owner_id(&self) -> RoleId;
479
480 fn privileges(&self) -> &PrivilegeMap;
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Arbitrary)]
486pub struct RoleAttributes {
487 pub inherit: bool,
489 pub password: Option<Password>,
491 pub superuser: Option<bool>,
493 pub login: Option<bool>,
495 _private: (),
497}
498
499impl RoleAttributes {
500 pub const fn new() -> RoleAttributes {
502 RoleAttributes {
503 inherit: true,
504 password: None,
505 superuser: None,
506 login: None,
507 _private: (),
508 }
509 }
510
511 pub const fn with_all(mut self) -> RoleAttributes {
513 self.inherit = true;
514 self
515 }
516
517 pub const fn is_inherit(&self) -> bool {
519 self.inherit
520 }
521
522 pub const fn has_password(&self) -> bool {
524 self.password.is_some()
525 }
526
527 pub fn without_password(self) -> RoleAttributes {
529 RoleAttributes {
530 inherit: self.inherit,
531 password: None,
532 superuser: self.superuser,
533 login: self.login,
534 _private: (),
535 }
536 }
537}
538
539impl From<PlannedRoleAttributes> for RoleAttributes {
540 fn from(
541 PlannedRoleAttributes {
542 inherit,
543 password,
544 superuser,
545 login,
546 ..
547 }: PlannedRoleAttributes,
548 ) -> RoleAttributes {
549 let default_attributes = RoleAttributes::new();
550 RoleAttributes {
551 inherit: inherit.unwrap_or(default_attributes.inherit),
552 password,
553 superuser,
554 login,
555 _private: (),
556 }
557 }
558}
559
560#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
562pub struct RoleVars {
563 pub map: BTreeMap<String, OwnedVarInput>,
565}
566
567pub trait CatalogRole {
569 fn name(&self) -> &str;
571
572 fn id(&self) -> RoleId;
574
575 fn membership(&self) -> &BTreeMap<RoleId, RoleId>;
580
581 fn attributes(&self) -> &RoleAttributes;
583
584 fn vars(&self) -> &BTreeMap<String, OwnedVarInput>;
586}
587
588pub trait CatalogNetworkPolicy {
590 fn name(&self) -> &str;
592
593 fn id(&self) -> NetworkPolicyId;
595
596 fn owner_id(&self) -> RoleId;
598
599 fn privileges(&self) -> &PrivilegeMap;
601}
602
603pub trait CatalogCluster<'a> {
605 fn name(&self) -> &str;
607
608 fn id(&self) -> ClusterId;
610
611 fn bound_objects(&self) -> &BTreeSet<CatalogItemId>;
613
614 fn replica_ids(&self) -> &BTreeMap<String, ReplicaId>;
617
618 fn replicas(&self) -> Vec<&dyn CatalogClusterReplica>;
620
621 fn replica(&self, id: ReplicaId) -> &dyn CatalogClusterReplica;
623
624 fn owner_id(&self) -> RoleId;
626
627 fn privileges(&self) -> &PrivilegeMap;
629
630 fn is_managed(&self) -> bool;
632
633 fn managed_size(&self) -> Option<&str>;
635
636 fn schedule(&self) -> Option<&ClusterSchedule>;
638
639 fn try_to_plan(&self) -> Result<CreateClusterPlan, PlanError>;
642}
643
644pub trait CatalogClusterReplica<'a>: Debug {
646 fn name(&self) -> &str;
648
649 fn cluster_id(&self) -> ClusterId;
651
652 fn replica_id(&self) -> ReplicaId;
654
655 fn owner_id(&self) -> RoleId;
657
658 fn internal(&self) -> bool;
660}
661
662pub trait CatalogItem {
667 fn name(&self) -> &QualifiedItemName;
669
670 fn id(&self) -> CatalogItemId;
672
673 fn global_ids(&self) -> Box<dyn Iterator<Item = GlobalId> + '_>;
675
676 fn oid(&self) -> u32;
678
679 fn func(&self) -> Result<&'static Func, CatalogError>;
684
685 fn source_desc(&self) -> Result<Option<&SourceDesc<ReferencedConnection>>, CatalogError>;
690
691 fn connection(&self) -> Result<Connection<ReferencedConnection>, CatalogError>;
695
696 fn item_type(&self) -> CatalogItemType;
698
699 fn create_sql(&self) -> &str;
702
703 fn references(&self) -> &ResolvedIds;
706
707 fn uses(&self) -> BTreeSet<CatalogItemId>;
710
711 fn referenced_by(&self) -> &[CatalogItemId];
713
714 fn used_by(&self) -> &[CatalogItemId];
716
717 fn subsource_details(
720 &self,
721 ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)>;
722
723 fn source_export_details(
726 &self,
727 ) -> Option<(
728 CatalogItemId,
729 &UnresolvedItemName,
730 &SourceExportDetails,
731 &SourceExportDataConfig<ReferencedConnection>,
732 )>;
733
734 fn is_progress_source(&self) -> bool;
736
737 fn progress_id(&self) -> Option<CatalogItemId>;
739
740 fn index_details(&self) -> Option<(&[MirScalarExpr], GlobalId)>;
743
744 fn writable_table_details(&self) -> Option<&[Expr<Aug>]>;
747
748 fn type_details(&self) -> Option<&CatalogTypeDetails<IdReference>>;
751
752 fn owner_id(&self) -> RoleId;
754
755 fn privileges(&self) -> &PrivilegeMap;
757
758 fn cluster_id(&self) -> Option<ClusterId>;
760
761 fn at_version(&self, version: RelationVersionSelector) -> Box<dyn CatalogCollectionItem>;
764
765 fn latest_version(&self) -> Option<RelationVersion>;
767}
768
769pub trait CatalogCollectionItem: CatalogItem + Send + Sync {
772 fn desc(&self, name: &FullItemName) -> Result<Cow<RelationDesc>, CatalogError>;
777
778 fn global_id(&self) -> GlobalId;
780}
781
782#[derive(Debug, Deserialize, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
784pub enum CatalogItemType {
785 Table,
787 Source,
789 Sink,
791 View,
793 MaterializedView,
795 Index,
797 Type,
799 Func,
801 Secret,
803 Connection,
805 ContinualTask,
807}
808
809impl CatalogItemType {
810 pub fn conflicts_with_type(&self) -> bool {
829 match self {
830 CatalogItemType::Table => true,
831 CatalogItemType::Source => true,
832 CatalogItemType::View => true,
833 CatalogItemType::MaterializedView => true,
834 CatalogItemType::Index => true,
835 CatalogItemType::Type => true,
836 CatalogItemType::Sink => false,
837 CatalogItemType::Func => false,
838 CatalogItemType::Secret => false,
839 CatalogItemType::Connection => false,
840 CatalogItemType::ContinualTask => true,
841 }
842 }
843}
844
845impl fmt::Display for CatalogItemType {
846 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
847 match self {
848 CatalogItemType::Table => f.write_str("table"),
849 CatalogItemType::Source => f.write_str("source"),
850 CatalogItemType::Sink => f.write_str("sink"),
851 CatalogItemType::View => f.write_str("view"),
852 CatalogItemType::MaterializedView => f.write_str("materialized view"),
853 CatalogItemType::Index => f.write_str("index"),
854 CatalogItemType::Type => f.write_str("type"),
855 CatalogItemType::Func => f.write_str("func"),
856 CatalogItemType::Secret => f.write_str("secret"),
857 CatalogItemType::Connection => f.write_str("connection"),
858 CatalogItemType::ContinualTask => f.write_str("continual task"),
859 }
860 }
861}
862
863impl From<CatalogItemType> for ObjectType {
864 fn from(value: CatalogItemType) -> Self {
865 match value {
866 CatalogItemType::Table => ObjectType::Table,
867 CatalogItemType::Source => ObjectType::Source,
868 CatalogItemType::Sink => ObjectType::Sink,
869 CatalogItemType::View => ObjectType::View,
870 CatalogItemType::MaterializedView => ObjectType::MaterializedView,
871 CatalogItemType::Index => ObjectType::Index,
872 CatalogItemType::Type => ObjectType::Type,
873 CatalogItemType::Func => ObjectType::Func,
874 CatalogItemType::Secret => ObjectType::Secret,
875 CatalogItemType::Connection => ObjectType::Connection,
876 CatalogItemType::ContinualTask => ObjectType::ContinualTask,
877 }
878 }
879}
880
881impl From<CatalogItemType> for mz_audit_log::ObjectType {
882 fn from(value: CatalogItemType) -> Self {
883 match value {
884 CatalogItemType::Table => mz_audit_log::ObjectType::Table,
885 CatalogItemType::Source => mz_audit_log::ObjectType::Source,
886 CatalogItemType::View => mz_audit_log::ObjectType::View,
887 CatalogItemType::MaterializedView => mz_audit_log::ObjectType::MaterializedView,
888 CatalogItemType::Index => mz_audit_log::ObjectType::Index,
889 CatalogItemType::Type => mz_audit_log::ObjectType::Type,
890 CatalogItemType::Sink => mz_audit_log::ObjectType::Sink,
891 CatalogItemType::Func => mz_audit_log::ObjectType::Func,
892 CatalogItemType::Secret => mz_audit_log::ObjectType::Secret,
893 CatalogItemType::Connection => mz_audit_log::ObjectType::Connection,
894 CatalogItemType::ContinualTask => mz_audit_log::ObjectType::ContinualTask,
895 }
896 }
897}
898
899#[derive(Clone, Debug, Eq, PartialEq)]
901pub struct CatalogTypeDetails<T: TypeReference> {
902 pub array_id: Option<CatalogItemId>,
904 pub typ: CatalogType<T>,
906 pub pg_metadata: Option<CatalogTypePgMetadata>,
908}
909
910#[derive(Clone, Debug, Eq, PartialEq)]
912pub struct CatalogTypePgMetadata {
913 pub typinput_oid: u32,
915 pub typreceive_oid: u32,
917}
918
919pub trait TypeReference {
921 type Reference: Clone + Debug + Eq + PartialEq;
923}
924
925#[derive(Clone, Debug, Eq, PartialEq)]
927pub struct NameReference;
928
929impl TypeReference for NameReference {
930 type Reference = &'static str;
931}
932
933#[derive(Clone, Debug, Eq, PartialEq)]
935pub struct IdReference;
936
937impl TypeReference for IdReference {
938 type Reference = CatalogItemId;
939}
940
941#[allow(missing_docs)]
947#[derive(Clone, Debug, Eq, PartialEq)]
948pub enum CatalogType<T: TypeReference> {
949 AclItem,
950 Array {
951 element_reference: T::Reference,
952 },
953 Bool,
954 Bytes,
955 Char,
956 Date,
957 Float32,
958 Float64,
959 Int16,
960 Int32,
961 Int64,
962 UInt16,
963 UInt32,
964 UInt64,
965 MzTimestamp,
966 Interval,
967 Jsonb,
968 List {
969 element_reference: T::Reference,
970 element_modifiers: Vec<i64>,
971 },
972 Map {
973 key_reference: T::Reference,
974 key_modifiers: Vec<i64>,
975 value_reference: T::Reference,
976 value_modifiers: Vec<i64>,
977 },
978 Numeric,
979 Oid,
980 PgLegacyChar,
981 PgLegacyName,
982 Pseudo,
983 Range {
984 element_reference: T::Reference,
985 },
986 Record {
987 fields: Vec<CatalogRecordField<T>>,
988 },
989 RegClass,
990 RegProc,
991 RegType,
992 String,
993 Time,
994 Timestamp,
995 TimestampTz,
996 Uuid,
997 VarChar,
998 Int2Vector,
999 MzAclItem,
1000}
1001
1002impl CatalogType<IdReference> {
1003 pub fn desc(&self, catalog: &dyn SessionCatalog) -> Result<Option<RelationDesc>, PlanError> {
1006 match &self {
1007 CatalogType::Record { fields } => {
1008 let mut desc = RelationDesc::builder();
1009 for f in fields {
1010 let name = f.name.clone();
1011 let ty = query::scalar_type_from_catalog(
1012 catalog,
1013 f.type_reference,
1014 &f.type_modifiers,
1015 )?;
1016 let ty = ty.nullable(true);
1019 desc = desc.with_column(name, ty);
1020 }
1021 Ok(Some(desc.finish()))
1022 }
1023 _ => Ok(None),
1024 }
1025 }
1026}
1027
1028#[derive(Clone, Debug, Eq, PartialEq)]
1030pub struct CatalogRecordField<T: TypeReference> {
1031 pub name: ColumnName,
1033 pub type_reference: T::Reference,
1035 pub type_modifiers: Vec<i64>,
1037}
1038
1039#[derive(Clone, Debug, Eq, PartialEq)]
1040pub enum TypeCategory {
1048 Array,
1050 BitString,
1052 Boolean,
1054 Composite,
1056 DateTime,
1058 Enum,
1060 Geometric,
1062 List,
1064 NetworkAddress,
1066 Numeric,
1068 Pseudo,
1070 Range,
1072 String,
1074 Timespan,
1076 UserDefined,
1078 Unknown,
1080}
1081
1082impl fmt::Display for TypeCategory {
1083 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1084 f.write_str(match self {
1085 TypeCategory::Array => "array",
1086 TypeCategory::BitString => "bit-string",
1087 TypeCategory::Boolean => "boolean",
1088 TypeCategory::Composite => "composite",
1089 TypeCategory::DateTime => "date-time",
1090 TypeCategory::Enum => "enum",
1091 TypeCategory::Geometric => "geometric",
1092 TypeCategory::List => "list",
1093 TypeCategory::NetworkAddress => "network-address",
1094 TypeCategory::Numeric => "numeric",
1095 TypeCategory::Pseudo => "pseudo",
1096 TypeCategory::Range => "range",
1097 TypeCategory::String => "string",
1098 TypeCategory::Timespan => "timespan",
1099 TypeCategory::UserDefined => "user-defined",
1100 TypeCategory::Unknown => "unknown",
1101 })
1102 }
1103}
1104
1105#[derive(Debug, Clone, PartialEq)]
1129pub struct EnvironmentId {
1130 cloud_provider: CloudProvider,
1131 cloud_provider_region: String,
1132 organization_id: Uuid,
1133 ordinal: u64,
1134}
1135
1136impl EnvironmentId {
1137 pub fn for_tests() -> EnvironmentId {
1139 EnvironmentId {
1140 cloud_provider: CloudProvider::Local,
1141 cloud_provider_region: "az1".into(),
1142 organization_id: Uuid::new_v4(),
1143 ordinal: 0,
1144 }
1145 }
1146
1147 pub fn cloud_provider(&self) -> &CloudProvider {
1149 &self.cloud_provider
1150 }
1151
1152 pub fn cloud_provider_region(&self) -> &str {
1154 &self.cloud_provider_region
1155 }
1156
1157 pub fn region(&self) -> String {
1162 format!("{}/{}", self.cloud_provider, self.cloud_provider_region)
1163 }
1164
1165 pub fn organization_id(&self) -> Uuid {
1167 self.organization_id
1168 }
1169
1170 pub fn ordinal(&self) -> u64 {
1172 self.ordinal
1173 }
1174}
1175
1176impl fmt::Display for EnvironmentId {
1182 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1183 write!(
1184 f,
1185 "{}-{}-{}-{}",
1186 self.cloud_provider, self.cloud_provider_region, self.organization_id, self.ordinal
1187 )
1188 }
1189}
1190
1191impl FromStr for EnvironmentId {
1192 type Err = InvalidEnvironmentIdError;
1193
1194 fn from_str(s: &str) -> Result<EnvironmentId, InvalidEnvironmentIdError> {
1195 static MATCHER: LazyLock<Regex> = LazyLock::new(|| {
1196 Regex::new(
1197 "^(?P<cloud_provider>[[:alnum:]]+)-\
1198 (?P<cloud_provider_region>[[:alnum:]\\-]+)-\
1199 (?P<organization_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-\
1200 (?P<ordinal>\\d{1,8})$"
1201 ).unwrap()
1202 });
1203 let captures = MATCHER.captures(s).ok_or(InvalidEnvironmentIdError)?;
1204 Ok(EnvironmentId {
1205 cloud_provider: CloudProvider::from_str(&captures["cloud_provider"])?,
1206 cloud_provider_region: captures["cloud_provider_region"].into(),
1207 organization_id: captures["organization_id"]
1208 .parse()
1209 .map_err(|_| InvalidEnvironmentIdError)?,
1210 ordinal: captures["ordinal"]
1211 .parse()
1212 .map_err(|_| InvalidEnvironmentIdError)?,
1213 })
1214 }
1215}
1216
1217#[derive(Debug, Clone, PartialEq)]
1219pub struct InvalidEnvironmentIdError;
1220
1221impl fmt::Display for InvalidEnvironmentIdError {
1222 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1223 f.write_str("invalid environment ID")
1224 }
1225}
1226
1227impl Error for InvalidEnvironmentIdError {}
1228
1229impl From<InvalidCloudProviderError> for InvalidEnvironmentIdError {
1230 fn from(_: InvalidCloudProviderError) -> Self {
1231 InvalidEnvironmentIdError
1232 }
1233}
1234
1235#[derive(Clone, Debug, Eq, PartialEq)]
1237pub enum CatalogError {
1238 UnknownDatabase(String),
1240 DatabaseAlreadyExists(String),
1242 UnknownSchema(String),
1244 SchemaAlreadyExists(String),
1246 UnknownRole(String),
1248 RoleAlreadyExists(String),
1250 NetworkPolicyAlreadyExists(String),
1252 UnknownCluster(String),
1254 UnexpectedBuiltinCluster(String),
1256 UnexpectedBuiltinClusterType(String),
1258 ClusterAlreadyExists(String),
1260 UnknownClusterReplica(String),
1262 UnknownClusterReplicaSize(String),
1264 DuplicateReplica(String, String),
1266 UnknownItem(String),
1268 ItemAlreadyExists(CatalogItemId, String),
1270 UnknownFunction {
1272 name: String,
1274 alternative: Option<String>,
1276 },
1277 UnknownType {
1279 name: String,
1281 },
1282 UnknownConnection(String),
1284 UnknownNetworkPolicy(String),
1286 UnexpectedType {
1288 name: String,
1290 actual_type: CatalogItemType,
1292 expected_type: CatalogItemType,
1294 },
1295 InvalidDependency {
1297 name: String,
1299 typ: CatalogItemType,
1301 },
1302 IdExhaustion,
1304 OidExhaustion,
1306 TimelineAlreadyExists(String),
1308 IdAllocatorAlreadyExists(String),
1310 ConfigAlreadyExists(String),
1312 FailedBuiltinSchemaMigration(String),
1314 StorageCollectionMetadataAlreadyExists(GlobalId),
1316}
1317
1318impl fmt::Display for CatalogError {
1319 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1320 match self {
1321 Self::UnknownDatabase(name) => write!(f, "unknown database '{}'", name),
1322 Self::DatabaseAlreadyExists(name) => write!(f, "database '{name}' already exists"),
1323 Self::UnknownFunction { name, .. } => write!(f, "function \"{}\" does not exist", name),
1324 Self::UnknownType { name, .. } => write!(f, "type \"{}\" does not exist", name),
1325 Self::UnknownConnection(name) => write!(f, "connection \"{}\" does not exist", name),
1326 Self::UnknownSchema(name) => write!(f, "unknown schema '{}'", name),
1327 Self::SchemaAlreadyExists(name) => write!(f, "schema '{name}' already exists"),
1328 Self::UnknownRole(name) => write!(f, "unknown role '{}'", name),
1329 Self::RoleAlreadyExists(name) => write!(f, "role '{name}' already exists"),
1330 Self::NetworkPolicyAlreadyExists(name) => {
1331 write!(f, "network policy '{name}' already exists")
1332 }
1333 Self::UnknownCluster(name) => write!(f, "unknown cluster '{}'", name),
1334 Self::UnknownNetworkPolicy(name) => write!(f, "unknown network policy '{}'", name),
1335 Self::UnexpectedBuiltinCluster(name) => {
1336 write!(f, "Unexpected builtin cluster '{}'", name)
1337 }
1338 Self::UnexpectedBuiltinClusterType(name) => {
1339 write!(f, "Unexpected builtin cluster type'{}'", name)
1340 }
1341 Self::ClusterAlreadyExists(name) => write!(f, "cluster '{name}' already exists"),
1342 Self::UnknownClusterReplica(name) => {
1343 write!(f, "unknown cluster replica '{}'", name)
1344 }
1345 Self::UnknownClusterReplicaSize(name) => {
1346 write!(f, "unknown cluster replica size '{}'", name)
1347 }
1348 Self::DuplicateReplica(replica_name, cluster_name) => write!(
1349 f,
1350 "cannot create multiple replicas named '{replica_name}' on cluster '{cluster_name}'"
1351 ),
1352 Self::UnknownItem(name) => write!(f, "unknown catalog item '{}'", name),
1353 Self::ItemAlreadyExists(_gid, name) => {
1354 write!(f, "catalog item '{name}' already exists")
1355 }
1356 Self::UnexpectedType {
1357 name,
1358 actual_type,
1359 expected_type,
1360 } => {
1361 write!(f, "\"{name}\" is a {actual_type} not a {expected_type}")
1362 }
1363 Self::InvalidDependency { name, typ } => write!(
1364 f,
1365 "catalog item '{}' is {} {} and so cannot be depended upon",
1366 name,
1367 if matches!(typ, CatalogItemType::Index) {
1368 "an"
1369 } else {
1370 "a"
1371 },
1372 typ,
1373 ),
1374 Self::IdExhaustion => write!(f, "id counter overflows i64"),
1375 Self::OidExhaustion => write!(f, "oid counter overflows u32"),
1376 Self::TimelineAlreadyExists(name) => write!(f, "timeline '{name}' already exists"),
1377 Self::IdAllocatorAlreadyExists(name) => {
1378 write!(f, "ID allocator '{name}' already exists")
1379 }
1380 Self::ConfigAlreadyExists(key) => write!(f, "config '{key}' already exists"),
1381 Self::FailedBuiltinSchemaMigration(objects) => {
1382 write!(f, "failed to migrate schema of builtin objects: {objects}")
1383 }
1384 Self::StorageCollectionMetadataAlreadyExists(key) => {
1385 write!(f, "storage metadata for '{key}' already exists")
1386 }
1387 }
1388 }
1389}
1390
1391impl CatalogError {
1392 pub fn hint(&self) -> Option<String> {
1394 match self {
1395 CatalogError::UnknownFunction { alternative, .. } => {
1396 match alternative {
1397 None => Some("No function matches the given name and argument types. You might need to add explicit type casts.".into()),
1398 Some(alt) => Some(format!("Try using {alt}")),
1399 }
1400 }
1401 _ => None,
1402 }
1403 }
1404}
1405
1406impl Error for CatalogError {}
1407
1408#[allow(missing_docs)]
1410#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Deserialize, Serialize)]
1411pub enum ObjectType {
1413 Table,
1414 View,
1415 MaterializedView,
1416 Source,
1417 Sink,
1418 Index,
1419 Type,
1420 Role,
1421 Cluster,
1422 ClusterReplica,
1423 Secret,
1424 Connection,
1425 Database,
1426 Schema,
1427 Func,
1428 ContinualTask,
1429 NetworkPolicy,
1430}
1431
1432impl ObjectType {
1433 pub fn is_relation(&self) -> bool {
1435 match self {
1436 ObjectType::Table
1437 | ObjectType::View
1438 | ObjectType::MaterializedView
1439 | ObjectType::Source
1440 | ObjectType::ContinualTask => true,
1441 ObjectType::Sink
1442 | ObjectType::Index
1443 | ObjectType::Type
1444 | ObjectType::Secret
1445 | ObjectType::Connection
1446 | ObjectType::Func
1447 | ObjectType::Database
1448 | ObjectType::Schema
1449 | ObjectType::Cluster
1450 | ObjectType::ClusterReplica
1451 | ObjectType::Role
1452 | ObjectType::NetworkPolicy => false,
1453 }
1454 }
1455}
1456
1457impl From<mz_sql_parser::ast::ObjectType> for ObjectType {
1458 fn from(value: mz_sql_parser::ast::ObjectType) -> Self {
1459 match value {
1460 mz_sql_parser::ast::ObjectType::Table => ObjectType::Table,
1461 mz_sql_parser::ast::ObjectType::View => ObjectType::View,
1462 mz_sql_parser::ast::ObjectType::MaterializedView => ObjectType::MaterializedView,
1463 mz_sql_parser::ast::ObjectType::Source => ObjectType::Source,
1464 mz_sql_parser::ast::ObjectType::Subsource => ObjectType::Source,
1465 mz_sql_parser::ast::ObjectType::Sink => ObjectType::Sink,
1466 mz_sql_parser::ast::ObjectType::Index => ObjectType::Index,
1467 mz_sql_parser::ast::ObjectType::Type => ObjectType::Type,
1468 mz_sql_parser::ast::ObjectType::Role => ObjectType::Role,
1469 mz_sql_parser::ast::ObjectType::Cluster => ObjectType::Cluster,
1470 mz_sql_parser::ast::ObjectType::ClusterReplica => ObjectType::ClusterReplica,
1471 mz_sql_parser::ast::ObjectType::Secret => ObjectType::Secret,
1472 mz_sql_parser::ast::ObjectType::Connection => ObjectType::Connection,
1473 mz_sql_parser::ast::ObjectType::Database => ObjectType::Database,
1474 mz_sql_parser::ast::ObjectType::Schema => ObjectType::Schema,
1475 mz_sql_parser::ast::ObjectType::Func => ObjectType::Func,
1476 mz_sql_parser::ast::ObjectType::ContinualTask => ObjectType::ContinualTask,
1477 mz_sql_parser::ast::ObjectType::NetworkPolicy => ObjectType::NetworkPolicy,
1478 }
1479 }
1480}
1481
1482impl From<CommentObjectId> for ObjectType {
1483 fn from(value: CommentObjectId) -> ObjectType {
1484 match value {
1485 CommentObjectId::Table(_) => ObjectType::Table,
1486 CommentObjectId::View(_) => ObjectType::View,
1487 CommentObjectId::MaterializedView(_) => ObjectType::MaterializedView,
1488 CommentObjectId::Source(_) => ObjectType::Source,
1489 CommentObjectId::Sink(_) => ObjectType::Sink,
1490 CommentObjectId::Index(_) => ObjectType::Index,
1491 CommentObjectId::Func(_) => ObjectType::Func,
1492 CommentObjectId::Connection(_) => ObjectType::Connection,
1493 CommentObjectId::Type(_) => ObjectType::Type,
1494 CommentObjectId::Secret(_) => ObjectType::Secret,
1495 CommentObjectId::Role(_) => ObjectType::Role,
1496 CommentObjectId::Database(_) => ObjectType::Database,
1497 CommentObjectId::Schema(_) => ObjectType::Schema,
1498 CommentObjectId::Cluster(_) => ObjectType::Cluster,
1499 CommentObjectId::ClusterReplica(_) => ObjectType::ClusterReplica,
1500 CommentObjectId::ContinualTask(_) => ObjectType::ContinualTask,
1501 CommentObjectId::NetworkPolicy(_) => ObjectType::NetworkPolicy,
1502 }
1503 }
1504}
1505
1506impl Display for ObjectType {
1507 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1508 f.write_str(match self {
1509 ObjectType::Table => "TABLE",
1510 ObjectType::View => "VIEW",
1511 ObjectType::MaterializedView => "MATERIALIZED VIEW",
1512 ObjectType::Source => "SOURCE",
1513 ObjectType::Sink => "SINK",
1514 ObjectType::Index => "INDEX",
1515 ObjectType::Type => "TYPE",
1516 ObjectType::Role => "ROLE",
1517 ObjectType::Cluster => "CLUSTER",
1518 ObjectType::ClusterReplica => "CLUSTER REPLICA",
1519 ObjectType::Secret => "SECRET",
1520 ObjectType::Connection => "CONNECTION",
1521 ObjectType::Database => "DATABASE",
1522 ObjectType::Schema => "SCHEMA",
1523 ObjectType::Func => "FUNCTION",
1524 ObjectType::ContinualTask => "CONTINUAL TASK",
1525 ObjectType::NetworkPolicy => "NETWORK POLICY",
1526 })
1527 }
1528}
1529
1530#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Deserialize, Serialize)]
1531pub enum SystemObjectType {
1533 Object(ObjectType),
1535 System,
1537}
1538
1539impl SystemObjectType {
1540 pub fn is_relation(&self) -> bool {
1542 match self {
1543 SystemObjectType::Object(object_type) => object_type.is_relation(),
1544 SystemObjectType::System => false,
1545 }
1546 }
1547}
1548
1549impl Display for SystemObjectType {
1550 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1551 match self {
1552 SystemObjectType::Object(object_type) => std::fmt::Display::fmt(&object_type, f),
1553 SystemObjectType::System => f.write_str("SYSTEM"),
1554 }
1555 }
1556}
1557
1558#[derive(Debug, Clone, PartialEq, Eq)]
1560pub enum ErrorMessageObjectDescription {
1561 Object {
1563 object_type: ObjectType,
1565 object_name: Option<String>,
1567 },
1568 System,
1570}
1571
1572impl ErrorMessageObjectDescription {
1573 pub fn from_id(
1575 object_id: &ObjectId,
1576 catalog: &dyn SessionCatalog,
1577 ) -> ErrorMessageObjectDescription {
1578 let object_name = match object_id {
1579 ObjectId::Cluster(cluster_id) => catalog.get_cluster(*cluster_id).name().to_string(),
1580 ObjectId::ClusterReplica((cluster_id, replica_id)) => catalog
1581 .get_cluster_replica(*cluster_id, *replica_id)
1582 .name()
1583 .to_string(),
1584 ObjectId::Database(database_id) => catalog.get_database(database_id).name().to_string(),
1585 ObjectId::Schema((database_spec, schema_spec)) => {
1586 let name = catalog.get_schema(database_spec, schema_spec).name();
1587 catalog.resolve_full_schema_name(name).to_string()
1588 }
1589 ObjectId::Role(role_id) => catalog.get_role(role_id).name().to_string(),
1590 ObjectId::Item(id) => {
1591 let name = catalog.get_item(id).name();
1592 catalog.resolve_full_name(name).to_string()
1593 }
1594 ObjectId::NetworkPolicy(network_policy_id) => catalog
1595 .get_network_policy(network_policy_id)
1596 .name()
1597 .to_string(),
1598 };
1599 ErrorMessageObjectDescription::Object {
1600 object_type: catalog.get_object_type(object_id),
1601 object_name: Some(object_name),
1602 }
1603 }
1604
1605 pub fn from_sys_id(
1607 object_id: &SystemObjectId,
1608 catalog: &dyn SessionCatalog,
1609 ) -> ErrorMessageObjectDescription {
1610 match object_id {
1611 SystemObjectId::Object(object_id) => {
1612 ErrorMessageObjectDescription::from_id(object_id, catalog)
1613 }
1614 SystemObjectId::System => ErrorMessageObjectDescription::System,
1615 }
1616 }
1617
1618 pub fn from_object_type(object_type: SystemObjectType) -> ErrorMessageObjectDescription {
1620 match object_type {
1621 SystemObjectType::Object(object_type) => ErrorMessageObjectDescription::Object {
1622 object_type,
1623 object_name: None,
1624 },
1625 SystemObjectType::System => ErrorMessageObjectDescription::System,
1626 }
1627 }
1628}
1629
1630impl Display for ErrorMessageObjectDescription {
1631 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1632 match self {
1633 ErrorMessageObjectDescription::Object {
1634 object_type,
1635 object_name,
1636 } => {
1637 let object_name = object_name
1638 .as_ref()
1639 .map(|object_name| format!(" {}", object_name.quoted()))
1640 .unwrap_or_else(|| "".to_string());
1641 write!(f, "{object_type}{object_name}")
1642 }
1643 ErrorMessageObjectDescription::System => f.write_str("SYSTEM"),
1644 }
1645 }
1646}
1647
1648#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
1649#[serde(into = "BTreeMap<String, RoleId>")]
1652#[serde(try_from = "BTreeMap<String, RoleId>")]
1653pub struct RoleMembership {
1655 pub map: BTreeMap<RoleId, RoleId>,
1661}
1662
1663impl RoleMembership {
1664 pub fn new() -> RoleMembership {
1666 RoleMembership {
1667 map: BTreeMap::new(),
1668 }
1669 }
1670}
1671
1672impl From<RoleMembership> for BTreeMap<String, RoleId> {
1673 fn from(value: RoleMembership) -> Self {
1674 value
1675 .map
1676 .into_iter()
1677 .map(|(k, v)| (k.to_string(), v))
1678 .collect()
1679 }
1680}
1681
1682impl TryFrom<BTreeMap<String, RoleId>> for RoleMembership {
1683 type Error = anyhow::Error;
1684
1685 fn try_from(value: BTreeMap<String, RoleId>) -> Result<Self, Self::Error> {
1686 Ok(RoleMembership {
1687 map: value
1688 .into_iter()
1689 .map(|(k, v)| Ok((RoleId::from_str(&k)?, v)))
1690 .collect::<Result<_, anyhow::Error>>()?,
1691 })
1692 }
1693}
1694
1695#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1697pub struct DefaultPrivilegeObject {
1698 pub role_id: RoleId,
1700 pub database_id: Option<DatabaseId>,
1702 pub schema_id: Option<SchemaId>,
1704 pub object_type: ObjectType,
1706}
1707
1708impl DefaultPrivilegeObject {
1709 pub fn new(
1711 role_id: RoleId,
1712 database_id: Option<DatabaseId>,
1713 schema_id: Option<SchemaId>,
1714 object_type: ObjectType,
1715 ) -> DefaultPrivilegeObject {
1716 DefaultPrivilegeObject {
1717 role_id,
1718 database_id,
1719 schema_id,
1720 object_type,
1721 }
1722 }
1723}
1724
1725impl std::fmt::Display for DefaultPrivilegeObject {
1726 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1727 write!(f, "{self:?}")
1729 }
1730}
1731
1732#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1734pub struct DefaultPrivilegeAclItem {
1735 pub grantee: RoleId,
1737 pub acl_mode: AclMode,
1739}
1740
1741impl DefaultPrivilegeAclItem {
1742 pub fn new(grantee: RoleId, acl_mode: AclMode) -> DefaultPrivilegeAclItem {
1744 DefaultPrivilegeAclItem { grantee, acl_mode }
1745 }
1746
1747 pub fn mz_acl_item(self, grantor: RoleId) -> MzAclItem {
1749 MzAclItem {
1750 grantee: self.grantee,
1751 grantor,
1752 acl_mode: self.acl_mode,
1753 }
1754 }
1755}
1756
1757#[derive(Debug, Clone)]
1763pub struct BuiltinsConfig {
1764 pub include_continual_tasks: bool,
1766}
1767
1768#[cfg(test)]
1769mod tests {
1770 use super::{CloudProvider, EnvironmentId, InvalidEnvironmentIdError};
1771
1772 #[mz_ore::test]
1773 fn test_environment_id() {
1774 for (input, expected) in [
1775 (
1776 "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1777 Ok(EnvironmentId {
1778 cloud_provider: CloudProvider::Local,
1779 cloud_provider_region: "az1".into(),
1780 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1781 ordinal: 452,
1782 }),
1783 ),
1784 (
1785 "aws-us-east-1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1786 Ok(EnvironmentId {
1787 cloud_provider: CloudProvider::Aws,
1788 cloud_provider_region: "us-east-1".into(),
1789 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1790 ordinal: 0,
1791 }),
1792 ),
1793 (
1794 "gcp-us-central1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1795 Ok(EnvironmentId {
1796 cloud_provider: CloudProvider::Gcp,
1797 cloud_provider_region: "us-central1".into(),
1798 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1799 ordinal: 0,
1800 }),
1801 ),
1802 (
1803 "azure-australiaeast-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1804 Ok(EnvironmentId {
1805 cloud_provider: CloudProvider::Azure,
1806 cloud_provider_region: "australiaeast".into(),
1807 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1808 ordinal: 0,
1809 }),
1810 ),
1811 (
1812 "generic-moon-station-11-darkside-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1813 Ok(EnvironmentId {
1814 cloud_provider: CloudProvider::Generic,
1815 cloud_provider_region: "moon-station-11-darkside".into(),
1816 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1817 ordinal: 0,
1818 }),
1819 ),
1820 ("", Err(InvalidEnvironmentIdError)),
1821 (
1822 "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-123456789",
1823 Err(InvalidEnvironmentIdError),
1824 ),
1825 (
1826 "local-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1827 Err(InvalidEnvironmentIdError),
1828 ),
1829 (
1830 "local-az1-1497a3b7-a455-4fc48752-b44a94b5f90a-452",
1831 Err(InvalidEnvironmentIdError),
1832 ),
1833 ] {
1834 let actual = input.parse();
1835 assert_eq!(expected, actual, "input = {}", input);
1836 if let Ok(actual) = actual {
1837 assert_eq!(input, actual.to_string(), "input = {}", input);
1838 }
1839 }
1840 }
1841}