tests.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import unittest
  2. from io import StringIO
  3. from unittest import mock
  4. from django.core.exceptions import ImproperlyConfigured
  5. from django.db import DatabaseError, connection, connections
  6. from django.db.backends.base.base import BaseDatabaseWrapper
  7. from django.test import TestCase, override_settings
  8. @unittest.skipUnless(connection.vendor == 'postgresql', 'PostgreSQL tests')
  9. class Tests(TestCase):
  10. databases = {'default', 'other'}
  11. def test_nodb_cursor(self):
  12. """
  13. The _nodb_cursor() fallbacks to the default connection database when
  14. access to the 'postgres' database is not granted.
  15. """
  16. orig_connect = BaseDatabaseWrapper.connect
  17. def mocked_connect(self):
  18. if self.settings_dict['NAME'] is None:
  19. raise DatabaseError()
  20. return orig_connect(self)
  21. with connection._nodb_cursor() as cursor:
  22. self.assertIs(cursor.closed, False)
  23. self.assertIsNotNone(cursor.db.connection)
  24. self.assertIsNone(cursor.db.settings_dict['NAME'])
  25. self.assertIs(cursor.closed, True)
  26. self.assertIsNone(cursor.db.connection)
  27. # Now assume the 'postgres' db isn't available
  28. msg = (
  29. "Normally Django will use a connection to the 'postgres' database "
  30. "to avoid running initialization queries against the production "
  31. "database when it's not needed (for example, when running tests). "
  32. "Django was unable to create a connection to the 'postgres' "
  33. "database and will use the first PostgreSQL database instead."
  34. )
  35. with self.assertWarnsMessage(RuntimeWarning, msg):
  36. with mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.connect',
  37. side_effect=mocked_connect, autospec=True):
  38. with mock.patch.object(
  39. connection,
  40. 'settings_dict',
  41. {**connection.settings_dict, 'NAME': 'postgres'},
  42. ):
  43. with connection._nodb_cursor() as cursor:
  44. self.assertIs(cursor.closed, False)
  45. self.assertIsNotNone(cursor.db.connection)
  46. self.assertIs(cursor.closed, True)
  47. self.assertIsNone(cursor.db.connection)
  48. self.assertIsNotNone(cursor.db.settings_dict['NAME'])
  49. self.assertEqual(cursor.db.settings_dict['NAME'], connections['other'].settings_dict['NAME'])
  50. def test_database_name_too_long(self):
  51. from django.db.backends.postgresql.base import DatabaseWrapper
  52. settings = connection.settings_dict.copy()
  53. max_name_length = connection.ops.max_name_length()
  54. settings['NAME'] = 'a' + (max_name_length * 'a')
  55. msg = (
  56. "The database name '%s' (%d characters) is longer than "
  57. "PostgreSQL's limit of %s characters. Supply a shorter NAME in "
  58. "settings.DATABASES."
  59. ) % (settings['NAME'], max_name_length + 1, max_name_length)
  60. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  61. DatabaseWrapper(settings).get_connection_params()
  62. def test_connect_and_rollback(self):
  63. """
  64. PostgreSQL shouldn't roll back SET TIME ZONE, even if the first
  65. transaction is rolled back (#17062).
  66. """
  67. new_connection = connection.copy()
  68. try:
  69. # Ensure the database default time zone is different than
  70. # the time zone in new_connection.settings_dict. We can
  71. # get the default time zone by reset & show.
  72. with new_connection.cursor() as cursor:
  73. cursor.execute("RESET TIMEZONE")
  74. cursor.execute("SHOW TIMEZONE")
  75. db_default_tz = cursor.fetchone()[0]
  76. new_tz = 'Europe/Paris' if db_default_tz == 'UTC' else 'UTC'
  77. new_connection.close()
  78. # Invalidate timezone name cache, because the setting_changed
  79. # handler cannot know about new_connection.
  80. del new_connection.timezone_name
  81. # Fetch a new connection with the new_tz as default
  82. # time zone, run a query and rollback.
  83. with self.settings(TIME_ZONE=new_tz):
  84. new_connection.set_autocommit(False)
  85. new_connection.rollback()
  86. # Now let's see if the rollback rolled back the SET TIME ZONE.
  87. with new_connection.cursor() as cursor:
  88. cursor.execute("SHOW TIMEZONE")
  89. tz = cursor.fetchone()[0]
  90. self.assertEqual(new_tz, tz)
  91. finally:
  92. new_connection.close()
  93. def test_connect_non_autocommit(self):
  94. """
  95. The connection wrapper shouldn't believe that autocommit is enabled
  96. after setting the time zone when AUTOCOMMIT is False (#21452).
  97. """
  98. new_connection = connection.copy()
  99. new_connection.settings_dict['AUTOCOMMIT'] = False
  100. try:
  101. # Open a database connection.
  102. with new_connection.cursor():
  103. self.assertFalse(new_connection.get_autocommit())
  104. finally:
  105. new_connection.close()
  106. def test_connect_isolation_level(self):
  107. """
  108. The transaction level can be configured with
  109. DATABASES ['OPTIONS']['isolation_level'].
  110. """
  111. import psycopg2
  112. from psycopg2.extensions import (
  113. ISOLATION_LEVEL_READ_COMMITTED as read_committed,
  114. ISOLATION_LEVEL_SERIALIZABLE as serializable,
  115. )
  116. # Since this is a django.test.TestCase, a transaction is in progress
  117. # and the isolation level isn't reported as 0. This test assumes that
  118. # PostgreSQL is configured with the default isolation level.
  119. # Check the level on the psycopg2 connection, not the Django wrapper.
  120. default_level = read_committed if psycopg2.__version__ < '2.7' else None
  121. self.assertEqual(connection.connection.isolation_level, default_level)
  122. new_connection = connection.copy()
  123. new_connection.settings_dict['OPTIONS']['isolation_level'] = serializable
  124. try:
  125. # Start a transaction so the isolation level isn't reported as 0.
  126. new_connection.set_autocommit(False)
  127. # Check the level on the psycopg2 connection, not the Django wrapper.
  128. self.assertEqual(new_connection.connection.isolation_level, serializable)
  129. finally:
  130. new_connection.close()
  131. def test_connect_no_is_usable_checks(self):
  132. new_connection = connection.copy()
  133. try:
  134. with mock.patch.object(new_connection, 'is_usable') as is_usable:
  135. new_connection.connect()
  136. is_usable.assert_not_called()
  137. finally:
  138. new_connection.close()
  139. def _select(self, val):
  140. with connection.cursor() as cursor:
  141. cursor.execute('SELECT %s', (val,))
  142. return cursor.fetchone()[0]
  143. def test_select_ascii_array(self):
  144. a = ['awef']
  145. b = self._select(a)
  146. self.assertEqual(a[0], b[0])
  147. def test_select_unicode_array(self):
  148. a = ['ᄲawef']
  149. b = self._select(a)
  150. self.assertEqual(a[0], b[0])
  151. def test_lookup_cast(self):
  152. from django.db.backends.postgresql.operations import DatabaseOperations
  153. do = DatabaseOperations(connection=None)
  154. lookups = (
  155. 'iexact', 'contains', 'icontains', 'startswith', 'istartswith',
  156. 'endswith', 'iendswith', 'regex', 'iregex',
  157. )
  158. for lookup in lookups:
  159. with self.subTest(lookup=lookup):
  160. self.assertIn('::text', do.lookup_cast(lookup))
  161. for lookup in lookups:
  162. for field_type in ('CICharField', 'CIEmailField', 'CITextField'):
  163. with self.subTest(lookup=lookup, field_type=field_type):
  164. self.assertIn('::citext', do.lookup_cast(lookup, internal_type=field_type))
  165. def test_correct_extraction_psycopg2_version(self):
  166. from django.db.backends.postgresql.base import psycopg2_version
  167. with mock.patch('psycopg2.__version__', '4.2.1 (dt dec pq3 ext lo64)'):
  168. self.assertEqual(psycopg2_version(), (4, 2, 1))
  169. with mock.patch('psycopg2.__version__', '4.2b0.dev1 (dt dec pq3 ext lo64)'):
  170. self.assertEqual(psycopg2_version(), (4, 2))
  171. @override_settings(DEBUG=True)
  172. def test_copy_cursors(self):
  173. out = StringIO()
  174. copy_expert_sql = 'COPY django_session TO STDOUT (FORMAT CSV, HEADER)'
  175. with connection.cursor() as cursor:
  176. cursor.copy_expert(copy_expert_sql, out)
  177. cursor.copy_to(out, 'django_session')
  178. self.assertEqual(
  179. [q['sql'] for q in connection.queries],
  180. [copy_expert_sql, 'COPY django_session TO STDOUT'],
  181. )