Skip to content

fix JSON params in pgx text query modes#1155

Open
bgentry wants to merge 1 commit intomasterfrom
bg/simple-protocol-jsonb-mode
Open

fix JSON params in pgx text query modes#1155
bgentry wants to merge 1 commit intomasterfrom
bg/simple-protocol-jsonb-mode

Conversation

@bgentry
Copy link
Contributor

@bgentry bgentry commented Feb 22, 2026

River passes marshaled JSON query inputs as []byte into sqlc-generated pgx calls. In QueryExecModeSimpleProtocol and QueryExecModeExec (including PgBouncer transaction-pooling setups), pgx treats []byte bind args as bytea, so json/jsonb inputs can fail with invalid JSON syntax.

This change adapts JSON bind arguments from []byte to string at wrapper Exec/Query/QueryRow boundaries, but only for pgx text execution modes. Query option parsing now mirrors pgx option semantics so per-query QueryExecMode overrides are honored. When a pgx.QueryRewriter is present, the driver wraps it so adaptation runs after rewrite against final SQL and bind args.

Explicit binary placeholders cast as $n::bytea or CAST($n AS bytea) are excluded from adaptation so intentional bytea inputs keep working in both extended and simple protocol paths. defaultQueryExecMode stays alongside templateReplaceWrapper, and the test-only SharedTx.Conn() now returns nil for capability probing instead of forcing panic matching.

Coverage includes driver tests for per-query mode overrides, QueryRewriter post-rewrite adaptation, Exec-path behavior, explicit bytea cast protection, and nil-conn fallback. riverdrivertest now exercises pgx endpoints in default, simple-protocol, and exec-mode configurations.

Fixes #1153.

River passes marshaled JSON query inputs as `[]byte` into sqlc-generated
`pgx` calls. In `QueryExecModeSimpleProtocol` and
`QueryExecModeExec` (including PgBouncer transaction-pooling setups),
pgx treats `[]byte` bind args as `bytea`, so `json`/`jsonb` inputs can
fail with invalid JSON syntax.

This commit adapts JSON bind arguments from `[]byte` to `string` at
wrapper `Exec`/`Query`/`QueryRow` boundaries, but only for pgx text
execution modes. Query option parsing now mirrors pgx option semantics
so per-query `QueryExecMode` overrides are honored. When a
`pgx.QueryRewriter` is present, the driver wraps it so adaptation runs
after rewrite against final SQL and bind args.

Explicit binary placeholders cast as `$n::bytea` or `CAST($n AS bytea)`
are excluded from adaptation so intentional `bytea` inputs keep working
in both extended and simple protocol paths. `defaultQueryExecMode`
stays alongside `templateReplaceWrapper`, and the test-only
`SharedTx.Conn()` now returns `nil` for capability probing instead of
forcing panic matching.

Coverage includes driver tests for per-query mode overrides,
`QueryRewriter` post-rewrite adaptation, `Exec`-path behavior, explicit
`bytea` cast protection, and nil-conn fallback. `riverdrivertest` now
exercises pgx endpoints in default, simple-protocol, and exec-mode
configurations.

Fixes #1153.
@bgentry bgentry force-pushed the bg/simple-protocol-jsonb-mode branch from 199a4dc to 61c06f6 Compare February 22, 2026 20:17
@bgentry
Copy link
Contributor Author

bgentry commented Feb 23, 2026

@casparwylie any chance you might be able to try this out on a dev setup to see if it works smoothly for you?

@casparwylie
Copy link

Just ran against the exact environment that was failing, and this does indeed fix the issue! Thank you

Copy link
Contributor

@brandur brandur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool! Another long standing problem bites the dust!

exerciseDriverRiverPgxV5WithMode(t, pgx.QueryExecModeCacheStatement)
})

// PgBouncer transaction-pooling compatibility path (`simple protocol`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, but is "simple protocol" in backticks like this intended? It seems like maybe it should've been just simple protocol without quotes, or something like pgx.QueryExecModeSimpleProtocol. It's repeated twice more below too which is what drew it to my attention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't use pgx5's QueryExecModeSimpleProtocol to avoid prepared statements when using connection pooling

3 participants