🌐 AI搜索 & 代理 主页
Skip to content

Conversation

@VIZZARD-X
Copy link
Contributor

@VIZZARD-X VIZZARD-X commented Nov 24, 2025

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 return None when 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 returning None.

This patch:

  • Adds the fallback for Oracle.
  • Matches the expected backend contract.
  • Has no behavioral impact on successful queries.
  • Only affects debug/inspection paths.
  • Requires no new tests (ticket metadata: Needs tests: no).

This completes the cleanup after #36380.

Checklist

  • This PR targets the main branch.
  • The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • I have checked the "Has patch" ticket flag in the Trac system.
  • I have added or updated relevant tests.
  • I have added or updated relevant docs, including release notes if applicable.
  • I have attached screenshots in both light and dark modes for any UI changes.

@VIZZARD-X VIZZARD-X force-pushed the ticket-36112-last-executed-query-fallback branch from 4faedf2 to 55ab407 Compare November 25, 2025 15:47
@github-actions
Copy link

📊 Coverage Report for Changed Files

-------------
Diff Coverage
Diff: origin/main...HEAD, staged and unstaged changes
-------------
No lines with coverage information in this diff.
-------------


Note: 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.

@VIZZARD-X
Copy link
Contributor Author

Added the requested fallback tests for last_executed_query().
All Checks have passed. This PR is ready for review.


def last_executed_query(self, cursor, sql, params):
statement = getattr(cursor, "statement", None)
return statement or super().last_executed_query(cursor, sql, params)
Copy link
Member

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.

@VIZZARD-X VIZZARD-X force-pushed the ticket-36112-last-executed-query-fallback branch 8 times, most recently from db02b33 to 8a4b29d Compare November 27, 2025 06:26
@VIZZARD-X
Copy link
Contributor Author

Thanks for the review.

I've updated the patch to include a proper regression test under
tests/backends/oracle/, guarded to avoid import errors on non-Oracle
environments.

The test now exercises the real Oracle backend implementation and verifies the
intended fallback to BaseDatabaseOperations.last_executed_query() when
cursor.statement is missing.

All CI checks have passed. This should be ready for another look.

Comment on lines 7 to 35
# 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)
Copy link
Member

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.

Suggested change
# 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)
Copy link
Member

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.

@felixxm
Copy link
Member

felixxm commented Nov 27, 2025

The same behavior can be observe and should be fixed on PostgreSQL.

@VIZZARD-X VIZZARD-X force-pushed the ticket-36112-last-executed-query-fallback branch from 8a4b29d to 00b5872 Compare November 27, 2025 10:01
@VIZZARD-X VIZZARD-X force-pushed the ticket-36112-last-executed-query-fallback branch from 00b5872 to 691979d Compare November 27, 2025 10:11
@VIZZARD-X
Copy link
Contributor Author

Done.
Per your feedback, I've updated the patch to:

  • Restore Oracle’s original statement = cursor.statement logic without unnecessary getattr().
  • Apply the same fallback behavior to the PostgreSQL backend.
  • Add backend-specific regression tests for both Oracle and PostgreSQL using the patterns you suggested.

Ensure that all backends now consistently fall back to BaseDatabaseOperations.last_executed_query() whenever the backend cannot provide an executed SQL string.

The changes are minimal and isolated to the fallback path only.
All CI checks were green previously, and this update keeps the patch aligned with the backend contract.

This should be ready for another review.
Thanks for your guidance.

Comment on lines +20 to +21
self.assertIsNotNone(result)
self.assertEqual(result, sql)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertIsNotNone is redundant

Suggested change
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):
Copy link
Member

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)
Copy link
Member

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

Suggested change
return statement or super().last_executed_query(cursor, sql, params)
return super().last_executed_query(cursor, sql, params) if statement is None else statement

@VIZZARD-X
Copy link
Contributor Author

I’m currently in my final university exam period, so I’ll be slower to update PRs for the next few days.
I will return to complete all pending changes right after exams conclude.
Thanks for your patience — I’ll follow up soon...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants