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

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.2
File Size Uploaded Provenance Actions
sqlclean-0.4.2.tar.gz 2.3 MB With provenance Details for sqlclean-0.4.2.tar.gz

Built distribution (wheel)

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

Total release size: 2.3 MB

Release files / sqlclean-0.4.2.tar.gz

Download URL sqlclean-0.4.2.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
444f25a3f0c79c03ca7109247c25129df1a6aa6ed9e3e567c008cb9d9c1b2c54
BLAKE2b-256 checksum
How to use checksums
77b53fee7c358a6020448f0f49696e83765508a35785a6ee5c1249f45a7fb459
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 Nov 1, 2026.

Transparency log

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

Download URL sqlclean-0.4.2-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83da03c47cbaac7957ae3d51d3694d5498876dd05dea5dc7be2a4562c482497e
BLAKE2b-256 checksum
How to use checksums
cc533d83d07efdbd0a02ff185240621915250c9bf3554f4395533be8bc861ba2
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 Nov 1, 2026.

Transparency log

Security for sqlclean 0.4.2

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.2.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 Nov 1, 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