CI and pre-commit¶
Run sqlty where it blocks bad queries: before a commit and on every pull
request. sqlty rewrite --check and sqlty strip --check exit 1 when files
would change; sqlty check exits 1 when a rule with severity error fires.
Any CI system can use them.
Pre-commit¶
The sqlty repository publishes two hooks. Both receive the staged Python, SQL and TOML files as arguments:
| Hook id | Runs |
|---|---|
sqlty-rewrite |
sqlty rewrite <files> |
sqlty-check |
sqlty check <files> |
# .pre-commit-config.yaml
default_language_version:
python: python3.14 # sqlty requires Python >= 3.14
repos:
- repo: https://github.com/jirikuncar/sqlty
rev: v0.4.0
hooks:
- id: sqlty-rewrite
- id: sqlty-check
pre-commit autoupdate moves rev to the latest release. pre-commit
builds the hook environment with the Python it runs on unless told otherwise,
and sqlty needs 3.14 or newer: keep default_language_version, or set
language_version on each hook. pre-commit installs sqlty from source into that environment, which
compiles the Rust extension, so the first run needs a Rust toolchain
(cargo) and takes a few minutes. Pass options with args, for example
args: [--dialect, sqlite].
sqlty-check fails the commit only for rules with severity error. Drift that
can break the code, narrow-type, is one by
default; an annotation only wider than the rows,
stale-type, is a warning unless you
promote it (see below). The sqlty hook
id is a deprecated alias of sqlty-rewrite: it rewrites the staged files.
A schema change checks the whole project
A changed schema or config file can break queries in files you didn't touch.
When one of the files is the schema of a project, or the sqlty.toml,
pyproject.toml or Cargo.toml that configures it, sqlty runs on every
Python file of that project, as sqlty check . would. Other .sql and
.toml files are ignored.
To skip the build or reuse the sqlty already installed in your
environment, use local hooks instead:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: sqlty-rewrite
name: sqlty rewrite
entry: sqlty rewrite .
language: system
pass_filenames: false
types_or: [python, sql, toml]
- id: sqlty-check
name: sqlty check
entry: sqlty check .
language: system
pass_filenames: false
types_or: [python, sql, toml]
sqlty rewrite edits files in place, which makes pre-commit fail the commit
and leave the rewritten files for you to stage. Use uv run sqlty ... as the
entry if sqlty lives in a uv-managed environment.
Each file uses the nearest sqlty.toml above it, or pyproject.toml or Cargo.toml
with a sqlty table, so one command covers several projects with their own settings. In a
monorepo, a package pyproject.toml without [tool.sqlty] is skipped and its
files use the root config; one with its own table replaces the root config
entirely (tables are not merged).
Fail on unwrapped queries¶
sqlty rewrite --check changes nothing and exits with 1 if any file would
be rewritten:
--diff prints the changes as a unified diff instead. Add --untyped
sentinel (or set untyped = "sentinel") to also require wrapping queries
whose row type is unknown; see Untyped queries.
Fail on drift and mismatches¶
sqlty check exits with 1 only when a rule with severity error fires.
By default that covers row type mismatches, names the schema doesn't have,
SELECT * without a schema, and annotations that don't admit the rows
(narrow-type); annotations only wider than
the rows (stale-type) and unwrapped strings
(stale-rewrite) are warnings. Promote
the rules you want to enforce:
stale-rewrite reports exactly the strings rewrite would wrap, so promoting
it to error is equivalent to rewrite --check.
Keep the schema file current, for example by regenerating it from the
database in the same pull request as the migration
(pg_dump --schema-only, sqlite3 app.db .schema,
mysqldump --no-data).
GitHub Actions¶
# .github/workflows/sqlty.yml
name: sqlty
on: [pull_request]
jobs:
sqlty:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # sqlty breaking reads the base branch
- uses: astral-sh/setup-uv@v7
- run: uv sync
- run: uv run sqlty rewrite --check .
- run: uv run sqlty check --output-format github .
- run: uv run sqlty breaking --output-format github --against origin/${{ github.base_ref }}
Each step catches something the others don't:
| Step | Fails when |
|---|---|
sqlty rewrite --check |
a --sql string is not wrapped yet |
sqlty check |
a rule with severity error fires: a row type mismatch, an unknown table or column, or an annotation that doesn't admit the rows (narrow-type) by default; see Fail on drift and mismatches |
sqlty breaking |
the pull request's schema breaks the base branch's queries, or its queries break on the base branch's schema: a dropped column, a column made nullable |
A wider annotation is only a warning
A migration that makes a column nullable leaves SQL[tuple[str]] too narrow,
and the code can now get None: sqlty check fails with narrow-type. A
migration that makes a column NOT NULL leaves SQL[tuple[str | None]] wider
than it needs to be: stale-type warns and sqlty check exits 0.
--output-format github prints each diagnostic, and each breaking change, as a GitHub
Actions annotation, so it shows on its line in the pull request's diff. See Schema changes in CI
for the --direction options.
GitHub code scanning¶
# .github/workflows/sqlty-code-scanning.yml
name: sqlty code scanning
on:
push:
branches: [main]
pull_request:
jobs:
sqlty:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
- run: uv sync
- run: uv run sqlty check --output-format sarif --exit-zero . > sqlty.sarif
- uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: sqlty.sarif
--output-format sarif prints a SARIF 2.1.0
log: each diagnostic with its rule, severity, location and fix edits, and every rule with
a link to its documentation. GitHub code scanning keeps the findings across runs, so you
can triage and dismiss them in the Security tab. GitLab, Azure DevOps and the VS Code
SARIF Viewer read the same file.
--exit-zero lets the upload step run when sqlty check finds errors. Keep the
sqlty check step of GitHub Actions to fail the pull request.
sqlty breaking --output-format sarif works too, with each finding's direction and
against in its properties.
Keeping sources stripped¶
If you commit plain --sql strings and rely on the
magic codec instead, sqlty strip --check . exits
with 1 when a file contains SQL[T](...) wrappers.