SQLx #[sqlx::test] macro slows Rust rebuilds in large test suites
SQLx’s handy test macro can become a rebuild tax in big suites. One crate with 350 tests and 30 migrations saw no-op rebuilds around 7.3 seconds and 32 MiB of expansion.
![SQLx #[sqlx::test] macro slows Rust rebuilds in large test suites](/_next/image?url=https%3A%2F%2Fcdn.prod.website-files.com%2F685fce904fe880c8d21eee28%2F68791a5149d8dcb3146493bf_TimescaleDB--SQLx-and-testing-in-Rust.jpg&w=1920&q=75)
SQLx’s convenience can turn into compile-time drag fast when a Rust test suite gets big enough. In Kobzol’s case, a bors rewrite crate with about 350 `#[sqlx::test]` cases and roughly 30 migrations had no-op rebuilds around 7.3 seconds on a laptop, and the macro expansion ballooned to about 32 MiB.
Where the rebuild time comes from
`#[sqlx::test]` is designed to make database-backed tests easy to write. SQLx documents it as a macro that can create a new live test database for every annotated function and automatically apply migrations when a `migrations/` folder is present. That is useful for correctness and isolation, but it also means each test carries generated code plus migration metadata, including the checksums and state needed to describe the database.
That duplication matters because the macro is not just wiring a function to a database connection. It is embedding enough machinery to make each test self-contained, and in a large suite that self-containment becomes visible at compile time. The result is slower `rustc` runs, slower no-op rebuilds, and an edit-test loop that feels heavier than the code changes themselves.
The measurements that exposed the problem
Kobzol’s June 21, 2026 GitHub issue #4318, opened to request a default migrator for `sqlx::test`, makes the problem concrete. In that crate, a no-op `cargo test no-run` rebuild took about 7.3 seconds. Running `cargo expand lib tests` produced around 32 MiB of output, which is a clear sign that the macro was generating far more than a small wrapper around the test body.
The same issue also shows how much of the cost was tied to repeated migration embedding. After removing all but one migration, rebuild time dropped to about 5 seconds and `cargo expand` output fell to about 5 MiB. That is the telltale pattern of macro bloat: the work scales with repeated embedded state, not just with test count.
Why SQLx’s design makes this easy to miss
SQLx documents `#[sqlx::test]` as a path to a fresh live database for each test, which is a strong default for isolation and debugging. It also notes that failed tests leave databases in place so you can inspect what went wrong later. Those are real quality-of-life features, but they come with a cost when the same migration bundle is generated hundreds of times.
The `migrate!` macro documentation points to the other half of the picture. It expands to a static `Migrator` embedded into the binary, and SQLx’s compile-time machinery can force recompilation when migrations change. In practice, that means a change to migration state can ripple through build output in a way that surprises teams who expect tests to be cheap to rebuild.
The same complaint has surfaced before
This is not a new tension inside SQLx. A 2022 feature request asked for different migration strategies for `sqlx::test`, especially running migrations once before all tests instead of before each test. The reason was simple: large migrators were making tests take minutes.
A later 2025 issue made the same pain point harder to ignore, saying that even a relatively small application with a few dozen migrations could push individual `sqlx::test` invocations past 60 seconds. Taken together, those reports show that the problem is not limited to one codebase. The macro’s ergonomics are excellent right up until a suite crosses the line where repeated migration work dominates the developer loop.
What to audit in your own project
If your Rust test suite uses SQLx heavily, the first thing to check is how often the macro is re-embedding the same database setup. A handful of tests usually stays manageable; hundreds of `#[sqlx::test]` functions paired with dozens of migrations can make rebuilds noticeably slower.
A quick audit can focus on a few concrete checks:
- Count how many `#[sqlx::test]` functions you have in each crate.
- Count how many migrations are being embedded into each test expansion.
- Run `cargo expand lib tests` and look at the size of the generated output.
- Measure no-op rebuilds with `cargo test no-run` after a trivial edit.
- Compare the cost of per-test migration setup against a shared migrator reused through `#[cfg(test)]`.
Kobzol notes that embedding the migrator once as a `#[cfg(test)] static` and reusing it across tests already works, but the pattern is easy to miss because it has to be repeated by hand. That is exactly the sort of detail worth surfacing in a codebase review: if one test helper can centralize the database setup, a lot of repeated macro expansion may disappear with it.
Why the fix discussion matters now
The issue on a default migrator is paired with a documentation pull request, `#4320`, titled to warn that many `#[sqlx::test]` cases can slow rebuild time. There is also a separate recent request for documentation on SQLx test connection limits, which shows that large test suites are still running into edge cases around setup and concurrency. Those are small paper cuts on their own, but together they point to a broader maintenance problem.
That is what makes this story bigger than one slow crate. SQLx 0.9.0 shipped on 2026-05-06, and the toolkit remains a core piece of async Rust database work. When a convenience macro starts adding seconds to every rebuild in a test-heavy project, the right response is not to abandon the tool, but to make its hidden duplication visible and trim it before the edit-test loop starts dragging.
This article was produced by Prism’s automated news system from verified source data, official records, and press releases, then run through automated quality and moderation checks before publishing. The system is built and supervised by the people who set the standards it runs under. Read our full AI policy.
Did this article answer your question?


