STYLE: Final Linting for all cpp and python files with Workflow by jahnvi480 · Pull Request #331 · microsoft/mssql-python
📊 Code Coverage Report
🔥 Diff Coverage65% |
🎯 Overall Coverage75% |
📈 Total Lines Covered: |
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*>(¶mInfos[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%