1use crate::migration_names::MigrationNames;
4use crate::sqlite::DatabaseConnection;
5use crate::{DbError, MigrationError};
6use rusqlite::{Params, Row};
7use std::cell::RefCell;
8use std::sync::Arc;
9
10#[derive(Clone, Debug, Eq, PartialEq)]
12pub enum MigrationChange {
13 NoChange,
16 Addition,
18 Breaking,
21}
22
23#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct MigrationOutcome {
26 pub version: u32,
28 pub name: &'static str,
30 pub change: MigrationChange,
32}
33
34pub(crate) type MigrationOperation =
35 dyn Fn(u32) -> Result<crate::NewOperation, DbError> + Send + Sync;
36
37type WriteConversion = dyn Fn(&mut crate::RowChange) -> Result<(), DbError> + Send + Sync;
38
39type MigrationBody = dyn Fn(&MigrationContext<'_>) -> Result<(), DbError> + Send + Sync;
40
41#[derive(Clone)]
43pub enum OldWrites {
44 Convert(Arc<WriteConversion>),
46 Drop,
48}
49
50#[derive(Clone)]
51pub(crate) enum MigrationKind {
52 Addition,
53 Breaking(OldWrites),
54}
55
56#[derive(Clone)]
59pub struct Migration {
60 pub(crate) version: u32,
61 pub(crate) name: &'static str,
62 body: Arc<MigrationBody>,
63 pub(crate) kind: MigrationKind,
64}
65
66impl Migration {
67 pub fn addition(version: u32, name: &'static str, sql: &'static str) -> Self {
70 Self::addition_with(version, name, move |context| {
71 context.execute_batch(sql)?;
72 Ok(())
73 })
74 }
75
76 pub fn addition_with<F>(version: u32, name: &'static str, f: F) -> Self
78 where
79 F: Fn(&MigrationContext<'_>) -> Result<(), DbError> + Send + Sync + 'static,
80 {
81 Self { version, name, body: Arc::new(f), kind: MigrationKind::Addition }
82 }
83
84 pub fn breaking(
93 version: u32,
94 name: &'static str,
95 sql: &'static str,
96 old_writes: OldWrites,
97 ) -> Self {
98 Self::breaking_with(
99 version,
100 name,
101 move |context| {
102 context.execute_batch(sql)?;
103 Ok(())
104 },
105 old_writes,
106 )
107 }
108
109 pub fn breaking_with<F>(version: u32, name: &'static str, f: F, old_writes: OldWrites) -> Self
119 where
120 F: Fn(&MigrationContext<'_>) -> Result<(), DbError> + Send + Sync + 'static,
121 {
122 Self { version, name, body: Arc::new(f), kind: MigrationKind::Breaking(old_writes) }
123 }
124
125 pub(crate) fn check_kind(&self, breaking: bool) -> Result<(), DbError> {
126 if matches!(self.kind, MigrationKind::Breaking(_)) != breaking {
127 return Err(DbError::MigrationKindMismatch { version: self.version });
128 }
129 Ok(())
130 }
131
132 pub(crate) fn apply(&self, context: &MigrationContext<'_>) -> Result<(), DbError> {
133 (self.body)(context)
134 }
135}
136
137pub(crate) fn validate_versions(migrations: &[Migration]) -> Result<u32, MigrationError> {
138 let mut supported = 0u32;
139 for (position, migration) in migrations.iter().enumerate() {
140 let expected = supported.checked_add(1).expect("migration list fits in u32");
141 if migration.version != expected {
142 return Err(MigrationError::NotContiguous {
143 position,
144 found: migration.version,
145 expected,
146 });
147 }
148 supported = expected;
149 }
150 Ok(supported)
151}
152
153pub struct MigrationContext<'connection> {
156 database: &'connection DatabaseConnection,
157 names: RefCell<MigrationNames>,
158 before: crate::schema::Schema,
160 capture: RefCell<crate::migration_snapshot::MigrationCapture<'connection>>,
162}
163
164impl<'connection> MigrationContext<'connection> {
165 pub(crate) fn new(
166 database: &'connection DatabaseConnection,
167 before: crate::schema::Schema,
168 capture: crate::migration_snapshot::MigrationCapture<'connection>,
169 ) -> Self {
170 Self {
171 database,
172 names: RefCell::new(MigrationNames::new(&before)),
173 before,
174 capture: RefCell::new(capture),
175 }
176 }
177
178 pub(crate) fn finish(
181 self,
182 after: &crate::schema::Schema,
183 ) -> (
184 crate::migration_names::MigrationEffects,
185 crate::schema::Schema,
186 crate::migration_snapshot::MigrationCapture<'connection>,
187 ) {
188 (
189 self.names.into_inner().finish(&self.before, after),
190 self.before,
191 self.capture.into_inner(),
192 )
193 }
194
195 fn statement<T>(
196 &self,
197 sql: &str,
198 run: impl FnOnce() -> rusqlite::Result<T>,
199 ) -> rusqlite::Result<T> {
200 self.before_statement(sql)?;
201 let before = self.database.schema_cookie()?;
202 let result = run();
203 if self.database.schema_cookie()? != before {
204 self.record(sql)?;
205 }
206 result
207 }
208
209 pub(crate) fn before_statement(&self, sql: &str) -> rusqlite::Result<()> {
210 self.capture.borrow_mut().before(&self.before, sql)
211 }
212
213 pub(crate) fn record(&self, sql: &str) -> rusqlite::Result<()> {
214 self.names.borrow_mut().record(sql)
215 }
216
217 pub fn execute<P: Params>(&self, sql: &str, params: P) -> rusqlite::Result<usize> {
219 self.statement(sql, || self.database.app_execute(sql, params))
220 }
221
222 pub fn execute_batch(&self, sql: &str) -> rusqlite::Result<()> {
224 self.database.app_batch_tracked(sql, Some(self))
225 }
226
227 pub fn query_row<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<T>
229 where
230 P: Params,
231 F: FnOnce(&Row<'_>) -> rusqlite::Result<T>,
232 {
233 self.statement(sql, || self.database.app_query_row(sql, params, map))
234 }
235
236 pub fn query<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<Vec<T>>
238 where
239 P: Params,
240 F: FnMut(&Row<'_>) -> rusqlite::Result<T>,
241 {
242 self.statement(sql, || self.database.app_query(sql, params, map))
243 }
244}
245
246#[cfg(test)]
247#[path = "migration_tests.rs"]
248pub(crate) mod tests;