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