Repository navigation
Conversation
…g_sql`
A library user wanting to dump the SQL that pending migrations would
execute — for DBA review, out-of-band application, or pre-deploy
inspection — currently has to copy `sqlx-postgres`/`sqlx-mysql`/
`sqlx-sqlite`'s private `execute_migration`/`apply()` body verbatim:
the per-driver INSERT into `_sqlx_migrations`, bytea-literal syntax
(`'\xHEX'::bytea` / `X'HEX'`), checksum hex encoding, and per-driver
transaction framing (Postgres/SQLite honor `Migration::no_tx`; MySQL
always wraps in `START TRANSACTION` and uses a `success=FALSE` →
`UPDATE TRUE` pair due to implicit DDL commits). The CLI's existing
`--dry-run` only prints status lines; it does not emit SQL.
Add a sibling-of-`Migrate` trait, `MigrateRender`, with a single sync
method:
fn append_apply_sql(&self, table_name, migration, buf: &mut String)
implemented per driver to mirror that driver's `apply()` exactly. The
output is **self-applying**: executing it produces the same post-state
as `apply()` (bookkeeping rows included, with the migration's SHA-384
checksum), so a subsequent `Migrator::run()` sees those versions as
applied and does not re-execute.
`Migrator::render_pending_sql(&conn)` ties phase 1's `pending` to the
new trait: returns a single `String` containing every pending migration
preceded by a `-- migration <version> (<description>)` header.
`pending_direct`'s validation (dirty DB, checksum drift) is shared, so
errors match `run`'s.
Two small `pub` helpers (`append_hex`, `append_sql_string`) live in
`sqlx-core::migrate::render` under `#[doc(hidden)]` for the driver
crates to reuse. They are not part of the documented public API.
Tests: end-to-end across all three drivers — render the pending SQL,
execute it via `raw_sql(AssertSqlSafe(...))`, assert `pending` is then
empty (proves bookkeeping rows are correct), assert a follow-up `run`
is a no-op (proves checksums match byte-for-byte what sqlx computes),
assert re-rendering yields the empty string. SQLite tests run locally
via `cargo test`; Postgres test verified locally against PG 16 (`pending`
+ `render_pending_sql` both pass); MySQL test compiles but was not
exercised against a live MySQL server.
Closes transact-rs#357.
Stacked on the `Migrator::pending` change (separate commit, separate
upstream PR) — `render_pending_sql` calls `pending_direct` rather than
duplicating the diff loop.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a per-driver SQL-rendering helper so a library user can dump the SQL that
apply()would execute for pending migrations — for DBA review, out-of-band application viapsql/mysql/sqlite3, or pre-deploy inspection. Today this requires copying privateexecute_migrationSQL from each driver crate (INSERT into_sqlx_migrations, bytea-literal syntax, checksum hex, transaction framing, MySQL'ssuccess=FALSE/UPDATE TRUEtwo-step). The CLI's existing--dry-runonly prints status lines.Targets upstream issue transact-rs#357 (the original "SQL that would have been executed, including the adjustments to the migrations table" ask was never delivered; transact-rs#357 was closed by transact-rs#649 which only added status-line output).
Stacking
Stacked on PR #1 (
feat/migrator-pending). This PR's base is set to that branch so its diff shows phase-2-only changes (~317 lines). Once PR #1 lands inmain, GitHub will auto-rebase this PR's base.Migrator::render_pending_sqlcallspending_direct(introduced in PR #1) rather than duplicating the diff loop.API
Plus
#[doc(hidden)] pub fn append_hexandappend_sql_stringinsqlx-core::migrate::renderfor driver-crate reuse.Per-driver behavior (mirrors each
apply()byte-for-byte)BEGIN; … COMMIT;(skipped onMigration::no_tx)'\xHEX'::byteaexecute_migrationexactlyBEGIN; … COMMIT;(skipped onno_tx)X'HEX'execute_migrationexactlySTART TRANSACTION; INSERT(success=FALSE); <sql>; UPDATE TRUE; COMMIT;X'HEX'no_txis intentionally ignored, matchingapply()Self-applying guarantee
Output includes the bookkeeping
INSERT INTO _sqlx_migrationsrow with the migration's SHA-384 checksum andexecution_time = -1. Executing the rendered SQL produces the same post-state asapply(), so a subsequentMigrator::run()sees those versions as applied (no checksum mismatch, no re-execution).Test plan
End-to-end test in each of the three driver test suites: render the pending SQL, execute it via
raw_sql(AssertSqlSafe(...)), assertpendingis then empty, assert a follow-uprunis a no-op (proves checksums match byte-for-byte what sqlx computes), assert re-rendering yields the empty string.cargo check -p sqlx-corecleancargo clippy -p sqlx-coreclean (no new warnings)pendingfrom PR feat(migrate): add Migrator::pending to inspect unapplied migrations #1 + newrender_pending_sql).pendingandrender_pending_sqlpass end-to-end.cargo test --no-run); not exercised against a live MySQL locally. Implementation validated by symmetry with PG/SQLite and direct comparison againstMySqlConnection::apply().Two design decisions worth review
MigrateRenderrather than method onMigrate.Migrate's methods are allasync+&mut self+BoxFuture; a sync&self-ignoring sibling method would clash with that shape. Easy to fold intoMigrateif reviewers prefer.append_apply_sqlignores&self. Pure formatting, no connection state needed.&selfis required for trait dispatch via the connection type. Static method (no&self) was an option but verbose at call sites. Easy to flip.