Remove ResultSet async_trait boxing - #291
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89c55fda-9c7a-4785-856d-e7e81c45f34f
There was a problem hiding this comment.
Pull request overview
Removes async_trait boxing from ResultSet, using native statically dispatched Send futures while preserving existing behavior.
Changes:
- Replaces boxed async trait methods with RPITIT futures.
- Delegates directly to native
TdsClientfutures. - Adds future-size and behavioral regression coverage.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89c55fda-9c7a-4785-856d-e7e81c45f34f
David Engel (David-Engel)
left a comment
There was a problem hiding this comment.
Summary
Converts ResultSet from #[async_trait] to native RPITIT futures with explicit + Send, and makes the TdsClient impl forward the existing native inherent futures. The change is correct and behavior-preserving. No blocking issues.
Reviewed on a dedicated worktree at head 418c3ab5, diffed against the stacked base dev/saurabh/remove-row-path-async-traits. Verified locally:
cargo clippy --workspace --all-features --all-targets— cleanmssql-py-core(excluded frombclippy) —cargo check --all-targetscleancargo test -p mssql-tds --lib— 1,659 passed, only the 4 known certificate-fixture failurescargo test -p mssql-tds --doc— 9 failures, all pre-existing private-module doctests unrelated to this PR
Claims I confirmed
Behavior parity on next_row. Dropping the outer maybe_has_unread_rows() guard and abort_pending_prepare_capture() is safe: maybe_has_unread_rows() is exactly !current_result_set_has_been_read_till_end, which the inherent next_row_into already short-circuits, and every error path in that method calls abort_pending_prepare_capture() before returning. The two capture-abort tests still cover it.
No trait-object consumers. Zero dyn ResultSet, a single impl ResultSet for TdsClient, and all consumers (mssql-js, mssql-odbc, mssql-py-core, mssql-tds-cli, benches, integration tests) go through concrete TdsClient. Method-call syntax (client.next_row().await) still compiles, so the "same source shape" claim holds.
CI note (not a code finding)
coverage-report failed with "No build found for PR #291 after 30 attempts", and the ADO mssql-rs Pull request validation check is skipping — both because the PR targets the stacked branch instead of main. This is not the usual 75-minute artifact-timeout flake, and a re-run won't fix it. Net effect: this change gets no ADO validation and no 85% diff-coverage signal on its own. Worth confirming the full stack (#286 -> #287 -> this) is validated against main before merge.
Four non-blocking comments inline.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89c55fda-9c7a-4785-856d-e7e81c45f34f
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89c55fda-9c7a-4785-856d-e7e81c45f34f
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89c55fda-9c7a-4785-856d-e7e81c45f34f
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89c55fda-9c7a-4785-856d-e7e81c45f34f
Description
This focused follow-up removes
#[async_trait]from the publicResultSettrait andimpl ResultSet for TdsClient. The async methods now use native RPITIT futures with explicit+ Send; the concrete implementation returns the existing native client futures directly, avoiding per-call trait-future boxing while preserving row cleanup and end-of-set behavior.This PR is stacked on #286 (
dev/saurabh/remove-row-path-async-traits) as the new top layer of native stack #287. It does not changeTdsTransport,TdsTokenStreamReader, or transport dispatch.Public API impact
ResultSetis intentionally no longer dyn-compatible. This is a breaking object-safety change fordyn ResultSetconsumers and for external implementations using#[async_trait]. Existing concrete call sites retain the same.method(...).awaitsource shape; implementations must migrate to nativeSendfutures. Repository search found no trait-object consumers.Measurements
A temporary in-crate release harness (removed before commit) compared native
ResultSet::next_rowwith an explicitPin<Box<dyn Future + Send>>wrapper around the same native method. Each independent process used a paired/interleaved ABBA sequence over 500,000 production-shaped rows with 48INTcolumns. Across seven processes, the boxed path was 1.52%–4.64% slower, with a 3.63% median. The range is reported as measured rather than excluding noisy runs.Current debug future sizes are:
ResultSet::next_row: 4,056 BResultSet::next_row_into: 4,032 BResultSet::close: 4,376 BBoth per-row futures are guarded under the existing 4,096 B hot-path budget.
closeis larger, but it is not a per-row operation, so this change documents the effect instead of adding broad boxing.Validation
cargo bfmt— passedcargo bclippy— passedcargo btest— 2,410 passed, 366 environment failures, 11 skipped; exactly matches the current lower-layer baseline (7 missing certificate-fixture tests and 359 live-SQL tests).\scripts\bfmt.ps1— passed.\scripts\bclippy.ps1— passed, including excludedmssql-py-coremssql-js, CLI, ODBC, and benchmarks) compile through the all-features/all-targets Clippy pass; no fuzz target referencesResultSetRelated Issues
Related to #265.
Checklist
cargo bfmtpassescargo bclippypassescargo btestpasses (blocked only by the 366 environment failures matching the lower-layer baseline)