Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

SQL Query Analyzer — static analysis and LLM-powered optimization for SQL queries

Crates.ioDocs.rsCIcodecovLicense: MITHits-of-CodeREUSE status

Static analysis and LLM-powered optimization for SQL queries.

A comprehensive SQL analysis tool that combines fast, deterministic static analysis with optional AI-powered insights. Identifies performance issues, style violations, and security vulnerabilities in your SQL queries.

Table of Contents

Highlights

  • 35 Built-in Rules — Performance, style, and security checks run instantly without API calls
  • Schema-Aware Analysis — Validates queries against your database schema, suggests missing indexes
  • Multi-Dialect Support — Generic, MySQL, PostgreSQL, SQLite, and ClickHouse with preprocessor for dialect-specific syntax
  • Multiple Output Formats — Text, JSON, YAML, and SARIF for CI/CD integration
  • Parallel Execution — Rules execute concurrently using rayon
  • Optional LLM Analysis — Deep semantic analysis via OpenAI, Anthropic, or local Ollama
  • Configurable — Disable rules, override severity levels, customize via TOML

Installation

From source

cargo install --path .

Pre-built binaries

Download from Releases.

Quick Start

# Run static analysis (no API key required)
sql-query-analyzer analyze -s schema.sql -q queries.sql
# Output as SARIF for CI/CD
sql-query-analyzer analyze -s schema.sql -q queries.sql -f sarif > results.sarif
# Pipe queries from stdinecho"SELECT * FROM users"| sql-query-analyzer analyze -s schema.sql -q -
# Enable LLM analysisexport LLM_API_KEY="sk-..."
sql-query-analyzer analyze -s schema.sql -q queries.sql --provider openai

Rules

Performance Rules

IDRuleSeverityDescription
PERF001Select star without limitWarningSELECT * without LIMIT can return unbounded rows
PERF002Leading wildcardWarningLIKE '%value' prevents index usage
PERF003OR instead of INInfoMultiple OR conditions can be simplified to IN
PERF004Large offsetWarningOFFSET > 1000 causes performance degradation
PERF005Missing join conditionErrorCartesian product detected
PERF006Distinct with order byInfoPotentially redundant operations
PERF007Scalar subqueryWarningN+1 query pattern detected
PERF008Function on columnWarningFunction calls prevent index usage
PERF009NOT IN with subqueryWarningCan cause unexpected NULL behavior
PERF010UNION without ALLInfoUnnecessary deduplication overhead
PERF011Select without whereInfoFull table scan on large tables
PERF012COUNT(*) without WHEREWarningCounting every row scans the entire table
PERF013ORDER BY RAND()WarningFull scan and sort regardless of LIMIT
PERF014Unnecessary DISTINCTInfoDISTINCT with JOIN often hides join fan-out; DISTINCT * escalates to Warning
PERF015Implicit type conversionWarningText column compared with numeric literal disables its index (needs schema)
PERF016Multiple scans of same tableInfoSelf-joins and repeated subqueries multiply I/O
PERF017Correlated subqueryWarningSubquery referencing the outer query re-executes per row
PERF018HAVING without aggregateWarningNon-aggregate conditions belong in WHERE
PERF019Large IN clauseWarning50+ values degrade planning; severity scales with size
PERF020Deeply nested subqueriesWarning3+ SELECT levels; severity scales with depth

Style Rules

IDRuleSeverityDescription
STYLE001Select starInfoExplicit column list preferred
STYLE002Missing table aliasInfoMulti-table queries should use aliases
STYLE004Ordinal in ORDER BY/GROUP BYInfoORDER BY 1 breaks silently when the SELECT list changes

Security Rules

IDRuleSeverityDescription
SEC001Missing WHERE in UPDATEErrorPotentially dangerous bulk update
SEC002Missing WHERE in DELETEErrorPotentially dangerous bulk delete
SEC003TRUNCATE detectedErrorInstant data deletion without logging
SEC004DROP detectedErrorPermanent data/schema destruction
SEC005GRANT/REVOKE detectedWarningPrivilege changes belong in reviewed migrations; broad grants escalate to Error
SEC006SQL injection patternErrorAlways-true OR tautology (OR 1 = 1)
SEC007Dynamic SQL executionWarningEXEC/EXECUTE/PREPARE runs a string assembled at runtime
SEC008Hardcoded credentialErrorPlaintext secret in IDENTIFIED BY, SET PASSWORD, or a sensitive column

Schema-Aware Rules

IDRuleSeverityDescription
SCHEMA001Missing index on filterWarningWHERE/JOIN column lacks index
SCHEMA002Column not in schemaWarningReferenced column doesn't exist
SCHEMA003Index suggestionInfoORDER BY column could benefit from index
SCHEMA004JOIN on non-indexed columnWarningJOIN column must lead an index of its own table

Configuration

Configuration is loaded from (in order of precedence):

  1. Command-line arguments
  2. Environment variables
  3. .sql-analyzer.toml in current directory
  4. ~/.config/sql-analyzer/config.toml

Example Configuration

[rules]
# Disable specific rules by IDdisabled = ["STYLE001", "PERF011"]
# Override default severity levels
[rules.severity]
PERF001 = "error"# Promote to errorSCHEMA001 = "info"# Demote to info
[llm]
provider = "ollama"model = "codellama"ollama_url = "http://localhost:11434"
[retry]
max_retries = 3initial_delay_ms = 1000max_delay_ms = 30000backoff_factor = 2.0

Environment Variables

VariableDescription
LLM_API_KEYAPI key for OpenAI/Anthropic
LLM_PROVIDERProvider name (openai, anthropic, ollama)
LLM_MODELModel identifier
OLLAMA_URLOllama base URL

CLI Reference

sql-query-analyzer analyze [OPTIONS] -s <SCHEMA> -q <QUERIES>

Options

FlagDescriptionDefault
-s, --schema <FILE>Path to SQL schema filerequired
-q, --queries <FILE>Path to SQL queries file (use - for stdin)required
-p, --provider <PROVIDER>LLM provider: openai, anthropic, ollamaollama
-a, --api-key <KEY>API key (or use LLM_API_KEY env)-
-m, --model <MODEL>Model nameprovider default
--ollama-url <URL>Ollama base URLhttp://localhost:11434
--dialect <DIALECT>SQL dialect: generic, mysql, postgresql, sqlite, clickhousegeneric
-f, --output-format <FMT>Output: text, json, yaml, sariftext
-v, --verboseShow complexity scoresfalse
--dry-runShow what would be sent to LLMfalse
--no-colorDisable colored outputfalse

Exit Codes

CodeMeaning
0Success, no issues or only informational
1Warnings found
2Errors found

Example

schema.sql:

CREATETABLEusers (
id INTPRIMARY KEY,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP
);
CREATETABLEorders (
id INTPRIMARY KEY,
user_id INTNOT NULL,
total DECIMAL(10,2),
status VARCHAR(20)
);
CREATEINDEXidx_orders_userON orders(user_id);

queries.sql:

SELECT*FROM users WHERE email ='test@example.com';
SELECT*FROM orders WHERE user_id =1ORDER BY created_at DESC;
DELETEFROM users;

Output:

=== Static Analysis ===
Found 1 error(s), 2 warning(s), 1 info
Query #1:
[ERROR] SEC002: DELETE without WHERE clause is dangerous
→ Add WHERE clause to limit affected rows
[ WARN] SCHEMA001: Column 'email' in WHERE clause has no index
→ Consider adding index on 'email'
Query #2:
[ WARN] SCHEMA001: Column 'created_at' in ORDER BY has no index
→ Consider adding index on 'created_at'
[ INFO] SCHEMA003: ORDER BY column 'created_at' could benefit from index
→ CREATE INDEX idx_created_at ON table(created_at)

CI/CD Integration

GitHub Action

The easiest way to integrate SQL Query Analyzer into your CI/CD pipeline.

name: SQL Analysison:
pull_request:
paths:
- '**/*.sql'jobs:
analyze:
runs-on: ubuntu-latestpermissions:
contents: readpull-requests: writesecurity-events: writesteps:
- uses: actions/checkout@v4
- uses: RAprogramm/sql-query-analyzer@v1with:
schema: db/schema.sqlqueries: db/queries.sqlupload-sarif: 'true'post-comment: 'true'env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Action Inputs

InputDescriptionDefault
schemaPath to SQL schema filerequired
queriesPath to SQL queries filerequired
dialectSQL dialect (generic, mysql, postgresql, sqlite, clickhouse)generic
formatOutput format (text, json, yaml, sarif)text
fail-on-warningFail if warnings are foundfalse
fail-on-errorFail if errors are foundtrue
upload-sarifUpload SARIF to GitHub Security tabfalse
post-commentPost analysis as PR commentfalse

Action Outputs

OutputDescription
analysisFull analysis result
error-countNumber of errors found
warning-countNumber of warnings found
exit-codeExit code (0=ok, 1=warnings, 2=errors)

Static Analysis Only (No LLM)

For fast CI checks without external API calls:

- uses: RAprogramm/sql-query-analyzer@v1with:
schema: db/schema.sqlqueries: db/queries.sqlfail-on-error: 'true'post-comment: 'true'env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

This runs all 35 built-in rules instantly without requiring any API keys.

Advanced Usage

- uses: RAprogramm/sql-query-analyzer@v1id: sql-analysiswith:
schema: db/schema.sqlqueries: db/queries.sqldialect: postgresqlformat: sariffail-on-warning: 'true'upload-sarif: 'true'post-comment: 'true'env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check resultsif: steps.sql-analysis.outputs.error-count > 0run: echo "Found ${{ steps.sql-analysis.outputs.error-count }} errors"

Manual Installation

For environments where the action is not available:

jobs:
analyze:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- name: Install sql-query-analyzerrun: cargo install sql_query_analyzer
- name: Analyze SQLrun: | sql_query_analyzer analyze \ -s db/schema.sql \ -q db/queries.sql \ -f sarif > results.sarif - name: Upload SARIFuses: github/codeql-action/upload-sarif@v3with:
sarif_file: results.sarif

GitLab CI

sql-analysis:
stage: testscript:
- cargo install sql-query-analyzer
- sql-query-analyzer analyze -s schema.sql -q queries.sql -f sarif > gl-sast-report.jsonartifacts:
reports:
sast: gl-sast-report.json

Pre-commit Hook

# .pre-commit-config.yamlrepos:
- repo: localhooks:
- id: sql-analyzername: SQL Query Analyzerentry: sql-query-analyzer analyze -s schema.sql -qlanguage: systemfiles: \.sql$

LLM Providers

ProviderModel ExamplesNotes
OpenAIgpt-4, gpt-3.5-turboRequires API key
Anthropicclaude-sonnet-4-20250514Requires API key
Ollamallama3.2, codellama, mistralLocal, no API key

Using Ollama (Recommended for Development)

# Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# Pull a model
ollama pull llama3.2
# Run analysis
sql-query-analyzer analyze -s schema.sql -q queries.sql

Architecture

┌─────────────────────────────────────────────────────┐
│ CLI Interface │
└─────────────────────┬───────────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ SQL Parser │ │ Schema Parser │
│ (sqlparser) │ │ (sqlparser) │
└────────┬────────┘ └────────┬────────┘
│ │
└────────────┬────────────┘
▼
┌────────────────────────┐
│ Static Analysis │
│ (35 rules, parallel) │
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ LLM Analysis (opt) │
│ OpenAI/Anthropic/ │
│ Ollama │
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ Output Formatter │
│ Text/JSON/YAML/SARIF │
└────────────────────────┘

ClickHouse Support

The analyzer includes a preprocessor that handles ClickHouse-specific DDL constructs not supported by the underlying SQL parser:

Supported Constructs

ConstructExampleDescription
CODECcol String CODEC(ZSTD)Column compression codecs
TTLTTL event_date + INTERVAL 90 DAYData expiration rules
SETTINGSSETTINGS index_granularity = 8192Table-level settings
PARTITION BYPARTITION BY toYYYYMM(date)Partitioning expressions

Example

CREATETABLEeventsON CLUSTER default (
event_date Date,
event_time DateTime CODEC(Delta, ZSTD),
user_id UInt64 CODEC(T64),
data String CODEC(ZSTD(3))
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
TTL event_date + INTERVAL 90 DAY
SETTINGS index_granularity =8192
sql-query-analyzer analyze --dialect clickhouse -s schema.sql -q queries.sql

The preprocessor extracts metadata (codecs, TTL, settings) and removes unsupported syntax before parsing, ensuring compatibility while preserving information for analysis output.

CI Pipeline

This project uses a comprehensive CI pipeline with 16 jobs organized into quality gates.

Pipeline Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│ CI PIPELINE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ changes │ ─── Detects modified files and triggers relevant jobs │
│ └────┬────┘ │
│ │ │
│ ├──────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ ┌─────────┐ │ │
│ │ fmt │ ─── cargo +nightly fmt --check │ │
│ └────┬────┘ │ │
│ │ │ │
│ ├─────────────────────┬─────────────────────┐ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ │ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ clippy │ │ msrv │ │ machete │ │ │
│ │ │ │ (1.90) │ │ │ │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │
│ ├──────────┬──────────┼─────────────────────┤ │ │
│ │ │ │ │ │ │
│ ▼ ▼ │ │ │ │
│ ┌─────────┐ ┌─────────┐ │ │ │ │
│ │ test │ │ doc │ │ │ │ │
│ │+coverage│ │ │ │ │ ┌─────────┐ │ │
│ └────┬────┘ └────┬────┘ │ │ │ audit │◄────┘ │
│ │ │ │ │ └────┬────┘ │
│ ▼ │ │ │ │ │
│ ┌─────────┐ │ │ │ ┌────▼────┐ │
│ │ doctest │ │ │ │ │ deny │ │
│ └────┬────┘ │ │ │ └────┬────┘ │
│ │ │ │ │ │ │
│ ▼ │ │ │ ┌────▼────┐ │
│ ┌─────────┐ │ │ │ │ reuse │ │
│ │ semver │ │ │ │ └────┬────┘ │
│ │(PR only)│ │ │ │ │ │
│ └────┬────┘ │ │ │ │ │
│ │ │ │ │ │ │
│ └──────────┴──────────┴─────────────────────┴───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────┐ │
│ │ build │ │
│ └─────┬─────┘ │
│ │ │
│ ▼ │
│ ┌───────────┐ │
│ │ changelog │ (main branch only) │
│ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Job Dependency Graph

 ┌─────────┐
│ changes │
└────┬────┘
│
┌───────────────┼───────────────┬───────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ fmt │ │ audit │ │ deny │ │ reuse │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
┌────┴────┐ │ │ │
│ │ │ │ │
▼ ▼ │ │ │
┌───────┐ ┌───────┐ │ │ │
│clippy │ │ msrv │ │ │ │
└───┬───┘ └───┬───┘ │ │ │
│ │ │ │ │
│ ┌────┘ │ │ │
│ │ │ │ │
▼ │ │ │ │
┌───────┐│ │ │ │
│machete│◄───────────────┤ │ │
└───┬───┘ │ │ │
│ │ │ │
├────────────────────┤ │ │
│ │ │ │
▼ │ │ │
┌────────┐ │ │ │
│ test │ │ │ │
│ doc │ │ │ │
│doctest │ │ │ │
│ semver │ │ │ │
└───┬────┘ │ │ │
│ │ │ │
└────────────────────┴───────────────┴───────────────┘
│
▼
┌─────────┐
│ build │
└────┬────┘
│
▼
┌───────────┐
│ changelog │
└───────────┘

Quality Gates

JobTriggerToolDescription
fmtsrc/**, tests/**, Cargo.*cargo +nightly fmtCode formatting verification
clippysrc/**, tests/**, Cargo.*cargo clippyStatic analysis with -D warnings
testsrc/**, tests/**, Cargo.*cargo-nextest + cargo-llvm-covTests with coverage upload to Codecov
docsrc/**, tests/**, Cargo.*cargo docDocumentation with -D warnings
doctestsrc/**, tests/**, Cargo.*cargo test --docDocumentation examples verification
auditCargo.toml, Cargo.lockcargo-auditSecurity vulnerability scanning (RustSec)
denyCargo.toml, Cargo.lockcargo-denyLicense and dependency policy
msrvsrc/**, tests/**, Cargo.*rustc 1.90MSRV compatibility check
macheteCargo.toml, Cargo.lockcargo-macheteUnused dependency detection
semverPull requests onlycargo-semver-checksPublic API compatibility
reuseLICENSES/**, **/*.rs, **/*.tomlreuse lintSPDX license compliance

Change Detection

The pipeline uses smart change detection to skip unnecessary jobs:

┌──────────────────────────────────────────────────────────────┐
│ Change Detection Matrix │
├────────────────────┬─────────────────────────────────────────┤
│ Filter │ Paths │
├────────────────────┼─────────────────────────────────────────┤
│ rust │ src/**, tests/**, Cargo.toml, │
│ │ Cargo.lock, .rustfmt.toml │
├────────────────────┼─────────────────────────────────────────┤
│ deps │ Cargo.toml, Cargo.lock │
├────────────────────┼─────────────────────────────────────────┤
│ reuse │ LICENSES/**, .reuse/**, **/*.rs, │
│ │ **/*.toml, **/*.yml, **/*.md │
└────────────────────┴─────────────────────────────────────────┘

Dependency Policy

The deny.toml configuration enforces:

PolicyConfiguration
Allowed LicensesMIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Zlib, CC0-1.0, Unicode-3.0, Unicode-DFS-2016, BSL-1.0, MPL-2.0
Banned Cratesopenssl, openssl-sys (use rustls instead)
Registrycrates.io only (no unknown registries or git sources)
DuplicatesWarn on multiple versions of the same crate
WildcardsDenied in version requirements

Release Pipeline

┌─────────────────────────────────────────────────────────────────────────────┐
│ RELEASE PIPELINE │
│ (triggered by v* tags) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Quality Gates │ │
│ │ test + doc + audit + deny + reuse + msrv + machete + doctest │ │
│ └──────────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ release-build (matrix) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ linux-gnu │ │ linux-musl │ │ linux-arm64 │ │ macos-x64 │ │ │
│ │ │ x86_64 │ │ x86_64 │ │ aarch64 │ │ x86_64 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ macos-arm64 │ │ windows-x64 │ │ │
│ │ │ aarch64 │ │ msvc │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ │
│ │ release │ │ publish │ │
│ │ (GitHub) │ │(crates.io)│ │
│ └───────────┘ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Supported Targets

TargetOSArchitectureBuild Method
x86_64-unknown-linux-gnuLinuxx86_64Native
x86_64-unknown-linux-muslLinux (static)x86_64Cross
aarch64-unknown-linux-gnuLinuxARM64Cross
x86_64-apple-darwinmacOSx86_64Native
aarch64-apple-darwinmacOSARM64Native
x86_64-pc-windows-msvcWindowsx86_64Native

Caching Strategy

All jobs utilize Swatinem/rust-cache@v2 with job-specific cache keys:

JobCache Key
clippyDefault
testDefault
docDefault
msrvmsrv
machetemachete
doctestdoctest
semversemver
release-buildrelease-{target}

Performance

  • Parallel rule execution via rayon
  • Query caching to avoid re-parsing identical queries
  • Lazy evaluation for complexity scoring
  • Memory-efficient string storage with CompactString

Typical performance: ~1000 queries analyzed in <100ms (static analysis only).

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Development

# Run tests
cargo test# Run with all checks
cargo clippy --all-targets -- -D warnings
# Generate docs
cargo doc --open
# Format code
cargo fmt

Acknowledgements

The idea for this tool came from Yegor Bugayenko:

It would be great to have a tool that takes two inputs: 1) the entire database schema in SQL, and 2) all SQL queries that my web app issues to the database during unit testing. The tool should use an LLM to analyze the queries and identify which ones are suboptimal, especially with respect to the existing indexes.

Coverage

Coverage Graphs

Sunburst

The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.

Sunburst

Grid

Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.

Grid

Icicle

The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.

Icicle

License

MIT © 2025

About

Static analysis and LLM-powered optimization for SQL queries. built-in rules, SARIF output, CI/CD ready.

Topics

Resources

Contributing

Stars

25 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages