rust_query/migrate/
migration.rs1use std::{
2 collections::{HashMap, HashSet},
3 convert::Infallible,
4 marker::PhantomData,
5 ops::Deref,
6};
7
8use sea_query::{Alias, IntoIden, TableDropStatement};
9
10use crate::{
11 Lazy, Table, TableRow, Transaction,
12 alias::{Scope, TmpTable},
13 migrate::new_table_inner,
14 transaction::try_insert_private,
15};
16
17pub trait Migration {
18 type FromSchema: 'static;
19 type From: Table<Schema = Self::FromSchema>;
20 type To: Table<MigrateFrom = Self::From>;
21 type Conflict;
22
23 #[doc(hidden)]
24 fn prepare(val: Self, prev: Lazy<'_, Self::From>) -> Self::To;
25 #[doc(hidden)]
26 fn map_conflict(val: TableRow<Self::From>) -> Self::Conflict;
27}
28
29pub struct TransactionMigrate<FromSchema> {
31 pub(super) inner: Transaction<FromSchema>,
32 pub(super) scope: Scope,
33 pub(super) rename_map: HashMap<&'static str, TmpTable>,
34 pub(super) extra_index: Vec<String>,
36}
37
38impl<FromSchema> Deref for TransactionMigrate<FromSchema> {
39 type Target = Transaction<FromSchema>;
40
41 fn deref(&self) -> &Self::Target {
42 &self.inner
43 }
44}
45
46impl<FromSchema: 'static> TransactionMigrate<FromSchema> {
47 fn new_table_name<T: Table>(&mut self) -> TmpTable {
48 *self.rename_map.entry(T::NAME).or_insert_with(|| {
49 let new_table_name = self.scope.tmp_table();
50 let table = crate::schema::from_macro::Table::new::<T>();
51 self.inner.execute(&new_table_inner(&table, new_table_name));
52 self.extra_index.extend(table.delayed_indices(T::NAME));
53 new_table_name
54 })
55 }
56
57 fn unmigrated<M: Migration<FromSchema = FromSchema>>(
58 &self,
59 new_name: TmpTable,
60 ) -> impl Iterator<Item = TableRow<M::From>> {
61 let data = self.inner.query(|rows| {
62 let old = rows.join_private::<M::From>();
63 rows.into_vec(old)
64 });
65
66 let migrated = Transaction::new().query(|rows| {
67 let new = rows.join_tmp::<M::To>(new_name);
68 rows.into_vec(new)
69 });
70 let migrated: HashSet<_> = migrated.into_iter().map(|x| x.inner.idx).collect();
71
72 data.into_iter()
73 .filter(move |row| !migrated.contains(&row.inner.idx))
74 }
75
76 pub fn migrate_optional<'t, M: Migration<FromSchema = FromSchema>>(
84 &'t mut self,
85 mut f: impl FnMut(Lazy<'t, M::From>) -> Option<M>,
86 ) -> Result<(), M::Conflict> {
87 let new_name = self.new_table_name::<M::To>();
88
89 for row in self.unmigrated::<M>(new_name) {
90 if let Some(new) = f(self.lazy(row)) {
91 let val = M::prepare(new, self.lazy(row));
93 try_insert_private::<M::To>(new_name.into_iden(), Some(row.inner.idx), val)
94 .map_err(|_| M::map_conflict(row))?;
95 };
96 }
97 Ok(())
98 }
99
100 pub fn migrate<'t, M: Migration<FromSchema = FromSchema>>(
107 &'t mut self,
108 mut f: impl FnMut(Lazy<'t, M::From>) -> M,
109 ) -> Result<Migrated<'static, FromSchema, M::To>, M::Conflict> {
110 self.migrate_optional::<M>(|x| Some(f(x)))?;
111
112 Ok(Migrated {
113 _p: PhantomData,
114 f: Box::new(|_| {}),
115 _local: PhantomData,
116 })
117 }
118
119 pub fn migrate_ok<'t, M: Migration<FromSchema = FromSchema, Conflict = Infallible>>(
123 &'t mut self,
124 f: impl FnMut(Lazy<'t, M::From>) -> M,
125 ) -> Migrated<'static, FromSchema, M::To> {
126 let Ok(res) = self.migrate(f);
127 res
128 }
129}
130
131pub struct Migrated<'t, FromSchema, T> {
135 _p: PhantomData<T>,
136 f: Box<dyn 't + FnOnce(&mut SchemaBuilder<'t, FromSchema>)>,
137 _local: PhantomData<*const ()>,
138}
139
140impl<'t, FromSchema: 'static, T: Table> Migrated<'t, FromSchema, T> {
141 pub fn map_fk_err(err: impl 't + FnOnce() -> Infallible) -> Self {
145 Self {
146 _p: PhantomData,
147 f: Box::new(|x| x.foreign_key::<T>(err)),
148 _local: PhantomData,
149 }
150 }
151
152 #[doc(hidden)]
153 pub fn apply(self, b: &mut SchemaBuilder<'t, FromSchema>) {
154 (self.f)(b)
155 }
156}
157
158pub struct SchemaBuilder<'t, FromSchema> {
159 pub(super) inner: TransactionMigrate<FromSchema>,
160 pub(super) drop: Vec<TableDropStatement>,
161 pub(super) foreign_key: HashMap<&'static str, Box<dyn 't + FnOnce() -> Infallible>>,
162}
163
164impl<'t, FromSchema: 'static> SchemaBuilder<'t, FromSchema> {
165 pub fn foreign_key<To: Table>(&mut self, err: impl 't + FnOnce() -> Infallible) {
166 self.inner.new_table_name::<To>();
167
168 self.foreign_key.insert(To::NAME, Box::new(err));
169 }
170
171 pub fn create_empty<To: Table>(&mut self) {
172 self.inner.new_table_name::<To>();
173 }
174
175 pub fn drop_table<T: Table>(&mut self) {
176 let name = Alias::new(T::NAME);
177 let step = sea_query::Table::drop().table(name).take();
178 self.drop.push(step);
179 }
180}