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, IntoTableRef, TableDropStatement};
9
10use crate::{
11 IntoExpr, Lazy, Table, TableRow, Transaction,
12 alias::{Scope, TmpTable},
13 migrate::new_table_inner,
14 transaction::{TXN, 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(
25 val: Self,
26 prev: crate::Expr<'static, Self::FromSchema, Self::From>,
27 ) -> <Self::To as Table>::Insert;
28 #[doc(hidden)]
29 fn map_conflict(val: TableRow<Self::From>) -> Self::Conflict;
30}
31
32pub struct TransactionMigrate<FromSchema> {
34 pub(super) inner: Transaction<FromSchema>,
35 pub(super) scope: Scope,
36 pub(super) rename_map: HashMap<&'static str, TmpTable>,
37 pub(super) extra_index: Vec<String>,
39}
40
41impl<FromSchema> Deref for TransactionMigrate<FromSchema> {
42 type Target = Transaction<FromSchema>;
43
44 fn deref(&self) -> &Self::Target {
45 &self.inner
46 }
47}
48
49impl<FromSchema: 'static> TransactionMigrate<FromSchema> {
50 fn new_table_name<T: Table>(&mut self) -> TmpTable {
51 *self.rename_map.entry(T::NAME).or_insert_with(|| {
52 let new_table_name = self.scope.tmp_table();
53 TXN.with_borrow(|txn| {
54 let conn = txn.as_ref().unwrap().get();
55 let table = crate::schema::from_macro::Table::new::<T>();
56 new_table_inner(conn, &table, new_table_name);
57 self.extra_index.extend(table.create_indices(T::NAME));
58 });
59 new_table_name
60 })
61 }
62
63 fn unmigrated<M: Migration<FromSchema = FromSchema>>(
64 &self,
65 new_name: TmpTable,
66 ) -> impl Iterator<Item = TableRow<M::From>> {
67 let data = self.inner.query(|rows| {
68 let old = rows.join_private::<M::From>();
69 rows.into_vec(old)
70 });
71
72 let migrated = Transaction::new().query(|rows| {
73 let new = rows.join_tmp::<M::To>(new_name);
74 rows.into_vec(new)
75 });
76 let migrated: HashSet<_> = migrated.into_iter().map(|x| x.inner.idx).collect();
77
78 data.into_iter()
79 .filter(move |row| !migrated.contains(&row.inner.idx))
80 }
81
82 pub fn migrate_optional<'t, M: Migration<FromSchema = FromSchema>>(
90 &'t mut self,
91 mut f: impl FnMut(Lazy<'t, M::From>) -> Option<M>,
92 ) -> Result<(), M::Conflict> {
93 let new_name = self.new_table_name::<M::To>();
94
95 for row in self.unmigrated::<M>(new_name) {
96 if let Some(new) = f(self.lazy(row)) {
97 try_insert_private::<M::To>(
98 new_name.into_table_ref(),
99 Some(row.inner.idx),
100 M::prepare(new, row.into_expr()),
101 )
102 .map_err(|_| M::map_conflict(row))?;
103 };
104 }
105 Ok(())
106 }
107
108 pub fn migrate<'t, M: Migration<FromSchema = FromSchema>>(
115 &'t mut self,
116 mut f: impl FnMut(Lazy<'t, M::From>) -> M,
117 ) -> Result<Migrated<'static, FromSchema, M::To>, M::Conflict> {
118 self.migrate_optional::<M>(|x| Some(f(x)))?;
119
120 Ok(Migrated {
121 _p: PhantomData,
122 f: Box::new(|_| {}),
123 _local: PhantomData,
124 })
125 }
126
127 pub fn migrate_ok<'t, M: Migration<FromSchema = FromSchema, Conflict = Infallible>>(
131 &'t mut self,
132 f: impl FnMut(Lazy<'t, M::From>) -> M,
133 ) -> Migrated<'static, FromSchema, M::To> {
134 let Ok(res) = self.migrate(f);
135 res
136 }
137}
138
139pub struct Migrated<'t, FromSchema, T> {
143 _p: PhantomData<T>,
144 f: Box<dyn 't + FnOnce(&mut SchemaBuilder<'t, FromSchema>)>,
145 _local: PhantomData<*const ()>,
146}
147
148impl<'t, FromSchema: 'static, T: Table> Migrated<'t, FromSchema, T> {
149 pub fn map_fk_err(err: impl 't + FnOnce() -> Infallible) -> Self {
153 Self {
154 _p: PhantomData,
155 f: Box::new(|x| x.foreign_key::<T>(err)),
156 _local: PhantomData,
157 }
158 }
159
160 #[doc(hidden)]
161 pub fn apply(self, b: &mut SchemaBuilder<'t, FromSchema>) {
162 (self.f)(b)
163 }
164}
165
166pub struct SchemaBuilder<'t, FromSchema> {
167 pub(super) inner: TransactionMigrate<FromSchema>,
168 pub(super) drop: Vec<TableDropStatement>,
169 pub(super) foreign_key: HashMap<&'static str, Box<dyn 't + FnOnce() -> Infallible>>,
170}
171
172impl<'t, FromSchema: 'static> SchemaBuilder<'t, FromSchema> {
173 pub fn foreign_key<To: Table>(&mut self, err: impl 't + FnOnce() -> Infallible) {
174 self.inner.new_table_name::<To>();
175
176 self.foreign_key.insert(To::NAME, Box::new(err));
177 }
178
179 pub fn create_empty<To: Table>(&mut self) {
180 self.inner.new_table_name::<To>();
181 }
182
183 pub fn drop_table<T: Table>(&mut self) {
184 let name = Alias::new(T::NAME);
185 let step = sea_query::Table::drop().table(name).take();
186 self.drop.push(step);
187 }
188}