-
-
Notifications
You must be signed in to change notification settings - Fork 33.3k
Fixed #36112 -- Fall back to BaseDatabaseOperations.last_executed_query() in Oracle backend. #20312
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?
Fixed #36112 -- Fall back to BaseDatabaseOperations.last_executed_query() in Oracle backend. #20312
Conversation
4faedf2 to
55ab407
Compare
📊 Coverage Report for Changed FilesNote: Missing lines are warnings only. Some lines may not be covered by SQLite tests as they are database-specific. For more information about code coverage on pull requests, see the contributing documentation. |
|
Added the requested fallback tests for |
tests/backends/base/test_base.py
Outdated
|
|
||
| def last_executed_query(self, cursor, sql, params): | ||
| statement = getattr(cursor, "statement", None) | ||
| return statement or super().last_executed_query(cursor, sql, params) |
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.
This is not a regression test. TBH it is not a test at all.
db02b33 to
8a4b29d
Compare
|
Thanks for the review. I've updated the patch to include a proper regression test under The test now exercises the real Oracle backend implementation and verifies the All CI checks have passed. This should be ready for another look. |
| # Oracle backend import guard. | ||
| # Importing oracle.operations will fail on | ||
| # environments without the oracledb driver. | ||
| try: | ||
| from django.db.backends.oracle.operations import DatabaseOperations | ||
| except Exception: | ||
| DatabaseOperations = None | ||
|
|
||
|
|
||
| @skipUnless(connection.vendor == "oracle", "Oracle-specific tests") | ||
| class TestLastExecutedQueryFallback(TestCase): | ||
| def test_last_executed_query_fallback(self): | ||
| if DatabaseOperations is None: | ||
| self.skipTest("Oracle backend not available") | ||
|
|
||
| class FakeCursor: | ||
| # Simulates a cursor lacking the `statement` attribute | ||
| pass | ||
|
|
||
| cursor = FakeCursor() | ||
| sql = "SELECT 1" | ||
| params = [] | ||
|
|
||
| ops = DatabaseOperations(None) | ||
|
|
||
| # Expected fallback from BaseDatabaseOperations | ||
| expected = BaseDatabaseOperations(None).last_executed_query(cursor, sql, params) | ||
|
|
||
| self.assertEqual(ops.last_executed_query(cursor, sql, params), expected) |
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.
Why do you need a new instance of DatabaseOperations? Also it should be enough to check that result of last_executed_query() is not None.
| # Oracle backend import guard. | |
| # Importing oracle.operations will fail on | |
| # environments without the oracledb driver. | |
| try: | |
| from django.db.backends.oracle.operations import DatabaseOperations | |
| except Exception: | |
| DatabaseOperations = None | |
| @skipUnless(connection.vendor == "oracle", "Oracle-specific tests") | |
| class TestLastExecutedQueryFallback(TestCase): | |
| def test_last_executed_query_fallback(self): | |
| if DatabaseOperations is None: | |
| self.skipTest("Oracle backend not available") | |
| class FakeCursor: | |
| # Simulates a cursor lacking the `statement` attribute | |
| pass | |
| cursor = FakeCursor() | |
| sql = "SELECT 1" | |
| params = [] | |
| ops = DatabaseOperations(None) | |
| # Expected fallback from BaseDatabaseOperations | |
| expected = BaseDatabaseOperations(None).last_executed_query(cursor, sql, params) | |
| self.assertEqual(ops.last_executed_query(cursor, sql, params), expected) | |
| @skipUnless(connection.vendor == "oracle", "Oracle specific tests") | |
| class TestLastExecutedQueryFallback(TestCase): | |
| def test_last_executed_query_fallback(self): | |
| class FakeCursor: | |
| # Simulates a cursor lacking the `statement` attribute | |
| pass | |
| cursor = FakeCursor() | |
| sql = "SELECT 1" | |
| params = [] | |
| self.assertIsNotNone(connection.ops.last_executed_query(cursor, sql, params)) |
We could also use a real cursor with an invalid SQL statement, e.g.
with connection.cursor() as cursor:
sql = "INVALID SQL"
params = []
cursor.execute(sql, params)
self.assertEqual(connection.ops.last_executed_query(cursor, sql, params), sql)
| # https://python-oracledb.readthedocs.io/en/latest/api_manual/cursor.html#Cursor.statement | ||
| # The DB API definition does not define this attribute. | ||
| statement = cursor.statement | ||
| statement = getattr(cursor, "statement", None) |
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.
As far as I'm aware it's unnecessary.
|
The same behavior can be observe and should be fixed on PostgreSQL. |
8a4b29d to
00b5872
Compare
…executed_query().
00b5872 to
691979d
Compare
|
Done.
Ensure that all backends now consistently fall back to The changes are minimal and isolated to the fallback path only. This should be ready for another review. |
| self.assertIsNotNone(result) | ||
| self.assertEqual(result, sql) |
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.
assertIsNotNone is redundant
| self.assertIsNotNone(result) | |
| self.assertEqual(result, sql) | |
| self.assertEqual(result, sql) |
|
|
||
| @skipUnless(connection.vendor == "oracle", "Oracle-specific tests") | ||
| class TestLastExecutedQueryFallback(TestCase): | ||
| def test_last_executed_query_fallback(self): |
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.
As far as I'm aware, this test should pass for all database backends, so we can move it to the
tests.backends.base.test_operations.DatabaseOperationTests class.
| key, force_str(params[key], errors="replace") | ||
| ) | ||
| return statement | ||
| return statement or super().last_executed_query(cursor, sql, params) |
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.
I think we should use super().last_executed_query() only when statement is None
| return statement or super().last_executed_query(cursor, sql, params) | |
| return super().last_executed_query(cursor, sql, params) if statement is None else statement |
|
I’m currently in my final university exam period, so I’ll be slower to update PRs for the next few days. |
Trac ticket number
ticket-36112
Branch description
This PR fixes the issue described in ticket #36112, where
DatabaseOperations.last_executed_query()in the Oracle backend may returnNonewhen a query fails. This causes SQL formatting to crash under--debug-sql.Following the core team’s guidance on the ticket (Jacob Walls & Mariusz Felisiak), backend implementations should fall back to
super().last_executed_query()rather than returningNone.This patch:
This completes the cleanup after #36380.
Checklist
mainbranch.