Skip to content

feat: Codegen plugins, powered by WASM #1470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build build-endtoend test test-ci test-examples test-endtoend regen start psql mysqlsh
.PHONY: build build-endtoend test test-ci test-examples test-endtoend regen start psql mysqlsh wasm

build:
go build ./...
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.17

require (
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220209173558-ad29539cd2e9
github.com/bytecodealliance/wasmtime-go v0.35.0
github.com/davecgh/go-spew v1.1.1
github.com/go-sql-driver/mysql v1.6.0
github.com/google/go-cmp v0.5.7
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220209173558-ad29539cd2e9 h1:z
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220209173558-ad29539cd2e9/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytecodealliance/wasmtime-go v0.34.0 h1:PaWS0DUusaXaU3aNoSYjag6WmuxjyPYBHgkrC4EXips=
github.com/bytecodealliance/wasmtime-go v0.34.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI=
github.com/bytecodealliance/wasmtime-go v0.35.0 h1:VZjaZ0XOY0qp9TQfh0CQj9zl/AbdeXePVTALy8V1sKs=
github.com/bytecodealliance/wasmtime-go v0.35.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
Expand Down
10 changes: 10 additions & 0 deletions internal/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/kyleconroy/sqlc/internal/codegen/golang"
"github.com/kyleconroy/sqlc/internal/codegen/kotlin"
"github.com/kyleconroy/sqlc/internal/codegen/python"
"github.com/kyleconroy/sqlc/internal/codegen/wasm"
"github.com/kyleconroy/sqlc/internal/compiler"
"github.com/kyleconroy/sqlc/internal/config"
"github.com/kyleconroy/sqlc/internal/debug"
Expand Down Expand Up @@ -138,6 +139,12 @@ func Generate(ctx context.Context, e Env, dir, filename string, stderr io.Writer
Gen: config.SQLGen{Python: sql.Gen.Python},
})
}
if sql.Gen.WASM != nil {
pairs = append(pairs, outPair{
SQL: sql,
Gen: config.SQLGen{WASM: sql.Gen.WASM},
})
}
}

for _, sql := range pairs {
Expand Down Expand Up @@ -196,6 +203,9 @@ func Generate(ctx context.Context, e Env, dir, filename string, stderr io.Writer
var genfunc func(req *plugin.CodeGenRequest) (*plugin.CodeGenResponse, error)
var out string
switch {
case sql.Gen.WASM != nil:
out = combo.WASM.Out
genfunc = wasm.Generate
case sql.Gen.Go != nil:
out = combo.Go.Out
genfunc = golang.Generate
Expand Down
8 changes: 8 additions & 0 deletions internal/cmd/shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func pluginSettings(cs config.CombinedSettings) *plugin.Settings {
Python: pluginPythonCode(cs.Python),
Kotlin: pluginKotlinCode(cs.Kotlin),
Go: pluginGoCode(cs.Go),
Wasm: pluginWASM(cs.WASM),
}
}

Expand Down Expand Up @@ -96,6 +97,13 @@ func pluginGoCode(s config.SQLGo) *plugin.GoCode {
}
}

func pluginWASM(s config.SQLWASM) *plugin.WASM {
return &plugin.WASM{
Out: s.Out,
Path: s.Path,
}
}

func pluginGoType(o config.Override) *plugin.ParsedGoType {
// Note that there is a slight mismatch between this and the
// proto api. The GoType on the override is the unparsed type,
Expand Down
13 changes: 13 additions & 0 deletions internal/codegen/wasm/nowasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !wasmtime

package wasm

import (
"fmt"

"github.com/kyleconroy/sqlc/internal/plugin"
)

func Generate(req *plugin.CodeGenRequest) (*plugin.CodeGenResponse, error) {
return nil, fmt.Errorf("sqlc built without wasmtime support")
}
118 changes: 118 additions & 0 deletions internal/codegen/wasm/wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//go:build wasmtime

package wasm

import (
"context"
_ "embed"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime/trace"

wasmtime "github.com/bytecodealliance/wasmtime-go"

"github.com/kyleconroy/sqlc/internal/plugin"
)

func Generate(req *plugin.CodeGenRequest) (*plugin.CodeGenResponse, error) {
ctx := context.Background() // XXX

engine := wasmtime.NewEngine()
// module, err = wasmtime.NewModuleDeserialize(engine, pythonModule)
// if err != nil {
// panic(err)
// }

// out, err := module.Serialize()
// if err != nil {
// panic(err)
// }

// err = os.WriteFile("sqlc-codegen-python.module", out, 0644)
// if err != nil {
// panic(err)
// }

linker := wasmtime.NewLinker(engine)
if err := linker.DefineWasi(); err != nil {
return nil, err
}

stdinBlob, err := req.MarshalVT()
if err != nil {
return nil, err
}

dir, err := ioutil.TempDir("", "out")
if err != nil {
return nil, fmt.Errorf("temp dir: %w", err)
}

defer os.RemoveAll(dir)
stdinPath := filepath.Join(dir, "stdin")
stderrPath := filepath.Join(dir, "stderr")
stdoutPath := filepath.Join(dir, "stdout")

if err := os.WriteFile(stdinPath, stdinBlob, 0755); err != nil {
return nil, fmt.Errorf("write file: %w", err)
}

// Configure WASI imports to write stdout into a file.
wasiConfig := wasmtime.NewWasiConfig()
wasiConfig.SetStdinFile(stdinPath)
wasiConfig.SetStdoutFile(stdoutPath)
wasiConfig.SetStderrFile(stderrPath)

store := wasmtime.NewStore(engine)
store.SetWasi(wasiConfig)

// Set the version to the same as in the WAT.
// wasi, err := wasmtime.NewWasiInstance(store, wasiConfig, "wasi_snapshot_preview1")
// if err != nil {
// return fmt.Errorf("new wasi instances: %w", err)
// }

// Create our module
//
// Compiling modules requires WebAssembly binary input, but the wasmtime
// package also supports converting the WebAssembly text format to the
// binary format.
wasm, err := os.ReadFile(req.Settings.Wasm.Path)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}

moduRegion := trace.StartRegion(ctx, "wasmtime.NewModule")
module, err := wasmtime.NewModule(store.Engine, wasm)
moduRegion.End()
if err != nil {
return nil, fmt.Errorf("define wasi: %w", err)
}

linkRegion := trace.StartRegion(ctx, "linker.Instantiate")
instance, err := linker.Instantiate(store, module)
linkRegion.End()
if err != nil {
return nil, fmt.Errorf("define wasi: %w", err)
}

// Run the function

callRegion := trace.StartRegion(ctx, "call _start")
nom := instance.GetExport(store, "_start").Func()
_, err = nom.Call(store)
callRegion.End()
if err != nil {
return nil, fmt.Errorf("call: %w", err)
}

// Print WASM stdout
stdoutBlob, err := os.ReadFile(stdoutPath)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}
var resp plugin.CodeGenResponse
return &resp, resp.UnmarshalVT(stdoutBlob)
}
10 changes: 10 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type SQLGen struct {
Go *SQLGo `json:"go,omitempty" yaml:"go"`
Kotlin *SQLKotlin `json:"kotlin,omitempty" yaml:"kotlin"`
Python *SQLPython `json:"python,omitempty" yaml:"python"`
WASM *SQLWASM `json:"wasm,omitempty" yaml:"wasm"`
}

type SQLGo struct {
Expand Down Expand Up @@ -155,6 +156,11 @@ type SQLPython struct {
EmitPydanticModels bool `json:"emit_pydantic_models,omitempty" yaml:"emit_pydantic_models"`
}

type SQLWASM struct {
Out string `json:"out" yaml:"out"`
Path string `json:"path" yaml:"path"`
}

type Override struct {
// name of the golang type to use, e.g. `github.com/segmentio/ksuid.KSUID`
GoType GoType `json:"go_type" yaml:"go_type"`
Expand Down Expand Up @@ -352,6 +358,7 @@ type CombinedSettings struct {
Go SQLGo
Kotlin SQLKotlin
Python SQLPython
WASM SQLWASM
Rename map[string]string
Overrides []Override
}
Expand Down Expand Up @@ -379,5 +386,8 @@ func Combine(conf Config, pkg SQL) CombinedSettings {
cs.Python = *pkg.Gen.Python
cs.Overrides = append(cs.Overrides, pkg.Gen.Python.Overrides...)
}
if pkg.Gen.WASM != nil {
cs.WASM = *pkg.Gen.WASM
}
return cs
}
Loading