base.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. """
  2. PostgreSQL database backend for Django.
  3. Requires psycopg 2: http://initd.org/projects/psycopg2
  4. """
  5. from django.conf import settings
  6. from django.db.backends import (BaseDatabaseFeatures, BaseDatabaseWrapper,
  7. BaseDatabaseValidation)
  8. from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
  9. from django.db.backends.postgresql_psycopg2.client import DatabaseClient
  10. from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
  11. from django.db.backends.postgresql_psycopg2.version import get_version
  12. from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
  13. from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
  14. from django.db.utils import InterfaceError
  15. from django.utils.encoding import force_str
  16. from django.utils.functional import cached_property
  17. from django.utils.safestring import SafeText, SafeBytes
  18. from django.utils.timezone import utc
  19. try:
  20. import psycopg2 as Database
  21. import psycopg2.extensions
  22. except ImportError as e:
  23. from django.core.exceptions import ImproperlyConfigured
  24. raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
  25. DatabaseError = Database.DatabaseError
  26. IntegrityError = Database.IntegrityError
  27. psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
  28. psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
  29. psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString)
  30. psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
  31. def utc_tzinfo_factory(offset):
  32. if offset != 0:
  33. raise AssertionError("database connection isn't set to UTC")
  34. return utc
  35. class DatabaseFeatures(BaseDatabaseFeatures):
  36. needs_datetime_string_cast = False
  37. can_return_id_from_insert = True
  38. requires_rollback_on_dirty_transaction = True
  39. has_real_datatype = True
  40. can_defer_constraint_checks = True
  41. has_select_for_update = True
  42. has_select_for_update_nowait = True
  43. has_bulk_insert = True
  44. uses_savepoints = True
  45. supports_tablespaces = True
  46. supports_transactions = True
  47. can_introspect_ip_address_field = True
  48. can_introspect_small_integer_field = True
  49. can_distinct_on_fields = True
  50. can_rollback_ddl = True
  51. supports_combined_alters = True
  52. nulls_order_largest = True
  53. closed_cursor_error_class = InterfaceError
  54. has_case_insensitive_like = False
  55. requires_sqlparse_for_splitting = False
  56. class DatabaseWrapper(BaseDatabaseWrapper):
  57. vendor = 'postgresql'
  58. operators = {
  59. 'exact': '= %s',
  60. 'iexact': '= UPPER(%s)',
  61. 'contains': 'LIKE %s',
  62. 'icontains': 'LIKE UPPER(%s)',
  63. 'regex': '~ %s',
  64. 'iregex': '~* %s',
  65. 'gt': '> %s',
  66. 'gte': '>= %s',
  67. 'lt': '< %s',
  68. 'lte': '<= %s',
  69. 'startswith': 'LIKE %s',
  70. 'endswith': 'LIKE %s',
  71. 'istartswith': 'LIKE UPPER(%s)',
  72. 'iendswith': 'LIKE UPPER(%s)',
  73. }
  74. pattern_ops = {
  75. 'startswith': "LIKE %s || '%%%%'",
  76. 'istartswith': "LIKE UPPER(%s) || '%%%%'",
  77. }
  78. Database = Database
  79. def __init__(self, *args, **kwargs):
  80. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  81. opts = self.settings_dict["OPTIONS"]
  82. RC = psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED
  83. self.isolation_level = opts.get('isolation_level', RC)
  84. self.features = DatabaseFeatures(self)
  85. self.ops = DatabaseOperations(self)
  86. self.client = DatabaseClient(self)
  87. self.creation = DatabaseCreation(self)
  88. self.introspection = DatabaseIntrospection(self)
  89. self.validation = BaseDatabaseValidation(self)
  90. def get_connection_params(self):
  91. settings_dict = self.settings_dict
  92. # None may be used to connect to the default 'postgres' db
  93. if settings_dict['NAME'] == '':
  94. from django.core.exceptions import ImproperlyConfigured
  95. raise ImproperlyConfigured(
  96. "settings.DATABASES is improperly configured. "
  97. "Please supply the NAME value.")
  98. conn_params = {
  99. 'database': settings_dict['NAME'] or 'postgres',
  100. }
  101. conn_params.update(settings_dict['OPTIONS'])
  102. if 'autocommit' in conn_params:
  103. del conn_params['autocommit']
  104. if 'isolation_level' in conn_params:
  105. del conn_params['isolation_level']
  106. if settings_dict['USER']:
  107. conn_params['user'] = settings_dict['USER']
  108. if settings_dict['PASSWORD']:
  109. conn_params['password'] = force_str(settings_dict['PASSWORD'])
  110. if settings_dict['HOST']:
  111. conn_params['host'] = settings_dict['HOST']
  112. if settings_dict['PORT']:
  113. conn_params['port'] = settings_dict['PORT']
  114. return conn_params
  115. def get_new_connection(self, conn_params):
  116. return Database.connect(**conn_params)
  117. def init_connection_state(self):
  118. settings_dict = self.settings_dict
  119. self.connection.set_client_encoding('UTF8')
  120. tz = 'UTC' if settings.USE_TZ else settings_dict.get('TIME_ZONE')
  121. if tz:
  122. try:
  123. get_parameter_status = self.connection.get_parameter_status
  124. except AttributeError:
  125. # psycopg2 < 2.0.12 doesn't have get_parameter_status
  126. conn_tz = None
  127. else:
  128. conn_tz = get_parameter_status('TimeZone')
  129. if conn_tz != tz:
  130. cursor = self.connection.cursor()
  131. try:
  132. cursor.execute(self.ops.set_time_zone_sql(), [tz])
  133. finally:
  134. cursor.close()
  135. # Commit after setting the time zone (see #17062)
  136. if not self.get_autocommit():
  137. self.connection.commit()
  138. def create_cursor(self):
  139. cursor = self.connection.cursor()
  140. cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
  141. return cursor
  142. def _set_isolation_level(self, isolation_level):
  143. assert isolation_level in range(1, 5) # Use set_autocommit for level = 0
  144. if self.psycopg2_version >= (2, 4, 2):
  145. self.connection.set_session(isolation_level=isolation_level)
  146. else:
  147. self.connection.set_isolation_level(isolation_level)
  148. def _set_autocommit(self, autocommit):
  149. with self.wrap_database_errors:
  150. if self.psycopg2_version >= (2, 4, 2):
  151. self.connection.autocommit = autocommit
  152. else:
  153. if autocommit:
  154. level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
  155. else:
  156. level = self.isolation_level
  157. self.connection.set_isolation_level(level)
  158. def check_constraints(self, table_names=None):
  159. """
  160. To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they
  161. are returned to deferred.
  162. """
  163. self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
  164. self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')
  165. def is_usable(self):
  166. try:
  167. # Use a psycopg cursor directly, bypassing Django's utilities.
  168. self.connection.cursor().execute("SELECT 1")
  169. except Database.Error:
  170. return False
  171. else:
  172. return True
  173. def schema_editor(self, *args, **kwargs):
  174. "Returns a new instance of this backend's SchemaEditor"
  175. return DatabaseSchemaEditor(self, *args, **kwargs)
  176. @cached_property
  177. def psycopg2_version(self):
  178. version = psycopg2.__version__.split(' ', 1)[0]
  179. return tuple(int(v) for v in version.split('.'))
  180. @cached_property
  181. def pg_version(self):
  182. with self.temporary_connection():
  183. return get_version(self.connection)