A fork of sqlc-gen-go that emits
per-query metadata alongside the usual sqlc output, plus a small runtime
that wraps any query as a CTE so you can compose filters, ordering,
pagination, and aggregations dynamically — without ever modifying the
original SQL.
Built for Go + sqlc, with adapters for Postgres + pgx/v5 and
SQLite + database/sql. Drop-in compatible with stock sqlc-gen-go: the
existing q.ListAuthors(ctx) methods are unchanged; the metaquery surface
is purely additive.
// Your sqlc query:
// -- name: ListAuthors :many
// SELECT id, name, bio FROM authors ORDER BY name;
// Wrap it, add filters/pagination, run it — all at runtime:
res, _ := mqpgx.Scan[db.Author](ctx, conn,
db.WrapListAuthors().
ApplyFilter(db.ListAuthorsCols.Name.ILike("%ada%")).
ApplyOrder(db.ListAuthorsCols.CreatedAt.Desc()).
ApplyPagination(metaquery.PageRequest{Page: 0, Size: 20, Total: true}))
// res.Data is []db.Author
// res.Meta has the applied Filter/OrderBy/Pagination + column metadata,
// ready to serialize as an HTTP response body.See examples/pgx/ for a full runnable demo (docker-compose
pg17, migrations, seed, JSON-output CLI), or examples/sqlite/
for the same demo against a local SQLite file (pure-Go driver, no Docker).
Stock sqlc is great for static queries but painful once you need dynamic filtering, pagination, or admin UIs on top. Typical workarounds:
- Write N variants of a query — explodes quickly
- Drop to
database/sql+ a query builder — loses sqlc's type safety - Build your own metadata indirection — what this project is
This fork keeps sqlc's compile-time query validation and generated types, adds a typed wrapper per query, and gives you a runtime API that's JSON-round-trippable for HTTP endpoints and admin consoles.
plugins:
- name: metaquery
wasm:
url: https://github.com/IodeSystems/sqlc-go-codegen-metaquery/releases/download/v1.1.1/sqlc-go-codegen-metaquery.wasm
sha256: feb4d49fa9cc10945685b888790e4467aad8828b7bb43ad5711b99e45e8e7f52git clone https://github.com/iodesystems/sqlc-go-codegen-metaquery.git
cd sqlc-go-codegen-metaquery
make all # produces bin/sqlc-go-codegen-metaquery (+ .wasm)Point sqlc at the binary:
# sqlc.yaml
version: '2'
plugins:
- name: metaquery
process:
cmd: /path/to/sqlc-go-codegen-metaquery
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
codegen:
- plugin: metaquery
out: db
options:
package: db
sql_package: pgx/v5
emit_db_tags: true
emit_json_tags: true
emit_metaquery: cols # off | meta | wrap | cols (default: cols)Then sqlc generate produces the usual db/query.sql.go + db/models.go +
a new db/query.sql.metaquery.go carrying the per-query metadata and typed
helpers.
In your Go code, import the runtime and pick the adapter for your engine:
// Postgres + pgx/v5:
import (
"github.com/iodesystems/sqlc-go-codegen-metaquery/metaquery"
"github.com/iodesystems/sqlc-go-codegen-metaquery/metaquery/mqpgx"
)
// SQLite + database/sql:
import (
"github.com/iodesystems/sqlc-go-codegen-metaquery/metaquery"
"github.com/iodesystems/sqlc-go-codegen-metaquery/metaquery/mqsqlite"
_ "modernc.org/sqlite" // or any database/sql sqlite driver
)mqsqlite is driver-agnostic — it talks to database/sql, so it works with
modernc.org/sqlite (pure Go), mattn/go-sqlite3, libsql, etc. The wrapped
query's Dialect is set automatically by the codegen based on
engine: sqlite in sqlc.yaml, and the builder emits ?N placeholders to
match.
For each query sqlc processes, three symbols (at the default cols level):
| Symbol | Purpose |
|---|---|
MetaListAuthors metaquery.Query |
Runtime-readable metadata: the SQL text, columns (Name, GoType, DBType, NotNull, Table, …), args, source file, etc. JSON-serializable. |
WrapListAuthors(args...) *metaquery.Builder |
Typed constructor that binds the original query's positional args at compile time and returns a Builder ready for filter/order/pagination/aggregation methods. |
ListAuthorsCols struct{...} |
Typed column references. ListAuthorsCols.Name is a TextCol; ListAuthorsCols.ID is an IntCol. Each exposes ops appropriate to its type (ILike(string) on text, Gt(int64), Between(int64, int64) on int, etc.). Column names and op/value types are compile-time checked. |
Six column kinds ship by default (Text/Int/Float/Bool/Time/Bytes), with
AnyCol as the escape hatch for arrays, enums, pgtype.Numeric, UUIDs, etc.
You pay only for what you use. Set globally via emit_metaquery or per-query
with -- metaquery: <level>:
| Level | Emits | Typical use |
|---|---|---|
off |
nothing | Query is only called via the regular sqlc method; no dynamic wrapping needed |
meta |
Meta<Name> only |
You want runtime introspection (schema export, generic API handler) without the builder surface |
wrap |
+ Wrap<Name>(args...) |
Typed wrappers but filter against column names as strings |
cols (default) |
+ <Name>Cols |
Full Tier 2 — typed wrappers + typed column refs |
Per-query override example:
-- name: TruncateAll :exec
-- metaquery: off
-- (Only called from seed/migration code; no builder needed.)
TRUNCATE authors RESTART IDENTITY CASCADE;| Failure mode | Caught | How |
|---|---|---|
| Wrong wrapper arg type | compile time | WrapGetAuthor("not an int") won't compile |
Unknown column in .Where/.OrderBy/etc |
compile time (via typed cols) or pre-query (via whitelist) | ListAuthorsCols.Typo → compile error; .Where("typo",...) → Build-time error |
| Wrong op for column type | compile time (typed cols) or pre-query (ValidateFilter) | IntCol has no .ILike; Filter{Op: OpILike} on an int column → op "ILIKE" not valid for column "id" (int64/int) |
| Wrong value type | compile time (typed cols) or pre-query (ValidateFilter) | IntCol.Eq takes int64; JSON-driven filters with string values for int columns are rejected before any query runs |
| Scan-struct shape drift | pre-query | Validate[T] reflects on T and diffs against b.OutputColumns() |
Malformed raw SQL in WhereExpr/Agg |
query time | Passed through to Postgres verbatim; caller owns safety |
The validator is also callable as a library function (metaquery.ValidateFilter(q, f))
so JSON-driven HTTP handlers can fail fast on bad client input, using the
same rules the builder applies internally.
This is a clean fork of
github.com/sqlc-dev/sqlc-gen-go,
not a registered GitHub fork. Upstream is tracked as a git remote:
git remote -v
# origin git@github.com:IodeSystems/sqlc-go-codegen-metaquery.git
# upstream https://github.com/sqlc-dev/sqlc-gen-go.gitTo sync upstream updates:
git fetch upstream
git merge upstream/main # resolve conflicts in the three touched files
# (internal/gen.go, internal/result.go, go.mod + Makefile)
# import-path conflicts resolve mechanically with sed.- Postgres (pgx/v5) and SQLite (database/sql) are supported. MySQL isn't blocked by the runtime design but doesn't yet have an adapter.
ILIKEon SQLite is auto-translated toLIKEat Build time. SQLite'sLIKEis case-insensitive for ASCII by default — same effective behavior as PostgresILIKEfor typical text columns. For Unicode-aware case-insensitive matching, useWhereExprwithLOWER(...).- The builder wraps, never rewrites. A filter references the output
columns of the original query. If you need to filter on a column the
query doesn't project, either widen the query or use
WhereExpr. Sum/Avg/Min/Maxaggregates project the source column's type without null-safety — aggregates over empty groups return NULL. UseAgg("x", "coalesce(sum(y), 0)", "int64")as the escape hatch.
MIT — same as upstream. See LICENSE.