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-linter

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

LSP server (optional, feature-gated):

cargo install squint-linter --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.1
    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.1-py3-none-win_amd64.whl (1.4 MB view details)

Uploaded Python 3Windows x86-64

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

Uploaded Python 3manylinux: glibc 2.28+ ARM64

pysquint-1.0.1-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.1-py3-none-macosx_11_0_arm64.whl (1.4 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

pysquint-1.0.1-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.1-py3-none-win_amd64.whl.

File metadata

  • Download URL: pysquint-1.0.1-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.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 1a719f2aafcb550e6d6cc8f630ff37ccba232106c6ab7f66f6590f081738b622
MD5 70b9939d5f5d3c5c249def1a13acd825
BLAKE2b-256 313a5b4d00979503d10f2c4a647791d5c7065fab4565593ab7702a4c057f06ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.1-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.1-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.1-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 ca4054c73ae20a66a1950e05bfa04254d2aff9caef6b52b7efd655d843c72a44
MD5 2af80504a84ffaac94f7004af55de56f
BLAKE2b-256 3f52131844f4d1b9973c5ef51d4b2de336ba1ebd5ce5780c35b56de71b226560

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.1-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.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 32e35598ef032c636ceb2f99f7d3007b6184ad4c6f38b689222a4479a1c517b5
MD5 c6d9312ad35bdecdcc8d824cadb2b989
BLAKE2b-256 0b3a7212f93699d057bd8396fbce0e12d90166bbcb59ac94a5ec05d019d7ac99

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.1-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.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2447411d6fc38033f5f09ea38ef4f1efcf9fd7df0f54ffb7cebf31ee1d29c12f
MD5 1352b9ef1a59190d7d7dcf7b6ae7a482
BLAKE2b-256 1c27a1dddee668395b76d674e076bdd44be18da07b459cbf9299368683210d79

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.1-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.1-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pysquint-1.0.1-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ba3c4f4faa9026ca8f05bd27b204b72ee3d55948384920907e700cd506bcd8ca
MD5 33dbc27ce31e7064bcb713a6d91418ae
BLAKE2b-256 222b199f47cf3cff71d9f4047976a517272b6be6dc76f1c301e8e2ee16d2eada

See more details on using hashes here.

Provenance

The following attestation bundles were made for pysquint-1.0.1-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