SQLSage is a command-line PostgreSQL query explainer and optimizer. Give it a
SELECT statement plus schema metadata and it will:
- explain the result in plain English;
- flag wrong-result and business-intent risks before performance issues;
- describe predicted or observed execution behavior;
- recommend conservative indexes and rewrites as explicit change sets; and
- label every claim as predicted, plan-observed, measured, or unverified.
SQLSage can run completely offline. A database connection is optional.
A query can run successfully and still return the wrong population:
SELECT c.customer_id, c.email
FROM shop.customers c
WHERE c.customer_id NOT IN (
SELECT e.customer_id FROM shop.events e WHERE e.event_type = 'checkout'
);If the subquery emits a NULL, otherwise-unmatched customers disappear from
the result. Run the bundled example with Node.js 22.18+:
npx sqlsage demoExcerpt captured from SQLSage 0.2.2 (line-wrapped for readability; full output):
Nullable NOT IN subquery can invalidate anti-membership.
SQL NOT IN becomes UNKNOWN for every otherwise-unmatched outer row if this
subquery emits even one NULL.
The report proposes a NOT EXISTS repair and marks it changes results.
You review the intended population before applying anything. This offline demo
executes no query and establishes no speedup.
Walk through the query and proposed repair · See supported inputs · Tell us what helped or missed on your query
Requirements: Node.js 22.18 or newer (Node 22 LTS and Node 24 are both tested in CI) and npm.
npm install --global sqlsage
sqlsage --versionOr run it without installing:
npx sqlsage demoEach GitHub release also carries a tarball
and SHA256SUMS.txt if you would rather install from a verified download.
One command, no files and no database:
sqlsage demoIt analyzes a bundled query with a nullable NOT IN correctness bug: SQLSage explains
why the query can return no rows, proposes a NOT EXISTS repair, labels the intentional
result change, and then prints the commands for analyzing your own query.
SQLSage also includes a demonstration catalog and twelve realistic queries:
sqlsage list
sqlsage analyze --corpus q05 --format textThree complete walkthroughs, each with commands and output verified against a live PostgreSQL 16 database:
- Catching a query that returns the wrong answer
— a nullable
NOT INthat silently returns zero rows instead of 196,000. No database needed. - Fixing a date filter an index cannot help — why adding the index alone changes nothing, and the rewrite plus index is 5.5x faster.
- Analyzing a real plan from your own database
—
pg_dump,EXPLAIN (ANALYZE, FORMAT JSON), and reading where the planner was wrong.
sqlsage doctor validates the runtime, any input files you pass, and — if you supply a
connection string — database connectivity and permissions. Every failure prints the exact
command that fixes it.
sqlsage doctor
sqlsage doctor --catalog catalog.json --schema schema.sql
sqlsage doctor --database-url "$DATABASE_URL" --schema-name publicIt is strictly read-only: it never issues DDL or DML, never runs EXPLAIN ANALYZE, and
never executes your query. It exits 0 when every check passes and 1 when any check fails,
so it is safe to use as a CI preflight step.
With a PostgreSQL schema file:
sqlsage analyze --query query.sql --schema schema.sqlWith SQLSage catalog JSON:
sqlsage analyze --query query.sql --catalog catalog.jsonFrom standard input, producing machine-readable output:
cat query.sql | sqlsage analyze --schema schema.sql --format json > analysis.jsonAvailable formats are text, markdown, and json. JSON output is written only to
stdout; diagnostics are written to stderr.
Pair raw EXPLAIN (FORMAT JSON) output with schema metadata:
sqlsage analyze \
--query query.sql \
--catalog catalog.json \
--plan plan.jsonPlan-aware JSON output is a reusable evidence bundle:
sqlsage analyze --query query.sql --catalog catalog.json --plan plan.json \
--format json > evidence.json
sqlsage analyze --query query.sql --plan evidence.jsonIf a bundle contains SQL, it must match the query being analyzed. SQLSage rejects mismatched evidence.
Connected mode collects a plan without executing the query by default:
sqlsage analyze \
--query query.sql \
--database-url "$DATABASE_URL" \
--schema-name publicThis uses EXPLAIN (VERBOSE, SETTINGS, FORMAT JSON) inside a timed, read-only
transaction and rolls back. The query must parse and bind as one supported SELECT
before PostgreSQL receives EXPLAIN.
Execution is explicit:
sqlsage analyze \
--query query.sql \
--database-url "$DATABASE_URL" \
--schema-name public \
--analyze \
--statement-timeout 5000--analyze executes one EXPLAIN (ANALYZE, BUFFERS, VERBOSE, SETTINGS, FORMAT JSON). Read-only mode prevents database writes, but it cannot prevent external side
effects inside an unfamiliar user-defined volatile function. Review the query before
opting in.
SQLSage never executes recommended rewrites or candidate index DDL.
sqlsage compare --before before.json --after after.jsonReports what changed between two captured plans — access paths, indexes, join strategies, spills, row-estimate accuracy — and whether it measurably improved.
It refuses verdicts the evidence cannot support: no timing comparison when either side
is plan-only, no "improvement" claimed below 1.2x since two single runs cannot separate
that from noise, and a prominent flag when the two captures describe different
statements. compare never executes a query.
0: analysis completed and output was written;1: usage, input, connection, plan, or output failure;2: unsupported, unparseable, unbound, or incomplete analysis.
Findings do not make the process fail; they are the product output.
The current release supports PostgreSQL SELECT statements, including common CTE,
subquery, join, aggregation, ordering, pagination, and window-function shapes.
The offline schema importer supports common CREATE SCHEMA, SET search_path,
CREATE TABLE, primary/foreign keys, nullability, and PostgreSQL index definitions.
It rejects unsupported DDL rather than silently returning partial metadata.
See Supported constructs for the measured list of what is accepted and rejected, Limitations and known gaps for where the advice is weak and where SQLSage has been wrong, Usage and inputs for the full input contract, and Architecture for the analysis pipeline.
git clone https://github.com/Figo5/sqlsage.git
cd sqlsage
npm ci
npm run check
npm linkThe package ships built JavaScript in dist/. The install smoke test packs the
project, installs it into a fresh temporary prefix, imports the package API, and runs
the CLI:
npm run test:installBug reports and focused pull requests are welcome. Read CONTRIBUTING.md before changing analysis rules, and report security issues according to SECURITY.md.