safe_migrate/ast/
identifiers.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4pub struct Ident {
5 pub text: String,
6 pub quoted: bool,
7}
8
9impl Ident {
10 pub fn new(text: impl Into<String>, quoted: bool) -> Self {
11 Self {
12 text: text.into(),
13 quoted,
14 }
15 }
16
17 pub fn resolve(&self) -> String {
22 let resolved = if self.quoted {
23 self.text.clone()
24 } else {
25 self.text.to_ascii_lowercase()
26 };
27 truncate_postgres_identifier(&resolved).to_string()
28 }
29}
30
31fn truncate_postgres_identifier(value: &str) -> &str {
32 const MAX_IDENTIFIER_BYTES: usize = 63;
33
34 let mut end = value.len().min(MAX_IDENTIFIER_BYTES);
35 while !value.is_char_boundary(end) {
36 end -= 1;
37 }
38 &value[..end]
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub struct QualifiedName {
43 pub schema: Option<Ident>,
44 pub name: Ident,
45}
46
47impl QualifiedName {
48 pub fn new(schema: Option<Ident>, name: Ident) -> Self {
49 Self { schema, name }
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::Ident;
56
57 #[test]
58 fn identifiers_follow_postgresql_byte_truncation() {
59 let ascii = "A".repeat(70);
60 assert_eq!(Ident::new(ascii, false).resolve(), "a".repeat(63));
61
62 let quoted = format!("{}suffix", "é".repeat(32));
63 let resolved = Ident::new(quoted, true).resolve();
64 assert_eq!(resolved.len(), 62);
65 assert_eq!(resolved, "é".repeat(31));
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ObjectId {
73 pub schema: String,
74 pub name: String,
75 #[serde(default)]
76 pub inferred_schema: bool,
77}
78
79impl PartialEq for ObjectId {
80 fn eq(&self, other: &Self) -> bool {
81 self.schema == other.schema && self.name == other.name
82 }
83}
84
85impl Eq for ObjectId {}
86
87impl std::hash::Hash for ObjectId {
88 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
89 self.schema.hash(state);
90 self.name.hash(state);
91 }
92}
93
94impl ObjectId {
95 pub fn new(schema: impl Into<String>, name: impl Into<String>) -> Self {
96 Self {
97 schema: schema.into(),
98 name: name.into(),
99 inferred_schema: false,
100 }
101 }
102}
103
104impl std::fmt::Display for ObjectId {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 if self.inferred_schema {
107 write!(f, "{}.{} (inferred)", self.schema, self.name)
108 } else {
109 write!(f, "{}.{}", self.schema, self.name)
110 }
111 }
112}