Skip to content

Fix sqlc fmt corruption in PostgreSQL and MySQL formatters - #4596

Open
kyleconroy wants to merge 1 commit into
mainfrom
claude/sqlc-fmt-fixes-mm7022
Open

Fix sqlc fmt corruption in PostgreSQL and MySQL formatters#4596
kyleconroy wants to merge 1 commit into
mainfrom
claude/sqlc-fmt-fixes-mm7022

Conversation

@kyleconroy

Copy link
Copy Markdown
Collaborator

Running sqlc fmt across all 800 PostgreSQL/MySQL endtoend configs and verifying each result (regenerated code compared with embedded SQL stripped, plus a parse-tree equivalence check for MySQL) surfaced 21 cases where formatting corrupted queries. They reduce to six root causes, all fixed here.

PostgreSQL

@name::type printed as @ name::type. The cast binds tighter than @, so the printer saw a prefix operator and added a space — and sqlc's named-param scanner then no longer matched, silently dropping the parameter from generated code. Prefix sigils (@, :, $) now glue to operands that start with an identifier. The pg fingerprint could never catch this one (@name and @ name parse to the same tree), so the printer fix is the fix. Affected corpus cases: named_param, unnest, unnest_star.

MySQL (dolphin)

The formatter reprinted from the compiler's normalized AST, which loses information the compiler doesn't need but a formatter must keep:

  • Identifier case folding`Event` became event, renaming the table on servers where table names are case-sensitive. sqlc fmt now uses dolphin.新建FormatParser(), which preserves the author's identifier case; the compiler's parser keeps lowercasing for case-insensitive catalog matching, so analysis is unchanged.
  • Literal corruptionNULL printed as '', 1.0 as 0, true as 1. Literals now convert by datum kind (ast.Null, ast.Boolean, decimals keep their written scale). This also fixes type inference: a bool literal column is now bool, not int32 (selectstatic/mysql golden regenerated to match PostgreSQL's inference).
  • Dropped clausesSELECT DISTINCT lost its DISTINCT; a UNION's trailing ORDER BY vanished; GROUP_CONCAT(... ORDER BY ...) lost its ordering; optimizer hints (/*+ ... */) were deleted; and a multi-table UPDATE ... JOIN lost its ON condition and SET qualifiers — printing an update of the whole cross product. All preserved now, via converter fixes plus two print-fidelity AST fields (UpdateStmt.TableRefs, ResTarget.Relation) that analysis ignores.
  • DDL type attributesDECIMAL(10,5) printed as DECIMAL and UNSIGNED was dropped in column definitions.
  • SHOW WARNINGS rewritten — dolphin converts SHOW into a synthetic SELECT for analysis, and the printer printed that internal form.

新建 safety net: a MySQL fingerprint. dolphin now implements Fingerprint (the proof fmt already requires from PostgreSQL via pg_query): the original and formatted statements are parsed with marino and their canonical restore forms compared, with identifier case preserved and redundant parentheses unwrapped. Any statement the printer cannot provably reproduce — SHOW WARNINGS, comma-join multi-table UPDATEs — is left exactly as written, which is fmt's documented contract. This is what fixes the SHOW class, and it structurally prevents this whole family of bugs from corrupting queries again.

Testing

  • Regression queries for every fixed class added to internal/endtoend/testdata/fmt/{mysql,postgresql} (goldens regenerated).
  • Full --tags=examples suite passes against live PostgreSQL 16 and MySQL 9.
  • Re-running the corpus verification after the fixes reports zero corrupted cases: 456 configs unchanged by fmt (including the statements it now declines to format), 344 reformatted with regenerated code and parse trees proven equivalent.

One pre-existing cosmetic limitation, unchanged here: a MySQL CREATE TABLE in a query file collapses to one line, because marino exposes no column positions for the author-line-break machinery to anchor on.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UZBvADuHMZEjoF551NccgP


Generated by Claude Code

Fix six root causes of corruption found by running sqlc fmt across the
endtoend test corpus and verifying the results:

PostgreSQL: a named parameter with a cast (@name::type) printed as
'@ name::type' because the cast binds tighter than the @ operator, so the
printer saw a prefix operator and added a space — silently dropping the
parameter from generated code. Prefix sigils now glue to operands that
start with an identifier.

MySQL (dolphin): the formatter reprinted from the compiler's normalized
AST, which loses information the compiler doesn't need but a formatter
must keep. sqlc fmt now uses a format parser that preserves identifier
case (table names are case-sensitive on most servers), and the converter
keeps what it used to drop:

- NULL, TRUE/FALSE, and decimal literals convert by datum kind, so
  NULL no longer prints as '', 1.0 as 0, or true as 1. This also fixes
  type inference: a bool literal column is now bool, not int32
  (selectstatic golden regenerated).
- SELECT DISTINCT keeps its DISTINCT.
- The ORDER BY of a compound (UNION) statement survives.
- GROUP_CONCAT(... ORDER BY ...) keeps its ordering.
- Optimizer hints (/*+ ... */) are carried through to printing.
- Multi-table UPDATE keeps its JOIN's ON condition and the table
  qualifiers in SET.
- DECIMAL(p,s) precision and UNSIGNED survive in column definitions.

Backstop: dolphin now implements Fingerprint (via marino's restore with
identifier case preserved and redundant parens unwrapped), giving MySQL
the same proof PostgreSQL has: fmt accepts a formatted statement only
when it provably means what the author wrote, and otherwise leaves the
statement exactly as written. That is what fixes SHOW WARNINGS, which
the parser rewrites into a synthetic SELECT for analysis: it now falls
back to the original text instead of printing the internal form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZBvADuHMZEjoF551NccgP
注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

None yet

项目

None yet

Development

Successfully merging this pull request may close these issues.

2 participants