use std::{collections::BTreeMap, ops::Not};
use crate::{
multi::{SingleVersionColumn, SingleVersionTable},
to_lower,
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::Ident;
pub fn migrations(
schema_name: &Ident,
mut prev_tables: BTreeMap<usize, SingleVersionTable>,
new_tables: &BTreeMap<usize, SingleVersionTable>,
prev_mod: TokenStream,
new_mod: TokenStream,
) -> Result<TokenStream, syn::Error> {
let mut tables = vec![];
let mut create_table_name = vec![];
let mut create_table_lower = vec![];
let mut table_migrations = TokenStream::new();
for (i, table) in new_tables {
let mut table_ident = table.name.ident.clone();
table_ident.set_span(Span::call_site());
let table_lower = to_lower(&table_ident);
if let Some(prev_table) = prev_tables.remove(i) {
let Some(migration) =
define_table_migration(&prev_table.columns, table, false, &new_mod)?
else {
continue;
};
table_migrations.extend(migration);
create_table_lower.push(table_lower);
create_table_name.push(table_ident.clone());
tables.push(quote! {b.drop_table::<#prev_mod::#table_ident>()})
} else if table.prev.is_some() {
let migration =
define_table_migration(&BTreeMap::new(), table, true, &new_mod).unwrap();
table_migrations.extend(migration);
create_table_lower.push(table_lower);
create_table_name.push(table_ident);
} else {
tables.push(quote! {b.create_empty::<#new_mod::#table_ident>()})
}
}
for prev_table in prev_tables.into_values() {
let table_ident = &prev_table.name.ident;
tables.push(quote! {b.drop_table::<#prev_mod::#table_ident>()})
}
let lifetime = create_table_name.is_empty().not().then_some(quote! {'t,});
Ok(quote! {
#table_migrations
pub struct #schema_name<#lifetime> {
#(pub #create_table_lower: ::rust_query::migration::Migrated<'t, #new_mod::#create_table_name>,)*
}
impl<'t> ::rust_query::private::SchemaMigration<'t> for #schema_name<#lifetime> {
type From = #prev_mod::#schema_name;
type To = #new_mod::#schema_name;
fn tables(self, b: &mut ::rust_query::private::SchemaBuilder<'t, Self::From>) {
#(#tables;)*
#(self.#create_table_lower.apply(b);)*
}
}
})
}
fn define_table_migration(
prev_columns: &BTreeMap<usize, SingleVersionColumn>,
table: &SingleVersionTable,
always_migrate: bool,
new_mod: &TokenStream,
) -> syn::Result<Option<TokenStream>> {
let mut table_ident = table.name.ident.clone();
table_ident.set_span(Span::call_site());
let mut alter_ident = vec![];
let mut old_ident = vec![];
let mut alter_typ = vec![];
let mut alter_tmp = vec![];
let mut migration_conflict = quote! {::std::convert::Infallible};
let mut conflict_from = quote! {::std::unreachable!()};
for (i, col) in &table.columns {
let col_ident = &col.name.ident;
if prev_columns.contains_key(i) {
old_ident.push(col_ident);
} else {
let mut unique_columns = table.indices.iter().flat_map(|u| &u.columns);
if unique_columns.any(|c| c == i) {
migration_conflict = quote! {::rust_query::TableRow<Self::MigrateFrom>};
conflict_from = quote! {val};
}
alter_ident.push(col_ident);
alter_typ.push(&col.typ);
alter_tmp.push(format_ident!("_{table_ident}{i}"))
}
}
if !always_migrate && alter_ident.is_empty() && table.columns.len() == prev_columns.len() {
return Ok(None);
}
let migration = quote! {
pub struct #table_ident {#(
pub #alter_ident: <#new_mod::#alter_tmp as ::rust_query::private::DbTyp>::Prev,
)*}
impl ::rust_query::private::Migrateable for #new_mod::#table_ident {
type Migration = #table_ident;
type FromSchema = <Self::MigrateFrom as ::rust_query::Table>::Schema;
type MigrateConflict = #migration_conflict;
fn prepare(
val: Self::Migration,
prev: ::rust_query::Lazy<'_, Self::MigrateFrom>,
) -> Self {
let prev = ::std::ops::Deref::deref(&prev);
Self {
#(#old_ident: ::rust_query::private::DbTyp::from_lazy(&prev.#old_ident),)*
#(#alter_ident: ::rust_query::private::DbTyp::migrate(val.#alter_ident),)*
}
}
fn map_conflict(val: ::rust_query::TableRow<Self::MigrateFrom>) -> Self::MigrateConflict {
#conflict_from
}
}
};
Ok(Some(migration))
}