Skip to content

Support running macros in "decoupled" mode #60

Closed
@abonander

Description

@abonander

This was a popular request in the original Reddit announcement: https://www.reddit.com/r/rust/comments/egpw7g/announcing_sqlx_a_fully_asynchronous_pure_rust/fc8ldpw/?context=1000

The idea is to allow expansion of query!(), et al, without having to connect directly to the database by running the PREPAREs for all queries in a separate step and caching the result.

The solution in my head looks something like this:

  • We create a Cargo subcommand, cargo-sqlx, which links sqlx-core internally.
  • The user runs cargo sqlx prepare in their project which invokes cargo check with QUERY_PREPARE_OUT=$OUT_DIR/sqlx set in the environment
    • By being a subcommand we can know which cargo they're using so we build with the right toolchain, e.g. if the user runs cargo +nightly sqlx prepare when their default toolchain is stable
  • When query!() and friends see QUERY_PREPARE_OUT in the environment they don't perform normal expansion:
    • Instead they run PREPARE on their query strings and output the hash of the query and the prepare result as randomly named JSON files to QUERY_PREPARE_OUT
    • Then emit panics instead of their normal expansion so compilation can still finish.
  • The subcommand then gathers the files inside QUERY_PREPARE_OUT and writes them to a single JSON file in a predetermined location in the project (TBD)
    • These separate scatter/gather steps is because the macro expansions may run in parallel, so we don't want them all to clobber the same file.
    • This new file is intended to be checked-in to source control where it can be used even without a database connection, e.g. in CI.
    • If two queries have the same hash, the subcommand deduplicates the entries by asserting that the PREPARE data is the same.
  • If the file exists when query!() is later expanded normally, the macro checks in the file for the hash of its query string and uses that data instead of connecting to the database.
    • If the hash isn't found it emits a compile error saying to run cargo sqlx prepare again.
    • We can also support cargo sqlx prepare --check which does the opposite; it reruns PREPARE for the cached query strings and asserts that the result is the same.

We can then later extend the cargo-sqlx command to add migrations. With migrations in place we have a stronger guarantee that the schema we compiled and cached the PREPARE data against is the same as the one we are running against.

Questions:

  • Is prepare the right name for the sub-subcommand?
  • What should the cached file be named and where should it go?
  • Should we support a sqlx.toml to change the location and possibly other behaviors?
  • Should the macro use an environment variable like QUERY_PREPARE_OUT or look at a cfg flag? I'm thinking the former might fail to trigger rebuilds when set if Cargo doesn't know to watch for it but the latter might trigger a rebuild of the whole dep graph.
    • We could also just touch src/ to trigger a rebuild, no?
  • Do we want to support checking the cached file at runtime to ensure the PREPARE data we saved still matches what we get back from the database server?

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requesthelp wantedExtra attention is needed

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions