CLI for SQL test validation
Project description
SQLCheck
SQLCheck turns SQL files into CI-grade tests with inline expectations. It scans SQL test source
files, extracts directives like {{ success(...) }} or {{ fail(...) }}, executes the compiled
SQL against a target database using SQLAlchemy, and reports per-test results with fast, parallel
execution.
Features
- Directive-based expectations:
{{ success(...) }}and{{ fail(...) }}directives define expected behavior directly inside SQL test files. - Deterministic parse/compile stage: Directives are stripped to produce executable SQL plus
structured
sql_parsedstatement metadata. - Parallel execution: Run tests concurrently with a configurable worker pool (default: 5).
- CI-friendly outputs: Clear per-test failures, non-zero exit codes, and JSON/JUnit reports.
- Extensible assertions: Register custom functions via plugins.
Installation
From PyPI
uv tool install pysqlcheck
SQLAlchemy requires a database-specific driver (dialect) package. Install the one for your database, for example:
# Snowflake
uv tool install pysqlcheck[snowflake]
Common optional extras (mirrors popular SQLAlchemy dialects) include: databricks, mssql, duckdb, oracle...
If you need a different database dialect, install the SQLAlchemy driver for it directly. See https://docs.sqlalchemy.org/en/20/dialects/ for the full list and driver guidance.
From GitHub (recommended)
Install the latest version directly from GitHub using uv:
uv tool install git+https://github.com/luisggc/sqlcheck
To install from a specific branch:
uv tool install git+https://github.com/luisggc/sqlcheck@branch-name
From source (for development)
git clone <repo-url>
cd sqlcheck
uv sync
source .venv/bin/activate
uv sync creates .venv by default and installs the sqlcheck entry point into it.
Prerequisites
- Python 3.11+
- SQLAlchemy-compatible database connection
Quick start
- Create a SQL test file (default pattern:
**/*.sql):
-- tests/example.sql
{{ success(name="basic insert") }}
CREATE TABLE t (id INT);
INSERT INTO t VALUES (1);
SELECT * FROM t;
- Run sqlcheck with a database connection:
# Option 1: Set a default connection (no -c flag needed)
export SQLCHECK_CONN_DEFAULT="sqlite:///tmp/sqlcheck.db"
sqlcheck run tests/
# Option 2: Use a named connection
export SQLCHECK_CONN_DEV="sqlite:///tmp/sqlcheck.db"
sqlcheck run tests/ --connection dev
# Short flag works too
sqlcheck run tests/ -c dev
# Option 3: Pass a direct URL
sqlcheck run tests/ -c "sqlite:///tmp/sqlcheck.db"
If any test fails, sqlcheck exits with a non-zero status code.
See Connection configuration for more options including YAML files.
SQLTest directives
Directives are un-commented blocks in the SQL source:
{{ success(name="my test", tags=["smoke"], timeout=30, retries=1) }}
{{ fail(match="'permission denied' in error_message") }}
{{ assess(match="stdout == 'ok' && rows.size() == 1") }}
{{ assess(match="status == 'fail' && 'type error' in error_message") }}
{{ assess(check="stdout.matches('^ok') && returncode == 0") }}
success(...): Asserts the SQL executed without errors. Optionalmatchexpressions add further checks.fail(...): Asserts the SQL failed. Optionalmatchexpressions add further checks.assess(...): Evaluates a CEL (Common Expression Language) expression supplied via the requiredmatch(orcheck) argument. The expression must evaluate totrue.
CEL variables available to match:
status:"success"or"fail".success: Boolean success flag.returncode: Integer return code.error_code: String version of the return code.duration_s: Execution duration in seconds.elapsed_ms: Execution duration in milliseconds.stdout: Captured stdout.stderr: Captured stderr.error_message: Alias for stderr.rows: Query result rows as a list of lists.output: Nested object withstdout,stderr, androws.sql: Full SQL source (directives stripped).statements: List of parsed SQL statements.statement_count: Count of parsed SQL statements.
Common CEL expressions:
- Contains text:
stdout.contains("warning") - Regex match:
stdout.matches("^ok")ormatches(stdout, "^ok") - Comparisons:
returncode != 0,statement_count >= 1 - Row assertions:
rows.size() == 1,rows[0][0] > 0 - Status checks:
status == "success",success == true
If no directive is provided, sqlcheck defaults to success(). The name parameter is optional;
when omitted, the test name defaults to the file path.
Template variables
SQL files are rendered with Jinja before directives are extracted, so you can use template
variables inside the SQL source (for example {{ schema }} or {{ limit }}). Supply values via
--vars (repeatable key=value pairs).
sqlcheck run tests/ --vars schema=public --vars limit=10
Undefined variables raise a template error.
CLI usage
sqlcheck run TARGET [options]
Options
--pattern: Glob for discovery (default:**/*.sql).--workers: Parallel worker count (default: 5).--connection,-c: Connection name forSQLCHECK_CONN_<NAME>lookup.--vars,-v: Template variables inkey=valueformat (repeatable).--json: Write JSON report to path.--junit: Write JUnit XML report to path.--plan-dir: Write per-test plan JSON files to a directory.--plugin: Load custom expectation functions (repeatable).
Connection configuration
SQLCheck resolves connection URIs in this order (first match wins):
- Default connection (when
-cis omitted) - Direct URL (if contains "://")
- Environment variables
- YAML configuration files
Environment variables
SQLCheck supports two environment variable prefixes:
SQLCHECK_CONN_{NAME}— SQLAlchemy URL for a named connectionDTK_CONN_{NAME}— Alternative prefix for compatibilitySQLCHECK_CONN_DEFAULT— Default connection used when-cis omittedDTK_CONN_DEFAULT— Alternative default connection
Connection names are normalized by converting to uppercase and replacing non-alphanumeric characters with underscores.
Example:
export SQLCHECK_CONN_DEFAULT="sqlite:///tmp/sqlcheck.db"
export SQLCHECK_CONN_SNOWFLAKE_PROD="snowflake://user:pass@account/db/schema"
# Uses default connection
sqlcheck run tests/
# Uses snowflake_prod connection
sqlcheck run tests/ --connection snowflake_prod
YAML configuration file (optional)
For better organization and to avoid exposing credentials in environment variables, you can use a YAML configuration file.
Default locations (checked in order):
~/.config/sqlcheck/connections.yaml~/.dtk/connections.yml
You can override the location using:
export SQLCHECK_CONNECTIONS_FILE="/path/to/your/connections.yaml"
Example ~/.config/sqlcheck/connections.yaml:
dev:
drivername: postgresql
username: myuser
password: ${DB_PASSWORD} # Environment variables are expanded
host: localhost
port: 5432
database: testdb
snowflake_prod:
drivername: snowflake
username: my_user
password: ${SNOWFLAKE_PASSWORD}
host: my_account
database: ANALYTICS
schema: PUBLIC
query:
warehouse: COMPUTE_WH
role: ANALYST
# Simple URL format also works
local: "sqlite:///tmp/local.db"
Usage:
# Uses connection defined in YAML
sqlcheck run tests/ --connection dev
# Environment variables in YAML are expanded
export DB_PASSWORD="secret123"
sqlcheck run tests/ --connection dev
Direct URL
You can also pass a connection URL directly (useful for testing):
sqlcheck run tests/ --connection "sqlite:///tmp/test.db"
Reports
- JSON: machine-readable summary of each test and its results.
- JUnit XML: CI-friendly test report format.
- Plan files: per-test JSON containing statement splits, directives, and metadata.
Contributing
Development setup
uv sync --extra dev
Plugin functions
Create a Python module with a register(registry) function:
# my_plugin.py
from sqlcheck.function_context import current_context
from sqlcheck.models import FunctionResult
def register(registry):
def assert_rows(min_rows=1, **kwargs):
context = current_context()
# Implement logic here based on stdout/stderr or engine-specific output
return FunctionResult(name="assert_rows", success=True)
registry.register("assert_rows", assert_rows)
Run with:
sqlcheck run tests/ --plugin my_plugin
Running tests
python -m unittest discover -s tests
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pysqlcheck-0.0.3.tar.gz.
File metadata
- Download URL: pysqlcheck-0.0.3.tar.gz
- Upload date:
- Size: 23.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1732885c4d482a5dfdfc464c5ce78db42872da52965a31215783d298c67d44be
|
|
| MD5 |
085a8c20151dedc7daaf5b32e106edfb
|
|
| BLAKE2b-256 |
dad845e331b61aca8dd8bd0605b16ad5eb3f2a006ce7524196a24c8bceda33d9
|
Provenance
The following attestation bundles were made for pysqlcheck-0.0.3.tar.gz:
Publisher:
deploy.yml on luisggc/sqlcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysqlcheck-0.0.3.tar.gz -
Subject digest:
1732885c4d482a5dfdfc464c5ce78db42872da52965a31215783d298c67d44be - Sigstore transparency entry: 798620145
- Sigstore integration time:
-
Permalink:
luisggc/sqlcheck@9cd89d601cf0f9c9b402c4c5bd83cc376165c758 -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/luisggc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
deploy.yml@9cd89d601cf0f9c9b402c4c5bd83cc376165c758 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pysqlcheck-0.0.3-py3-none-any.whl.
File metadata
- Download URL: pysqlcheck-0.0.3-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b125c34a94651b3bce56e04f301fa8013eddc47e21359442d60d620dcc36b02
|
|
| MD5 |
50cb5bcb796b0c0d69c7234ccb7f0880
|
|
| BLAKE2b-256 |
d1e25bade563f1cfe7a4f54bbce7e443ffaddf15c13a81a72edb63fa064acf3e
|
Provenance
The following attestation bundles were made for pysqlcheck-0.0.3-py3-none-any.whl:
Publisher:
deploy.yml on luisggc/sqlcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysqlcheck-0.0.3-py3-none-any.whl -
Subject digest:
5b125c34a94651b3bce56e04f301fa8013eddc47e21359442d60d620dcc36b02 - Sigstore transparency entry: 798620150
- Sigstore integration time:
-
Permalink:
luisggc/sqlcheck@9cd89d601cf0f9c9b402c4c5bd83cc376165c758 -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/luisggc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
deploy.yml@9cd89d601cf0f9c9b402c4c5bd83cc376165c758 -
Trigger Event:
release
-
Statement type: