Skip to content

767 no tx migration #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sqlx-core/src/migrate/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct Migration {
pub migration_type: MigrationType,
pub sql: Cow<'static, str>,
pub checksum: Cow<'static, [u8]>,
pub no_tx: bool,
}

impl Migration {
Expand All @@ -19,6 +20,7 @@ impl Migration {
description: Cow<'static, str>,
migration_type: MigrationType,
sql: Cow<'static, str>,
no_tx: bool,
) -> Self {
let checksum = Cow::Owned(Vec::from(Sha384::digest(sql.as_bytes()).as_slice()));

Expand All @@ -28,6 +30,7 @@ impl Migration {
migration_type,
sql,
checksum,
no_tx,
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions sqlx-core/src/migrate/migrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub struct Migrator {
pub ignore_missing: bool,
#[doc(hidden)]
pub locking: bool,
#[doc(hidden)]
pub no_tx: bool
}

fn validate_applied_migrations(
Expand All @@ -47,6 +49,7 @@ impl Migrator {
pub const DEFAULT: Migrator = Migrator {
migrations: Cow::Borrowed(&[]),
ignore_missing: false,
no_tx: false,
locking: true,
};

Expand Down
8 changes: 7 additions & 1 deletion sqlx-core/src/migrate/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub fn resolve_blocking(path: PathBuf) -> Result<Vec<(Migration, PathBuf)>, Reso
let parts = file_name.splitn(2, '_').collect::<Vec<_>>();

if parts.len() != 2 || !parts[1].ends_with(".sql") {
// not of the format: <VERSION>_<DESCRIPTION>.sql; ignore
// not of the format: <VERSION>_<DESCRIPTION>.<REVERSIBLE_DIRECTION>.sql; ignore
continue;
}

Expand All @@ -108,9 +108,11 @@ pub fn resolve_blocking(path: PathBuf) -> Result<Vec<(Migration, PathBuf)>, Reso
})?;

let migration_type = MigrationType::from_filename(parts[1]);

// remove the `.sql` and replace `_` with ` `
let description = parts[1]
.trim_end_matches(migration_type.suffix())
.trim_end_matches(".no_tx")
.replace('_', " ")
.to_owned();

Expand All @@ -122,12 +124,16 @@ pub fn resolve_blocking(path: PathBuf) -> Result<Vec<(Migration, PathBuf)>, Reso
source: Some(e),
})?;

// opt-out of migration transaction
let no_tx = sql.starts_with("-- no-transaction");

migrations.push((
Migration::new(
version,
Cow::Owned(description),
migration_type,
Cow::Owned(sql),
no_tx,
),
entry_path,
));
Expand Down
2 changes: 2 additions & 0 deletions sqlx-macros-core/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl ToTokens for QuoteMigration {
description,
migration_type,
checksum,
no_tx,
..
} = &self.migration;

Expand Down Expand Up @@ -69,6 +70,7 @@ impl ToTokens for QuoteMigration {
description: ::std::borrow::Cow::Borrowed(#description),
migration_type: #migration_type,
sql: ::std::borrow::Cow::Borrowed(#sql),
no_tx: #no_tx,
checksum: ::std::borrow::Cow::Borrowed(&[
#(#checksum),*
]),
Expand Down
54 changes: 35 additions & 19 deletions sqlx-postgres/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,30 +208,46 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations (
migration: &'m Migration,
) -> BoxFuture<'m, Result<Duration, MigrateError>> {
Box::pin(async move {
let mut tx = self.begin().await?;
let start = Instant::now();

// Use a single transaction for the actual migration script and the essential bookeeping so we never
// execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966.
// The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for
// data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1
// and update it once the actual transaction completed.
let _ = tx.execute(&*migration.sql).await?;

// language=SQL
let _ = query(
r#"
if !migration.no_tx {
let mut tx = self.begin().await?;

// Use a single transaction for the actual migration script and the essential bookeeping so we never
// execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966.
// The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for
// data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1
// and update it once the actual transaction completed.
let _ = tx.execute(&*migration.sql).await?;

// language=SQL
let _ = query(
r#"
INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time )
VALUES ( $1, $2, TRUE, $3, -1 )
"#,
)
.bind(migration.version)
.bind(&*migration.description)
.bind(&*migration.checksum)
.execute(&mut *tx)
.await?;
)
.bind(migration.version)
.bind(&*migration.description)
.bind(&*migration.checksum)
.execute(&mut *tx)
.await?;

tx.commit().await?;
tx.commit().await?;
} else {
query(&migration.sql).execute(&mut *self).await?;
// language=SQL
let _ = query(
r#"
INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time )
VALUES ( $1, $2, TRUE, $3, -1 )
"#,
)
.bind(migration.version)
.bind(&migration.description)
.bind(&*migration.checksum)
.execute(&mut *self)
.await?;
}

// Update `elapsed_time`.
// NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept
Expand Down
1 change: 1 addition & 0 deletions src/macros/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ use sqlx::{PgPool, Row};
# migrations: Cow::Borrowed(&[]),
# ignore_missing: false,
# locking: true,
# no_tx: false
# };
# }

Expand Down
20 changes: 20 additions & 0 deletions tests/postgres/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,28 @@ async fn reversible(mut conn: PoolConnection<Postgres>) -> anyhow::Result<()> {
Ok(())
}

#[sqlx::test(migrations = false)]
async fn no_tx(mut conn: PoolConnection<Postgres>) -> anyhow::Result<()> {
clean_up(&mut conn).await?;
let migrator = Migrator::new(Path::new("tests/postgres/migrations_no_tx")).await?;

// run migration
migrator.run(&mut conn).await?;

// check outcome
let res: String = conn
.fetch_one("SELECT datname FROM pg_database WHERE datname = 'test_db'")
.await?
.get(0);

assert_eq!(res, "test_db");

Ok(())
}

/// Ensure that we have a clean initial state.
async fn clean_up(conn: &mut PgConnection) -> anyhow::Result<()> {
conn.execute("DROP DATABASE IF EXISTS test_db").await.ok();
conn.execute("DROP TABLE migrations_simple_test").await.ok();
conn.execute("DROP TABLE migrations_reversible_test")
.await
Expand Down
3 changes: 3 additions & 0 deletions tests/postgres/migrations_no_tx/0_create_db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- no-transaction

CREATE DATABASE test_db;
2 changes: 1 addition & 1 deletion tests/postgres/test-attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async fn it_gets_posts_mixed_fixtures_path(pool: PgPool) -> sqlx::Result<()> {
// This should apply migrations and then `../fixtures/postgres/users.sql` and `../fixtures/postgres/posts.sql`
#[sqlx::test(
migrations = "tests/postgres/migrations",
fixtures(path = "../fixtures/postgres", scripts("users.sql", "posts"))
fixtures("../fixtures/postgres/users.sql", "../fixtures/postgres/posts.sql")
)]
async fn it_gets_posts_custom_relative_fixtures_path(pool: PgPool) -> sqlx::Result<()> {
let post_contents: Vec<String> =
Expand Down