sqlc plugins receive a protobuf GenerateRequest on stdin and answer with a GenerateResponse on stdout, either as a
process or as a WASI module. This plugin is pure Ruby and is distributed as a WASI module (plugin.wasm).
What each file in lib/sqlc_gen_ruby/ does is in Architecture.md. Two constraints shape it:
google-protobufis a C extension and cannot be loaded inside ruby.wasm, so the plugin brings its own ~150 line protobuf codec (proto.rb) and the subset of sqlc'scodegen.protoit reads (plugin_pb.rb, field numbers must match upstream).- Driver differences are data, not copy/paste:
Drivers::Basedefines hooks (how to prepare and run a statement, how rows are read -row_data_type:hashforrow['col']vs:arrayforrow[0]-, how:execlastid/:execresult/:copyfrom/ transactions work) and a column mapping table;Pg,Mysql2andSqlite3fill them in.
- downloads the prebuilt
ruby-3.3-wasm32-unknown-wasip1-fullrelease from ruby/ruby.wasm (this avoids compiling CRuby with a wasi-sdk); - strips the ~18 MB of DWARF debug info / name section from the interpreter (
strip_wasm.rb); - stages a slim stdlib (the plugin only uses core +
json) +lib/+exe/, plus an emptysyntax_suggest/core_ext.rb(the interpreter loads it to format errors and would otherwise print a warning before the plugin's error message); - embeds that filesystem into the interpreter with
rbwasm pack(wasi-vfs), because sqlc gives plugins no filesystem.
The argv trick: sqlc runs the module as plugin.wasm /plugin.CodegenService/Generate, so the interpreter treats that
path as its script. The build embeds a file at exactly that path containing load '/app/exe/plugin.rb'.
How the plugin is tested (golden files from real sqlc requests, sqlc diff over examples/, end-to-end tests
against real databases) is described in Testing.md; how it works inside in Architecture.md.
Generated code follows what Ruby code usually looks like rather than mirroring the C# plugin: keyword arguments instead of
parameter objects, Data rows, implicit returns, result.map { ... }, 2-space indentation, a frozen-string-literal
header, SQL in raw heredocs, connection_pool for pooling with an injectable pool, nested transactions via savepoints.
PostgreSQL prepared statements are named per query class and remembered per connection (a module mixed into each
connection), so several query objects can share a pool. MySQL quotes parameters client-side and sends queries through
Mysql2::Client#query rather than server-side prepared statements.
The plugin started as a port of the C# sqlc-gen-csharp plugin and reproduced its output byte for byte (verified
against the C# binary; see the git history before the commit that removed the C# code). It has since gained the sqlc features of
that plugin (:execrows, :execlastid for every driver, :copyfrom, sqlc.slice, sqlc.embed, enums, models,
transactions, nullability, overrides) and Ruby conventions (bang finders, logger, pooling), so the output is
now idiomatic Ruby rather than a copy of the C# one.
See Limitations in the README. Design choices that look like limitations:
- Overrides only change the RBS types; values are never converted, so the generated code stays a thin layer over the driver.
- SQLite queries do not overlap: a
SQLite3::Databaseis one connection, so the generated code takes one lock per database (held for a whole transaction) to keep threads and fibers from mixing their transactions.