Skip to main content

diesel_migrations/
rust_migrations.rs

1use std::sync::Arc;
2
3use diesel::migration::{
4    Migration, MigrationMetadata, MigrationName, MigrationSource, MigrationVersion,
5};
6use diesel::{Connection, QueryResult};
7
8use crate::MigrationError;
9
10/// A migration source that allows to register rust functions as migrations
11///
12/// The main use-case of this migration source is to allow writing migrations in Rust
13/// instead of in SQL. You need to specify at construction time which for which connection
14/// type this migration source is indented to be used with.
15/// It allows you to register different kinds of hooks to be executed as migrations later:
16///
17/// * A closure `Fn(&mut Conn) -> QueryResult<()>` that accepts a mutable connection
18///   reference as argument and returns a `QueryResult<()>`. This closure is executed as
19///   **up migration**.
20/// * A function with a signature of `fn(&mut Conn) -> QueryResult<()>`. This function
21///   is executed as **up migration**.
22/// * An instance of [`RustMigration`], which allows you to register an up and a down migration
23///   as required. This type allows you also to configure the migration behaviour.
24///   See the documentation there for details.
25/// * Any type implementing [`TypedMigration`]. This allows you to provide a custom type with
26///   custom fields to supply additional information to your migration. This trait gives you the
27///   full control over how the migration should behave. See the documentation there for details.
28///
29/// A single migration source can mix all of the variants of migrations listed above.
30///
31/// # Example
32/// ```
33/// # include!("../../diesel/src/doctest_setup.rs");
34/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
35/// use diesel::prelude::*;
36/// use diesel_migrations::MigrationHarness;
37/// use diesel_migrations::RustMigration;
38/// use diesel_migrations::RustMigrationSource;
39/// use diesel_migrations::TypedMigration;
40///
41/// # #[cfg(feature = "postgres")]
42/// # let connection_url = database_url_from_env("PG_DATABASE_URL");
43/// # // An in-memory database avoids lock races between concurrently running doctests
44/// # // and keeps this example's applied migrations out of the shared test database.
45/// # #[cfg(feature = "sqlite")]
46/// # let connection_url = String::from(":memory:");
47/// # #[cfg(feature = "mysql")]
48/// # let connection_url = database_url_from_env("MYSQL_DATABASE_URL");
49/// # #[cfg(feature = "mariadb")]
50/// # let connection_url = database_url_from_env("MARIADB_DATABASE_URL");
51/// # #[cfg(feature = "postgres")]
52/// # type SqliteConnection = PgConnection;
53/// # #[cfg(feature = "mysql")]
54/// # type SqliteConnection = MysqlConnection;
55/// # #[cfg(feature = "mariadb")]
56/// # type SqliteConnection = MariadbConnection;
57/// fn migration_function(conn: &mut SqliteConnection) -> QueryResult<()> {
58///     diesel::sql_query("SELECT 'EXECUTE YOUR MIGRATION HERE'").execute(conn)?;
59///     Ok(())
60/// }
61///
62/// struct CustomMigration(&'static str);
63///
64/// impl TypedMigration<SqliteConnection> for CustomMigration {
65///     fn up(&self, conn: &mut SqliteConnection) -> QueryResult<()> {
66/// #       #[cfg(feature = "sqlite")]
67///         diesel::sql_query("SELECT 'YOUR MIGRATION', ?")
68///             .bind::<diesel::sql_types::Text, _>(self.0)
69///             .execute(conn)?;
70///         Ok(())
71///     }
72/// }
73///
74/// let mut rust_migrations = RustMigrationSource::<SqliteConnection>::new();
75///
76/// // register migrations callback
77/// rust_migrations.add_migration(
78///     "2026-01-30-121720",
79///     "callback",
80///     |conn: &mut SqliteConnection| {
81///         diesel::sql_query("SELECT 'EXECUTE YOUR MIGRATION HERE'").execute(conn)?;
82///         Ok(())
83///     },
84/// );
85///
86/// // register a migration as function
87/// rust_migrations.add_migration("2026-01-30-122420", "function", migration_function);
88///
89/// // register a RustMigration
90/// let migration = RustMigration::new(|conn| {
91///     diesel::sql_query("SELECT 'EXECUTE YOUR MIGRATION HERE'").execute(conn)?;
92///     Ok(())
93/// })
94/// .with_down(|conn| {
95///     diesel::sql_query("SELECT 'REVERT YOUR MIGRATION HERE'").execute(conn)?;
96///     Ok(())
97/// });
98/// rust_migrations.add_migration("2026-01-30-122520", "rust_migration", migration);
99///
100/// // register a custom migration type
101/// rust_migrations.add_migration(
102///     "2026-01-30-122920", "custom_type",
103///     CustomMigration("your custom args"),
104/// );
105///
106/// // run the migrations
107/// let mut conn = SqliteConnection::establish(&connection_url)?;
108/// conn.run_pending_migrations(rust_migrations)?;
109/// # Ok(())
110/// # }
111/// ```
112pub struct RustMigrationSource<Conn>
113where
114    Conn: Connection,
115{
116    migrations: Vec<FunctionBasedMigration<Conn>>,
117}
118
119impl<Conn> Default for RustMigrationSource<Conn>
120where
121    Conn: Connection,
122{
123    fn default() -> Self {
124        Self {
125            migrations: Default::default(),
126        }
127    }
128}
129
130impl<Conn> Clone for RustMigrationSource<Conn>
131where
132    Conn: Connection,
133{
134    fn clone(&self) -> Self {
135        Self {
136            migrations: self.migrations.clone(),
137        }
138    }
139}
140
141impl<Conn> MigrationSource<Conn::Backend> for RustMigrationSource<Conn>
142where
143    Conn: Connection + 'static,
144{
145    fn migrations(&self) -> diesel::migration::Result<Vec<Box<dyn Migration<Conn::Backend>>>> {
146        Ok(self
147            .migrations
148            .iter()
149            .map(|m| Box::new(m.clone()) as Box<dyn Migration<Conn::Backend>>)
150            .collect())
151    }
152}
153
154impl<Conn> RustMigrationSource<Conn>
155where
156    Conn: Connection,
157{
158    /// Create a new empty migration source
159    pub fn new() -> Self {
160        Self {
161            migrations: Vec::new(),
162        }
163    }
164
165    /// Register a new migration
166    ///
167    /// See the documentation on the type itself for examples
168    pub fn add_migration(
169        &mut self,
170        version: impl Into<String>,
171        name: impl Into<String>,
172        migration: impl TypedMigration<Conn> + 'static,
173    ) -> Result<&mut Self, MigrationError> {
174        self.migrations.push(FunctionBasedMigration {
175            migration: Arc::new(migration),
176            name: RustMigrationName {
177                version: version.into(),
178                name: name.into(),
179            },
180        });
181        Ok(self)
182    }
183}
184
185#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RustMigrationName {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "RustMigrationName", "version", &self.version, "name",
            &&self.name)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RustMigrationName {
    #[inline]
    fn clone(&self) -> RustMigrationName {
        RustMigrationName {
            version: ::core::clone::Clone::clone(&self.version),
            name: ::core::clone::Clone::clone(&self.name),
        }
    }
}Clone)]
186struct RustMigrationName {
187    version: String,
188    name: String,
189}
190
191impl std::fmt::Display for RustMigrationName {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        f.write_fmt(format_args!("{0}_{1}", self.version, self.name))write!(f, "{}_{}", self.version, self.name)
194    }
195}
196
197impl MigrationName for RustMigrationName {
198    fn version(&self) -> MigrationVersion<'_> {
199        MigrationVersion::from(&self.version)
200    }
201}
202
203/// A typed rust migration for a given connection type
204///
205/// This type describes a typed rust migration for a specific connection type `Conn`
206///
207/// This trait only requires you to provide an up migration by implementing the relevant function.
208/// Optionally you can overwrite the down migration and the migration settings by providing a custom
209/// implementation for the relevant functions.
210pub trait TypedMigration<Conn> {
211    /// The implementation of the up migration.
212    ///
213    /// This function is supposed to migrate your database from an old schema version
214    /// considered valid before this migration was written to a new schema version expected
215    /// after this migration was written.
216    ///
217    /// This will be run inside of a transaction if `Self::run_in_transaction` is
218    /// is not customized to return `false`
219    fn up(&self, conn: &mut Conn) -> QueryResult<()>;
220
221    /// The implementation of the down migration.
222    ///
223    /// This function is supposed to revert everything that's done in your up migration.
224    ///
225    /// The default implementation doesn't perform any action. If you never plan to
226    /// revert migrations it can be fine to not provide a custom implementation of
227    /// this function.
228    ///
229    /// This will be run inside of a transaction if `Self::run_in_transaction` is
230    /// is not customized to return `false`
231    fn down(&self, _conn: &mut Conn) -> QueryResult<()> {
232        Ok(())
233    }
234
235    /// Should the given migration be run in a transaction or not
236    ///
237    /// By default diesel runs migrations inside of transactions
238    /// (if the underlying database system supports that). This ensures
239    /// that each migration is ever only executed as single unit or fails as
240    /// single unit.
241    ///
242    /// Nevertheless specific database operations might require to be run
243    /// outside of transactions. If you plan to use such an operation you
244    /// want to provide a custom implementation of this function that returns
245    /// `false`
246    fn run_in_transaction(&self) -> bool {
247        true
248    }
249}
250
251impl<Conn, F> TypedMigration<Conn> for F
252where
253    F: Fn(&mut Conn) -> QueryResult<()>,
254{
255    fn up(&self, conn: &mut Conn) -> QueryResult<()> {
256        self(conn)
257    }
258}
259
260type MigrationFunction<Conn> = dyn Fn(&mut Conn) -> QueryResult<()>;
261
262/// A rust side migration
263///
264/// This type represents a simple rust side migration builder
265/// that allows you to register a rust callback as up and down migration
266/// and that also allows you to customize the migration settings
267///
268/// Constructing a `RustMigration` requires to specify an up migration that
269/// describes how to migrate your database schema from an old to an new version.
270///
271/// Down migrations, that describe how to revert the changes done by the up migrations,
272/// are optional. If you don't plan to revert migrations you don't need to provide them.
273pub struct RustMigration<Conn> {
274    up: Box<MigrationFunction<Conn>>,
275    down: Option<Box<MigrationFunction<Conn>>>,
276    run_in_transaction: bool,
277}
278
279impl<Conn> RustMigration<Conn> {
280    /// Construct a new instance of this type with a given up migration function.
281    ///
282    /// This function needs to perform any action to migrate your database from an old version
283    /// to the expected new version
284    pub fn new(up: impl Fn(&mut Conn) -> QueryResult<()> + 'static) -> Self {
285        Self {
286            up: Box::new(up),
287            down: None,
288            run_in_transaction: true,
289        }
290    }
291
292    /// Register a down migration
293    ///
294    /// This function allows you to register a down migration to revert any changes done
295    /// by the up migration. It is used to restore the database schema used before this migration
296    /// was applied in the case of an revert. If you don't plan to revert migrations you don't need to
297    /// provide a down migration.
298    pub fn with_down(mut self, down: impl Fn(&mut Conn) -> QueryResult<()> + 'static) -> Self {
299        self.down = Some(Box::new(down));
300        self
301    }
302
303    /// Customizes the migration settings to not run this migration in a transaction
304    ///
305    /// By default diesel will execute migrations inside of a transaction on all database systems
306    /// supporting this to ensure that migrations are either fully executed or not.
307    ///
308    /// Some database operations require to be run outside transactions. If you use such an
309    /// operation in either your up or down migration you need to use this function to disable
310    /// the default transaction behaviour.
311    pub fn without_transaction(mut self) -> Self {
312        self.run_in_transaction = false;
313        self
314    }
315}
316
317impl<Conn> TypedMigration<Conn> for RustMigration<Conn> {
318    fn up(&self, conn: &mut Conn) -> QueryResult<()> {
319        (self.up)(conn)
320    }
321
322    fn down(&self, conn: &mut Conn) -> QueryResult<()> {
323        if let Some(down) = self.down.as_deref() {
324            down(conn)?
325        }
326        Ok(())
327    }
328
329    fn run_in_transaction(&self) -> bool {
330        self.run_in_transaction
331    }
332}
333
334struct FunctionBasedMigration<Conn> {
335    migration: Arc<dyn TypedMigration<Conn>>,
336    name: RustMigrationName,
337}
338
339impl<Conn> Clone for FunctionBasedMigration<Conn> {
340    fn clone(&self) -> Self {
341        Self {
342            migration: self.migration.clone(),
343            name: self.name.clone(),
344        }
345    }
346}
347
348impl<Conn> Migration<Conn::Backend> for FunctionBasedMigration<Conn>
349where
350    Conn: Connection + 'static,
351    Conn::Backend: 'static,
352{
353    fn run(
354        &self,
355        conn: &mut dyn diesel::connection::BoxableConnection<Conn::Backend>,
356    ) -> diesel::migration::Result<()> {
357        let conn = conn
358            .downcast_mut::<Conn>()
359            .ok_or("Unable to downcast connection type to the right type")?;
360        self.migration.up(conn)?;
361        Ok(())
362    }
363
364    fn revert(
365        &self,
366        conn: &mut dyn diesel::connection::BoxableConnection<Conn::Backend>,
367    ) -> diesel::migration::Result<()> {
368        let conn = conn
369            .downcast_mut::<Conn>()
370            .ok_or("Unable to downcast connection type to the right type")?;
371        self.migration.down(conn)?;
372        Ok(())
373    }
374
375    fn metadata(&self) -> &dyn MigrationMetadata {
376        self as &dyn MigrationMetadata
377    }
378
379    fn name(&self) -> &dyn diesel::migration::MigrationName {
380        &self.name
381    }
382}
383
384impl<Conn> MigrationMetadata for FunctionBasedMigration<Conn> {
385    fn run_in_transaction(&self) -> bool {
386        self.migration.run_in_transaction()
387    }
388}