50× faster than sqruff. Lint 10,000 SQL files in under a second. Benchmarks
Website ·
Get started ·
Docs ·
Benchmarks
Catch SQL mistakes, apply safe fixes, and format consistently. Add your own rules in Python. SQLClean parses and checks SQL in Rust, and runs your Python Rules on statements it has already parsed.
uv tool install sqlclean # or: pip install sqlcleansqlclean check reports an unused CTE, a case-sensitive email comparison that a team's custom
Rule forbids, and a = NULL comparison that is never true. sqlclean fix applies the safe fix,
removing the CTE, and formats the file. It leaves = NULL alone because rewriting it to IS NULL
changes which rows match; sqlclean fix --unsafe-fixes applies that fix. The custom Rule only
reports, so its finding stays.
sqlclean check # report findings in every .sql file under the directory
sqlclean fix # apply safe fixes, then format
sqlclean format # rewrite files in the canonical layoutAdd a sqlclean.toml to choose a dialect, files or Rules. See the
quickstart and
configuration.
Linting the same 10,282 Snowflake SQL files on a Linux machine with 4 CPUs, each tool with its default rules and no cache:
| Tool | Wall time |
|---|---|
sqlclean check |
0.81 s |
sqruff lint |
41.0 s |
sqlfluff lint --processes 4 |
919 s |
The tools don't check the same things: almost every sqruff and SQLFluff finding comes from a layout rule, which SQLClean leaves to its formatter. See the benchmarks for coverage, the corpus, the method and how to run it yourself.
SQLClean in other configurations, on the same files:
| SQLClean command | Wall time |
|---|---|
sqlclean check --select SQLCLEAN (every built-in Rule) |
1.33 s |
sqlclean check plus three custom Python Rules |
2.27 s |
sqlclean check rerun with nothing changed (result cache) |
0.23 s |
- Rules for SQL that runs but is wrong. Built-in Rules flag comparisons with
NULL,NOT INlists that containNULL,ROW_NUMBERwith no deterministic order and more. See Rules. - Safe fixes by default.
sqlclean fixapplies safe fixes only. A fix that can change which rows match waits for--unsafe-fixes, and a fix that would break parsing is discarded. Seesqlclean fix. - An idempotent formatter. Every statement gets one canonical layout, and formatting a formatted file changes nothing. See formatting.
A custom Rule is a Python function over each statement, already parsed and located by the Rust
engine. This team uses COALESCE instead of IFNULL to keep queries portable. The Rule reads
function facts, not SQL text, so comments and string literals don't match. It only reports;
the author chooses the replacement.
# sql_rules/team.py
from sqlclean import Finding, RuleContext, Statement, rule
@rule(
code="XSQLCLEANTEAM001",
message="Use COALESCE instead of IFNULL",
remediation="Write COALESCE(value, fallback).",
enabled_by_default=True,
)
def prefer_coalesce(*, statement: Statement, ctx: RuleContext) -> list[Finding]:
return [
ctx.finding(subject=statement, node=function)
for function in statement.functions
if function.name.upper() == "IFNULL"
]Point [rules] custom = ["sql_rules"] in sqlclean.toml at it, then run sqlclean check:
SELECT IFNULL(discount, 0) FROM orders;error[XSQLCLEANTEAM001]: Use COALESCE instead of IFNULL
--> orders.sql:1:8
|
1 | SELECT IFNULL(discount, 0) FROM orders;
| ^^^^^^
= help: Write COALESCE(value, fallback).
No extra dependencies are needed. See custom Rules, statement facts and testing custom Rules.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/sqlstack-io/sqlclean
rev: v0.1.0
hooks:
- id: sqlclean-fix
- id: sqlclean-formatUse sqlclean-check where a hook must not change files. In CI, sqlclean check exits non-zero
on findings and writes SARIF with --output-format sarif for code-scanning annotations. See
integrations. sqlclean skills installs a skill for AI
coding agents; see sqlclean skills.
uv sync --all-groups # builds the native executable into .venv
make check # format, lint, type-check, fensu and clippy
make test # Rust and Python tests