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(Debug, 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 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}