|
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) |