Skip to content

Commit 6dd48b1

Browse files
committed
Add more migration sources
This PR adds more migration sources to diesel_migrations with the goal to make it easier to write different kinds of migrations in multi-crate deployments. It adds a CombinedMigrations source that is essentially just a list of sources. The resulting list of migrations is just all migrations from all sources. That is useful for multi-crate projects as then each sub-project can have their own set of migrations (by using files or embedded migrations etc) and then combine them into a final list using this migration source. As a second variant a RustMigrationSource is added. The goal there is to make it much easier to register rust based migrations by having some sort of typed interface for this. This migration source needs to know the connection type, not only the backend. This information is then used to internally receive the right connection type from the migration connection for the user. The user has the ability to provide different kinds of rust structs/functions all "migration. This includes closures, functions, a migration builder (that in turn accepts clousures but allows more fine grained configuration) or even custom types (via the provided trait).
1 parent 2a878f0 commit 6dd48b1

File tree

9 files changed

+576
-10
lines changed

9 files changed

+576
-10
lines changed

diesel/src/connection/instrumentation.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use downcast_rs::Downcast;
22
use std::fmt::{Debug, Display};
33
use std::num::NonZeroU32;
4-
use std::ops::{Deref, DerefMut};
4+
use std::ops::DerefMut;
55

66
static GLOBAL_INSTRUMENTATION: std::sync::RwLock<fn() -> Option<Box<dyn Instrumentation>>> =
77
std::sync::RwLock::new(|| None);
@@ -321,6 +321,12 @@ where
321321
#[diesel_derives::__diesel_public_if(
322322
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
323323
)]
324+
#[cfg(any(
325+
feature = "postgres",
326+
feature = "sqlite",
327+
feature = "mysql",
328+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
329+
))]
324330
/// An optional dyn instrumentation.
325331
///
326332
/// For ease of use, this type implements [`Deref`] and [`DerefMut`] to `&dyn Instrumentation`,
@@ -335,14 +341,26 @@ pub(crate) struct DynInstrumentation {
335341
inner: Option<Box<dyn Instrumentation>>,
336342
}
337343

338-
impl Deref for DynInstrumentation {
344+
#[cfg(any(
345+
feature = "postgres",
346+
feature = "sqlite",
347+
feature = "mysql",
348+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
349+
))]
350+
impl core::ops::Deref for DynInstrumentation {
339351
type Target = dyn Instrumentation;
340352

341353
fn deref(&self) -> &Self::Target {
342354
self.inner.as_deref().unwrap_or(&self.no_instrumentation)
343355
}
344356
}
345357

358+
#[cfg(any(
359+
feature = "postgres",
360+
feature = "sqlite",
361+
feature = "mysql",
362+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
363+
))]
346364
impl DerefMut for DynInstrumentation {
347365
fn deref_mut(&mut self) -> &mut Self::Target {
348366
self.inner
@@ -351,6 +369,12 @@ impl DerefMut for DynInstrumentation {
351369
}
352370
}
353371

372+
#[cfg(any(
373+
feature = "postgres",
374+
feature = "sqlite",
375+
feature = "mysql",
376+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
377+
))]
354378
impl DynInstrumentation {
355379
/// Create a instance of the default instrumentation provider
356380
#[diesel_derives::__diesel_public_if(
@@ -406,6 +430,12 @@ impl DynInstrumentation {
406430
}
407431
}
408432

433+
#[cfg(any(
434+
feature = "postgres",
435+
feature = "sqlite",
436+
feature = "mysql",
437+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
438+
))]
409439
impl<I: Instrumentation> From<I> for DynInstrumentation {
410440
fn from(instrumentation: I) -> Self {
411441
Self {
@@ -414,14 +444,31 @@ impl<I: Instrumentation> From<I> for DynInstrumentation {
414444
}
415445
}
416446
}
417-
447+
#[cfg(any(
448+
feature = "postgres",
449+
feature = "sqlite",
450+
feature = "mysql",
451+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
452+
))]
418453
struct NoInstrumentation;
419454

455+
#[cfg(any(
456+
feature = "postgres",
457+
feature = "sqlite",
458+
feature = "mysql",
459+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
460+
))]
420461
impl Instrumentation for NoInstrumentation {
421462
fn on_connection_event(&mut self, _: InstrumentationEvent<'_>) {}
422463
}
423464

424465
/// Unwrap unnecessary boxing levels
466+
#[cfg(any(
467+
feature = "postgres",
468+
feature = "sqlite",
469+
feature = "mysql",
470+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
471+
))]
425472
fn unpack_instrumentation(
426473
mut instrumentation: Box<dyn Instrumentation>,
427474
) -> Box<dyn Instrumentation> {

diesel/src/query_source/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,15 @@ pub trait AppearsInFromClause<QS> {
142142
///
143143
/// (Notably, a bunch of [`AppearsInFromClause`] for the tables and their aliases.)
144144
///
145-
/// This trait is implemented by the [`allow_tables_to_appear_in_same_query!`] macro.
145+
/// This trait is implemented by the
146+
/// [`allow_tables_to_appear_in_same_query!`](crate::allow_tables_to_appear_in_same_query)
147+
/// macro.
146148
///
147149
/// Troubleshooting
148150
/// ---------------
149151
/// If you encounter an error mentioning this trait, it could mean that either:
150152
/// - You are attempting to use tables that don't belong to the same database together
151-
/// (no call to [`allow_tables_to_appear_in_same_query!`] was made)
153+
/// (no call to [`allow_tables_to_appear_in_same_query!`](crate::allow_tables_to_appear_in_same_query) was made)
152154
/// - You are attempting to use two aliases to the same table in the same query, but they
153155
/// were declared through different calls to [`alias!`](crate::alias)
154156
#[diagnostic::on_unimplemented(
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::sync::Arc;
2+
3+
use diesel::backend::Backend;
4+
use diesel::migration::{Migration, MigrationSource};
5+
6+
/// A diesel migration source that combines several other sources
7+
///
8+
/// This source will act like all migrations came from a single source.
9+
/// It orders all the migrations by version
10+
///
11+
/// # Example
12+
/// ```
13+
/// # include!("../../diesel/src/doctest_setup.rs");
14+
/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
15+
/// use diesel::prelude::*;
16+
/// use diesel_migrations::EmbeddedMigrations;
17+
/// use diesel_migrations::CombinedMigrationSource;
18+
/// use crate::diesel_migrations::MigrationHarness;
19+
/// use migrations_macros::embed_migrations;
20+
///
21+
/// pub const PG_MIGRATIONS: EmbeddedMigrations = embed_migrations!("../migrations/postgres");
22+
/// pub const SQLITE_MIGRATIONS: EmbeddedMigrations = embed_migrations!("../migrations/sqlite");
23+
///
24+
/// # #[cfg(feature = "postgres")]
25+
/// # let connection_url = database_url_from_env("PG_DATABASE_URL");
26+
/// # #[cfg(feature = "sqlite")]
27+
/// # let connection_url = database_url_from_env("SQLITE_DATABASE_URL");
28+
/// # #[cfg(feature = "mysql")]
29+
/// # let connection_url = database_url_from_env("MYSQL_DATABASE_URL");
30+
/// # #[cfg(feature = "postgres")]
31+
/// # type SqliteConnection = PgConnection;
32+
/// # #[cfg(feature = "mysql")]
33+
/// # type SqliteConnection = MysqlConnection;
34+
/// #
35+
/// // Create a new empty combined source
36+
/// let mut combined_sources = CombinedMigrationSource::default();
37+
///
38+
/// // It's not particular meaningful to combine PostgreSQL and SQLite like this,
39+
/// // but that reasonable demonstrates the API
40+
/// combined_sources.add_source(PG_MIGRATIONS);
41+
/// combined_sources.add_source(SQLITE_MIGRATIONS);
42+
///
43+
/// // run the migrations
44+
/// let mut connection = SqliteConnection::establish(&connection_url)?;
45+
/// let res = connection.run_pending_migrations(combined_sources);
46+
/// # assert!(res.is_err(), "This is supposed to fail as you cannot run postgres migrations using sqlite");
47+
/// # Ok(())
48+
/// # }
49+
/// ```
50+
#[derive(Default, Clone)]
51+
pub struct CombinedMigrationSource<DB> {
52+
migrations: Vec<Arc<dyn MigrationSource<DB> + Send + Sync>>,
53+
}
54+
55+
impl<DB> CombinedMigrationSource<DB>
56+
where
57+
DB: Backend,
58+
{
59+
/// Register another source with the given migration source
60+
pub fn add_source(&mut self, source: impl MigrationSource<DB> + Send + Sync + 'static) {
61+
self.migrations.push(Arc::new(source))
62+
}
63+
}
64+
65+
impl<DB> MigrationSource<DB> for CombinedMigrationSource<DB>
66+
where
67+
DB: Backend,
68+
{
69+
fn migrations(&self) -> diesel::migration::Result<Vec<Box<dyn Migration<DB>>>> {
70+
let mut migrations = Vec::new();
71+
for source in &self.migrations {
72+
migrations.extend(source.migrations()?);
73+
}
74+
migrations.sort_by(|m1, m2| m1.name().version().cmp(&m2.name().version()));
75+
Ok(migrations)
76+
}
77+
}

diesel_migrations/src/lib.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,23 @@
3232
//! Migrations can either be run with the CLI or embedded into the compiled application
3333
//! and executed with code, for example right after establishing a database connection.
3434
//! For more information, consult the [`embed_migrations!`] macro.
35+
//!
36+
//! You can also define migrations in your rust code by using the [`RustMigrationSource`].
37+
//! The [`CombinedMigrationSource`] allows you to combine migrations from different sources
38+
//! to execute them together
3539
40+
mod combined_migrations;
3641
mod embedded_migrations;
3742
mod errors;
3843
mod file_based_migrations;
3944
mod migration_harness;
45+
mod rust_migrations;
4046

41-
pub use crate::embedded_migrations::EmbeddedMigrations;
42-
pub use crate::file_based_migrations::FileBasedMigrations;
43-
pub use crate::migration_harness::{HarnessWithOutput, MigrationHarness};
47+
pub use self::embedded_migrations::EmbeddedMigrations;
48+
pub use self::file_based_migrations::FileBasedMigrations;
49+
pub use self::migration_harness::{HarnessWithOutput, MigrationHarness};
50+
pub use self::rust_migrations::{RustMigration, RustMigrationSource, TypedMigration};
51+
pub use combined_migrations::CombinedMigrationSource;
4452
pub use migrations_macros::embed_migrations;
4553

4654
#[doc(hidden)]

diesel_migrations/src/migration_harness.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ pub trait MigrationHarness<DB: Backend> {
118118
.into_iter()
119119
.map(|m| (m.name().version().as_owned(), m))
120120
.collect::<HashMap<_, _>>();
121-
122121
for applied_version in applied_versions {
123122
migrations.remove(&applied_version);
124123
}

0 commit comments

Comments
 (0)