uqa_sql/schema/indexes/
names.rs1#[cfg(test)]
10mod tests;
11
12use crate::{
13 ast::{TableKeyConstraint, TableKeyConstraintKind},
14 SQLError,
15};
16use uqa_core::RelationIdentity;
17
18pub trait IndexNameCatalog {
20 fn existing_constraint_keys(&self, table: &str) -> Result<Vec<TableKeyConstraint>, SQLError>;
21 fn existing_constraint_names(
22 &self,
23 table: &str,
24 ) -> Result<std::collections::BTreeSet<String>, SQLError>;
25 fn automatic_constraint_names(
26 &self,
27 table: &str,
28 ) -> Result<std::collections::BTreeSet<String>, SQLError>;
29 fn relation_name_available(&self, qualified_name: &str) -> Result<bool, SQLError>;
30}
31
32pub fn name_constraint_indexes(
33 catalog: &dyn IndexNameCatalog,
34 table: &str,
35 keys: &mut [TableKeyConstraint],
36) -> Result<(), SQLError> {
37 let relation = RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
38 let existing = catalog.existing_constraint_keys(table)?;
39 let occupied = catalog.existing_constraint_names(table)?;
40 let automatic = catalog.automatic_constraint_names(table)?;
41 let mut used = std::collections::BTreeSet::new();
42 for key in keys {
43 if let Some(old) = existing.iter().find(|old| {
44 *old == key
45 || key.catalog_identity.is_some_and(|identity| {
46 old.catalog_identity
47 .is_some_and(|old| old.object_id == identity.object_id)
48 })
49 }) {
50 key.name.clone_from(&old.name);
51 used.extend(key.name.iter().cloned());
52 continue;
53 }
54 if let Some(name) = &key.name {
55 if occupied.contains(name) {
56 return Err(crate::schema::constraint_changes::constraint_error(
57 "42710",
58 format!(
59 "constraint \"{name}\" for relation \"{}\" already exists",
60 relation.name
61 ),
62 ));
63 }
64 if !used.insert(name.clone()) || !available(catalog, &relation, name)? {
65 return Err(SQLError::Routine {
66 sqlstate: "42P07".into(),
67 message: format!("relation \"{name}\" already exists"),
68 });
69 }
70 continue;
71 }
72 let suffix = if key.kind == TableKeyConstraintKind::PrimaryKey {
73 "pkey"
74 } else {
75 "key"
76 };
77 let component = if key.kind == TableKeyConstraintKind::PrimaryKey {
78 String::new()
79 } else {
80 super::keys::key_names(&constraint_index_attributes(key)).join("_")
81 };
82 for number in 0_u64.. {
83 let label = if number == 0 {
84 suffix.into()
85 } else {
86 format!("{suffix}{number}")
87 };
88 let candidate = object_name(&relation.name, &component, &label);
89 if !used.contains(&candidate)
90 && !occupied.contains(&candidate)
91 && !automatic.contains(&candidate)
92 && available(catalog, &relation, &candidate)?
93 {
94 used.insert(candidate.clone());
95 key.name = Some(candidate);
96 break;
97 }
98 }
99 }
100 Ok(())
101}
102
103pub fn constraint_index_attributes(key: &TableKeyConstraint) -> Vec<crate::ast::IndexKey> {
105 key.columns
106 .iter()
107 .chain(&key.included_columns)
108 .cloned()
109 .map(crate::ast::IndexKey::Column)
110 .collect()
111}
112
113fn available(
114 catalog: &dyn IndexNameCatalog,
115 table: &RelationIdentity,
116 name: &str,
117) -> Result<bool, SQLError> {
118 if name == table.name {
119 return Ok(false);
120 }
121 catalog.relation_name_available(&RelationIdentity::new(&table.schema, name).qualified_name())
122}
123
124pub(in crate::schema) fn object_name(table: &str, columns: &str, label: &str) -> String {
126 let mut table_length = table.len();
127 let mut column_length = columns.len();
128 let overhead = label.len() + 1 + usize::from(!columns.is_empty());
129 while table_length + column_length + overhead > 63 {
130 if table_length > column_length {
131 table_length -= 1;
132 } else {
133 column_length -= 1;
134 }
135 }
136 while !table.is_char_boundary(table_length) {
137 table_length -= 1;
138 }
139 while !columns.is_char_boundary(column_length) {
140 column_length -= 1;
141 }
142 if columns.is_empty() {
143 format!("{}_{label}", &table[..table_length])
144 } else {
145 format!(
146 "{}_{}_{label}",
147 &table[..table_length],
148 &columns[..column_length]
149 )
150 }
151}
152
153pub fn allocate_default_index_name(
154 catalog: &dyn IndexNameCatalog,
155 table: &RelationIdentity,
156 columns: &[crate::ast::IndexKey],
157) -> Result<String, SQLError> {
158 let component = super::keys::key_names(columns).join("_");
159 for number in 0_u64.. {
160 let label = if number == 0 {
161 "idx".to_owned()
162 } else {
163 format!("idx{number}")
164 };
165 let candidate = object_name(&table.name, &component, &label);
166 if available(catalog, table, &candidate)? {
167 return Ok(candidate);
168 }
169 }
170 unreachable!("u64 index-name suffix space is non-empty")
171}