yukikotani231/sqlsift

SQL static analyzer - validates queries against schema definitions without database connection

Rust

1

232 commits

updated Oct 18, 2026

See the code

See what people are saying

SourceMessageScoreDate

I built a schema-aware static analyzer for SQL. (r/coolgithubprojects)

[https://sqlsift.dev/](https://sqlsift.dev/) I built a schema-aware static analyzer for SQL. It reads your schema (migrations or a dump) and checks your queries against it, without connecting to a database. Besides type checking, it catches queries that are syntactically valid but almost certainly…

0

Oct 13, 2026

README

sqlsift

CI npm License: MIT Playground Docs

Schema-aware static analysis for SQL. No database needed.

Think of it as a type checker for your SQL: sqlsift replays your migrations into an in-memory schema and checks every query against it — in .sql files, sqlc and aiosql query files, sql`...` templates in TypeScript / JavaScript, sqlx queries in Rust, and SQL strings passed to database calls in Python and Go. It runs offline in milliseconds, so it fits in your editor, pre-commit hooks and CI.

▶ Try it in your browser — no install; the playground runs sqlsift locally via WebAssembly, so your SQL never leaves the page. 📖 Read the user guide for everything else.

A migration renames page_views.site_id to website_id; sqlsift init detects the project and sqlsift check reports the query that still uses site_id, an ON CONFLICT target with no unique key, and a WHERE clause that turns a LEFT JOIN into an inner join

The same output as text
$ npx sqlsift-cli init     # finds your migrations, dialect and queries, writes sqlsift.toml
$ npx sqlsift-cli check
error[E0002]: Column 'site_id' not found in table 'page_views'
  --> src/retention.ts:6:11
    |
  6 |     WHERE site_id = ${siteId} AND created_at < ${cutoff}
    |           ^^^^^^^
    = help: 'site_id' was renamed to 'website_id' in migrations/002_rename_site_id.sql:1

error[E0014]: No unique constraint or primary key of 'users' matches ON CONFLICT (email)
  --> src/retention.ts:11:70
    |
 11 |   await sql`INSERT INTO users (email) VALUES (${email}) ON CONFLICT (email) DO NOTHING`;
    |                                                                      ^^^^^
    = help: The unique keys of 'users' are (id); add a unique constraint or index on (email)

warning[E0023]: WHERE condition on 'p.published_at' removes the rows the outer join adds without a match in 'p': it works as an inner join
  --> src/retention.ts:19:11
    |
 19 |     WHERE p.published_at > now() - interval '7 days'
    |           ^^^^^^^^^^^^^^
    = help: Move the condition into the join's ON clause to keep unmatched rows, or write an inner JOIN (or test 'p.published_at IS NULL OR ...')


Found 2 error(s), 1 warning(s) in 1 file(s)

28 rules cover what the schema alone can tell you: missing tables and columns, type mismatches, NOT NULL columns left out, ON CONFLICT targets with no unique key, GROUP BY errors, values out of range, and warnings for queries that are valid but probably not what you meant. Anything sqlsift can't work out is left unreported: on 16 open-source apps (687 files) it reports zero false positives.

Status: early development (alpha). Diagnostics may change between versions. Feedback, bug reports and real-world SQL that sqlsift gets wrong are very welcome — please open an issue.

Why sqlsift?

Raw SQL is usually only checked when it runs. Rename a column in a migration and a query in some other file breaks silently until it hits staging — or production. sqlsift closes that gap:

  • No database needed — no Docker, no connection string, no test fixtures. Just your .sql files.
  • Knows your schema — understands CREATE TABLE, ALTER TABLE, views, enums and migration directories, and keeps track of what each query can see (CTEs, subqueries, LATERAL, aliases).
  • Helpful diagnostics — source spans plus "did you mean" suggestions for typos.
  • Runs everywhere — CLI, GitHub Actions / SARIF code scanning, and a language server with a VS Code extension.
  • Fast — written in Rust; checks a typical project in milliseconds.
  • PostgreSQL, MySQL and SQLite dialects, plus Snowflake, BigQuery, Redshift and Databricks in alpha.

How it compares

ToolWhat it checksNeeds a running DB?
sqlsiftQueries against your schema (tables, columns, types)No
SQLFluffStyle and formattingNo — but does not know your schema
SquawkMigration safety (locking, backwards compatibility)No — lints DDL, not queries
sqlx query!Queries in Rust code, at compile timeYes (or a cache prepared from one)
sqlcQueries it generates code fromNo — but you adopt its codegen workflow

sqlsift complements these tools: keep your formatter and migration linter, and add sqlsift to make sure the queries still match the schema.

Installation

# npm (prebuilt binaries for macOS, Linux and Windows)
npm install -g sqlsift-cli
# or run without installing
npx sqlsift-cli check --schema schema.sql queries/*.sql

# From source (Rust toolchain required)
cargo install --git https://github.com/yukikotani231/sqlsift sqlsift-cli

Prebuilt binaries are also available on the Releases page, or try it without installing in the Playground.

Quick Start

In an existing project, start with:

sqlsift init

It finds your schema (sqlc config, Prisma / Supabase / Drizzle / Flyway / golang-migrate / goose / dbmate migrations, structure.sql or schema.sql dumps, dbt), your queries (.sql files, Prisma TypedSQL, dbt models, sql`...` templates in TypeScript / JavaScript, sqlx queries in Rust, database calls in Python and Go) and the dialect, writes a commented sqlsift.toml, runs a first check and prints the next steps. It never overwrites an existing sqlsift.toml without --force; --dry-run only prints the configuration. See sqlsift init.

Or pass everything on the command line:

# Validate queries against a schema file
sqlsift check --schema schema.sql queries/*.sql

# Use multiple schema files
sqlsift check -s users.sql -s orders.sql queries/*.sql

# Use a migrations directory (all *.sql files, recursively, in filename order;
# rollback files such as *.down.sql are skipped)
sqlsift check --schema-dir ./migrations queries/*.sql

# Other dialects
sqlsift check --dialect mysql --schema schema.sql queries/*.sql

# Read a query from stdin (e.g. the staged version in a pre-commit hook)
git show :queries/users.sql | sqlsift check -s schema.sql --stdin-filename queries/users.sql -

# SQL in TypeScript / JavaScript (and Vue / Svelte <script>) tagged templates
# (sql`...`; node_modules and dist are skipped; more tags with
# `embedded_sql_tags` in sqlsift.toml)
sqlsift check -s schema.sql 'src/**/*.ts'

# SQL strings passed to database/sql, sqlx and pgx calls in Go
# (db.QueryContext(ctx, "..."); vendor is skipped; more calls with
# `embedded_sql_go_functions` in sqlsift.toml)
sqlsift check -s schema.sql 'internal/**/*.go'

To avoid repeating flags, add a sqlsift.toml to your project root:

schema_dir = "db/migrations"     # or: schema = ["db/schema.sql"]
files = ["queries/**/*.sql"]
dialect = "postgresql"

[rules]
ambiguous-column = "warn"        # report, but don't fail the check

Then just run sqlsift check. See the configuration reference for every key, and sqlsift schema to inspect what sqlsift loaded from your schema.

Use It With Your Stack

sqlsift only needs SQL files for the schema, so it works with whatever produces them.

StackSchema source
Prismasqlsift check --schema-dir prisma/migrations queries/*.sql
Rails (schema_format = :sql)sqlsift check --schema db/structure.sql queries/*.sql
sqlx / golang-migrate / goose / Flyway / dbmatesqlsift check --schema-dir migrations queries/*.sql
pg_dump --schema-onlysqlsift check --schema schema.sql queries/*.sql
Hand-written DDLsqlsift check --schema schema/*.sql queries/**/*.sql
aiosql / HugSQL (named queries in .sql files)sqlsift check --schema-dir migrations queries/*.sql (:param placeholders; diagnostics name the -- name: / -- :name query)
dbt (Postgres, MySQL, SQLite; Snowflake, BigQuery, Redshift, Databricks in alpha)sqlsift check --schema sources.sql models/**/*.sql (Jinja is masked automatically next to dbt_project.yml; after dbt docs generate, ref() / source() columns come from target/catalog.json and --schema is optional — alpha)

Rollback migrations are skipped automatically; see Loading your schema.

Example projects, each with a ready-to-copy sqlsift.toml and checked in CI: sqlc (Go), database/sql, sqlx and pgx (Go), Prisma TypedSQL, aiosql (Python), postgres.js, sqlx (Rust), plain SQL + migrations (a migration renames a column that a query still uses), MySQL and dbt on PostgreSQL. See examples/.

Editor Integration

sqlsift ships a language server (sqlsift-lsp) that shows diagnostics as you type. Install the sqlsift VS Code extension (sqlsift.sqlsift), which bundles the server, or set it up in Neovim, Helix and other editors.

CI Integration

# .github/workflows/sqlsift.yml
name: SQL Lint
on: [push, pull_request]
jobs:
  sqlsift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: yukikotani231/sqlsift@main  # or pin a release tag
        with:
          schema: db/schema.sql           # or schema-dir: db/migrations
          files: queries/**/*.sql

The action is also listed on the GitHub Marketplace. Errors are shown as annotations on the pull request diff. Add diff-base: ${{ github.base_ref }} to report only the diagnostics a pull request introduces (the action checks the base commit too, so no baseline file is needed). The CI guide covers the action's inputs, re-checking every query when the schema changes, GitHub Code Scanning (SARIF), --format github / json for other setups, --max-warnings for rolling out a rule gradually, and a baseline for adopting sqlsift on an existing codebase (sqlsift check --write-baseline records the current diagnostics in sqlsift-baseline.json; with baseline = "sqlsift-baseline.json" in sqlsift.toml only new ones are reported). There is also a pre-commit recipe.

Diagnostic Rules

CodeNameDescription
E0001table-not-foundReferenced table does not exist in schema
E0002column-not-foundReferenced column does not exist in table
E0003type-mismatchType incompatibility in expressions (comparisons, arithmetic, INSERT/UPDATE values, enums)
E0004potential-null-violationPotential NOT NULL violation (explicit NULL assignment)
E0005column-count-mismatchColumn count doesn't match (INSERT values, column alias lists, subqueries, row comparisons)
E0006ambiguous-columnColumn reference is ambiguous across tables
E0007join-type-mismatchJOIN condition compares incompatible types
E0008missing-required-columnINSERT omits a NOT NULL column that has no default
E0009duplicate-nameTable alias or CTE name given twice in one query
E0010duplicate-target-columnColumn given twice in an INSERT column list or UPDATE SET
E0011position-out-of-rangeORDER BY / GROUP BY position is not in the select list
E0012misplaced-aggregateAggregate or window function where it is not allowed
E0013generated-column-writeINSERT or UPDATE gives a value to a generated column
E0014unmatched-conflict-targetON CONFLICT columns match no unique constraint
E0015distinct-order-bySELECT DISTINCT ordered by a column it doesn't select
E0016grouping-errorColumn must appear in GROUP BY or be used in an aggregate
E0017value-out-of-rangeLiteral too long or out of range for the column type
E0018null-comparisonComparison with NULL using = or <> is never true
E0019not-in-with-nullsNOT IN over a nullable subquery column
E0020outer-column-in-subqueryIN subquery selects a column of the outer query
E0021missing-join-conditionTables in FROM with no condition linking them
E0022constant-conditionCondition that is always or never true from the schema
E0023outer-join-filteredWHERE condition turns an outer join into an inner join
E0024unfiltered-writeUPDATE or DELETE without WHERE
E0025insert-without-columnsINSERT without a column list
E0026select-starSELECT * in a query's result columns
E0027wrong-argument-typeFunction or operator given an argument type it doesn't take
E0028limit-without-order-byLIMIT or OFFSET without ORDER BY
E0029implicit-conversionType mismatch MySQL or SQLite converts implicitly
E0030foreign-key-join-mismatchJOIN condition doesn't follow the foreign key between the tables
E0031join-fan-outAggregate over rows that another one-to-many join multiplies
E0032integer-divisionDivision of integer columns truncates toward zero

E0001-E0017 and E0027 report queries the database rejects and are errors by default; E0018-E0023 and E0029-E0031 (suspicious) report valid queries that almost certainly don't do what was meant and are warnings by default; E0024-E0026 and E0028 (restriction) and E0032 (pedantic) are off until enabled (-W restriction, -W pedantic).

Every rule can be set to off, warn or error per project ([rules] in sqlsift.toml) or per run (-A / -W / -D), and silenced for a line or a file with -- sqlsift:disable / -- sqlsift:disable-file comments, or for a whole existing backlog with a baseline (--write-baseline / --baseline). See Rules and levels and Suppressing diagnostics.

Documentation

The user guide covers:

Roadmap

Completed

  • Configuration file (sqlsift.toml)

  • MySQL dialect support

  • SQLite dialect support

  • Type inference for expressions (WHERE, JOIN, arithmetic, INSERT/UPDATE)

  • LSP server for editor integration (VS Code extension)

  • CASE expression type consistency checking

  • Subquery/CTE/VIEW column type inference

  • Per-rule and per-category levels (off / warn / error)

  • Baseline of known diagnostics (--write-baseline, baseline in sqlsift.toml)

  • SQL embedded in application code (sqlc, aiosql and HugSQL query names and parameters, TypeScript tagged templates, sqlx in Rust, database calls in Python and Go)

  • sqlsift init, example projects per stack, and a GitHub Action that reports only what a pull request introduces

  • 28 rules, including suspicious warnings and opt-in restriction rules

  • Snowflake, BigQuery, Redshift and Databricks dialects and dbt catalog.json (alpha)

Planned

See ROADMAP.md for what comes next.

Contributing

Contributions are welcome! The most useful things right now are:

  • Real-world SQL that sqlsift gets wrong — false positives and missed errors. Please open an issue with a minimal schema and query.
  • Dialect coverage — MySQL and SQLite edge cases, and the alpha warehouse dialects (Snowflake, BigQuery, Redshift, Databricks).
  • Editor integrations — setup guides or plugins for editors other than VS Code.

To work on the code:

git clone https://github.com/yukikotani231/sqlsift && cd sqlsift
git config core.hooksPath .githooks   # fmt + clippy pre-commit checks
cargo test
cargo run -- check --schema tests/fixtures/schema.sql tests/fixtures/invalid_query.sql

See CONTRIBUTING.md for the development setup, repository layout and how to add a rule. Commits follow Conventional Commits (feat:, fix:, docs: …), which drive automated releases.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

cli
code-quality
developer-tools
linter
postgresql
rust
sql
sql-parser
static-analysis

yukikotani231/sqlsift

SQL static analyzer - validates queries against schema definitions without database connection

Rust

1

232 commits

updated Oct 18, 2026

See the code

See what people are saying

SourceMessageScoreDate

I built a schema-aware static analyzer for SQL. (r/coolgithubprojects)

[https://sqlsift.dev/](https://sqlsift.dev/) I built a schema-aware static analyzer for SQL. It reads your schema (migrations or a dump) and checks your queries against it, without connecting to a database. Besides type checking, it catches queries that are syntactically valid but almost certainly…

0

Oct 13, 2026

README

sqlsift

CI npm License: MIT Playground Docs

Schema-aware static analysis for SQL. No database needed.

Think of it as a type checker for your SQL: sqlsift replays your migrations into an in-memory schema and checks every query against it — in .sql files, sqlc and aiosql query files, sql`...` templates in TypeScript / JavaScript, sqlx queries in Rust, and SQL strings passed to database calls in Python and Go. It runs offline in milliseconds, so it fits in your editor, pre-commit hooks and CI.

▶ Try it in your browser — no install; the playground runs sqlsift locally via WebAssembly, so your SQL never leaves the page. 📖 Read the user guide for everything else.

A migration renames page_views.site_id to website_id; sqlsift init detects the project and sqlsift check reports the query that still uses site_id, an ON CONFLICT target with no unique key, and a WHERE clause that turns a LEFT JOIN into an inner join

The same output as text
$ npx sqlsift-cli init     # finds your migrations, dialect and queries, writes sqlsift.toml
$ npx sqlsift-cli check
error[E0002]: Column 'site_id' not found in table 'page_views'
  --> src/retention.ts:6:11
    |
  6 |     WHERE site_id = ${siteId} AND created_at < ${cutoff}
    |           ^^^^^^^
    = help: 'site_id' was renamed to 'website_id' in migrations/002_rename_site_id.sql:1

error[E0014]: No unique constraint or primary key of 'users' matches ON CONFLICT (email)
  --> src/retention.ts:11:70
    |
 11 |   await sql`INSERT INTO users (email) VALUES (${email}) ON CONFLICT (email) DO NOTHING`;
    |                                                                      ^^^^^
    = help: The unique keys of 'users' are (id); add a unique constraint or index on (email)

warning[E0023]: WHERE condition on 'p.published_at' removes the rows the outer join adds without a match in 'p': it works as an inner join
  --> src/retention.ts:19:11
    |
 19 |     WHERE p.published_at > now() - interval '7 days'
    |           ^^^^^^^^^^^^^^
    = help: Move the condition into the join's ON clause to keep unmatched rows, or write an inner JOIN (or test 'p.published_at IS NULL OR ...')


Found 2 error(s), 1 warning(s) in 1 file(s)

28 rules cover what the schema alone can tell you: missing tables and columns, type mismatches, NOT NULL columns left out, ON CONFLICT targets with no unique key, GROUP BY errors, values out of range, and warnings for queries that are valid but probably not what you meant. Anything sqlsift can't work out is left unreported: on 16 open-source apps (687 files) it reports zero false positives.

Status: early development (alpha). Diagnostics may change between versions. Feedback, bug reports and real-world SQL that sqlsift gets wrong are very welcome — please open an issue.

Why sqlsift?

Raw SQL is usually only checked when it runs. Rename a column in a migration and a query in some other file breaks silently until it hits staging — or production. sqlsift closes that gap:

  • No database needed — no Docker, no connection string, no test fixtures. Just your .sql files.
  • Knows your schema — understands CREATE TABLE, ALTER TABLE, views, enums and migration directories, and keeps track of what each query can see (CTEs, subqueries, LATERAL, aliases).
  • Helpful diagnostics — source spans plus "did you mean" suggestions for typos.
  • Runs everywhere — CLI, GitHub Actions / SARIF code scanning, and a language server with a VS Code extension.
  • Fast — written in Rust; checks a typical project in milliseconds.
  • PostgreSQL, MySQL and SQLite dialects, plus Snowflake, BigQuery, Redshift and Databricks in alpha.

How it compares

ToolWhat it checksNeeds a running DB?
sqlsiftQueries against your schema (tables, columns, types)No
SQLFluffStyle and formattingNo — but does not know your schema
SquawkMigration safety (locking, backwards compatibility)No — lints DDL, not queries
sqlx query!Queries in Rust code, at compile timeYes (or a cache prepared from one)
sqlcQueries it generates code fromNo — but you adopt its codegen workflow

sqlsift complements these tools: keep your formatter and migration linter, and add sqlsift to make sure the queries still match the schema.

Installation

# npm (prebuilt binaries for macOS, Linux and Windows)
npm install -g sqlsift-cli
# or run without installing
npx sqlsift-cli check --schema schema.sql queries/*.sql

# From source (Rust toolchain required)
cargo install --git https://github.com/yukikotani231/sqlsift sqlsift-cli

Prebuilt binaries are also available on the Releases page, or try it without installing in the Playground.

Quick Start

In an existing project, start with:

sqlsift init

It finds your schema (sqlc config, Prisma / Supabase / Drizzle / Flyway / golang-migrate / goose / dbmate migrations, structure.sql or schema.sql dumps, dbt), your queries (.sql files, Prisma TypedSQL, dbt models, sql`...` templates in TypeScript / JavaScript, sqlx queries in Rust, database calls in Python and Go) and the dialect, writes a commented sqlsift.toml, runs a first check and prints the next steps. It never overwrites an existing sqlsift.toml without --force; --dry-run only prints the configuration. See sqlsift init.

Or pass everything on the command line:

# Validate queries against a schema file
sqlsift check --schema schema.sql queries/*.sql

# Use multiple schema files
sqlsift check -s users.sql -s orders.sql queries/*.sql

# Use a migrations directory (all *.sql files, recursively, in filename order;
# rollback files such as *.down.sql are skipped)
sqlsift check --schema-dir ./migrations queries/*.sql

# Other dialects
sqlsift check --dialect mysql --schema schema.sql queries/*.sql

# Read a query from stdin (e.g. the staged version in a pre-commit hook)
git show :queries/users.sql | sqlsift check -s schema.sql --stdin-filename queries/users.sql -

# SQL in TypeScript / JavaScript (and Vue / Svelte <script>) tagged templates
# (sql`...`; node_modules and dist are skipped; more tags with
# `embedded_sql_tags` in sqlsift.toml)
sqlsift check -s schema.sql 'src/**/*.ts'

# SQL strings passed to database/sql, sqlx and pgx calls in Go
# (db.QueryContext(ctx, "..."); vendor is skipped; more calls with
# `embedded_sql_go_functions` in sqlsift.toml)
sqlsift check -s schema.sql 'internal/**/*.go'

To avoid repeating flags, add a sqlsift.toml to your project root:

schema_dir = "db/migrations"     # or: schema = ["db/schema.sql"]
files = ["queries/**/*.sql"]
dialect = "postgresql"

[rules]
ambiguous-column = "warn"        # report, but don't fail the check

Then just run sqlsift check. See the configuration reference for every key, and sqlsift schema to inspect what sqlsift loaded from your schema.

Use It With Your Stack

sqlsift only needs SQL files for the schema, so it works with whatever produces them.

StackSchema source
Prismasqlsift check --schema-dir prisma/migrations queries/*.sql
Rails (schema_format = :sql)sqlsift check --schema db/structure.sql queries/*.sql
sqlx / golang-migrate / goose / Flyway / dbmatesqlsift check --schema-dir migrations queries/*.sql
pg_dump --schema-onlysqlsift check --schema schema.sql queries/*.sql
Hand-written DDLsqlsift check --schema schema/*.sql queries/**/*.sql
aiosql / HugSQL (named queries in .sql files)sqlsift check --schema-dir migrations queries/*.sql (:param placeholders; diagnostics name the -- name: / -- :name query)
dbt (Postgres, MySQL, SQLite; Snowflake, BigQuery, Redshift, Databricks in alpha)sqlsift check --schema sources.sql models/**/*.sql (Jinja is masked automatically next to dbt_project.yml; after dbt docs generate, ref() / source() columns come from target/catalog.json and --schema is optional — alpha)

Rollback migrations are skipped automatically; see Loading your schema.

Example projects, each with a ready-to-copy sqlsift.toml and checked in CI: sqlc (Go), database/sql, sqlx and pgx (Go), Prisma TypedSQL, aiosql (Python), postgres.js, sqlx (Rust), plain SQL + migrations (a migration renames a column that a query still uses), MySQL and dbt on PostgreSQL. See examples/.

Editor Integration

sqlsift ships a language server (sqlsift-lsp) that shows diagnostics as you type. Install the sqlsift VS Code extension (sqlsift.sqlsift), which bundles the server, or set it up in Neovim, Helix and other editors.

CI Integration

# .github/workflows/sqlsift.yml
name: SQL Lint
on: [push, pull_request]
jobs:
  sqlsift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: yukikotani231/sqlsift@main  # or pin a release tag
        with:
          schema: db/schema.sql           # or schema-dir: db/migrations
          files: queries/**/*.sql

The action is also listed on the GitHub Marketplace. Errors are shown as annotations on the pull request diff. Add diff-base: ${{ github.base_ref }} to report only the diagnostics a pull request introduces (the action checks the base commit too, so no baseline file is needed). The CI guide covers the action's inputs, re-checking every query when the schema changes, GitHub Code Scanning (SARIF), --format github / json for other setups, --max-warnings for rolling out a rule gradually, and a baseline for adopting sqlsift on an existing codebase (sqlsift check --write-baseline records the current diagnostics in sqlsift-baseline.json; with baseline = "sqlsift-baseline.json" in sqlsift.toml only new ones are reported). There is also a pre-commit recipe.

Diagnostic Rules

CodeNameDescription
E0001table-not-foundReferenced table does not exist in schema
E0002column-not-foundReferenced column does not exist in table
E0003type-mismatchType incompatibility in expressions (comparisons, arithmetic, INSERT/UPDATE values, enums)
E0004potential-null-violationPotential NOT NULL violation (explicit NULL assignment)
E0005column-count-mismatchColumn count doesn't match (INSERT values, column alias lists, subqueries, row comparisons)
E0006ambiguous-columnColumn reference is ambiguous across tables
E0007join-type-mismatchJOIN condition compares incompatible types
E0008missing-required-columnINSERT omits a NOT NULL column that has no default
E0009duplicate-nameTable alias or CTE name given twice in one query
E0010duplicate-target-columnColumn given twice in an INSERT column list or UPDATE SET
E0011position-out-of-rangeORDER BY / GROUP BY position is not in the select list
E0012misplaced-aggregateAggregate or window function where it is not allowed
E0013generated-column-writeINSERT or UPDATE gives a value to a generated column
E0014unmatched-conflict-targetON CONFLICT columns match no unique constraint
E0015distinct-order-bySELECT DISTINCT ordered by a column it doesn't select
E0016grouping-errorColumn must appear in GROUP BY or be used in an aggregate
E0017value-out-of-rangeLiteral too long or out of range for the column type
E0018null-comparisonComparison with NULL using = or <> is never true
E0019not-in-with-nullsNOT IN over a nullable subquery column
E0020outer-column-in-subqueryIN subquery selects a column of the outer query
E0021missing-join-conditionTables in FROM with no condition linking them
E0022constant-conditionCondition that is always or never true from the schema
E0023outer-join-filteredWHERE condition turns an outer join into an inner join
E0024unfiltered-writeUPDATE or DELETE without WHERE
E0025insert-without-columnsINSERT without a column list
E0026select-starSELECT * in a query's result columns
E0027wrong-argument-typeFunction or operator given an argument type it doesn't take
E0028limit-without-order-byLIMIT or OFFSET without ORDER BY
E0029implicit-conversionType mismatch MySQL or SQLite converts implicitly
E0030foreign-key-join-mismatchJOIN condition doesn't follow the foreign key between the tables
E0031join-fan-outAggregate over rows that another one-to-many join multiplies
E0032integer-divisionDivision of integer columns truncates toward zero

E0001-E0017 and E0027 report queries the database rejects and are errors by default; E0018-E0023 and E0029-E0031 (suspicious) report valid queries that almost certainly don't do what was meant and are warnings by default; E0024-E0026 and E0028 (restriction) and E0032 (pedantic) are off until enabled (-W restriction, -W pedantic).

Every rule can be set to off, warn or error per project ([rules] in sqlsift.toml) or per run (-A / -W / -D), and silenced for a line or a file with -- sqlsift:disable / -- sqlsift:disable-file comments, or for a whole existing backlog with a baseline (--write-baseline / --baseline). See Rules and levels and Suppressing diagnostics.

Documentation

The user guide covers:

Roadmap

Completed

  • Configuration file (sqlsift.toml)

  • MySQL dialect support

  • SQLite dialect support

  • Type inference for expressions (WHERE, JOIN, arithmetic, INSERT/UPDATE)

  • LSP server for editor integration (VS Code extension)

  • CASE expression type consistency checking

  • Subquery/CTE/VIEW column type inference

  • Per-rule and per-category levels (off / warn / error)

  • Baseline of known diagnostics (--write-baseline, baseline in sqlsift.toml)

  • SQL embedded in application code (sqlc, aiosql and HugSQL query names and parameters, TypeScript tagged templates, sqlx in Rust, database calls in Python and Go)

  • sqlsift init, example projects per stack, and a GitHub Action that reports only what a pull request introduces

  • 28 rules, including suspicious warnings and opt-in restriction rules

  • Snowflake, BigQuery, Redshift and Databricks dialects and dbt catalog.json (alpha)

Planned

See ROADMAP.md for what comes next.

Contributing

Contributions are welcome! The most useful things right now are:

  • Real-world SQL that sqlsift gets wrong — false positives and missed errors. Please open an issue with a minimal schema and query.
  • Dialect coverage — MySQL and SQLite edge cases, and the alpha warehouse dialects (Snowflake, BigQuery, Redshift, Databricks).
  • Editor integrations — setup guides or plugins for editors other than VS Code.

To work on the code:

git clone https://github.com/yukikotani231/sqlsift && cd sqlsift
git config core.hooksPath .githooks   # fmt + clippy pre-commit checks
cargo test
cargo run -- check --schema tests/fixtures/schema.sql tests/fixtures/invalid_query.sql

See CONTRIBUTING.md for the development setup, repository layout and how to add a rule. Commits follow Conventional Commits (feat:, fix:, docs: …), which drive automated releases.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

cli
code-quality
developer-tools
linter
postgresql
rust
sql
sql-parser
static-analysis