STYLE: Final Linting for all cpp and python files with Workflow by jahnvi480 · Pull Request #331 · microsoft/mssql-python

📊 Code Coverage Report

🔥 Diff Coverage

65%


🎯 Overall Coverage

75%


📈 Total Lines Covered: 5089 out of 6748
📁 Project: mssql-python


Diff Coverage

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

  • mssql_python/init.py (0.0%): Missing lines 193
  • mssql_python/auth.py (100%)
  • mssql_python/connection.py (92.9%): Missing lines 1267
  • mssql_python/connection_string_builder.py (100%)
  • mssql_python/connection_string_parser.py (100%)
  • mssql_python/constants.py (100%)
  • mssql_python/cursor.py (87.8%): Missing lines 814,1342,1345,1348,1911
  • mssql_python/ddbc_bindings.py (0.0%): Missing lines 123
  • mssql_python/exceptions.py (100%)
  • mssql_python/helpers.py (100%)
  • mssql_python/logging.py (88.9%): Missing lines 156,297,322
  • mssql_python/pybind/connection/connection.cpp (79.5%): Missing lines 205-207,220-222,253,280,283
  • mssql_python/pybind/connection/connection_pool.cpp (71.4%): Missing lines 32-34,66,88-89
  • mssql_python/pybind/ddbc_bindings.cpp (60.7%): Missing lines 32,79,86,93,100,107,240-243,306-310,408,412-413,415-417,420-421,444-446,459-461,477-479,493-495,524-526,559-561,578-580,655-657,702-704,709-711,718-720,726-728,734-736,778,822-824,847-848,908,910,912,995-1000,1339,1346-1347,1379-1380,1440-1442,1450-1453,1608-1610,1674-1675,1680-1682,1696-1697,1699-1701,1721-1723,1732-1733,1772-1774,1789-1790,1796,1804-1806,1811-1812,1817-1818,1821-1823,1850-1858,1903-1907,1929-1930,1937-1941,1945,1971-1976,1991-1993,2013-2017,2028-2030,2084-2088,2102-2104,2110-2114,2128-2130,2136-2140,2154-2155,2159-2161,2184-2185,2190-2192,2234-2238,2248-2252,2255-2259,2271-2273,2302-2307,2314-2318,2339-2343,2356-2358,2363-2364,2427-2428,2435-2436,2449-2451,2453-2455,2468-2470,2473-2475,2477-2478,2481-2483,2486-2488,2496-2498,2505-2507,2518-2519,2635-2636,2761-2763,2798-2800,2809-2812,2815-2819,2822-2824,2879-2882,2885-2889,2892-2894,2916-2918,2929-2931,2981-2983,2987-2989,3002-3004,3015-3017,3044-3046,3064-3066,3088-3089,3103-3105,3141-3143,3148-3150,3162-3164,3175-3177,3208-3210,3233-3234,3536,3566-3568,3592-3594,3601-3603,3732-3733,3883,4013,4088-4089,4100-4101,4117-4118,4241-4242
  • mssql_python/pybind/ddbc_bindings.h (76.9%): Missing lines 81,398,446,721-722,788-789,828-829
  • mssql_python/pybind/logger_bridge.cpp (80.0%): Missing lines 178-180
  • mssql_python/pybind/logger_bridge.hpp (100%)
  • mssql_python/row.py (100%)
  • mssql_python/type.py (100%)

Summary

  • Total: 1171 lines
  • Missing: 402 lines
  • Coverage: 65%

mssql_python/init.py

Lines 189-197

  189 _original_module_setattr = sys.modules[__name__].__setattr__
  190 
  191 
  192 def _custom_setattr(name, value):
! 193     if name == "lowercase":
  194         with _settings_lock:
  195             _settings.lowercase = bool(value)
  196             # Update the module's lowercase variable
  197             _original_module_setattr(name, _settings.lowercase)

mssql_python/connection.py

Lines 1263-1271

  1263                         pass
  1264 
  1265                     # Last resort: return as integer if all else fails
  1266                     try:
! 1267                         return int.from_bytes(data[: min(length, 8)], "little", signed=True)
  1268                     except Exception:
  1269                         return 0
  1270                 elif isinstance(data, (int, float)):
  1271                     # Already numeric

mssql_python/cursor.py

Lines 810-818

  810             if sql_type in (
  811                 ddbc_sql_const.SQL_DECIMAL.value,
  812                 ddbc_sql_const.SQL_NUMERIC.value,
  813             ):
! 814                 column_size = max(1, min(int(column_size) if column_size > 0 else 18, 38))
  815                 decimal_digits = min(max(0, decimal_digits), column_size)
  816 
  817         else:
  818             # Fall back to automatic type inference

Lines 1338-1352

  1338                 return rows
  1339 
  1340             # Save original fetch methods
  1341             if not hasattr(self, "_original_fetchone"):
! 1342                 self._original_fetchone = (
  1343                     self.fetchone
  1344                 )  # pylint: disable=attribute-defined-outside-init
! 1345                 self._original_fetchmany = (
  1346                     self.fetchmany
  1347                 )  # pylint: disable=attribute-defined-outside-init
! 1348                 self._original_fetchall = (
  1349                     self.fetchall
  1350                 )  # pylint: disable=attribute-defined-outside-init
  1351 
  1352             # Use specialized mapping methods

Lines 1907-1915

  1907                 if sql_type in (
  1908                     ddbc_sql_const.SQL_DECIMAL.value,
  1909                     ddbc_sql_const.SQL_NUMERIC.value,
  1910                 ):
! 1911                     column_size = max(1, min(int(column_size) if column_size > 0 else 18, 38))
  1912                     decimal_digits = min(max(0, decimal_digits), column_size)
  1913 
  1914                 # For binary data columns with mixed content, we need to find max size
  1915                 if sql_type in (

mssql_python/ddbc_bindings.py

Lines 119-127

  119             f"No ddbc_bindings module found for {python_version}-{architecture} "
  120             f"with extension {extension}"
  121         )
  122     module_path = os.path.join(module_dir, module_files[0])
! 123     print(f"Warning: Using fallback module file {module_files[0]} instead of " f"{expected_module}")
  124 
  125 
  126 # Use the original module name 'ddbc_bindings' that the C extension was compiled with
  127 module_name = "ddbc_bindings"

mssql_python/logging.py

Lines 152-160

  152                     end_bracket = msg.index("]")
  153                     source = msg[1:end_bracket]
  154                     message = msg[end_bracket + 2 :].strip()  # Skip '] '
  155                 else:
! 156                     source = "Unknown"
  157                     message = msg
  158 
  159                 # Format timestamp with milliseconds using period separator
  160                 timestamp = self.formatTime(record, "%Y-%m-%d %H:%M:%S")

Lines 293-301

  293                 from mssql_python import __version__
  294 
  295                 driver_version = __version__
  296             except:
! 297                 driver_version = "unknown"
  298 
  299             # Get current time
  300             start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

Lines 318-326

  318 
  319         except Exception as e:
  320             # Notify on stderr so user knows why header is missing
  321             try:
! 322                 sys.stderr.write(
  323                     f"[MSSQL-Python] Warning: Failed to write log header to {self._log_file}: {type(e).__name__}\n"
  324                 )
  325                 sys.stderr.flush()
  326             except:

mssql_python/pybind/connection/connection.cpp

Lines 201-211

  201 
  202             // Convert to wide string
  203             std::wstring wstr = Utf8ToWString(utf8_str);
  204             if (wstr.empty() && !utf8_str.empty()) {
! 205                 LOG("Failed to convert string value to wide string for "
! 206                     "attribute=%d",
! 207                     attribute);
  208                 return SQL_ERROR;
  209             }
  210             this->wstrStringBuffer.clear();
  211             this->wstrStringBuffer = std::move(wstr);

Lines 216-226

  216 #if defined(__APPLE__) || defined(__linux__)
  217             // For macOS/Linux, convert wstring to SQLWCHAR buffer
  218             std::vector<SQLWCHAR> sqlwcharBuffer = WStringToSQLWCHAR(this->wstrStringBuffer);
  219             if (sqlwcharBuffer.empty() && !this->wstrStringBuffer.empty()) {
! 220                 LOG("Failed to convert wide string to SQLWCHAR buffer for "
! 221                     "attribute=%d",
! 222                     attribute);
  223                 return SQL_ERROR;
  224             }
  225 
  226             ptr = sqlwcharBuffer.data();

Lines 249-257

  249             this->strBytesBuffer = std::move(binary_data);
  250             SQLPOINTER ptr = const_cast<char*>(this->strBytesBuffer.c_str());
  251             SQLINTEGER length = static_cast<SQLINTEGER>(this->strBytesBuffer.size());
  252 
! 253             SQLRETURN ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), attribute, ptr, length);
  254             if (!SQL_SUCCEEDED(ret)) {
  255                 LOG("Failed to set binary attribute=%d, ret=%d", attribute, ret);
  256             } else {
  257                 LOG("Set binary attribute=%d successfully (length=%d)", attribute, length);

Lines 276-287

  276             continue;
  277         }
  278 
  279         // Apply all supported attributes
! 280         SQLRETURN ret = setAttribute(key, py::reinterpret_borrow<py::object>(item.second));
  281         if (!SQL_SUCCEEDED(ret)) {
  282             std::string attrName = std::to_string(key);
! 283             std::string errorMsg = "Failed to set attribute " + attrName + " before connect";
  284             ThrowStdException(errorMsg);
  285         }
  286     }
  287 }

mssql_python/pybind/connection/connection_pool.cpp

Lines 28-38

  28                                            std::chrono::duration_cast<std::chrono::seconds>(
  29                                                now - conn->lastUsed())
  30                                                .count();
  31                                        if (idle_time > _idle_timeout_secs) {
! 32                                            to_disconnect.push_back(conn);
! 33                                            return true;
! 34                                        }
  35                                        return false;
  36                                    }),
  37                     _pool.end());

Lines 62-70

  62             valid_conn = std::make_shared<Connection>(connStr, true);
  63             valid_conn->connect(attrs_before);
  64             ++_current_size;
  65         } else if (!valid_conn) {
! 66             throw std::runtime_error("ConnectionPool::acquire: pool size limit reached");
  67         }
  68     }
  69 
  70     // Phase 3: Disconnect expired/bad connections outside lock

Lines 84-93

  84         conn->updateLastUsed();
  85         _pool.push_back(conn);
  86     } else {
  87         conn->disconnect();
! 88         if (_current_size > 0)
! 89             --_current_size;
  90     }
  91 }
  92 
  93 void ConnectionPool::close() {

mssql_python/pybind/ddbc_bindings.cpp

Lines 28-36

  28 #define SQL_MAX_NUMERIC_LEN 16
  29 #define SQL_SS_XML (-152)
  30 
  31 #define STRINGIFY_FOR_CASE(x)                                                                      \
! 32     case x:                                                                                        \
  33         return #x
  34 
  35 // Architecture-specific defines
  36 #ifndef ARCHITECTURE

Lines 75-83

  75 py::object get_datetime_class() {
  76     if (cache_initialized && datetime_class) {
  77         return datetime_class;
  78     }
! 79     return py::module_::import("datetime").attr("datetime");
  80 }
  81 
  82 py::object get_date_class() {
  83     if (cache_initialized && date_class) {

Lines 82-90

  82 py::object get_date_class() {
  83     if (cache_initialized && date_class) {
  84         return date_class;
  85     }
! 86     return py::module_::import("datetime").attr("date");
  87 }
  88 
  89 py::object get_time_class() {
  90     if (cache_initialized && time_class) {

Lines 89-97

  89 py::object get_time_class() {
  90     if (cache_initialized && time_class) {
  91         return time_class;
  92     }
! 93     return py::module_::import("datetime").attr("time");
  94 }
  95 
  96 py::object get_decimal_class() {
  97     if (cache_initialized && decimal_class) {

Lines 96-104

   96 py::object get_decimal_class() {
   97     if (cache_initialized && decimal_class) {
   98         return decimal_class;
   99     }
! 100     return py::module_::import("decimal").attr("Decimal");
  101 }
  102 
  103 py::object get_uuid_class() {
  104     if (cache_initialized && uuid_class) {

Lines 103-111

  103 py::object get_uuid_class() {
  104     if (cache_initialized && uuid_class) {
  105         return uuid_class;
  106     }
! 107     return py::module_::import("uuid").attr("UUID");
  108 }
  109 }  // namespace PythonObjectCache
  110 
  111 //-------------------------------------------------------------------------------------------------

Lines 236-247

  236     }
  237 }
  238 
  239 std::string MakeParamMismatchErrorStr(const SQLSMALLINT cType, const int paramIndex) {
! 240     std::string errorString = "Parameter's object type does not match "
! 241                               "parameter's C type. paramIndex - " +
! 242                               std::to_string(paramIndex) + ", C type - " +
! 243                               GetSqlCTypeAsString(cType);
  244     return errorString;
  245 }
  246 
  247 // This function allocates a buffer of ParamType, stores it as a void* in

Lines 302-314

  302                     !py::isinstance<py::bytes>(param)) {
  303                     ThrowStdException(MakeParamMismatchErrorStr(paramInfo.paramCType, paramIndex));
  304                 }
  305                 if (paramInfo.isDAE) {
! 306                     LOG("BindParameters: param[%d] SQL_C_CHAR - Using DAE "
! 307                         "(Data-At-Execution) for large string streaming",
! 308                         paramIndex);
! 309                     dataPtr =
! 310                         const_cast<void*>(reinterpret_cast<const void*>(&paramInfos[paramIndex]));
  311                     strLenOrIndPtr = AllocateParamBuffer<SQLLEN>(paramBuffers);
  312                     *strLenOrIndPtr = SQL_LEN_DATA_AT_EXEC(0);
  313                     bufferLength = 0;
  314                 } else {

Lines 404-425

  404                 SQLULEN columnSize = paramInfo.columnSize;
  405                 SQLSMALLINT decimalDigits = paramInfo.decimalDigits;
  406                 if (sqlType == SQL_UNKNOWN_TYPE) {
  407                     SQLSMALLINT describedType;
! 408                     SQLULEN describedSize;
  409                     SQLSMALLINT describedDigits;
  410                     SQLSMALLINT nullable;
  411                     RETCODE rc = SQLDescribeParam_ptr(
! 412                         hStmt, static_cast<SQLUSMALLINT>(paramIndex + 1), &describedType,
! 413                         &describedSize, &describedDigits, &nullable);
  414                     if (!SQL_SUCCEEDED(rc)) {
! 415                         LOG("BindParameters: SQLDescribeParam failed for "
! 416                             "param[%d] (NULL parameter) - SQLRETURN=%d",
! 417                             paramIndex, rc);
  418                         return rc;
  419                     }
! 420                     sqlType = describedType;
! 421                     columnSize = describedSize;
  422                     decimalDigits = describedDigits;
  423                 }
  424                 dataPtr = nullptr;
  425                 strLenOrIndPtr = AllocateParamBuffer<SQLLEN>(paramBuffers);

Lines 440-450

  440                 int value = param.cast<int>();
  441                 // Range validation for signed 16-bit integer
  442                 if (value < std::numeric_limits<short>::min() ||
  443                     value > std::numeric_limits<short>::max()) {
! 444                     ThrowStdException("Signed short integer parameter out of "
! 445                                       "range at paramIndex " +
! 446                                       std::to_string(paramIndex));
  447                 }
  448                 dataPtr =
  449                     static_cast<void*>(AllocateParamBuffer<int>(paramBuffers, param.cast<int>()));
  450                 break;

Lines 455-465

  455                     ThrowStdException(MakeParamMismatchErrorStr(paramInfo.paramCType, paramIndex));
  456                 }
  457                 unsigned int value = param.cast<unsigned int>();
  458                 if (value > std::numeric_limits<unsigned short>::max()) {
! 459                     ThrowStdException("Unsigned short integer parameter out of "
! 460                                       "range at paramIndex " +
! 461                                       std::to_string(paramIndex));
  462                 }
  463                 dataPtr = static_cast<void*>(
  464                     AllocateParamBuffer<unsigned int>(paramBuffers, param.cast<unsigned int>()));
  465                 break;

Lines 473-483

  473                 int64_t value = param.cast<int64_t>();
  474                 // Range validation for signed 64-bit integer
  475                 if (value < std::numeric_limits<int64_t>::min() ||
  476                     value > std::numeric_limits<int64_t>::max()) {
! 477                     ThrowStdException("Signed 64-bit integer parameter out of "
! 478                                       "range at paramIndex " +
! 479                                       std::to_string(paramIndex));
  480                 }
  481                 dataPtr = static_cast<void*>(
  482                     AllocateParamBuffer<int64_t>(paramBuffers, param.cast<int64_t>()));
  483                 break;

Lines 489-499

  489                 }
  490                 uint64_t value = param.cast<uint64_t>();
  491                 // Range validation for unsigned 64-bit integer
  492                 if (value > std::numeric_limits<uint64_t>::max()) {
! 493                     ThrowStdException("Unsigned 64-bit integer parameter out "
! 494                                       "of range at paramIndex " +
! 495                                       std::to_string(paramIndex));
  496                 }
  497                 dataPtr = static_cast<void*>(
  498                     AllocateParamBuffer<uint64_t>(paramBuffers, param.cast<uint64_t>()));
  499                 break;

Lines 520-530

  520                     ThrowStdException(MakeParamMismatchErrorStr(paramInfo.paramCType, paramIndex));
  521                 }
  522                 int year = param.attr("year").cast<int>();
  523                 if (year < 1753 || year > 9999) {
! 524                     ThrowStdException("Date out of range for SQL Server "
! 525                                       "(1753-9999) at paramIndex " +
! 526                                       std::to_string(paramIndex));
  527                 }
  528                 // TODO: can be moved to python by registering SQL_DATE_STRUCT
  529                 // in pybind
  530                 SQL_DATE_STRUCT* sqlDatePtr = AllocateParamBuffer<SQL_DATE_STRUCT>(paramBuffers);

Lines 555-565

  555                 }
  556                 // Checking if the object has a timezone
  557                 py::object tzinfo = param.attr("tzinfo");
  558                 if (tzinfo.is_none()) {
! 559                     ThrowStdException("Datetime object must have tzinfo for "
! 560                                       "SQL_C_SS_TIMESTAMPOFFSET at paramIndex " +
! 561                                       std::to_string(paramIndex));
  562                 }
  563 
  564                 DateTimeOffset* dtoPtr = AllocateParamBuffer<DateTimeOffset>(paramBuffers);

Lines 574-584

  574                     static_cast<SQLUINTEGER>(param.attr("microsecond").cast<int>() * 1000);
  575 
  576                 py::object utcoffset = tzinfo.attr("utcoffset")(param);
  577                 if (utcoffset.is_none()) {
! 578                     ThrowStdException("Datetime object's tzinfo.utcoffset() "
! 579                                       "returned None at paramIndex " +
! 580                                       std::to_string(paramIndex));
  581                 }
  582 
  583                 int total_seconds =
  584                     static_cast<int>(utcoffset.attr("total_seconds")().cast<double>());

Lines 651-661

  651                 py::bytes uuid_bytes = param.cast<py::bytes>();
  652                 const unsigned char* uuid_data =
  653                     reinterpret_cast<const unsigned char*>(PyBytes_AS_STRING(uuid_bytes.ptr()));
  654                 if (PyBytes_GET_SIZE(uuid_bytes.ptr()) != 16) {
! 655                     LOG("BindParameters: param[%d] SQL_C_GUID - Invalid UUID "
! 656                         "length: expected 16 bytes, got %ld bytes",
! 657                         paramIndex, PyBytes_GET_SIZE(uuid_bytes.ptr()));
  658                     ThrowStdException("UUID binary data must be exactly 16 bytes long.");
  659                 }
  660                 SQLGUID* guid_data_ptr = AllocateParamBuffer<SQLGUID>(paramBuffers);
  661                 guid_data_ptr->Data1 = (static_cast<uint32_t>(uuid_data[3]) << 24) |

Lines 698-715

  698         if (paramInfo.paramCType == SQL_C_NUMERIC) {
  699             SQLHDESC hDesc = nullptr;
  700             rc = SQLGetStmtAttr_ptr(hStmt, SQL_ATTR_APP_PARAM_DESC, &hDesc, 0, NULL);
  701             if (!SQL_SUCCEEDED(rc)) {
! 702                 LOG("BindParameters: SQLGetStmtAttr(SQL_ATTR_APP_PARAM_DESC) "
! 703                     "failed for param[%d] - SQLRETURN=%d",
! 704                     paramIndex, rc);
  705                 return rc;
  706             }
  707             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_TYPE, (SQLPOINTER)SQL_C_NUMERIC, 0);
  708             if (!SQL_SUCCEEDED(rc)) {
! 709                 LOG("BindParameters: SQLSetDescField(SQL_DESC_TYPE) failed for "
! 710                     "param[%d] - SQLRETURN=%d",
! 711                     paramIndex, rc);
  712                 return rc;
  713             }
  714             SQL_NUMERIC_STRUCT* numericPtr = reinterpret_cast<SQL_NUMERIC_STRUCT*>(dataPtr);
  715             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_PRECISION,

Lines 714-724

  714             SQL_NUMERIC_STRUCT* numericPtr = reinterpret_cast<SQL_NUMERIC_STRUCT*>(dataPtr);
  715             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_PRECISION,
  716                                      (SQLPOINTER)numericPtr->precision, 0);
  717             if (!SQL_SUCCEEDED(rc)) {
! 718                 LOG("BindParameters: SQLSetDescField(SQL_DESC_PRECISION) "
! 719                     "failed for param[%d] - SQLRETURN=%d",
! 720                     paramIndex, rc);
  721                 return rc;
  722             }
  723 
  724             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_SCALE, (SQLPOINTER)numericPtr->scale, 0);

Lines 722-732

  722             }
  723 
  724             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_SCALE, (SQLPOINTER)numericPtr->scale, 0);
  725             if (!SQL_SUCCEEDED(rc)) {
! 726                 LOG("BindParameters: SQLSetDescField(SQL_DESC_SCALE) failed "
! 727                     "for param[%d] - SQLRETURN=%d",
! 728                     paramIndex, rc);
  729                 return rc;
  730             }
  731 
  732             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_DATA_PTR, (SQLPOINTER)numericPtr, 0);

Lines 730-740

  730             }
  731 
  732             rc = SQLSetDescField_ptr(hDesc, 1, SQL_DESC_DATA_PTR, (SQLPOINTER)numericPtr, 0);
  733             if (!SQL_SUCCEEDED(rc)) {
! 734                 LOG("BindParameters: SQLSetDescField(SQL_DESC_DATA_PTR) failed "
! 735                     "for param[%d] - SQLRETURN=%d",
! 736                     paramIndex, rc);
  737                 return rc;
  738             }
  739         }
  740     }

Lines 774-782

  774             // version compatibility)
  775             if (py::hasattr(sys_module, "_is_finalizing")) {
  776                 py::object finalizing_func = sys_module.attr("_is_finalizing");
  777                 if (!finalizing_func.is_none() && finalizing_func().cast<bool>()) {
! 778                     return true;  // Python is finalizing
  779                 }
  780             }
  781         }
  782         return false;

Lines 818-828

  818     if (pos != std::string::npos) {
  819         std::string dir = module_file.substr(0, pos);
  820         return dir;
  821     }
! 822     LOG("GetModuleDirectory: Could not extract directory from module path - "
! 823         "path='%s'",
! 824         module_file.c_str());
  825     return module_file;
  826 #endif
  827 }

Lines 843-852

  843 #else
  844     // macOS/Unix: Use dlopen
  845     void* handle = dlopen(driverPath.c_str(), RTLD_LAZY);
  846     if (!handle) {
! 847         LOG("LoadDriverLibrary: dlopen failed for path='%s' - %s", driverPath.c_str(),
! 848             dlerror() ? dlerror() : "unknown error");
  849     }
  850     return handle;
  851 #endif
  852 }

Lines 904-916

  904 
  905 // Detect platform and set path
  906 #ifdef __linux__
  907     if (fs::exists("/etc/alpine-release")) {
! 908         platform = "alpine";
  909     } else if (fs::exists("/etc/redhat-release") || fs::exists("/etc/centos-release")) {
! 910         platform = "rhel";
  911     } else if (fs::exists("/etc/SuSE-release") || fs::exists("/etc/SUSE-brand")) {
! 912         platform = "suse";
  913     } else {
  914         platform = "debian_ubuntu";  // Default to debian_ubuntu for other distros
  915     }

Lines 991-1004

   991     }
   992 
   993     DriverHandle handle = LoadDriverLibrary(driverPath.string());
   994     if (!handle) {
!  995         LOG("LoadDriverOrThrowException: Failed to load ODBC driver - "
!  996             "path='%s', error='%s'",
!  997             driverPath.string().c_str(), GetLastErrorMessage().c_str());
!  998         ThrowStdException("Failed to load the driver. Please read the documentation "
!  999                           "(https://github.com/microsoft/mssql-python#installation) to "
! 1000                           "install the required dependencies.");
  1001     }
  1002     LOG("LoadDriverOrThrowException: ODBC driver library loaded successfully "
  1003         "from '%s'",
  1004         driverPath.string().c_str());

Lines 1335-1343

  1335     LOG("SQLCheckError: Checking ODBC errors - handleType=%d, retcode=%d", handleType, retcode);
  1336     ErrorInfo errorInfo;
  1337     if (retcode == SQL_INVALID_HANDLE) {
  1338         LOG("SQLCheckError: SQL_INVALID_HANDLE detected - handle is invalid");
! 1339         errorInfo.ddbcErrorMsg = std::wstring(L"Invalid handle!");
  1340         return errorInfo;
  1341     }
  1342     assert(handle != 0);
  1343     SQLHANDLE rawHandle = handle->get();

Lines 1342-1351

  1342     assert(handle != 0);
  1343     SQLHANDLE rawHandle = handle->get();
  1344     if (!SQL_SUCCEEDED(retcode)) {
  1345         if (!SQLGetDiagRec_ptr) {
! 1346             LOG("SQLCheckError: SQLGetDiagRec function pointer not "
! 1347                 "initialized, loading driver");
  1348             DriverLoader::getInstance().loadDriver();  // Load the driver
  1349         }
  1350 
  1351         SQLWCHAR sqlState[6], message[SQL_MAX_MESSAGE_LENGTH];

Lines 1375-1384

  1375     LOG("SQLGetAllDiagRecords: Retrieving all diagnostic records for handle "
  1376         "%p, handleType=%d",
  1377         (void*)handle->get(), handle->type());
  1378     if (!SQLGetDiagRec_ptr) {
! 1379         LOG("SQLGetAllDiagRecords: SQLGetDiagRec function pointer not "
! 1380             "initialized, loading driver");
  1381         DriverLoader::getInstance().loadDriver();
  1382     }
  1383 
  1384     py::list records;

Lines 1436-1446

  1436 
  1437 // Wrap SQLExecDirect
  1438 SQLRETURN SQLExecDirect_wrap(SqlHandlePtr StatementHandle, const std::wstring& Query) {
  1439     std::string queryUtf8 = WideToUTF8(Query);
! 1440     LOG("SQLExecDirect: Executing query directly - statement_handle=%p, "
! 1441         "query_length=%zu chars",
! 1442         (void*)StatementHandle->get(), Query.length());
  1443     if (!SQLExecDirect_ptr) {
  1444         LOG("SQLExecDirect: Function pointer not initialized, loading driver");
  1445         DriverLoader::getInstance().loadDriver();  // Load the driver
  1446     }

Lines 1446-1457

  1446     }
  1447 
  1448     // Configure forward-only cursor
  1449     if (SQLSetStmtAttr_ptr && StatementHandle && StatementHandle->get()) {
! 1450         SQLSetStmtAttr_ptr(StatementHandle->get(), SQL_ATTR_CURSOR_TYPE,
! 1451                            (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, 0);
! 1452         SQLSetStmtAttr_ptr(StatementHandle->get(), SQL_ATTR_CONCURRENCY,
! 1453                            (SQLPOINTER)SQL_CONCUR_READ_ONLY, 0);
  1454     }
  1455 
  1456     SQLWCHAR* queryPtr;
  1457 #if defined(__APPLE__) || defined(__linux__)

Lines 1604-1614

  1604         assert(isStmtPrepared.size() == 1);
  1605         if (usePrepare) {
  1606             rc = SQLPrepare_ptr(hStmt, queryPtr, SQL_NTS);
  1607             if (!SQL_SUCCEEDED(rc)) {
! 1608                 LOG("SQLExecute: SQLPrepare failed - SQLRETURN=%d, "
! 1609                     "statement_handle=%p",
! 1610                     rc, (void*)hStmt);
  1611                 return rc;
  1612             }
  1613             isStmtPrepared[0] = py::cast(true);
  1614         } else {

Lines 1670-1686

  1670                             size_t len = std::min(chunkChars, totalChars - offset);
  1671                             size_t lenBytes = len * sizeof(SQLWCHAR);
  1672                             if (lenBytes >
  1673                                 static_cast<size_t>(std::numeric_limits<SQLLEN>::max())) {
! 1674                                 ThrowStdException("Chunk size exceeds maximum "
! 1675                                                   "allowed by SQLLEN");
  1676                             }
  1677                             rc = SQLPutData_ptr(hStmt, (SQLPOINTER)(dataPtr + offset),
  1678                                                 static_cast<SQLLEN>(lenBytes));
  1679                             if (!SQL_SUCCEEDED(rc)) {
! 1680                                 LOG("SQLExecute: SQLPutData failed for "
! 1681                                     "SQL_C_WCHAR chunk - offset=%zu",
! 1682                                     offset, totalChars, lenBytes, rc);
  1683                                 return rc;
  1684                             }
  1685                             offset += len;
  1686                         }

Lines 1692-1705

  1692                         size_t chunkBytes = DAE_CHUNK_SIZE;
  1693                         while (offset < totalBytes) {
  1694                             size_t len = std::min(chunkBytes, totalBytes - offset);
  1695 
! 1696                             rc = SQLPutData_ptr(hStmt, (SQLPOINTER)(dataPtr + offset),
! 1697                                                 static_cast<SQLLEN>(len));
  1698                             if (!SQL_SUCCEEDED(rc)) {
! 1699                                 LOG("SQLExecute: SQLPutData failed for "
! 1700                                     "SQL_C_CHAR chunk - offset=%zu",
! 1701                                     offset, totalBytes, len, rc);
  1702                                 return rc;
  1703                             }
  1704                             offset += len;
  1705                         }

Lines 1717-1727

  1717                         size_t len = std::min(chunkSize, totalBytes - offset);
  1718                         rc = SQLPutData_ptr(hStmt, (SQLPOINTER)(dataPtr + offset),
  1719                                             static_cast<SQLLEN>(len));
  1720                         if (!SQL_SUCCEEDED(rc)) {
! 1721                             LOG("SQLExecute: SQLPutData failed for "
! 1722                                 "binary/bytes chunk - offset=%zu",
! 1723                                 offset, totalBytes, len, rc);
  1724                             return rc;
  1725                         }
  1726                     }
  1727                 } else {

Lines 1728-1737

  1728                     ThrowStdException("DAE only supported for str or bytes");
  1729                 }
  1730             }
  1731             if (!SQL_SUCCEEDED(rc)) {
! 1732                 LOG("SQLExecute: SQLParamData final call %s - SQLRETURN=%d",
! 1733                     (rc == SQL_NO_DATA ? "completed with no data" : "failed"), rc);
  1734                 return rc;
  1735             }
  1736             LOG("SQLExecute: DAE streaming completed successfully, SQLExecute "
  1737                 "resumed");

Lines 1768-1778

  1768                 "SQL_type=%d, column_size=%zu, decimal_digits=%d",
  1769                 paramIndex, info.paramCType, info.paramSQLType, info.columnSize,
  1770                 info.decimalDigits);
  1771             if (columnValues.size() != paramSetSize) {
! 1772                 LOG("BindParameterArray: Size mismatch - param_index=%d, "
! 1773                     "expected=%zu, actual=%zu",
! 1774                     paramIndex, paramSetSize, columnValues.size());
  1775                 ThrowStdException("Column " + std::to_string(paramIndex) + " has mismatched size.");
  1776             }
  1777             void* dataPtr = nullptr;
  1778             SQLLEN* strLenOrIndArray = nullptr;

Lines 1785-1794

  1785                     int* dataArray = AllocateParamBufferArray<int>(tempBuffers, paramSetSize);
  1786                     for (size_t i = 0; i < paramSetSize; ++i) {
  1787                         if (columnValues[i].is_none()) {
  1788                             if (!strLenOrIndArray)
! 1789                                 strLenOrIndArray =
! 1790                                     AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  1791                             dataArray[i] = 0;
  1792                             strLenOrIndArray[i] = SQL_NULL_DATA;
  1793                         } else {
  1794                             dataArray[i] = columnValues[i].cast<int>();

Lines 1792-1800

  1792                             strLenOrIndArray[i] = SQL_NULL_DATA;
  1793                         } else {
  1794                             dataArray[i] = columnValues[i].cast<int>();
  1795                             if (strLenOrIndArray)
! 1796                                 strLenOrIndArray[i] = 0;
  1797                         }
  1798                     }
  1799                     LOG("BindParameterArray: SQL_C_LONG bound - param_index=%d", paramIndex);
  1800                     dataPtr = dataArray;

Lines 1800-1827

  1800                     dataPtr = dataArray;
  1801                     break;
  1802                 }
  1803                 case SQL_C_DOUBLE: {
! 1804                     LOG("BindParameterArray: Binding SQL_C_DOUBLE array - "
! 1805                         "param_index=%d, count=%zu",
! 1806                         paramIndex, paramSetSize);
  1807                     double* dataArray = AllocateParamBufferArray<double>(tempBuffers, paramSetSize);
  1808                     for (size_t i = 0; i < paramSetSize; ++i) {
  1809                         if (columnValues[i].is_none()) {
  1810                             if (!strLenOrIndArray)
! 1811                                 strLenOrIndArray =
! 1812                                     AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  1813                             dataArray[i] = 0;
  1814                             strLenOrIndArray[i] = SQL_NULL_DATA;
  1815                         } else {
  1816                             dataArray[i] = columnValues[i].cast<double>();
! 1817                             if (strLenOrIndArray)
! 1818                                 strLenOrIndArray[i] = 0;
  1819                         }
  1820                     }
! 1821                     LOG("BindParameterArray: SQL_C_DOUBLE bound - "
! 1822                         "param_index=%d",
! 1823                         paramIndex);
  1824                     dataPtr = dataArray;
  1825                     break;
  1826                 }
  1827                 case SQL_C_WCHAR: {

Lines 1846-1862

  1846                             // Check UTF-16 length (excluding null terminator)
  1847                             // against column size
  1848                             if (utf16Buf.size() > 0 && utf16_len > info.columnSize) {
  1849                                 std::string offending = WideToUTF8(wstr);
! 1850                                 LOG("BindParameterArray: SQL_C_WCHAR string "
! 1851                                     "too long - param_index=%d, row=%zu, "
! 1852                                     "utf16_length=%zu, max=%zu",
! 1853                                     paramIndex, i, utf16_len, info.columnSize);
! 1854                                 ThrowStdException("Input string UTF-16 length exceeds "
! 1855                                                   "allowed column size at parameter index " +
! 1856                                                   std::to_string(paramIndex) + ". UTF-16 length: " +
! 1857                                                   std::to_string(utf16_len) + ", Column size: " +
! 1858                                                   std::to_string(info.columnSize));
  1859                             }
  1860                             // If we reach here, the UTF-16 string fits - copy
  1861                             // it completely
  1862                             std::memcpy(wcharArray + i * (info.columnSize + 1), utf16Buf.data(),

Lines 1899-1911

  1899                             strLenOrIndArray[i] = SQL_NULL_DATA;
  1900                         } else {
  1901                             int intVal = columnValues[i].cast<int>();
  1902                             if (intVal < 0 || intVal > 255) {
! 1903                                 LOG("BindParameterArray: TINYINT value out of "
! 1904                                     "range - param_index=%d, row=%zu, value=%d",
! 1905                                     paramIndex, i, intVal);
! 1906                                 ThrowStdException("UTINYINT value out of range at rowIndex " +
! 1907                                                   std::to_string(i));
  1908                             }
  1909                             dataArray[i] = static_cast<unsigned char>(intVal);
  1910                             if (strLenOrIndArray)
  1911                                 strLenOrIndArray[i] = 0;

Lines 1925-1934

  1925                     short* dataArray = AllocateParamBufferArray<short>(tempBuffers, paramSetSize);
  1926                     for (size_t i = 0; i < paramSetSize; ++i) {
  1927                         if (columnValues[i].is_none()) {
  1928                             if (!strLenOrIndArray)
! 1929                                 strLenOrIndArray =
! 1930                                     AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  1931                             dataArray[i] = 0;
  1932                             strLenOrIndArray[i] = SQL_NULL_DATA;
  1933                         } else {
  1934                             int intVal = columnValues[i].cast<int>();

Lines 1933-1949

  1933                         } else {
  1934                             int intVal = columnValues[i].cast<int>();
  1935                             if (intVal < std::numeric_limits<short>::min() ||
  1936                                 intVal > std::numeric_limits<short>::max()) {
! 1937                                 LOG("BindParameterArray: SHORT value out of "
! 1938                                     "range - param_index=%d, row=%zu, value=%d",
! 1939                                     paramIndex, i, intVal);
! 1940                                 ThrowStdException("SHORT value out of range at rowIndex " +
! 1941                                                   std::to_string(i));
  1942                             }
  1943                             dataArray[i] = static_cast<short>(intVal);
  1944                             if (strLenOrIndArray)
! 1945                                 strLenOrIndArray[i] = 0;
  1946                         }
  1947                     }
  1948                     LOG("BindParameterArray: SQL_C_SHORT bound - "
  1949                         "param_index=%d",

Lines 1967-1980

  1967                                         info.columnSize + 1);
  1968                         } else {
  1969                             std::string str = columnValues[i].cast<std::string>();
  1970                             if (str.size() > info.columnSize) {
! 1971                                 LOG("BindParameterArray: String/binary too "
! 1972                                     "long - param_index=%d, row=%zu, size=%zu, "
! 1973                                     "max=%zu",
! 1974                                     paramIndex, i, str.size(), info.columnSize);
! 1975                                 ThrowStdException("Input exceeds column size at index " +
! 1976                                                   std::to_string(i));
  1977                             }
  1978                             std::memcpy(charArray + i * (info.columnSize + 1), str.c_str(),
  1979                                         str.size());
  1980                             strLenOrIndArray[i] = static_cast<SQLLEN>(str.size());

Lines 1987-1997

  1987                     bufferLength = info.columnSize + 1;
  1988                     break;
  1989                 }
  1990                 case SQL_C_BIT: {
! 1991                     LOG("BindParameterArray: Binding SQL_C_BIT array - "
! 1992                         "param_index=%d, count=%zu",
! 1993                         paramIndex, paramSetSize);
  1994                     char* boolArray = AllocateParamBufferArray<char>(tempBuffers, paramSetSize);
  1995                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  1996                     for (size_t i = 0; i < paramSetSize; ++i) {
  1997                         if (columnValues[i].is_none()) {

Lines 2009-2021

  2009                     break;
  2010                 }
  2011                 case SQL_C_STINYINT:
  2012                 case SQL_C_USHORT: {
! 2013                     LOG("BindParameterArray: Binding SQL_C_USHORT/STINYINT "
! 2014                         "array - param_index=%d, count=%zu",
! 2015                         paramIndex, paramSetSize);
! 2016                     unsigned short* dataArray =
! 2017                         AllocateParamBufferArray<unsigned short>(tempBuffers, paramSetSize);
  2018                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2019                     for (size_t i = 0; i < paramSetSize; ++i) {
  2020                         if (columnValues[i].is_none()) {
  2021                             strLenOrIndArray[i] = SQL_NULL_DATA;

Lines 2024-2034

  2024                             dataArray[i] = columnValues[i].cast<unsigned short>();
  2025                             strLenOrIndArray[i] = 0;
  2026                         }
  2027                     }
! 2028                     LOG("BindParameterArray: SQL_C_USHORT bound - "
! 2029                         "param_index=%d",
! 2030                         paramIndex);
  2031                     dataPtr = dataArray;
  2032                     bufferLength = sizeof(unsigned short);
  2033                     break;
  2034                 }

Lines 2080-2092

  2080                     bufferLength = sizeof(float);
  2081                     break;
  2082                 }
  2083                 case SQL_C_TYPE_DATE: {
! 2084                     LOG("BindParameterArray: Binding SQL_C_TYPE_DATE array - "
! 2085                         "param_index=%d, count=%zu",
! 2086                         paramIndex, paramSetSize);
! 2087                     SQL_DATE_STRUCT* dateArray =
! 2088                         AllocateParamBufferArray<SQL_DATE_STRUCT>(tempBuffers, paramSetSize);
  2089                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2090                     for (size_t i = 0; i < paramSetSize; ++i) {
  2091                         if (columnValues[i].is_none()) {
  2092                             strLenOrIndArray[i] = SQL_NULL_DATA;

Lines 2098-2108

  2098                             dateArray[i].day = dateObj.attr("day").cast<SQLUSMALLINT>();
  2099                             strLenOrIndArray[i] = 0;
  2100                         }
  2101                     }
! 2102                     LOG("BindParameterArray: SQL_C_TYPE_DATE bound - "
! 2103                         "param_index=%d",
! 2104                         paramIndex);
  2105                     dataPtr = dateArray;
  2106                     bufferLength = sizeof(SQL_DATE_STRUCT);
  2107                     break;
  2108                 }

Lines 2106-2118

  2106                     bufferLength = sizeof(SQL_DATE_STRUCT);
  2107                     break;
  2108                 }
  2109                 case SQL_C_TYPE_TIME: {
! 2110                     LOG("BindParameterArray: Binding SQL_C_TYPE_TIME array - "
! 2111                         "param_index=%d, count=%zu",
! 2112                         paramIndex, paramSetSize);
! 2113                     SQL_TIME_STRUCT* timeArray =
! 2114                         AllocateParamBufferArray<SQL_TIME_STRUCT>(tempBuffers, paramSetSize);
  2115                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2116                     for (size_t i = 0; i < paramSetSize; ++i) {
  2117                         if (columnValues[i].is_none()) {
  2118                             strLenOrIndArray[i] = SQL_NULL_DATA;

Lines 2124-2134

  2124                             timeArray[i].second = timeObj.attr("second").cast<SQLUSMALLINT>();
  2125                             strLenOrIndArray[i] = 0;
  2126                         }
  2127                     }
! 2128                     LOG("BindParameterArray: SQL_C_TYPE_TIME bound - "
! 2129                         "param_index=%d",
! 2130                         paramIndex);
  2131                     dataPtr = timeArray;
  2132                     bufferLength = sizeof(SQL_TIME_STRUCT);
  2133                     break;
  2134                 }

Lines 2132-2144

  2132                     bufferLength = sizeof(SQL_TIME_STRUCT);
  2133                     break;
  2134                 }
  2135                 case SQL_C_TYPE_TIMESTAMP: {
! 2136                     LOG("BindParameterArray: Binding SQL_C_TYPE_TIMESTAMP "
! 2137                         "array - param_index=%d, count=%zu",
! 2138                         paramIndex, paramSetSize);
! 2139                     SQL_TIMESTAMP_STRUCT* tsArray =
! 2140                         AllocateParamBufferArray<SQL_TIMESTAMP_STRUCT>(tempBuffers, paramSetSize);
  2141                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2142                     for (size_t i = 0; i < paramSetSize; ++i) {
  2143                         if (columnValues[i].is_none()) {
  2144                             strLenOrIndArray[i] = SQL_NULL_DATA;

Lines 2150-2165

  2150                             tsArray[i].day = dtObj.attr("day").cast<SQLUSMALLINT>();
  2151                             tsArray[i].hour = dtObj.attr("hour").cast<SQLUSMALLINT>();
  2152                             tsArray[i].minute = dtObj.attr("minute").cast<SQLUSMALLINT>();
  2153                             tsArray[i].second = dtObj.attr("second").cast<SQLUSMALLINT>();
! 2154                             tsArray[i].fraction = static_cast<SQLUINTEGER>(
! 2155                                 dtObj.attr("microsecond").cast<int>() * 1000);  // µs to ns
  2156                             strLenOrIndArray[i] = 0;
  2157                         }
  2158                     }
! 2159                     LOG("BindParameterArray: SQL_C_TYPE_TIMESTAMP bound - "
! 2160                         "param_index=%d",
! 2161                         paramIndex);
  2162                     dataPtr = tsArray;
  2163                     bufferLength = sizeof(SQL_TIMESTAMP_STRUCT);
  2164                     break;
  2165                 }

Lines 2180-2196

  2180                             std::memset(&dtoArray[i], 0, sizeof(DateTimeOffset));
  2181                             strLenOrIndArray[i] = SQL_NULL_DATA;
  2182                         } else {
  2183                             if (!py::isinstance(param, datetimeType)) {
! 2184                                 ThrowStdException(
! 2185                                     MakeParamMismatchErrorStr(info.paramCType, paramIndex));
  2186                             }
  2187 
  2188                             py::object tzinfo = param.attr("tzinfo");
  2189                             if (tzinfo.is_none()) {
! 2190                                 ThrowStdException("Datetime object must have tzinfo for "
! 2191                                                   "SQL_C_SS_TIMESTAMPOFFSET at paramIndex " +
! 2192                                                   std::to_string(paramIndex));
  2193                             }
  2194 
  2195                             // Populate the C++ struct directly from the Python
  2196                             // datetime object.

Lines 2230-2242

  2230                     bufferLength = sizeof(DateTimeOffset);
  2231                     break;
  2232                 }
  2233                 case SQL_C_NUMERIC: {
! 2234                     LOG("BindParameterArray: Binding SQL_C_NUMERIC array - "
! 2235                         "param_index=%d, count=%zu",
! 2236                         paramIndex, paramSetSize);
! 2237                     SQL_NUMERIC_STRUCT* numericArray =
! 2238                         AllocateParamBufferArray<SQL_NUMERIC_STRUCT>(tempBuffers, paramSetSize);
  2239                     strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
  2240                     for (size_t i = 0; i < paramSetSize; ++i) {
  2241                         const py::handle& element = columnValues[i];
  2242                         if (element.is_none()) {

Lines 2244-2263

  2244                             std::memset(&numericArray[i], 0, sizeof(SQL_NUMERIC_STRUCT));
  2245                             continue;
  2246                         }
  2247                         if (!py::isinstance<NumericData>(element)) {
! 2248                             LOG("BindParameterArray: NUMERIC type mismatch - "
! 2249                                 "param_index=%d, row=%zu",
! 2250                                 paramIndex, i);
! 2251                             throw std::runtime_error(
! 2252                                 MakeParamMismatchErrorStr(info.paramCType, paramIndex));
  2253                         }
  2254                         NumericData decimalParam = element.cast<NumericData>();
! 2255                         LOG("BindParameterArray: NUMERIC value - "
! 2256                             "param_index=%d, row=%zu, precision=%d, scale=%d, "
! 2257                             "sign=%d",
! 2258                             paramIndex, i, decimalParam.precision, decimalParam.scale,
! 2259                             decimalParam.sign);
  2260                         SQL_NUMERIC_STRUCT& target = numericArray[i];
  2261                         std::memset(&target, 0, sizeof(SQL_NUMERIC_STRUCT));
  2262                         target.precision = decimalParam.precision;
  2263                         target.scale = decimalParam.scale;

Lines 2267-2277

  2267                             std::memcpy(target.val, decimalParam.val.data(), copyLen);
  2268                         }
  2269                         strLenOrIndArray[i] = sizeof(SQL_NUMERIC_STRUCT);
  2270                     }
! 2271                     LOG("BindParameterArray: SQL_C_NUMERIC bound - "
! 2272                         "param_index=%d",
! 2273                         paramIndex);
  2274                     dataPtr = numericArray;
  2275                     bufferLength = sizeof(SQL_NUMERIC_STRUCT);
  2276                     break;
  2277                 }

Lines 2298-2311

  2298                             continue;
  2299                         } else if (py::isinstance<py::bytes>(element)) {
  2300                             py::bytes b = element.cast<py::bytes>();
  2301                             if (PyBytes_GET_SIZE(b.ptr()) != 16) {
! 2302                                 LOG("BindParameterArray: GUID bytes wrong "
! 2303                                     "length - param_index=%d, row=%zu, "
! 2304                                     "length=%d",
! 2305                                     paramIndex, i, PyBytes_GET_SIZE(b.ptr()));
! 2306                                 ThrowStdException("UUID binary data must be "
! 2307                                                   "exactly 16 bytes long.");
  2308                             }
  2309                             std::memcpy(uuid_bytes.data(), PyBytes_AS_STRING(b.ptr()), 16);
  2310                         } else if (py::isinstance(element, uuid_class)) {
  2311                             py::bytes b = element.attr("bytes_le").cast<py::bytes>();

Lines 2310-2322

  2310                         } else if (py::isinstance(element, uuid_class)) {
  2311                             py::bytes b = element.attr("bytes_le").cast<py::bytes>();
  2312                             std::memcpy(uuid_bytes.data(), PyBytes_AS_STRING(b.ptr()), 16);
  2313                         } else {
! 2314                             LOG("BindParameterArray: GUID type mismatch - "
! 2315                                 "param_index=%d, row=%zu",
! 2316                                 paramIndex, i);
! 2317                             ThrowStdException(
! 2318                                 MakeParamMismatchErrorStr(info.paramCType, paramIndex));
  2319                         }
  2320                         guidArray[i].Data1 = (static_cast<uint32_t>(uuid_bytes[3]) << 24) |
  2321                                              (static_cast<uint32_t>(uuid_bytes[2]) << 16) |
  2322                                              (static_cast<uint32_t>(uuid_bytes[1]) << 8) |

Lines 2335-2347

  2335                     bufferLength = sizeof(SQLGUID);
  2336                     break;
  2337                 }
  2338                 default: {
! 2339                     LOG("BindParameterArray: Unsupported C type - "
! 2340                         "param_index=%d, C_type=%d",
! 2341                         paramIndex, info.paramCType);
! 2342                     ThrowStdException("BindParameterArray: Unsupported C type: " +
! 2343                                       std::to_string(info.paramCType));
  2344                 }
  2345             }
  2346             LOG("BindParameterArray: Calling SQLBindParameter - "
  2347                 "param_index=%d, buffer_length=%lld",

Lines 2352-2368

  2352                                      static_cast<SQLSMALLINT>(info.paramCType),
  2353                                      static_cast<SQLSMALLINT>(info.paramSQLType), info.columnSize,
  2354                                      info.decimalDigits, dataPtr, bufferLength, strLenOrIndArray);
  2355             if (!SQL_SUCCEEDED(rc)) {
! 2356                 LOG("BindParameterArray: SQLBindParameter failed - "
! 2357                     "param_index=%d, SQLRETURN=%d",
! 2358                     paramIndex, rc);
  2359                 return rc;
  2360             }
  2361         }
  2362     } catch (...) {
! 2363         LOG("BindParameterArray: Exception during binding, cleaning up "
! 2364             "buffers");
  2365         throw;
  2366     }
  2367     paramBuffers.insert(paramBuffers.end(), tempBuffers.begin(), tempBuffers.end());
  2368     LOG("BindParameterArray: Successfully bound all parameters - "

Lines 2423-2432

  2423         rc = SQLExecute_ptr(hStmt);
  2424         LOG("SQLExecuteMany: SQLExecute completed - rc=%d", rc);
  2425         return rc;
  2426     } else {
! 2427         LOG("SQLExecuteMany: Using DAE (data-at-execution) - row_count=%zu",
! 2428             columnwise_params.size());
  2429         size_t rowCount = columnwise_params.size();
  2430         for (size_t rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
  2431             LOG("SQLExecuteMany: Processing DAE row %zu of %zu", rowIndex + 1, rowCount);
  2432             py::list rowParams = columnwise_params[rowIndex];

Lines 2431-2440

  2431             LOG("SQLExecuteMany: Processing DAE row %zu of %zu", rowIndex + 1, rowCount);
  2432             py::list rowParams = columnwise_params[rowIndex];
  2433 
  2434             std::vector<std::shared_ptr<void>> paramBuffers;
! 2435             rc = BindParameters(hStmt, rowParams, const_cast<std::vector<ParamInfo>&>(paramInfos),
! 2436                                 paramBuffers);
  2437             if (!SQL_SUCCEEDED(rc)) {
  2438                 LOG("SQLExecuteMany: BindParameters failed for row %zu - rc=%d", rowIndex, rc);
  2439                 return rc;
  2440             }

Lines 2445-2459

  2445             size_t dae_chunk_count = 0;
  2446             while (rc == SQL_NEED_DATA) {
  2447                 SQLPOINTER token;
  2448                 rc = SQLParamData_ptr(hStmt, &token);
! 2449                 LOG("SQLExecuteMany: SQLParamData called - chunk=%zu, rc=%d, "
! 2450                     "token=%p",
! 2451                     dae_chunk_count, rc, token);
  2452                 if (!SQL_SUCCEEDED(rc) && rc != SQL_NEED_DATA) {
! 2453                     LOG("SQLExecuteMany: SQLParamData failed - chunk=%zu, "
! 2454                         "rc=%d",
! 2455                         dae_chunk_count, rc);
  2456                     return rc;
  2457                 }
  2458 
  2459                 py::object* py_obj_ptr = reinterpret_cast<py::object*>(token);

Lines 2464-2492

  2464 
  2465                 if (py::isinstance<py::str>(*py_obj_ptr)) {
  2466                     std::string data = py_obj_ptr->cast<std::string>();
  2467                     SQLLEN data_len = static_cast<SQLLEN>(data.size());
! 2468                     LOG("SQLExecuteMany: Sending string DAE data - chunk=%zu, "
! 2469                         "length=%lld",
! 2470                         dae_chunk_count, static_cast<long long>(data_len));
  2471                     rc = SQLPutData_ptr(hStmt, (SQLPOINTER)data.c_str(), data_len);
  2472                     if (!SQL_SUCCEEDED(rc) && rc != SQL_NEED_DATA) {
! 2473                         LOG("SQLExecuteMany: SQLPutData(string) failed - "
! 2474                             "chunk=%zu, rc=%d",
! 2475                             dae_chunk_count, rc);
  2476                     }
! 2477                 } else if (py::isinstance<py::bytes>(*py_obj_ptr) ||
! 2478                            py::isinstance<py::bytearray>(*py_obj_ptr)) {
  2479                     std::string data = py_obj_ptr->cast<std::string>();
  2480                     SQLLEN data_len = static_cast<SQLLEN>(data.size());
! 2481                     LOG("SQLExecuteMany: Sending bytes/bytearray DAE data - "
! 2482                         "chunk=%zu, length=%lld",
! 2483                         dae_chunk_count, static_cast<long long>(data_len));
  2484                     rc = SQLPutData_ptr(hStmt, (SQLPOINTER)data.c_str(), data_len);
  2485                     if (!SQL_SUCCEEDED(rc) && rc != SQL_NEED_DATA) {
! 2486                         LOG("SQLExecuteMany: SQLPutData(bytes) failed - "
! 2487                             "chunk=%zu, rc=%d",
! 2488                             dae_chunk_count, rc);
  2489                     }
  2490                 } else {
  2491                     LOG("SQLExecuteMany: Unsupported DAE data type - chunk=%zu", dae_chunk_count);
  2492                     return SQL_ERROR;

Lines 2492-2502

  2492                     return SQL_ERROR;
  2493                 }
  2494                 dae_chunk_count++;
  2495             }
! 2496             LOG("SQLExecuteMany: DAE completed for row %zu - total_chunks=%zu, "
! 2497                 "final_rc=%d",
! 2498                 rowIndex, dae_chunk_count, rc);
  2499 
  2500             if (!SQL_SUCCEEDED(rc)) {
  2501                 LOG("SQLExecuteMany: DAE row %zu failed - rc=%d", rowIndex, rc);
  2502                 return rc;

Lines 2501-2511

  2501                 LOG("SQLExecuteMany: DAE row %zu failed - rc=%d", rowIndex, rc);
  2502                 return rc;
  2503             }
  2504         }
! 2505         LOG("SQLExecuteMany: All DAE rows processed successfully - "
! 2506             "total_rows=%zu",
! 2507             rowCount);
  2508         return SQL_SUCCESS;
  2509     }
  2510 }

Lines 2514-2523

  2514     LOG("SQLNumResultCols: Getting number of columns in result set for "
  2515         "statement_handle=%p",
  2516         (void*)statementHandle->get());
  2517     if (!SQLNumResultCols_ptr) {
! 2518         LOG("SQLNumResultCols: Function pointer not initialized, loading "
! 2519             "driver");
  2520         DriverLoader::getInstance().loadDriver();  // Load the driver
  2521     }
  2522 
  2523     SQLSMALLINT columnCount;

Lines 2631-2640

  2631         ret = SQLGetData_ptr(hStmt, colIndex, cType, chunk.data(), DAE_CHUNK_SIZE, &actualRead);
  2632 
  2633         if (ret == SQL_ERROR || !SQL_SUCCEEDED(ret) && ret != SQL_SUCCESS_WITH_INFO) {
  2634             std::ostringstream oss;
! 2635             oss << "Error fetching LOB for column " << colIndex << ", cType=" << cType
! 2636                 << ", loop=" << loopCount << ", SQLGetData return=" << ret;
  2637             LOG("FetchLobColumnData: %s", oss.str().c_str());
  2638             ThrowStdException(oss.str());
  2639         }
  2640         if (actualRead == SQL_NULL_DATA) {

Lines 2757-2767

  2757 
  2758         ret = SQLDescribeCol_ptr(hStmt, i, columnName, sizeof(columnName) / sizeof(SQLWCHAR),
  2759                                  &columnNameLen, &dataType, &columnSize, &decimalDigits, &nullable);
  2760         if (!SQL_SUCCEEDED(ret)) {
! 2761             LOG("SQLGetData: Error retrieving metadata for column %d - "
! 2762                 "SQLDescribeCol SQLRETURN=%d",
! 2763                 i, ret);
  2764             row.append(py::none());
  2765             continue;
  2766         }

Lines 2794-2804

  2794                                 row.append(std::string(reinterpret_cast<char*>(dataBuffer.data())));
  2795 #endif
  2796                             } else {
  2797                                 // Buffer too small, fallback to streaming
! 2798                                 LOG("SQLGetData: CHAR column %d data truncated "
! 2799                                     "(buffer_size=%zu), using streaming LOB",
! 2800                                     i, dataBuffer.size());
  2801                                 row.append(FetchLobColumnData(hStmt, i, SQL_C_CHAR, false, false));
  2802                             }
  2803                         } else if (dataLen == SQL_NULL_DATA) {
  2804                             LOG("SQLGetData: Column %d is NULL (CHAR)", i);

Lines 2805-2828

  2805                             row.append(py::none());
  2806                         } else if (dataLen == 0) {
  2807                             row.append(py::str(""));
  2808                         } else if (dataLen == SQL_NO_TOTAL) {
! 2809                             LOG("SQLGetData: Cannot determine data length "
! 2810                                 "(SQL_NO_TOTAL) for column %d (SQL_CHAR), "
! 2811                                 "returning NULL",
! 2812                                 i);
  2813                             row.append(py::none());
  2814                         } else if (dataLen < 0) {
! 2815                             LOG("SQLGetData: Unexpected negative data length "
! 2816                                 "for column %d - dataType=%d, dataLen=%ld",
! 2817                                 i, dataType, (long)dataLen);
! 2818                             ThrowStdException("SQLGetData returned an unexpected negative "
! 2819                                               "data length");
  2820                         }
  2821                     } else {
! 2822                         LOG("SQLGetData: Error retrieving data for column %d "
! 2823                             "(SQL_CHAR) - SQLRETURN=%d, returning NULL",
! 2824                             i, ret);
  2825                         row.append(py::none());
  2826                     }
  2827                 }
  2828                 break;

Lines 2875-2898

  2875                             row.append(py::none());
  2876                         } else if (dataLen == 0) {
  2877                             row.append(py::str(""));
  2878                         } else if (dataLen == SQL_NO_TOTAL) {
! 2879                             LOG("SQLGetData: Cannot determine NVARCHAR data "
! 2880                                 "length (SQL_NO_TOTAL) for column %d, "
! 2881                                 "returning NULL",
! 2882                                 i);
  2883                             row.append(py::none());
  2884                         } else if (dataLen < 0) {
! 2885                             LOG("SQLGetData: Unexpected negative data length "
! 2886                                 "for column %d (NVARCHAR) - dataLen=%ld",
! 2887                                 i, (long)dataLen);
! 2888                             ThrowStdException("SQLGetData returned an unexpected negative "
! 2889                                               "data length");
  2890                         }
  2891                     } else {
! 2892                         LOG("SQLGetData: Error retrieving data for column %d "
! 2893                             "(NVARCHAR) - SQLRETURN=%d",
! 2894                             i, ret);
  2895                         row.append(py::none());
  2896                     }
  2897                 }
  2898                 break;

Lines 2912-2922

  2912                 ret = SQLGetData_ptr(hStmt, i, SQL_C_SHORT, &smallIntValue, 0, NULL);
  2913                 if (SQL_SUCCEEDED(ret)) {
  2914                     row.append(static_cast<int>(smallIntValue));
  2915                 } else {
! 2916                     LOG("SQLGetData: Error retrieving SQL_SMALLINT for column "
! 2917                         "%d - SQLRETURN=%d",
! 2918                         i, ret);
  2919                     row.append(py::none());
  2920                 }
  2921                 break;
  2922             }

Lines 2925-2935

  2925                 ret = SQLGetData_ptr(hStmt, i, SQL_C_FLOAT, &realValue, 0, NULL);
  2926                 if (SQL_SUCCEEDED(ret)) {
  2927                     row.append(realValue);
  2928                 } else {
! 2929                     LOG("SQLGetData: Error retrieving SQL_REAL for column %d - "
! 2930                         "SQLRETURN=%d",
! 2931                         i, ret);
  2932                     row.append(py::none());
  2933                 }
  2934                 break;
  2935             }

Lines 2977-2993

  2977                             PythonObjectCache::get_decimal_class()(py::str(cnum, safeLen));
  2978                         row.append(decimalObj);
  2979                     } catch (const py::error_already_set& e) {
  2980                         // If conversion fails, append None
! 2981                         LOG("SQLGetData: Error converting to decimal for "
! 2982                             "column %d - %s",
! 2983                             i, e.what());
  2984                         row.append(py::none());
  2985                     }
  2986                 } else {
! 2987                     LOG("SQLGetData: Error retrieving SQL_NUMERIC/DECIMAL for "
! 2988                         "column %d - SQLRETURN=%d",
! 2989                         i, ret);
  2990                     row.append(py::none());
  2991                 }
  2992                 break;
  2993             }

Lines 2998-3008

  2998                 ret = SQLGetData_ptr(hStmt, i, SQL_C_DOUBLE, &doubleValue, 0, NULL);
  2999                 if (SQL_SUCCEEDED(ret)) {
  3000                     row.append(doubleValue);
  3001                 } else {
! 3002                     LOG("SQLGetData: Error retrieving SQL_DOUBLE/FLOAT for "
! 3003                         "column %d - SQLRETURN=%d",
! 3004                         i, ret);
  3005                     row.append(py::none());
  3006                 }
  3007                 break;
  3008             }

Lines 3011-3021

  3011                 ret = SQLGetData_ptr(hStmt, i, SQL_C_SBIGINT, &bigintValue, 0, NULL);
  3012                 if (SQL_SUCCEEDED(ret)) {
  3013                     row.append(static_cast<long long>(bigintValue));
  3014                 } else {
! 3015                     LOG("SQLGetData: Error retrieving SQL_BIGINT for column %d "
! 3016                         "- SQLRETURN=%d",
! 3017                         i, ret);
  3018                     row.append(py::none());
  3019                 }
  3020                 break;
  3021             }

Lines 3040-3050

  3040                 if (SQL_SUCCEEDED(ret)) {
  3041                     row.append(PythonObjectCache::get_time_class()(timeValue.hour, timeValue.minute,
  3042                                                                    timeValue.second));
  3043                 } else {
! 3044                     LOG("SQLGetData: Error retrieving SQL_TYPE_TIME for column "
! 3045                         "%d - SQLRETURN=%d",
! 3046                         i, ret);
  3047                     row.append(py::none());
  3048                 }
  3049                 break;
  3050             }

Lines 3060-3070

  3060                         timestampValue.hour, timestampValue.minute, timestampValue.second,
  3061                         timestampValue.fraction / 1000  // Convert back ns to µs
  3062                         ));
  3063                 } else {
! 3064                     LOG("SQLGetData: Error retrieving SQL_TYPE_TIMESTAMP for "
! 3065                         "column %d - SQLRETURN=%d",
! 3066                         i, ret);
  3067                     row.append(py::none());
  3068                 }
  3069                 break;
  3070             }

Lines 3084-3093

  3084                     int totalMinutes = dtoValue.timezone_hour * 60 + dtoValue.timezone_minute;
  3085                     // Validating offset
  3086                     if (totalMinutes < -24 * 60 || totalMinutes > 24 * 60) {
  3087                         std::ostringstream oss;
! 3088                         oss << "Invalid timezone offset from "
! 3089                                "SQL_SS_TIMESTAMPOFFSET_STRUCT: "
  3090                             << totalMinutes << " minutes for column " << i;
  3091                         ThrowStdException(oss.str());
  3092                     }
  3093                     // Convert fraction from ns to µs

Lines 3099-3109

  3099                         dtoValue.year, dtoValue.month, dtoValue.day, dtoValue.hour, dtoValue.minute,
  3100                         dtoValue.second, microseconds, tzinfo);
  3101                     row.append(py_dt);
  3102                 } else {
! 3103                     LOG("SQLGetData: Error fetching DATETIMEOFFSET for column "
! 3104                         "%d - SQLRETURN=%d, indicator=%ld",
! 3105                         i, ret, (long)indicator);
  3106                     row.append(py::none());
  3107                 }
  3108                 break;
  3109             }

Lines 3137-3154

  3137                         } else if (dataLen == 0) {
  3138                             row.append(py::bytes(""));
  3139                         } else {
  3140                             std::ostringstream oss;
! 3141                             oss << "Unexpected negative length (" << dataLen
! 3142                                 << ") returned by SQLGetData. ColumnID=" << i
! 3143                                 << ", dataType=" << dataType << ", bufferSize=" << columnSize;
  3144                             LOG("SQLGetData: %s", oss.str().c_str());
  3145                             ThrowStdException(oss.str());
  3146                         }
  3147                     } else {
! 3148                         LOG("SQLGetData: Error retrieving VARBINARY data for "
! 3149                             "column %d - SQLRETURN=%d",
! 3150                             i, ret);
  3151                         row.append(py::none());
  3152                     }
  3153                 }
  3154                 break;

Lines 3158-3168

  3158                 ret = SQLGetData_ptr(hStmt, i, SQL_C_TINYINT, &tinyIntValue, 0, NULL);
  3159                 if (SQL_SUCCEEDED(ret)) {
  3160                     row.append(static_cast<int>(tinyIntValue));
  3161                 } else {
! 3162                     LOG("SQLGetData: Error retrieving SQL_TINYINT for column "
! 3163                         "%d - SQLRETURN=%d",
! 3164                         i, ret);
  3165                     row.append(py::none());
  3166                 }
  3167                 break;
  3168             }

Lines 3171-3181

  3171                 ret = SQLGetData_ptr(hStmt, i, SQL_C_BIT, &bitValue, 0, NULL);
  3172                 if (SQL_SUCCEEDED(ret)) {
  3173                     row.append(static_cast<bool>(bitValue));
  3174                 } else {
! 3175                     LOG("SQLGetData: Error retrieving SQL_BIT for column %d - "
! 3176                         "SQLRETURN=%d",
! 3177                         i, ret);
  3178                     row.append(py::none());
  3179                 }
  3180                 break;
  3181             }

Lines 3204-3214

  3204                     row.append(uuid_obj);
  3205                 } else if (indicator == SQL_NULL_DATA) {
  3206                     row.append(py::none());
  3207                 } else {
! 3208                     LOG("SQLGetData: Error retrieving SQL_GUID for column %d - "
! 3209                         "SQLRETURN=%d, indicator=%ld",
! 3210                         i, ret, (long)indicator);
  3211                     row.append(py::none());
  3212                 }
  3213                 break;
  3214             }

Lines 3229-3238

  3229                               SQLLEN FetchOffset, py::list& row_data) {
  3230     LOG("SQLFetchScroll_wrap: Fetching with scroll orientation=%d, offset=%ld", FetchOrientation,
  3231         (long)FetchOffset);
  3232     if (!SQLFetchScroll_ptr) {
! 3233         LOG("SQLFetchScroll_wrap: Function pointer not initialized. Loading "
! 3234             "the driver.");
  3235         DriverLoader::getInstance().loadDriver();  // Load the driver
  3236     }
  3237 
  3238     // Unbind any columns from previous fetch operations to avoid memory

Lines 3532-3540

  3532         bool released;
  3533         RowGuard() : row(nullptr), released(false) {}
  3534         ~RowGuard() {
  3535             if (row && !released)
! 3536                 Py_DECREF(row);
  3537         }
  3538         void release() { released = true; }
  3539     };

Lines 3562-3572

  3562                 PyList_SET_ITEM(row, col - 1, Py_None);
  3563                 continue;
  3564             }
  3565             if (dataLen == SQL_NO_TOTAL) {
! 3566                 LOG("Cannot determine the length of the data. Returning NULL "
! 3567                     "value instead. Column ID - {}",
! 3568                     col);
  3569                 Py_INCREF(Py_None);
  3570                 PyList_SET_ITEM(row, col - 1, Py_None);
  3571                 continue;
  3572             }

Lines 3588-3598

  3588 
  3589             // Additional validation for complex types
  3590             if (dataLen == 0) {
  3591                 // Handle zero-length (non-NULL) data for complex types
! 3592                 LOG("Column data length is 0 for complex datatype. Setting "
! 3593                     "None to the result row. Column ID - {}",
! 3594                     col);
  3595                 Py_INCREF(Py_None);
  3596                 PyList_SET_ITEM(row, col - 1, Py_None);
  3597                 continue;
  3598             } else if (dataLen < 0) {

Lines 3597-3607

  3597                 continue;
  3598             } else if (dataLen < 0) {
  3599                 // Negative value is unexpected, log column index, SQL type &
  3600                 // raise exception
! 3601                 LOG("FetchBatchData: Unexpected negative data length - "
! 3602                     "column=%d, SQL_type=%d, dataLen=%ld",
! 3603                     col, dataType, (long)dataLen);
  3604                 ThrowStdException("Unexpected negative data length, check logs for details");
  3605             }
  3606             assert(dataLen > 0 && "Data length must be > 0");

Lines 3728-3737

  3728         // Row is now fully populated - add it to results list atomically
  3729         // This ensures no partially-filled rows exist in the list on exception
  3730         if (PyList_Append(rowsList, row) < 0) {
  3731             // RowGuard will clean up row automatically
! 3732             throw std::runtime_error("Failed to append row to results list - "
! 3733                                      "memory allocation failure");
  3734         }
  3735         // PyList_Append increments refcount, so we can release our reference
  3736         // Mark guard as released so destructor doesn't double-free
  3737         guard.release();

Lines 3879-3887

  3879             ret = SQLFetch_ptr(hStmt);
  3880             if (ret == SQL_NO_DATA)
  3881                 break;
  3882             if (!SQL_SUCCEEDED(ret))
! 3883                 return ret;
  3884 
  3885             py::list row;
  3886             SQLGetData_wrap(StatementHandle, numCols,
  3887                             row);  // <-- streams LOBs correctly

Lines 4009-4017

  4009             ret = SQLFetch_ptr(hStmt);
  4010             if (ret == SQL_NO_DATA)
  4011                 break;
  4012             if (!SQL_SUCCEEDED(ret))
! 4013                 return ret;
  4014 
  4015             py::list row;
  4016             SQLGetData_wrap(StatementHandle, numCols,
  4017                             row);  // <-- streams LOBs correctly

Lines 4084-4093

  4084 // Wrap SQLMoreResults
  4085 SQLRETURN SQLMoreResults_wrap(SqlHandlePtr StatementHandle) {
  4086     LOG("SQLMoreResults_wrap: Check for more results");
  4087     if (!SQLMoreResults_ptr) {
! 4088         LOG("SQLMoreResults_wrap: Function pointer not initialized. Loading "
! 4089             "the driver.");
  4090         DriverLoader::getInstance().loadDriver();  // Load the driver
  4091     }
  4092 
  4093     return SQLMoreResults_ptr(StatementHandle->get());

Lines 4096-4105

  4096 // Wrap SQLFreeHandle
  4097 SQLRETURN SQLFreeHandle_wrap(SQLSMALLINT HandleType, SqlHandlePtr Handle) {
  4098     LOG("SQLFreeHandle_wrap: Free SQL handle type=%d", HandleType);
  4099     if (!SQLAllocHandle_ptr) {
! 4100         LOG("SQLFreeHandle_wrap: Function pointer not initialized. Loading the "
! 4101             "driver.");
  4102         DriverLoader::getInstance().loadDriver();  // Load the driver
  4103     }
  4104 
  4105     SQLRETURN ret = SQLFreeHandle_ptr(HandleType, Handle->get());

Lines 4113-4122

  4113 // Wrap SQLRowCount
  4114 SQLLEN SQLRowCount_wrap(SqlHandlePtr StatementHandle) {
  4115     LOG("SQLRowCount_wrap: Get number of rows affected by last execute");
  4116     if (!SQLRowCount_ptr) {
! 4117         LOG("SQLRowCount_wrap: Function pointer not initialized. Loading the "
! 4118             "driver.");
  4119         DriverLoader::getInstance().loadDriver();  // Load the driver
  4120     }
  4121 
  4122     SQLLEN rowCount;

Lines 4237-4246

  4237           "Set the decimal separator character");
  4238     m.def(
  4239         "DDBCSQLSetStmtAttr",
  4240         [](SqlHandlePtr stmt, SQLINTEGER attr, SQLPOINTER value) {
! 4241             return SQLSetStmtAttr_ptr(stmt->get(), attr, value, 0);
! 4242         },
  4243         "Set statement attributes");
  4244     m.def("DDBCSQLGetTypeInfo", &SQLGetTypeInfo_Wrapper,
  4245           "Returns information about the data types that are supported by the "
  4246           "data source",

mssql_python/pybind/ddbc_bindings.h

  77 }
  78 
  79 inline std::wstring SQLWCHARToWString(const SQLWCHAR* sqlwStr, size_t length = SQL_NTS) {
  80     if (!sqlwStr)
! 81         return std::wstring();
  82     if (length == SQL_NTS) {
  83         size_t i = 0;
  84         while (sqlwStr[i] != 0)
  85             ++i;

  394 ErrorInfo SQLCheckError_Wrap(SQLSMALLINT handleType, SqlHandlePtr handle, SQLRETURN retcode);
  395 
  396 inline std::string WideToUTF8(const std::wstring& wstr) {
  397     if (wstr.empty())
! 398         return {};
  399 
  400 #if defined(_WIN32)
  401     int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.size()),
  402                                           nullptr, 0, nullptr, nullptr);

  442 }
  443 
  444 inline std::wstring Utf8ToWString(const std::string& str) {
  445     if (str.empty())
! 446         return {};
  447 #if defined(_WIN32)
  448     int size_needed =
  449         MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.size()), nullptr, 0);
  450     if (size_needed == 0) {

  717             PyList_SET_ITEM(row, col - 1, pyStr);
  718         }
  719     } else {
  720         // Slow path: LOB data requires separate fetch call
! 721         PyList_SET_ITEM(row, col - 1,
! 722                         FetchLobColumnData(hStmt, col, SQL_C_CHAR, false, false).release().ptr());
  723     }
  724 }
  725 
  726 // Process SQL NCHAR/NVARCHAR (wide/Unicode string) column into Python str

  784         }
  785 #endif
  786     } else {
  787         // Slow path: LOB data requires separate fetch call
! 788         PyList_SET_ITEM(row, col - 1,
! 789                         FetchLobColumnData(hStmt, col, SQL_C_WCHAR, true, false).release().ptr());
  790     }
  791 }
  792 
  793 // Process SQL BINARY/VARBINARY (binary data) column into Python bytes

  824             PyList_SET_ITEM(row, col - 1, pyBytes);
  825         }
  826     } else {
  827         // Slow path: LOB data requires separate fetch call
! 828         PyList_SET_ITEM(row, col - 1,
! 829                         FetchLobColumnData(hStmt, col, SQL_C_BINARY, false, true).release().ptr());
  830     }
  831 }
  832 
  833 }  // namespace ColumnProcessors

mssql_python/pybind/logger_bridge.cpp

Lines 174-184

  174     constexpr size_t MAX_LOG_SIZE = 4095;  // Keep same limit for consistency
  175     if (complete_message.size() > MAX_LOG_SIZE) {
  176         // Use stderr to notify about truncation (logging may be the truncated
  177         // call itself)
! 178         std::cerr << "[MSSQL-Python] Warning: Log message truncated from "
! 179                   << complete_message.size() << " bytes to " << MAX_LOG_SIZE << " bytes at " << file
! 180                   << ":" << line << std::endl;
  181         complete_message.resize(MAX_LOG_SIZE);
  182     }
  183 
  184     // Lock for Python call (minimize critical section)


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.row.py: 66.2%
mssql_python.pybind.ddbc_bindings.cpp: 67.1%
mssql_python.helpers.py: 67.5%
mssql_python.pybind.connection.connection.cpp: 74.7%
mssql_python.pybind.ddbc_bindings.h: 76.9%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 79.6%
mssql_python.connection.py: 82.5%
mssql_python.cursor.py: 83.6%

🔗 Quick Links