Repository: sqlc-dev/sqlc
Pull request #3042 (2023-12-05 23:04:08)

feat(plugins): Use wazero instead of wasmtime
After merging #3027 and #3040, wasmtime is our only cgo dependency left. This change replaces with wazero, a pure-Go WASM runtime. I've played around with it locally and everything appears to work, but I'm going to need to understand how the caching works before this is merged.

anuraaga: Oops sorry looks like need to update the type argument to `checkError` method
anuraaga: Hi @kyleconroy - sorry if this steps on any toes and feel free to ignore it, being interested in this PR I was hoping to help continue it. I sent #3082 as a PR to this branch with the suggestions. Thanks.

Patch:
diff --git a/docs/reference/config.md b/docs/reference/config.md
index a05367436..85e68cf05 100644
--- a/docs/reference/config.md
+++ b/docs/reference/config.md
@@ -156,13 +156,15 @@ The `gen` mapping supports the following keys:
 - `emit_methods_with_db_argument`:
   - If true, generated methods will accept a DBTX argument instead of storing a DBTX on the `*Queries` struct. Defaults to `false`.
 - `emit_pointers_for_null_types`:
-  - If true and `sql_package` is set to `pgx/v4` or `pgx/v5`, generated types for nullable columns are emitted as pointers (ie. `*string`) instead of `database/sql` null types (ie. `NullString`). Defaults to `false`.
+  - If true, generated types for nullable columns are emitted as pointers (ie. `*string`) instead of `database/sql` null types (ie. `NullString`). Currently only supported for PostgreSQL if `sql_package` is `pgx/v4` or `pgx/v5`, and for SQLite. Defaults to `false`.
 - `emit_enum_valid_method`:
   - If true, generate a Valid method on enum types,
     indicating whether a string is a valid enum value.
 - `emit_all_enum_values`:
   - If true, emit a function per enum type
     that returns all valid enum values.
+- `emit_sql_as_comment`:
+  - If true, emits the SQL statement as a code-block comment above the generated function, appending to any existing comments. Defaults to `false`.
 - `build_tags`:
   - If set, add a `//go:build <build_tags>` directive at the beginning of each generated Go file.
 - `json_tags_id_uppercase`:
diff --git a/go.mod b/go.mod
index 1a87e8e6e..1ae761e76 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,6 @@ go 1.21
 
 require (
 	github.com/antlr4-go/antlr/v4 v4.13.0
-	github.com/bytecodealliance/wasmtime-go/v14 v14.0.0
 	github.com/cubicdaiya/gonp v1.0.4
 	github.com/davecgh/go-spew v1.1.1
 	github.com/fatih/structtag v1.2.0
@@ -20,6 +19,7 @@ require (
 	github.com/riza-io/grpc-go v0.2.0
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/pflag v1.0.5
+	github.com/tetratelabs/wazero v1.5.0
 	github.com/wasilibs/go-pgquery v0.0.0-20231208014744-de63626a1e99
 	github.com/xeipuuv/gojsonschema v1.2.0
 	golang.org/x/sync v0.5.0
diff --git a/go.sum b/go.sum
index 9b6ea6d98..d7c014ce5 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,6 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
 github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
-github.com/bytecodealliance/wasmtime-go/v14 v14.0.0 h1:ur7S3P+PAeJmgllhSrKnGQOAmmtUbLQxb/nw2NZiaEM=
-github.com/bytecodealliance/wasmtime-go/v14 v14.0.0/go.mod h1:tqOVEUjnXY6aGpSfM9qdVRR6G//Yc513fFYUdzZb/DY=
 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=
@@ -183,6 +181,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
+github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
+github.com/wasilibs/go-pgquery v0.0.0-20231205013331-96e794bb074e h1:sGIC6/D0KqpA+qBSDSVDQswU/IJVYkbnUXnipgTLQWk=
+github.com/wasilibs/go-pgquery v0.0.0-20231205013331-96e794bb074e/go.mod h1:KW0azBSWqkPZ71r+3O4qt8h6A/NisFLp0rbjZ3py4OE=
+github.com/wasilibs/wazerox v0.0.0-20231117065139-b3503f4aeff6 h1:jwbU8u5TuXModzdEG4wI0g4FyuD7ROSttU86go5sPdU=
+github.com/wasilibs/wazerox v0.0.0-20231117065139-b3503f4aeff6/go.mod h1:IQNVyA4d1hWIe23mlMMuqXjyWMdndgSlNx6FqBkwPsM=
 github.com/wasilibs/go-pgquery v0.0.0-20231208014744-de63626a1e99 h1:HFee1ByN4FrqNVd53Mo28ccGO+g5gxqUV/gdvKMe4b8=
 github.com/wasilibs/go-pgquery v0.0.0-20231208014744-de63626a1e99/go.mod h1:f2JMhFocVxY3VKMd9ykUxMnX4EVew9WOgjnfaNBB6C8=
 github.com/wasilibs/wazerox v0.0.0-20231208014050-e6b725634531 h1:zVJ4SZgaEE9sEH2L9k1+eAvCNa/WAAnT9UiMa3/tQrI=
diff --git a/internal/codegen/golang/go_type.go b/internal/codegen/golang/go_type.go
index 4c491cba5..0d72621ef 100644
--- a/internal/codegen/golang/go_type.go
+++ b/internal/codegen/golang/go_type.go
@@ -85,7 +85,7 @@ func goInnerType(req *plugin.GenerateRequest, options *opts.Options, col *plugin
 	case "postgresql":
 		return postgresType(req, options, col)
 	case "sqlite":
-		return sqliteType(req, col)
+		return sqliteType(req, options, col)
 	default:
 		return "interface{}"
 	}
diff --git a/internal/codegen/golang/opts/options.go b/internal/codegen/golang/opts/options.go
index 7325520e7..0b6697550 100644
--- a/internal/codegen/golang/opts/options.go
+++ b/internal/codegen/golang/opts/options.go
@@ -24,6 +24,7 @@ type Options struct {
 	EmitPointersForNullTypes    bool              `json:"emit_pointers_for_null_types" yaml:"emit_pointers_for_null_types"`
 	EmitEnumValidMethod         bool              `json:"emit_enum_valid_method,omitempty" yaml:"emit_enum_valid_method"`
 	EmitAllEnumValues           bool              `json:"emit_all_enum_values,omitempty" yaml:"emit_all_enum_values"`
+	EmitSqlAsComment            bool              `json:"emit_sql_as_comment,omitempty" yaml:"emit_sql_as_comment"`
 	JsonTagsCaseStyle           string            `json:"json_tags_case_style,omitempty" yaml:"json_tags_case_style"`
 	Package                     string            `json:"package" yaml:"package"`
 	Out                         string            `json:"out" yaml:"out"`
diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go
index 787329dea..475d55ae0 100644
--- a/internal/codegen/golang/result.go
+++ b/internal/codegen/golang/result.go
@@ -199,6 +199,17 @@ func buildQueries(req *plugin.GenerateRequest, options *opts.Options, structs []
 			constantName = sdk.LowerTitle(query.Name)
 		}
 
+		comments := query.Comments
+		if options.EmitSqlAsComment {
+			if len(comments) == 0 {
+				comments = append(comments, query.Name)
+			}
+			comments = append(comments, " ")
+			for _, line := range strings.Split(query.Text, "\n") {
+				comments = append(comments, "  "+line)
+			}
+		}
+
 		gq := Query{
 			Cmd:          query.Cmd,
 			ConstantName: constantName,
@@ -206,7 +217,7 @@ func buildQueries(req *plugin.GenerateRequest, options *opts.Options, structs []
 			MethodName:   query.Name,
 			SourceName:   query.Filename,
 			SQL:          query.Text,
-			Comments:     query.Comments,
+			Comments:     comments,
 			Table:        query.InsertIntoTable,
 		}
 		sqlpkg := parseDriver(options.SqlPackage)
diff --git a/internal/codegen/golang/sqlite_type.go b/internal/codegen/golang/sqlite_type.go
index a0117e69e..92c13557f 100644
--- a/internal/codegen/golang/sqlite_type.go
+++ b/internal/codegen/golang/sqlite_type.go
@@ -4,14 +4,16 @@ import (
 	"log"
 	"strings"
 
+	"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
 	"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
 	"github.com/sqlc-dev/sqlc/internal/debug"
 	"github.com/sqlc-dev/sqlc/internal/plugin"
 )
 
-func sqliteType(req *plugin.GenerateRequest, col *plugin.Column) string {
+func sqliteType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
 	dt := strings.ToLower(sdk.DataType(col.Type))
 	notNull := col.NotNull || col.IsArray
+	emitPointersForNull := options.EmitPointersForNullTypes
 
 	switch dt {
 
@@ -19,6 +21,9 @@ func sqliteType(req *plugin.GenerateRequest, col *plugin.Column) string {
 		if notNull {
 			return "int64"
 		}
+		if emitPointersForNull {
+			return "*int64"
+		}
 		return "sql.NullInt64"
 
 	case "blob":
@@ -28,18 +33,27 @@ func sqliteType(req *plugin.GenerateRequest, col *plugin.Column) string {
 		if notNull {
 			return "float64"
 		}
+		if emitPointersForNull {
+			return "*float64"
+		}
 		return "sql.NullFloat64"
 
 	case "boolean", "bool":
 		if notNull {
 			return "bool"
 		}
+		if emitPointersForNull {
+			return "*bool"
+		}
 		return "sql.NullBool"
 
 	case "date", "datetime", "timestamp":
 		if notNull {
 			return "time.Time"
 		}
+		if emitPointersForNull {
+			return "*time.Time"
+		}
 		return "sql.NullTime"
 
 	case "any":
@@ -60,12 +74,18 @@ func sqliteType(req *plugin.GenerateRequest, col *plugin.Column) string {
 		if notNull {
 			return "string"
 		}
+		if emitPointersForNull {
+			return "*string"
+		}
 		return "sql.NullString"
 
 	case strings.HasPrefix(dt, "decimal"), dt == "numeric":
 		if notNull {
 			return "float64"
 		}
+		if emitPointersForNull {
+			return "*float64"
+		}
 		return "sql.NullFloat64"
 
 	default:
diff --git a/internal/config/v_one.go b/internal/config/v_one.go
index 17c4fcffa..8efa9f42f 100644
--- a/internal/config/v_one.go
+++ b/internal/config/v_one.go
@@ -41,6 +41,7 @@ type v1PackageSettings struct {
 	EmitPointersForNullTypes  bool              `json:"emit_pointers_for_null_types" yaml:"emit_pointers_for_null_types"`
 	EmitEnumValidMethod       bool              `json:"emit_enum_valid_method,omitempty" yaml:"emit_enum_valid_method"`
 	EmitAllEnumValues         bool              `json:"emit_all_enum_values,omitempty" yaml:"emit_all_enum_values"`
+	EmitSqlAsComment          bool              `json:"emit_sql_as_comment,omitempty" yaml:"emit_sql_as_comment"`
 	JSONTagsCaseStyle         string            `json:"json_tags_case_style,omitempty" yaml:"json_tags_case_style"`
 	SQLPackage                string            `json:"sql_package" yaml:"sql_package"`
 	SQLDriver                 string            `json:"sql_driver" yaml:"sql_driver"`
@@ -150,6 +151,7 @@ func (c *V1GenerateSettings) Translate() Config {
 					EmitPointersForNullTypes:  pkg.EmitPointersForNullTypes,
 					EmitEnumValidMethod:       pkg.EmitEnumValidMethod,
 					EmitAllEnumValues:         pkg.EmitAllEnumValues,
+					EmitSqlAsComment:          pkg.EmitSqlAsComment,
 					Package:                   pkg.Name,
 					Out:                       pkg.Path,
 					SqlPackage:                pkg.SQLPackage,
diff --git a/internal/config/v_one.json b/internal/config/v_one.json
index 166888ab9..a0667a7c9 100644
--- a/internal/config/v_one.json
+++ b/internal/config/v_one.json
@@ -131,6 +131,9 @@
                     "emit_all_enum_values": {
                         "type": "boolean"
                     },
+                    "emit_sql_as_comment": {
+                        "type": "boolean"
+                    },
                     "build_tags": {
                         "type": "string"
                     },
@@ -340,4 +343,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/internal/config/v_two.json b/internal/config/v_two.json
index dd39fddc1..acf914997 100644
--- a/internal/config/v_two.json
+++ b/internal/config/v_two.json
@@ -140,6 +140,9 @@
                                     "emit_all_enum_values": {
                                         "type": "boolean"
                                     },
+                                    "emit_sql_as_comment": {
+                                        "type": "boolean"
+                                    },
                                     "build_tags": {
                                         "type": "string"
                                     },
diff --git a/internal/ext/wasm/nowasm.go b/internal/ext/wasm/nowasm.go
deleted file mode 100644
index 14af0b54a..000000000
--- a/internal/ext/wasm/nowasm.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build nowasm || !(cgo && ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)))
-
-package wasm
-
-import (
-	"context"
-
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/status"
-)
-
-func Enabled() bool {
-	return false
-}
-
-func (r *Runner) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error {
-	return status.Error(codes.FailedPrecondition, "sqlc built without wasmtime support")
-}
-
-func (r *Runner) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
-	return nil, status.Error(codes.Unimplemented, codes.Unimplemented.String())
-}
diff --git a/internal/ext/wasm/wasm.go b/internal/ext/wasm/wasm.go
index c096ec984..a14c71d8a 100644
--- a/internal/ext/wasm/wasm.go
+++ b/internal/ext/wasm/wasm.go
@@ -1,10 +1,7 @@
-//go:build !nowasm && cgo && ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64))
-
-// The above build constraint is based of the cgo directives in this file:
-// https://github.com/bytecodealliance/wasmtime-go/blob/main/ffi.go
 package wasm
 
 import (
+	"bytes"
 	"context"
 	"crypto/sha256"
 	"errors"
@@ -15,10 +12,11 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
-	"runtime/trace"
 	"strings"
 
-	wasmtime "github.com/bytecodealliance/wasmtime-go/v14"
+	"github.com/tetratelabs/wazero"
+	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+	"github.com/tetratelabs/wazero/sys"
 	"golang.org/x/sync/singleflight"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
@@ -31,31 +29,13 @@ import (
 	"github.com/sqlc-dev/sqlc/internal/plugin"
 )
 
-func Enabled() bool {
-	return true
-}
-
-// This version must be updated whenever the wasmtime-go dependency is updated
-const wasmtimeVersion = `v14.0.0`
+var flight singleflight.Group
 
-func cacheDir() (string, error) {
-	cache := os.Getenv("SQLCCACHE")
-	if cache != "" {
-		return cache, nil
-	}
-	cacheHome := os.Getenv("XDG_CACHE_HOME")
-	if cacheHome == "" {
-		home, err := os.UserHomeDir()
-		if err != nil {
-			return "", err
-		}
-		cacheHome = filepath.Join(home, ".cache")
-	}
-	return filepath.Join(cacheHome, "sqlc"), nil
+type runtimeAndCode struct {
+	rt   wazero.Runtime
+	code wazero.CompiledModule
 }
 
-var flight singleflight.Group
-
 // Verify the provided sha256 is valid.
 func (r *Runner) getChecksum(ctx context.Context) (string, error) {
 	if r.SHA256 != "" {
@@ -70,67 +50,26 @@ func (r *Runner) getChecksum(ctx context.Context) (string, error) {
 	return sum, nil
 }
 
-func (r *Runner) loadModule(ctx context.Context, engine *wasmtime.Engine) (*wasmtime.Module, error) {
+func (r *Runner) loadAndCompile(ctx context.Context) (*runtimeAndCode, error) {
 	expected, err := r.getChecksum(ctx)
 	if err != nil {
 		return nil, err
 	}
-	value, err, _ := flight.Do(expected, func() (interface{}, error) {
-		return r.loadSerializedModule(ctx, engine, expected)
-	})
-	if err != nil {
-		return nil, err
-	}
-	data, ok := value.([]byte)
-	if !ok {
-		return nil, fmt.Errorf("returned value was not a byte slice")
-	}
-	return wasmtime.NewModuleDeserialize(engine, data)
-}
-
-func (r *Runner) loadSerializedModule(ctx context.Context, engine *wasmtime.Engine, expectedSha string) ([]byte, error) {
 	cacheDir, err := cache.PluginsDir()
 	if err != nil {
 		return nil, err
 	}
-
-	pluginDir := filepath.Join(cacheDir, expectedSha)
-	modName := fmt.Sprintf("plugin_%s_%s_%s.module", runtime.GOOS, runtime.GOARCH, wasmtimeVersion)
-	modPath := filepath.Join(pluginDir, modName)
-	_, staterr := os.Stat(modPath)
-	if staterr == nil {
-		data, err := os.ReadFile(modPath)
-		if err != nil {
-			return nil, err
-		}
-		return data, nil
-	}
-
-	wmod, err := r.loadWASM(ctx, cacheDir, expectedSha)
+	value, err, _ := flight.Do(expected, func() (interface{}, error) {
+		return r.loadAndCompileWASM(ctx, cacheDir, expected)
+	})
 	if err != nil {
 		return nil, err
 	}
-
-	moduRegion := trace.StartRegion(ctx, "wasmtime.NewModule")
-	module, err := wasmtime.NewModule(engine, wmod)
-	moduRegion.End()
-	if err != nil {
-		return nil, fmt.Errorf("define wasi: %w", err)
-	}
-
-	err = os.Mkdir(pluginDir, 0755)
-	if err != nil && !os.IsExist(err) {
-		return nil, fmt.Errorf("mkdirall: %w", err)
-	}
-	out, err := module.Serialize()
-	if err != nil {
-		return nil, fmt.Errorf("serialize: %w", err)
-	}
-	if err := os.WriteFile(modPath, out, 0444); err != nil {
-		return nil, fmt.Errorf("cache wasm: %w", err)
+	data, ok := value.(*runtimeAndCode)
+	if !ok {
+		return nil, fmt.Errorf("returned value was not a compiled module")
 	}
-
-	return out, nil
+	return data, nil
 }
 
 func (r *Runner) fetch(ctx context.Context, uri string) ([]byte, string, error) {
@@ -174,7 +113,7 @@ func (r *Runner) fetch(ctx context.Context, uri string) ([]byte, string, error)
 	return wmod, actual, nil
 }
 
-func (r *Runner) loadWASM(ctx context.Context, cache string, expected string) ([]byte, error) {
+func (r *Runner) loadAndCompileWASM(ctx context.Context, cache string, expected string) (*runtimeAndCode, error) {
 	pluginDir := filepath.Join(cache, expected)
 	pluginPath := filepath.Join(pluginDir, "plugin.wasm")
 	_, staterr := os.Stat(pluginPath)
@@ -203,7 +142,26 @@ func (r *Runner) loadWASM(ctx context.Context, cache string, expected string) ([
 		}
 	}
 
-	return wmod, nil
+	wazeroCache, err := wazero.NewCompilationCacheWithDir(filepath.Join(cache, "wazero"))
+	if err != nil {
+		return nil, fmt.Errorf("wazero.NewCompilationCacheWithDir: %w", err)
+	}
+
+	config := wazero.NewRuntimeConfig().WithCompilationCache(wazeroCache)
+	rt := wazero.NewRuntimeWithConfig(ctx, config)
+
+	if _, err := wasi_snapshot_preview1.Instantiate(ctx, rt); err != nil {
+		return nil, fmt.Errorf("wasi_snapshot_preview1 instantiate: %w", err)
+	}
+
+	// Compile the Wasm binary once so that we can skip the entire compilation
+	// time during instantiation.
+	code, err := rt.CompileModule(ctx, wmod)
+	if err != nil {
+		return nil, fmt.Errorf("compile module: %w", err)
+	}
+
+	return &runtimeAndCode{rt: rt, code: code}, nil
 }
 
 // removePGCatalog removes the pg_catalog schema from the request. There is a
@@ -245,75 +203,34 @@ func (r *Runner) Invoke(ctx context.Context, method string, args any, reply any,
 		return fmt.Errorf("failed to encode codegen request: %w", err)
 	}
 
-	engine := wasmtime.NewEngine()
-	module, err := r.loadModule(ctx, engine)
-	if err != nil {
-		return fmt.Errorf("loadModule: %w", err)
-	}
-
-	linker := wasmtime.NewLinker(engine)
-	if err := linker.DefineWasi(); err != nil {
-		return err
-	}
-
-	dir, err := os.MkdirTemp(os.Getenv("SQLCTMPDIR"), "out")
+	runtimeAndCode, err := r.loadAndCompile(ctx)
 	if err != nil {
-		return 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 fmt.Errorf("write file: %w", err)
+		return fmt.Errorf("loadBytes: %w", err)
 	}
 
-	// Configure WASI imports to write stdout into a file.
-	wasiConfig := wasmtime.NewWasiConfig()
-	wasiConfig.SetArgv([]string{"plugin.wasm", method})
-	wasiConfig.SetStdinFile(stdinPath)
-	wasiConfig.SetStdoutFile(stdoutPath)
-	wasiConfig.SetStderrFile(stderrPath)
+	var stderr, stdout bytes.Buffer
 
-	keys := []string{"SQLC_VERSION"}
-	vals := []string{info.Version}
+	conf := wazero.NewModuleConfig().
+		WithName("").
+		WithArgs("plugin.wasm", method).
+		WithStdin(bytes.NewReader(stdinBlob)).
+		WithStdout(&stdout).
+		WithStderr(&stderr).
+		WithEnv("SQLC_VERSION", info.Version)
 	for _, key := range r.Env {
-		keys = append(keys, key)
-		vals = append(vals, os.Getenv(key))
-	}
-	wasiConfig.SetEnv(keys, vals)
-
-	store := wasmtime.NewStore(engine)
-	store.SetWasi(wasiConfig)
-
-	linkRegion := trace.StartRegion(ctx, "linker.DefineModule")
-	err = linker.DefineModule(store, "", module)
-	linkRegion.End()
-	if err != nil {
-		return fmt.Errorf("define wasi: %w", err)
+		conf = conf.WithEnv(key, os.Getenv(key))
 	}
 
-	// Run the function
-	fn, err := linker.GetDefault(store, "")
-	if err != nil {
-		return fmt.Errorf("wasi: get default: %w", err)
+	result, err := runtimeAndCode.rt.InstantiateModule(ctx, runtimeAndCode.code, conf)
+	if result != nil {
+		defer result.Close(ctx)
 	}
-
-	callRegion := trace.StartRegion(ctx, "call _start")
-	_, err = fn.Call(store)
-	callRegion.End()
-
-	if cerr := checkError(err, stderrPath); cerr != nil {
+	if cerr := checkError(err, stderr); cerr != nil {
 		return cerr
 	}
 
 	// Print WASM stdout
-	stdoutBlob, err := os.ReadFile(stdoutPath)
-	if err != nil {
-		return fmt.Errorf("read file: %w", err)
-	}
+	stdoutBlob := stdout.Bytes()
 
 	resp, ok := reply.(protoreflect.ProtoMessage)
 	if !ok {
@@ -331,23 +248,21 @@ func (r *Runner) NewStream(ctx context.Context, desc *grpc.StreamDesc, method st
 	return nil, status.Error(codes.Unimplemented, "")
 }
 
-func checkError(err error, stderrPath string) error {
+func checkError(err error, stderr bytes.Buffer) error {
 	if err == nil {
 		return err
 	}
 
-	var wtError *wasmtime.Error
-	if errors.As(err, &wtError) {
-		if code, ok := wtError.ExitStatus(); ok {
-			if code == 0 {
-				return nil
-			}
+	if exitErr, ok := err.(*sys.ExitError); ok {
+		if exitErr.ExitCode() == 0 {
+			return nil
 		}
 	}
+
 	// Print WASM stdout
-	stderrBlob, rferr := os.ReadFile(stderrPath)
-	if rferr == nil && len(stderrBlob) > 0 {
-		return errors.New(string(stderrBlob))
+	stderrBlob := stderr.String()
+	if len(stderrBlob) > 0 {
+		return errors.New(stderrBlob)
 	}
 	return fmt.Errorf("call: %w", err)
 }


Test patch:
diff --git a/internal/endtoend/case_test.go b/internal/endtoend/case_test.go
index 208b3fb9f..50dcc57ec 100644
--- a/internal/endtoend/case_test.go
+++ b/internal/endtoend/case_test.go
@@ -22,7 +22,6 @@ type Exec struct {
 	Contexts []string          `json:"contexts"`
 	Process  string            `json:"process"`
 	OS       []string          `json:"os"`
-	WASM     bool              `json:"wasm"`
 	Env      map[string]string `json:"env"`
 }
 
diff --git a/internal/endtoend/endtoend_test.go b/internal/endtoend/endtoend_test.go
index 2054baeee..5753ce6d3 100644
--- a/internal/endtoend/endtoend_test.go
+++ b/internal/endtoend/endtoend_test.go
@@ -16,7 +16,6 @@ import (
 
 	"github.com/sqlc-dev/sqlc/internal/cmd"
 	"github.com/sqlc-dev/sqlc/internal/config"
-	"github.com/sqlc-dev/sqlc/internal/ext/wasm"
 	"github.com/sqlc-dev/sqlc/internal/opts"
 )
 
@@ -177,10 +176,6 @@ func TestReplay(t *testing.T) {
 					}
 				}
 
-				if args.WASM && !wasm.Enabled() {
-					t.Skipf("wasm support not enabled")
-				}
-
 				if len(args.OS) > 0 {
 					if !slices.Contains(args.OS, runtime.GOOS) {
 						t.Skipf("unsupported os: %s", runtime.GOOS)
diff --git a/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/db.go b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/db.go
new file mode 100644
index 000000000..b2523a3c9
--- /dev/null
+++ b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package datatype
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/models.go b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/models.go
new file mode 100644
index 000000000..0a9117e06
--- /dev/null
+++ b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/models.go
@@ -0,0 +1,27 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package datatype
+
+import (
+	"time"
+)
+
+type DtType struct {
+	A *int64
+	B *float64
+	C *bool
+	D *time.Time
+	E *string
+	F *float64
+}
+
+type DtTypesNotNull struct {
+	A int64
+	B float64
+	C bool
+	D time.Time
+	E string
+	F float64
+}
diff --git a/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/query.sql.go b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/query.sql.go
new file mode 100644
index 000000000..8f6d9ffc6
--- /dev/null
+++ b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/go/query.sql.go
@@ -0,0 +1,21 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+// source: query.sql
+
+package datatype
+
+import (
+	"context"
+)
+
+const noop = `-- name: Noop :one
+SELECT 1
+`
+
+func (q *Queries) Noop(ctx context.Context) (int64, error) {
+	row := q.db.QueryRowContext(ctx, noop)
+	var column_1 int64
+	err := row.Scan(&column_1)
+	return column_1, err
+}
diff --git a/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sql/query.sql b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sql/query.sql
new file mode 100644
index 000000000..d32220c3d
--- /dev/null
+++ b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sql/query.sql
@@ -0,0 +1,2 @@
+-- name: Noop :one
+SELECT 1;
diff --git a/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sql/types.sql b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sql/types.sql
new file mode 100644
index 000000000..eb55c80ed
--- /dev/null
+++ b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sql/types.sql
@@ -0,0 +1,18 @@
+
+CREATE TABLE dt_types (
+    a int,
+    b real,
+    c bool,
+    d date,
+    e text,
+    f numeric
+);
+
+CREATE TABLE dt_types_not_null (
+    a int NOT NULL,
+    b real NOT NULL,
+    c bool NOT NULL,
+    d date NOT NULL,
+    e text NOT NULL,
+    f numeric NOT NULL
+);
diff --git a/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sqlc.json b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sqlc.json
new file mode 100644
index 000000000..9b8d7693b
--- /dev/null
+++ b/internal/endtoend/testdata/emit_pointers_for_null_types/sqlite/sqlc.json
@@ -0,0 +1,13 @@
+{
+  "version": "1",
+  "packages": [
+    {
+      "path": "go",
+      "engine": "sqlite",
+      "name": "datatype",
+      "schema": "sql/types.sql",
+      "queries": "sql/query.sql",
+      "emit_pointers_for_null_types": true
+    }
+  ]
+}
diff --git a/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/db.go b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/db.go
new file mode 100644
index 000000000..7cd3a5735
--- /dev/null
+++ b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package querytest
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/models.go b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/models.go
new file mode 100644
index 000000000..919d2b723
--- /dev/null
+++ b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/models.go
@@ -0,0 +1,11 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package querytest
+
+import ()
+
+type Bar struct {
+	ID int32
+}
diff --git a/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/query.sql.go b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/query.sql.go
new file mode 100644
index 000000000..7f1c9097d
--- /dev/null
+++ b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/go/query.sql.go
@@ -0,0 +1,56 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+// source: query.sql
+
+package querytest
+
+import (
+	"context"
+)
+
+const listBar = `-- name: ListBar :many
+SELECT id FROM (
+  SELECT id FROM bar
+) bar
+`
+
+// Lists all bars
+//
+//	SELECT id FROM (
+//	  SELECT id FROM bar
+//	) bar
+func (q *Queries) ListBar(ctx context.Context) ([]int32, error) {
+	rows, err := q.db.QueryContext(ctx, listBar)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []int32
+	for rows.Next() {
+		var id int32
+		if err := rows.Scan(&id); err != nil {
+			return nil, err
+		}
+		items = append(items, id)
+	}
+	if err := rows.Close(); err != nil {
+		return nil, err
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return items, nil
+}
+
+const removeBar = `-- name: RemoveBar :exec
+DELETE FROM bar WHERE id = $1
+`
+
+// RemoveBar
+//
+//	DELETE FROM bar WHERE id = $1
+func (q *Queries) RemoveBar(ctx context.Context, id int32) error {
+	_, err := q.db.ExecContext(ctx, removeBar, id)
+	return err
+}
diff --git a/internal/endtoend/testdata/emit_sql_as_comment/stdlib/query.sql b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/query.sql
new file mode 100644
index 000000000..e3551898f
--- /dev/null
+++ b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/query.sql
@@ -0,0 +1,8 @@
+-- name: ListBar :many
+-- Lists all bars
+SELECT id FROM (
+  SELECT * FROM bar
+) bar;
+
+-- name: RemoveBar :exec
+DELETE FROM bar WHERE id = $1;
diff --git a/internal/endtoend/testdata/emit_sql_as_comment/stdlib/schema.sql b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/schema.sql
new file mode 100644
index 000000000..682614c57
--- /dev/null
+++ b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/schema.sql
@@ -0,0 +1,1 @@
+CREATE TABLE bar (id serial not null);
diff --git a/internal/endtoend/testdata/emit_sql_as_comment/stdlib/sqlc.json b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/sqlc.json
new file mode 100644
index 000000000..9464b72b2
--- /dev/null
+++ b/internal/endtoend/testdata/emit_sql_as_comment/stdlib/sqlc.json
@@ -0,0 +1,12 @@
+{
+  "version": "1",
+  "packages": [
+    {
+      "path": "go",
+      "name": "querytest",
+      "schema": "schema.sql",
+      "queries": "query.sql",
+      "emit_sql_as_comment": true
+    }
+  ]
+}
diff --git a/internal/endtoend/testdata/process_plugin_disabled/gen/codegen.json b/internal/endtoend/testdata/process_plugin_disabled/gen/codegen.json
index 5b460f31b..acb45343e 100644
--- a/internal/endtoend/testdata/process_plugin_disabled/gen/codegen.json
+++ b/internal/endtoend/testdata/process_plugin_disabled/gen/codegen.json
@@ -43,7 +43,8 @@
       "query_parameter_limit": 1,
       "output_batch_file_name": "",
       "json_tags_id_uppercase": false,
-      "omit_unused_structs": false
+      "omit_unused_structs": false,
+      "emit_sql_as_comment": false
     },
     "json": {
       "out": "",
diff --git a/internal/endtoend/testdata/wasm_plugin_sqlc_gen_greeter/exec.json b/internal/endtoend/testdata/wasm_plugin_sqlc_gen_greeter/exec.json
deleted file mode 100644
index efe8bbc9a..000000000
--- a/internal/endtoend/testdata/wasm_plugin_sqlc_gen_greeter/exec.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "wasm": true
-}
diff --git a/internal/endtoend/testdata/wasm_plugin_sqlc_gen_test/exec.json b/internal/endtoend/testdata/wasm_plugin_sqlc_gen_test/exec.json
deleted file mode 100644
index efe8bbc9a..000000000
--- a/internal/endtoend/testdata/wasm_plugin_sqlc_gen_test/exec.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "wasm": true
-}