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_name = table.name.clone();
table_name.set_span(Span::call_site());
let table_lower = to_lower(&table_name);
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_name.clone());
tables.push(quote! {b.drop_table::<#prev_mod::#table_name>()})
} 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_name);
} else {
tables.push(quote! {b.create_empty::<#new_mod::#table_name>()})
}
}
for prev_table in prev_tables.into_values() {
let table_ident = &prev_table.name;
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, #prev_mod::#schema_name, #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.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 name = &col.name;
if prev_columns.contains_key(i) {
old_ident.push(name);
} else {
let mut unique_columns = table.indices.iter().flat_map(|u| &u.columns);
if unique_columns.any(|c| c == name) {
migration_conflict = quote! {::rust_query::TableRow<Self::From>};
conflict_from = quote! {val};
}
alter_ident.push(name);
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::Migration for #table_ident {
type To = #new_mod::#table_ident;
type FromSchema = <Self::From as ::rust_query::Table>::Schema;
type From = <Self::To as ::rust_query::Table>::MigrateFrom;
type Conflict = #migration_conflict;
fn prepare(
val: Self,
prev: ::rust_query::Lazy<'_, Self::From>,
) -> Self::To {
let prev = ::std::ops::Deref::deref(&prev);
#new_mod::#table_ident {
#(#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::From>) -> Self::Conflict {
#conflict_from
}
}
};
Ok(Some(migration))
}