Skip to content

Commit ad448fe

Browse files
committed
Resolving comments
1 parent 4142a97 commit ad448fe

2 files changed

Lines changed: 31 additions & 18 deletions

File tree

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
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
}

tests/test_004_cursor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ def test_insert_nvarchar_column(cursor, db_connection):
307307
except Exception as e:
308308
pytest.fail(f"Nvarchar column insertion/fetch failed: {e}")
309309
finally:
310-
cursor.execute("DROP TABLE #pytest_single_column")
310+
drop_table_if_exists(cursor, "#pytest_single_column")
311311
db_connection.commit()
312312

313313

@@ -328,7 +328,7 @@ def test_insert_time_column(cursor, db_connection):
328328
except Exception as e:
329329
pytest.fail(f"Time column insertion/fetch failed: {e}")
330330
finally:
331-
cursor.execute("DROP TABLE #pytest_single_column")
331+
drop_table_if_exists(cursor, "#pytest_single_column")
332332
db_connection.commit()
333333

334334

@@ -354,7 +354,7 @@ def test_insert_time_column_preserves_microseconds(cursor, db_connection):
354354
except Exception as e:
355355
pytest.fail(f"TIME microseconds round-trip failed: {e}")
356356
finally:
357-
cursor.execute("DROP TABLE #pytest_time_microseconds")
357+
drop_table_if_exists(cursor, "#pytest_time_microseconds")
358358
db_connection.commit()
359359

360360

@@ -377,7 +377,7 @@ def test_insert_datetime_column(cursor, db_connection):
377377
except Exception as e:
378378
pytest.fail(f"Datetime column insertion/fetch failed: {e}")
379379
finally:
380-
cursor.execute("DROP TABLE #pytest_single_column")
380+
drop_table_if_exists(cursor, "#pytest_single_column")
381381
db_connection.commit()
382382

383383

0 commit comments

Comments
 (0)