go-googlesql
June 10, 2026 · View on GitHub
Pure-Go bindings for GoogleSQL, the SQL dialect that powers BigQuery, Spanner, and other Google Cloud databases.
Why this library
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.
Features
- Pure Go, no cgo, no wasm runtime. GoogleSQL is transpiled from
WebAssembly directly to Go source by
goccy/wasm2go, so there is no embedded interpreter or JIT — the engine is just Go code the toolchain compiles ahead of time.CGO_ENABLED=0builds, static linking, and cross-compilation all work without extra setup. - Auto-generated end-to-end. The Go bridge
googlesql.goand the transpiled engine underinternal/wasm2go/are produced upstream bygoccy/wasm2go+goccy/googlesql-wasm. When upstream GoogleSQL ships a new revision, the artifacts here follow without manual intervention. - End-to-end provenance. Every released artifact is signed with GitHub artifact attestations. CI re-verifies the in-tree files against the signed release on every PR (see Verifying provenance).
Status
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.
Installation
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.
Synopsis
Initialise the engine
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...
}
Parse a SQL statement
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
Analyze a SQL statement against a catalog
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
Verifying provenance
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-releaserunsshasum -a 256 -c googlesql_wasm2go.sha256as a fast sanity check that every file extracted fromgooglesql_wasm2go.tar.gzmatches its manifest entry byte-for-byte.verify-attestationfetches the upstream SLSA build-attestation bundle from the public/repos/.../attestations/sha256:<digest>API (anonymously) and hands it togh attestation verify --bundlefor every file listed in the manifest. The--signer-workflowflag pins the trusted signer togoccy/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.
Resource footprint
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:
- Hardware: Apple M5, 10 cores, 32 GB RAM
- OS: macOS 26.2 (Darwin 25.2.0, arm64)
- Toolchain: Go 1.26.2 darwin/amd64 (x86_64 binary via Rosetta — a native arm64 toolchain should be somewhat faster)
Build
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.
Runtime — googlesql.Init
Init 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.
License
MIT.