Pure-Go bindings for GoogleSQL, the SQL dialect that powers BigQuery, Spanner, and other Google Cloud databases.
I previously maintained
goccy/go-zetasql, which
exposed the same engine through cgo. The cgo dependency made
cross-compilation, static linking, and overall portability painful.
go-googlesql solves that by compiling GoogleSQL down to WebAssembly
and then transpiling that wasm to pure Go source via
goccy/wasm2go — no cgo, no
native toolchain, no embedded wasm runtime, just a regular Go library.
goccy/wasm2go, so there is no
embedded interpreter or JIT — the engine is just Go code the toolchain
compiles ahead of time. CGO_ENABLED=0 builds, static linking, and
cross-compilation all work without extra setup.googlesql.go and the
transpiled engine under internal/wasm2go/ are produced upstream by
goccy/wasm2go +
goccy/googlesql-wasm.
When upstream GoogleSQL ships a new revision, the artifacts here
follow without manual intervention.Tracks GoogleSQL revision
36dd14aa0657ea299725504bc0f938732f58f380
(2026-01-31). New upstream revisions are picked up here as they land.
go-googlesql is used by
goccy/googlesqlite and
goccy/bigquery-emulator,
both of which have completed their migration onto it.
go get github.com/goccy/go-googlesql
The first build is heavy: the engine is shipped as transpiled Go
source (~108 MB across internal/wasm2go/) plus a ~10 MB bridge in
googlesql.go. Expect the Go compiler to need several gigabytes of
RAM and a couple of minutes for a cold build. See
Resource footprint for measured numbers and
runtime cost.
Init initialises the transpiled wasm2go engine. Call it once per
process before using any other API; it is sync.Once-guarded so
calling more than once is a no-op. There is no runtime to tear down,
so no Close is needed.
package main
import "github.com/goccy/go-googlesql"
func main() {
if err := googlesql.Init(); err != nil {
panic(err)
}
// ...use the parser / analyzer APIs here...
}
opts, err := googlesql.NewParserOptions()
if err != nil {
panic(err)
}
out, err := googlesql.ParseStatement("SELECT * FROM Samples WHERE id = 1", opts)
if err != nil {
panic(err)
}
stmt, err := out.Statement()
if err != nil {
panic(err)
}
// Use a type assertion to reach concrete AST node types.
queryStmt, ok := stmt.(*googlesql.ASTQueryStatement)
_ = queryStmt
_ = ok
catalog, err := googlesql.NewSimpleCatalog("catalog", nil)
if err != nil {
panic(err)
}
langOpts, err := googlesql.NewLanguageOptions()
if err != nil {
panic(err)
}
_ = langOpts.EnableMaximumLanguageFeaturesForDevelopment()
_ = langOpts.SetSupportsAllStatementKinds()
if err := catalog.AddBuiltinFunctionsAndTypes(
&googlesql.BuiltinFunctionOptions{LanguageOptions: langOpts},
); err != nil {
panic(err)
}
opts, err := googlesql.NewAnalyzerOptions2()
if err != nil {
panic(err)
}
_ = opts.SetLanguage(langOpts)
tf, err := googlesql.NewTypeFactory()
if err != nil {
panic(err)
}
out, err := googlesql.AnalyzeStatement(
"SELECT 1 AS col1, 'hi' AS col2",
opts, catalog, tf,
)
if err != nil {
panic(err)
}
resolved, err := out.ResolvedStatement()
if err != nil {
panic(err)
}
_ = resolved
You can re-run the upstream attestation check yourself, locally and without a GitHub access token:
make verify
The Makefile target does two things:
verify-release runs shasum -a 256 -c googlesql_wasm2go.sha256
as a fast sanity check that every file extracted from
googlesql_wasm2go.tar.gz matches its manifest entry byte-for-byte.verify-attestation fetches the upstream SLSA build-attestation
bundle from the public /repos/.../attestations/sha256:<digest>
API (anonymously) and hands it to gh attestation verify --bundle
for every file listed in the manifest. The --signer-workflow flag
pins the trusted signer to
goccy/googlesql-wasm/.github/workflows/build.yml, so only files
produced by that workflow verify successfully.Both checks run unauthenticated — no gh auth login, no GH_TOKEN /
GITHUB_TOKEN (technique from
https://zenn.dev/shunsuke_suzuki/articles/gh-at-verify-without-access-token).
CI runs the same target on every push to main and every pull
request before running the test suite.
Because GoogleSQL ships as ahead-of-time transpiled Go (under
internal/wasm2go/) instead of a wasm module plus a runtime, the cost
shifts from process startup to the Go toolchain: compilation is
heavier than a typical dependency, but in exchange googlesql.Init
is fast and the steady-state heap is small.
All numbers below were measured on the same host:
Cold-cache go test -c against this package (GOCACHE pointed at an
empty directory, measured with /usr/bin/time -l).
| Phase | Wall time | Peak RSS | Binary size |
|---|---|---|---|
go test -c . | ~76 s | ~5.6 GB | ~63 MB |
Subsequent builds with a warm cache complete in a few hundred milliseconds. The peak RSS spike is the linker; expect a build host with at least 8 GB of RAM available.
googlesql.InitInit is called once per process. Wall and CPU were captured around
the Init call via syscall.Getrusage; heap was sampled via
runtime.MemStats after a runtime.GC() so transient init
allocations are excluded; process peak RSS came from
/usr/bin/time -l over the whole process.
| Metric | Value |
|---|---|
| Init wall time | ~150 ms |
| Init CPU time | ~250 ms |
| Steady-state Go heap | ~70 MiB |
| Process peak RSS | ~87 MiB |
There are no execution-mode knobs — Init() takes no arguments and
there is no Close. Pay the one-time ~150 ms latency, then expect a
roughly 70 MiB resident heap for the lifetime of the process.
MIT.
618 followers · starred May 2026
121 followers · starred May 2026
Go
98.2%
Makefile
1.8%
Pure-Go bindings for GoogleSQL, the SQL dialect that powers BigQuery, Spanner, and other Google Cloud databases.
I previously maintained
goccy/go-zetasql, which
exposed the same engine through cgo. The cgo dependency made
cross-compilation, static linking, and overall portability painful.
go-googlesql solves that by compiling GoogleSQL down to WebAssembly
and then transpiling that wasm to pure Go source via
goccy/wasm2go — no cgo, no
native toolchain, no embedded wasm runtime, just a regular Go library.
goccy/wasm2go, so there is no
embedded interpreter or JIT — the engine is just Go code the toolchain
compiles ahead of time. CGO_ENABLED=0 builds, static linking, and
cross-compilation all work without extra setup.googlesql.go and the
transpiled engine under internal/wasm2go/ are produced upstream by
goccy/wasm2go +
goccy/googlesql-wasm.
When upstream GoogleSQL ships a new revision, the artifacts here
follow without manual intervention.Tracks GoogleSQL revision
36dd14aa0657ea299725504bc0f938732f58f380
(2026-01-31). New upstream revisions are picked up here as they land.
go-googlesql is used by
goccy/googlesqlite and
goccy/bigquery-emulator,
both of which have completed their migration onto it.
go get github.com/goccy/go-googlesql
The first build is heavy: the engine is shipped as transpiled Go
source (~108 MB across internal/wasm2go/) plus a ~10 MB bridge in
googlesql.go. Expect the Go compiler to need several gigabytes of
RAM and a couple of minutes for a cold build. See
Resource footprint for measured numbers and
runtime cost.
Init initialises the transpiled wasm2go engine. Call it once per
process before using any other API; it is sync.Once-guarded so
calling more than once is a no-op. There is no runtime to tear down,
so no Close is needed.
package main
import "github.com/goccy/go-googlesql"
func main() {
if err := googlesql.Init(); err != nil {
panic(err)
}
// ...use the parser / analyzer APIs here...
}
opts, err := googlesql.NewParserOptions()
if err != nil {
panic(err)
}
out, err := googlesql.ParseStatement("SELECT * FROM Samples WHERE id = 1", opts)
if err != nil {
panic(err)
}
stmt, err := out.Statement()
if err != nil {
panic(err)
}
// Use a type assertion to reach concrete AST node types.
queryStmt, ok := stmt.(*googlesql.ASTQueryStatement)
_ = queryStmt
_ = ok
catalog, err := googlesql.NewSimpleCatalog("catalog", nil)
if err != nil {
panic(err)
}
langOpts, err := googlesql.NewLanguageOptions()
if err != nil {
panic(err)
}
_ = langOpts.EnableMaximumLanguageFeaturesForDevelopment()
_ = langOpts.SetSupportsAllStatementKinds()
if err := catalog.AddBuiltinFunctionsAndTypes(
&googlesql.BuiltinFunctionOptions{LanguageOptions: langOpts},
); err != nil {
panic(err)
}
opts, err := googlesql.NewAnalyzerOptions2()
if err != nil {
panic(err)
}
_ = opts.SetLanguage(langOpts)
tf, err := googlesql.NewTypeFactory()
if err != nil {
panic(err)
}
out, err := googlesql.AnalyzeStatement(
"SELECT 1 AS col1, 'hi' AS col2",
opts, catalog, tf,
)
if err != nil {
panic(err)
}
resolved, err := out.ResolvedStatement()
if err != nil {
panic(err)
}
_ = resolved
You can re-run the upstream attestation check yourself, locally and without a GitHub access token:
make verify
The Makefile target does two things:
verify-release runs shasum -a 256 -c googlesql_wasm2go.sha256
as a fast sanity check that every file extracted from
googlesql_wasm2go.tar.gz matches its manifest entry byte-for-byte.verify-attestation fetches the upstream SLSA build-attestation
bundle from the public /repos/.../attestations/sha256:<digest>
API (anonymously) and hands it to gh attestation verify --bundle
for every file listed in the manifest. The --signer-workflow flag
pins the trusted signer to
goccy/googlesql-wasm/.github/workflows/build.yml, so only files
produced by that workflow verify successfully.Both checks run unauthenticated — no gh auth login, no GH_TOKEN /
GITHUB_TOKEN (technique from
https://zenn.dev/shunsuke_suzuki/articles/gh-at-verify-without-access-token).
CI runs the same target on every push to main and every pull
request before running the test suite.
Because GoogleSQL ships as ahead-of-time transpiled Go (under
internal/wasm2go/) instead of a wasm module plus a runtime, the cost
shifts from process startup to the Go toolchain: compilation is
heavier than a typical dependency, but in exchange googlesql.Init
is fast and the steady-state heap is small.
All numbers below were measured on the same host:
Cold-cache go test -c against this package (GOCACHE pointed at an
empty directory, measured with /usr/bin/time -l).
| Phase | Wall time | Peak RSS | Binary size |
|---|---|---|---|
go test -c . | ~76 s | ~5.6 GB | ~63 MB |
Subsequent builds with a warm cache complete in a few hundred milliseconds. The peak RSS spike is the linker; expect a build host with at least 8 GB of RAM available.
googlesql.InitInit is called once per process. Wall and CPU were captured around
the Init call via syscall.Getrusage; heap was sampled via
runtime.MemStats after a runtime.GC() so transient init
allocations are excluded; process peak RSS came from
/usr/bin/time -l over the whole process.
| Metric | Value |
|---|---|
| Init wall time | ~150 ms |
| Init CPU time | ~250 ms |
| Steady-state Go heap | ~70 MiB |
| Process peak RSS | ~87 MiB |
There are no execution-mode knobs — Init() takes no arguments and
there is no Close. Pay the one-time ~150 ms latency, then expect a
roughly 70 MiB resident heap for the lifetime of the process.
MIT.
618 followers · starred May 2026
121 followers · starred May 2026
Go
98.2%
Makefile
1.8%