Austin Bonander 25cbeedab4
feat: create sqlx.toml format (#3383)
* feat: create `sqlx.toml` format

* feat: add support for ignored_chars config to sqlx_core::migrate

* chore: test ignored_chars with `U+FEFF` (ZWNBSP/BOM)

https://en.wikipedia.org/wiki/Byte_order_mark

* refactor: make `Config` always compiled

simplifies usage while still making parsing optional for less generated code

* refactor: add origin information to `Column`

* feat(macros): implement `type_override` and `column_override` from `sqlx.toml`

* refactor(sqlx.toml): make all keys kebab-case, create `macros.preferred-crates`

* feat: make macros aware of `macros.preferred-crates`

* feat: make `sqlx-cli` aware of `database-url-var`

* feat: teach macros about `migrate.table-name`, `migrations-dir`

* feat: teach macros about `migrate.ignored-chars`

* chore: delete unused source file `sqlx-cli/src/migration.rs`

* feat: teach `sqlx-cli` about `migrate.defaults`

* feat: teach `sqlx-cli` about `migrate.migrations-dir`

* feat: teach `sqlx-cli` about `migrate.table-name`

* feat: introduce `migrate.create-schemas`

* WIP feat: create multi-tenant database example

* fix(postgres): don't fetch `ColumnOrigin` for transparently-prepared statements

* feat: progress on axum-multi-tenant example

* feat(config): better errors for mislabeled fields

* WIP feat: filling out axum-multi-tenant example

* feat: multi-tenant example

No longer Axum-based because filling out the request routes would have distracted from the purpose of the example.

* chore(ci): test multi-tenant example

* fixup after merge

* fix(ci): enable `sqlx-toml` in CLI build for examples

* fix: CI, README for `multi-tenant`

* fix: clippy warnings

* fix: multi-tenant README

* fix: sequential versioning inference for migrations

* fix: migration versioning with explicit overrides

* fix: only warn on ambiguous crates if the invocation relies on it

* fix: remove unused imports

* fix: doctest

* fix: `sqlx mig add` behavior and tests

* fix: restore original type-checking order

* fix: deprecation warning in `tests/postgres/macros.rs`

* feat: create postgres/multi-database example

* fix: examples/postgres/multi-database

* fix: cargo fmt

* chore: add tests for config `migrate.defaults`

* fix: sqlx-cli/tests/add.rs

* feat(cli): add `--config` override to all relevant commands

* chore: run `sqlx mig add` test with `RUST_BACKTRACE=1`

* fix: properly canonicalize config path for `sqlx mig add` test

* fix: get `sqlx mig add` test passing

* fix(cli): test `migrate.ignored-chars`, fix bugs

* feat: create `macros.preferred-crates` example

* fix(examples): use workspace `sqlx`

* fix: examples

* fix(sqlite): unexpected feature flags in `type_checking.rs`

* fix: run `cargo fmt`

* fix: more example fixes

* fix(ci): preferred-crates setup

* fix(examples): enable default-features for workspace `sqlx`

* fix(examples): issues in `preferred-crates`

* chore: adjust error message for missing param type in `query!()`

* doc: mention new `sqlx.toml` configuration

* chore: add `CHANGELOG` entry

Normally I generate these when cutting the release, but I wanted to take time to editorialize this one.

* doc: fix new example titles

* refactor: make `sqlx-toml` feature non-default, improve errors

* refactor: eliminate panics in `Config` read path

* chore: remove unused `axum` dependency from new examples

* fix(config): restore fallback to default config for macros

* chore(config): remove use of `once_cell` (to match `main`)
2025-06-30 16:34:46 -07:00

3.2 KiB

Usage of macros.preferred-crates in sqlx.toml

The Problem

SQLx has many optional features that enable integrations for external crates to map from/to SQL types.

In some cases, more than one optional feature applies to the same set of types:

  • The chrono and time features enable mapping SQL date/time types to those in these crates.
  • Similarly, bigdecimal and rust_decimal enable mapping for the SQL NUMERIC type.

Throughout its existence, the query!() family of macros has inferred which crate to use based on which optional feature was enabled. If multiple features are enabled, one takes precedent over the other: time over chrono, rust_decimal over bigdecimal, etc. The ordering is purely the result of historical happenstance and does not indicate any specific preference for one crate over another. They each have their tradeoffs.

This works fine when only one crate in the dependency graph depends on SQLx, but can break down if another crate in the dependency graph also depends on SQLx. Because of Cargo's feature unification, any features enabled by this other crate are also forced on for all other crates that depend on the same version of SQLx in the same project.

This is intentional design on Cargo's part; features are meant to be purely additive, so it can build each transitive dependency just once no matter how many crates depend on it. Otherwise, this could result in combinatorial explosion.

Unfortunately for us, this means that if your project depends on SQLx and enables the chrono feature, but also depends on another crate that enables the time feature, the query!() macros will end up thinking that you want to use the time crate, because they don't know any better.

Fixing this has historically required patching the dependency, which is annoying to maintain long-term.

The Solution

However, as of 0.9.0, SQLx has gained the ability to configure the macros through the use of a sqlx.toml file.

This includes the ability to tell the macros which crate you prefer, overriding the inference.

See the sqlx.toml file in this directory for details.

A full reference sqlx.toml is also available as sqlx-core/src/config/reference.toml.

This Example

This example exists both to showcase the macro configuration and also serve as a test for the functionality.

It consists of three crates:

  • The root crate, which depends on SQLx and enables the chrono and bigdecimal features,
  • uses-rust-decimal, a dependency which also depends on SQLx and enables the rust_decimal feature,
  • and uses-time, a dependency which also depends on SQLx and enables the time feature.

Given that both dependencies enable features with higher precedence, they would historically have interfered with the usage in the root crate. (Pretend that they're published to crates.io and cannot be easily changed.) However, because the root crate uses a sqlx.toml, the macros know exactly which crates it wants to use and everyone's happy.