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 -- noqasuppression — per-line and per-rule (-- noqa: CP01,LT05)-- fmt: offblocks — opt out of formatting for hand-crafted sections- Severity levels — configure rules as
error(exit 1) orwarning(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
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:
- Create
src/rules/<group>/<id>.rsand implement theRuletrait - Re-export from the group's
mod.rs - Instantiate in
build_rules()insrc/lib.rs - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a719f2aafcb550e6d6cc8f630ff37ccba232106c6ab7f66f6590f081738b622
|
|
| MD5 |
70b9939d5f5d3c5c249def1a13acd825
|
|
| BLAKE2b-256 |
313a5b4d00979503d10f2c4a647791d5c7065fab4565593ab7702a4c057f06ba
|
Provenance
The following attestation bundles were made for pysquint-1.0.1-py3-none-win_amd64.whl:
Publisher:
pypi.yml on IlllIIIlllIlIlIIllllIIIlI/squint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysquint-1.0.1-py3-none-win_amd64.whl -
Subject digest:
1a719f2aafcb550e6d6cc8f630ff37ccba232106c6ab7f66f6590f081738b622 - Sigstore transparency entry: 1245380662
- Sigstore integration time:
-
Permalink:
IlllIIIlllIlIlIIllllIIIlI/squint@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/IlllIIIlllIlIlIIllllIIIlI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pysquint-1.0.1-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: pysquint-1.0.1-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca4054c73ae20a66a1950e05bfa04254d2aff9caef6b52b7efd655d843c72a44
|
|
| MD5 |
2af80504a84ffaac94f7004af55de56f
|
|
| BLAKE2b-256 |
3f52131844f4d1b9973c5ef51d4b2de336ba1ebd5ce5780c35b56de71b226560
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysquint-1.0.1-py3-none-manylinux_2_28_aarch64.whl -
Subject digest:
ca4054c73ae20a66a1950e05bfa04254d2aff9caef6b52b7efd655d843c72a44 - Sigstore transparency entry: 1245380670
- Sigstore integration time:
-
Permalink:
IlllIIIlllIlIlIIllllIIIlI/squint@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/IlllIIIlllIlIlIIllllIIIlI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pysquint-1.0.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pysquint-1.0.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.5 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32e35598ef032c636ceb2f99f7d3007b6184ad4c6f38b689222a4479a1c517b5
|
|
| MD5 |
c6d9312ad35bdecdcc8d824cadb2b989
|
|
| BLAKE2b-256 |
0b3a7212f93699d057bd8396fbce0e12d90166bbcb59ac94a5ec05d019d7ac99
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysquint-1.0.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
32e35598ef032c636ceb2f99f7d3007b6184ad4c6f38b689222a4479a1c517b5 - Sigstore transparency entry: 1245380665
- Sigstore integration time:
-
Permalink:
IlllIIIlllIlIlIIllllIIIlI/squint@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/IlllIIIlllIlIlIIllllIIIlI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pysquint-1.0.1-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: pysquint-1.0.1-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2447411d6fc38033f5f09ea38ef4f1efcf9fd7df0f54ffb7cebf31ee1d29c12f
|
|
| MD5 |
1352b9ef1a59190d7d7dcf7b6ae7a482
|
|
| BLAKE2b-256 |
1c27a1dddee668395b76d674e076bdd44be18da07b459cbf9299368683210d79
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysquint-1.0.1-py3-none-macosx_11_0_arm64.whl -
Subject digest:
2447411d6fc38033f5f09ea38ef4f1efcf9fd7df0f54ffb7cebf31ee1d29c12f - Sigstore transparency entry: 1245380657
- Sigstore integration time:
-
Permalink:
IlllIIIlllIlIlIIllllIIIlI/squint@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/IlllIIIlllIlIlIIllllIIIlI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pysquint-1.0.1-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: pysquint-1.0.1-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba3c4f4faa9026ca8f05bd27b204b72ee3d55948384920907e700cd506bcd8ca
|
|
| MD5 |
33dbc27ce31e7064bcb713a6d91418ae
|
|
| BLAKE2b-256 |
222b199f47cf3cff71d9f4047976a517272b6be6dc76f1c301e8e2ee16d2eada
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysquint-1.0.1-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
ba3c4f4faa9026ca8f05bd27b204b72ee3d55948384920907e700cd506bcd8ca - Sigstore transparency entry: 1245380661
- Sigstore integration time:
-
Permalink:
IlllIIIlllIlIlIIllllIIIlI/squint@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/IlllIIIlllIlIlIIllllIIIlI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@8fdd796a3587766bb815aa4b6df5b392dbfbcec9 -
Trigger Event:
push
-
Statement type: