sql(postgres): route the six Bind encoder call sites through PostgresSQLConnection::encode_request; pin Bind wire bytes
Merged
RO
1mo ago+497+497+012345678901234567890123456789-115-115-012345678901234567890123456789
Behaviour change: none
Part 1 of 3 of the follow-up to #34732 (plan). Rebased on main after #43898 merged.
Problem
- Six call sites encode a request's Bind parameters: four in
PostgresSQLConnection::advanceand two inPostgresSQLQuery::do_run. Each calls one of three batch writers inPostgresRequest.rsdirectly, andadvancecarries seven copies of the same error arm. - The next two PRs change what happens around that encode. With six entry points each change needs six edits, and a seventh call site can bypass them.
Fix
PostgresSQLConnection::encode_request(global, EncodeRequest)is now the only route tobind_and_execute,prepare_and_query_with_signatureandparse_and_bind_and_execute. They andwrite_bindare private toPostgresRequest.rs.advancerejects a request whose write failed through one function,reject_failed_write, at all seven arms. The one difference between the old arms (a statement's first write marks the statement failed) is itsnew_statementargument.postgres-bind-wire.test.tspins the exact frontend bytes for 13 kinds of parameter, a binary result column, thesql()helper, no parameters, andprepare: false. It passes on main without this PR.wire-frames.tsgets frontend builders with a layout self-test.- Verified: existing coverage is
sql.test.ts,sql-prepare-false.test.ts,postgres-prepared-pipeline-reorder.test.ts,postgres-split-prepare-reorder.test.ts,postgres-simple-query-pipeline.test.ts,postgres-bytea-bind.test.ts,wire-frames.test.ts. All 255 tests intest/js/sql/postgres-*pass on the debug build.
Background
- A Bind carries the parameter values of one execution. Encoding them calls into JS (
toString,toJSON, getters). - The corpus leaves out the types whose announced format and encoding disagree today (
float4,numeric,time,int4[]). #41976 fixes those.
Downsides
- Per batch: one call and one match on a 3-variant enum of 48 bytes, if
encode_requestis not inlined. No allocation, no syscall.
[human-review] gate passed · iteration 3 · 6 files touched
fails on main (without fix)
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/sql/postgres-bind-wire.test.ts test/js/sql/wire-frames.test.ts
ninja: Entering directory `/workspace/bun/build/debug'
[0/2] cargo plan → /workspace/bun/build/debug/rust-target/plan.json
FAILED: [code=1] rust-target/plan.json /workspace/bun/build/debug/rust-target/plan.json
/workspace/bun/build/release/bun /workspace/bun/scripts/build/stream.ts cargo --console /workspace/bun/build/release/bun /workspace/bun/scripts/build/rust/plan.ts /workspace/bun/build/debug/rust-target/plan.input.json /workspace/bun/build/debug/rust-target/plan.json
[1m[91merror[0m: cannot update the lock file /workspace/bun/Cargo.lock because --locked was passed to prevent this
help: to generate the lock file without accessing the network, remove the --locked flag and use --offline instead.
error: /root/.cargo/bin/cargo build -p bun_runtime --lib … exited with 101
ninja: error: rebuilding 'build.ninja': subcommand failed
error: script "bd" exited with code 1
__F:-1:S:0
release without fix: all passed
bun test v1.4.3-canary.1 (41e52b124)
test/js/sql/wire-frames.test.ts:
(pass) mysqlLenencInt encodes per page_protocol_basic_dt_integers.html [0.16ms]
(pass) pgErrorResponse encodes per §55.7 [0.17ms]
(pass) frontend message builders encode per §55.7 [0.55ms]
(pass) postgres: pgAuthenticationOk + pgReadyForQuery are accepted by Bun's parser [6.68ms]
(pass) postgres: COPY OUT response frames are consumed and the following result set decodes [3.62ms]
(pass) postgres: pgMinimalReadyServer satisfies connect() [0.88ms]
(pass) mysql: mysqlHandshakeV10 + mysqlOkPacket are accepted by Bun's parser [1.71ms]
test/js/sql/postgres-bind-wire.test.ts:
(pass) named statements > null [1.80ms]
(pass) named statements > boolean as bool [0.57ms]
(pass) named statements > integer as int4 [0.46ms]
(pass) named statements > BigInt as int8 [0.50ms]
(pass) named statements > double as float8 [0.42ms]
(pass) named statements > Date as timestamptz [0.50ms]
(pass) named statements > ASCII string as text [0.36ms]
(pass) named statements > non-ASCII string as text [0.50ms]
(pass) named statements > object as jsonb [0.62ms]
(pass) named statements > array as json [0.51ms]
(pass) named stateme
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/sql/postgres-bind-wire.test.ts test/js/sql/wire-frames.test.ts
bun test v1.4.3 (367d939d9)
test/js/sql/wire-frames.test.ts:
(pass) mysqlLenencInt encodes per page_protocol_basic_dt_integers.html [12.75ms]
(pass) pgErrorResponse encodes per §55.7 [8.47ms]
(pass) frontend message builders encode per §55.7 [36.72ms]
(pass) postgres: pgAuthenticationOk + pgReadyForQuery are accepted by Bun's parser [380.29ms]
(pass) postgres: COPY OUT response frames are consumed and the following result set decodes [236.22ms]
(pass) postgres: pgMinimalReadyServer satisfies connect() [46.69ms]
(pass) mysql: mysqlHandshakeV10 + mysqlOkPacket are accepted by Bun's parser [69.90ms]
test/js/sql/postgres-bind-wire.test.ts:
(pass) named statements > null [79.26ms]
(pass) named statements > boolean as bool [23.18ms]
(pass) named statements > integer as int4 [20.74ms]
(pass) named statements > BigInt as int8 [24.29ms]
(pass) named statements > double as float8 [20.51ms]
(pass) named statements > Date as timestamptz [23.33ms]
(pass) named statements > AS
... (truncated)
release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
target linux-x64-gnu
build type Release
build dir ./build/release
revision ec7e250da5
features lto, baseline
23 deps, 136 codegen, 1176 objects in 1214ms
ninja: Entering directory `/workspace/bun/build/release'
[1/4] fetch rust-argon2
[rust-argon2] up to date
[2/4] fetch lolhtml
[lolhtml] up to date
[2/4] cargo plan → /workspace/bun/build/release/rust-target/plan.json
244 units: 172 lib, 16 proc-macro (host), 19 custom-build (host), 15 run custom-build, 17 lib (host), 4 run custom-build (host), 1 rlib
[1/1493] install /workspace/bun
bun install v1.4.3-canary.1 (41e52b124)
Checked 26 installs across 65 packages (no changes) [42.00ms]
[2/1493] rustc unicode_ident
[3/1493] rustc heck
[4/1493] rustc build_script_build
[5/1493] install /workspace/bun/src/node-fallbacks
bun install v1.4.3-canary.1 (41e52b124)
Checked 111 installs across 104 packages (no changes) [18.00ms]
[6/1493] build.rs build_script_build
[7/1493] rustc build_script_build
[8/1493] gen generated_host_exports.rs
generated_host_exports.rs: 121 exports (host=5, lazy=10, generic=106, r
... (truncated)
diff hotspot
src/sql_jsc/postgres/PostgresRequest.rs | 70 ++++++-
src/sql_jsc/postgres/PostgresSQLConnection.rs | 162 +++++++----------
src/sql_jsc/postgres/PostgresSQLQuery.rs | 23 ++-
test/js/sql/postgres-bind-wire.test.ts | 253 ++++++++++++++++++++++++++
test/js/sql/wire-frames.test.ts | 36 ++++
test/js/sql/wire-frames.ts | 68 +++++++
6 files changed, 497 insertions(+), 115 deletions(-)
gate history · 1 passed · 0 rejected · iteration 3
evidence per changed file
file reads edits tests
src/sql_jsc/postgres/PostgresRequest.rs 5 4 32
src/sql_jsc/postgres/PostgresSQLConnection.rs 10 1 30
src/sql_jsc/postgres/PostgresSQLQuery.rs 8 3 31
test/js/sql/postgres-bind-wire.test.ts 0 1 16
test/js/sql/wire-frames.test.ts 0 0 21
test/js/sql/wire-frames.ts 3 1 35