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