Skip to main content

SQLClean

The fastest SQL linter.

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 sqlclean

sqlclean check reports an unused CTE, a case-sensitive email comparison from a custom Rule and a comparison with NULL; sqlclean fix removes the CTE and formats the file; sqlclean fix --unsafe-fixes rewrites = NULL to IS NULL

sqlclean 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.

Quick start

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 layout

Add a sqlclean.toml to choose a dialect, files or Rules. See the quickstart and configuration.

Speed

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

Correct and safe

  • Rules for SQL that runs but is wrong. Built-in Rules flag comparisons with NULL, NOT IN lists that contain NULL, ROW_NUMBER with no deterministic order and more. See Rules.
  • Safe fixes by default. sqlclean fix applies safe fixes only. A fix that can change which rows match waits for --unsafe-fixes, and a fix that would break parsing is discarded. See sqlclean fix.
  • An idempotent formatter. Every statement gets one canonical layout, and formatting a formatted file changes nothing. See formatting.

Custom Rules in Python

A custom Rule is a Python function over each statement, already parsed and located by the Rust engine. This one enforces a team convention, not a rule of SQL: the columns the team lists in EMAIL_COLUMNS are compared case-insensitively. An = or <> that reads one of them, bare or as LOWER(email), passes only when both sides are LOWER(...) or a lower-case string literal. It reads comparisons from the statement's syntax tree, so comments and string literals never match, and it only reports, because changing a comparison is the author's call.

# sql_rules/team.py
import polyglot_sql as ast  # pip install "sqlclean[ast]"

from sqlclean import Finding, RuleContext, Statement, rule

EMAIL_COLUMNS = {"email", "billing_email"}  # the columns this team treats as email addresses


def email_column(operand: ast.Expression) -> ast.Column | None:
    """The designated email column an operand reads, written bare or as LOWER(column)."""
    column = operand.this if isinstance(operand, ast.Lower) else operand
    is_email = isinstance(column, ast.Column) and column.name.lower() in EMAIL_COLUMNS
    return column if is_email else None


def lowered(operand: ast.Expression) -> bool:
    """Whether an operand is LOWER(...) or a string literal with no upper-case letters."""
    literal = isinstance(operand, ast.Literal) and operand.is_string
    return isinstance(operand, ast.Lower) or (literal and operand.name == operand.name.lower())


@rule(
    code="XSQLCLEANTEAM001",
    message="Email comparisons must be case-insensitive",
    remediation="Compare LOWER(email) with LOWER(...) instead.",
    enabled_by_default=True,
)
def case_insensitive_email(*, statement: Statement, ctx: RuleContext) -> list[Finding]:
    starts = {
        column.to_dict()["column"]["span"]["start"]
        for comparison in statement.polyglot_ast().find_all(ast.Eq, ast.Neq)
        for column in map(email_column, (comparison.this, comparison.expression))
        if column is not None and not all(map(lowered, (comparison.this, comparison.expression)))
    }
    return [
        ctx.finding(subject=statement, node=column)
        for column in statement.columns
        if column.start - statement.start in starts
    ]

Point [rules] custom = ["sql_rules"] in sqlclean.toml at it. The syntax tree needs the ast extra: pip install "sqlclean[ast]". See custom Rules, statement facts and testing custom Rules.

Integrations

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/sqlstack-io/sqlclean
    rev: v0.1.0
    hooks:
      - id: sqlclean-fix
      - id: sqlclean-format

Use 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.

Docs

Development

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

License

Apache License 2.0.

Metadata

Release files for sqlclean 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sqlclean 0.4.0
File Size Uploaded Provenance Actions
sqlclean-0.4.0.tar.gz 2.3 MB With provenance Details for sqlclean-0.4.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sqlclean 0.4.0
File Interpreter ABI Platform Actions
sqlclean-0.4.0-py3-none-any.whl Python 3 none any Details for sqlclean-0.4.0-py3-none-any.whl

Total release size: 2.3 MB

Release files / sqlclean-0.4.0.tar.gz

Download URL sqlclean-0.4.0.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
831ddd07f2923fd6ba9d08635cc475148f2f83cd4573c729995b3d59f377419f
BLAKE2b-256 checksum
How to use checksums
a2a215a654f87381836c5f6a7ca7987f08fac8a457f46eeeb9f0c3208c9c7e4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

Release files / sqlclean-0.4.0-py3-none-any.whl

Download URL sqlclean-0.4.0-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2872e925348bd96264c383b493add2e8bad2304878c7691c4273933112021b67
BLAKE2b-256 checksum
How to use checksums
6330849b462461dbe1e4b0cf7001ea497853e2d566c88bd8f8090340a6f2734e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

Security for sqlclean 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sqlclean-0.4.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page