Skip to content

Add analyze command for static query analysis#4476

Open
kyleconroy wants to merge 6 commits into
mainfrom
claude/analyze-command-8uWVs
Open

Add analyze command for static query analysis#4476
kyleconroy wants to merge 6 commits into
mainfrom
claude/analyze-command-8uWVs

Conversation

@kyleconroy

Copy link
Copy Markdown
Collaborator

Summary

Adds a new sqlc analyze command that analyzes a query against a schema and outputs the inferred result columns and parameters as JSON.

Like sqlc parse, it's a standalone command — no configuration file required and no database connection. It drives sqlc's native static analysis (the catalog-based compiler) to infer types directly from the provided schema, supporting the postgresql, mysql, and sqlite dialects.

sqlc analyze --dialect postgresql --schema schema.sql query.sql
  • --dialect / -dpostgresql, mysql, or sqlite (required)
  • --schema / -s — path to the schema file (required)
  • query file — positional argument, or piped via stdin

Example

Given schema.sql:

CREATE TABLE authors (
  id   BIGSERIAL PRIMARY KEY,
  name text      NOT NULL,
  bio  text
);

and the query -- name: GetAuthor :one / SELECT id, name FROM authors WHERE id = $1;:

[
  {
    "name": "GetAuthor",
    "cmd": ":one",
    "columns": [
      { "name": "id",   "data_type": "bigserial", "not_null": true, "is_array": false, "table": "authors" },
      { "name": "name", "data_type": "text",      "not_null": true, "is_array": false, "table": "authors" }
    ],
    "params": [
      { "number": 1, "column": { "name": "id", "data_type": "bigserial", "not_null": true, "is_array": false, "table": "authors" } }
    ]
  }
]

Output format alignment

This also aligns sqlc parse and sqlc analyze on a single-document JSON output. parse previously emitted one JSON object per statement (newline-delimited), which is not parseable as a single document; it now emits a single JSON array of statements, matching the array analyze produces.

Note: changing parse's output is a behavior change to an existing command, but it has no tests or golden files, and the previous newline-delimited form was not valid as a single JSON document.

Implementation

  • internal/cmd/analyze.go (new) — the command, with small serializable output structs (analyzedQuery / analyzedColumn / analyzedParam) so internal AST types aren't dumped raw. Schema/query parse errors are unwrapped from multierr.Error and reported with file:line:col locations. Stdin (when no file arg is given) is written to a temp file so the compiler can read it, mirroring parse.
  • internal/cmd/parse.go — emit a single JSON array of statements.
  • internal/cmd/cmd.go — register analyzeCmd and its --dialect / --schema flags.

Testing

Manually verified across all three engines and the error/stdin paths:

  • PostgreSQL — SELECT * expansion, RETURNING *, params, and nullability resolved from the schema
  • SQLite — column types and NOT NULL inferred correctly
  • stdin — query piped in works; empty input gives a clear error
  • errors — unknown column reported as bad.sql:2:8: column "nonexistent" does not exist (exit 1); missing --dialect gives a clear message
  • parse output now loads as a single JSON array

go build ./... and go vet ./internal/cmd/ are clean.

https://claude.ai/code/session_01Wyz9n2TDrUMXT2LMk7qjgV


Generated by Claude Code

claude added 6 commits June 8, 2026 16:22
Add a new `sqlc analyze` command that analyzes a query file against a
schema file and outputs the inferred result columns and parameters as
JSON.

Unlike `sqlc generate`, this command does not require a configuration
file and does not connect to a database. It drives sqlc's native static
analysis (the catalog-based compiler) to infer types directly from the
provided schema, supporting the postgresql, mysql, and sqlite dialects.

Usage:
  sqlc analyze --dialect postgresql --schema schema.sql query.sql
Add stdin support to `sqlc analyze`: when no query file argument is
given, the query is read from stdin (written to a temporary file so the
compiler can read it), mirroring `sqlc parse`.

Align the two commands on a single-document JSON output. `parse`
previously emitted one JSON object per statement (newline-delimited),
which is not parseable as a single document; it now emits a single JSON
array of statements, matching the array `analyze` already produces.
Each parsed statement now reports its sqlc query name and command,
extracted from the "-- name:" annotation using the dialect's comment
syntax. The fields are omitted for statements without an annotation
(e.g. schema DDL). The statement AST is nested under an "ast" key.
When --ast is passed, each analyzed query includes its raw statement AST
under an "ast" key, matching the AST that "sqlc parse" emits. The field
is omitted by default.
Extend the endtoend replay framework to run config-less, flag-driven
commands. Exec gains an "args" field, FindTests discovers directories by
exec.json when no sqlc config is present, and TestReplay dispatches
parse/analyze through the CLI entry point, comparing stdout to a
stdout.txt golden file. The config-based consumers (TestValidSchema,
TestFormat) skip these config-less cases.

Add two cases that pin the JSON output format of each command so future
changes don't break it: parse of a named PostgreSQL query and analyze of
a SELECT * query against a schema.
Convert the parse and analyze commands to constructor functions
(newParseCmd/newAnalyzeCmd), matching the existing NewCmdVet pattern, so
each Do invocation gets fresh flag state. Previously the shared
package-level command vars leaked flag values (e.g. --ast) between calls,
which surfaced when running multiple analyze cases in one test process.

Add replay cases pinning the output format for each supported engine:
parse for postgresql, mysql, sqlite, and clickhouse; analyze for
postgresql, mysql, and sqlite; plus an analyze case exercising --ast.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants