FIX: GH-Issue (#532) - Login failures are raised as RuntimeError instead of a mssql_python exception#562
Open
gargsaumya wants to merge 1 commit into
Open
FIX: GH-Issue (#532) - Login failures are raised as RuntimeError instead of a mssql_python exception#562gargsaumya wants to merge 1 commit into
gargsaumya wants to merge 1 commit into
Conversation
Login failures and other connection-time errors raised by the C++ pybind layer were surfacing as plain RuntimeError instead of a mssql_python exception, making it impossible for callers to catch them via the DB-API 2.0 exception hierarchy. Connection::checkError() now embeds the SQLSTATE code in the thrown message (SQLSTATE:XXXXX:<msg>) so the Python layer can map it to the correct exception class via sqlstate_to_exception() -- consistent with how cursor-level errors are already handled in helpers.py. The new _raise_connection_error() helper is applied to all four connection operations that go through checkError(): connect, commit, rollback, and set_autocommit. Note: when PR #526 (simdutf) merges, the two WideToUTF8() calls in connection.cpp::checkError() will need updating to utf16LeToUtf8Alloc(). Fixes #532
| side_effect=RuntimeError("SQLSTATE:28000:Login failed for user 'baduser'."), | ||
| ): | ||
| with pytest.raises(OperationalError) as exc_info: | ||
| connect("Server=localhost;Database=mydb;UID=baduser;PWD=wrongpassword;") |
| ), | ||
| ): | ||
| with pytest.raises(OperationalError) as exc_info: | ||
| connect("Server=localhost;Database=mydb;UID=u;PWD=p;") |
| side_effect=RuntimeError("Connection handle not allocated"), | ||
| ): | ||
| with pytest.raises(OperationalError) as exc_info: | ||
| connect("Server=localhost;Database=mydb;UID=u;PWD=p;") |
| mock_conn.commit.side_effect = RuntimeError("SQLSTATE:08S01:Communication link failure") | ||
| mock_conn.get_autocommit.return_value = False | ||
| with patch("mssql_python.connection.ddbc_bindings.Connection", return_value=mock_conn): | ||
| conn = connect("Server=localhost;Database=mydb;UID=u;PWD=p;") |
| mock_conn2.rollback.side_effect = RuntimeError("SQLSTATE:08S01:Communication link failure") | ||
| mock_conn2.get_autocommit.return_value = False | ||
| with patch("mssql_python.connection.ddbc_bindings.Connection", return_value=mock_conn2): | ||
| conn2 = connect("Server=localhost;Database=mydb;UID=u;PWD=p;") |
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/connection.pyLines 73-81 73 sqlstate, ddbc_error = match.group(1), match.group(2)
74 exc = sqlstate_to_exception(sqlstate, ddbc_error)
75 if exc is None:
76 logger.error("Unknown SQLSTATE %s, raising DatabaseError", sqlstate)
! 77 raise DatabaseError(
78 driver_error=f"An error occurred with SQLSTATE code: {sqlstate}",
79 ddbc_error=ddbc_error,
80 ) from None
81 logger.error("Connection error (SQLSTATE %s): %s", sqlstate, ddbc_error)Lines 526-535 526 DatabaseError: If there is an error while setting the autocommit mode.
527 """
528 try:
529 self._conn.set_autocommit(value)
! 530 except RuntimeError as e:
! 531 _raise_connection_error(e)
532
533 def setencoding(self, encoding: Optional[str] = None, ctype: Optional[int] = None) -> None:
534 """
535 Sets the text encoding for SQL statements and text parameters.📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 67.9%
mssql_python.row.py: 70.5%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 74.6%
mssql_python.pybind.connection.connection.cpp: 76.2%
mssql_python.__init__.py: 77.3%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 79.6%
mssql_python.connection.py: 85.3%🔗 Quick Links
|
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request addresses GitHub Issue #532 by ensuring connection-layer failures raised from the native pybind layer as RuntimeError are consistently mapped to the appropriate DB-API 2.0 mssql_python exception types using SQLSTATE codes, improving predictability for scenarios like login/token failures and transaction operations.
Changes:
- Add Python-side
_raise_connection_error()to parseRuntimeErrormessages with embedded SQLSTATE and raise the mapped DB-API exception (with an OperationalError fallback when no SQLSTATE is present). - Wrap native connection initialization,
setautocommit(),commit(), androllback()to route pybindRuntimeErrorthrough the new mapping logic. - Update native
Connection::checkError()to include SQLSTATE in thrown error strings and add a regression test covering connect/commit/rollback mapping behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
mssql_python/connection.py |
Adds SQLSTATE parsing + exception mapping helper and uses it to wrap key connection lifecycle calls. |
mssql_python/pybind/connection/connection.cpp |
Prefixes checkError() exceptions with SQLSTATE to enable Python-side parsing/mapping. |
tests/test_006_exceptions.py |
Adds regression coverage for mapping pybind RuntimeError to DB-API exceptions during connect/commit/rollback. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+182
to
+186
| // Format: "SQLSTATE:XXXXX:<odbc_error_message>" — parsed by _raise_connection_error() | ||
| ErrorInfo err = SQLCheckError_Wrap(SQL_HANDLE_DBC, _dbcHandle, ret); | ||
| std::string sqlState = WideToUTF8(err.sqlState); | ||
| std::string errorMsg = WideToUTF8(err.ddbcErrorMsg); | ||
| ThrowStdException(errorMsg); | ||
| ThrowStdException("SQLSTATE:" + sqlState + ":" + errorMsg); |
Comment on lines
+61
to
+62
| _SQLSTATE_RE = re.compile(r"^SQLSTATE:([A-Z0-9]{5}):(.*)", re.DOTALL) | ||
|
|
| connect("Server=localhost;Database=mydb;UID=u;PWD=p;") | ||
| assert "no default driver" in exc_info.value.ddbc_error | ||
| assert not isinstance(exc_info.value, RuntimeError) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work Item / Issue Reference
Summary
This pull request improves error handling in the
mssql_pythonconnection layer by ensuring that low-level runtime errors from the C++ pybind layer are consistently mapped to the correct DB-API 2.0 exceptions using SQLSTATE codes. This makes error reporting more robust and predictable, especially during connection, commit, and rollback operations. The changes also add comprehensive tests to verify this behavior.Error handling improvements:
_raise_connection_errorfunction inconnection.pyto parseRuntimeErrormessages from the C++ layer, extract SQLSTATE codes, and map them to appropriate DB-API exceptions usingsqlstate_to_exception. If no SQLSTATE is found, a fallbackOperationalErroris raised. ([mssql_python/connection.pyR61-R89](https://github.com/microsoft/mssql-python/pull/562/files#diff-29bb94de45aae51c23a6426d40133c28e4161e68769e08d046059c7186264e90R61-R89))setautocommit,commit, androllbackmethods inconnection.pyto wrap calls to the native layer in try/except blocks that utilize_raise_connection_error, ensuring consistent exception mapping throughout the connection lifecycle. ([[1]](https://github.com/microsoft/mssql-python/pull/562/files#diff-29bb94de45aae51c23a6426d40133c28e4161e68769e08d046059c7186264e90R362-R367),[[2]](https://github.com/microsoft/mssql-python/pull/562/files#diff-29bb94de45aae51c23a6426d40133c28e4161e68769e08d046059c7186264e90R528-R531),[[3]](https://github.com/microsoft/mssql-python/pull/562/files#diff-29bb94de45aae51c23a6426d40133c28e4161e68769e08d046059c7186264e90R1516-R1519),[[4]](https://github.com/microsoft/mssql-python/pull/562/files#diff-29bb94de45aae51c23a6426d40133c28e4161e68769e08d046059c7186264e90R1542-R1545))Connection::checkErrormethod to format error messages as"SQLSTATE:XXXXX:<odbc_error_message>", enabling the Python layer to parse and handle errors correctly. ([mssql_python/pybind/connection/connection.cppR182-R186](https://github.com/microsoft/mssql-python/pull/562/files#diff-eca696c13d997f510e5f9b16288ed1deb0ad132768c283eda1518f78edf9b6ecR182-R186))Testing enhancements:
test_connect_runtime_error_mapped_to_correct_dbapi_exception) to verify that connection, commit, and rollback errors from the C++ layer are mapped to the correct DB-API exceptions, including handling of unknown or missing SQLSTATE codes. ([tests/test_006_exceptions.pyR193-R257](https://github.com/microsoft/mssql-python/pull/562/files#diff-16f96f3facfc774c12b49a09116108763139a8a92ceef526fbb4f4f01600c6fdR193-R257))Dependency update:
sqlstate_to_exceptioninconnection.pyto support the new error mapping logic. ([mssql_python/connection.pyR41](https://github.com/microsoft/mssql-python/pull/562/files#diff-29bb94de45aae51c23a6426d40133c28e4161e68769e08d046059c7186264e90R41))