Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
aafc662
refactor: replace text/template with programmatic Go code generation
claude Jan 5, 2026
da674d6
refactor(poet): use custom AST structs instead of go/ast wrappers
claude Jan 5, 2026
e825282
feat(poet): add statement types and pointer support, delete unused te…
claude Jan 5, 2026
49a0f51
feat(poet): add Switch statement, remove Func.Body field
claude Jan 5, 2026
17bedc0
refactor(generator): use poet statement types instead of RawStmt
claude Jan 5, 2026
8926302
feat(poet): add Defer, Assign, and CallStmt statement types
claude Jan 5, 2026
42f48db
feat(poet): add VarDecl and convert query functions to structured sta…
claude Jan 5, 2026
1f2aefa
feat(poet): add expression types and additional statement types
claude Jan 5, 2026
b1a18ba
fix(poet): add Multiline option to SliceLit for proper formatting
claude Jan 5, 2026
5b314a6
refactor(generator): convert Prepare/Close to structured poet statements
claude Jan 5, 2026
95ce348
refactor(generator): convert query slice fallbacks to poet statements
claude Jan 5, 2026
7dd9065
style: run go fmt on codegen/golang package
claude Jan 5, 2026
cc8ad0c
docs: add code formatting section to CLAUDE.md
claude Jan 5, 2026
5c1a05d
refactor(generator): convert addQueryExec slice fallbacks to poet
claude Jan 5, 2026
8c25017
refactor(generator): convert PGX and batch functions to poet statements
claude Jan 5, 2026
2eb3639
refactor(poet): add Indent field to FuncLit for nested function literals
claude Jan 6, 2026
905a60c
style: break long lines in DBTX interface methods
claude Jan 6, 2026
fb8d610
refactor(generator): convert writeQuerySliceExec to use poet statements
claude Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,25 @@ docker compose up -d
3. **Use specific package tests:** Faster iteration during development
4. **Start databases early:** `docker compose up -d` before running integration tests
5. **Read existing tests:** Good examples in `/internal/engine/postgresql/*_test.go`
6. **Always run go fmt:** Format code before committing (see Code Formatting below)

## Code Formatting

**Always run `go fmt` before committing changes.** This ensures consistent code style across the codebase.

```bash
# Format specific packages
go fmt ./internal/codegen/golang/...
go fmt ./internal/poet/...

# Format all packages
go fmt ./...
```

For the code generation packages specifically:
```bash
go fmt ./internal/codegen/golang/... ./internal/poet/...
```

## Git Workflow

Expand Down
141 changes: 62 additions & 79 deletions internal/codegen/golang/gen.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package golang

import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"go/format"
"strings"
"text/template"

"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
"github.com/sqlc-dev/sqlc/internal/metadata"
"github.com/sqlc-dev/sqlc/internal/plugin"
)
Expand Down Expand Up @@ -171,7 +166,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
Structs: structs,
}

tctx := tmplCtx{
tctx := &tmplCtx{
EmitInterface: options.EmitInterface,
EmitJSONTags: options.EmitJsonTags,
JsonTagsIDUppercase: options.JsonTagsIdUppercase,
Expand Down Expand Up @@ -209,64 +204,9 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
return nil, errors.New(":batch* commands are only supported by pgx")
}

funcMap := template.FuncMap{
"lowerTitle": sdk.LowerTitle,
"comment": sdk.DoubleSlashComment,
"escape": sdk.EscapeBacktick,
"imports": i.Imports,
"hasImports": i.HasImports,
"hasPrefix": strings.HasPrefix,

// These methods are Go specific, they do not belong in the codegen package
// (as that is language independent)
"dbarg": tctx.codegenDbarg,
"emitPreparedQueries": tctx.codegenEmitPreparedQueries,
"queryMethod": tctx.codegenQueryMethod,
"queryRetval": tctx.codegenQueryRetval,
}

tmpl := template.Must(
template.New("table").
Funcs(funcMap).
ParseFS(
templates,
"templates/*.tmpl",
"templates/*/*.tmpl",
),
)

output := map[string]string{}

execute := func(name, templateName string) error {
imports := i.Imports(name)
replacedQueries := replaceConflictedArg(imports, queries)

var b bytes.Buffer
w := bufio.NewWriter(&b)
tctx.SourceName = name
tctx.GoQueries = replacedQueries
err := tmpl.ExecuteTemplate(w, templateName, &tctx)
w.Flush()
if err != nil {
return err
}
code, err := format.Source(b.Bytes())
if err != nil {
fmt.Println(b.String())
return fmt.Errorf("source error: %w", err)
}

if templateName == "queryFile" && options.OutputFilesSuffix != "" {
name += options.OutputFilesSuffix
}

if !strings.HasSuffix(name, ".go") {
name += ".go"
}
output[name] = string(code)
return nil
}

// File names
dbFileName := "db.go"
if options.OutputDbFileName != "" {
dbFileName = options.OutputDbFileName
Expand All @@ -283,46 +223,89 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
if options.OutputCopyfromFileName != "" {
copyfromFileName = options.OutputCopyfromFileName
}

batchFileName := "batch.go"
if options.OutputBatchFileName != "" {
batchFileName = options.OutputBatchFileName
}

if err := execute(dbFileName, "dbFile"); err != nil {
return nil, err
// Generate db.go
tctx.SourceName = dbFileName
tctx.GoQueries = replaceConflictedArg(i.Imports(dbFileName), queries)
gen := NewCodeGenerator(tctx, i)

code, err := gen.GenerateDBFile()
if err != nil {
return nil, fmt.Errorf("db file error: %w", err)
}
if err := execute(modelsFileName, "modelsFile"); err != nil {
return nil, err
output[dbFileName] = string(code)

// Generate models.go
tctx.SourceName = modelsFileName
tctx.GoQueries = replaceConflictedArg(i.Imports(modelsFileName), queries)
code, err = gen.GenerateModelsFile()
if err != nil {
return nil, fmt.Errorf("models file error: %w", err)
}
output[modelsFileName] = string(code)

// Generate querier.go
if options.EmitInterface {
if err := execute(querierFileName, "interfaceFile"); err != nil {
return nil, err
tctx.SourceName = querierFileName
tctx.GoQueries = replaceConflictedArg(i.Imports(querierFileName), queries)
code, err = gen.GenerateQuerierFile()
if err != nil {
return nil, fmt.Errorf("querier file error: %w", err)
}
output[querierFileName] = string(code)
}

// Generate copyfrom.go
if tctx.UsesCopyFrom {
if err := execute(copyfromFileName, "copyfromFile"); err != nil {
return nil, err
tctx.SourceName = copyfromFileName
tctx.GoQueries = replaceConflictedArg(i.Imports(copyfromFileName), queries)
code, err = gen.GenerateCopyFromFile()
if err != nil {
return nil, fmt.Errorf("copyfrom file error: %w", err)
}
output[copyfromFileName] = string(code)
}

// Generate batch.go
if tctx.UsesBatch {
if err := execute(batchFileName, "batchFile"); err != nil {
return nil, err
tctx.SourceName = batchFileName
tctx.GoQueries = replaceConflictedArg(i.Imports(batchFileName), queries)
code, err = gen.GenerateBatchFile()
if err != nil {
return nil, fmt.Errorf("batch file error: %w", err)
}
output[batchFileName] = string(code)
}

files := map[string]struct{}{}
// Generate query files
sourceFiles := map[string]struct{}{}
for _, gq := range queries {
files[gq.SourceName] = struct{}{}
sourceFiles[gq.SourceName] = struct{}{}
}

for source := range files {
if err := execute(source, "queryFile"); err != nil {
return nil, err
for source := range sourceFiles {
tctx.SourceName = source
tctx.GoQueries = replaceConflictedArg(i.Imports(source), queries)
code, err = gen.GenerateQueryFile(source)
if err != nil {
return nil, fmt.Errorf("query file error for %s: %w", source, err)
}

filename := source
if options.OutputFilesSuffix != "" {
filename += options.OutputFilesSuffix
}
if !strings.HasSuffix(filename, ".go") {
filename += ".go"
}
output[filename] = string(code)
}
resp := plugin.GenerateResponse{}

resp := plugin.GenerateResponse{}
for filename, code := range output {
resp.Files = append(resp.Files, &plugin.File{
Name: filename,
Expand Down
Loading
Loading