web/lib/django/db/backends/postgresql_psycopg2/base.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 """
       
     2 PostgreSQL database backend for Django.
       
     3 
       
     4 Requires psycopg 2: http://initd.org/projects/psycopg2
       
     5 """
       
     6 
       
     7 import sys
       
     8 
       
     9 from django.db import utils
       
    10 from django.db.backends import *
       
    11 from django.db.backends.signals import connection_created
       
    12 from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations
       
    13 from django.db.backends.postgresql.client import DatabaseClient
       
    14 from django.db.backends.postgresql.creation import DatabaseCreation
       
    15 from django.db.backends.postgresql.version import get_version
       
    16 from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
       
    17 from django.utils.safestring import SafeUnicode, SafeString
       
    18 
       
    19 try:
       
    20     import psycopg2 as Database
       
    21     import psycopg2.extensions
       
    22 except ImportError, e:
       
    23     from django.core.exceptions import ImproperlyConfigured
       
    24     raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
       
    25 
       
    26 DatabaseError = Database.DatabaseError
       
    27 IntegrityError = Database.IntegrityError
       
    28 
       
    29 psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
       
    30 psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
       
    31 psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString)
       
    32 
       
    33 class CursorWrapper(object):
       
    34     """
       
    35     A thin wrapper around psycopg2's normal cursor class so that we can catch
       
    36     particular exception instances and reraise them with the right types.
       
    37     """
       
    38 
       
    39     def __init__(self, cursor):
       
    40         self.cursor = cursor
       
    41 
       
    42     def execute(self, query, args=None):
       
    43         try:
       
    44             return self.cursor.execute(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 
       
    50     def executemany(self, query, args):
       
    51         try:
       
    52             return self.cursor.executemany(query, args)
       
    53         except Database.IntegrityError, e:
       
    54             raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
       
    55         except Database.DatabaseError, e:
       
    56             raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
       
    57 
       
    58     def __getattr__(self, attr):
       
    59         if attr in self.__dict__:
       
    60             return self.__dict__[attr]
       
    61         else:
       
    62             return getattr(self.cursor, attr)
       
    63 
       
    64     def __iter__(self):
       
    65         return iter(self.cursor)
       
    66 
       
    67 class DatabaseFeatures(BaseDatabaseFeatures):
       
    68     needs_datetime_string_cast = False
       
    69     can_return_id_from_insert = False
       
    70 
       
    71 class DatabaseOperations(PostgresqlDatabaseOperations):
       
    72     def last_executed_query(self, cursor, sql, params):
       
    73         # With psycopg2, cursor objects have a "query" attribute that is the
       
    74         # exact query sent to the database. See docs here:
       
    75         # http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query
       
    76         return cursor.query
       
    77 
       
    78     def return_insert_id(self):
       
    79         return "RETURNING %s", ()
       
    80 
       
    81 class DatabaseWrapper(BaseDatabaseWrapper):
       
    82     operators = {
       
    83         'exact': '= %s',
       
    84         'iexact': '= UPPER(%s)',
       
    85         'contains': 'LIKE %s',
       
    86         'icontains': 'LIKE UPPER(%s)',
       
    87         'regex': '~ %s',
       
    88         'iregex': '~* %s',
       
    89         'gt': '> %s',
       
    90         'gte': '>= %s',
       
    91         'lt': '< %s',
       
    92         'lte': '<= %s',
       
    93         'startswith': 'LIKE %s',
       
    94         'endswith': 'LIKE %s',
       
    95         'istartswith': 'LIKE UPPER(%s)',
       
    96         'iendswith': 'LIKE UPPER(%s)',
       
    97     }
       
    98 
       
    99     def __init__(self, *args, **kwargs):
       
   100         super(DatabaseWrapper, self).__init__(*args, **kwargs)
       
   101 
       
   102         self.features = DatabaseFeatures()
       
   103         autocommit = self.settings_dict["OPTIONS"].get('autocommit', False)
       
   104         self.features.uses_autocommit = autocommit
       
   105         self._set_isolation_level(int(not autocommit))
       
   106         self.ops = DatabaseOperations(self)
       
   107         self.client = DatabaseClient(self)
       
   108         self.creation = DatabaseCreation(self)
       
   109         self.introspection = DatabaseIntrospection(self)
       
   110         self.validation = BaseDatabaseValidation(self)
       
   111 
       
   112     def _cursor(self):
       
   113         new_connection = False
       
   114         set_tz = False
       
   115         settings_dict = self.settings_dict
       
   116         if self.connection is None:
       
   117             new_connection = True
       
   118             set_tz = settings_dict.get('TIME_ZONE')
       
   119             if settings_dict['NAME'] == '':
       
   120                 from django.core.exceptions import ImproperlyConfigured
       
   121                 raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
       
   122             conn_params = {
       
   123                 'database': settings_dict['NAME'],
       
   124             }
       
   125             conn_params.update(settings_dict['OPTIONS'])
       
   126             if 'autocommit' in conn_params:
       
   127                 del conn_params['autocommit']
       
   128             if settings_dict['USER']:
       
   129                 conn_params['user'] = settings_dict['USER']
       
   130             if settings_dict['PASSWORD']:
       
   131                 conn_params['password'] = settings_dict['PASSWORD']
       
   132             if settings_dict['HOST']:
       
   133                 conn_params['host'] = settings_dict['HOST']
       
   134             if settings_dict['PORT']:
       
   135                 conn_params['port'] = settings_dict['PORT']
       
   136             self.connection = Database.connect(**conn_params)
       
   137             self.connection.set_client_encoding('UTF8')
       
   138             self.connection.set_isolation_level(self.isolation_level)
       
   139             connection_created.send(sender=self.__class__)
       
   140         cursor = self.connection.cursor()
       
   141         cursor.tzinfo_factory = None
       
   142         if new_connection:
       
   143             if set_tz:
       
   144                 cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
       
   145             if not hasattr(self, '_version'):
       
   146                 self.__class__._version = get_version(cursor)
       
   147             if self._version[0:2] < (8, 0):
       
   148                 # No savepoint support for earlier version of PostgreSQL.
       
   149                 self.features.uses_savepoints = False
       
   150             if self.features.uses_autocommit:
       
   151                 if self._version[0:2] < (8, 2):
       
   152                     # FIXME: Needs extra code to do reliable model insert
       
   153                     # handling, so we forbid it for now.
       
   154                     from django.core.exceptions import ImproperlyConfigured
       
   155                     raise ImproperlyConfigured("You cannot use autocommit=True with PostgreSQL prior to 8.2 at the moment.")
       
   156                 else:
       
   157                     # FIXME: Eventually we're enable this by default for
       
   158                     # versions that support it, but, right now, that's hard to
       
   159                     # do without breaking other things (#10509).
       
   160                     self.features.can_return_id_from_insert = True
       
   161         return CursorWrapper(cursor)
       
   162 
       
   163     def _enter_transaction_management(self, managed):
       
   164         """
       
   165         Switch the isolation level when needing transaction support, so that
       
   166         the same transaction is visible across all the queries.
       
   167         """
       
   168         if self.features.uses_autocommit and managed and not self.isolation_level:
       
   169             self._set_isolation_level(1)
       
   170 
       
   171     def _leave_transaction_management(self, managed):
       
   172         """
       
   173         If the normal operating mode is "autocommit", switch back to that when
       
   174         leaving transaction management.
       
   175         """
       
   176         if self.features.uses_autocommit and not managed and self.isolation_level:
       
   177             self._set_isolation_level(0)
       
   178 
       
   179     def _set_isolation_level(self, level):
       
   180         """
       
   181         Do all the related feature configurations for changing isolation
       
   182         levels. This doesn't touch the uses_autocommit feature, since that
       
   183         controls the movement *between* isolation levels.
       
   184         """
       
   185         assert level in (0, 1)
       
   186         try:
       
   187             if self.connection is not None:
       
   188                 self.connection.set_isolation_level(level)
       
   189         finally:
       
   190             self.isolation_level = level
       
   191             self.features.uses_savepoints = bool(level)