Skip to content

Commit 4251fd7

Browse files
committed
fmt: keep authored shape for CREATE TABLE, CTEs and VALUES
Three layout regressions from the corpus sweep: - A multi-line CREATE TABLE collapsed onto one line: its Format method had no layout tokens at all. It now models the column list the way DML lists are modeled — boundaries between (and before) the columns, so the author's breaks are kept and a one-liner stays a one-liner — and prints a space before the open parenthesis, matching how these statements are written. - A multi-line CTE collapsed: the author's break after "AS (" sat inside the parenthesized body, where no boundary was modeled. The CTE body now has one, and each statement marks the boundary between its WITH clause and its own first keyword. - A break before VALUES exploded the INSERT column list: the columns from convertColumnNames carried no source positions, so the break classifier had nothing to anchor the list's boundaries to and blamed them for the line change. The converter now stamps positions there and on CREATE TABLE column definitions. Statements with a WITH clause also wrap their body in its own group, so a break inside the WITH clause no longer forces the body apart clause by clause. CREATE VIRTUAL TABLE now renders as nothing rather than as a plain CREATE TABLE — its module arguments are parsed away, so no faithful rendering exists and the verification net keeps the statement as written. The fmt endtoend case pins all of these. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MTvpHqNMadH12pTtsgUq2
1 parent 907d926 commit 4251fd7

9 files changed

Lines changed: 78 additions & 4 deletions

File tree

internal/endtoend/testdata/fmt/sqlite/query.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,19 @@ FROM "Events" WHERE id = ? LIMIT 1;
2626
-- name: CountSigils :one
2727
SELECT count(*) FROM authors
2828
WHERE id <> @at_param AND id <> :colon_param AND id <> $dollar_param;
29+
30+
-- name: TopAuthors :many
31+
WITH ranked AS (
32+
SELECT id, name FROM authors
33+
)
34+
SELECT * FROM ranked;
35+
36+
-- name: AddAuthor :exec
37+
INSERT INTO authors (name, bio)
38+
VALUES (?, ?);
39+
40+
-- name: MakeScratch :exec
41+
CREATE TABLE scratch (
42+
id INTEGER NOT NULL,
43+
label TEXT
44+
);

internal/endtoend/testdata/fmt/sqlite/stdout.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
ORDER BY name;
3333

3434
-- name: GetEvent :one
35-
@@ -23,6 +27,9 @@
35+
@@ -23,8 +27,11 @@
3636
SELECT "EventName", "order"
3737
+FROM "Events"
3838
+WHERE id = ?
@@ -44,3 +44,5 @@
4444
+FROM authors
4545
-SELECT count(*) FROM authors
4646
WHERE id <> @at_param AND id <> :colon_param AND id <> $dollar_param;
47+
48+
-- name: TopAuthors :many

internal/engine/sqlite/convert.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ func (c *cc) convertCreateTableStmt(n *meyer.CreateTableStmt) ast.Node {
284284
Colname: identifier(def.Name),
285285
IsNotNull: hasNotNullConstraint(def.Constraints),
286286
TypeName: &ast.TypeName{Name: columnTypeName(def.Type)},
287+
Location: def.Pos(),
287288
})
288289
}
289290
return stmt
@@ -306,6 +307,7 @@ func (c *cc) convertCreateVirtualTableFTS5(n *meyer.CreateVirtualTableStmt) ast.
306307
stmt := &ast.CreateTableStmt{
307308
Name: parseTableName(n.Name),
308309
IfNotExists: n.IfNotExists,
310+
Virtual: true,
309311
}
310312

311313
// The module arguments of a virtual table are an arbitrary token
@@ -437,7 +439,8 @@ func (c *cc) convertColumnNames(cols []*meyer.Ident) *ast.List {
437439
for _, col := range cols {
438440
name := identifier(col)
439441
list.Items = append(list.Items, &ast.ResTarget{
440-
Name: &name,
442+
Name: &name,
443+
Location: col.Pos(),
441444
})
442445
}
443446
return list

internal/sql/ast/common_table_expr.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ func (n *CommonTableExpr) Format(buf *TrackedBuffer, d format.Dialect) {
3434
buf.WriteString(" AS (")
3535
buf.Group()
3636
buf.Indent()
37+
// The body's boundary: a comment at the top of the CTE prints here, and
38+
// an author who broke the line after the parenthesis keeps the CTE — and
39+
// therefore the statement around it — broken open.
40+
buf.boundary(n.Ctequery)
3741
buf.Softline()
3842
buf.astFormat(n.Ctequery, d)
3943
buf.EndIndent()

internal/sql/ast/create_table_stmt.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ type CreateTableStmt struct {
99
ReferTable *TableName
1010
Comment string
1111
Inherits []*TableName
12+
// Virtual marks a table backed by a module (SQLite's CREATE VIRTUAL
13+
// TABLE). The statement's argument list cannot be reconstructed from
14+
// this node, so it has no faithful rendering.
15+
Virtual bool
1216
}
1317

1418
func (n *CreateTableStmt) Pos() int {
@@ -19,15 +23,32 @@ func (n *CreateTableStmt) Format(buf *TrackedBuffer, d format.Dialect) {
1923
if n == nil {
2024
return
2125
}
26+
// A virtual table cannot be printed back: its module arguments were
27+
// parsed away. Render nothing, which no verification accepts, so the
28+
// formatter keeps the statement as written.
29+
if n.Virtual {
30+
return
31+
}
2232
buf.WriteString("CREATE TABLE ")
2333
buf.astFormat(n.Name, d)
2434

25-
buf.WriteString("(")
35+
buf.WriteString(" (")
36+
buf.Group()
37+
buf.Indent()
38+
if len(n.Cols) > 0 {
39+
buf.boundary(n.Cols[0])
40+
}
41+
buf.Softline()
2642
for i, col := range n.Cols {
2743
if i > 0 {
28-
buf.WriteString(", ")
44+
buf.WriteString(",")
45+
buf.boundary(col)
46+
buf.Line()
2947
}
3048
buf.astFormat(col, d)
3149
}
50+
buf.EndIndent()
51+
buf.Softline()
52+
buf.EndGroup()
3253
buf.WriteString(")")
3354
}

internal/sql/ast/delete_stmt.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ func (n *DeleteStmt) Format(buf *TrackedBuffer, d format.Dialect) {
3131

3232
if n.WithClause != nil {
3333
buf.astFormat(n.WithClause, d)
34+
// The boundary between the WITH clause and the statement's own
35+
// first keyword; the group keeps a break inside the WITH clause
36+
// from forcing the statement body apart clause by clause.
37+
buf.boundary(n.Relations)
3438
buf.Line()
39+
buf.Group()
40+
defer buf.EndGroup()
3541
}
3642

3743
buf.WriteString("DELETE ")

internal/sql/ast/insert_stmt.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ func (n *InsertStmt) Format(buf *TrackedBuffer, d format.Dialect) {
3131

3232
if n.WithClause != nil {
3333
buf.astFormat(n.WithClause, d)
34+
// The boundary between the WITH clause and the statement's own
35+
// first keyword; the group keeps a break inside the WITH clause
36+
// from forcing the statement body apart clause by clause.
37+
buf.boundary(n.Relation)
3438
buf.Line()
39+
buf.Group()
40+
defer buf.EndGroup()
3541
}
3642

3743
buf.WriteString("INSERT INTO ")

internal/sql/ast/select_stmt.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,17 @@ func (n *SelectStmt) Format(buf *TrackedBuffer, d format.Dialect) {
6363

6464
if n.WithClause != nil {
6565
buf.astFormat(n.WithClause, d)
66+
// The boundary between the WITH clause and the statement's own
67+
// first keyword; the group keeps a break inside the WITH clause
68+
// from forcing the statement body apart clause by clause.
69+
if n.Larg != nil {
70+
buf.boundary(n.Larg)
71+
} else {
72+
buf.boundary(n.TargetList)
73+
}
6674
buf.Line()
75+
buf.Group()
76+
defer buf.EndGroup()
6777
}
6878

6979
if n.Larg != nil && n.Rarg != nil {

internal/sql/ast/update_stmt.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ func (n *UpdateStmt) Format(buf *TrackedBuffer, d format.Dialect) {
3232

3333
if n.WithClause != nil {
3434
buf.astFormat(n.WithClause, d)
35+
// The boundary between the WITH clause and the statement's own
36+
// first keyword; the group keeps a break inside the WITH clause
37+
// from forcing the statement body apart clause by clause.
38+
buf.boundary(n.Relations)
3539
buf.Line()
40+
buf.Group()
41+
defer buf.EndGroup()
3642
}
3743

3844
buf.WriteString("UPDATE ")

0 commit comments

Comments
 (0)