diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 9068d87a8974..dd957ec76595 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1629,9 +1629,12 @@ def execute_sql( cursor = self.connection.cursor() try: cursor.execute(sql, params) - except Exception: + except Exception as e: # Might fail for server-side cursors (e.g. connection closed) - cursor.close() + try: + cursor.close() + except DatabaseError: + raise e from None raise if result_type == ROW_COUNT: diff --git a/docs/releases/5.2.11.txt b/docs/releases/5.2.11.txt new file mode 100644 index 000000000000..9428663332fd --- /dev/null +++ b/docs/releases/5.2.11.txt @@ -0,0 +1,12 @@ +=========================== +Django 5.2.11 release notes +=========================== + +*Expected February 3, 2026* + +Django 5.2.11 fixes several bugs in 5.2.10. + +Bugfixes +======== + +* ... diff --git a/docs/releases/6.0.2.txt b/docs/releases/6.0.2.txt new file mode 100644 index 000000000000..c708f699bb04 --- /dev/null +++ b/docs/releases/6.0.2.txt @@ -0,0 +1,12 @@ +========================== +Django 6.0.2 release notes +========================== + +*Expected February 3, 2026* + +Django 6.0.2 fixes several bugs in 6.0.1. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 4dff9514c7b2..ce64c8ff0268 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -32,6 +32,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 6.0.2 6.0.1 6.0 @@ -40,6 +41,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 5.2.11 5.2.10 5.2.9 5.2.8 diff --git a/tests/queries/test_sqlcompiler.py b/tests/queries/test_sqlcompiler.py index 72a66ad07ca5..0f6f2fc10bc7 100644 --- a/tests/queries/test_sqlcompiler.py +++ b/tests/queries/test_sqlcompiler.py @@ -1,11 +1,14 @@ -from django.db import DEFAULT_DB_ALIAS, connection +from unittest import mock + +from django.db import DEFAULT_DB_ALIAS, DatabaseError, connection from django.db.models.sql import Query -from django.test import SimpleTestCase +from django.db.models.sql.compiler import SQLCompiler +from django.test import TestCase from .models import Item -class SQLCompilerTest(SimpleTestCase): +class SQLCompilerTest(TestCase): def test_repr(self): query = Query(Item) compiler = query.get_compiler(DEFAULT_DB_ALIAS, connection) @@ -15,3 +18,24 @@ def test_repr(self): f" " f"using='default'>", ) + + def test_execute_sql_suppresses_cursor_closing_failure_on_exception(self): + query = Query(Item) + compiler = SQLCompiler(query, connection, None) + cursor = mock.MagicMock() + + # When execution fails, the cursor may have been closed. + # Django's attempt to close it again will fail, and needs catching. + execute_err = DatabaseError("execute failed") + cursor.execute.side_effect = execute_err + cursor.close.side_effect = DatabaseError("close failed") + + with mock.patch.object(connection, "cursor", return_value=cursor): + with self.assertRaises(DatabaseError) as ctx: + compiler.execute_sql("SELECT 1", []) + + # There is no irrelevant context from trying to close a closed cursor. + exc = ctx.exception + self.assertIs(exc, execute_err) + self.assertIsNone(exc.__cause__) + self.assertTrue(exc.__suppress_context__)