Skip to content

Commit 061ddf7

Browse files
committed
fmt: model virtual tables instead of refusing to print them
CREATE VIRTUAL TABLE no longer falls back through the Incomplete guard: meyer keeps a module's argument list as raw source text (the grammar belongs to the module), so the statement can print faithfully. ast.CreateTableStmt carries the module name and its arguments verbatim alongside the catalog-facing columns, and prints as the declaration. The arguments have no spans of their own, so the author's exact breaks cannot be observed; a declaration written across lines keeps the canonical broken form — one argument per line — and a one-liner stays a one-liner, decided from the statement's source span. Incomplete remains for what the node still cannot carry: column and table constraints beyond plain NOT NULL and PRIMARY KEY, table options, TEMP, and AS SELECT bodies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MTvpHqNMadH12pTtsgUq2
1 parent 7b9eff4 commit 061ddf7

5 files changed

Lines changed: 83 additions & 8 deletions

File tree

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,12 @@ CREATE TABLE counters (id INTEGER PRIMARY KEY AUTOINCREMENT, hits INTEGER DEFA
5656

5757
-- name: CastLabel :one
5858
SELECT CAST(bio AS VARYING CHARACTER(120)) FROM authors LIMIT 1;
59+
60+
-- name: MakeSearchIndex :exec
61+
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(body, tag UNINDEXED, tokenize = 'porter');
62+
63+
-- name: MakeRecipeIndex :exec
64+
CREATE VIRTUAL TABLE recipes_fts USING fts5(
65+
name,
66+
ingredients
67+
);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,12 @@
5555
ratio DECIMAL(10,5),
5656
note
5757
);
58+
@@ -58,7 +65,7 @@
59+
SELECT CAST(bio AS VARYING CHARACTER(120)) FROM authors LIMIT 1;
60+
61+
-- name: MakeSearchIndex :exec
62+
+CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(body, tag UNINDEXED, tokenize = 'porter');
63+
-CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(body, tag UNINDEXED, tokenize = 'porter');
64+
65+
-- name: MakeRecipeIndex :exec
66+
CREATE VIRTUAL TABLE recipes_fts USING fts5(

internal/engine/sqlite/convert.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import (
1414

1515
// cc converts a meyer syntax tree into sqlc's engine-independent AST. One
1616
// converter is used per statement; paramCount numbers the anonymous "?"
17-
// markers within it.
17+
// markers within it, and src is the source the statement was parsed from.
1818
type cc struct {
1919
paramCount int
20+
src string
2021
}
2122

2223
func todo(funcname string, n meyer.Node) *ast.TODO {
@@ -324,9 +325,19 @@ func (c *cc) convertCreateVirtualTableFTS5(n *meyer.CreateVirtualTableStmt) ast.
324325
stmt := &ast.CreateTableStmt{
325326
Name: parseTableName(n.Name),
326327
IfNotExists: n.IfNotExists,
327-
// A virtual table's module arguments are parsed away, so the
328-
// statement has no faithful rendering.
329-
Incomplete: true,
328+
Using: identifier(n.Module),
329+
}
330+
if n.HasArgs {
331+
stmt.ModuleArgs = n.Args
332+
if stmt.ModuleArgs == nil {
333+
stmt.ModuleArgs = []string{}
334+
}
335+
// The arguments carry no spans of their own, so the author's exact
336+
// breaks cannot be kept; a declaration written across lines keeps
337+
// the canonical broken form instead.
338+
if n.Pos() >= 0 && n.End() <= len(c.src) {
339+
stmt.ModuleArgsMultiline = strings.Contains(c.src[n.Pos():n.End()], "\n")
340+
}
330341
}
331342

332343
// The module arguments of a virtual table are an arbitrary token

internal/engine/sqlite/parse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (p *Parser) ParseFile(r io.Reader) (*ast.File, error) {
5454
// reads the "-- name:" annotation out of that range.
5555
loc := 0
5656
for _, raw := range parsed.Stmts {
57-
converter := &cc{}
57+
converter := &cc{src: src}
5858
out := converter.convert(raw)
5959
if _, ok := out.(*ast.TODO); !ok {
6060
stmts = append(stmts, ast.Statement{

internal/sql/ast/create_table_stmt.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,24 @@ type CreateTableStmt struct {
99
ReferTable *TableName
1010
Comment string
1111
Inherits []*TableName
12+
// Using names the module of a virtual table (SQLite's CREATE VIRTUAL
13+
// TABLE ... USING module(...)), and ModuleArgs carries the module's
14+
// argument list as written — its grammar belongs to the module, so the
15+
// arguments pass through verbatim; nil means the declaration had no
16+
// argument list at all. Cols still holds the columns sqlc derives from
17+
// the arguments for the catalog; the statement prints as its
18+
// declaration, not as those columns.
19+
Using string
20+
ModuleArgs []string
21+
// ModuleArgsMultiline records that the declaration was written across
22+
// lines. The arguments carry no positions, so the printer cannot keep
23+
// the author's exact breaks and prints the canonical broken form — one
24+
// argument per line — instead.
25+
ModuleArgsMultiline bool
1226
// Incomplete marks a statement whose source carried syntax this node
13-
// does not model — a virtual table's module arguments, column or table
14-
// constraints beyond plain NOT NULL and PRIMARY KEY, table options,
15-
// TEMP, or an AS SELECT body. No faithful rendering exists for it.
27+
// does not model — column or table constraints beyond plain NOT NULL
28+
// and PRIMARY KEY, table options, TEMP, or an AS SELECT body. No
29+
// faithful rendering exists for it.
1630
Incomplete bool
1731
}
1832

@@ -24,6 +38,38 @@ func (n *CreateTableStmt) Format(buf *TrackedBuffer, d format.Dialect) {
2438
if n == nil {
2539
return
2640
}
41+
// A virtual table prints as its declaration: the module name and the
42+
// argument list as written.
43+
if n.Using != "" {
44+
buf.WriteString("CREATE VIRTUAL TABLE ")
45+
if n.IfNotExists {
46+
buf.WriteString("IF NOT EXISTS ")
47+
}
48+
buf.astFormat(n.Name, d)
49+
buf.WriteString(" USING ")
50+
buf.WriteString(n.Using)
51+
if n.ModuleArgs != nil {
52+
buf.WriteString("(")
53+
buf.Group()
54+
buf.Indent()
55+
if n.ModuleArgsMultiline {
56+
buf.Breaker()
57+
}
58+
buf.Softline()
59+
for i, arg := range n.ModuleArgs {
60+
if i > 0 {
61+
buf.WriteString(",")
62+
buf.Line()
63+
}
64+
buf.WriteString(arg)
65+
}
66+
buf.EndIndent()
67+
buf.Softline()
68+
buf.EndGroup()
69+
buf.WriteString(")")
70+
}
71+
return
72+
}
2773
// An incomplete statement cannot be printed back: part of its source
2874
// was parsed away. Render nothing, which no verification accepts, so
2975
// the formatter keeps the statement as written.

0 commit comments

Comments
 (0)