Building with SQLx on Nix
SQLx is a Rust crate for asynchronously accessing SQL databases. It works by checking all queries at compile time, which means it needs access to the database when building.
Although it supports an offline-mode (intended for CI or network-blocked builds), I prefer avoiding having to remember to manually run commands to keep schemas in sync.
Let's take a look at how we can efficiently automate this with Naersk!
# mycrate.nix
{ naersk
, runCommand
, sqlx-cli
, targetPlatform
}:
let
src = ./mycrate; # 1
srcMigrations = src + /migrations; # 2
sqlx-db = runCommand "sqlx-db-prepare" # 3
{
nativeBuildInputs = [ sqlx-cli ];
} ''
mkdir $out
export DATABASE_URL=sqlite:$out/db.sqlite3
sqlx database create
sqlx migrate --source ${srcMigrations} run
'';
in
naersk.lib."${targetPlatform.system}".buildPackage {
inherit src;
doCheck = true;
CARGO_BUILD_INCREMENTAL = "false";
RUST_BACKTRACE = "full";
copyLibs = false;
overrideMain = old: { # 4
linkDb = ''
export DATABASE_URL=sqlite:${sqlx-db}/db.sqlite3 # 5
'';
preBuildPhases = [ "linkDb" ] ++ (old.preBuildPhases or [ ]); # 6
};
}
At a high level, Naersk builds the crate in two parts: one derivation builds all
cargo dependencies on their own, and second derivation uses the artifacts from
the first when building the actual crate source. This pattern can be extended by
adding a third derivation which can prepare a SQLite database which sqlx
can
use for query validation.
Let's break down the configuration above:
- We define a path to the cargo root. I prefer to keep the crate files in their
own sub-directory so that builds don't get accidentally invalidated when
other files get changed (e.g. extra nix files, READMEs, etc.). It is possible
to use
cleanSourceWith
ornix-gitignore
to filter out extra files, but it can get a bit fiddly at times, and easy to forget to allow/block list new files. - Regardless of where the crate source is hosted, we want to make sure that the
source we pass into
sqlx
only contains themigrations
directory. This will avoid having to rebuild the database unnecessarily. - This command defines the script for having
sqlx
create the database and perform any migrations, using the source from step #2, and saving the result to the derivation's$out
directory. - Naersk will, by default, pass all of its inputs to both the crate and deps
derivations. Here we use
overrideMain
such that our changes apply only to the final crate derivation. Since the deps derivation does not need to usesqlx
we can avoid having to rebuild it if the database schema changes. - We define our
linkDb
step which will set theDATABASE_URL
variable thatsqlx
will use when doing the query validation. - And lastly, we register the step as a
preBuildPhase
since it needs to run before all cargo build steps are invoked.
This leaves us with the following (minimal) dependency tree: