Skip to content

Commit 865a379

Browse files
authored
FIX: Segmentation Fault when interleaving fetchmany and fetchone calls #427 (#441)
### Work Item / Issue Reference <!-- IMPORTANT: Please follow the PR template guidelines below. For mssql-python maintainers: Insert your ADO Work Item ID below For external contributors: Insert Github Issue number below Only one reference is required - either GitHub issue OR ADO Work Item. --> <!-- mssql-python maintainers: ADO Work Item --> > [AB#42605](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/42605) <!-- External contributors: GitHub Issue --> > GitHub Issue: #427 ------------------------------------------------------------------- ### Summary <!-- Insert your summary of changes below. Minimum 10 characters required. --> This pull request addresses a segmentation fault issue that occurred when interleaving `fetchmany()` and `fetchone()` calls on a database cursor, as reported in GitHub Issue #427. The main improvements include unbinding columns after fetch operations to prevent conflicts with `SQLGetData`, and adding comprehensive tests to ensure correct interleaved fetch behavior. Fixes for fetch interleaving bug: * Added calls to `SQLFreeStmt_ptr(hStmt, SQL_UNBIND)` after `fetchmany()` and `fetchall()` operations in `ddbc_bindings.cpp` to unbind columns, allowing subsequent `fetchone()` calls to use `SQLGetData` without causing segmentation faults. [[1]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1R4097-R4100) [[2]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1R4236-R4238) * Added column unbinding at the start of `FetchOne_wrap` to avoid conflicts from previous fetch operations, and improved error handling for `SQLGetData` failures. Testing improvements: * Introduced a new test file `test_017_fetchmany_fetchone_interleave.py` with multiple test cases to verify that interleaving `fetchmany()` and `fetchone()` calls works correctly and no longer causes segmentation faults. <!-- ### PR Title Guide > For feature requests FEAT: (short-description) > For non-feature requests like test case updates, config updates , dependency updates etc CHORE: (short-description) > For Fix requests FIX: (short-description) > For doc update requests DOC: (short-description) > For Formatting, indentation, or styling update STYLE: (short-description) > For Refactor, without any feature changes REFACTOR: (short-description) > For release related changes, without any feature changes RELEASE: #<RELEASE_VERSION> (short-description) ### Contribution Guidelines External contributors: - Create a GitHub issue first: https://github.com/microsoft/mssql-python/issues/new - Link the GitHub issue in the "GitHub Issue" section above - Follow the PR title format and provide a meaningful summary mssql-python maintainers: - Create an ADO Work Item following internal processes - Link the ADO Work Item in the "ADO Work Item" section above - Follow the PR title format and provide a meaningful summary -->
1 parent f8a6ca1 commit 865a379

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4094,6 +4094,10 @@ SQLRETURN FetchMany_wrap(SqlHandlePtr StatementHandle, py::list& rows, int fetch
40944094
// Reset attributes before returning to avoid using stack pointers later
40954095
SQLSetStmtAttr_ptr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)1, 0);
40964096
SQLSetStmtAttr_ptr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, NULL, 0);
4097+
4098+
// Unbind columns to allow subsequent fetchone() calls to use SQLGetData
4099+
SQLFreeStmt_ptr(hStmt, SQL_UNBIND);
4100+
40974101
return ret;
40984102
}
40994103

@@ -4228,6 +4232,9 @@ SQLRETURN FetchAll_wrap(SqlHandlePtr StatementHandle, py::list& rows,
42284232
// Reset attributes before returning to avoid using stack pointers later
42294233
SQLSetStmtAttr_ptr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)1, 0);
42304234
SQLSetStmtAttr_ptr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, NULL, 0);
4235+
4236+
// Unbind columns to allow subsequent fetchone() calls to use SQLGetData
4237+
SQLFreeStmt_ptr(hStmt, SQL_UNBIND);
42314238

42324239
return ret;
42334240
}
@@ -4254,12 +4261,21 @@ SQLRETURN FetchOne_wrap(SqlHandlePtr StatementHandle, py::list& row,
42544261
SQLRETURN ret;
42554262
SQLHSTMT hStmt = StatementHandle->get();
42564263

4264+
// Unbind any columns from previous fetch operations (e.g., fetchmany)
4265+
// to avoid conflicts with SQLGetData. SQLGetData cannot be used on
4266+
// columns that are already bound.
4267+
SQLFreeStmt_ptr(hStmt, SQL_UNBIND);
4268+
42574269
// Assume hStmt is already allocated and a query has been executed
42584270
ret = SQLFetch_ptr(hStmt);
42594271
if (SQL_SUCCEEDED(ret)) {
42604272
// Retrieve column count
42614273
SQLSMALLINT colCount = SQLNumResultCols_wrap(StatementHandle);
42624274
ret = SQLGetData_wrap(StatementHandle, colCount, row, charEncoding, wcharEncoding);
4275+
if (!SQL_SUCCEEDED(ret)) {
4276+
LOG("FetchOne_wrap: Error retrieving data with SQLGetData - SQLRETURN=%d", ret);
4277+
return ret;
4278+
}
42634279
} else if (ret != SQL_NO_DATA) {
42644280
LOG("FetchOne_wrap: Error when fetching data - SQLRETURN=%d", ret);
42654281
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Test for GitHub Issue #427: Segmentation fault when interleaving fetchmany() and fetchone()
3+
https://github.com/microsoft/mssql-python/issues/427
4+
"""
5+
6+
import pytest
7+
8+
9+
def test_fetchmany_then_fetchone_interleave(cursor):
10+
"""
11+
Test that fetchmany() followed by fetchone() doesn't cause segfault.
12+
This was the original failing case from issue #427.
13+
"""
14+
cursor.execute("SELECT 1 UNION SELECT 2")
15+
16+
result1 = cursor.fetchmany(1)
17+
assert len(result1) == 1
18+
assert result1[0][0] == 1
19+
20+
# This used to cause segfault on Linux, return None on Windows
21+
result2 = cursor.fetchone()
22+
assert result2 is not None
23+
assert result2[0] == 2
24+
25+
26+
def test_fetchone_then_fetchmany_interleave(cursor):
27+
"""Test that fetchone() followed by fetchmany() works correctly."""
28+
cursor.execute("SELECT 1 UNION SELECT 2 UNION SELECT 3")
29+
30+
result1 = cursor.fetchone()
31+
assert result1[0] == 1
32+
33+
result2 = cursor.fetchmany(2)
34+
assert len(result2) == 2
35+
assert result2[0][0] == 2
36+
assert result2[1][0] == 3
37+
38+
39+
def test_multiple_interleaved_fetches(cursor):
40+
"""Test multiple alternating fetchmany() and fetchone() calls."""
41+
cursor.execute("SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4")
42+
43+
result = cursor.fetchmany(1)
44+
assert result[0][0] == 1
45+
46+
result = cursor.fetchone()
47+
assert result[0] == 2
48+
49+
result = cursor.fetchmany(1)
50+
assert result[0][0] == 3
51+
52+
result = cursor.fetchone()
53+
assert result[0] == 4

0 commit comments

Comments
 (0)