Skip to content

sql: deduplicate and remove dead code across the MySQL/Postgres drivers#31664

Closed
alii wants to merge 11 commits into
mainfrom
ali/sql-jsc-cleanup
Closed

sql: deduplicate and remove dead code across the MySQL/Postgres drivers#31664
alii wants to merge 11 commits into
mainfrom
ali/sql-jsc-cleanup

Conversation

@alii

@alii alii commented Jun 1, 2026

Copy link
Copy Markdown
Member

A cleanup pass over the bun_sql_jsc crate: net -1,012 lines (1,563 added, 2,575 removed) across 33 source files.

Exact list of behavior deltas (everything else is equivalence-audited refactor)

Re-audited file-by-file against main after the question below about observable differences. The complete list of places where behavior differs, all on error paths:

Not JS-observable (allocator-failure paths only; same errors thrown, just no leaked refs):

  1. PostgresSQLQuery::do_run now releases the speculative query ref on all three OOM-reachable error exits — the Sync write failure (456f648), and the statements.get_or_put / final requests.write_item failures (flagged by coderabbit as the remaining leaks of the same class). The omissions were inherited from the Zig original, which cleans up the simple-query writeItem failure but not these. Same error is thrown at the same point; the query/statement just no longer leak.
  2. mark_duplicate_columns uses unwrap_or_oom() where the old inline code used .expect("OOM") — crash style on allocation failure only.

JS-observable, Postgres only — the shared connection_args::parse resolves two explicit TODO(port) deviations by adopting the behavior MySQL already had, which is also exactly what the Zig reference does:

  1. SSL_CTX-creation failure now throws the structured socket error (code: "BORINGSSL", message from the BoringSSL error queue) instead of a plain Error ("Failed to create SSL context"). Old code carried TODO(port): Zig err.toJS(...); Zig does globalObject.throwValue(err.toJS(globalObject)) (PostgresSQLConnection.zig:646), which is what MySQL's parser already did on main.
  2. The null-byte rejection for username/password/database/path now throws ERR_INVALID_ARG_TYPE (a TypeError) instead of a plain Error. Message text is unchanged. Zig uses throwInvalidArguments (PostgresSQLConnection.zig:704); MySQL on main already threw this.

If bug-for-bug compatibility with the old Postgres plain-Errors is preferred over Zig/MySQL parity for 3–4, say so and I'll parameterize those two throws — but the deleted code itself documented them as port deviations.

What this does

Deduplication

  • Add tag constructors to the shared SQLDataCell and replace ~94 hand-rolled struct literals across the Postgres DataCell and MySQL ResultSet / DecodeBinaryValue decoders
  • Share createInstance argument parsing between the Postgres and MySQL connection and query classes (new shared/connection_args.rs, shared/query_args.rs)
  • Extract repeated error-cleanup blocks in PostgresSQLConnection::advance and PostgresSQLQuery::do_run
  • Collapse JSMySQLQuery's 8 copy-pasted cached-property accessors
  • Share the TLS handshake-verify and socket-adoption paths between drivers
  • Dedupe the field tables in error_response_jsc::to_js and the duplicate-field checks in Statement

Dead code removal

  • Unused Signature::hash in both drivers
  • Unused to_js helpers in postgres/types (bool.rs, bytea.rs, json.rs, PostgresString.rs deleted)
  • Unused MySQLValue Decimal parsing and to_unix_timestamp
  • Unused QueryBindingIterator::reset and IntoJSHostFn marker impls

Simplification

  • Inline single-use traits (MaybeBytes, IntoOptionalData, DateToJs, Reader/Writer forwarding)
  • Replace MySQLQuery's hand-rolled Flags bitfield with a plain struct

How this was reviewed

Each refactor was audited against the pre-refactor code for behavioral equivalence and performance (no added allocations, no removed early exits, no complexity changes on row-decode hot paths). A second full-diff audit after the rebase onto #31663 verified per area: every SQLDataCell constructor's tag/free_value/value against all ~94 replaced literals; every flag read/write in the Flags→struct swap; the advance() cleanup extraction (discard/defer offsets, exception-first ordering); the TLS verify truth table including the null-servername fail-closed case; the error_response_jsc field table (all field-code → property pairs, last-occurrence-wins, property order unchanged); mark_duplicate_columns semantics (last occurrence wins, byte-wise names, no column-count cap change); the validate_int/validate_bigint ranges per type; and the argument-decode order/messages in connection_args/query_args for both drivers. The deltas found are items 1–4 above; nothing else differs.

How we know it works

  • The two JS-observable deltas (3–4) are locked in by assertions that fail on main: the Postgres null-byte rejection's ERR_INVALID_ARG_TYPE TypeError and the SSL_CTX-failure ERR_BORINGSSL code (sql.test.ts, no server needed).
  • New no-docker tests lock in the behavior the consolidation must preserve: the shared connection_args null-byte/tls validation and query_args simple-query check for both adapters (sql-mysql-createinstance-validation.test.ts, the Postgres twin in sql.test.ts — the errors throw before any socket exists), and duplicate-column decode through a mock MySQL server (sql-mysql-duplicate-columns.test.ts: object rows keep the last occurrence, .values() keeps every cell, text + binary protocol).
  • Builds clean; bun bd test passes on sql-mysql-datetime-roundtrip, sql-postgres-datetime-roundtrip, sql-mysql-mediumint, sql-mysql-raw-length-prefix, sql-mysql-bind-oob, sql-mysql.test.ts, and the no-docker parts of sql.test.ts — including the sql: share the wall-clock datetime text parser between MySQL and Postgres #31663 shared datetime parser now feeding the refactored decode paths.
  • The real-server SQL tests only run in CI's docker lanes — local runs skip them when no MySQL/Postgres is reachable. CI green on those lanes is the meaningful signal for this PR; please don't merge before they pass.

Notes for review

The biggest hunks by far are mechanical: the SQLDataCell constructor swap accounts for most of the changed lines in DataCell.rs, ResultSet.rs, and DecodeBinaryValue.rs. The structural changes worth a closer look are shared/connection_args.rs, shared/query_args.rs, and the error-cleanup extraction in PostgresSQLConnection::advance.

@robobun

robobun commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator
Updated 3:10 PM PT - Jun 4th, 2026

@robobun, your commit 2fb9ef5 has 3 failures in Build #60346 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 31664

That installs a local version of the PR into your bun-31664 executable, so you can run:

bun-31664 --bun

@github-actions

This comment was marked as off-topic.

@alii

alii commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@robobun fix review comments

@robobun

robobun commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

All review rounds addressed — every thread replied & resolved, full equivalence audit pinned in the PR description, and the two deliberate Postgres error-shape deltas are locked in by tests that fail on main.

CI state: the only recurring red is macOS lane infrastructure — Buildkite agents repeatedly show Expired (never picking jobs up), and the one x64 run that executed failed only on pre-existing docker-gated files (PostgresError: role "bun_sql_test" does not exist — container/role provisioning) plus autobahn.test.ts, none touched by this PR. The SQL files this PR adds/changes are green everywhere they ran, including on that macOS lane. Every Linux docker lane (the real-server MySQL/Postgres suites) and both Windows lanes pass.

@alii has since merged main into the branch (e9ed282), which includes his docker-test-infra fixes (#31731, #31732) — the latest build is running on that.

Summary of what landed across the rounds

  • clippy fix + unwrap_or_oom per repo OOM guidelines
  • all three do_run OOM-exit paths release the speculative query ref (inherited-from-Zig leak class, flagged by both bots)
  • no-docker suites split into sql-mysql-createinstance-validation.test.ts / sql-mysql-duplicate-columns.test.ts per the docker/non-docker convention
  • full-diff behavioral-equivalence audit after the sql: share the wall-clock datetime text parser between MySQL and Postgres #31663 rebase; the only JS-observable deltas are the two Zig-parity TODO(port) resolutions (Postgres null-byte → ERR_INVALID_ARG_TYPE TypeError, SSL_CTX failure → ERR_BORINGSSL), documented in the description and asserted in sql.test.ts
  • the options null-byte suggestion declined with evidence (it's a pre-assembled key\0value\0 wire string; per-field NULs are rejected in JS)

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR centralizes SQL connection and query argument parsing into shared modules (connection_args and query_args), narrowing the JSC façade, expanding SQLDataCell with public constructors, unifying TLS adoption and duplicate-column detection, refactoring MySQL and PostgreSQL protocol layers with consolidated error handling, and adding validation tests. The changes span ~1100 lines across 40+ files.

Changes

SQL JSC Refactor: Shared Parsing and Protocol Unification

Layer / File(s) Summary
JSC façade narrowing and TLS foundation
src/sql_jsc/jsc.rs, src/sql_jsc/lib.rs
JSC documentation updated to distinguish core handle types from SQL-specific extension traits. pub use bun_jsc::{...} re-export list trimmed; host-function marker types (HostFnRaw, HostFnPlain) removed with only HostFnResult retained. New adopt_socket_tls<T> unsafe helper added for generic TLS socket adoption. Module tree updated to expose connection_args and query_args submodules.
Shared connection and query argument parsing
src/sql_jsc/shared/connection_args.rs, src/sql_jsc/shared/query_args.rs
New connection_args module centralizes decoding 15 common createInstance arguments (hostname, port, credentials, TLS, callbacks, timeouts) via generic parse<Mode> and SslModeArg trait for MySQL/Postgres SSLMode variants. TLS resources managed via TlsGuard with early-return cleanup. verify_tls_server helper enforces post-handshake verification. New query_args module parses 6 query arguments (query string, values array, pending value, columns, bigint/simple flags) with type enforcement and simple-mode constraints (no parameters, query length < i32::MAX).
SQLDataCell and duplicate-detection APIs
src/sql_jsc/shared/SQLDataCell.rs, src/sql_jsc/shared/CachedStructure.rs, src/sql_jsc/shared/QueryBindingIterator.rs
SQLDataCell constructor methods added: null(), int4(), uint4(), int8(), uint8(), float8(), bool_(), date(), date_with_tz(), string(), json(). raw() signature narrowed to Option<&Data> with fallback to null. Public to_js_object() wrapper added for consistent JS conversion. mark_duplicate_columns() pre-pass function detects and retags duplicate named/indexed columns. QueryBindingIterator::reset() removed.
MySQL JS binding integration
src/sql_jsc/mysql/JSMySQLConnection.rs, src/sql_jsc/mysql/JSMySQLQuery.rs, src/sql_jsc/mysql/MySQLContext.rs, src/sql_jsc/mysql/MySQLQuery.rs
JSMySQLConnection create_instance delegates argument parsing to connection_args::parse::<SSLMode>, uses parsed fields for hostname/port/credentials/TLS/callbacks, introduces live_js_value() for safe callback consumption. JSMySQLQuery uses query_args::parse for query validation, introduces live_this_value() for cached field access, updates error messages to byte literals. MySQLContext init returns JsResult<JSValue>. MySQLQuery Flags refactored from packed u8 bitfield to plain bool struct with direct field access.
MySQL TLS adoption and verification
src/sql_jsc/mysql/MySQLConnection.rs
upgrade_to_tls refactored to extract TCP socket via pattern match, delegates TLS adoption to adopt_socket_tls helper with callback for socket update, maps adoption failure to AuthenticationFailed. do_handshake verifies TLS via delegated verify_tls_server check, consolidating rejection logic and setting tls_status to SslFailed on verification failure.
MySQL protocol and type updates
src/sql_jsc/mysql/MySQLValue.rs, src/sql_jsc/mysql/MySQLStatement.rs, src/sql_jsc/mysql/protocol/DecodeBinaryValue.rs, src/sql_jsc/mysql/protocol/ResultSet.rs, src/sql_jsc/mysql/protocol/Signature.rs, src/sql_jsc/mysql/protocol/any_mysql_error_jsc.rs
MySQLValue adds generic integer-range validation helpers and removes Decimal struct/Time::to_unix_timestamp. MySQLStatement delegates duplicate detection to mark_duplicate_columns. DecodeBinaryValue and ResultSet converted to use SQLDataCell constructors throughout (null, int4/uint4/int8/uint8, float8, bool_, date, string, json, raw). Signature::hash() removed. mysql_error_to_js signature narrowed to require &[u8] message slice; MaybeBytes trait removed.
Postgres JS binding integration
src/sql_jsc/postgres/PostgresSQLContext.rs, src/sql_jsc/postgres/PostgresSQLQuery.rs, src/sql_jsc/postgres/PostgresSQLConnection.rs
PostgresSQLContext init returns JsResult<JSValue>. PostgresSQLQuery uses query_args::parse for validation, introduces do_run closures for release_query_ref() and throw_write_error() to consolidate cleanup/exception handling across execution paths. PostgresSQLConnection delegates argument parsing to connection_args::parse::<SSLMode>, makes update_flags() public, builds connection-string buffers from parsed args fields.
Postgres TLS setup and request protocol
src/sql_jsc/postgres/PostgresSQLConnection.rs, src/sql_jsc/postgres/PostgresRequest.rs
PostgresSQLConnection setup_tls extracts TCP socket and delegates adoption to adopt_socket_tls, fails fast on adoption failure. on_handshake simplified to single ok boolean from success/auth/verification gating via verify_tls_server. PostgresRequest introduces execute_flush_sync helper to consolidate Execute+FLUSH+SYNC message sequence, removes mut from prepare_and_query_with_signature and bind_and_execute writer parameters, simplifies parse_and_bind_and_execute include_describe branching.
Postgres protocol context and data cell updates
src/sql_jsc/postgres/DataCell.rs, src/sql_jsc/postgres/PostgresSQLConnection.rs, src/sql_jsc/postgres/protocol/error_response_jsc.rs
DataCell array/scalar parsing and from_bytes refactored to use SQLDataCell constructors throughout. Putter::to_js calls to_js_object. Writer implements protocol::WriterContext via with_mut. Reader implements protocol::ReaderContext with internal ensure_capacity, delegates head advancement via skip, returns ShortRead on insufficient data. PostgresSQLConnection introduces report_write_failure (handles JS exceptions vs statement updates) and discard_or_defer (head discard vs deferred cleanup), consolidates advance() across all query execution paths. error_response_jsc refactored to build PostgresErrorOptions incrementally via helper loop.
Postgres type module consolidation and removal
src/sql_jsc/postgres.rs, src/sql_jsc/postgres/types/date.rs, src/sql_jsc/postgres/types/tag_jsc.rs, src/sql_jsc/postgres/types/PostgresString.rs, src/sql_jsc/postgres/types/bytea.rs, src/sql_jsc/postgres/types/json.rs, src/sql_jsc/postgres/types/bool.rs
Postgres types namespace removes bool, bytea, json, and postgres_string modules; date module retained. date.rs deletes DateToJs trait and to_js functions. tag_jsc.rs deletes TagToJs trait. PostgresString.rs module deleted entirely (ToJsWithType trait removed). bytea.rs and json.rs emptied (ByteaToJs and JsonToJs traits removed). bool.rs comment removed.
Validation and duplicate column tests
test/js/sql/sql.test.ts, test/js/sql/sql-mysql-createinstance-validation.test.ts, test/js/sql/sql-mysql-duplicate-columns.test.ts
New test suites validate createInstance argument parsing without server contact: PostgreSQL and MySQL both verify null-byte injection rejection (username/password/database), TLS type validation (reject non-bool/non-object), simple query parameter rejection. Dedicated MySQL mock-server test validates duplicate column name handling: object-row mode keeps last duplicate occurrence, .values() preserves all cells in order across text and binary protocols.

Possibly related PRs

  • oven-sh/bun#31513: Overlaps in MySQL protocol decoding for MYSQL_TYPE_NEWDECIMAL and related field type handling in DecodeBinaryValue and parse_value_and_set_cell.
  • oven-sh/bun#31175: Both PRs modify MySQL TLS handshake and connection initialization logic in MySQLConnection.rs.

Suggested reviewers

  • cirospaciari

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sql_jsc/mysql/JSMySQLConnection.rs (1)

481-526: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Reject negative timeout values instead of panicking.

connection_args::parse() preserves these as signed i32s, so a JS caller can pass -1 and hit the expect("int cast") on Lines 524-526. That turns invalid user input into a runtime panic in core code; validate and throw before disarming args.tls.

Suggested fix
         let Some(args) = connection_args::parse::<SSLMode>(vm, global_object, callframe)? else {
             return Ok(JSValue::ZERO);
         };
+        let idle_timeout_interval_ms = u32::try_from(args.idle_timeout).map_err(|_| {
+            global_object.throw_invalid_arguments(format_args!("idleTimeout must be >= 0"))
+        })?;
+        let connection_timeout_ms = u32::try_from(args.connection_timeout).map_err(|_| {
+            global_object.throw_invalid_arguments(format_args!("connectionTimeout must be >= 0"))
+        })?;
+        let max_lifetime_interval_ms = u32::try_from(args.max_lifetime).map_err(|_| {
+            global_object.throw_invalid_arguments(format_args!("maxLifetime must be >= 0"))
+        })?;
...
-            idle_timeout_interval_ms: u32::try_from(args.idle_timeout).expect("int cast"),
-            connection_timeout_ms: u32::try_from(args.connection_timeout).expect("int cast"),
-            max_lifetime_interval_ms: u32::try_from(args.max_lifetime).expect("int cast"),
+            idle_timeout_interval_ms,
+            connection_timeout_ms,
+            max_lifetime_interval_ms,
As per coding guidelines, `src/**/*.rs`: Do not use `.unwrap()` on fallible paths that user input or the OS can hit at runtime — return the error instead. `.unwrap()` is only for invariants you can prove.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sql_jsc/mysql/JSMySQLConnection.rs` around lines 481 - 526, The code
currently converts signed timeout fields (args.idle_timeout,
args.connection_timeout, args.max_lifetime) to u32 with expect(...) which panics
on negative input; instead validate these three fields are >= 0 and fit into u32
before calling scopeguard::ScopeGuard::into_inner(args.tls) (i.e. before
disarming TLS ownership), and if any are invalid return a JS exception/Err
(propagate the parse/validation error) rather than panicking; replace the
u32::try_from(...).expect(...) calls in the JSMySQLConnection construction with
safe checked conversions that produce an early return with a descriptive error
when a negative value is provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sql_jsc/mysql/JSMySQLQuery.rs`:
- Line 100: The shared parser query_args::parse is making pending_value
effectively required by unwrapping to JSValue::UNDEFINED and then rejecting
non-array-like values; change the parser so it preserves whether the third
argument was actually provided (e.g., return Option<JSValue> or a flag) and only
perform the "must be array-like" validation when the caller actually passed a
third argument, leaving omitted pendingValue as undefined for callers like
JSMySQLQuery construction that rely on the default behavior; update any call
sites (e.g., the call in JSMySQLQuery.rs that does let args =
query_args::parse(global_this, callframe)?) to handle the new optional/flag
return so omitted pendingValue continues to behave as before.

In `@src/sql_jsc/postgres/DataCell.rs`:
- Around line 779-787: The T::int8 branch currently ignores the binary flag and
always treats the payload as text or raw bytes; update the T::int8 match to
first check if binary is true and, if so, decode the 8-byte wire format into an
i64 (use an explicit signed 8-byte conversion from the bytes buffer) and return
SQLDataCell::int8(parsed_i64); otherwise keep the existing text-path behavior
that uses bigint/text handling and bun_core::fmt::parse_decimal::<i64>(bytes) or
SQLDataCell::string(bytes). Ensure you reference and modify the T::int8 branch
where bigint, binary, SQLDataCell::int8, and bun_core::fmt::parse_decimal are
used.

In `@src/sql_jsc/postgres/PostgresSQLQuery.rs`:
- Around line 774-777: In do_run(), the error path that handles
writer.write(&protocol::SYNC) returns without releasing the speculative query
ref taken earlier; ensure you call release_query_ref() before
remove_statements_entry()/drop(signature)/return Err(...) so the extra
this.ref_() from Line 524 is released and prepared queries aren’t pinned—i.e.,
add a release_query_ref() call in the Err(err) branch that currently calls
remove_statements_entry() and drop(signature) before returning the
throw_write_error.

In `@src/sql_jsc/shared/CachedStructure.rs`:
- Around line 32-35: The code calls
seen_fields.get_or_put(name.slice()).expect("OOM") which can panic on
allocation; replace this with the repo's OOM trap by using the provided helper
(e.g., call .unwrap_or_oom() on the Result or invoke bun_core::handle_oom) so
the allocation failure triggers the controlled crash path instead of unwinding
through the SQL/JSC boundary; update the expression around
seen_fields.get_or_put and ensure the resulting value uses the
unwrap_or_oom/handle_oom variant instead of expect("OOM").

In `@src/sql_jsc/shared/connection_args.rs`:
- Around line 97-101: Move the existing SAFETY comment so it immediately
precedes the unsafe block that calls ssl_ptr.as_mut() to satisfy Clippy;
specifically, relocate the comment about the native handle of a connected TLS
socket being `SSL*` and living for the duration of the handshake callback to
directly above the unsafe { ssl_ptr.as_mut() } expression (referencing ssl_ptr
and the unsafe block that yields ssl) so the safety rationale is attached to
that unsafe operation.

In `@src/sql_jsc/shared/query_args.rs`:
- Around line 44-62: The code currently treats a missing pendingValue (default
JSValue::UNDEFINED) as an error because it unconditionally checks
pending_value.js_type().is_array_like(); update the validation in the shared
createInstance path to allow undefined by only rejecting when pending_value is
not undefined and also not array-like (i.e., change the condition to skip the
array-like check if pending_value is JSValue::UNDEFINED). Reference:
pending_value, JSValue::UNDEFINED, createInstance.

---

Outside diff comments:
In `@src/sql_jsc/mysql/JSMySQLConnection.rs`:
- Around line 481-526: The code currently converts signed timeout fields
(args.idle_timeout, args.connection_timeout, args.max_lifetime) to u32 with
expect(...) which panics on negative input; instead validate these three fields
are >= 0 and fit into u32 before calling
scopeguard::ScopeGuard::into_inner(args.tls) (i.e. before disarming TLS
ownership), and if any are invalid return a JS exception/Err (propagate the
parse/validation error) rather than panicking; replace the
u32::try_from(...).expect(...) calls in the JSMySQLConnection construction with
safe checked conversions that produce an early return with a descriptive error
when a negative value is provided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b55b9eef-489c-41b0-b8a8-3d71e625f14a

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac120c and eadf7f0.

📒 Files selected for processing (33)
  • src/sql_jsc/jsc.rs
  • src/sql_jsc/lib.rs
  • src/sql_jsc/mysql/JSMySQLConnection.rs
  • src/sql_jsc/mysql/JSMySQLQuery.rs
  • src/sql_jsc/mysql/MySQLConnection.rs
  • src/sql_jsc/mysql/MySQLContext.rs
  • src/sql_jsc/mysql/MySQLQuery.rs
  • src/sql_jsc/mysql/MySQLStatement.rs
  • src/sql_jsc/mysql/MySQLValue.rs
  • src/sql_jsc/mysql/protocol/DecodeBinaryValue.rs
  • src/sql_jsc/mysql/protocol/ResultSet.rs
  • src/sql_jsc/mysql/protocol/Signature.rs
  • src/sql_jsc/mysql/protocol/any_mysql_error_jsc.rs
  • src/sql_jsc/postgres.rs
  • src/sql_jsc/postgres/DataCell.rs
  • src/sql_jsc/postgres/PostgresRequest.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • src/sql_jsc/postgres/PostgresSQLContext.rs
  • src/sql_jsc/postgres/PostgresSQLQuery.rs
  • src/sql_jsc/postgres/PostgresSQLStatement.rs
  • src/sql_jsc/postgres/Signature.rs
  • src/sql_jsc/postgres/protocol/error_response_jsc.rs
  • src/sql_jsc/postgres/types/PostgresString.rs
  • src/sql_jsc/postgres/types/bool.rs
  • src/sql_jsc/postgres/types/bytea.rs
  • src/sql_jsc/postgres/types/date.rs
  • src/sql_jsc/postgres/types/json.rs
  • src/sql_jsc/postgres/types/tag_jsc.rs
  • src/sql_jsc/shared/CachedStructure.rs
  • src/sql_jsc/shared/QueryBindingIterator.rs
  • src/sql_jsc/shared/SQLDataCell.rs
  • src/sql_jsc/shared/connection_args.rs
  • src/sql_jsc/shared/query_args.rs
💤 Files with no reviewable changes (10)
  • src/sql_jsc/postgres/types/bool.rs
  • src/sql_jsc/postgres/types/json.rs
  • src/sql_jsc/postgres/types/PostgresString.rs
  • src/sql_jsc/postgres.rs
  • src/sql_jsc/postgres/types/bytea.rs
  • src/sql_jsc/postgres/types/date.rs
  • src/sql_jsc/shared/QueryBindingIterator.rs
  • src/sql_jsc/postgres/Signature.rs
  • src/sql_jsc/postgres/types/tag_jsc.rs
  • src/sql_jsc/mysql/protocol/Signature.rs

Comment thread src/sql_jsc/mysql/JSMySQLQuery.rs
Comment thread src/sql_jsc/postgres/DataCell.rs
Comment thread src/sql_jsc/postgres/PostgresSQLQuery.rs
Comment thread src/sql_jsc/shared/CachedStructure.rs
Comment thread src/sql_jsc/shared/connection_args.rs Outdated
Comment thread src/sql_jsc/shared/query_args.rs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I didn't find any bugs, but this is a large refactor (33 files, ~3.8k lines) that touches security-sensitive paths — TLS server verification, socket adoption, SSL_CTX lifetime, and null-byte injection checks — so it warrants a human look, and per the description it should wait on the CI docker lanes before merge.

Extended reasoning...

Overview

This PR is a no-behavior-change cleanup pass over the bun_sql_jsc crate, net -1,303 lines across 33 files. It deduplicates SQL driver code between MySQL and Postgres: shared SQLDataCell tag constructors replacing ~94 hand-rolled struct literals; shared createInstance argument parsing (shared/connection_args.rs, shared/query_args.rs); shared TLS handshake verification and socket-adoption (adopt_socket_tls, verify_tls_server); extracted error-cleanup blocks in PostgresSQLConnection::advance and PostgresSQLQuery::do_run; consolidated duplicate-field checks; plus dead-code removal (unused Signature::hash, postgres/types/{bool,bytea,json,PostgresString}.rs, Decimal, MaybeBytes/IntoOptionalData traits, etc.).

Security risks

Several refactors touch security-relevant code: verify_tls_server in shared/connection_args.rs consolidates the VerifyCa/VerifyFull cert and hostname-identity check that both drivers run after the TLS handshake; adopt_socket_tls in jsc.rs consolidates unsafe raw us_socket_t* adoption into the TLS group with SNI; the shared connection_args::parse owns the null-byte injection check on username/password/database/path and the SSL_CTX errdefer guard. These all look behavior-preserving on inspection (the fail-closed paths for missing servername / null SSL handle are retained, the null-byte check covers the same four fields), but they are exactly the kind of consolidation where a subtle ordering or short-circuit change could weaken verification, so a second pair of eyes is appropriate.

Level of scrutiny

This deserves careful human review. Beyond the TLS surface, the structural refactors carry subtleties: report_write_failure takes a raw *mut PostgresSQLStatement to avoid a protected &mut across JS re-entry; the Utf8Arg struct relies on field declaration order for drop correctness (the ZigStringSlice may borrow the OwnedString); PostgresRequest::parse_and_bind_and_execute was reshaped to call write_query (Parse+Describe) instead of separate Parse/Describe writes; and the Postgres on_handshake logic was flattened into a single boolean expression. The bulk of the diff (the SQLDataCell::int4/uint4/string/etc. constructor swaps) is mechanical and low-risk.

Other factors

The bug-hunting system found no issues. No CODEOWNERS cover src/sql_jsc/. The PR description explicitly notes that the real-server SQL tests only run in CI's docker lanes and asks not to merge before they pass — local tests skip when no DB is reachable, so CI green on those lanes is the meaningful signal here. Given the size, the unsafe/FFI-adjacent restructuring, and the security-sensitive TLS consolidation, I'm deferring rather than approving.

Comment thread src/sql_jsc/postgres/PostgresSQLQuery.rs
alii and others added 4 commits June 1, 2026 15:53
Net -1573 lines across the bun_sql_jsc crate with no behavior change.

Deduplication:
- Add SQLDataCell tag constructors and replace ~94 hand-rolled struct
  literals across the Postgres DataCell and MySQL ResultSet /
  DecodeBinaryValue decoders
- Share createInstance argument parsing between the Postgres and MySQL
  connection and query classes (new shared/connection_args.rs and
  shared/query_args.rs)
- Extract repeated error-cleanup blocks in PostgresSQLConnection::advance
  and PostgresSQLQuery::do_run
- Collapse JSMySQLQuery's 8 copy-pasted cached-property accessors
- Share the TLS handshake-verify and socket-adoption paths between drivers
- Dedupe field tables in error_response_jsc::to_js and duplicate-field
  checks in Statement

Dead code removal:
- Unused Signature::hash in both drivers
- Unused to_js helpers in postgres/types (bool, bytea, json,
  PostgresString)
- Unused MySQLValue Decimal parsing and to_unix_timestamp
- Unused QueryBindingIterator::reset and IntoJSHostFn marker impls

Simplification:
- Inline single-use traits (MaybeBytes, IntoOptionalData, DateToJs,
  Reader/Writer forwarding)
- Replace MySQLQuery's hand-rolled Flags bitfield with a plain struct
…mark_duplicate_columns

- clippy (undocumented_unsafe_blocks) requires the SAFETY comment to sit
  directly on the statement containing the unsafe block; the intervening
  ssl_ptr binding broke that association in connection_args::verify_tls_server.
- The dedup-map allocation in mark_duplicate_columns now routes OOM through
  bun_core's controlled out_of_memory() handler instead of a plain panic.
The Sync-failure branch in do_run() was the only write-error path that
returned without pairing the speculative this.ref_(), pinning the query
object if the flush write failed. The omission is inherited from the Zig
original; every sibling branch releases the ref, so this one now does too.
…ecode

Covers the consolidated argument parsers and statement decode path without
needing docker or a live server:

- connection_args: null bytes in username/password/database and a
  non-boolean/non-object tls reject before any socket is created, for both
  the postgres and mysql adapters
- query_args: a simple query with parameters is refused at handle creation
- mark_duplicate_columns + the SQLDataCell decode paths, via a mock MySQL
  server returning two identically-named columns: object rows keep the last
  occurrence (text and binary protocol), .values() keeps every cell
@alii alii force-pushed the ali/sql-jsc-cleanup branch from 8cd63ec to 7fef319 Compare June 1, 2026 22:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/js/sql/sql.test.ts (1)

12667-12667: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use port: 0 in test base options.

port: 1 violates the test rule requiring port: 0. Since these cases assert pre-connect validation, 0 is sufficient and keeps the suite consistent.

As per coding guidelines: “Always use port: 0 in tests - do not hardcode ports or use custom random port functions”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/js/sql/sql.test.ts` at line 12667, The test is using a hardcoded port
value of 1 in the test's base options which violates the rule to always use
port: 0; locate where the test constructs its base options (the object passed to
the client/server setup in this test case—look for "port: 1" near the test that
calls commandComplete("SELECT 1")) and change that port value to 0 so the test
uses an ephemeral port and remains consistent with the test-suite guideline.
src/sql_jsc/postgres/PostgresSQLConnection.rs (1)

425-465: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Start the protocol only after TLS verification succeeds.

adopt_socket_tls() kicks off the TLS handshake, but Line 464 immediately calls self.start(). In SSLMode::VerifyCa / SSLMode::VerifyFull, that can queue the Postgres startup packet before on_handshake() finishes the cert/hostname check, so we may send application bytes to a peer we reject moments later.

Suggested fix
     pub fn setup_tls(&self) {
         debug!("setupTLS");
         // `vm_mut()` is `'static`, so `tls_group` borrows the VM singleton —
         // not `*self` — and stays live across the field reads below.
         let tls_group: &mut bun_uws::SocketGroup = self.vm_mut().postgres_socket_group::<true>();

@@
         if !adopted {
             self.fail(
                 b"Failed to upgrade to TLS",
                 AnyPostgresError::TLSUpgradeFailed,
             );
             return;
         }
-        self.start();
     }

@@
         let ok = success == 1
             && (!verify
                 || connection_args::verify_tls_server(
                     self.ssl_mode == SSLMode::VerifyFull,
                     &self.tls_config,
                     self.socket.get().get_native_handle(),
                     ssl_error.error_no,
                 ));
         if !ok {
             let Ok(v) = verify_error_to_js(&ssl_error, self.global()) else {
                 return;
             };
             self.fail_with_js_value(v);
+            return;
         }
+
+        self.start();
     }

Also applies to: 813-833

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sql_jsc/postgres/PostgresSQLConnection.rs` around lines 425 - 465, The
TLS handshake currently starts via jsc::adopt_socket_tls in setup_tls but
setup_tls calls self.start() immediately, which can send the startup packet
before certificate/hostname verification completes; change the flow so that
self.start() is invoked only after the TLS verification callback (on_handshake /
the adopt_socket_tls completion handler) confirms success — i.e., remove the
immediate self.start() from setup_tls and instead call self.start() from the
handshake-success path inside the adopt_socket_tls/on_handshake callback (and on
error call self.fail with AnyPostgresError::TLSUpgradeFailed); apply the same
change to the other setup_tls occurrence referenced (lines ~813-833) so protocol
start is gated on successful verification.
src/sql_jsc/postgres/DataCell.rs (1)

830-865: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a binary DATE decode path.

T::date uses a 4-byte binary payload, but this branch only treats 8-byte values as binary. In binary results, DATE therefore falls through to the text parser and feeds raw bytes into parse_date, which corrupts prepared-query decoding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sql_jsc/postgres/DataCell.rs` around lines 830 - 865, The binary decode
branch incorrectly only checks for 8-byte payloads, causing 4-byte T::date
binary values to be treated as text; update the tag handling in the match inside
DataCell.rs so that when binary is true and tag == T::date with bytes.len() == 4
you call crate::postgres::types::date::from_binary(bytes) and return
SQLDataCell::date(...) (similar to the T::timestamp/T::timestamptz cases that
call from_binary), ensuring T::date is decoded via the 4-byte binary path
instead of falling through to parse_date.
♻️ Duplicate comments (1)
src/sql_jsc/shared/query_args.rs (1)

44-62: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Allow omitted pendingValue to stay undefined.

Line 44 defaults a missing third argument to JSValue::UNDEFINED, and Line 60 immediately rejects it because undefined is not array-like. That turns an omitted optional argument into a construction-time throw on every caller of this shared parser.

Suggested fix
-    if !pending_value.js_type().is_array_like() {
+    if !pending_value.is_undefined() && !pending_value.js_type().is_array_like() {
         return Err(global_this.throw_invalid_argument_type("query", "pendingValue", "Array"));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sql_jsc/shared/query_args.rs` around lines 44 - 62, The code currently
treats a missing/omitted pending_value as JSValue::UNDEFINED then immediately
rejects it because pending_value.js_type().is_array_like() fails; update the
validation in the shared parser (symbols: pending_value,
pending_value.js_type(), global_this.throw_invalid_argument_type) to allow
undefined by only performing the "array-like" check when pending_value is not
undefined (e.g., change the condition to check pending_value.is_undefined()
first and only call js_type().is_array_like() when it's not undefined), so
omitted pendingValue stays undefined instead of throwing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sql_jsc/postgres/PostgresSQLQuery.rs`:
- Around line 526-545: The OOM/error early returns after calling this.ref_() are
leaking the speculative ref and sometimes leaving this.statement or cache
entries pinned; replace the direct returns at the get_or_put(...) allocation
error and the requests.write_item(this_ptr) failure sites so they first call the
shared cleanup closure release_query_ref (which invokes this.release_statement()
and unsafe { Self::deref(this_ptr) }) and then route the error through the
existing throw_write_error closure (postgres_error_to_js via throw_write_error),
instead of returning immediately; ensure both failure paths use
release_query_ref() followed by returning throw_write_error(...) to reuse the
same speculative-ref cleanup and avoid leaks.

In `@src/sql_jsc/shared/connection_args.rs`:
- Around line 229-240: The null-byte check loop currently iterates over
(&username, "username"), (&password, "password"), (&database, "database"),
(&path, "path") but omits the Postgres startup-string field `options`; update
the iteration to include (&options, "options") so `options` is rejected on
embedded NULs as well, keeping the same logic that calls
global_object.throw_invalid_arguments when strings::index_of_char finds a null
byte.

In `@test/js/sql/sql-mysql.test.ts`:
- Around line 1385-1432: The shared no-server MySQL test suite (the describe
block titled "shared createInstance validation (no server)" and its contained
tests using SQL/using/sql`...`) should be moved out of sql-mysql.test.ts into a
new file named like sql-mysql-nodocker.test.ts (or sql-mysql.shared.test.ts) so
non-Docker/mock cases are separated from Docker-gated MySQL container tests;
locate the describe("shared createInstance validation (no server)") block and
its tests (including the three test.concurrent.each for null-byte fields, the
tls type test, and the simple query parameters test using SQL and
unsafe().simple()) and copy them to the new test file, remove them from the
original file, and ensure any necessary imports/exports (e.g., SQL, using
helper) are present at the top of the new file so the tests run in the
non-Docker suite.

---

Outside diff comments:
In `@src/sql_jsc/postgres/DataCell.rs`:
- Around line 830-865: The binary decode branch incorrectly only checks for
8-byte payloads, causing 4-byte T::date binary values to be treated as text;
update the tag handling in the match inside DataCell.rs so that when binary is
true and tag == T::date with bytes.len() == 4 you call
crate::postgres::types::date::from_binary(bytes) and return
SQLDataCell::date(...) (similar to the T::timestamp/T::timestamptz cases that
call from_binary), ensuring T::date is decoded via the 4-byte binary path
instead of falling through to parse_date.

In `@src/sql_jsc/postgres/PostgresSQLConnection.rs`:
- Around line 425-465: The TLS handshake currently starts via
jsc::adopt_socket_tls in setup_tls but setup_tls calls self.start() immediately,
which can send the startup packet before certificate/hostname verification
completes; change the flow so that self.start() is invoked only after the TLS
verification callback (on_handshake / the adopt_socket_tls completion handler)
confirms success — i.e., remove the immediate self.start() from setup_tls and
instead call self.start() from the handshake-success path inside the
adopt_socket_tls/on_handshake callback (and on error call self.fail with
AnyPostgresError::TLSUpgradeFailed); apply the same change to the other
setup_tls occurrence referenced (lines ~813-833) so protocol start is gated on
successful verification.

In `@test/js/sql/sql.test.ts`:
- Line 12667: The test is using a hardcoded port value of 1 in the test's base
options which violates the rule to always use port: 0; locate where the test
constructs its base options (the object passed to the client/server setup in
this test case—look for "port: 1" near the test that calls
commandComplete("SELECT 1")) and change that port value to 0 so the test uses an
ephemeral port and remains consistent with the test-suite guideline.

---

Duplicate comments:
In `@src/sql_jsc/shared/query_args.rs`:
- Around line 44-62: The code currently treats a missing/omitted pending_value
as JSValue::UNDEFINED then immediately rejects it because
pending_value.js_type().is_array_like() fails; update the validation in the
shared parser (symbols: pending_value, pending_value.js_type(),
global_this.throw_invalid_argument_type) to allow undefined by only performing
the "array-like" check when pending_value is not undefined (e.g., change the
condition to check pending_value.is_undefined() first and only call
js_type().is_array_like() when it's not undefined), so omitted pendingValue
stays undefined instead of throwing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4058b6ff-55ae-43cf-af22-9b7e9c2ed5f4

📥 Commits

Reviewing files that changed from the base of the PR and between 102b319 and 0797c17.

📒 Files selected for processing (35)
  • src/sql_jsc/jsc.rs
  • src/sql_jsc/lib.rs
  • src/sql_jsc/mysql/JSMySQLConnection.rs
  • src/sql_jsc/mysql/JSMySQLQuery.rs
  • src/sql_jsc/mysql/MySQLConnection.rs
  • src/sql_jsc/mysql/MySQLContext.rs
  • src/sql_jsc/mysql/MySQLQuery.rs
  • src/sql_jsc/mysql/MySQLStatement.rs
  • src/sql_jsc/mysql/MySQLValue.rs
  • src/sql_jsc/mysql/protocol/DecodeBinaryValue.rs
  • src/sql_jsc/mysql/protocol/ResultSet.rs
  • src/sql_jsc/mysql/protocol/Signature.rs
  • src/sql_jsc/mysql/protocol/any_mysql_error_jsc.rs
  • src/sql_jsc/postgres.rs
  • src/sql_jsc/postgres/DataCell.rs
  • src/sql_jsc/postgres/PostgresRequest.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • src/sql_jsc/postgres/PostgresSQLContext.rs
  • src/sql_jsc/postgres/PostgresSQLQuery.rs
  • src/sql_jsc/postgres/PostgresSQLStatement.rs
  • src/sql_jsc/postgres/Signature.rs
  • src/sql_jsc/postgres/protocol/error_response_jsc.rs
  • src/sql_jsc/postgres/types/PostgresString.rs
  • src/sql_jsc/postgres/types/bool.rs
  • src/sql_jsc/postgres/types/bytea.rs
  • src/sql_jsc/postgres/types/date.rs
  • src/sql_jsc/postgres/types/json.rs
  • src/sql_jsc/postgres/types/tag_jsc.rs
  • src/sql_jsc/shared/CachedStructure.rs
  • src/sql_jsc/shared/QueryBindingIterator.rs
  • src/sql_jsc/shared/SQLDataCell.rs
  • src/sql_jsc/shared/connection_args.rs
  • src/sql_jsc/shared/query_args.rs
  • test/js/sql/sql-mysql.test.ts
  • test/js/sql/sql.test.ts
💤 Files with no reviewable changes (9)
  • src/sql_jsc/shared/QueryBindingIterator.rs
  • src/sql_jsc/postgres/types/bytea.rs
  • src/sql_jsc/postgres/types/PostgresString.rs
  • src/sql_jsc/postgres.rs
  • src/sql_jsc/postgres/Signature.rs
  • src/sql_jsc/postgres/types/tag_jsc.rs
  • src/sql_jsc/postgres/types/json.rs
  • src/sql_jsc/mysql/protocol/Signature.rs
  • src/sql_jsc/postgres/types/bool.rs

Comment thread src/sql_jsc/postgres/PostgresSQLQuery.rs
Comment thread src/sql_jsc/shared/connection_args.rs
Comment thread test/js/sql/sql-mysql.test.ts Outdated
@alii

alii commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@robobun make sure there is absolutely no observable difference and this is a refactor only

@robobun

robobun commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Re-audited the full diff file-by-file against main (constructor tables, flag reads/writes, advance() cleanup offsets, TLS verify truth table, error-field tables, dedup semantics, integer ranges, argument-decode order/messages for both drivers). Result, now pinned in the PR description:

  • Query/decode behavior: zero differences. Every row-decode path, wire write, check order, early exit, and error message is equivalent.
  • Four deltas exist, all on error paths — the three do_run OOM-exit ref releases + unwrap_or_oom (not JS-observable: same errors thrown, just no leaked refs on allocator failure), and two Postgres-only error-shape changes where the shared parser resolved explicit TODO(port) deviations by adopting what MySQL already did and what the Zig reference specifies: SSL_CTX-creation failure throws the structured code: "BORINGSSL" error instead of a plain Error (PostgresSQLConnection.zig:646), and the null-byte rejection throws ERR_INVALID_ARG_TYPE instead of a plain Error with the same message (PostgresSQLConnection.zig:704).

If you want strict bug-for-bug compat with old-Postgres plain Errors on those two paths instead of Zig/MySQL parity, say the word and I'll parameterize the two throws.

Comment thread src/sql_jsc/shared/connection_args.rs
@alii alii requested a review from cirospaciari June 2, 2026 00:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/sql_jsc/postgres/PostgresSQLQuery.rs (1)

837-843: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Also unwind the named statement cache entry on enqueue OOM.

By this point the named-prepared-statement path may already have stored stmt into connection.statements (Lines 797-820). release_query_ref() only drops the query-held ref, so this branch can leave an orphaned Pending/Parsing statement cached even though the request never entered connection.requests. A later query can then pick up that stale cached statement.

Hoist the same statement-map cleanup used by the earlier write-failure branches into this OOM path before returning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sql_jsc/postgres/PostgresSQLQuery.rs` around lines 837 - 843, The OOM
branch that returns after failing to enqueue the request must also remove the
prepared-statement entry from connection.statements to avoid leaving a stale
Pending/Parsing cache; update the block handling
connection.requests.with_mut(...).is_err() so that, before calling
release_query_ref() and returning Err(...), it performs the same statement-map
cleanup used in the earlier write-failure branches (remove the stmt entry from
connection.statements if it was inserted), referencing the same stmt identifier
and connection.statements map to ensure the cached statement is unwound on
enqueue failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/sql_jsc/postgres/PostgresSQLQuery.rs`:
- Around line 837-843: The OOM branch that returns after failing to enqueue the
request must also remove the prepared-statement entry from connection.statements
to avoid leaving a stale Pending/Parsing cache; update the block handling
connection.requests.with_mut(...).is_err() so that, before calling
release_query_ref() and returning Err(...), it performs the same statement-map
cleanup used in the earlier write-failure branches (remove the stmt entry from
connection.statements if it was inserted), referencing the same stmt identifier
and connection.statements map to ensure the cached statement is unwound on
enqueue failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fad7ccd6-3b5b-4b8c-ab68-ee722389b2ce

📥 Commits

Reviewing files that changed from the base of the PR and between 0797c17 and e9ed282.

📒 Files selected for processing (6)
  • src/sql_jsc/postgres/PostgresRequest.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • src/sql_jsc/postgres/PostgresSQLQuery.rs
  • test/js/sql/sql-mysql-createinstance-validation.test.ts
  • test/js/sql/sql-mysql-duplicate-columns.test.ts
  • test/js/sql/sql.test.ts

@alii

alii commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@robobun conflicts

# Conflicts:
#	src/sql_jsc/mysql/MySQLStatement.rs
#	src/sql_jsc/postgres/DataCell.rs
#	src/sql_jsc/postgres/PostgresSQLConnection.rs
@robobun

robobun commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Conflicts resolved — merged main in 2fb9ef5. All three conflicts were against #31746 (the stale-TODO/dead-code sweep) editing comments inside blocks this PR had already replaced with the shared helpers (mark_duplicate_columns, SQLDataCell::json/to_js_object, connection_args::parse), so the resolution keeps the refactored side in each case. cargo clippy clean, SQL suites green locally (27/27 across the no-docker files).

@alii alii closed this Jun 9, 2026
@alii

alii commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

Closing because conflicts

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.

2 participants