Rewire mssql-py-core onto the reactor-free sync core (opt-in sync cursor) - #205
Draft
Saurabh Singh (saurabh500) wants to merge 1 commit into
Conversation
Saurabh Singh (saurabh500)
force-pushed
the
dev/saurabh/rewire-py-core-sync-async-cursor-l6
branch
from
August 10, 2026 05:31
c08aea3 to
d9dba58
Compare
Saurabh Singh (saurabh500)
force-pushed
the
dev/saurabh/rewire-py-core-sync-async-cursor-l6
branch
2 times, most recently
from
August 10, 2026 05:41
a0d95c0 to
c08aea3
Compare
Saurabh Singh (saurabh500)
force-pushed
the
dev/saurabh/rewire-py-core-sync-async-cursor-l6
branch
2 times, most recently
from
August 10, 2026 05:56
a8b9f5a to
c08aea3
Compare
Saurabh Singh (saurabh500)
marked this pull request as ready for review
August 10, 2026 06:38
Copilot started reviewing on behalf of
Saurabh Singh (saurabh500)
August 10, 2026 06:39
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Rewires Python bindings to share the async and reactor-free sync TDS core through an opt-in synchronous cursor.
Changes:
- Adds the shared async/sync client state machine.
- Introduces
PyCoreSyncCursorand row-count support. - Adds live-server and mock-server coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
mssql-py-core/src/pyclient.rs |
Implements shared client transitions and operations. |
mssql-py-core/src/sync_cursor.rs |
Adds the reactor-free sync cursor. |
mssql-py-core/src/cursor.rs |
Adopts the shared client and exposes rowcount. |
mssql-py-core/src/connection.rs |
Creates shared clients and sync cursors. |
mssql-py-core/src/lib.rs |
Registers the new Python class. |
mssql-py-core/tests/test_sync_cursor.py |
Adds live SQL Server tests. |
mssql-py-core/tests/rs-only-tests/test_sync_async_cursor_mock.py |
Adds mock-server path and transition tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
|
||
| /// Python synchronous Cursor class driving the reactor-free sync core. | ||
| #[pyclass] | ||
| pub struct PyCoreSyncCursor { |
Comment on lines
+192
to
+199
| pub(crate) fn is_on_rows(cell: &SharedClient) -> Result<bool, PyErr> { | ||
| let guard = cell.lock().map_err(|_| poisoned())?; | ||
| Ok(match &*guard { | ||
| PyClient::Async(c) => c.on_rows(), | ||
| PyClient::Sync(s) => !s.get_metadata().is_empty(), | ||
| PyClient::Transitioning | PyClient::Dead(_) => false, | ||
| }) | ||
| } |
Comment on lines
+108
to
+111
| bad_query = ( | ||
| "SELECT CAST(value AS INT) AS n " | ||
| "FROM (VALUES ('1'), ('2'), ('notanumber')) AS t(value)" | ||
| ) |
Comment on lines
+264
to
+267
| try: | ||
| conn = _connect(ctx) | ||
| except Exception as exc: # noqa: BLE001 - env-sensitive TLS handshake | ||
| pytest.skip(f"TLS connect to mock unavailable in this environment: {exc}") |
| conn = mssql_py_core.PyCoreConnection(client_context) | ||
| try: | ||
| cursor = conn.sync_cursor() | ||
| cursor.execute("SELECT 1 AS value UNION ALL SELECT 2 UNION ALL SELECT 3") |
Saurabh Singh (saurabh500)
marked this pull request as draft
August 10, 2026 06:49
Saurabh Singh (saurabh500)
marked this pull request as ready for review
August 10, 2026 07:40
Saurabh Singh (saurabh500)
marked this pull request as draft
August 10, 2026 13:28
…ync cursors Expose both a first-class sync cursor and the existing async cursor from one shared sans-I/O protocol core. Replace the connection's Arc<Mutex<TdsClient>> cell with Arc<std::sync::Mutex<PyClient>>, where PyClient flips in place between the async TdsClient edge and the reactor-free TdsSyncClient edge (Transitioning sentinel between take and store; Dead on unrecoverable flip/revert). Cursors clone the Arc to the cell, so ownership-by-value flips never need Arc::try_unwrap. The new PyCoreSyncCursor flips to the sync edge only after execute resolves metadata with rows pending, pulls rows via TdsSyncClient::next_row_into with no block_on, and reverts to async before any control-plane op or on a fetch error (recover via into_async drain). TLS connections report NotEligible, so the sync cursor transparently falls back to the async block_on path byte-identically. The async PyCoreCursor keeps its public Python API and never flips. rowcount is an additive read-only property sourced from last_rows_affected() on the async edge before any flip, so sync == async by construction. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e2c378f8-3ba1-4b48-9ebe-5a4ea2bd2761
Saurabh Singh (saurabh500)
force-pushed
the
dev/saurabh/rewire-py-core-sync-async-cursor-l6
branch
from
August 10, 2026 21:17
c08aea3 to
452caa9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
L6 — Rewire
mssql-py-coreonto the reactor-free sync TDS core.Stacked on the frozen L5 tip (
dev/saurabh/rewire-odbc-sync-core-l5@5c09f0e4). Append-only single commit; base = L5 branch, notmain. Scope ismssql-py-coreonly — all frozen crates (mssql-tds,mssql-odbc,mssql-mock-tds,mssql-js,mssql-tds-cli) are empty-diff.What this does
The Python bindings now share ONE protocol core across two cursor surfaces via a flip-in-place ownership cell:
enum PyClient { Async(TdsClient) | Sync(TdsSyncClient) | Transitioning | Dead(String) }behindArc<std::sync::Mutex<PyClient>>(pyclient.rs). TheArcpoints at the cell, soArc::try_unwrapis never used — cursors keep their clone and match the enum under the lock.std::mem::replace(&mut *guard, Transitioning)→ callinto_sync/into_asyncon the moved-out value → store the result variant back.Transitioningis a sentinel that only exists between take and store (no early-return while transitioning); an unrecoverable flip storesDead(String)and surfacesErr.into_sync()runs insidehandle.enter()so the runtime handle is captured for the later revert (a nakedinto_sync()off-runtime would captureNoneand poisoninto_async).Asyncat end-of-rows / beforeclose_query/ before any control-plane op, mirroring L5.Cursor surfaces (mode A for this layer)
PyCoreSyncCursor(new): reactor-free row-pull. Plaintext connections flip to theSync(TdsSyncClient)arm and drivenext_row_intowith noblock_on; TLS/non-eligible transports fall back to theblock_on-over-async path, byte-identically.PyCoreCursor(existing): kept exactly as-is —block_on-over-async backed. Public Python API unchanged — no regression. This is not a first-class async cursor: itsblock_onblocks the Python event loop. The genuine coroutine (async def/ awaitable) cursor is L7, not this layer.Connection.cursor()/Connection.sync_cursor()expose the two surfaces.execute/ COLMETADATA / advance / close / DML / bulkcopy / auth all stay async (control-plane rule); the sync flip is for the pure SELECT row-pull hot loop only, per result set.rowcount
Additive
rowcountcaptured fromlast_rows_affected()pre-flip on the async arm (faithful-count parity contract, populated by the sharedapply_row_read_tokencount_map at L4 B′/L5). No independent count logic.Scope decision (mode A) — real coroutine async cursor is L7
This layer ships mode A: sync arm reactor-free + the existing cursor retained as
block_on-over-async. A genuinely first-class coroutine async cursor (pyo3-async-runtimes/future_into_pydrivingnext_row_into().awaiton theAsyncarm, event-loop driven, non-blocking) is a large net-new public surface and lands as its own reviewable layer L7 stacked on top of this one. Nopyo3-async-runtimesdependency is added here;Cargo.lockis unchanged.Build / test path
mssql-py-coreis edition 2024 and excluded from the cargo workspace, so the plaincargo b*aliases do NOT cover it — validated viascripts\bfmt.ps1+scripts\bclippy.ps1(both cover py-core) +maturin+ the Python suite through the existingconftest.pyfixtures.Tests
block_on-over-async path) — env-skips locally on the pre-existing mock-TLS-on-Windows timeout; CI-validated.block_on-backed cursor expose the same surface.fetchoneresults identical to theblock_onpath.into_asyncdrain, connection reused (validated live: SQL 245 conversion error mid-stream).Gates:
scripts\bfmt.ps1clean,scripts\bclippy.ps1(-D warnings) clean (workspace + py-core),maturin developbuildsmssql_py_core+mssql_mock_tds, mock cursor suite = 10 passed / 1 env-skip.Checklist
cargo bfmtpassescargo bclippypassescargo btestpassesStack governance: DRAFT — the whole stack flips to ready-for-review together, bottom-to-top, after parent sign-off.
Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com
Related work item / issue: Tracked as part of the sans-I/O native stack (#192)