use crate::schema::db;
use hashbrown::{HashMap, HashSet};
pub enum Type<'a> {
Create(&'a db::TypeEnum),
Rename {
previous: &'a db::TypeEnum,
next: &'a db::TypeEnum,
},
AddVariants {
ty: &'a db::TypeEnum,
added: Vec<&'a db::EnumVariant>,
},
}
impl<'a> Type<'a> {
pub(super) fn diff(previous: &'a db::Schema, next: &'a db::Schema) -> Vec<Self> {
let prev_types = collect_named_enums(previous);
let next_types = collect_named_enums(next);
let mut changes = Vec::new();
let mut renamed = HashSet::new();
for (name, next_type) in &next_types {
match prev_types.get(name) {
None => {
let rename = prev_types.iter().find(|(previous_name, previous_type)| {
!next_types.contains_key(*previous_name)
&& !renamed.contains(*previous_name)
&& previous_type.columns == next_type.columns
&& variants_are_prefix(previous_type.ty, next_type.ty)
});
if let Some((previous_name, previous_type)) = rename {
changes.push(Self::Rename {
previous: previous_type.ty,
next: next_type.ty,
});
renamed.insert(*previous_name);
add_variants(&mut changes, name, previous_type.ty, next_type.ty);
} else {
changes.push(Self::Create(next_type.ty));
}
}
Some(previous_type) => {
add_variants(&mut changes, name, previous_type.ty, next_type.ty)
}
}
}
changes
}
}
struct NamedEnum<'a> {
ty: &'a db::TypeEnum,
columns: HashSet<(&'a str, &'a str, bool)>,
}
fn collect_named_enums(schema: &db::Schema) -> HashMap<&str, NamedEnum<'_>> {
let mut result = HashMap::new();
for table in &schema.tables {
for column in &table.columns {
if let Some(type_enum) = column.storage_ty.named_enum()
&& let Some(name) = &type_enum.name
{
result
.entry(name.as_str())
.or_insert_with(|| NamedEnum {
ty: type_enum,
columns: HashSet::new(),
})
.columns
.insert((
table.name.as_str(),
column.name.as_str(),
matches!(column.storage_ty, db::Type::List(_)),
));
}
}
}
result
}
fn variants_are_prefix(previous: &db::TypeEnum, next: &db::TypeEnum) -> bool {
previous.variants.len() <= next.variants.len()
&& previous
.variants
.iter()
.zip(&next.variants)
.all(|(previous, next)| previous.name == next.name)
}
fn add_variants<'a>(
changes: &mut Vec<Type<'a>>,
name: &str,
previous: &'a db::TypeEnum,
next: &'a db::TypeEnum,
) {
assert!(
next.variants.len() >= previous.variants.len(),
"enum type `{name}`: removing variants is not supported; previous had {} variants, next has {}",
previous.variants.len(),
next.variants.len()
);
for (i, (previous, next)) in previous.variants.iter().zip(&next.variants).enumerate() {
assert!(
previous.name == next.name,
"enum type `{name}`: variant at position {i} changed from `{}` to `{}`; \
reordering or renaming variants is not supported",
previous.name,
next.name
);
}
if next.variants.len() > previous.variants.len() {
changes.push(Type::AddVariants {
ty: next,
added: next.variants[previous.variants.len()..].iter().collect(),
});
}
}