sql_jsc: add SQLDataCell tag constructors and replace hand-rolled struct literals#32367
Conversation
Re-adds dedupe_columns (#32135) and the IntoOptionalData trait + raw() signature that the cherry-pick clobbered, and strips the stale TODO(port)/PORT NOTE comments that #31783 had already resolved on main. All 12 tag constructors verified field-for-field identical to the struct literals they replace (tag, value active member, free_value).
|
Updated 8:47 AM PT - Jun 16th, 2026
❌ @robobun, your commit 03acefd has 1 failures in
🧪 To try this PR locally: bunx bun-pr 32367That installs a local version of the PR into your bun-32367 --bun |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughIntroduces typed constructor methods ( ChangesSQLDataCell constructor API and SQL value decoding refactor
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
@robobun adopt |
|
Adopted. All review findings addressed; HEAD is 03acefd. 6785a4e fixes a pre-existing bug the refactor surfaced: 50739b9 completes the constructor migration in 03acefd drops the custom mysqld_safe harness; both MySQL tests now use
|
…as_deref() Addresses review on #32367: - postgres DataCell.rs: the IntoOptionalData impl for Option<&mut Data> was restored in the reconcile commit, so .as_deref() is dead weight - mysql ResultSet.rs: Row::to_js now calls the shared to_js_object helper, matching its doc comment and the postgres Putter::to_js path; drops the now-unused ExternColumnIdentifier import
…uctors Replaces the remaining hand-rolled SQLDataCell struct literals in ResultSet.rs (parse_value_and_set_cell, decode_text, decode_binary) with the named constructors. This removes every caller of the file-local clone_wtf_string_or_null helper, which duplicated the new clone_utf8_or_null in SQLDataCell.rs, so it is deleted along with the now-unused ptr/BunString/Tag/Value imports. Also gives SQLDataCell::uint8() its first caller (MYSQL_TYPE_LONGLONG unsigned bigint path).
The mysql binary-protocol row decoder did *value = SQLDataCell::null(); continue; when the null bitmap marked a column NULL, skipping the index / is_indexed_column assignments below. For digit-named columns (which ColumnIdentifier classifies as Index(n)) those fields tell SQLClient.cpp which object slot to place the value in, so a NULL on such a column landed at index 0 instead of the column's numeric name, and tripped ASSERT(cell.isIndexedColumn()) in debug builds. The text decoder already set both fields for NULL cells; decode_binary now mirrors that structure. Also drops the redundant count param from SQLDataCell::to_js_object: both callers pass cells.len(), so derive it from the slice.
There was a problem hiding this comment.
All prior feedback addressed (including the decode_binary NULL-index fix + regression test) and nothing new from this pass — deferring only because this is a ~600-line change across the per-row decode hot path for both drivers plus a behavioral fix, which is more than I'll auto-approve.
Extended reasoning...
Overview
This PR adds 12 #[inline] named constructors on SQLDataCell and replaces ~110 hand-rolled SQLDataCell { tag: ..., value: ..., ..Default::default() } literals across src/sql_jsc/postgres/DataCell.rs and src/sql_jsc/mysql/protocol/ResultSet.rs. It also adds a shared to_js_object wrapper used by both Putter::to_js and Row::to_js, refactors the MYSQL_TYPE_{SHORT,LONG,LONGLONG} arms of Value::from_js into generic validate_int/validate_bigint helpers driven by bun_core::Integer, and — going beyond pure refactoring — fixes a pre-existing bug where decode_binary's is_null branch continue;d past the index/is_indexed_column assignments, with a new mock-server regression test (test/js/sql/sql-mysql-binary-null-indexed.test.ts). Net −327 lines.
Security risks
None identified. No auth, crypto, permissions, or untrusted-input parsing surface is changed. The refactor preserves free_value semantics (every replaced Tag::String/Tag::Json literal already set free_value: 1, matching string()/json()), and to_js_object now derives the cell count from the slice length, eliminating a (ptr, separate-len) footgun at the FFI boundary rather than introducing one.
Level of scrutiny
Medium-high. The bulk of the diff is mechanical and individually verifiable, but it sits in the per-row decode hot path that every Postgres/MySQL result flows through, the cells cross the Rust↔C++ FFI boundary into JSC__constructObjectFromDataCell, and one hunk (decode_binary) is a real control-flow / behavioral change rather than a literal-for-constructor swap. That combination puts it past my auto-approve threshold even though I found nothing wrong with it.
Other factors
I've iterated on this PR three times; every comment (the leftover .as_deref(), the unmigrated mysql Row::to_js, the duplicate clone_wtf_string_or_null, the redundant count param, and the pre-existing decode_binary NULL-cell index bug) has been addressed and resolved, the last with a dedicated test. The current bug-hunting pass came back clean. CodeRabbit also had no actionable comments. No CODEOWNERS entry covers src/sql_jsc/. The MySQLValue.rs helper refactor relies on T::MIN_I128/T::MAX_I128/T::ZERO reproducing the previously hard-coded bounds (including min: 0 for u64), which holds for bun_core::Integer, but is one more thing a human glance would confirm.
Completes the migration started in 0967443: replaces the remaining ~25 hand-rolled SQLDataCell struct literals in DecodeBinaryValue.rs with the named constructors, and deletes the third clone-utf8 helper (clone_utf8_wtf_impl) plus the now-unused Tag/Value import aliases.
There was a problem hiding this comment.
All five of my earlier review points are addressed and I found nothing new on 50739b9 — the constructor migrations look field-for-field equivalent and the decode_binary NULL-index fix is covered by the new test — but given the size (~600 lines across the row-decode hot path, FFI-adjacent #[repr(C)] struct) plus the one real behavioral change, this is worth a quick human pass before merge.
Extended reasoning...
Overview
This PR adds 12 named SQLDataCell constructors (null, int4, uint4, int8, uint8, float8, bool_, date, date_with_tz, string, json, plus a shared to_js_object wrapper) and replaces ~110 hand-rolled SQLDataCell { tag, value, free_value, ..Default::default() } literals across src/sql_jsc/postgres/DataCell.rs, src/sql_jsc/mysql/protocol/ResultSet.rs, and src/sql_jsc/mysql/protocol/DecodeBinaryValue.rs. It also extracts validate_int/validate_bigint generics in MySQLValue.rs, and fixes a pre-existing bug in decode_binary where the is_null branch continue;d before assigning index/is_indexed_column, with a new mock-server regression test.
I reviewed this across five iterations; every point I raised (.as_deref() leftover, to_js_object second caller, duplicate clone_wtf_string_or_null, redundant count param, unmigrated DecodeBinaryValue.rs) was addressed in 53e4076..50739b9, and the bug-hunting system found nothing on the final commit.
Security risks
None identified. The changes are internal to SQL row decoding; no auth, crypto, permissions, or untrusted-input parsing surface is altered. The #[repr(C)] layout of SQLDataCell is unchanged (constructors use ..Default::default() exactly as the literals did), so the FFI boundary with SQLClient.cpp is preserved.
Level of scrutiny
Moderate-to-high. ~95% of the diff is purely mechanical (literal → constructor) and I spot-checked every constructor against the literals it replaces — including the few sites where the old code skipped the is_empty() check (numeric, time/timetz, "00:00:00", b"\x08") and confirmed the new null-pointer-on-empty path is either unreachable or renders identically in C++. But this is the per-row decode hot path for both Postgres and MySQL, the touched struct crosses FFI to C++, and the decode_binary restructuring is a real control-flow change (not a rename). That combination puts it outside the "obvious mechanical change" bucket I'd auto-approve.
Other factors
- All inline review threads are resolved; no outstanding human reviewer comments.
- PR description reports
cargo check/clippy clean and 37 mock-server decode tests passing; the newsql-mysql-binary-null-indexed.test.tsexercises both protocols. - The
MySQLValue.rsint_range<T>refactor relies onbun_core::Integer::{MIN_I128, MAX_I128, ZERO}matching the previously hard-coded bounds; that's straightforward but worth a glance from someone familiar with that trait. - coderabbit suggested cirospaciari as reviewer, which seems right given the area.
…k server Follows the sql-mysql-column-name-digits.test.ts pattern: describeWithContainer against mysql_plain in CI, native MariaDB fallback (starts mysqld_safe + provisions a TCP user over the root unix socket) otherwise. Verified fail-before/pass-after against MariaDB 11.8.
The native-MariaDB fallback (mysqld_safe startup + unix-socket user provisioning) was copy-pasted between sql-mysql-column-name-digits.test.ts and sql-mysql-binary-null-indexed.test.ts. Extract to test/js/sql/mysql-local-harness.ts and call from both.
There was a problem hiding this comment.
All prior review feedback addressed (07d66dc extracts the shared ensureLocalMySQL harness — thanks). Nothing further from me; given this rewrites ~135 sites on the row-decode hot path across both SQL backends and folds in the decode_binary NULL-index behavioral fix, worth a human pass before merge.
Extended reasoning...
Overview
This PR adds 12 named SQLDataCell constructors and replaces ~135 hand-rolled #[repr(C)] struct literals across the Postgres and MySQL row decoders (postgres/DataCell.rs, mysql/protocol/ResultSet.rs, mysql/protocol/DecodeBinaryValue.rs), adds a shared to_js_object wrapper, consolidates three duplicate clone-utf8 helpers into one, refactors MySQLValue.rs integer validation into generic validate_int/validate_bigint helpers backed by bun_core::Integer::{MIN_I128, MAX_I128, ZERO}, fixes a pre-existing decode_binary bug where NULL cells skipped index/is_indexed_column assignment, adds a real-MySQL regression test for that fix, and extracts a shared mysql-local-harness.ts for the two non-docker MySQL tests. Net −294 lines of src across 8 files.
Security risks
None identified. The change is internal to SQL result decoding; no new untrusted-input parsing surface, no auth/crypto/permissions, no new FFI signatures. The #[repr(C)] SQLDataCell layout and Default impl are unchanged, so the C++ ABI contract with JSC__constructObjectFromDataCell (SQLClient.cpp) is preserved.
Level of scrutiny
High. Every Postgres and MySQL result row flows through these decoders, the cells cross FFI to C++ for object construction, and the PR bundles a behavioral fix (decode_binary NULL placement) alongside the refactor. I spot-checked the constructors against the literals they replace (tag, active union member, free_value) and verified the bun_core::Integer trait constants (<$t>::MIN as i128 / <$t>::MAX as i128 / 0) match the hardcoded IntegerRange bounds they replace in MySQLValue.rs — all semantically identical. But the surface area is large enough that a maintainer who owns this path should sign off.
Other factors
Six rounds of bot review feedback have all been applied (the last, extracting ensureLocalMySQL, landed in 07d66dc — HEAD). The bug-hunting system found nothing on the current state. The behavioral fix has a regression test against real MySQL/MariaDB that fails on main and passes here. No CODEOWNERS entry covers src/sql_jsc/. alii has been engaged on the PR (adopted it, requested the real-server test) but hasn't yet reviewed the final state.
Drop the custom mysql-local-harness.ts and the if(isDockerEnabled()) split. describeWithContainer already handles docker-compose, the CI coordinator, the BUN_TEST_SERVICE_mysql_plain env override, and auto-skip via describe.todo when none are available, matching sql-mysql.helpers.test.ts / sql-mysql.auth.test.ts / sql-mysql.transactions.test.ts.
What this does
Extracts the
SQLDataCelltag-constructor work from #31664 (closed). Adds 12 named constructors (SQLDataCell::null(),int4(v),uint4(v),int8(v),float8(v),bool_(v),date(v),date_with_tz(v),string(s),json(s),raw(d),uint8(v)) and replaces ~135 hand-rolledSQLDataCell { tag: Tag::X, value: SQLDataCellValue { x: v }, free_value: 0, ..Default::default() }literals across the postgres and mysql row decoders (DataCell.rs,ResultSet.rs,DecodeBinaryValue.rs). Adds a sharedto_js_objecthelper used by bothPutter::to_jsandRow::to_js. Deletes three near-identical private clone-utf8 helpers in favour of one. Net −294 lines of src.Row-decode hot path; every constructor verified field-for-field identical (
tag, activevaluemember,free_value) to the literal it replaces.Default::default()is unchanged from main ({Null, {null:0}, 0, 0, 0}) so every..Default::default()expands the same.Also fixes a pre-existing bug the refactor surfaced in
decode_binary: theis_nullbranch didcontinue;before assigningindex/is_indexed_column, so a NULL on a digit-named column over the binary protocol landed at object index 0 instead of the column's numeric name (and hitASSERT(cell.isIndexedColumn())in debug).decode_binarynow mirrorsdecode_text.Commits:
origin/ali/sql-jsc-cleanup(sql: deduplicate and remove dead code across the MySQL/Postgres drivers #31664)dedupe_columns(sql_jsc: extract shared query/connection ctor-arg parsing across postgres and mysql #32135) and theIntoOptionalDatatrait +raw()signature the cherry-pick clobbered; strips staleTODO(port)/PORT NOTEcommentsRow::to_jsthrough the sharedto_js_object; drop redundant.as_deref()parse_value_and_set_cell/decode_text/decode_binaryto the named constructors; delete duplicateclone_wtf_string_or_nullhelperdecode_binaryNULL cellindex/is_indexed_column; drop redundantcountparam fromto_js_objectdecode_binary_valueto the named constructors; delete thirdclone_utf8_wtf_implhelperVerification
cargo check -p bun_sql_jscandcargo clippy --no-deps: cleansql-mysql-binary-null-indexed.test.ts(new): fails on main ({0: null, 2: 42}), passes with fix ({2: 42, 5: null, 7: null})postgres-binary-numeric,postgres-binary-array-bounds,sql-mysql-mediumint,sql-mysql-raw-length-prefix,postgres-multi-statement-fieldssql-mysql-datetime-roundtripmock-server TZ variants pass (text-protocol DATE/DATETIME path)Noted (pre-existing, not introduced)
Array::allocated_slice()(SQLDataCell.rs:123) has no callers; the diff added a CAUTION comment about its uninit-spare-capacity hazard rather than deleting itTODOcomments in the touched files preserved verbatim