99#include " connection/connection_pool.h"
1010#include " logger_bridge.hpp"
1111
12- #include < cstdint>
1312#include < cctype>
13+ #include < cstdint>
1414#include < cstring> // For std::memcpy
1515#include < filesystem>
1616#include < iomanip> // std::setw, std::setfill
1717#include < iostream>
1818#include < utility> // std::forward
1919
20+
2021// -------------------------------------------------------------------------------------------------
2122// Macro definitions
2223// -------------------------------------------------------------------------------------------------
@@ -64,9 +65,16 @@ inline py::object ParseSqlTimeTextToPythonObject(const char* timeText, SQLLEN ti
6465 return py::none ();
6566 }
6667
67- size_t len = static_cast < size_t >(timeTextLen) ;
68+ size_t len;
6869 if (timeTextLen == SQL_NO_TOTAL) {
69- len = std::strlen (timeText);
70+ // When the driver reports SQL_NO_TOTAL, the buffer may not be null-terminated.
71+ // Bound the scan to the maximum expected TIME/TIME2 text length.
72+ len = static_cast <size_t >(std::strnlen (timeText, SQL_TIME_TEXT_MAX_LEN - 1 ));
73+ } else {
74+ len = static_cast <size_t >(timeTextLen);
75+ if (len > SQL_TIME_TEXT_MAX_LEN - 1 ) {
76+ len = SQL_TIME_TEXT_MAX_LEN - 1 ;
77+ }
7078 }
7179
7280 std::string value (timeText, len);
@@ -79,8 +87,8 @@ inline py::object ParseSqlTimeTextToPythonObject(const char* timeText, SQLLEN ti
7987 value = value.substr (start, end - start + 1 );
8088
8189 size_t firstColon = value.find (' :' );
82- size_t secondColon = (firstColon == std::string::npos) ? std::string::npos
83- : value.find (' :' , firstColon + 1 );
90+ size_t secondColon =
91+ (firstColon == std::string::npos) ? std::string::npos : value.find (' :' , firstColon + 1 );
8492 if (firstColon == std::string::npos || secondColon == std::string::npos) {
8593 ThrowStdException (" Failed to parse TIME/TIME2 value: missing ':' separators" );
8694 }
@@ -99,7 +107,8 @@ inline py::object ParseSqlTimeTextToPythonObject(const char* timeText, SQLLEN ti
99107 std::string frac = value.substr (dotPos + 1 );
100108
101109 size_t digitCount = 0 ;
102- while (digitCount < frac.size () && std::isdigit (static_cast <unsigned char >(frac[digitCount]))) {
110+ while (digitCount < frac.size () &&
111+ std::isdigit (static_cast <unsigned char >(frac[digitCount]))) {
103112 ++digitCount;
104113 }
105114 frac = frac.substr (0 , digitCount);
@@ -3312,10 +3321,15 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
33123321 case SQL_SS_TIME2: {
33133322 char timeTextBuffer[SQL_TIME_TEXT_MAX_LEN] = {0 };
33143323 SQLLEN timeDataLen = 0 ;
3315- ret = SQLGetData_ptr (hStmt, i, SQL_C_CHAR, & timeTextBuffer, sizeof (timeTextBuffer),
3324+ ret = SQLGetData_ptr (hStmt, i, SQL_C_CHAR, timeTextBuffer, sizeof (timeTextBuffer),
33163325 &timeDataLen);
3317- if (SQL_SUCCEEDED (ret) && timeDataLen != SQL_NULL_DATA) {
3318- row.append (ParseSqlTimeTextToPythonObject (timeTextBuffer, timeDataLen));
3326+ if (SQL_SUCCEEDED (ret)) {
3327+ if (timeDataLen == SQL_NULL_DATA) {
3328+ // Normal NULL value: append None without logging an error.
3329+ row.append (py::none ());
3330+ } else {
3331+ row.append (ParseSqlTimeTextToPythonObject (timeTextBuffer, timeDataLen));
3332+ }
33193333 } else {
33203334 LOG (" SQLGetData: Error retrieving SQL_SS_TIME2 for column "
33213335 " %d - SQLRETURN=%d" ,
@@ -4140,7 +4154,8 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
41404154 break ;
41414155 case SQL_SS_UDT:
41424156 rowSize += (static_cast <SQLLEN>(columnSize) == SQL_NO_TOTAL || columnSize == 0 )
4143- ? SQL_MAX_LOB_SIZE : columnSize;
4157+ ? SQL_MAX_LOB_SIZE
4158+ : columnSize;
41444159 break ;
41454160 case SQL_BINARY:
41464161 case SQL_VARBINARY:
@@ -4204,8 +4219,7 @@ SQLRETURN FetchMany_wrap(SqlHandlePtr StatementHandle, py::list& rows, int fetch
42044219
42054220 if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
42064221 dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
4207- dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML ||
4208- dataType == SQL_SS_UDT) &&
4222+ dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML || dataType == SQL_SS_UDT) &&
42094223 (columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
42104224 lobColumns.push_back (i + 1 ); // 1-based
42114225 }
@@ -4344,8 +4358,7 @@ SQLRETURN FetchAll_wrap(SqlHandlePtr StatementHandle, py::list& rows,
43444358
43454359 if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
43464360 dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
4347- dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML ||
4348- dataType == SQL_SS_UDT) &&
4361+ dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML || dataType == SQL_SS_UDT) &&
43494362 (columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
43504363 lobColumns.push_back (i + 1 ); // 1-based
43514364 }
0 commit comments