refactor: resolve parameters once and serialize them through a write contract - #1774
refactor: resolve parameters once and serialize them through a write contract#1774arthurschreiber wants to merge 6 commits into
Conversation
…contract Splits parameter handling into two phases. `resolveParameter` validates the value and determines the declaration facts (length, precision, scale, collation) once, before a request is sent; `writeTypeInfo` and `writeValue` serialize the resolved parameter into a `WritableTrackingBuffer`. Types can implement `resolve`, `writeTypeInfo` and `writeValue` natively; the helpers adapt everything else from the existing `validate` / `resolve*` / `generate*` methods, so types can be migrated one at a time. Int, NVarChar and VarBinary are migrated. `Request.validateParameters` now resolves the request's parameters and keeps the result; the RPC payload takes resolved parameters and only serializes. `Connection.execSql`, `callProcedure`, `prepare`, `unprepare`, `execute` and the Always Encrypted metadata request build their payloads from resolved parameters. Bulk load writes column metadata and row values through the same helpers. Two behaviour changes come with the shared resolution: - Lengths are resolved for every type that can resolve one, not only for type ids matching the legacy variable-length bit pattern (the fix proposed in #1771). - Errors thrown while writing a parameter's TYPE_INFO are wrapped in the same `InputError` as errors from writing its value (the RPC half of #1772). A new unit test serializes 40 parameter cases across every type, on TDS 7.4 and 7.2, with and without a collation, through the new payload and through an inline copy of the previous serialization, and asserts the bytes are identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
ReviewSolid refactor — the single One thing worth fixing before merge: bulk load isn't actually part of the #1771 fixThe PR says "#1771 can be closed once this lands," but #1771 explicitly scopes the fix to both This PR only changes the RPC path. if ((type.id & 0x30) === 0x20) {
if (column.length == null && type.resolveLength) {
column.length = type.resolveLength(column);
}
}So a bulk load column using a modern-id type with a resolvable length (e.g. the upcoming Recommend either applying the same unconditional- Minor notes, non-blocking
Not verifiedI wasn't able to run |
Constraint names on temporary tables are unique per database, so the
named constraint collided when two CI jobs ran this test against the
same Azure database at the same time ("There is already an object named
'chk_id' in the database").
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug
|
One Azure job on c6342d9 ( Cause, reproduced locally against SQL Server 2022 with two sessions: the test creates Fixed in the test by leaving the constraint unnamed, which gets a unique generated name; pushed as the next commit. Generated by Claude Code |
- `BulkLoad.addColumn` no longer gates length resolution on the legacy variable-length type id bit pattern, so the RPC and bulk load paths agree and #1771 is covered in full. - `resolveParameter` treats an explicitly specified length, precision or scale of 0 as specified instead of falling through to the type's resolver. Every existing resolver re-checked for an explicit value itself, so this changes no bytes for existing types; it removes the trap for a future type whose resolver does not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug
|
Addressed the review:
Generated by Claude Code |
|
Nicely scoped refactor — the One behavior change that isn't called out in the "Behaviour changes" section, and doesn't look intentional:
Before this PR, every call site that invoked Five types key their out-of-range validation on if (options && options.useUTC) {
year = value.getUTCFullYear(); ...
} else {
year = value.getFullYear(); ...
}Since This isn't mentioned in the PR description's two enumerated behaviour changes, and none of the new tests exercise it — the date values used (e.g.
Everything else looked solid:
🤖 Generated with Claude Code |
|
Review I read through the full diff ( Code quality / design
Minor observations (non-blocking)
Bugs Test coverage Security / performance |
`resolveParameter` calls `type.validate(value, collation)` without the connection options, as every caller did before. Passing the options would activate the `useUTC`-dependent range checks in the date and time types, which have never run; that is a behaviour change to make on its own. A unit test pins the call shape. `Connection.resolveParameter` is renamed `resolveRequestParameter` so it is not misread as recursion into the free function it wraps. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug
|
Good catch on Also renamed Generated by Claude Code |
ReviewI read through the diff in full (data-type.ts, the Int/NVarChar/VarBinary write methods, request.ts, connection.ts, bulk-load.ts, rpcrequest-payload.ts, and the new tests) and traced the two documented behaviour changes against every existing Code quality / design
Correctness
Minor observations (not blocking)
Test coverage
Performance / security
Overall this is a well-scoped, well-tested refactor that keeps byte-for-byte compatibility while opening the door for streaming TVP support. Nice work. |
|
On the two minor notes from the latest review:
Generated by Claude Code |
…quest The Always Encrypted metadata request is built from raw parameters rather than a `Request` that went through `validateParameters`, so the parameters are resolved inline. A comment says so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug
ReviewThis is a large, carefully-scoped refactor, and it reads that way: the PR description states the exact behavior changes up front, and the new tests ( Correctness checks I did
Minor observations (not blockers)
Test coverageGood — byte-equivalence tests for the three natively-migrated types across null/empty/short/ Didn't find security concerns — this is wire-serialization code operating on already-validated/typed values, no injection surface here. I wasn't able to run the suite in this sandbox (no network access to install dependencies), so this is a static review; the PR description's own numbers (498 unit tests, full integration suite passing, lint/typecheck clean) cover that ground. |
|
The Corroboration on the same commit:
There's nothing to fix in the PR — re-running the single failed job clears it. This integration can't trigger a re-run (the API returns 403), so a maintainer re-run or the next push will resolve it. Keeping the PR watched until CI is green. Generated by Claude Code |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1774 +/- ##
==========================================
+ Coverage 81.89% 82.20% +0.30%
==========================================
Files 92 92
Lines 4950 5019 +69
Branches 933 954 +21
==========================================
+ Hits 4054 4126 +72
+ Misses 601 599 -2
+ Partials 295 294 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c5d24ccc4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ReviewThis is a well-executed refactor. The Correctness
Minor observations (non-blocking)
Test coverageExcellent — SecurityNo concerns — this is internal wire-format serialization logic with no new external input handling; validation behavior for user-supplied values is preserved or made stricter (never weaker). Nothing here blocks merging; the minor points above are observations for awareness rather than requested changes. |
Problem
A parameter's handling is spread over five
DataTypemethods and two call sites that each combine them differently.validateruns inRequest.validateParameters;resolveLength/resolvePrecision/resolveScalerun inside the RPC payload while the request is being written;generateTypeInfo,generateParameterLengthandgenerateParameterDataproduce a list of small buffers per parameter. Bulk load repeats the resolution logic with slightly different rules inaddColumn. There is no single place that says what a parameter's declaration is, and no way for a type to write its bytes directly into the shared buffer that #1773 introduced.This is the second of the series described in #1773. The third (streaming table-valued parameters) needs parameters resolved before the request starts and types that write into a buffer; this PR provides both.
Change
DataTypegains three optional methods, anddata-type.tsthree helpers that adapt types which do not implement them:resolve(parameter, collation, options)→ParameterData: validate the value and determine length, precision, scale and collation.resolveParameterfalls back tovalidateand theresolve*methods; an explicitly specified fact wins, including an explicit0.writeTypeInfo(buffer, data, options): write the TYPE_INFO.writeTypeInfofalls back togenerateTypeInfo.writeValue(buffer, data, options): write the length prefix and data.writeValuefalls back togenerateParameterLengthandgenerateParameterData.Int,NVarCharandVarBinaryimplement the write methods natively; every other type goes through the adapters unchanged, so migration can continue one type at a time.Resolution happens once, up front:
Request.validateParameters(collation, options)resolves every parameter and keeps the result inrequest.resolvedParameters. It still writes the validated value back toparameter.value, whichmakeParamsParameterrelies on.RpcRequestPayloadtakesResolvedParameter[](name, output flag, type, resolved data) and only serializes. It writes each parameter's header, TYPE_INFO and value into oneWritableTrackingBufferand yields its chunks, so a large value written by reference stays by reference.Connection.execSql,callProcedure,prepare,unprepare,executeand the Always Encryptedsp_describe_parameter_encryptionrequest build their payloads from resolved parameters.executeresolves each parameter with the value supplied for that execution, as it validated before. The wrapper parameters these methods add (statement,params,handle,stmt,tsql) now pass throughvalidatelike every other parameter; their values are always well-formed strings and integers, so nothing observable changes.writeTypeInfoand each row's cells throughwriteValueinto one buffer per row. Its error handling is unchanged.validateis still called asvalidate(value, collation), without the connection options, as every caller did before. TheuseUTC-dependent range checks in the date and time types' validators have therefore never been active; enabling them is a behaviour change to make deliberately, not as a side effect here. A unit test pins the call shape.Behaviour changes
All fall out of sharing one resolution path; all are covered by tests.
BulkLoad.addColumn, not only for type ids matching(id & 0x30) === 0x20. For every existing type this is byte-identical (all types withresolveLengthhave legacy ids); it fixes the case fix: resolve parameter lengths for all types that can resolve one #1771 describes for TDS 7.2+ ids such asVECTORTYPEandJSONTYPE, in both places fix: resolve parameter lengths for all types that can resolve one #1771 changes. fix: resolve parameter lengths for all types that can resolve one #1771 can be closed once this lands.0for length, precision or scale is kept rather than treated as unspecified. Every existingresolve*implementation already re-checks for an explicit value, so no bytes change for existing types; this removes the trap for a future type whose resolver does not.InputErrornaming the parameter that an error from its value did. This is the RPC half of fix: wrap bulk load serialization errors in InputError #1772; the bulk load half is not included.Validation
test/unit/rpcrequest-payload-test.tsserializes 40 parameter cases across every input type (int, string, binary, decimal, date/time, GUID, TVP, output and unnamed parameters, null values,maxvalues over 8000 bytes) on TDS 7.4 and 7.2, with and without a collation, through the new payload and through an inline copy of the previous serialization algorithm, and asserts the bytes are identical. It also checks that a 1 MB value is passed through by reference and that a failing type surfaces asInputError.test/unit/parameter-contract-test.tscoversresolveParameter(explicit facts win, explicit zero kept, nativeresolvedelegation, modern-id lengths, validation errors, thevalidatecall shape) and byte equivalence of the three native types against their legacy methods across 19 value/length combinations.test/unit/bulk-load-test.tscovers modern-id length resolution inaddColumn.should not leave any dangling sockets after connection timeout.checkConstraintsintegration test no longer names its CHECK constraint. Constraint names on temp tables are unique per database, so the named one collided when two Azure CI jobs ran the test concurrently.Measurements
Serialization only, same machine.
benchmarks/parameters/scalar-params.jsis new (20 scalar parameters per request, resolved once per request asvalidateParametersdoes).Within noise of master: this PR is a restructuring, the throughput gain over 20.0.0 is #1773's.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Fxv5h4UMCGJEpjgCKcAxug