123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- """
- PostgreSQL database backend for Django.
- Requires psycopg 2: http://initd.org/projects/psycopg2
- """
- import sys
- from django.db import utils
- from django.db.backends import *
- from django.db.backends.signals import connection_created
- from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
- from django.db.backends.postgresql_psycopg2.client import DatabaseClient
- from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
- from django.db.backends.postgresql_psycopg2.version import get_version
- from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
- from django.utils.safestring import SafeUnicode, SafeString
- from django.utils.log import getLogger
- try:
- import psycopg2 as Database
- import psycopg2.extensions
- except ImportError, e:
- from django.core.exceptions import ImproperlyConfigured
- raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
- DatabaseError = Database.DatabaseError
- IntegrityError = Database.IntegrityError
- psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
- psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
- psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString)
- logger = getLogger('django.db.backends')
- class CursorWrapper(object):
- """
- A thin wrapper around psycopg2's normal cursor class so that we can catch
- particular exception instances and reraise them with the right types.
- """
- def __init__(self, cursor):
- self.cursor = cursor
- def execute(self, query, args=None):
- try:
- return self.cursor.execute(query, args)
- except Database.IntegrityError, e:
- raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
- except Database.DatabaseError, e:
- raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
- def executemany(self, query, args):
- try:
- return self.cursor.executemany(query, args)
- except Database.IntegrityError, e:
- raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
- except Database.DatabaseError, e:
- raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
- def __getattr__(self, attr):
- if attr in self.__dict__:
- return self.__dict__[attr]
- else:
- return getattr(self.cursor, attr)
- def __iter__(self):
- return iter(self.cursor)
- class DatabaseFeatures(BaseDatabaseFeatures):
- needs_datetime_string_cast = False
- can_return_id_from_insert = True
- requires_rollback_on_dirty_transaction = True
- has_real_datatype = True
- can_defer_constraint_checks = True
- has_select_for_update = True
- has_select_for_update_nowait = True
- has_bulk_insert = True
- supports_tablespaces = True
- class DatabaseWrapper(BaseDatabaseWrapper):
- vendor = 'postgresql'
- operators = {
- 'exact': '= %s',
- 'iexact': '= UPPER(%s)',
- 'contains': 'LIKE %s',
- 'icontains': 'LIKE UPPER(%s)',
- 'regex': '~ %s',
- 'iregex': '~* %s',
- 'gt': '> %s',
- 'gte': '>= %s',
- 'lt': '< %s',
- 'lte': '<= %s',
- 'startswith': 'LIKE %s',
- 'endswith': 'LIKE %s',
- 'istartswith': 'LIKE UPPER(%s)',
- 'iendswith': 'LIKE UPPER(%s)',
- }
- def __init__(self, *args, **kwargs):
- super(DatabaseWrapper, self).__init__(*args, **kwargs)
- self.features = DatabaseFeatures(self)
- autocommit = self.settings_dict["OPTIONS"].get('autocommit', False)
- self.features.uses_autocommit = autocommit
- self._set_isolation_level(int(not autocommit))
- self.ops = DatabaseOperations(self)
- self.client = DatabaseClient(self)
- self.creation = DatabaseCreation(self)
- self.introspection = DatabaseIntrospection(self)
- self.validation = BaseDatabaseValidation(self)
- self._pg_version = None
- def check_constraints(self, table_names=None):
- """
- To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they
- are returned to deferred.
- """
- self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
- self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')
- def close(self):
- if self.connection is None:
- return
- try:
- self.connection.close()
- self.connection = None
- except Database.Error:
- # In some cases (database restart, network connection lost etc...)
- # the connection to the database is lost without giving Django a
- # notification. If we don't set self.connection to None, the error
- # will occur a every request.
- self.connection = None
- logger.warning('psycopg2 error while closing the connection.',
- exc_info=sys.exc_info()
- )
- raise
- def _get_pg_version(self):
- if self._pg_version is None:
- self._pg_version = get_version(self.connection)
- return self._pg_version
- pg_version = property(_get_pg_version)
- def _cursor(self):
- new_connection = False
- set_tz = False
- settings_dict = self.settings_dict
- if self.connection is None:
- new_connection = True
- set_tz = settings_dict.get('TIME_ZONE')
- if settings_dict['NAME'] == '':
- from django.core.exceptions import ImproperlyConfigured
- raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
- conn_params = {
- 'database': settings_dict['NAME'],
- }
- conn_params.update(settings_dict['OPTIONS'])
- if 'autocommit' in conn_params:
- del conn_params['autocommit']
- if settings_dict['USER']:
- conn_params['user'] = settings_dict['USER']
- if settings_dict['PASSWORD']:
- conn_params['password'] = settings_dict['PASSWORD']
- if settings_dict['HOST']:
- conn_params['host'] = settings_dict['HOST']
- if settings_dict['PORT']:
- conn_params['port'] = settings_dict['PORT']
- self.connection = Database.connect(**conn_params)
- self.connection.set_client_encoding('UTF8')
- self.connection.set_isolation_level(self.isolation_level)
- connection_created.send(sender=self.__class__, connection=self)
- cursor = self.connection.cursor()
- cursor.tzinfo_factory = None
- if new_connection:
- if set_tz:
- cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
- self._get_pg_version()
- return CursorWrapper(cursor)
- def _enter_transaction_management(self, managed):
- """
- Switch the isolation level when needing transaction support, so that
- the same transaction is visible across all the queries.
- """
- if self.features.uses_autocommit and managed and not self.isolation_level:
- self._set_isolation_level(1)
- def _leave_transaction_management(self, managed):
- """
- If the normal operating mode is "autocommit", switch back to that when
- leaving transaction management.
- """
- if self.features.uses_autocommit and not managed and self.isolation_level:
- self._set_isolation_level(0)
- def _set_isolation_level(self, level):
- """
- Do all the related feature configurations for changing isolation
- levels. This doesn't touch the uses_autocommit feature, since that
- controls the movement *between* isolation levels.
- """
- assert level in (0, 1)
- try:
- if self.connection is not None:
- self.connection.set_isolation_level(level)
- finally:
- self.isolation_level = level
- self.features.uses_savepoints = bool(level)
- def _commit(self):
- if self.connection is not None:
- try:
- return self.connection.commit()
- except Database.IntegrityError, e:
- raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
|