Skip to content

Commit 0ca51b7

Browse files
authored
feat: Process-based codegen plugins (#1578)
* feat: Add support for generic process plugins * Create a new ext package * Add end-to-end test for process plugins * Put test data in the correct spot
1 parent 4ca7301 commit 0ca51b7

File tree

21 files changed

+1886
-509
lines changed

21 files changed

+1886
-509
lines changed

.github/workflows/ci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ jobs:
3636
with:
3737
go-version: '1.18'
3838

39+
- name: install ./...
40+
run: go install ./...
41+
3942
- name: test ./...
4043
run: go test --tags=examples ./...
4144
env:

Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
build:
44
go build ./...
55

6+
install:
7+
go install ./...
8+
69
test:
710
go test ./...
811

cmd/sqlc-gen-json/main.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"os"
8+
9+
"github.com/kyleconroy/sqlc/internal/codegen/json"
10+
"github.com/kyleconroy/sqlc/internal/plugin"
11+
)
12+
13+
func main() {
14+
if err := run(); err != nil {
15+
fmt.Fprintf(os.Stderr, "error generating JSON: %s", err)
16+
os.Exit(2)
17+
}
18+
}
19+
20+
func run() error {
21+
var req plugin.CodeGenRequest
22+
reqBlob, err := io.ReadAll(os.Stdin)
23+
if err != nil {
24+
return err
25+
}
26+
if err := req.UnmarshalVT(reqBlob); err != nil {
27+
return err
28+
}
29+
resp, err := json.Generate(&req)
30+
if err != nil {
31+
return err
32+
}
33+
respBlob, err := resp.MarshalVT()
34+
if err != nil {
35+
return err
36+
}
37+
w := bufio.NewWriter(os.Stdout)
38+
if _, err := w.Write(respBlob); err != nil {
39+
return err
40+
}
41+
if err := w.Flush(); err != nil {
42+
return err
43+
}
44+
return nil
45+
}

internal/cmd/generate.go

+43-12
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import (
1818
"github.com/kyleconroy/sqlc/internal/compiler"
1919
"github.com/kyleconroy/sqlc/internal/config"
2020
"github.com/kyleconroy/sqlc/internal/debug"
21+
"github.com/kyleconroy/sqlc/internal/ext"
22+
"github.com/kyleconroy/sqlc/internal/ext/process"
2123
"github.com/kyleconroy/sqlc/internal/multierr"
2224
"github.com/kyleconroy/sqlc/internal/opts"
23-
"github.com/kyleconroy/sqlc/internal/plugin"
2425
)
2526

2627
const errMessageNoVersion = `The configuration file must have a version number.
@@ -44,7 +45,9 @@ func printFileErr(stderr io.Writer, dir string, fileErr *multierr.FileError) {
4445
}
4546

4647
type outPair struct {
47-
Gen config.SQLGen
48+
Gen config.SQLGen
49+
Plugin *config.Codegen
50+
4851
config.SQL
4952
}
5053

@@ -145,10 +148,19 @@ func Generate(ctx context.Context, e Env, dir, filename string, stderr io.Writer
145148
Gen: config.SQLGen{JSON: sql.Gen.JSON},
146149
})
147150
}
151+
for i, _ := range sql.Codegen {
152+
pairs = append(pairs, outPair{
153+
SQL: sql,
154+
Plugin: &sql.Codegen[i],
155+
})
156+
}
148157
}
149158

150159
for _, sql := range pairs {
151160
combo := config.Combine(*conf, sql.SQL)
161+
if sql.Plugin != nil {
162+
combo.Codegen = *sql.Plugin
163+
}
152164

153165
// TODO: This feels like a hack that will bite us later
154166
joined := make([]string, 0, len(sql.Schema))
@@ -167,24 +179,32 @@ func Generate(ctx context.Context, e Env, dir, filename string, stderr io.Writer
167179
parseOpts := opts.Parser{
168180
Debug: debug.Debug,
169181
}
170-
if sql.Gen.Go != nil {
182+
183+
switch {
184+
case sql.Gen.Go != nil:
171185
name = combo.Go.Package
172186
lang = "golang"
173-
} else if sql.Gen.Kotlin != nil {
187+
188+
case sql.Gen.Kotlin != nil:
174189
if sql.Engine == config.EnginePostgreSQL {
175190
parseOpts.UsePositionalParameters = true
176191
}
177192
lang = "kotlin"
178193
name = combo.Kotlin.Package
179-
} else if sql.Gen.Python != nil {
194+
195+
case sql.Gen.Python != nil:
180196
lang = "python"
181197
name = combo.Python.Package
198+
199+
case sql.Plugin != nil:
200+
lang = fmt.Sprintf("process:%s", sql.Plugin.Plugin)
201+
name = sql.Plugin.Plugin
182202
}
183203

184204
var packageRegion *trace.Region
185205
if debug.Traced {
186206
packageRegion = trace.StartRegion(ctx, "package")
187-
trace.Logf(ctx, "", "name=%s dir=%s language=%s", name, dir, lang)
207+
trace.Logf(ctx, "", "name=%s dir=%s plugin=%s", name, dir, lang)
188208
}
189209

190210
result, failed := parse(ctx, e, name, dir, sql.SQL, combo, parseOpts, stderr)
@@ -200,25 +220,36 @@ func Generate(ctx context.Context, e Env, dir, filename string, stderr io.Writer
200220
if debug.Traced {
201221
region = trace.StartRegion(ctx, "codegen")
202222
}
203-
var genfunc func(req *plugin.CodeGenRequest) (*plugin.CodeGenResponse, error)
223+
var handler ext.Handler
204224
var out string
205225
switch {
206226
case sql.Gen.Go != nil:
207227
out = combo.Go.Out
208-
genfunc = golang.Generate
228+
handler = ext.HandleFunc(golang.Generate)
229+
209230
case sql.Gen.Kotlin != nil:
210231
out = combo.Kotlin.Out
211-
genfunc = kotlin.Generate
232+
handler = ext.HandleFunc(kotlin.Generate)
233+
212234
case sql.Gen.Python != nil:
213235
out = combo.Python.Out
214-
genfunc = python.Generate
236+
handler = ext.HandleFunc(python.Generate)
237+
215238
case sql.Gen.JSON != nil:
216239
out = combo.JSON.Out
217-
genfunc = json.Generate
240+
handler = ext.HandleFunc(json.Generate)
241+
242+
case sql.Plugin != nil:
243+
out = sql.Plugin.Out
244+
handler = &process.Runner{
245+
Config: combo.Global,
246+
Plugin: sql.Plugin.Plugin,
247+
}
248+
218249
default:
219250
panic("missing language backend")
220251
}
221-
resp, err := genfunc(codeGenRequest(result, combo))
252+
resp, err := handler.Generate(codeGenRequest(result, combo))
222253
if region != nil {
223254
region.End()
224255
}

internal/cmd/shim.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/kyleconroy/sqlc/internal/compiler"
77
"github.com/kyleconroy/sqlc/internal/config"
8+
"github.com/kyleconroy/sqlc/internal/config/convert"
89
"github.com/kyleconroy/sqlc/internal/info"
910
"github.com/kyleconroy/sqlc/internal/plugin"
1011
"github.com/kyleconroy/sqlc/internal/sql/catalog"
@@ -56,13 +57,26 @@ func pluginSettings(cs config.CombinedSettings) *plugin.Settings {
5657
Queries: []string(cs.Package.Queries),
5758
Overrides: over,
5859
Rename: cs.Rename,
60+
Codegen: pluginCodegen(cs.Codegen),
5961
Python: pluginPythonCode(cs.Python),
6062
Kotlin: pluginKotlinCode(cs.Kotlin),
6163
Go: pluginGoCode(cs.Go),
6264
Json: pluginJSONCode(cs.JSON),
6365
}
6466
}
6567

68+
func pluginCodegen(s config.Codegen) *plugin.Codegen {
69+
opts, err := convert.YAMLtoJSON(s.Options)
70+
if err != nil {
71+
panic(err)
72+
}
73+
return &plugin.Codegen{
74+
Out: s.Out,
75+
Plugin: s.Plugin,
76+
Options: opts,
77+
}
78+
}
79+
6680
func pluginPythonCode(s config.SQLPython) *plugin.PythonCode {
6781
return &plugin.PythonCode{
6882
Out: s.Out,
@@ -130,8 +144,9 @@ func pluginKotlinCode(s config.SQLKotlin) *plugin.KotlinCode {
130144

131145
func pluginJSONCode(s config.SQLJSON) *plugin.JSONCode {
132146
return &plugin.JSONCode{
133-
Out: s.Out,
134-
Indent: s.Indent,
147+
Out: s.Out,
148+
Indent: s.Indent,
149+
Filename: s.Filename,
135150
}
136151
}
137152

internal/codegen/json/gen.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,52 @@
11
package json
22

33
import (
4+
"bytes"
45
ejson "encoding/json"
6+
"fmt"
57

68
"google.golang.org/protobuf/encoding/protojson"
79

810
"github.com/kyleconroy/sqlc/internal/plugin"
911
)
1012

13+
func parseOptions(req *plugin.CodeGenRequest) (plugin.JSONCode, error) {
14+
if req.Settings == nil {
15+
return plugin.JSONCode{}, nil
16+
}
17+
if req.Settings.Codegen != nil {
18+
if len(req.Settings.Codegen.Options) != 0 {
19+
var options plugin.JSONCode
20+
dec := ejson.NewDecoder(bytes.NewReader(req.Settings.Codegen.Options))
21+
dec.DisallowUnknownFields()
22+
if err := dec.Decode(&options); err != nil {
23+
return options, fmt.Errorf("unmarshalling options: %s", err)
24+
}
25+
return options, nil
26+
}
27+
}
28+
if req.Settings.Json != nil {
29+
return *req.Settings.Json, nil
30+
}
31+
return plugin.JSONCode{}, nil
32+
}
33+
1134
func Generate(req *plugin.CodeGenRequest) (*plugin.CodeGenResponse, error) {
12-
indent := ""
13-
if req.Settings != nil && req.Settings.Json != nil {
14-
indent = req.Settings.Json.Indent
35+
options, err := parseOptions(req)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
indent := " "
41+
if options.Indent != "" {
42+
indent = options.Indent
1543
}
44+
45+
filename := "codegen_request.json"
46+
if options.Filename != "" {
47+
filename = options.Filename
48+
}
49+
1650
// The output of protojson has randomized whitespace
1751
// https://github.com/golang/protobuf/issues/1082
1852
m := &protojson.MarshalOptions{
@@ -32,7 +66,7 @@ func Generate(req *plugin.CodeGenRequest) (*plugin.CodeGenResponse, error) {
3266
return &plugin.CodeGenResponse{
3367
Files: []*plugin.File{
3468
{
35-
Name: "codegen_request.json",
69+
Name: filename,
3670
Contents: append(blob, '\n'),
3771
},
3872
},

0 commit comments

Comments
 (0)