v0.4.0
The release ADR-0040 was announced for. v0.3.0 said the driver question
had been decided and that nothing of it was built; this is it built.
sqlb depends on pgx v5, database/sql is not the contract, and Executor —
Frozen in compatibility.md — broke on purpose, before the tag that would
have made the same work a major version and a hand migration for
everybody.
The mechanical edit is at the seam: pass a *pgxpool.Pool where a *sql.DB
used to go. *pgx.Conn and pgx.Tx satisfy Executor as they stand, and the
last of those is what this was for — sqlb writes now join a transaction
the application opened itself, which two handles over one pool could
never do. sqlb.New(tx) knows it is inside one and deliberately does not
take the boundary over, so AfterCommit refuses there rather than
queueing callbacks behind a commit sqlb will never perform. Something
that still wants a *sql.DB — goose, sqlc — gets one from
stdlib.OpenDBFromPool over the same pool, and the examples are written
that way because that is the shape a real adopter lands on.
Two surfaces disappear with it. sqlb.EncodeArray is gone with nothing in
its place, and the 449-line array-literal codec behind it: a []string
binds as text[] and scans back from one because pgx does that.
SetErrorClassifier stays, but the case it was written for is now the
default — ConstraintError carries the constraint name, table, column and
detail read off *pgconn.PgError, with nothing registered.
The second break is smaller and shows up on the next regeneration. A
nullable jsonb column's model field is *json.RawMessage: it was the one
column whose generated type did not say it could be NULL. It was also
unreadable through database/sql, which is how it was found in a real
port — that half no longer reproduces, because taking pgx replaced the
executor that had the gap. Regenerate, and the compiler names the call
sites.
Additive, and new:
- A jsonb column is filterable. ?metadata=hasdoc.{"lang":"de"}
compiles to @>, subset containment rather than equality, so a
document carrying more keys than the filter named still matches.
Not spelledcontains, for the third time and for the same reason:
that name is the text substring operator, and one name dispatched
on column type is the ambiguity the generated clients exist to
remove. A document column takes hasdoc, isnull and notnull and
nothing else — there is no bare-value shorthand, and the ordering
and pattern operators would answer rather than refuse, which is
worse. - A vector column. schema.Vector("embedding", dim) stores a pgvector
embedding, sqlb.Near yields the score, the ordering and an AtLeast
threshold from one call rather than three that must agree, and
RegisterVectorType puts the binary codec on the connection — a pgx
API with no database/sql spelling, and one of ADR-0040's arguments.
The column is Hidden and not optionally so. There is no index kind
and no REST search operation: a similarity search is an exact scan
over the rows a filter already selected. ADR-0026 stages the index
as a second decision and stays Exploring.
Fixed, most of them found by adopting sqlb over something that already
existed. A VARCHAR(n) default round-tripped as an expression, so Diff
proposed the same ALTER on every run and the drift gate stayed red for a
reason that was not real. A schema package under internal/ could not be
read by the generator. attgenerated was misread after the flip, so every
column of every imported database looked generated. A rejected write
arrived as "none of the result columns map to T". And sqlb generate's
scratch directory survived an interrupted compile, into somebody's
git add -A.
The rest is evidence rather than surface. ADR-0026's physical claims
about pgvector are measured now instead of read out of documentation,
and a fourth was added: the planner may decline the ANN index, which
makes the silent under-return conditional on statistics nobody watches.
ADR-0041 decides computed fields, including the per-viewer tier a static
SQL string cannot express, and builds none of it. example/recipes is 86
Go example functions, one point each, whose printed output is compared
on every go test — so a recipe describing an API that changed fails the
build instead of misleading the next reader.
See docs/compatibility.md#the-driver, which says what the break bought
and what it cost, in both directions.