Skip to content

sqlx::migrate!() does not trigger recompile on migration file content changes (stable Rust, sccache) #4267

@mtrogman

Description

@mtrogman

Summary

sqlx::migrate!("./migrations") bakes migration SQL + no_tx flags into the binary at compile time via a proc-macro. On stable Rust, changes to migration files do not cause cargo to recompile the calling crate, so the binary silently retains stale migration content.

Root cause

sqlx-macros-core/src/migrate.rs registers the migrations directory with cargo only under a nightly gate:

#[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))]
{
    proc_macro::tracked_path::path(path);   // <— never runs on stable
}

The macro does emit include_str!(path) for each migration (which rustc tracks), but that tracking is bypassed when RUSTC_WRAPPER=sccache is in use: sccache hashes the source file (src/db.rs), sees it unchanged, and returns the cached object without re-running rustc or the proc-macro.

Even without sccache, cargo's fingerprint/incremental system can miss these deps in edge cases when the invoking source file is unchanged but a migration file has changed.

Impact

  • The no_tx flag / SQL content embedded in the binary does not update when a migration file is edited.
  • Workaround requires touch src/<file-containing-migrate!>.rs or cargo clean.
  • CI builds are unaffected if CARGO_INCREMENTAL=0 is set, so CI binaries are always correct. This is a local dev bug.

Concrete incident

During a post-merge hotfix: an -- no-transaction marker was added to a migration file to prevent DROP INDEX CONCURRENTLY from running inside a transaction. Despite cargo clean + disabling sccache, the compiled binary retained the old content. The hotfix had to rewrite the migration differently to force cargo to see a change. (upstream context)

Workaround (works today)

Add to build.rs:

if let Ok(entries) = std::fs::read_dir("migrations") {
    for entry in entries.flatten() {
        if let Some(path) = entry.path().to_str() {
            println!("cargo:rerun-if-changed={path}");
        }
    }
}
println!("cargo:rerun-if-changed=migrations");

Requested fix (any of these would help)

  1. Best: Drop the nightly gate on tracked_path::path() once proc_macro::tracked_path is stabilised (Tracking Issue for proc_macro::{tracked_env, tracked_path} rust-lang/rust#99515).
  2. Good: Add a sqlx_build_support::watch_migrations!(dir) macro / function users can call from build.rs.
  3. Minimal: Document the build.rs snippet above prominently in the migrate!() docs so users know they need it when sccache or incremental compilation is in play.

Version

sqlx 0.8.6, stable Rust (1.87), sccache in RUSTC_WRAPPER.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions