uqa_core/
relation_identity.rs1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
17pub struct RelationIdentity {
18 pub schema: String,
19 pub name: String,
20}
21
22impl RelationIdentity {
23 pub fn new(schema: impl Into<String>, name: impl Into<String>) -> Self {
24 Self {
25 schema: schema.into(),
26 name: name.into(),
27 }
28 }
29
30 pub fn qualified_name(&self) -> String {
31 format!(
32 "{}.{}",
33 render_relation_component(&self.schema),
34 render_relation_component(&self.name)
35 )
36 }
37
38 pub fn canonical_and_legacy_public_names(&self) -> Vec<String> {
43 let canonical = self.qualified_name();
44 if self.schema != "public" {
45 return vec![canonical];
46 }
47 let mut names = vec![canonical];
48 let rendered_alias = render_relation_component(&self.name);
49 if !names.contains(&rendered_alias) {
50 names.push(rendered_alias);
51 }
52 if RelationIdentity::from_legacy_name(&self.name).is_ok_and(|raw| raw == *self)
57 && !names.contains(&self.name)
58 {
59 names.push(self.name.clone());
60 }
61 names
62 }
63
64 pub fn from_legacy_name(value: &str) -> Result<Self, String> {
69 let (schema, name) = Self::parse_reference(value)?;
70 Ok(Self::new(
71 schema.unwrap_or_else(|| "public".to_string()),
72 name,
73 ))
74 }
75
76 pub fn from_legacy_index_name(value: &str, table: &Self) -> Self {
78 Self::new(&table.schema, value)
79 }
80
81 pub fn parse_reference(value: &str) -> Result<(Option<String>, String), String> {
84 let components = parse_relation_components(value)?;
85 match components.as_slice() {
86 [name] => Ok((None, name.clone())),
87 [schema, name] => Ok((Some(schema.clone()), name.clone())),
88 _ => Err(format!("invalid persisted relation name `{value}`")),
89 }
90 }
91}
92
93fn render_relation_component(component: &str) -> String {
94 let can_render_bare = component
95 .bytes()
96 .enumerate()
97 .all(|(index, byte)| match byte {
98 b'a'..=b'z' | b'_' => true,
99 b'0'..=b'9' | b'$' => index != 0,
100 _ => false,
101 });
102 if can_render_bare && !component.is_empty() {
103 component.to_string()
104 } else {
105 format!("\"{}\"", component.replace('"', "\"\""))
106 }
107}
108
109fn parse_relation_components(value: &str) -> Result<Vec<String>, String> {
110 if value.is_empty() {
111 return Err("persisted relation name is empty".to_string());
112 }
113 let mut components = Vec::with_capacity(2);
114 let mut chars = value.char_indices().peekable();
115 while chars.peek().is_some() {
116 let mut component = String::new();
117 if chars.peek().is_some_and(|(_, ch)| *ch == '"') {
118 chars.next();
119 let mut terminated = false;
120 while let Some((_, ch)) = chars.next() {
121 if ch != '"' {
122 component.push(ch);
123 continue;
124 }
125 if chars.peek().is_some_and(|(_, next)| *next == '"') {
126 chars.next();
127 component.push('"');
128 } else {
129 terminated = true;
130 break;
131 }
132 }
133 if !terminated {
134 return Err(format!("unterminated quoted relation name `{value}`"));
135 }
136 if chars.peek().is_some_and(|(_, ch)| *ch != '.') {
137 return Err(format!("invalid persisted relation name `{value}`"));
138 }
139 } else {
140 while let Some((_, ch)) = chars.peek() {
141 if *ch == '.' {
142 break;
143 }
144 if *ch == '"' {
145 return Err(format!("invalid persisted relation name `{value}`"));
146 }
147 component.push(*ch);
148 chars.next();
149 }
150 }
151 if component.is_empty() {
152 return Err(format!("invalid persisted relation name `{value}`"));
153 }
154 components.push(component);
155 if components.len() > 2 {
156 return Err(format!("invalid persisted relation name `{value}`"));
157 }
158 match chars.next() {
159 Some((_, '.')) if chars.peek().is_some() => {}
160 Some(_) => return Err(format!("invalid persisted relation name `{value}`")),
161 None => break,
162 }
163 }
164 Ok(components)
165}