Fixed #32403 -- Fixed re-raising DatabaseErrors when using only 'postgres' database.

Thanks Kazantcev Andrey for the report.

Regression in f48f671223.
This commit is contained in:
Mariusz Felisiak 2021-02-02 21:34:36 +01:00 committed by GitHub
parent f9cfd346f0
commit f131841c60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 2 deletions

View File

@ -332,6 +332,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
yield cursor yield cursor
finally: finally:
conn.close() conn.close()
break
else:
raise
@cached_property @cached_property
def pg_version(self): def pg_version(self):

View File

@ -9,4 +9,6 @@ Django 3.1.7 fixes several bugs in 3.1.6.
Bugfixes Bugfixes
======== ========
* ... * Fixed a regression in Django 3.1 that caused ``RuntimeError`` instead of
connection errors when using only the ``'postgres'`` database
(:ticket:`32403`).

View File

@ -1,9 +1,10 @@
import copy
import unittest import unittest
from io import StringIO from io import StringIO
from unittest import mock from unittest import mock
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import DatabaseError, connection, connections from django.db import DEFAULT_DB_ALIAS, DatabaseError, connection, connections
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
@ -54,6 +55,53 @@ class Tests(TestCase):
self.assertIsNone(cursor.db.connection) self.assertIsNone(cursor.db.connection)
self.assertIsNotNone(cursor.db.settings_dict['NAME']) self.assertIsNotNone(cursor.db.settings_dict['NAME'])
self.assertEqual(cursor.db.settings_dict['NAME'], connections['other'].settings_dict['NAME']) self.assertEqual(cursor.db.settings_dict['NAME'], connections['other'].settings_dict['NAME'])
# Cursor is yielded only for the first PostgreSQL database.
with self.assertWarnsMessage(RuntimeWarning, msg):
with mock.patch(
'django.db.backends.base.base.BaseDatabaseWrapper.connect',
side_effect=mocked_connect,
autospec=True,
):
with connection._nodb_cursor() as cursor:
self.assertIs(cursor.closed, False)
self.assertIsNotNone(cursor.db.connection)
def test_nodb_cursor_raises_postgres_authentication_failure(self):
"""
_nodb_cursor() re-raises authentication failure to the 'postgres' db
when other connection to the PostgreSQL database isn't available.
"""
def mocked_connect(self):
raise DatabaseError()
def mocked_all(self):
test_connection = copy.copy(connections[DEFAULT_DB_ALIAS])
test_connection.settings_dict = copy.deepcopy(connection.settings_dict)
test_connection.settings_dict['NAME'] = 'postgres'
return [test_connection]
msg = (
"Normally Django will use a connection to the 'postgres' database "
"to avoid running initialization queries against the production "
"database when it's not needed (for example, when running tests). "
"Django was unable to create a connection to the 'postgres' "
"database and will use the first PostgreSQL database instead."
)
with self.assertWarnsMessage(RuntimeWarning, msg):
mocker_connections_all = mock.patch(
'django.utils.connection.BaseConnectionHandler.all',
side_effect=mocked_all,
autospec=True,
)
mocker_connect = mock.patch(
'django.db.backends.base.base.BaseDatabaseWrapper.connect',
side_effect=mocked_connect,
autospec=True,
)
with mocker_connections_all, mocker_connect:
with self.assertRaises(DatabaseError):
with connection._nodb_cursor():
pass
def test_database_name_too_long(self): def test_database_name_too_long(self):
from django.db.backends.postgresql.base import DatabaseWrapper from django.db.backends.postgresql.base import DatabaseWrapper