Skip to content

Commit a1675fb

Browse files
committed
copilot review fix
1 parent 39d9aaf commit a1675fb

2 files changed

Lines changed: 103 additions & 15 deletions

File tree

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,7 +2995,7 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
29952995
// Note: wcharEncoding parameter is reserved for future use
29962996
// Currently WCHAR data always uses UTF-16LE for Windows compatibility
29972997
(void)wcharEncoding; // Suppress unused parameter warning
2998-
#if !defined(__APPLE__) && !defined(__linux__)
2998+
#if defined(_WIN32)
29992999
// On Windows, VARCHAR is fetched as SQL_C_WCHAR, so charEncoding is unused.
30003000
(void)charEncoding;
30013001
#endif
@@ -3120,11 +3120,17 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
31203120
} else if (dataLen == 0) {
31213121
row.append(py::str(""));
31223122
} else if (dataLen == SQL_NO_TOTAL) {
3123+
// SQL_NO_TOTAL means the driver has data but
3124+
// cannot report its total length. The buffer
3125+
// may contain a truncated prefix — fall back to
3126+
// the streaming LOB path to retrieve the full
3127+
// value.
31233128
LOG("SQLGetData: Cannot determine data length "
31243129
"(SQL_NO_TOTAL) for column %d (SQL_CHAR), "
3125-
"returning NULL",
3130+
"falling back to LOB streaming",
31263131
i);
3127-
row.append(py::none());
3132+
row.append(FetchLobColumnData(hStmt, i, SQL_C_CHAR, false, false,
3133+
charEncoding));
31283134
} else if (dataLen < 0) {
31293135
LOG("SQLGetData: Unexpected negative data length "
31303136
"for column %d - dataType=%d, dataLen=%ld",
@@ -3179,11 +3185,17 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
31793185
} else if (dataLen == 0) {
31803186
row.append(py::str(""));
31813187
} else if (dataLen == SQL_NO_TOTAL) {
3188+
// SQL_NO_TOTAL means the driver has data but
3189+
// cannot report its total length. The buffer
3190+
// may contain a truncated prefix — fall back to
3191+
// the streaming LOB path to retrieve the full
3192+
// value.
31823193
LOG("SQLGetData: Cannot determine data length "
31833194
"(SQL_NO_TOTAL) for column %d (VARCHAR via WCHAR), "
3184-
"returning NULL",
3195+
"falling back to LOB streaming",
31853196
i);
3186-
row.append(py::none());
3197+
row.append(
3198+
FetchLobColumnData(hStmt, i, SQL_C_WCHAR, true, false, "utf-16le"));
31873199
} else if (dataLen < 0) {
31883200
LOG("SQLGetData: Unexpected negative data length "
31893201
"for column %d (VARCHAR via WCHAR) - dataLen=%ld",
@@ -3251,11 +3263,17 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
32513263
} else if (dataLen == 0) {
32523264
row.append(py::str(""));
32533265
} else if (dataLen == SQL_NO_TOTAL) {
3266+
// SQL_NO_TOTAL means the driver has data but
3267+
// cannot report its total length. The buffer
3268+
// may contain a truncated prefix — fall back to
3269+
// the streaming LOB path to retrieve the full
3270+
// value.
32543271
LOG("SQLGetData: Cannot determine NVARCHAR data "
32553272
"length (SQL_NO_TOTAL) for column %d, "
3256-
"returning NULL",
3273+
"falling back to LOB streaming",
32573274
i);
3258-
row.append(py::none());
3275+
row.append(
3276+
FetchLobColumnData(hStmt, i, SQL_C_WCHAR, true, false, "utf-16le"));
32593277
} else if (dataLen < 0) {
32603278
LOG("SQLGetData: Unexpected negative data length "
32613279
"for column %d (NVARCHAR) - dataLen=%ld",
@@ -3975,11 +3993,62 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
39753993
continue;
39763994
}
39773995
if (dataLen == SQL_NO_TOTAL) {
3978-
LOG("Cannot determine the length of the data. Returning NULL "
3979-
"value instead. Column ID - {}",
3980-
col);
3981-
Py_INCREF(Py_None);
3982-
PyList_SET_ITEM(row, col - 1, Py_None);
3996+
// SQL_NO_TOTAL means the driver has data but cannot report
3997+
// its total length (common for variable-length columns).
3998+
// The bound buffer may contain a truncated prefix — fall
3999+
// back to LOB streaming to retrieve the full value instead
4000+
// of silently returning NULL.
4001+
const ColumnInfoExt& noTotalColInfo = columnInfosExt[col - 1];
4002+
LOG("SQLGetData: SQL_NO_TOTAL for column %d (dataType=%d), "
4003+
"falling back to LOB streaming",
4004+
col, noTotalColInfo.dataType);
4005+
switch (noTotalColInfo.dataType) {
4006+
case SQL_CHAR:
4007+
case SQL_VARCHAR:
4008+
case SQL_LONGVARCHAR:
4009+
#if defined(__APPLE__) || defined(__linux__)
4010+
PyList_SET_ITEM(row, col - 1,
4011+
FetchLobColumnData(hStmt, col, SQL_C_CHAR, false, false,
4012+
noTotalColInfo.charEncoding)
4013+
.release()
4014+
.ptr());
4015+
#else
4016+
PyList_SET_ITEM(
4017+
row, col - 1,
4018+
FetchLobColumnData(hStmt, col, SQL_C_WCHAR, true, false, "utf-16le")
4019+
.release()
4020+
.ptr());
4021+
#endif
4022+
break;
4023+
case SQL_WCHAR:
4024+
case SQL_WVARCHAR:
4025+
case SQL_WLONGVARCHAR:
4026+
case SQL_SS_XML:
4027+
PyList_SET_ITEM(
4028+
row, col - 1,
4029+
FetchLobColumnData(hStmt, col, SQL_C_WCHAR, true, false, "utf-16le")
4030+
.release()
4031+
.ptr());
4032+
break;
4033+
case SQL_BINARY:
4034+
case SQL_VARBINARY:
4035+
case SQL_LONGVARBINARY:
4036+
case SQL_SS_UDT:
4037+
PyList_SET_ITEM(row, col - 1,
4038+
FetchLobColumnData(hStmt, col, SQL_C_BINARY, false, true)
4039+
.release()
4040+
.ptr());
4041+
break;
4042+
default:
4043+
// For fixed-length types SQL_NO_TOTAL should not
4044+
// occur; treat as NULL to avoid undefined behaviour.
4045+
LOG("SQL_NO_TOTAL for unexpected fixed-type column %d "
4046+
"(dataType=%d), returning NULL",
4047+
col, noTotalColInfo.dataType);
4048+
Py_INCREF(Py_None);
4049+
PyList_SET_ITEM(row, col - 1, Py_None);
4050+
break;
4051+
}
39834052
continue;
39844053
}
39854054

@@ -4166,13 +4235,21 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
41664235
case SQL_CHAR:
41674236
case SQL_VARCHAR:
41684237
case SQL_LONGVARCHAR:
4169-
rowSize += columnSize;
4238+
#if defined(_WIN32)
4239+
// Windows binds VARCHAR as SQL_C_WCHAR: buffer = (columnSize + 1) *
4240+
// sizeof(SQLWCHAR)
4241+
rowSize += (columnSize + 1) * sizeof(SQLWCHAR);
4242+
#else
4243+
// Linux/macOS bind VARCHAR as SQL_C_CHAR with UTF-8 expansion: buffer = columnSize
4244+
// * 4 + 1
4245+
rowSize += columnSize * 4 + 1;
4246+
#endif
41704247
break;
41714248
case SQL_SS_XML:
41724249
case SQL_WCHAR:
41734250
case SQL_WVARCHAR:
41744251
case SQL_WLONGVARCHAR:
4175-
rowSize += columnSize * sizeof(SQLWCHAR);
4252+
rowSize += (columnSize + 1) * sizeof(SQLWCHAR);
41764253
break;
41774254
case SQL_INTEGER:
41784255
rowSize += sizeof(SQLINTEGER);

tests/test_013_encoding_decoding.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7330,6 +7330,10 @@ def test_cp1252_varchar_byte_returns_str_fetchone(
73307330
f"Expected str for CHAR({byte_val}) ({description}) but got "
73317331
f"{type(value).__name__}: {value!r} (platform={sys.platform})."
73327332
)
7333+
assert value == expected_char, (
7334+
f"CHAR({byte_val}) ({description}) decoded to {value!r} but expected "
7335+
f"{expected_char!r} (U+{ord(expected_char):04X}) (platform={sys.platform})."
7336+
)
73337337
finally:
73347338
cursor.close()
73357339

@@ -7358,6 +7362,10 @@ def test_cp1252_varchar_byte173_batch_fetch(db_connection, fetch_method):
73587362
f"{fetch_method}: expected str but got {type(value).__name__}: "
73597363
f"{value!r} (platform={sys.platform})."
73607364
)
7365+
assert "\u00ad" in value, (
7366+
f"{fetch_method}: byte 0xAD must decode to U+00AD SOFT HYPHEN, "
7367+
f"but got {value!r} (platform={sys.platform})."
7368+
)
73617369
finally:
73627370
cursor.close()
73637371

@@ -7381,7 +7389,10 @@ def test_cp1252_varchar_byte173_embedded_in_string(db_connection):
73817389
f"Expected str but got {type(value).__name__}: {value!r}. "
73827390
f"Embedded byte 0xAD was not decoded (platform={sys.platform})."
73837391
)
7384-
assert "hello" in value and "world" in value
7392+
assert value == "hello\u00adworld", (
7393+
f"Expected 'hello\\u00adworld' but got {value!r}. "
7394+
f"Byte 0xAD must decode to U+00AD SOFT HYPHEN (platform={sys.platform})."
7395+
)
73857396
finally:
73867397
cursor.close()
73877398

0 commit comments

Comments
 (0)