Skip to content

PERF: Optimize fetchone, fetchmany(1) and fetchval paths - #829

Open
Jahnvi Thakkar (jahnvi480) wants to merge 21 commits into
mainfrom
jahnvi/candidate-a-fetchone-column-count
Open

Jahnvi Thakkar (jahnvi480) wants to merge 21 commits into
mainfrom
jahnvi/candidate-a-fetchone-column-count

Conversation

@jahnvi480

@jahnvi480 Jahnvi Thakkar (jahnvi480) commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#48364

Not applicable; the work item above is the single reference.


Summary

  • Consolidate fetchone(), fetchmany(1) and inherited fetchval()/iterator optimization work in this existing PR.
  • Retain generation-scoped full column-count caching, separate from prefix SQLGetData metadata. Direct column-count calls remain uncached; all nine original count-cache regressions remain.
  • Share native single-row fetching and reuse successful unbinds only within a valid generation. Invalidate before binding, including Arrow/partial binds, and during cleanup. The marker does not certify row-array attributes.
  • Route only all-numeric fetchmany(1) results through SQLFetchScroll and SQLGetData, preserving eager count/name validation and row-array configuration/cleanup. Mixed INT/NVARCHAR and other types retain their existing native paths.
  • Use direct one-row wrapping only for exact built-in integer size 1, one returned row, canonical Row/factory, and no converter/UUID work. Preserve larger-request tails, substituted factories, integer subclasses and actual fetchone() overrides.
  • Keep full-row construction and all-column converter callbacks for fetchval(). Replace Python single-row phase context managers with equivalent paired start/stop instrumentation.
  • Add numeric parity, EOF, override/factory, diagnostics, generation-change, mixed-API and fault-recovery regressions, plus attribution documentation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 09:00
@github-actions github-actions Bot added the pr-size: medium Moderate update size label Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

PR Performance Report

✅ No regression detected

No consistent slowdowns detected across all 2 environments.

0 IMPROVEMENTS 0 SLOWDOWNS 2/2 ENVIRONMENTS

Coverage: 2 of 2 environments completed. Advisory result; does not block merging.

Performance diagnostics

Phase times are inclusive diagnostics and must not be added together. They identify where measured time changed, not why it changed.

Unix / SQL Server 2022

Row-by-row fetching: ddbc::SQLGetData_wrap +0.107 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (1000 -> 1 calls).
Repeated positional queries: ddbc::FetchOne_wrap +0.041 ms; py::execute::param_prep +0.010 ms; py::fetchone::cpp_call +0.009 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
Repeated named-parameter queries: py::execute::cpp_call +0.244 ms; ddbc::SQLExecute_wrap +0.235 ms; ddbc::FetchOne_wrap +0.052 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
10,000 scalar values / fetchval() (debug disabled): ddbc::SQLGetData_wrap +0.205 ms; ddbc::AppendDiagRecords::SQLGetDiagRec_call +0.004 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (10000 -> 1 calls).

Unix / SQL Server 2025

Row-by-row fetching: ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (1000 -> 1 calls).
Repeated positional queries: ddbc::SQLExecute_wrap +0.127 ms; py::execute::cpp_call +0.126 ms; ddbc::FetchOne_wrap +0.030 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
Repeated named-parameter queries: py::execute::post_execute +0.027 ms; ddbc::FetchOne_wrap +0.022 ms; py::fetchone::cpp_call +0.013 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent).
10,000 scalar values / fetchval() (debug disabled): ddbc::SQLGetData_wrap +0.066 ms; ddbc::AppendDiagRecords::SQLGetDiagRec_call +0.002 ms. Call changes: ddbc::FetchSingleRow::SQL_UNBIND (added, removed, or intermittent); ddbc::SQLNumResultCols_wrap (10000 -> 1 calls).

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 10.008 ms 10.247 ms +2.5% no signal
SELECT queries 1.038 ms 1.049 ms -0.2% no signal
Row insertion 34.676 ms 34.535 ms -0.6% no signal
Executemany inserts 156.075 ms 157.226 ms -0.0% no signal
Fetch-all queries 119.741 ms 118.886 ms -0.5% no signal
Row-by-row fetching 14.363 ms 13.349 ms -6.0% no signal
Batched row fetching 117.852 ms 116.657 ms -1.0% no signal
Transaction commit and rollback 115.123 ms 114.390 ms -0.5% no signal
Arrow row fetching 93.156 ms 94.054 ms -0.2% no signal
100,000-row insertion 439.749 ms 444.700 ms +1.8% no signal
Row fetching in batches of 100 121.929 ms 120.912 ms -1.5% no signal
Row fetching in batches of 10,000 135.388 ms 137.175 ms -1.3% no signal
Repeated positional queries 34.493 ms 34.038 ms -1.2% no signal
Repeated named-parameter queries 36.073 ms 36.465 ms -0.0% no signal
Legacy 100,000-row insertion 347.935 ms 353.604 ms +1.5% no signal
Insertion with explicit input sizes 480.720 ms 486.870 ms +0.7% no signal
Joined aggregation queries 178.616 ms 180.101 ms +1.3% no signal
Large joined-result fetching 173.782 ms 173.383 ms +0.7% no signal
1.2-million-row fetching 3414.761 ms 3408.890 ms -0.4% no signal
Common table expression queries 5.400 ms 5.381 ms -1.7% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.329 ms 1.237 ms -0.5% no signal
10,000 scalar values / fetchval() (debug disabled) 107.453 ms 97.184 ms -10.8% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 98.803 ms 97.744 ms -1.6% no signal
SELECT queries 1.057 ms 1.092 ms +3.3% no signal
Row insertion 34.552 ms 34.428 ms -0.3% no signal
Executemany inserts 151.326 ms 152.401 ms +0.7% no signal
Fetch-all queries 121.834 ms 122.856 ms +0.8% no signal
Row-by-row fetching 14.504 ms 13.146 ms -7.7% no signal
Batched row fetching 119.656 ms 120.567 ms +0.8% no signal
Transaction commit and rollback 116.543 ms 114.821 ms +0.1% no signal
Arrow row fetching 95.165 ms 94.896 ms -0.3% no signal
100,000-row insertion 451.498 ms 455.759 ms -1.5% no signal
Row fetching in batches of 100 123.635 ms 121.634 ms -1.6% no signal
Row fetching in batches of 10,000 140.801 ms 142.511 ms +0.8% no signal
Repeated positional queries 33.789 ms 33.419 ms -0.6% no signal
Repeated named-parameter queries 36.817 ms 36.260 ms -1.7% no signal
Legacy 100,000-row insertion 355.087 ms 356.918 ms +0.8% no signal
Insertion with explicit input sizes 489.383 ms 493.727 ms -0.2% no signal
Joined aggregation queries 162.462 ms 161.730 ms -0.4% no signal
Large joined-result fetching 186.203 ms 187.140 ms +0.8% no signal
1.2-million-row fetching 3485.109 ms 3511.137 ms +0.7% no signal
Common table expression queries 5.235 ms 5.420 ms +3.5% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.513 ms 1.455 ms +4.0% no signal
10,000 scalar values / fetchval() (debug disabled) 107.057 ms 96.161 ms -9.9% no signal
Build and measurement details

ADO build 181937

PR head: d533112b82f46dc813d53fa225d53d0fcfa28972
Base: 666f3cb6d23981bb23cd182ec273df10a7b2c805
Measured merge: 6a561b840a4b18dc790972000b321c2561962055

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4295.3; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5005.3; 5 paired comparisons and 1 warmup.

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.

This headline uses the original 22-task profiling-enabled diagnostics; separate OFF/OFF latency and ON/OFF route measurements, when available, are retained in the raw artifacts and are not headline inputs.

Raw samples and logs are attached to the ADO run as profiler-* artifacts.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The native hot-path change still requires live correctness and performance validation, as acknowledged by the draft description.

Review effort: Balanced
Findings: None

What changed in this PR

Introduces an experimental native cache to avoid repeated column-count queries during fetchone.

Changes:

  • Caches full column counts by metadata generation.
  • Reuses cached counts while preserving invalidation and error handling.
  • Adds nine subprocess-isolated integration scenarios.
File Description
mssql_python/​pybind/​ddbc_bindings.cpp Uses the cached count in FetchOne_wrap.
mssql_python/​pybind/​result_metadata.hpp Stores and invalidates full column counts.
tests/​test_fetch_settings_cache.py Tests reuse, invalidation, failures, and mixed fetching.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Use a separate connection for the cross-handle assertion without requiring MARS. Preserve all fetch and native call-count assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 10:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The native hot-path change remains an explicitly unvalidated draft with correctness tests and performance measurements still pending.

Review effort: Balanced
Findings: None

Copilot AI balanced review requested due to automatic review settings October 1, 2026 10:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The native cache behavior and performance impact remain unvalidated by runtime execution.

Review effort: Balanced
Findings: None

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

84%


🎯 Overall Coverage

84%


📈 Total Lines Covered: 9734 out of 11454
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/cursor.py (85.4%): Missing lines 2921,2939-2940,2952-2955,2979,3031,3069-3072,3113
  • mssql_python/pybind/ddbc_bindings.cpp (92.1%): Missing lines 3355,3734-3736,3753-3755,3825-3827,3847,3901,5481
  • mssql_python/pybind/ddbc_bindings.h (100%)
  • mssql_python/pybind/result_metadata.hpp (100%)
  • mssql_python/pybind/row_factory.hpp (72.2%): Missing lines 15-16,20,27,31,40,59,100,152-172,176-177,185-186,189-190
  • mssql_python/row.py (100%)

Summary

  • Total: 399 lines
  • Missing: 62 lines
  • Coverage: 84%

mssql_python/cursor.py

Lines 2917-2925

  2917                         self.messages,
  2918                     )
  2919             finally:
  2920                 if started:
! 2921                     perf_stop("py::fetchone::cpp_call", started)
  2922 
  2923             return self._finish_fetchone(ret, row_data)
  2924         except Exception:
  2925             # On error, don't increment rownumber - rethrow the error

Lines 2935-2944

  2935             return None
  2936 
  2937         # Update internal position after successful fetch
  2938         if self._skip_increment_for_next_fetch:
! 2939             self._skip_increment_for_next_fetch = False
! 2940             self._next_row_index += 1
  2941         else:
  2942             self._increment_rownumber()
  2943 
  2944         self.rowcount = self._next_row_index

Lines 2948-2959

  2948         started = perf_start()
  2949         try:
  2950             if not converter_map and not self._uuid_str_indices:
  2951                 if native and not started and _native_row_eligible(Row):
! 2952                     row_type = Row
! 2953                     factory = row_type._fast_create
! 2954                     column_names = self._cached_result_columns
! 2955                     return (
  2956                         _NATIVE_ROW_PLAN,
  2957                         row_type,
  2958                         column_map,
  2959                         self,

Lines 2975-2983

  2975                 column_names=self._cached_result_columns,
  2976             )
  2977         finally:
  2978             if started:
! 2979                 perf_stop("py::fetchone::row_wrap", started)
  2980 
  2981     def fetchmany(self, size: Optional[int] = None) -> List[Row]:
  2982         """
  2983         Fetch the next set of rows of a query result.

Lines 3027-3035

  3027                         self.messages,
  3028                     )
  3029             finally:
  3030                 if started:
! 3031                     perf_stop("py::fetchmany::cpp_call", started)
  3032 
  3033             return self._finish_fetchmany(ret, rows_data, size=size)
  3034         except Exception:
  3035             # On error, don't increment rownumber - rethrow the error

Lines 3065-3076

  3065                     and Row is _DEFAULT_ROW_TYPE
  3066                     and Row._fast_create is _DEFAULT_FAST_ROW_CREATE
  3067                 ):
  3068                     if native and not started and _native_row_eligible(Row):
! 3069                         row_type = Row
! 3070                         factory = row_type._fast_create
! 3071                         column_names = self._cached_result_columns
! 3072                         return (
  3073                             _NATIVE_ROW_PLAN,
  3074                             row_type,
  3075                             column_map,
  3076                             self,

Lines 3109-3117

  3109                 for row_data in rows_data
  3110             ]
  3111         finally:
  3112             if started:
! 3113                 perf_stop("py::fetchmany::row_wrap", started)
  3114 
  3115     def fetchall(self) -> List[Row]:
  3116         """
  3117         Fetch all (remaining) rows of a query result.

mssql_python/pybind/ddbc_bindings.cpp

Lines 3351-3359

  3351                     }
  3352                 }
  3353             }
  3354         }
! 3355         buffer.resize(offset + bytesRead);
  3356         if (bytesRead > 0) {
  3357             LOG("FetchLobColumnData: Appended %zu bytes at loop %d", bytesRead, loopCount);
  3358         }
  3359         if (ret == SQL_SUCCESS) {

Lines 3730-3740

  3730                                         PyErr_SetString(PyExc_ValueError, "embedded null character");
  3731                                         throw py::error_already_set();
  3732                                     }
  3733                                     decoded = steal(PyUnicode_Decode(
! 3734                                         reinterpret_cast<const char*>(dataBuffer.data()),
! 3735                                         static_cast<Py_ssize_t>(dataLen),
! 3736                                         decodeEncoding.c_str(), "strict"));
  3737                                     if (!decoded) throw py::error_already_set();
  3738                                     if (PyList_Append(row.ptr(), decoded.ptr()) < 0)
  3739                                         throw py::error_already_set();
  3740                                     LOG("SQLGetData: CHAR column %d decoded with '%s', %zu bytes "

Lines 3749-3759

  3749                                     // Preserve the existing codec-error bytes fallback.
  3750                                     decoded = steal(PyBytes_FromStringAndSize(
  3751                                         reinterpret_cast<const char*>(dataBuffer.data()),
  3752                                         static_cast<Py_ssize_t>(dataLen)));
! 3753                                     if (!decoded) throw py::error_already_set();
! 3754                                     if (PyList_Append(row.ptr(), decoded.ptr()) < 0)
! 3755                                         throw py::error_already_set();
  3756                                 }
  3757                             } else {
  3758                                 // Buffer too small, fallback to streaming
  3759                                 LOG("SQLGetData: CHAR column %d data truncated "

Lines 3821-3831

  3821                     SQLWCHAR* dataBuffer;
  3822                     if (bufferChars <= std::size(inlineBuffer)) {
  3823                         std::fill_n(inlineBuffer, bufferChars, SQLWCHAR{});
  3824                         dataBuffer = inlineBuffer;
! 3825                     } else {
! 3826                         heapBuffer.resize(bufferChars);
! 3827                         dataBuffer = heapBuffer.data();
  3828                     }
  3829                     SQLLEN dataLen;
  3830                     ret = SQLGetData_ptr(hStmt, i, SQL_C_WCHAR, dataBuffer, fetchBufferSize,
  3831                                          &dataLen);

Lines 3843-3851

  3843                                 // null termination. This preserves embedded NULs and avoids
  3844                                 // any risk of reading past the valid range if the driver
  3845                                 // omits the terminator.
  3846                                 row.append(FetchText::from_utf16_native(
! 3847                                     reinterpret_cast<const char*>(dataBuffer),
  3848                                     static_cast<Py_ssize_t>(numCharsInData * sizeof(SQLWCHAR))));
  3849                                 LOG("SQLGetData: Appended NVARCHAR string "
  3850                                     "length=%lu for column %d",
  3851                                     (unsigned long)numCharsInData, i);

Lines 3897-3905

  3897                 SQLLEN indicator = 0;
  3898                 ret = SQLGetData_ptr(hStmt, i, SQL_C_LONG, &intValue, 0, &indicator);
  3899                 CaptureFetchDiagnostics(hStmt, ret, messages);
  3900                 if (SQL_SUCCEEDED(ret) && indicator != SQL_NULL_DATA) {
! 3901                     AppendFetchedCell(row, PyLong_FromLong(intValue));
  3902                 } else {
  3903                     row.append(py::none());
  3904                 }
  3905                 break;

Lines 5477-5485

  5477     FetchStateGuard fetchStateGuard(StatementHandle, messages);
  5478 
  5479     if (!hasLobColumns && fetchSize > 0) {
  5480         ret = SQLBindColums(StatementHandle, buffers, columnNames, numCols, fetchSize, charCtype,
! 5481                             messages);
  5482         if (!SQL_SUCCEEDED(ret)) {
  5483             LOG("Error when binding columns");
  5484             return ret;
  5485         }

mssql_python/pybind/row_factory.hpp

Lines 11-24

  11 
  12 // Only the post-fetch cached-map loop: no ODBC access, Row allocation, or UUID work.
  13 inline py::list apply_output_converters(const py::object& values, const py::object& converters) {
  14     if (!PyList_CheckExact(values.ptr()) || !PyList_CheckExact(converters.ptr())) {
! 15         throw py::type_error("converter values and map must be exact lists");
! 16     }
  17     py::list result =
  18         steal<py::list>(PyList_GetSlice(values.ptr(), 0, PyList_GET_SIZE(values.ptr())));
  19     if (!result)
! 20         throw py::error_already_set();
  21 
  22     // Keep the Python iterators: their retained tuples affect finalizer timing
  23     // when a callback replaces itself or mutates the source lists.
  24     py::object pairs = steal(PyObject_CallFunctionObjArgs(reinterpret_cast<PyObject*>(&PyZip_Type),

Lines 23-35

  23     // when a callback replaces itself or mutates the source lists.
  24     py::object pairs = steal(PyObject_CallFunctionObjArgs(reinterpret_cast<PyObject*>(&PyZip_Type),
  25                                                           values.ptr(), converters.ptr(), nullptr));
  26     if (!pairs)
! 27         throw py::error_already_set();
  28     py::object items =
  29         steal(PyObject_CallOneArg(reinterpret_cast<PyObject*>(&PyEnum_Type), pairs.ptr()));
  30     if (!items)
! 31         throw py::error_already_set();
  32     pairs = py::object();
  33 
  34     // Retain the current inputs and last encoded value like the Python locals.
  35     py::object value, converter, value_bytes;

Lines 36-44

  36     for (Py_ssize_t i = 0;; ++i) {
  37         py::object item = steal(PyIter_Next(items.ptr()));
  38         if (!item) {
  39             if (PyErr_Occurred())
! 40                 throw py::error_already_set();
  41             break;
  42         }
  43         PyObject* pair = PyTuple_GET_ITEM(item.ptr(), 1);
  44         py::object next_value = borrow(PyTuple_GET_ITEM(pair, 0));

Lines 55-63

  55         try {
  56             const int is_string =
  57                 PyObject_IsInstance(value.ptr(), reinterpret_cast<PyObject*>(&PyUnicode_Type));
  58             if (is_string < 0)
! 59                 throw py::error_already_set();
  60             PyObject* argument = value.ptr();
  61             if (is_string) {
  62                 // Match str.encode's codec lookup, including the spelling.
  63                 // Subclasses and __class__ proxies still dispatch encode dynamically.

Lines 96-104

   96     const py::handle& attr_cursor, const py::handle& attr_column_map_lower,
   97     const py::handle& attr_column_names,
   98     int (*set_attr)(PyObject*, PyObject*, PyObject*) = PyObject_GenericSetAttr) {
   99     if (!row)
! 100         throw py::error_already_set();
  101 
  102     if (set_attr(row.ptr(), attr_values.ptr(), row_data) < 0 ||
  103         set_attr(row.ptr(), attr_column_map.ptr(), column_map.ptr()) < 0 ||
  104         set_attr(row.ptr(), attr_cursor.ptr(), cursor_obj.ptr()) < 0 ||

Lines 148-181

  148 }
  149 
  150 // Passive final eligibility check: do not execute newly installed class descriptors.
  151 inline bool has_default_row_allocation(PyObject* row_class, const py::str& new_name,
! 152                                        const py::str& setattr_name) {
! 153     if (!PyUnicode_CheckExact(new_name.ptr()) || !PyUnicode_CheckExact(setattr_name.ptr())) {
! 154         throw py::type_error("Row allocation guard names must be exact strings");
! 155     }
! 156     if (!row_class || Py_TYPE(row_class) != &PyType_Type) {
! 157         return false;
! 158     }
! 159     auto* row_type = reinterpret_cast<PyTypeObject*>(row_class);
! 160     if (!row_type->tp_bases || !PyTuple_CheckExact(row_type->tp_bases) ||
! 161         PyTuple_GET_SIZE(row_type->tp_bases) != 1 ||
! 162         PyTuple_GET_ITEM(row_type->tp_bases, 0) != reinterpret_cast<PyObject*>(&PyBaseObject_Type) ||
! 163         !row_type->tp_dict) {
! 164         return false;
! 165     }
! 166     PyObject* names[] = {new_name.ptr(), setattr_name.ptr()};
! 167     for (PyObject* name : names) {
! 168         PyObject* member = PyDict_GetItemWithError(row_type->tp_dict, name);
! 169         if (member) {
! 170             return false;
! 171         }
! 172         if (PyErr_Occurred()) {
  173             throw py::error_already_set();
  174         }
  175     }
! 176     return true;
! 177 }
  178 
  179 // Attribute names are prepared once as binding defaults, not allocated for each row.
  180 inline py::object construct_row(const py::object& values, const py::type& row_class,
  181                                 const py::object& column_map, const py::object& cursor_obj,

Lines 181-194

  181                                 const py::object& column_map, const py::object& cursor_obj,
  182                                 const py::object& column_map_lower, const py::object& column_names,
  183                                 const py::tuple& attributes) {
  184     if (attributes.size() != 5) {
! 185         throw py::value_error("Row construction requires five attribute names");
! 186     }
  187     for (py::handle name : attributes) {
  188         if (!PyUnicode_Check(name.ptr())) {
! 189             throw py::type_error("Row attribute names must be strings");
! 190         }
  191     }
  192     const py::object new_method = row_class.attr("__new__");
  193     py::object row = steal(PyObject_CallOneArg(new_method.ptr(), row_class.ptr()));
  194     initialize_row(row, values.ptr(), column_map, cursor_obj, column_map_lower, column_names,


📋 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: 62.7%
mssql_python.pybind.row_factory.hpp: 74.3%
mssql_python.pybind.ddbc_bindings.cpp: 80.2%
mssql_python.pybind.connection.connection_pool.cpp: 82.3%
mssql_python.pybind.connection.connection.cpp: 83.1%
mssql_python.logging.py: 86.2%
mssql_python.pybind.logger_bridge.hpp: 87.5%
mssql_python.pooling.py: 90.1%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Copilot AI balanced review requested due to automatic review settings October 5, 2026 06:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Native correctness tests and uninstrumented Release performance measurements remain unrun.

Review effort: Balanced
Findings: None

@jahnvi480
Jahnvi Thakkar (jahnvi480) marked this pull request as ready for review October 5, 2026 07:56
Copilot AI balanced review requested due to automatic review settings October 5, 2026 07:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The native hot-path changes and fault-injection tests remain unexecuted, with no Release-OFF performance result available.

Review effort: Balanced
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 10:54
@github-actions github-actions Bot added pr-size: large Substantial code update and removed pr-size: medium Moderate update size labels Oct 5, 2026
@jahnvi480 Jahnvi Thakkar (jahnvi480) changed the title PERF: Cache full column counts for fetchone PERF: Optimize fetchone, fetchmany(1) and fetchval paths Oct 5, 2026
Restore the original 22-task diagnostic headline and report layout.
Keep separate OFF/OFF latency and ON/OFF route artifacts, validation,
thresholds and collection unchanged, with an explicit scope caveat.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 7, 2026 03:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Native ODBC state, Python ownership, and CI orchestration changed substantially, while exact-head native qualification remains pending.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Keep logical buffer capacity, initialization and conversion unchanged,
with heap fallback for larger bounded values. Preserve existing tests
and append boundary, strict-error and isolated buffer-contract cases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 7, 2026 06:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The latency benchmark currently builds unoptimized Unix binaries instead of the documented Release configuration.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Consolidate essential fetch regressions into existing suites and remove
PR-added benchmark-only and unified-coverage-skipped tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 8, 2026 05:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The numeric route can mask SQLGetData failures, and latency CI does not produce optimized Release builds.

3 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread mssql_python/pybind/ddbc_bindings.cpp
Comment thread eng/profiler_benchmarks/controller.py
Use existing Python Row completion while retaining native fetch behavior.
Keep route provenance accurate and update existing regression expectations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 8, 2026 08:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Latency builds are not verified Release builds, route documentation conflicts with source, and new CI orchestration lacks automated coverage.

4 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread profiler/README.md Outdated
@jahnvi480
Jahnvi Thakkar (jahnvi480) marked this pull request as ready for review October 8, 2026 09:10
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 8, 2026 11:45
Comment thread mssql_python/pybind/ddbc_bindings.cpp Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Latency builds are not guaranteed to be Release builds, CI orchestration lacks tests, and route documentation contradicts the implemented policy.

4 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Preserve existing LOB semantics, require Release benchmark configurations, and cover aggregate report failures and numeric fetch errors.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 8, 2026 12:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new fetchmany(1) factory check can invoke replacement descriptors after consuming a row, and the source-identity recognizer lacks direct tests.

0 open findings

4 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Add regression tests for closed source recognition

eng/​profiler_benchmarks/​controller.py:294

The new closed source recognizer has no direct regression coverage: the aggregate test monkeypatches python_source_identity, and no test invokes this function. A stale digest or an AST false rejection would make every latency/route worker fail before measurement. Add no-DB tests covering the current cursor source, a supported legacy source without the declaration, and representative duplicate/rebinding rejection cases.

Medium severity Avoid descriptor invocation when resolving _fast_create

mssql_python/​cursor.py:3066

This check resolves _fast_create through Python's descriptor protocol after the native fetch has already advanced the cursor. A replacement descriptor can therefore run side effects or raise here, making fetchmany(1) consume a row and fail even though the previous batch wrapper never consulted that factory. Inspect the raw class dictionary, as _native_row_eligible does, so substituted descriptors reliably stay on the batch path.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 07:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The profiler documentation attributes native fusion to a nonexistent test and an inactive default route.

0 open findings

Previously missed (1)

In code that hasn't changed since last review

Low severity Update attribution docs for inactive binding and actual route checks

profiler/​README.md:248

This attribution section describes a phase-controlled fused route and test_single_row_fusion_native_counters_in_subprocess, but neither exists in the current source: ordinary fetches call the split helpers with native=False, and the test name appears only here. The documented counters therefore cannot be produced by the current default path; update this block to describe the inactive binding and the route-mode checks that actually run.

🧠 Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants