-
Notifications
You must be signed in to change notification settings - Fork 27
STYLE: Final Linting for all cpp and python files with Workflow #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
devskim found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request introduces comprehensive linting and code formatting infrastructure for both Python and C++ code, along with automated workflow checks. The changes consist primarily of automated formatting applied consistently across the codebase.
Key Changes:
- Added GitHub Actions workflow for automated linting checks (
.github/workflows/lint-check.yml) - Introduced linting configuration files (
.flake8,.clang-format,pyproject.toml) - Applied consistent formatting to all Python test files and source modules
- Applied clang-format to C++ pybind11 files with LLVM style conventions
- Updated
requirements.txtto include linting tools
Reviewed Changes
Copilot reviewed 47 out of 51 changed files in this pull request and generated 47 comments.
Show a summary per file
| File | Description |
|---|---|
.flake8, pyproject.toml |
Added Python linting configuration with 100-character line limit |
.clang-format |
Added C++ formatting rules using LLVM style with Microsoft modifications |
requirements.txt |
Added linting dependencies (black, flake8, pylint, cpplint, mypy) |
tests/test_*.py |
Applied consistent Python formatting (quotes, spacing, line breaks) |
mssql_python/*.py |
Applied formatting to source modules |
mssql_python/pybind/*.h, *.cpp |
Applied clang-format to C++ headers and implementations |
The formatting changes are consistent and maintain code functionality. All modifications appear to be purely stylistic with no semantic changes to the codebase. The linting infrastructure will help maintain code quality going forward.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -756,16 +715,12 @@ def test_longvarchar(cursor, db_connection): | |||
| assert ( | |||
| cursor.fetchone() == None | |||
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing for None should use the 'is' operator.
| @@ -795,16 +748,12 @@ def test_longwvarchar(cursor, db_connection): | |||
| assert ( | |||
| cursor.fetchone() == None | |||
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing for None should use the 'is' operator.
| @@ -2554,9 +2444,7 @@ def test_cursor_context_manager_exception_handling(db_connection): | |||
| with pytest.raises(ValueError): | |||
| with db_connection.cursor() as cursor: | |||
| cursor_ref = cursor | |||
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable cursor_ref is not used.
| assert ( | ||
| cursor in db_connection._cursors | ||
| ), "Cursor from execute() not tracked by connection" | ||
| assert cursor in db_connection._cursors, "Cursor from execute() not tracked by connection" | ||
|
|
||
| # Test with data modification and verify it requires commit | ||
| if not db_connection.autocommit: | ||
| drop_table_if_exists(db_connection.cursor(), "#pytest_test_execute") | ||
| cursor1 = db_connection.execute( |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable cursor1 is not used.
| cursor2 = db_connection.execute( | ||
| "INSERT INTO #pytest_test_execute VALUES (1, 'test_value')" | ||
| ) | ||
| cursor2 = db_connection.execute("INSERT INTO #pytest_test_execute VALUES (1, 'test_value')") |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable cursor2 is not used.
| db_connection.set_attr( | ||
| mssql_python.SQL_ATTR_CONNECTION_TIMEOUT, 2147483647 | ||
| ) # Max int32 | ||
| db_connection.set_attr(mssql_python.SQL_ATTR_CONNECTION_TIMEOUT, 2147483647) # Max int32 | ||
| pass |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary 'pass' statement.
| f"Warning: Using fallback module file {module_files[0]} instead of " | ||
| f"{expected_module}" | ||
| ) | ||
| print(f"Warning: Using fallback module file {module_files[0]} instead of " f"{expected_module}") |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Print statement may execute during import.
| settings["ctype"] == ctype | ||
| ), f"Failed to set ctype for sqltype {sqltype}" | ||
| assert settings["encoding"] == encoding, f"Failed to set encoding for sqltype {sqltype}" | ||
| assert settings["ctype"] == ctype, f"Failed to set ctype for sqltype {sqltype}" | ||
|
|
||
| finally: | ||
| conn.close() |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instance of context-manager class Connection is closed in a finally block. Consider using 'with' statement.
| settings["ctype"] == ctype | ||
| ), f"Failed to set ctype for sqltype {sqltype}" | ||
| assert settings["encoding"] == encoding, f"Failed to set encoding for sqltype {sqltype}" | ||
| assert settings["ctype"] == ctype, f"Failed to set ctype for sqltype {sqltype}" | ||
|
|
||
| finally: | ||
| conn.close() |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instance of context-manager class Connection is closed in a finally block. Consider using 'with' statement.
| settings["ctype"] == ctype | ||
| ), f"Failed to set ctype for sqltype {sqltype}" | ||
| assert settings["encoding"] == encoding, f"Failed to set encoding for sqltype {sqltype}" | ||
| assert settings["ctype"] == ctype, f"Failed to set ctype for sqltype {sqltype}" | ||
|
|
||
| finally: | ||
| conn.close() |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instance of context-manager class Connection is closed in a finally block. Consider using 'with' statement.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/init.pyLines 186-194 186 _original_module_setattr = sys.modules[__name__].__setattr__
187
188
189 def _custom_setattr(name, value):
! 190 if name == "lowercase":
191 with _settings_lock:
192 _settings.lowercase = bool(value)
193 # Update the module's lowercase variable
194 _original_module_setattr(name, _settings.lowercase)mssql_python/connection.pyLines 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 numericmssql_python/cursor.pyLines 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 inferenceLines 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 methodsLines 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.pyLines 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.pyLines 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 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.cppLines 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.cppLines 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 lockLines 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.cppLines 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 ARCHITECTURELines 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* inLines 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 µsLines 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 memoryLines 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 correctlyLines 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 correctlyLines 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.hmssql_python/pybind/logger_bridge.cppLines 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
|
Work Item / Issue Reference
Summary
This pull request introduces a comprehensive linting workflow for both Python and C++ code, updates code style configuration files, and refactors the
benchmarks/bench_mssql.pyscript for improved readability and consistency. The most significant changes are grouped below.Linting Workflow & Configuration
.github/workflows/lint-check.yml) to automate linting and formatting checks for Python and C++ files, including job summaries and failure handling..flake8for Python linting configuration, specifying line length, ignored warnings, excluded directories, and per-file ignores..clang-formatto define C++ code style, switching to LLVM style with Microsoft modifications, reducing column limit, and adding detailed alignment and spacing rules.Benchmark Script Refactoring (
benchmarks/bench_mssql.py)These changes collectively improve code quality, maintainability, and ensure consistent style enforcement across the project.