pg2sqlite_core/transform/
name_resolve.rs1use std::collections::HashMap;
3
4use crate::diagnostics::warning::{self, Severity, Warning};
5use crate::ir::{Ident, QualifiedName, SchemaModel, TableConstraint};
6
7pub fn resolve_names(
10 model: &mut SchemaModel,
11 include_all_schemas: bool,
12 warnings: &mut Vec<Warning>,
13) {
14 if !include_all_schemas {
15 strip_schemas(model);
17 return;
18 }
19
20 let mut name_counts: HashMap<String, Vec<(Option<String>, usize)>> = HashMap::new();
22 for (i, table) in model.tables.iter().enumerate() {
23 let schema = table.name.schema.as_ref().map(|s| s.normalized.clone());
24 name_counts
25 .entry(table.name.name.normalized.clone())
26 .or_default()
27 .push((schema, i));
28 }
29
30 let mut rename_map: HashMap<(Option<String>, String), String> = HashMap::new();
32 for (name, entries) in &name_counts {
33 if entries.len() > 1 {
34 for (schema, _) in entries {
35 if let Some(s) = schema {
36 let new_name = format!("{s}__{name}");
37 rename_map.insert((Some(s.clone()), name.clone()), new_name.clone());
38 warnings.push(
39 Warning::new(
40 warning::SCHEMA_PREFIXED,
41 Severity::Lossy,
42 format!(
43 "table '{s}.{name}' renamed to '{new_name}' to avoid collision"
44 ),
45 )
46 .with_object(&new_name),
47 );
48 }
49 }
50 }
51 }
52
53 for table in &mut model.tables {
55 let key = (
56 table.name.schema.as_ref().map(|s| s.normalized.clone()),
57 table.name.name.normalized.clone(),
58 );
59 if let Some(new_name) = rename_map.get(&key) {
60 table.name = QualifiedName::new(Ident::new(new_name));
61 } else {
62 table.name.schema = None;
63 }
64 }
65
66 for table in &mut model.tables {
68 for constraint in &mut table.constraints {
69 if let TableConstraint::ForeignKey { ref_table, .. } = constraint {
70 let key = (
71 ref_table.schema.as_ref().map(|s| s.normalized.clone()),
72 ref_table.name.normalized.clone(),
73 );
74 if let Some(new_name) = rename_map.get(&key) {
75 *ref_table = QualifiedName::new(Ident::new(new_name));
76 } else {
77 ref_table.schema = None;
78 }
79 }
80 }
81
82 for col in &mut table.columns {
84 if let Some(fk) = &mut col.references {
85 let key = (
86 fk.table.schema.as_ref().map(|s| s.normalized.clone()),
87 fk.table.name.normalized.clone(),
88 );
89 if let Some(new_name) = rename_map.get(&key) {
90 fk.table = QualifiedName::new(Ident::new(new_name));
91 } else {
92 fk.table.schema = None;
93 }
94 }
95 }
96 }
97
98 for index in &mut model.indexes {
100 let key = (
101 index.table.schema.as_ref().map(|s| s.normalized.clone()),
102 index.table.name.normalized.clone(),
103 );
104 if let Some(new_name) = rename_map.get(&key) {
105 index.table = QualifiedName::new(Ident::new(new_name));
106 } else {
107 index.table.schema = None;
108 }
109 }
110}
111
112fn strip_schemas(model: &mut SchemaModel) {
114 for table in &mut model.tables {
115 table.name.schema = None;
116 }
117 for index in &mut model.indexes {
118 index.table.schema = None;
119 }
120 for table in &mut model.tables {
121 for constraint in &mut table.constraints {
122 if let TableConstraint::ForeignKey { ref_table, .. } = constraint {
123 ref_table.schema = None;
124 }
125 }
126 for col in &mut table.columns {
127 if let Some(fk) = &mut col.references {
128 fk.table.schema = None;
129 }
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::ir::{Column, PgType, Table};
138
139 fn make_table(schema: Option<&str>, name: &str) -> Table {
140 Table {
141 name: match schema {
142 Some(s) => QualifiedName::with_schema(Ident::new(s), Ident::new(name)),
143 None => QualifiedName::new(Ident::new(name)),
144 },
145 columns: vec![Column {
146 name: Ident::new("id"),
147 pg_type: PgType::Integer,
148 sqlite_type: None,
149 not_null: false,
150 default: None,
151 is_primary_key: false,
152 is_unique: false,
153 references: None,
154 check: None,
155 }],
156 constraints: vec![],
157 }
158 }
159
160 #[test]
161 fn test_strip_schemas_single() {
162 let mut model = SchemaModel {
163 tables: vec![make_table(Some("public"), "users")],
164 ..Default::default()
165 };
166 let mut w = Vec::new();
167 resolve_names(&mut model, false, &mut w);
168 assert!(model.tables[0].name.schema.is_none());
169 }
170
171 #[test]
172 fn test_collision_prefixing() {
173 let mut model = SchemaModel {
174 tables: vec![
175 make_table(Some("public"), "users"),
176 make_table(Some("other"), "users"),
177 ],
178 ..Default::default()
179 };
180 let mut w = Vec::new();
181 resolve_names(&mut model, true, &mut w);
182
183 let names: Vec<&str> = model
184 .tables
185 .iter()
186 .map(|t| t.name.name.normalized.as_str())
187 .collect();
188 assert!(names.contains(&"public__users"));
189 assert!(names.contains(&"other__users"));
190 assert!(w.iter().any(|w| w.code == warning::SCHEMA_PREFIXED));
191 }
192}