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::num::NonZeroU32;
20use std::str::FromStr;
21use std::sync::LazyLock;
22use std::time::{Duration, Instant};
23
24use chrono::{DateTime, Utc};
25use mz_auth::password::Password;
26use mz_build_info::BuildInfo;
27use mz_cloud_provider::{CloudProvider, InvalidCloudProviderError};
28use mz_controller_types::{ClusterId, ReplicaId};
29use mz_expr::MirScalarExpr;
30use mz_ore::now::{EpochMillis, NowFn};
31use mz_ore::str::StrExt;
32use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap};
33use mz_repr::explain::ExprHumanizer;
34use mz_repr::network_policy_id::NetworkPolicyId;
35use mz_repr::role_id::RoleId;
36use mz_repr::{
37 CatalogItemId, ColumnName, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector,
38};
39use mz_sql_parser::ast::{Expr, QualifiedReplica, UnresolvedItemName};
40use mz_storage_types::connections::inline::{ConnectionResolver, ReferencedConnection};
41use mz_storage_types::connections::{Connection, ConnectionContext};
42use mz_storage_types::sources::{SourceDesc, SourceExportDataConfig, SourceExportDetails};
43use proptest_derive::Arbitrary;
44use regex::Regex;
45use serde::{Deserialize, Serialize};
46use uuid::Uuid;
47
48use crate::func::Func;
49use crate::names::{
50 Aug, CommentObjectId, DatabaseId, FullItemName, FullSchemaName, ObjectId, PartialItemName,
51 QualifiedItemName, QualifiedSchemaName, ResolvedDatabaseSpecifier, ResolvedIds, SchemaId,
52 SchemaSpecifier, SystemObjectId,
53};
54use crate::plan::statement::StatementDesc;
55use crate::plan::statement::ddl::PlannedRoleAttributes;
56use crate::plan::{ClusterSchedule, CreateClusterPlan, PlanError, PlanNotice, query};
57use crate::session::vars::{OwnedVarInput, SystemVars};
58
59pub trait SessionCatalog: fmt::Debug + ExprHumanizer + Send + Sync + ConnectionResolver {
88 fn active_role_id(&self) -> &RoleId;
90
91 fn active_database_name(&self) -> Option<&str> {
93 self.active_database()
94 .map(|id| self.get_database(id))
95 .map(|db| db.name())
96 }
97
98 fn active_database(&self) -> Option<&DatabaseId>;
100
101 fn active_cluster(&self) -> &str;
103
104 fn search_path(&self) -> &[(ResolvedDatabaseSpecifier, SchemaSpecifier)];
106
107 fn get_prepared_statement_desc(&self, name: &str) -> Option<&StatementDesc>;
110
111 fn get_portal_desc_unverified(&self, portal_name: &str) -> Option<&StatementDesc>;
115
116 fn resolve_database(&self, database_name: &str) -> Result<&dyn CatalogDatabase, CatalogError>;
121
122 fn get_database(&self, id: &DatabaseId) -> &dyn CatalogDatabase;
126
127 fn get_databases(&self) -> Vec<&dyn CatalogDatabase>;
129
130 fn resolve_schema(
135 &self,
136 database_name: Option<&str>,
137 schema_name: &str,
138 ) -> Result<&dyn CatalogSchema, CatalogError>;
139
140 fn resolve_schema_in_database(
145 &self,
146 database_spec: &ResolvedDatabaseSpecifier,
147 schema_name: &str,
148 ) -> Result<&dyn CatalogSchema, CatalogError>;
149
150 fn get_schema(
154 &self,
155 database_spec: &ResolvedDatabaseSpecifier,
156 schema_spec: &SchemaSpecifier,
157 ) -> &dyn CatalogSchema;
158
159 fn get_schemas(&self) -> Vec<&dyn CatalogSchema>;
161
162 fn get_mz_internal_schema_id(&self) -> SchemaId;
164
165 fn get_mz_unsafe_schema_id(&self) -> SchemaId;
167
168 fn is_system_schema_specifier(&self, schema: SchemaSpecifier) -> bool;
170
171 fn resolve_role(&self, role_name: &str) -> Result<&dyn CatalogRole, CatalogError>;
173
174 fn resolve_network_policy(
176 &self,
177 network_policy_name: &str,
178 ) -> Result<&dyn CatalogNetworkPolicy, CatalogError>;
179
180 fn try_get_role(&self, id: &RoleId) -> Option<&dyn CatalogRole>;
182
183 fn get_role(&self, id: &RoleId) -> &dyn CatalogRole;
187
188 fn get_roles(&self) -> Vec<&dyn CatalogRole>;
190
191 fn mz_system_role_id(&self) -> RoleId;
193
194 fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId>;
196
197 fn get_network_policy(&self, id: &NetworkPolicyId) -> &dyn CatalogNetworkPolicy;
202
203 fn get_network_policies(&self) -> Vec<&dyn CatalogNetworkPolicy>;
205
206 fn resolve_cluster<'a, 'b>(
209 &'a self,
210 cluster_name: Option<&'b str>,
211 ) -> Result<&'a dyn CatalogCluster<'a>, CatalogError>;
212
213 fn resolve_cluster_replica<'a, 'b>(
215 &'a self,
216 cluster_replica_name: &'b QualifiedReplica,
217 ) -> Result<&'a dyn CatalogClusterReplica<'a>, CatalogError>;
218
219 fn resolve_item(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
234
235 fn resolve_function(
238 &self,
239 item_name: &PartialItemName,
240 ) -> Result<&dyn CatalogItem, CatalogError>;
241
242 fn resolve_type(&self, item_name: &PartialItemName) -> Result<&dyn CatalogItem, CatalogError>;
245
246 fn resolve_item_or_type(
248 &self,
249 name: &PartialItemName,
250 ) -> Result<&dyn CatalogItem, CatalogError> {
251 if let Ok(ty) = self.resolve_type(name) {
252 return Ok(ty);
253 }
254 self.resolve_item(name)
255 }
256
257 fn get_system_type(&self, name: &str) -> &dyn CatalogItem;
263
264 fn try_get_item(&self, id: &CatalogItemId) -> Option<&dyn CatalogItem>;
266
267 fn try_get_item_by_global_id<'a>(
272 &'a self,
273 id: &GlobalId,
274 ) -> Option<Box<dyn CatalogCollectionItem + 'a>>;
275
276 fn get_item(&self, id: &CatalogItemId) -> &dyn CatalogItem;
280
281 fn get_item_by_global_id<'a>(&'a self, id: &GlobalId) -> Box<dyn CatalogCollectionItem + 'a>;
287
288 fn get_items(&self) -> Vec<&dyn CatalogItem>;
290
291 fn get_item_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
293
294 fn get_type_by_name(&self, name: &QualifiedItemName) -> Option<&dyn CatalogItem>;
296
297 fn get_cluster(&self, id: ClusterId) -> &dyn CatalogCluster<'_>;
299
300 fn get_clusters(&self) -> Vec<&dyn CatalogCluster<'_>>;
302
303 fn get_cluster_replica(
305 &self,
306 cluster_id: ClusterId,
307 replica_id: ReplicaId,
308 ) -> &dyn CatalogClusterReplica<'_>;
309
310 fn get_cluster_replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>>;
312
313 fn get_system_privileges(&self) -> &PrivilegeMap;
315
316 fn get_default_privileges(
318 &self,
319 ) -> Vec<(&DefaultPrivilegeObject, Vec<&DefaultPrivilegeAclItem>)>;
320
321 fn find_available_name(&self, name: QualifiedItemName) -> QualifiedItemName;
325
326 fn resolve_full_name(&self, name: &QualifiedItemName) -> FullItemName;
328
329 fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName;
332
333 fn resolve_item_id(&self, global_id: &GlobalId) -> CatalogItemId;
335
336 fn resolve_global_id(
338 &self,
339 item_id: &CatalogItemId,
340 version: RelationVersionSelector,
341 ) -> GlobalId;
342
343 fn config(&self) -> &CatalogConfig;
345
346 fn now(&self) -> EpochMillis;
350
351 fn aws_privatelink_availability_zones(&self) -> Option<BTreeSet<String>>;
353
354 fn system_vars(&self) -> &SystemVars;
356
357 fn system_vars_mut(&mut self) -> &mut SystemVars;
364
365 fn get_owner_id(&self, id: &ObjectId) -> Option<RoleId>;
367
368 fn get_privileges(&self, id: &SystemObjectId) -> Option<&PrivilegeMap>;
370
371 fn object_dependents(&self, ids: &Vec<ObjectId>) -> Vec<ObjectId>;
377
378 fn item_dependents(&self, id: CatalogItemId) -> Vec<ObjectId>;
384
385 fn all_object_privileges(&self, object_type: SystemObjectType) -> AclMode;
387
388 fn get_object_type(&self, object_id: &ObjectId) -> ObjectType;
390
391 fn get_system_object_type(&self, id: &SystemObjectId) -> SystemObjectType;
393
394 fn minimal_qualification(&self, qualified_name: &QualifiedItemName) -> PartialItemName;
397
398 fn add_notice(&self, notice: PlanNotice);
401
402 fn get_item_comments(&self, id: &CatalogItemId) -> Option<&BTreeMap<Option<usize>, String>>;
404
405 fn is_cluster_size_cc(&self, size: &str) -> bool;
408}
409
410#[derive(Debug, Clone)]
412pub struct CatalogConfig {
413 pub start_time: DateTime<Utc>,
415 pub start_instant: Instant,
417 pub nonce: u64,
422 pub environment_id: EnvironmentId,
424 pub session_id: Uuid,
426 pub build_info: &'static BuildInfo,
428 pub timestamp_interval: Duration,
430 pub now: NowFn,
433 pub connection_context: ConnectionContext,
435 pub builtins_cfg: BuiltinsConfig,
437 pub helm_chart_version: Option<String>,
439}
440
441pub trait CatalogDatabase {
443 fn name(&self) -> &str;
445
446 fn id(&self) -> DatabaseId;
448
449 fn has_schemas(&self) -> bool;
451
452 fn schema_ids(&self) -> &BTreeMap<String, SchemaId>;
455
456 fn schemas(&self) -> Vec<&dyn CatalogSchema>;
458
459 fn owner_id(&self) -> RoleId;
461
462 fn privileges(&self) -> &PrivilegeMap;
464}
465
466pub trait CatalogSchema {
468 fn database(&self) -> &ResolvedDatabaseSpecifier;
470
471 fn name(&self) -> &QualifiedSchemaName;
473
474 fn id(&self) -> &SchemaSpecifier;
476
477 fn has_items(&self) -> bool;
479
480 fn item_ids(&self) -> Box<dyn Iterator<Item = CatalogItemId> + '_>;
482
483 fn owner_id(&self) -> RoleId;
485
486 fn privileges(&self) -> &PrivilegeMap;
488}
489
490#[derive(Debug, Clone, Eq, PartialEq, Arbitrary)]
492pub struct PasswordConfig {
493 pub password: Password,
495 pub scram_iterations: NonZeroU32,
497}
498
499#[derive(Debug, Clone, Eq, PartialEq, Arbitrary)]
501pub enum PasswordAction {
502 Set(PasswordConfig),
504 Clear,
506 NoChange,
508}
509
510#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Arbitrary)]
515pub struct RoleAttributesRaw {
516 pub inherit: bool,
518 pub password: Option<Password>,
520 pub scram_iterations: Option<NonZeroU32>,
522 pub superuser: Option<bool>,
524 pub login: Option<bool>,
526 _private: (),
528}
529
530#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialEq, Ord, PartialOrd, Arbitrary)]
532pub struct RoleAttributes {
533 pub inherit: bool,
535 pub superuser: Option<bool>,
537 pub login: Option<bool>,
539 _private: (),
541}
542
543impl RoleAttributesRaw {
544 pub const fn new() -> RoleAttributesRaw {
546 RoleAttributesRaw {
547 inherit: true,
548 password: None,
549 scram_iterations: None,
550 superuser: None,
551 login: None,
552 _private: (),
553 }
554 }
555
556 pub const fn with_all(mut self) -> RoleAttributesRaw {
558 self.inherit = true;
559 self.superuser = Some(true);
560 self.login = Some(true);
561 self
562 }
563}
564
565impl RoleAttributes {
566 pub const fn new() -> RoleAttributes {
568 RoleAttributes {
569 inherit: true,
570 superuser: None,
571 login: None,
572 _private: (),
573 }
574 }
575
576 pub const fn with_all(mut self) -> RoleAttributes {
578 self.inherit = true;
579 self.superuser = Some(true);
580 self.login = Some(true);
581 self
582 }
583
584 pub const fn is_inherit(&self) -> bool {
586 self.inherit
587 }
588}
589
590impl From<RoleAttributesRaw> for RoleAttributes {
591 fn from(
592 RoleAttributesRaw {
593 inherit,
594 superuser,
595 login,
596 ..
597 }: RoleAttributesRaw,
598 ) -> RoleAttributes {
599 RoleAttributes {
600 inherit,
601 superuser,
602 login,
603 _private: (),
604 }
605 }
606}
607
608impl From<RoleAttributes> for RoleAttributesRaw {
609 fn from(
610 RoleAttributes {
611 inherit,
612 superuser,
613 login,
614 ..
615 }: RoleAttributes,
616 ) -> RoleAttributesRaw {
617 RoleAttributesRaw {
618 inherit,
619 password: None,
620 scram_iterations: None,
621 superuser,
622 login,
623 _private: (),
624 }
625 }
626}
627
628impl From<PlannedRoleAttributes> for RoleAttributesRaw {
629 fn from(
630 PlannedRoleAttributes {
631 inherit,
632 password,
633 scram_iterations,
634 superuser,
635 login,
636 ..
637 }: PlannedRoleAttributes,
638 ) -> RoleAttributesRaw {
639 let default_attributes = RoleAttributesRaw::new();
640 RoleAttributesRaw {
641 inherit: inherit.unwrap_or(default_attributes.inherit),
642 password,
643 scram_iterations,
644 superuser,
645 login,
646 _private: (),
647 }
648 }
649}
650
651#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
653pub struct RoleVars {
654 pub map: BTreeMap<String, OwnedVarInput>,
656}
657
658pub trait CatalogRole {
660 fn name(&self) -> &str;
662
663 fn id(&self) -> RoleId;
665
666 fn membership(&self) -> &BTreeMap<RoleId, RoleId>;
671
672 fn attributes(&self) -> &RoleAttributes;
674
675 fn vars(&self) -> &BTreeMap<String, OwnedVarInput>;
677}
678
679pub trait CatalogNetworkPolicy {
681 fn name(&self) -> &str;
683
684 fn id(&self) -> NetworkPolicyId;
686
687 fn owner_id(&self) -> RoleId;
689
690 fn privileges(&self) -> &PrivilegeMap;
692}
693
694pub trait CatalogCluster<'a> {
696 fn name(&self) -> &str;
698
699 fn id(&self) -> ClusterId;
701
702 fn bound_objects(&self) -> &BTreeSet<CatalogItemId>;
704
705 fn replica_ids(&self) -> &BTreeMap<String, ReplicaId>;
708
709 fn replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>>;
711
712 fn replica(&self, id: ReplicaId) -> &dyn CatalogClusterReplica<'_>;
714
715 fn owner_id(&self) -> RoleId;
717
718 fn privileges(&self) -> &PrivilegeMap;
720
721 fn is_managed(&self) -> bool;
723
724 fn managed_size(&self) -> Option<&str>;
726
727 fn schedule(&self) -> Option<&ClusterSchedule>;
729
730 fn try_to_plan(&self) -> Result<CreateClusterPlan, PlanError>;
733}
734
735pub trait CatalogClusterReplica<'a>: Debug {
737 fn name(&self) -> &str;
739
740 fn cluster_id(&self) -> ClusterId;
742
743 fn replica_id(&self) -> ReplicaId;
745
746 fn owner_id(&self) -> RoleId;
748
749 fn internal(&self) -> bool;
751}
752
753pub trait CatalogItem {
758 fn name(&self) -> &QualifiedItemName;
760
761 fn id(&self) -> CatalogItemId;
763
764 fn global_ids(&self) -> Box<dyn Iterator<Item = GlobalId> + '_>;
766
767 fn oid(&self) -> u32;
769
770 fn func(&self) -> Result<&'static Func, CatalogError>;
775
776 fn source_desc(&self) -> Result<Option<&SourceDesc<ReferencedConnection>>, CatalogError>;
781
782 fn connection(&self) -> Result<Connection<ReferencedConnection>, CatalogError>;
786
787 fn item_type(&self) -> CatalogItemType;
789
790 fn create_sql(&self) -> &str;
793
794 fn references(&self) -> &ResolvedIds;
797
798 fn uses(&self) -> BTreeSet<CatalogItemId>;
801
802 fn referenced_by(&self) -> &[CatalogItemId];
804
805 fn used_by(&self) -> &[CatalogItemId];
807
808 fn subsource_details(
811 &self,
812 ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)>;
813
814 fn source_export_details(
817 &self,
818 ) -> Option<(
819 CatalogItemId,
820 &UnresolvedItemName,
821 &SourceExportDetails,
822 &SourceExportDataConfig<ReferencedConnection>,
823 )>;
824
825 fn is_progress_source(&self) -> bool;
827
828 fn progress_id(&self) -> Option<CatalogItemId>;
830
831 fn index_details(&self) -> Option<(&[MirScalarExpr], GlobalId)>;
834
835 fn writable_table_details(&self) -> Option<&[Expr<Aug>]>;
838
839 fn replacement_target(&self) -> Option<CatalogItemId>;
841
842 fn type_details(&self) -> Option<&CatalogTypeDetails<IdReference>>;
845
846 fn owner_id(&self) -> RoleId;
848
849 fn privileges(&self) -> &PrivilegeMap;
851
852 fn cluster_id(&self) -> Option<ClusterId>;
854
855 fn at_version(&self, version: RelationVersionSelector) -> Box<dyn CatalogCollectionItem>;
858
859 fn latest_version(&self) -> Option<RelationVersion>;
861}
862
863pub trait CatalogCollectionItem: CatalogItem + Send + Sync {
866 fn relation_desc(&self) -> Option<Cow<'_, RelationDesc>>;
871
872 fn global_id(&self) -> GlobalId;
874}
875
876#[derive(Debug, Deserialize, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
878pub enum CatalogItemType {
879 Table,
881 Source,
883 Sink,
885 View,
887 MaterializedView,
889 Index,
891 Type,
893 Func,
895 Secret,
897 Connection,
899 ContinualTask,
901}
902
903impl CatalogItemType {
904 pub fn conflicts_with_type(&self) -> bool {
923 match self {
924 CatalogItemType::Table => true,
925 CatalogItemType::Source => true,
926 CatalogItemType::View => true,
927 CatalogItemType::MaterializedView => true,
928 CatalogItemType::Index => true,
929 CatalogItemType::Type => true,
930 CatalogItemType::Sink => false,
931 CatalogItemType::Func => false,
932 CatalogItemType::Secret => false,
933 CatalogItemType::Connection => false,
934 CatalogItemType::ContinualTask => true,
935 }
936 }
937}
938
939impl fmt::Display for CatalogItemType {
940 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
941 match self {
942 CatalogItemType::Table => f.write_str("table"),
943 CatalogItemType::Source => f.write_str("source"),
944 CatalogItemType::Sink => f.write_str("sink"),
945 CatalogItemType::View => f.write_str("view"),
946 CatalogItemType::MaterializedView => f.write_str("materialized view"),
947 CatalogItemType::Index => f.write_str("index"),
948 CatalogItemType::Type => f.write_str("type"),
949 CatalogItemType::Func => f.write_str("func"),
950 CatalogItemType::Secret => f.write_str("secret"),
951 CatalogItemType::Connection => f.write_str("connection"),
952 CatalogItemType::ContinualTask => f.write_str("continual task"),
953 }
954 }
955}
956
957impl From<CatalogItemType> for ObjectType {
958 fn from(value: CatalogItemType) -> Self {
959 match value {
960 CatalogItemType::Table => ObjectType::Table,
961 CatalogItemType::Source => ObjectType::Source,
962 CatalogItemType::Sink => ObjectType::Sink,
963 CatalogItemType::View => ObjectType::View,
964 CatalogItemType::MaterializedView => ObjectType::MaterializedView,
965 CatalogItemType::Index => ObjectType::Index,
966 CatalogItemType::Type => ObjectType::Type,
967 CatalogItemType::Func => ObjectType::Func,
968 CatalogItemType::Secret => ObjectType::Secret,
969 CatalogItemType::Connection => ObjectType::Connection,
970 CatalogItemType::ContinualTask => ObjectType::ContinualTask,
971 }
972 }
973}
974
975impl From<CatalogItemType> for mz_audit_log::ObjectType {
976 fn from(value: CatalogItemType) -> Self {
977 match value {
978 CatalogItemType::Table => mz_audit_log::ObjectType::Table,
979 CatalogItemType::Source => mz_audit_log::ObjectType::Source,
980 CatalogItemType::View => mz_audit_log::ObjectType::View,
981 CatalogItemType::MaterializedView => mz_audit_log::ObjectType::MaterializedView,
982 CatalogItemType::Index => mz_audit_log::ObjectType::Index,
983 CatalogItemType::Type => mz_audit_log::ObjectType::Type,
984 CatalogItemType::Sink => mz_audit_log::ObjectType::Sink,
985 CatalogItemType::Func => mz_audit_log::ObjectType::Func,
986 CatalogItemType::Secret => mz_audit_log::ObjectType::Secret,
987 CatalogItemType::Connection => mz_audit_log::ObjectType::Connection,
988 CatalogItemType::ContinualTask => mz_audit_log::ObjectType::ContinualTask,
989 }
990 }
991}
992
993#[derive(Clone, Debug, Eq, PartialEq)]
995pub struct CatalogTypeDetails<T: TypeReference> {
996 pub array_id: Option<CatalogItemId>,
998 pub typ: CatalogType<T>,
1000 pub pg_metadata: Option<CatalogTypePgMetadata>,
1002}
1003
1004#[derive(Clone, Debug, Eq, PartialEq)]
1006pub struct CatalogTypePgMetadata {
1007 pub typinput_oid: u32,
1009 pub typreceive_oid: u32,
1011}
1012
1013pub trait TypeReference {
1015 type Reference: Clone + Debug + Eq + PartialEq;
1017}
1018
1019#[derive(Clone, Debug, Eq, PartialEq)]
1021pub struct NameReference;
1022
1023impl TypeReference for NameReference {
1024 type Reference = &'static str;
1025}
1026
1027#[derive(Clone, Debug, Eq, PartialEq)]
1029pub struct IdReference;
1030
1031impl TypeReference for IdReference {
1032 type Reference = CatalogItemId;
1033}
1034
1035#[allow(missing_docs)]
1041#[derive(Clone, Debug, Eq, PartialEq)]
1042pub enum CatalogType<T: TypeReference> {
1043 AclItem,
1044 Array {
1045 element_reference: T::Reference,
1046 },
1047 Bool,
1048 Bytes,
1049 Char,
1050 Date,
1051 Float32,
1052 Float64,
1053 Int16,
1054 Int32,
1055 Int64,
1056 UInt16,
1057 UInt32,
1058 UInt64,
1059 MzTimestamp,
1060 Interval,
1061 Jsonb,
1062 List {
1063 element_reference: T::Reference,
1064 element_modifiers: Vec<i64>,
1065 },
1066 Map {
1067 key_reference: T::Reference,
1068 key_modifiers: Vec<i64>,
1069 value_reference: T::Reference,
1070 value_modifiers: Vec<i64>,
1071 },
1072 Numeric,
1073 Oid,
1074 PgLegacyChar,
1075 PgLegacyName,
1076 Pseudo,
1077 Range {
1078 element_reference: T::Reference,
1079 },
1080 Record {
1081 fields: Vec<CatalogRecordField<T>>,
1082 },
1083 RegClass,
1084 RegProc,
1085 RegType,
1086 String,
1087 Time,
1088 Timestamp,
1089 TimestampTz,
1090 Uuid,
1091 VarChar,
1092 Int2Vector,
1093 MzAclItem,
1094}
1095
1096impl CatalogType<IdReference> {
1097 pub fn desc(&self, catalog: &dyn SessionCatalog) -> Result<Option<RelationDesc>, PlanError> {
1100 match &self {
1101 CatalogType::Record { fields } => {
1102 let mut desc = RelationDesc::builder();
1103 for f in fields {
1104 let name = f.name.clone();
1105 let ty = query::scalar_type_from_catalog(
1106 catalog,
1107 f.type_reference,
1108 &f.type_modifiers,
1109 )?;
1110 let ty = ty.nullable(true);
1113 desc = desc.with_column(name, ty);
1114 }
1115 Ok(Some(desc.finish()))
1116 }
1117 _ => Ok(None),
1118 }
1119 }
1120}
1121
1122#[derive(Clone, Debug, Eq, PartialEq)]
1124pub struct CatalogRecordField<T: TypeReference> {
1125 pub name: ColumnName,
1127 pub type_reference: T::Reference,
1129 pub type_modifiers: Vec<i64>,
1131}
1132
1133#[derive(Clone, Debug, Eq, PartialEq)]
1134pub enum TypeCategory {
1142 Array,
1144 BitString,
1146 Boolean,
1148 Composite,
1150 DateTime,
1152 Enum,
1154 Geometric,
1156 List,
1158 NetworkAddress,
1160 Numeric,
1162 Pseudo,
1164 Range,
1166 String,
1168 Timespan,
1170 UserDefined,
1172 Unknown,
1174}
1175
1176impl fmt::Display for TypeCategory {
1177 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1178 f.write_str(match self {
1179 TypeCategory::Array => "array",
1180 TypeCategory::BitString => "bit-string",
1181 TypeCategory::Boolean => "boolean",
1182 TypeCategory::Composite => "composite",
1183 TypeCategory::DateTime => "date-time",
1184 TypeCategory::Enum => "enum",
1185 TypeCategory::Geometric => "geometric",
1186 TypeCategory::List => "list",
1187 TypeCategory::NetworkAddress => "network-address",
1188 TypeCategory::Numeric => "numeric",
1189 TypeCategory::Pseudo => "pseudo",
1190 TypeCategory::Range => "range",
1191 TypeCategory::String => "string",
1192 TypeCategory::Timespan => "timespan",
1193 TypeCategory::UserDefined => "user-defined",
1194 TypeCategory::Unknown => "unknown",
1195 })
1196 }
1197}
1198
1199#[derive(Debug, Clone, PartialEq)]
1223pub struct EnvironmentId {
1224 cloud_provider: CloudProvider,
1225 cloud_provider_region: String,
1226 organization_id: Uuid,
1227 ordinal: u64,
1228}
1229
1230impl EnvironmentId {
1231 pub fn for_tests() -> EnvironmentId {
1233 EnvironmentId {
1234 cloud_provider: CloudProvider::Local,
1235 cloud_provider_region: "az1".into(),
1236 organization_id: Uuid::new_v4(),
1237 ordinal: 0,
1238 }
1239 }
1240
1241 pub fn cloud_provider(&self) -> &CloudProvider {
1243 &self.cloud_provider
1244 }
1245
1246 pub fn cloud_provider_region(&self) -> &str {
1248 &self.cloud_provider_region
1249 }
1250
1251 pub fn region(&self) -> String {
1256 format!("{}/{}", self.cloud_provider, self.cloud_provider_region)
1257 }
1258
1259 pub fn organization_id(&self) -> Uuid {
1261 self.organization_id
1262 }
1263
1264 pub fn ordinal(&self) -> u64 {
1266 self.ordinal
1267 }
1268}
1269
1270impl fmt::Display for EnvironmentId {
1276 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1277 write!(
1278 f,
1279 "{}-{}-{}-{}",
1280 self.cloud_provider, self.cloud_provider_region, self.organization_id, self.ordinal
1281 )
1282 }
1283}
1284
1285impl FromStr for EnvironmentId {
1286 type Err = InvalidEnvironmentIdError;
1287
1288 fn from_str(s: &str) -> Result<EnvironmentId, InvalidEnvironmentIdError> {
1289 static MATCHER: LazyLock<Regex> = LazyLock::new(|| {
1290 Regex::new(
1291 "^(?P<cloud_provider>[[:alnum:]]+)-\
1292 (?P<cloud_provider_region>[[:alnum:]\\-]+)-\
1293 (?P<organization_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-\
1294 (?P<ordinal>\\d{1,8})$"
1295 ).unwrap()
1296 });
1297 let captures = MATCHER.captures(s).ok_or(InvalidEnvironmentIdError)?;
1298 Ok(EnvironmentId {
1299 cloud_provider: CloudProvider::from_str(&captures["cloud_provider"])?,
1300 cloud_provider_region: captures["cloud_provider_region"].into(),
1301 organization_id: captures["organization_id"]
1302 .parse()
1303 .map_err(|_| InvalidEnvironmentIdError)?,
1304 ordinal: captures["ordinal"]
1305 .parse()
1306 .map_err(|_| InvalidEnvironmentIdError)?,
1307 })
1308 }
1309}
1310
1311#[derive(Debug, Clone, PartialEq)]
1313pub struct InvalidEnvironmentIdError;
1314
1315impl fmt::Display for InvalidEnvironmentIdError {
1316 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1317 f.write_str("invalid environment ID")
1318 }
1319}
1320
1321impl Error for InvalidEnvironmentIdError {}
1322
1323impl From<InvalidCloudProviderError> for InvalidEnvironmentIdError {
1324 fn from(_: InvalidCloudProviderError) -> Self {
1325 InvalidEnvironmentIdError
1326 }
1327}
1328
1329#[derive(Clone, Debug, Eq, PartialEq)]
1331pub enum CatalogError {
1332 UnknownDatabase(String),
1334 DatabaseAlreadyExists(String),
1336 UnknownSchema(String),
1338 SchemaAlreadyExists(String),
1340 UnknownRole(String),
1342 RoleAlreadyExists(String),
1344 NetworkPolicyAlreadyExists(String),
1346 UnknownCluster(String),
1348 UnexpectedBuiltinCluster(String),
1350 UnexpectedBuiltinClusterType(String),
1352 ClusterAlreadyExists(String),
1354 UnknownClusterReplica(String),
1356 UnknownClusterReplicaSize(String),
1358 DuplicateReplica(String, String),
1360 UnknownItem(String),
1362 ItemAlreadyExists(CatalogItemId, String),
1364 UnknownFunction {
1366 name: String,
1368 alternative: Option<String>,
1370 },
1371 UnknownType {
1373 name: String,
1375 },
1376 UnknownConnection(String),
1378 UnknownNetworkPolicy(String),
1380 UnexpectedType {
1382 name: String,
1384 actual_type: CatalogItemType,
1386 expected_type: CatalogItemType,
1388 },
1389 IdExhaustion,
1391 OidExhaustion,
1393 TimelineAlreadyExists(String),
1395 IdAllocatorAlreadyExists(String),
1397 ConfigAlreadyExists(String),
1399 FailedBuiltinSchemaMigration(String),
1401 StorageCollectionMetadataAlreadyExists(GlobalId),
1403}
1404
1405impl fmt::Display for CatalogError {
1406 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1407 match self {
1408 Self::UnknownDatabase(name) => write!(f, "unknown database '{}'", name),
1409 Self::DatabaseAlreadyExists(name) => write!(f, "database '{name}' already exists"),
1410 Self::UnknownFunction { name, .. } => write!(f, "function \"{}\" does not exist", name),
1411 Self::UnknownType { name, .. } => write!(f, "type \"{}\" does not exist", name),
1412 Self::UnknownConnection(name) => write!(f, "connection \"{}\" does not exist", name),
1413 Self::UnknownSchema(name) => write!(f, "unknown schema '{}'", name),
1414 Self::SchemaAlreadyExists(name) => write!(f, "schema '{name}' already exists"),
1415 Self::UnknownRole(name) => write!(f, "unknown role '{}'", name),
1416 Self::RoleAlreadyExists(name) => write!(f, "role '{name}' already exists"),
1417 Self::NetworkPolicyAlreadyExists(name) => {
1418 write!(f, "network policy '{name}' already exists")
1419 }
1420 Self::UnknownCluster(name) => write!(f, "unknown cluster '{}'", name),
1421 Self::UnknownNetworkPolicy(name) => write!(f, "unknown network policy '{}'", name),
1422 Self::UnexpectedBuiltinCluster(name) => {
1423 write!(f, "Unexpected builtin cluster '{}'", name)
1424 }
1425 Self::UnexpectedBuiltinClusterType(name) => {
1426 write!(f, "Unexpected builtin cluster type'{}'", name)
1427 }
1428 Self::ClusterAlreadyExists(name) => write!(f, "cluster '{name}' already exists"),
1429 Self::UnknownClusterReplica(name) => {
1430 write!(f, "unknown cluster replica '{}'", name)
1431 }
1432 Self::UnknownClusterReplicaSize(name) => {
1433 write!(f, "unknown cluster replica size '{}'", name)
1434 }
1435 Self::DuplicateReplica(replica_name, cluster_name) => write!(
1436 f,
1437 "cannot create multiple replicas named '{replica_name}' on cluster '{cluster_name}'"
1438 ),
1439 Self::UnknownItem(name) => write!(f, "unknown catalog item '{}'", name),
1440 Self::ItemAlreadyExists(_gid, name) => {
1441 write!(f, "catalog item '{name}' already exists")
1442 }
1443 Self::UnexpectedType {
1444 name,
1445 actual_type,
1446 expected_type,
1447 } => {
1448 write!(f, "\"{name}\" is a {actual_type} not a {expected_type}")
1449 }
1450 Self::IdExhaustion => write!(f, "id counter overflows i64"),
1451 Self::OidExhaustion => write!(f, "oid counter overflows u32"),
1452 Self::TimelineAlreadyExists(name) => write!(f, "timeline '{name}' already exists"),
1453 Self::IdAllocatorAlreadyExists(name) => {
1454 write!(f, "ID allocator '{name}' already exists")
1455 }
1456 Self::ConfigAlreadyExists(key) => write!(f, "config '{key}' already exists"),
1457 Self::FailedBuiltinSchemaMigration(objects) => {
1458 write!(f, "failed to migrate schema of builtin objects: {objects}")
1459 }
1460 Self::StorageCollectionMetadataAlreadyExists(key) => {
1461 write!(f, "storage metadata for '{key}' already exists")
1462 }
1463 }
1464 }
1465}
1466
1467impl CatalogError {
1468 pub fn hint(&self) -> Option<String> {
1470 match self {
1471 CatalogError::UnknownFunction { alternative, .. } => {
1472 match alternative {
1473 None => Some("No function matches the given name and argument types. You might need to add explicit type casts.".into()),
1474 Some(alt) => Some(format!("Try using {alt}")),
1475 }
1476 }
1477 _ => None,
1478 }
1479 }
1480}
1481
1482impl Error for CatalogError {}
1483
1484#[allow(missing_docs)]
1486#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Deserialize, Serialize)]
1487pub enum ObjectType {
1489 Table,
1490 View,
1491 MaterializedView,
1492 Source,
1493 Sink,
1494 Index,
1495 Type,
1496 Role,
1497 Cluster,
1498 ClusterReplica,
1499 Secret,
1500 Connection,
1501 Database,
1502 Schema,
1503 Func,
1504 ContinualTask,
1505 NetworkPolicy,
1506}
1507
1508impl ObjectType {
1509 pub fn is_relation(&self) -> bool {
1511 match self {
1512 ObjectType::Table
1513 | ObjectType::View
1514 | ObjectType::MaterializedView
1515 | ObjectType::Source
1516 | ObjectType::ContinualTask => true,
1517 ObjectType::Sink
1518 | ObjectType::Index
1519 | ObjectType::Type
1520 | ObjectType::Secret
1521 | ObjectType::Connection
1522 | ObjectType::Func
1523 | ObjectType::Database
1524 | ObjectType::Schema
1525 | ObjectType::Cluster
1526 | ObjectType::ClusterReplica
1527 | ObjectType::Role
1528 | ObjectType::NetworkPolicy => false,
1529 }
1530 }
1531}
1532
1533impl From<mz_sql_parser::ast::ObjectType> for ObjectType {
1534 fn from(value: mz_sql_parser::ast::ObjectType) -> Self {
1535 match value {
1536 mz_sql_parser::ast::ObjectType::Table => ObjectType::Table,
1537 mz_sql_parser::ast::ObjectType::View => ObjectType::View,
1538 mz_sql_parser::ast::ObjectType::MaterializedView => ObjectType::MaterializedView,
1539 mz_sql_parser::ast::ObjectType::Source => ObjectType::Source,
1540 mz_sql_parser::ast::ObjectType::Subsource => ObjectType::Source,
1541 mz_sql_parser::ast::ObjectType::Sink => ObjectType::Sink,
1542 mz_sql_parser::ast::ObjectType::Index => ObjectType::Index,
1543 mz_sql_parser::ast::ObjectType::Type => ObjectType::Type,
1544 mz_sql_parser::ast::ObjectType::Role => ObjectType::Role,
1545 mz_sql_parser::ast::ObjectType::Cluster => ObjectType::Cluster,
1546 mz_sql_parser::ast::ObjectType::ClusterReplica => ObjectType::ClusterReplica,
1547 mz_sql_parser::ast::ObjectType::Secret => ObjectType::Secret,
1548 mz_sql_parser::ast::ObjectType::Connection => ObjectType::Connection,
1549 mz_sql_parser::ast::ObjectType::Database => ObjectType::Database,
1550 mz_sql_parser::ast::ObjectType::Schema => ObjectType::Schema,
1551 mz_sql_parser::ast::ObjectType::Func => ObjectType::Func,
1552 mz_sql_parser::ast::ObjectType::ContinualTask => ObjectType::ContinualTask,
1553 mz_sql_parser::ast::ObjectType::NetworkPolicy => ObjectType::NetworkPolicy,
1554 }
1555 }
1556}
1557
1558impl From<CommentObjectId> for ObjectType {
1559 fn from(value: CommentObjectId) -> ObjectType {
1560 match value {
1561 CommentObjectId::Table(_) => ObjectType::Table,
1562 CommentObjectId::View(_) => ObjectType::View,
1563 CommentObjectId::MaterializedView(_) => ObjectType::MaterializedView,
1564 CommentObjectId::Source(_) => ObjectType::Source,
1565 CommentObjectId::Sink(_) => ObjectType::Sink,
1566 CommentObjectId::Index(_) => ObjectType::Index,
1567 CommentObjectId::Func(_) => ObjectType::Func,
1568 CommentObjectId::Connection(_) => ObjectType::Connection,
1569 CommentObjectId::Type(_) => ObjectType::Type,
1570 CommentObjectId::Secret(_) => ObjectType::Secret,
1571 CommentObjectId::Role(_) => ObjectType::Role,
1572 CommentObjectId::Database(_) => ObjectType::Database,
1573 CommentObjectId::Schema(_) => ObjectType::Schema,
1574 CommentObjectId::Cluster(_) => ObjectType::Cluster,
1575 CommentObjectId::ClusterReplica(_) => ObjectType::ClusterReplica,
1576 CommentObjectId::ContinualTask(_) => ObjectType::ContinualTask,
1577 CommentObjectId::NetworkPolicy(_) => ObjectType::NetworkPolicy,
1578 }
1579 }
1580}
1581
1582impl Display for ObjectType {
1583 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1584 f.write_str(match self {
1585 ObjectType::Table => "TABLE",
1586 ObjectType::View => "VIEW",
1587 ObjectType::MaterializedView => "MATERIALIZED VIEW",
1588 ObjectType::Source => "SOURCE",
1589 ObjectType::Sink => "SINK",
1590 ObjectType::Index => "INDEX",
1591 ObjectType::Type => "TYPE",
1592 ObjectType::Role => "ROLE",
1593 ObjectType::Cluster => "CLUSTER",
1594 ObjectType::ClusterReplica => "CLUSTER REPLICA",
1595 ObjectType::Secret => "SECRET",
1596 ObjectType::Connection => "CONNECTION",
1597 ObjectType::Database => "DATABASE",
1598 ObjectType::Schema => "SCHEMA",
1599 ObjectType::Func => "FUNCTION",
1600 ObjectType::ContinualTask => "CONTINUAL TASK",
1601 ObjectType::NetworkPolicy => "NETWORK POLICY",
1602 })
1603 }
1604}
1605
1606#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Deserialize, Serialize)]
1607pub enum SystemObjectType {
1609 Object(ObjectType),
1611 System,
1613}
1614
1615impl SystemObjectType {
1616 pub fn is_relation(&self) -> bool {
1618 match self {
1619 SystemObjectType::Object(object_type) => object_type.is_relation(),
1620 SystemObjectType::System => false,
1621 }
1622 }
1623}
1624
1625impl Display for SystemObjectType {
1626 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1627 match self {
1628 SystemObjectType::Object(object_type) => std::fmt::Display::fmt(&object_type, f),
1629 SystemObjectType::System => f.write_str("SYSTEM"),
1630 }
1631 }
1632}
1633
1634#[derive(Debug, Clone, PartialEq, Eq)]
1636pub enum ErrorMessageObjectDescription {
1637 Object {
1639 object_type: ObjectType,
1641 object_name: Option<String>,
1643 },
1644 System,
1646}
1647
1648impl ErrorMessageObjectDescription {
1649 pub fn from_id(
1651 object_id: &ObjectId,
1652 catalog: &dyn SessionCatalog,
1653 ) -> ErrorMessageObjectDescription {
1654 let object_name = match object_id {
1655 ObjectId::Cluster(cluster_id) => catalog.get_cluster(*cluster_id).name().to_string(),
1656 ObjectId::ClusterReplica((cluster_id, replica_id)) => catalog
1657 .get_cluster_replica(*cluster_id, *replica_id)
1658 .name()
1659 .to_string(),
1660 ObjectId::Database(database_id) => catalog.get_database(database_id).name().to_string(),
1661 ObjectId::Schema((database_spec, schema_spec)) => {
1662 let name = catalog.get_schema(database_spec, schema_spec).name();
1663 catalog.resolve_full_schema_name(name).to_string()
1664 }
1665 ObjectId::Role(role_id) => catalog.get_role(role_id).name().to_string(),
1666 ObjectId::Item(id) => {
1667 let name = catalog.get_item(id).name();
1668 catalog.resolve_full_name(name).to_string()
1669 }
1670 ObjectId::NetworkPolicy(network_policy_id) => catalog
1671 .get_network_policy(network_policy_id)
1672 .name()
1673 .to_string(),
1674 };
1675 ErrorMessageObjectDescription::Object {
1676 object_type: catalog.get_object_type(object_id),
1677 object_name: Some(object_name),
1678 }
1679 }
1680
1681 pub fn from_sys_id(
1683 object_id: &SystemObjectId,
1684 catalog: &dyn SessionCatalog,
1685 ) -> ErrorMessageObjectDescription {
1686 match object_id {
1687 SystemObjectId::Object(object_id) => {
1688 ErrorMessageObjectDescription::from_id(object_id, catalog)
1689 }
1690 SystemObjectId::System => ErrorMessageObjectDescription::System,
1691 }
1692 }
1693
1694 pub fn from_object_type(object_type: SystemObjectType) -> ErrorMessageObjectDescription {
1696 match object_type {
1697 SystemObjectType::Object(object_type) => ErrorMessageObjectDescription::Object {
1698 object_type,
1699 object_name: None,
1700 },
1701 SystemObjectType::System => ErrorMessageObjectDescription::System,
1702 }
1703 }
1704}
1705
1706impl Display for ErrorMessageObjectDescription {
1707 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1708 match self {
1709 ErrorMessageObjectDescription::Object {
1710 object_type,
1711 object_name,
1712 } => {
1713 let object_name = object_name
1714 .as_ref()
1715 .map(|object_name| format!(" {}", object_name.quoted()))
1716 .unwrap_or_else(|| "".to_string());
1717 write!(f, "{object_type}{object_name}")
1718 }
1719 ErrorMessageObjectDescription::System => f.write_str("SYSTEM"),
1720 }
1721 }
1722}
1723
1724#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
1725#[serde(into = "BTreeMap<String, RoleId>")]
1728#[serde(try_from = "BTreeMap<String, RoleId>")]
1729pub struct RoleMembership {
1731 pub map: BTreeMap<RoleId, RoleId>,
1737}
1738
1739impl RoleMembership {
1740 pub fn new() -> RoleMembership {
1742 RoleMembership {
1743 map: BTreeMap::new(),
1744 }
1745 }
1746}
1747
1748impl From<RoleMembership> for BTreeMap<String, RoleId> {
1749 fn from(value: RoleMembership) -> Self {
1750 value
1751 .map
1752 .into_iter()
1753 .map(|(k, v)| (k.to_string(), v))
1754 .collect()
1755 }
1756}
1757
1758impl TryFrom<BTreeMap<String, RoleId>> for RoleMembership {
1759 type Error = anyhow::Error;
1760
1761 fn try_from(value: BTreeMap<String, RoleId>) -> Result<Self, Self::Error> {
1762 Ok(RoleMembership {
1763 map: value
1764 .into_iter()
1765 .map(|(k, v)| Ok((RoleId::from_str(&k)?, v)))
1766 .collect::<Result<_, anyhow::Error>>()?,
1767 })
1768 }
1769}
1770
1771#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1773pub struct DefaultPrivilegeObject {
1774 pub role_id: RoleId,
1776 pub database_id: Option<DatabaseId>,
1778 pub schema_id: Option<SchemaId>,
1780 pub object_type: ObjectType,
1782}
1783
1784impl DefaultPrivilegeObject {
1785 pub fn new(
1787 role_id: RoleId,
1788 database_id: Option<DatabaseId>,
1789 schema_id: Option<SchemaId>,
1790 object_type: ObjectType,
1791 ) -> DefaultPrivilegeObject {
1792 DefaultPrivilegeObject {
1793 role_id,
1794 database_id,
1795 schema_id,
1796 object_type,
1797 }
1798 }
1799}
1800
1801impl std::fmt::Display for DefaultPrivilegeObject {
1802 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1803 write!(f, "{self:?}")
1805 }
1806}
1807
1808#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1810pub struct DefaultPrivilegeAclItem {
1811 pub grantee: RoleId,
1813 pub acl_mode: AclMode,
1815}
1816
1817impl DefaultPrivilegeAclItem {
1818 pub fn new(grantee: RoleId, acl_mode: AclMode) -> DefaultPrivilegeAclItem {
1820 DefaultPrivilegeAclItem { grantee, acl_mode }
1821 }
1822
1823 pub fn mz_acl_item(self, grantor: RoleId) -> MzAclItem {
1825 MzAclItem {
1826 grantee: self.grantee,
1827 grantor,
1828 acl_mode: self.acl_mode,
1829 }
1830 }
1831}
1832
1833#[derive(Debug, Clone)]
1839pub struct BuiltinsConfig {
1840 pub include_continual_tasks: bool,
1842}
1843
1844#[cfg(test)]
1845mod tests {
1846 use super::{CloudProvider, EnvironmentId, InvalidEnvironmentIdError};
1847
1848 #[mz_ore::test]
1849 fn test_environment_id() {
1850 for (input, expected) in [
1851 (
1852 "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1853 Ok(EnvironmentId {
1854 cloud_provider: CloudProvider::Local,
1855 cloud_provider_region: "az1".into(),
1856 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1857 ordinal: 452,
1858 }),
1859 ),
1860 (
1861 "aws-us-east-1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1862 Ok(EnvironmentId {
1863 cloud_provider: CloudProvider::Aws,
1864 cloud_provider_region: "us-east-1".into(),
1865 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1866 ordinal: 0,
1867 }),
1868 ),
1869 (
1870 "gcp-us-central1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1871 Ok(EnvironmentId {
1872 cloud_provider: CloudProvider::Gcp,
1873 cloud_provider_region: "us-central1".into(),
1874 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1875 ordinal: 0,
1876 }),
1877 ),
1878 (
1879 "azure-australiaeast-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1880 Ok(EnvironmentId {
1881 cloud_provider: CloudProvider::Azure,
1882 cloud_provider_region: "australiaeast".into(),
1883 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1884 ordinal: 0,
1885 }),
1886 ),
1887 (
1888 "generic-moon-station-11-darkside-1497a3b7-a455-4fc4-8752-b44a94b5f90a-0",
1889 Ok(EnvironmentId {
1890 cloud_provider: CloudProvider::Generic,
1891 cloud_provider_region: "moon-station-11-darkside".into(),
1892 organization_id: "1497a3b7-a455-4fc4-8752-b44a94b5f90a".parse().unwrap(),
1893 ordinal: 0,
1894 }),
1895 ),
1896 ("", Err(InvalidEnvironmentIdError)),
1897 (
1898 "local-az1-1497a3b7-a455-4fc4-8752-b44a94b5f90a-123456789",
1899 Err(InvalidEnvironmentIdError),
1900 ),
1901 (
1902 "local-1497a3b7-a455-4fc4-8752-b44a94b5f90a-452",
1903 Err(InvalidEnvironmentIdError),
1904 ),
1905 (
1906 "local-az1-1497a3b7-a455-4fc48752-b44a94b5f90a-452",
1907 Err(InvalidEnvironmentIdError),
1908 ),
1909 ] {
1910 let actual = input.parse();
1911 assert_eq!(expected, actual, "input = {}", input);
1912 if let Ok(actual) = actual {
1913 assert_eq!(input, actual.to_string(), "input = {}", input);
1914 }
1915 }
1916 }
1917}