Skip to main content

rust_query/migrate/
migration.rs

1use 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
29/// Transaction type for use in migrations.
30pub struct TransactionMigrate<FromSchema> {
31    pub(super) inner: Transaction<FromSchema>,
32    pub(super) scope: Scope,
33    pub(super) rename_map: HashMap<&'static str, TmpTable>,
34    // creating non unique indices is delayed so that they don't need to be renamed
35    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    /// Migrate some rows to the new schema.
77    ///
78    /// This will return an error when there is a conflict.
79    /// The error type depends on the number of unique constraints that the
80    /// migration can violate:
81    /// - 0 => [Infallible]
82    /// - 1.. => `TableRow<T::From>` (row in the old table that could not be migrated)
83    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                // TODO: deduplicate this self.lazy call
92                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    /// Migrate all rows to the new schema.
101    ///
102    /// Conflict errors work the same as in [Self::migrate_optional].
103    ///
104    /// However, this method will return [Migrated] when all rows are migrated.
105    /// This can then be used as proof that there will be no foreign key violations.
106    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    /// Helper method for [Self::migrate].
120    ///
121    /// It can only be used when the migration is known to never cause unique constraint conflicts.
122    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
131/// [Migrated] provides a proof of migration.
132///
133/// This only needs to be provided for tables that are migrated from a previous table.
134pub 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    /// Don't migrate the remaining rows.
142    ///
143    /// This can cause foreign key constraint violations, which is why an error callback needs to be provided.
144    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}