Skip to main content

A fast SQL linter for dbt and Jinja SQL files

Project description

squint

A fast SQL linter and formatter for dbt/Jinja SQL files, written in Rust. Modelled on sqlfluff and sqlfmt, with first-class support for Jinja templating.

Features

  • 36 rules covering capitalisation, layout, aliasing, references, structure, and more
  • Auto-fix — rewrite files in place with --fix, or check for drift with --check
  • Jinja-aware — tokenises {{ }} and {% %} blocks without stripping them
  • -- noqa suppression — per-line and per-rule (-- noqa: CP01,LT05)
  • -- fmt: off blocks — opt out of formatting for hand-crafted sections
  • Severity levels — configure rules as error (exit 1) or warning (exit 0)
  • JSON output — for CI dashboards, editor plugins, and scripts
  • LSP server — real-time diagnostics in VS Code, Neovim, Helix, and any LSP client
  • Fast — ~100 µs to lint a 240-line file; 64 files in parallel in ~1.4 ms

Full documentation →

Installation

pip (no Rust required — downloads a pre-built binary):

pip install pysquint
# or with uv
uv add --dev pysquint

cargo (compiles from source):

cargo install squint

Pre-built binaries for Linux, macOS, and Windows are attached to each GitHub Release.

LSP server (optional, feature-gated):

cargo install squint --features lsp --bin squint-lsp

Quick start

# Lint all SQL files in a directory
squint models/

# Auto-fix violations
squint --fix models/

# CI gate — exit 1 if any file would be changed by --fix
squint --check models/

# Lint a single file piped from stdin
cat models/my_model.sql | squint --stdin-filename models/my_model.sql

Usage

squint [OPTIONS] [FILES]...

Files and directories can be mixed. Directories are walked recursively for *.sql files, respecting .gitignore.

Flag Description
--fix Rewrite files in place, applying all auto-fixable violations
--check Exit 1 if any file would be changed by --fix, without writing (CI gate)
--rules <IDs> Comma-separated rule IDs to run, e.g. --rules CP01,LT05
--max-line-length <N> Override the LT05 line length limit
-q / --quiet Show only the violation count per file
--format <fmt> Output format: text (default) or json
--output <file> Write violations to a file in addition to stdout
--exclude <pattern> Glob pattern to exclude (repeatable)
--stdin-filename <NAME> Read SQL from stdin; report violations under this filename

Exit codes: 0 = no errors, 1 = one or more error-severity violations (or --check detected drift), 2 = I/O error.

Configuration

Create squint.toml in your project root, or use [tool.squint] in pyproject.toml. CLI flags override config values.

# Paths to exclude (matched relative to the config file)
exclude = ["target/**", "**/node_modules/**", "vendor/*.sql"]

[rules.LT05]
max_line_length = 120        # default: 120

[rules.AL06]
min_alias_length = 1         # default: 1 (0 = no minimum)
max_alias_length = 0         # default: 0 (0 = no maximum)

[rules.CV03]
select_clause_trailing_comma = "forbid"  # "forbid" (default) | "require"

[rules.CV04]
prefer_count_1 = false       # false = require COUNT(*) (default); true = require COUNT(1)

[rules.AM06]
group_by_and_order_by_style = "explicit"  # "explicit" | "implicit" | "consistent"

# Per-rule severity overrides (all rules default to "error")
[rules.severity]
LT05 = "warning"   # long lines are warnings, not errors
CP01 = "error"     # keyword casing is still an error

Rules

All rules enforce lowercase SQL by default (keywords, identifiers, function names, type names, boolean/null literals). Fixable rules are marked with ✓.

Capitalisation

ID Description Fixable
CP01 Keywords must be lowercase (SELECT, FROM, WHERE, …)
CP02 Unquoted identifiers must be lowercase
CP03 Function names must be lowercase (COUNT, COALESCE, …)
CP04 Boolean/null literals must be lowercase (TRUE, FALSE, NULL)
CP05 Data type names must be lowercase (INT, VARCHAR, TIMESTAMP, …)

Layout

ID Description Fixable
LT01 No space before comma; no consecutive spaces mid-line; no space between function name and (
LT02 Indentation must use spaces (no tabs) and be a multiple of 4
LT03 Lines must not have trailing whitespace (spaces or tabs before the newline)
LT05 Lines must not exceed max_line_length characters (default 120)
LT06 No space between a function name and ( — e.g. count (id)count(id)
LT07 CTE closing ) must be on its own line
LT08 A blank line is required after each CTE closing )
LT09 Clauses in standard order: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
LT10 DISTINCT/ALL modifier must be on the same line as SELECT
LT11 Set operators (UNION, INTERSECT, EXCEPT) must be on their own line
LT12 File must end with exactly one trailing newline

Aliasing

ID Description Fixable
AL02 Column aliases must use explicit AS keyword
AL03 Expressions in SELECT must have an alias
AL04 Table aliases must be unique within a query
AL05 Table aliases that are defined but never referenced
AL06 Table alias length must be within configured bounds
AL08 Column aliases in a SELECT must be unique (case-insensitive)
AL09 A column must not be aliased to itself (col AS col)

Ambiguous

ID Description Fixable
AM01 SELECT DISTINCT with GROUP BY is redundant
AM02 UNION must be followed by ALL or DISTINCT
AM05 Implicit comma joins in FROM clauses are forbidden; use explicit JOIN syntax
AM06 GROUP BY/ORDER BY must use a consistent reference style

Convention

ID Description Fixable
CV03 Trailing comma policy in SELECT clauses (forbid or require)
CV04 Consistent row-counting syntax: COUNT(*) vs COUNT(1)
CV05 NULL comparisons must use IS NULL/IS NOT NULL
CV10 Identifiers must use a consistent quoting style within a file

References

ID Description Fixable
RF01 Qualified column references must use a known table alias
RF02 Wildcard (*) column references are not allowed; list columns explicitly

Structure

ID Description Fixable
ST03 CTEs that are defined but never referenced
ST08 COUNT(DISTINCT *) is not valid SQL

Jinja

ID Description Fixable
JJ01 Jinja tags must have single-space padding: {{ col }}, {% if cond %}

Suppression

Per-line: -- noqa

SELECT A  -- noqa            suppresses all rule violations on this line
FROM T    -- noqa: CP01      suppresses only CP01 on this line
WHERE X = 1  -- noqa: CP01, LT05   suppresses CP01 and LT05

Rule IDs are case-insensitive. -- noqa also suppresses auto-fixes on that line.

Block: -- fmt: off / -- fmt: on

-- fmt: off
SELECT A, B,   -- hand-crafted alignment, not touched by the linter
       C
-- fmt: on
SELECT d FROM t  -- linting resumes here

-- fmt: off inline (after SQL on a line) suppresses only that line. A standalone -- fmt: off with no matching -- fmt: on suppresses to end of file.

Severity levels

Rules default to error severity (exit 1). Override per rule in config:

[rules.severity]
LT05 = "warning"

Warnings are reported but do not affect the exit code. JSON output includes a "severity" field per violation.

JSON output

squint --format json models/
[
  {
    "path": "models/my_model.sql",
    "violations": [
      { "line": 5, "col": 1, "rule_id": "CP01", "message": "...", "severity": "error" }
    ],
    "fixed": false
  }
]

LSP server

Build the LSP binary (requires --features lsp):

cargo build --release --features lsp --bin squint-lsp

Neovim (nvim-lspconfig)

local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')

if not configs.squint then
  configs.squint = {
    default_config = {
      cmd = { '/path/to/squint-lsp' },
      filetypes = { 'sql' },
      root_dir = lspconfig.util.root_pattern('squint.toml', 'pyproject.toml', '.git'),
      settings = {},
    },
  }
end

lspconfig.squint.setup {}

The LSP server loads squint.toml from the working directory at startup, so per-rule severity overrides are respected in editor diagnostics.

pre-commit

Add to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/IlllIIIlllIlIlIIllllIIIlI/squint
    rev: v1.0.0
    hooks:
      - id: squint

pre-commit will build the binary from source on first run (requires Rust installed on the CI runner or developer machine). Subsequent runs use the cached build.

Available hooks:

Hook ID Behaviour
squint Lint staged .sql files; fail the commit if any error-severity violations exist
squint-fix Lint and auto-fix staged .sql files in place; pre-commit re-stages the changes

Lint only (recommended for CI):

- repo: https://github.com/IlllIIIlllIlIlIIllllIIIlI/squint
  rev: v0.1.0
  hooks:
    - id: squint

Lint + auto-fix on commit (recommended for local development):

- repo: https://github.com/IlllIIIlllIlIlIIllllIIIlI/squint
  rev: v0.1.0
  hooks:
    - id: squint-fix

Run only specific rules:

- repo: https://github.com/IlllIIIlllIlIlIIllllIIIlI/squint
  rev: v0.1.0
  hooks:
    - id: squint
      args: [--rules, "CP01,LT05,LT12"]

Suppress a violation inline without disabling the whole hook:

SELECT A  -- noqa: CP01
FROM T

Contributing

Running tests

cargo test                        # all tests
cargo test --test layout          # one integration test group
cargo test --test noqa            # noqa suppression tests

Adding a rule

The short steps:

  1. Create src/rules/<group>/<id>.rs and implement the Rule trait
  2. Re-export from the group's mod.rs
  3. Instantiate in build_rules() in src/lib.rs
  4. Add integration tests in tests/<group>.rs

Running benchmarks

cargo bench                       # Criterion microbenchmarks
./scripts/bench_compare.sh        # wall-clock comparison vs sqlfluff/sqlfmt

Fuzz testing

cargo install cargo-fuzz
cargo +nightly fuzz run fuzz_lint fuzz/seeds/fuzz_lint -- -max_total_time=60

See fuzz/fuzz_targets/ for all three targets (fuzz_lex, fuzz_lint, fuzz_fix) and CONTRIBUTING.md for full instructions.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pysquint-1.0.0-py3-none-win_amd64.whl (1.4 MB view details)

Uploaded Python 3Windows x86-64

pysquint-1.0.0-py3-none-manylinux_2_28_aarch64.whl (1.4 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

pysquint-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.5 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

pysquint-1.0.0-py3-none-macosx_11_0_arm64.whl (1.4 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

pysquint-1.0.0-py3-none-macosx_10_12_x86_64.whl (1.4 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file pysquint-1.0.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: pysquint-1.0.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pysquint-1.0.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 12549ba2358f42935d4594e0b7886b20313525f019418bdfd7abd3b54305a3de
MD5 0ca5549edc51bc9fb7d12355aaa1580b
BLAKE2b-256 e4f6e80541d6b38163cc16398f725fdac2357ba5009587781032f5fa54f1beea

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.0-py3-none-win_amd64.whl:

Publisher: pypi.yml on IlllIIIlllIlIlIIllllIIIlI/squint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pysquint-1.0.0-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.0-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 353e58c23d2d30247624355ddcece98dc58d78d0f4a9fa05bbc3c7d8ee8424f0
MD5 5df31e7299d8ee5f4887b46beb6c49bb
BLAKE2b-256 445aa0278a54edbc70b74c2a1b681aa9fe28feeb1078448a2996c1be19458876

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.0-py3-none-manylinux_2_28_aarch64.whl:

Publisher: pypi.yml on IlllIIIlllIlIlIIllllIIIlI/squint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pysquint-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 648e192af91e2c3b37195b04577502d5b29060620275c6848a0feb9d044db281
MD5 3017d207fda73d82147b9e5fa59349e5
BLAKE2b-256 c275027419ecc3ba7af89ae8cd111e10303df8ad94b731c032fcf80a65d2dfb8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: pypi.yml on IlllIIIlllIlIlIIllllIIIlI/squint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pysquint-1.0.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9166d3e5b77e18864254c88e776b3725b5c3213d78fcbab72e65425a8ecc4047
MD5 da447b1d088cb8c5041e354907c32f6c
BLAKE2b-256 d0dad72117c078c5294a10fd84a2b9f7e5b5382d0f7e59cac1a982980b24b82e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.0-py3-none-macosx_11_0_arm64.whl:

Publisher: pypi.yml on IlllIIIlllIlIlIIllllIIIlI/squint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pysquint-1.0.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 3898e28eaf2ffdc70000e8df72e78fc8f2d9f2e9fb9b6853c977e9ed2409912d
MD5 4ecbd311bf501a09eed81e4885a47a2c
BLAKE2b-256 68058b3f42800eaeb6a02c28e9ce74890ea7432fbff6e559278e855ac48883c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: pypi.yml on IlllIIIlllIlIlIIllllIIIlI/squint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page