feat: no tx migration (#3181)

* test: add a failing test

* feat: add no_tx to migration struct

* feat: execute migration with no tx block

* fix: expected string literal compilation error

* test: update no tx to content comment

* refactor: use the sql comment instead of file name semantics

* docs: remove no_tx from file format comment

* fix: remove filename matches

* fix: messed up merge

* refactor: dedupe migration

* fix: move comment to where it makes sense

* fix: linter error
This commit is contained in:
Jaime
2024-04-19 17:42:44 -05:00
committed by GitHub
parent 25efb2f7f4
commit 40aef6da2c
9 changed files with 77 additions and 28 deletions

View File

@@ -208,38 +208,25 @@ 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
.map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?;
// 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?;
tx.commit().await?;
// execute migration queries
if migration.no_tx {
execute_migration(self, migration).await?;
} else {
// 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 mut tx = self.begin().await?;
execute_migration(&mut tx, migration).await?;
tx.commit().await?;
}
// Update `elapsed_time`.
// NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept
// this small risk since this value is not super important.
let elapsed = start.elapsed();
// language=SQL
@@ -286,6 +273,31 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations (
}
}
async fn execute_migration(
conn: &mut PgConnection,
migration: &Migration,
) -> Result<(), MigrateError> {
let _ = conn
.execute(&*migration.sql)
.await
.map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?;
// 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(conn)
.await?;
Ok(())
}
async fn current_database(conn: &mut PgConnection) -> Result<String, MigrateError> {
// language=SQL
Ok(query_scalar("SELECT current_database()")