base.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. """
  2. PostgreSQL database backend for Django.
  3. Requires psycopg 2: http://initd.org/projects/psycopg2
  4. """
  5. import sys
  6. from django.db import utils
  7. from django.db.backends import *
  8. from django.db.backends.signals import connection_created
  9. from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
  10. from django.db.backends.postgresql_psycopg2.client import DatabaseClient
  11. from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
  12. from django.db.backends.postgresql_psycopg2.version import get_version
  13. from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
  14. from django.utils.safestring import SafeUnicode, SafeString
  15. from django.utils.log import getLogger
  16. try:
  17. import psycopg2 as Database
  18. import psycopg2.extensions
  19. except ImportError, e:
  20. from django.core.exceptions import ImproperlyConfigured
  21. raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
  22. DatabaseError = Database.DatabaseError
  23. IntegrityError = Database.IntegrityError
  24. psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
  25. psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
  26. psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString)
  27. logger = getLogger('django.db.backends')
  28. class CursorWrapper(object):
  29. """
  30. A thin wrapper around psycopg2's normal cursor class so that we can catch
  31. particular exception instances and reraise them with the right types.
  32. """
  33. def __init__(self, cursor):
  34. self.cursor = cursor
  35. def execute(self, query, args=None):
  36. try:
  37. return self.cursor.execute(query, args)
  38. except Database.IntegrityError, e:
  39. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
  40. except Database.DatabaseError, e:
  41. raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
  42. def executemany(self, query, args):
  43. try:
  44. return self.cursor.executemany(query, args)
  45. except Database.IntegrityError, e:
  46. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
  47. except Database.DatabaseError, e:
  48. raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
  49. def __getattr__(self, attr):
  50. if attr in self.__dict__:
  51. return self.__dict__[attr]
  52. else:
  53. return getattr(self.cursor, attr)
  54. def __iter__(self):
  55. return iter(self.cursor)
  56. class DatabaseFeatures(BaseDatabaseFeatures):
  57. needs_datetime_string_cast = False
  58. can_return_id_from_insert = True
  59. requires_rollback_on_dirty_transaction = True
  60. has_real_datatype = True
  61. can_defer_constraint_checks = True
  62. has_select_for_update = True
  63. has_select_for_update_nowait = True
  64. has_bulk_insert = True
  65. supports_tablespaces = True
  66. class DatabaseWrapper(BaseDatabaseWrapper):
  67. vendor = 'postgresql'
  68. operators = {
  69. 'exact': '= %s',
  70. 'iexact': '= UPPER(%s)',
  71. 'contains': 'LIKE %s',
  72. 'icontains': 'LIKE UPPER(%s)',
  73. 'regex': '~ %s',
  74. 'iregex': '~* %s',
  75. 'gt': '> %s',
  76. 'gte': '>= %s',
  77. 'lt': '< %s',
  78. 'lte': '<= %s',
  79. 'startswith': 'LIKE %s',
  80. 'endswith': 'LIKE %s',
  81. 'istartswith': 'LIKE UPPER(%s)',
  82. 'iendswith': 'LIKE UPPER(%s)',
  83. }
  84. def __init__(self, *args, **kwargs):
  85. super(DatabaseWrapper, self).__init__(*args, **kwargs)
  86. self.features = DatabaseFeatures(self)
  87. autocommit = self.settings_dict["OPTIONS"].get('autocommit', False)
  88. self.features.uses_autocommit = autocommit
  89. self._set_isolation_level(int(not autocommit))
  90. self.ops = DatabaseOperations(self)
  91. self.client = DatabaseClient(self)
  92. self.creation = DatabaseCreation(self)
  93. self.introspection = DatabaseIntrospection(self)
  94. self.validation = BaseDatabaseValidation(self)
  95. self._pg_version = None
  96. def check_constraints(self, table_names=None):
  97. """
  98. To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they
  99. are returned to deferred.
  100. """
  101. self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
  102. self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')
  103. def close(self):
  104. if self.connection is None:
  105. return
  106. try:
  107. self.connection.close()
  108. self.connection = None
  109. except Database.Error:
  110. # In some cases (database restart, network connection lost etc...)
  111. # the connection to the database is lost without giving Django a
  112. # notification. If we don't set self.connection to None, the error
  113. # will occur a every request.
  114. self.connection = None
  115. logger.warning('psycopg2 error while closing the connection.',
  116. exc_info=sys.exc_info()
  117. )
  118. raise
  119. def _get_pg_version(self):
  120. if self._pg_version is None:
  121. self._pg_version = get_version(self.connection)
  122. return self._pg_version
  123. pg_version = property(_get_pg_version)
  124. def _cursor(self):
  125. new_connection = False
  126. set_tz = False
  127. settings_dict = self.settings_dict
  128. if self.connection is None:
  129. new_connection = True
  130. set_tz = settings_dict.get('TIME_ZONE')
  131. if settings_dict['NAME'] == '':
  132. from django.core.exceptions import ImproperlyConfigured
  133. raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
  134. conn_params = {
  135. 'database': settings_dict['NAME'],
  136. }
  137. conn_params.update(settings_dict['OPTIONS'])
  138. if 'autocommit' in conn_params:
  139. del conn_params['autocommit']
  140. if settings_dict['USER']:
  141. conn_params['user'] = settings_dict['USER']
  142. if settings_dict['PASSWORD']:
  143. conn_params['password'] = settings_dict['PASSWORD']
  144. if settings_dict['HOST']:
  145. conn_params['host'] = settings_dict['HOST']
  146. if settings_dict['PORT']:
  147. conn_params['port'] = settings_dict['PORT']
  148. self.connection = Database.connect(**conn_params)
  149. self.connection.set_client_encoding('UTF8')
  150. self.connection.set_isolation_level(self.isolation_level)
  151. connection_created.send(sender=self.__class__, connection=self)
  152. cursor = self.connection.cursor()
  153. cursor.tzinfo_factory = None
  154. if new_connection:
  155. if set_tz:
  156. cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
  157. self._get_pg_version()
  158. return CursorWrapper(cursor)
  159. def _enter_transaction_management(self, managed):
  160. """
  161. Switch the isolation level when needing transaction support, so that
  162. the same transaction is visible across all the queries.
  163. """
  164. if self.features.uses_autocommit and managed and not self.isolation_level:
  165. self._set_isolation_level(1)
  166. def _leave_transaction_management(self, managed):
  167. """
  168. If the normal operating mode is "autocommit", switch back to that when
  169. leaving transaction management.
  170. """
  171. if self.features.uses_autocommit and not managed and self.isolation_level:
  172. self._set_isolation_level(0)
  173. def _set_isolation_level(self, level):
  174. """
  175. Do all the related feature configurations for changing isolation
  176. levels. This doesn't touch the uses_autocommit feature, since that
  177. controls the movement *between* isolation levels.
  178. """
  179. assert level in (0, 1)
  180. try:
  181. if self.connection is not None:
  182. self.connection.set_isolation_level(level)
  183. finally:
  184. self.isolation_level = level
  185. self.features.uses_savepoints = bool(level)
  186. def _commit(self):
  187. if self.connection is not None:
  188. try:
  189. return self.connection.commit()
  190. except Database.IntegrityError, e:
  191. raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]