Skip to content

Validate

import { Aside } from ‘@astrojs/starlight/components’;

sqlanvil validate checks that every model’s SQL is valid — syntax, missing tables, missing columns, type errors — before you run the project, without executing or materializing anything. It’s the fast feedback loop: catch the typo in seconds instead of partway through a run.

Terminal window
sqlanvil validate # validate the whole project
sqlanvil validate --actions my_model --include-deps
sqlanvil validate --environment dev # use a named environment's config + credentials
sqlanvil validate --json # machine-readable results

run --dry-run is an alias on PostgreSQL/Supabase/MySQL — it validates instead of executing. (On BigQuery, run --dry-run keeps BigQuery’s own server-side dry-run.)

SQLAnvil walks your DAG in dependency order and validates each model against the warehouse’s own plannerEXPLAIN on PostgreSQL/Supabase/MySQL, a dry-run on BigQuery — so there’s no separate SQL engine to drift from your warehouse’s behavior.

The trick to validating a whole project (where model B selects from model A, which doesn’t exist yet) is an isolated shadow namespace: SQLAnvil compiles into a temporary, timestamped schema (a dataset on BigQuery, a database on MySQL), and after each model validates it materializes an empty stub (CREATE TABLE … WITH NO DATA, or LIMIT 0) so the next model’s references resolve. The shadow namespace is dropped when validation finishes — nothing touches your real tables.

Each action is reported as one of:

StatusMeaning
PASSThe model’s SQL planned cleanly.
FAILThe model’s own SQL is invalid (with the error message + line/column).
BLOCKSkipped because an upstream model failed — so this one can’t be meaningfully checked.
SKIPNot validated (e.g. an operations block — arbitrary, possibly side-effecting SQL).

validate exits non-zero if anything FAILed or was BLOCKed, so it drops straight into CI.

Python script actions get their own validation — the Python analog of EXPLAIN, run without executing the script: the resolved interpreter must satisfy pythonVersion, every requirements: spec must be satisfied by the installed packages (checked offline — nothing is installed), and the script must parse. A failing script action reports FAIL with each problem on its own line (“requests is not installed (need >=2.31)”), and its dependents are BLOCKed, exactly like models.

$ sqlanvil validate
PASS analytics.stg_orders
FAIL analytics.orders
column "totl" does not exist (line 4, col 8)
BLOCK analytics.daily_revenue — blocked by an upstream failure
1 passed, 1 failed, 1 blocked

The FAIL vs BLOCK split keeps the signal clean: one real error doesn’t bury you in a cascade of confusing failures — you see the one model to fix.