app/django/db/backends/postgresql/base.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """
       
     2 PostgreSQL database backend for Django.
       
     3 
       
     4 Requires psycopg 1: http://initd.org/projects/psycopg1
       
     5 """
       
     6 
       
     7 from django.utils.encoding import smart_str, smart_unicode
       
     8 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util
       
     9 from django.db.backends.postgresql.operations import DatabaseOperations
       
    10 try:
       
    11     import psycopg as Database
       
    12 except ImportError, e:
       
    13     from django.core.exceptions import ImproperlyConfigured
       
    14     raise ImproperlyConfigured("Error loading psycopg module: %s" % e)
       
    15 
       
    16 DatabaseError = Database.DatabaseError
       
    17 IntegrityError = Database.IntegrityError
       
    18 
       
    19 class UnicodeCursorWrapper(object):
       
    20     """
       
    21     A thin wrapper around psycopg cursors that allows them to accept Unicode
       
    22     strings as params.
       
    23 
       
    24     This is necessary because psycopg doesn't apply any DB quoting to
       
    25     parameters that are Unicode strings. If a param is Unicode, this will
       
    26     convert it to a bytestring using database client's encoding before passing
       
    27     it to psycopg.
       
    28 
       
    29     All results retrieved from the database are converted into Unicode strings
       
    30     before being returned to the caller.
       
    31     """
       
    32     def __init__(self, cursor, charset):
       
    33         self.cursor = cursor
       
    34         self.charset = charset
       
    35 
       
    36     def format_params(self, params):
       
    37         if isinstance(params, dict):
       
    38             result = {}
       
    39             charset = self.charset
       
    40             for key, value in params.items():
       
    41                 result[smart_str(key, charset)] = smart_str(value, charset)
       
    42             return result
       
    43         else:
       
    44             return tuple([smart_str(p, self.charset, True) for p in params])
       
    45 
       
    46     def execute(self, sql, params=()):
       
    47         return self.cursor.execute(smart_str(sql, self.charset), self.format_params(params))
       
    48 
       
    49     def executemany(self, sql, param_list):
       
    50         new_param_list = [self.format_params(params) for params in param_list]
       
    51         return self.cursor.executemany(sql, new_param_list)
       
    52 
       
    53     def __getattr__(self, attr):
       
    54         if attr in self.__dict__:
       
    55             return self.__dict__[attr]
       
    56         else:
       
    57             return getattr(self.cursor, attr)
       
    58 
       
    59     def __iter__(self):
       
    60         return iter(self.cursor)
       
    61 
       
    62 class DatabaseFeatures(BaseDatabaseFeatures):
       
    63     pass # This backend uses all the defaults.
       
    64 
       
    65 class DatabaseWrapper(BaseDatabaseWrapper):
       
    66     features = DatabaseFeatures()
       
    67     ops = DatabaseOperations()
       
    68     operators = {
       
    69         'exact': '= %s',
       
    70         'iexact': 'ILIKE %s',
       
    71         'contains': 'LIKE %s',
       
    72         'icontains': 'ILIKE %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': 'ILIKE %s',
       
    82         'iendswith': 'ILIKE %s',
       
    83     }
       
    84 
       
    85     def _cursor(self, settings):
       
    86         set_tz = False
       
    87         if self.connection is None:
       
    88             set_tz = True
       
    89             if settings.DATABASE_NAME == '':
       
    90                 from django.core.exceptions import ImproperlyConfigured
       
    91                 raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.")
       
    92             conn_string = "dbname=%s" % settings.DATABASE_NAME
       
    93             if settings.DATABASE_USER:
       
    94                 conn_string = "user=%s %s" % (settings.DATABASE_USER, conn_string)
       
    95             if settings.DATABASE_PASSWORD:
       
    96                 conn_string += " password='%s'" % settings.DATABASE_PASSWORD
       
    97             if settings.DATABASE_HOST:
       
    98                 conn_string += " host=%s" % settings.DATABASE_HOST
       
    99             if settings.DATABASE_PORT:
       
   100                 conn_string += " port=%s" % settings.DATABASE_PORT
       
   101             self.connection = Database.connect(conn_string, **self.options)
       
   102             self.connection.set_isolation_level(1) # make transactions transparent to all cursors
       
   103         cursor = self.connection.cursor()
       
   104         if set_tz:
       
   105             cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
       
   106         cursor.execute("SET client_encoding to 'UNICODE'")
       
   107         cursor = UnicodeCursorWrapper(cursor, 'utf-8')
       
   108         return cursor
       
   109 
       
   110 def typecast_string(s):
       
   111     """
       
   112     Cast all returned strings to unicode strings.
       
   113     """
       
   114     if not s and not isinstance(s, str):
       
   115         return s
       
   116     return smart_unicode(s)
       
   117 
       
   118 # Register these custom typecasts, because Django expects dates/times to be
       
   119 # in Python's native (standard-library) datetime/time format, whereas psycopg
       
   120 # use mx.DateTime by default.
       
   121 try:
       
   122     Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
       
   123 except AttributeError:
       
   124     raise Exception("You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'.")
       
   125 Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
       
   126 Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
       
   127 Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
       
   128 Database.register_type(Database.new_type((1700,), "NUMERIC", util.typecast_decimal))
       
   129 Database.register_type(Database.new_type(Database.types[1043].values, 'STRING', typecast_string))