Skip to content

Commit 143735a

Browse files
committed
Fix sqlc fmt corruption in PostgreSQL and MySQL formatters
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
1 parent f9f610c commit 143735a

15 files changed

Lines changed: 369 additions & 76 deletions

File tree

‎internal/cmd/fmt.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ func newQueryFormatter(engine config.Engine) queryFormatter {
4949
case config.EngineSQLite:
5050
return sqlite.NewParser()
5151
case config.EngineMySQL:
52-
return dolphin.NewParser()
52+
// The format parser preserves identifier case and proves every
53+
// statement via Fingerprint, the way oliphant does for PostgreSQL.
54+
return dolphin.NewFormatParser()
5355
default:
5456
return nil
5557
}

‎internal/endtoend/testdata/fmt/mysql/query.sql‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,34 @@ insert into authors (
3939
) values (
4040
?, ?
4141
);
42+
43+
-- name: CasePreserved :many
44+
SELECT ID, Name FROM Authors WHERE Name <> '' ORDER BY Name;
45+
46+
-- name: LiteralsSurvive :one
47+
SELECT true AS t, false AS f, 1.50 AS score, CASE WHEN name = '' THEN NULL ELSE name END AS n FROM authors LIMIT 1;
48+
49+
-- name: DistinctNames :many
50+
SELECT DISTINCT name FROM authors;
51+
52+
-- name: UnionOrdered :many
53+
SELECT name AS foo FROM authors
54+
UNION
55+
SELECT bio AS foo FROM authors
56+
ORDER BY foo;
57+
58+
-- name: ConcatNames :many
59+
SELECT group_concat(DISTINCT name ORDER BY name DESC SEPARATOR ' ') FROM authors GROUP BY bio;
60+
61+
-- name: Hinted :one
62+
SELECT /*+ MAX_EXECUTION_TIME(1000) */ id FROM authors LIMIT 1;
63+
64+
-- name: UpdateWithJoin :exec
65+
UPDATE authors AS a JOIN authors AS p ON p.id = a.id
66+
SET a.name = ?
67+
WHERE p.bio IS NOT NULL;
68+
69+
-- name: ShowWarnings :many
70+
SHOW WARNINGS;
71+
72+
CREATE TABLE scores (points decimal(10, 5), views bigint unsigned NOT NULL);

‎internal/endtoend/testdata/fmt/mysql/stdout.txt‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
-- name: CastUnsigned :one
4545
SELECT CAST(id AS UNSIGNED) FROM authors LIMIT 1;
46-
@@ -34,8 +39,5 @@
46+
@@ -34,14 +39,11 @@
4747
SELECT id FROM authors LIMIT 1;
4848

4949
-- name: CreateAuthor :execresult
@@ -54,3 +54,20 @@
5454
-) values (
5555
- ?, ?
5656
-);
57+
58+
-- name: CasePreserved :many
59+
+SELECT ID, Name FROM Authors WHERE Name != '' ORDER BY Name;
60+
-SELECT ID, Name FROM Authors WHERE Name <> '' ORDER BY Name;
61+
62+
-- name: LiteralsSurvive :one
63+
SELECT true AS t, false AS f, 1.50 AS score, CASE WHEN name = '' THEN NULL ELSE name END AS n FROM authors LIMIT 1;
64+
@@ -62,7 +64,8 @@
65+
SELECT /*+ MAX_EXECUTION_TIME(1000) */ id FROM authors LIMIT 1;
66+
67+
-- name: UpdateWithJoin :exec
68+
+UPDATE authors AS a
69+
+JOIN authors AS p ON p.id = a.id
70+
-UPDATE authors AS a JOIN authors AS p ON p.id = a.id
71+
SET a.name = ?
72+
WHERE p.bio IS NOT NULL;
73+

‎internal/endtoend/testdata/fmt/postgresql/query.sql‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,8 @@ WHERE id > $1;
2626
-- name: SearchAuthors :many
2727
SELECT id, name, bio, created_at FROM authors WHERE name LIKE $1 AND bio IS NOT NULL AND id > $2 AND name <> $3 ORDER BY name, id LIMIT $4;
2828

29+
-- name: AtParamsGlued :many
30+
SELECT name FROM authors WHERE name = @slug AND @filter::bool;
31+
2932
-- name: DeleteAuthor :exec
30-
DELETE FROM authors WHERE id = @id
33+
DELETE FROM authors WHERE id = @id

‎internal/endtoend/testdata/fmt/postgresql/stdout.txt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
FROM authors
4141
WHERE id > $1;
4242

43-
@@ -27,4 +27,4 @@
44-
SELECT id, name, bio, created_at FROM authors WHERE name LIKE $1 AND bio IS NOT NULL AND id > $2 AND name <> $3 ORDER BY name, id LIMIT $4;
43+
@@ -30,4 +30,4 @@
44+
SELECT name FROM authors WHERE name = @slug AND @filter::bool;
4545

4646
-- name: DeleteAuthor :exec
4747
-DELETE FROM authors WHERE id = @id

‎internal/endtoend/testdata/selectstatic/mysql/go/query.sql.go‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)