1use std::fmt;
11use std::str::FromStr;
12
13use anyhow::{Error, anyhow};
14use columnar::Columnar;
15use mz_ore::id_gen::AtomicIdGen;
16#[cfg(any(test, feature = "proptest"))]
17use proptest_derive::Arbitrary;
18use serde::{Deserialize, Serialize};
19
20use crate::CatalogItemId;
21
22#[derive(
28 Clone,
29 Copy,
30 Debug,
31 Eq,
32 PartialEq,
33 Ord,
34 PartialOrd,
35 Hash,
36 Serialize,
37 Deserialize,
38 Columnar
39)]
40#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
41pub enum GlobalId {
42 System(u64),
44 IntrospectionSourceIndex(u64),
46 User(u64),
48 Transient(u64),
50 Explain,
52}
53
54static_assertions::assert_eq_size!(GlobalId, [u8; 16]);
57
58impl GlobalId {
59 pub fn is_system(&self) -> bool {
61 matches!(
62 self,
63 GlobalId::System(_) | GlobalId::IntrospectionSourceIndex(_)
64 )
65 }
66
67 pub fn is_user(&self) -> bool {
69 matches!(self, GlobalId::User(_))
70 }
71
72 pub fn is_transient(&self) -> bool {
74 matches!(self, GlobalId::Transient(_))
75 }
76}
77
78impl FromStr for GlobalId {
79 type Err = Error;
80
81 fn from_str(mut s: &str) -> Result<Self, Self::Err> {
82 if s.len() < 2 {
83 return Err(anyhow!("couldn't parse id {}", s));
84 }
85 if s == "Explained Query" {
86 return Ok(GlobalId::Explain);
87 }
88 let tag = s.chars().next().unwrap();
89 s = &s[tag.len_utf8()..];
90 let variant = match tag {
91 's' => {
92 if Some('i') == s.chars().next() {
93 s = &s[1..];
94 GlobalId::IntrospectionSourceIndex
95 } else {
96 GlobalId::System
97 }
98 }
99 'u' => GlobalId::User,
100 't' => GlobalId::Transient,
101 _ => return Err(anyhow!("couldn't parse id {}", s)),
102 };
103 let val: u64 = s.parse()?;
104 Ok(variant(val))
105 }
106}
107
108impl fmt::Display for GlobalId {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 match self {
111 GlobalId::System(id) => write!(f, "s{}", id),
112 GlobalId::IntrospectionSourceIndex(id) => write!(f, "si{}", id),
113 GlobalId::User(id) => write!(f, "u{}", id),
114 GlobalId::Transient(id) => write!(f, "t{}", id),
115 GlobalId::Explain => write!(f, "Explained Query"),
116 }
117 }
118}
119
120#[derive(Debug)]
121pub struct TransientIdGen(AtomicIdGen);
122
123impl TransientIdGen {
124 pub fn new() -> Self {
125 let inner = AtomicIdGen::default();
126 let _ = inner.allocate_id();
128 Self(inner)
129 }
130
131 pub fn allocate_id(&self) -> (CatalogItemId, GlobalId) {
132 let inner = self.0.allocate_id();
133 (CatalogItemId::Transient(inner), GlobalId::Transient(inner))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use proptest::prelude::*;
140
141 use super::*;
142
143 #[mz_ore::test]
144 fn proptest_global_id_roundtrips() {
145 fn testcase(og: GlobalId) {
146 let s = og.to_string();
147 let rnd: GlobalId = s.parse().unwrap();
148 assert_eq!(og, rnd);
149 }
150
151 proptest!(|(id in any::<GlobalId>())| {
152 testcase(id);
153 })
154 }
155
156 #[mz_ore::test]
157 fn test_global_id_from_str_non_ascii() {
158 for invalid in ["ü1", "ü", "é42", "🦀7", ""] {
161 assert!(
162 invalid.parse::<GlobalId>().is_err(),
163 "expected {invalid:?} to fail to parse"
164 );
165 }
166 }
167}