use std::collections::HashSet;
use super::names;
use super::*;
impl Schema {
pub fn normalize(&mut self) {
for (table_name, table) in self.tables.iter_mut() {
let table_name = table_name.clone();
if table.name.is_empty() {
table.name = table_name.clone();
}
normalize_table_primary_key(table);
for foreign_key in &mut table.foreign_keys {
foreign_key.on_delete = normalize_foreign_key_action(foreign_key.on_delete.take());
foreign_key.on_update = normalize_foreign_key_action(foreign_key.on_update.take());
}
for col in table.columns.iter_mut() {
if let Some(r) = col.references.take() {
let fk_name = r
.name
.unwrap_or_else(|| names::foreign_key(&table_name, &[col.name.as_str()]));
let mut foreign_key =
ForeignKey::single(fk_name, col.name.clone(), r.table, r.column);
foreign_key.on_delete = normalize_foreign_key_action(r.on_delete);
foreign_key.on_update = normalize_foreign_key_action(r.on_update);
table.foreign_keys.push(foreign_key);
}
if let Some(expr) = col.check.take() {
table.constraints.push(Constraint::Check {
name: names::column_check(&table_name, &col.name),
expression: expr,
});
}
}
fill_derived_names(table);
for trigger in table.triggers.iter_mut() {
if trigger.name.is_none() {
let mut event_parts: Vec<&str> = trigger
.events
.iter()
.map(|e| match e {
TriggerEvent::Insert => "insert",
TriggerEvent::Update => "update",
TriggerEvent::Delete => "delete",
TriggerEvent::Truncate => "truncate",
})
.collect();
event_parts.sort_unstable();
let timing_part = match trigger.timing {
TriggerTiming::Before => "before",
TriggerTiming::After => "after",
TriggerTiming::InsteadOf => "instead_of",
};
trigger.name = Some(format!(
"{}_{}_{}_trg",
table_name,
event_parts.join("_"),
timing_part
));
}
}
}
for (key, func) in self.functions.iter_mut() {
if func.name.is_empty() {
func.name = key.clone();
}
}
}
}
fn normalize_foreign_key_action(action: Option<String>) -> Option<String> {
let action = action?;
let trimmed = action.trim();
if trimmed.is_empty() {
None
} else {
Some(
canonical_foreign_key_action(trimmed)
.unwrap_or(trimmed)
.to_string(),
)
}
}
pub(crate) fn normalize_table_primary_key(table: &mut Table) {
let flagged: Vec<String> = table
.columns
.iter()
.filter(|column| column.primary_key)
.map(|column| column.name.clone())
.collect();
if table.primary_key.is_none() && !flagged.is_empty() {
table.primary_key = Some(PrimaryKey {
name: names::primary_key(&table.name),
columns: flagged.clone(),
});
}
if let Some(pk) = &mut table.primary_key
&& pk.name.is_empty()
{
pk.name = names::primary_key(&table.name);
}
let Some(pk) = &table.primary_key else {
return;
};
if !flagged.is_empty() && !same_string_set(&flagged, &pk.columns) {
return;
}
let pk_columns = pk.columns.clone();
for column in &mut table.columns {
column.primary_key = pk_columns.iter().any(|name| name == &column.name);
if column.primary_key {
column.nullable = false;
}
}
}
fn fill_derived_names(table: &mut Table) {
let table_name = table.name.as_str();
for fk in &mut table.foreign_keys {
if fk.name.is_empty() {
fk.name = names::foreign_key(table_name, &fk.columns);
}
}
for index in &mut table.indexes {
if index.name.is_empty() {
index.name = names::index(table_name, &index.columns);
}
}
for constraint in &mut table.constraints {
match constraint {
Constraint::Unique { name, columns } if name.is_empty() => {
*name = names::unique(table_name, columns);
}
Constraint::Check { name, .. } if name.is_empty() => {
*name = names::table_check(table_name);
}
_ => {}
}
}
}
fn same_string_set(left: &[String], right: &[String]) -> bool {
let left: HashSet<&str> = left.iter().map(String::as_str).collect();
let right: HashSet<&str> = right.iter().map(String::as_str).collect();
left == right
}