PERF: Optimize checked temporal fetch construction - #795
Jahnvi Thakkar (jahnvi480) wants to merge 7 commits into
Conversation
Use direct CPython date/time/datetime construction for exact cached standard types, preserving substituted constructors and exception behavior. Cover row-wise and batch fetch contracts in isolated subprocesses. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Native extension changes span multiple fetch paths, with limited platform and validation coverage.
Pull request overview
Optimizes native SQL temporal fetch construction while preserving custom constructors and conversion behavior.
Changes:
- Adds checked datetime construction helpers.
- Integrates them into six temporal fetch paths.
- Adds regression tests and changelog documentation.
File summaries
| File | Reviewed changes |
|---|---|
tests/test_038_fetch_temporal.py |
Temporal parity, constructor, exception, and fetch API coverage |
mssql_python/pybind/fetch_temporal.hpp |
Translation-unit-local checked construction helpers |
mssql_python/pybind/ddbc_bindings.cpp |
Integration into row-wise and batch temporal fetch paths |
CHANGELOG.md |
Documents the optimization and preserved behavior |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/pybind/ddbc_bindings.cppLines 4429-4437 4429 case SQL_SS_TIME2: {
4430 const SQL_SS_TIME2_STRUCT& t2 = buffers.timeBuffers[col - 1][i];
4431 py::object timeObj =
4432 FetchTemporal::time(t2.hour, t2.minute, t2.second, t2.fraction / 1000);
! 4433 PyList_SET_ITEM(row, col - 1, timeObj.release().ptr());
4434 break;
4435 }
4436 case SQL_SS_TIMESTAMPOFFSET: {
4437 SQLULEN rowIdx = i;mssql_python/pybind/fetch_temporal.hppLines 13-23 13
14 // datetime.h keeps PyDateTimeAPI per translation unit, so these helpers must too.
15 static inline void ensure_datetime_api() {
16 if (PyDateTimeAPI == nullptr) {
! 17 PyDateTime_IMPORT;
! 18 if (PyDateTimeAPI == nullptr) throw py::error_already_set();
! 19 }
20 }
21
22 static inline py::object date(int year, int month, int day) {
23 ensure_datetime_api();📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.performance_counter.hpp: 0.7%
mssql_python.pybind.logger_bridge.cpp: 57.9%
mssql_python.pybind.ddbc_bindings.h: 64.1%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 78.4%
mssql_python.pybind.connection.connection_pool.cpp: 82.3%
mssql_python.pybind.connection.connection.cpp: 82.5%
mssql_python.row.py: 83.4%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%🔗 Quick Links
|
PR Performance ReportNo consistent slowdowns detected across all 2 environments. Coverage: 2 of 2 environments completed. Advisory result; does not block merging.
Affected phases and call countsPhase times are inclusive diagnostics and must not be added together. They identify where measured time changed, not why it changed. No affected phases or call-count changes were recorded. All database tasks and timingsUnix / SQL Server 2022
Unix / SQL Server 2025
Build, commits and measurement detailsPR head:
A consistent change requires more than 20% median paired movement, at least 1 ms between the median runtimes, and at least 80% of pairs exceeding the relative threshold in the same direction. A slowdown without enough pair agreement is reported as inconsistent. The displayed change is the median of paired before-and-after ratios. It is not recalculated from the two displayed median runtimes. Both revisions use profiling-enabled builds on the same agent and database, with alternating order and discarded warmups. Results are diagnostic and do not represent production-wheel latency. Raw samples and logs are attached to the ADO run as |
There was a problem hiding this comment.
🟡 Changes recommended
The isolated Linux wheel-validation job fails on a source-tree-relative module path assertion.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
Preserve strict source-snapshot checks while allowing isolated wheel tests to validate the native module against its imported package. Add focused source and installed layout regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Work Item / Issue Reference
Summary
Reduce Python object-construction overhead when fetching DATE, TIME, and TIMESTAMP values. Previously, native temporal fields were converted into Python arguments and passed through a generic call to an already-cached constructor. Repeated imports were not the bottleneck.
The new helper uses checked CPython construction APIs when the cached constructor is the exact standard type; substituted constructors retain the original call. Six row-wise/batch conversion sites change. Field validation and final object allocation remain. NULLs, precision, timezone/fold, ownership, and exception behavior are preserved; DATETIMEOFFSET, UUID, Decimal, and text are untouched by this PR.
flowchart LR A["Native temporal fields after NULL checks"] --> B["Before: Python arguments and generic cached-constructor call"] A --> C{"After: exact standard type?"} C -->|"Yes"| D["Direct checked CPython construction"] C -->|"No: original fallback"| B B --> E["Validated Python object in result row"] D --> EFresh measurements
Temporal cases contain NULLs every seventh row. Pure cases have eight temporal columns; the row-wise case adds one harmless MAX column. Mixed has DATE/TIME/DATETIME2/DATETIMEOFFSET; narrow is an unchanged int/text/float control.
fetchmany(1000)fetchmany(1000)fetchmany(1000)fetchall()/ all remainingfetchone()/ 1fetchmany(1000)Method/build: September 21 Docker Linux x64; Python 3.13.15, pybind11 3.0.1, GCC 12.2 Release
-O3 -DNDEBUG, profiling OFF, SQL Server 16.0.4225.2, ODBC 18.6.2.1. Mainc963ee1eversus PR5aaa6aae: 10 counterbalanced pairs × 5 samples × 14 cases, totaling 1,400 validated drains. Reductions are ratios of medians, excluding execute/validation; no outlier removal or retries. Fallback checks observed three callbacks per temporal type with 3/4/7 positional arguments—not optimized-path or allocation counts.Limits: identical-build A/A calibration was noisy (speed-ratio interval 0.882×–1.345×). Mixed
fetchoneand bounded DATEfetchallremain inconclusive; unchanged narrow many/all had negative point estimates with intervals spanning zero. These are scoped bulk-temporal gains, not universal speedups or a no-regression guarantee.Both builds passed six fresh-process compatibility modes covering boundaries, NULLs, types, substitutions, exceptions, recovery, and both fetch paths. No full-suite or all-OS success is claimed. Complete samples, intervals, provenance, and historical limitations remain in retained local evidence.