|
0
|
1 |
""" |
|
|
2 |
PostgreSQL database backend for Django. |
|
|
3 |
|
|
|
4 |
Requires psycopg 2: http://initd.org/projects/psycopg2 |
|
|
5 |
""" |
|
|
6 |
|
|
29
|
7 |
import sys |
|
|
8 |
|
|
|
9 |
from django.db import utils |
|
0
|
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 |
|
|
29
|
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 |
|
|
0
|
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() |
|
29
|
103 |
autocommit = self.settings_dict["OPTIONS"].get('autocommit', False) |
|
0
|
104 |
self.features.uses_autocommit = autocommit |
|
|
105 |
self._set_isolation_level(int(not autocommit)) |
|
29
|
106 |
self.ops = DatabaseOperations(self) |
|
0
|
107 |
self.client = DatabaseClient(self) |
|
|
108 |
self.creation = DatabaseCreation(self) |
|
|
109 |
self.introspection = DatabaseIntrospection(self) |
|
29
|
110 |
self.validation = BaseDatabaseValidation(self) |
|
0
|
111 |
|
|
|
112 |
def _cursor(self): |
|
29
|
113 |
new_connection = False |
|
0
|
114 |
set_tz = False |
|
|
115 |
settings_dict = self.settings_dict |
|
|
116 |
if self.connection is None: |
|
29
|
117 |
new_connection = True |
|
|
118 |
set_tz = settings_dict.get('TIME_ZONE') |
|
|
119 |
if settings_dict['NAME'] == '': |
|
0
|
120 |
from django.core.exceptions import ImproperlyConfigured |
|
29
|
121 |
raise ImproperlyConfigured("You need to specify NAME in your Django settings file.") |
|
0
|
122 |
conn_params = { |
|
29
|
123 |
'database': settings_dict['NAME'], |
|
0
|
124 |
} |
|
29
|
125 |
conn_params.update(settings_dict['OPTIONS']) |
|
0
|
126 |
if 'autocommit' in conn_params: |
|
|
127 |
del conn_params['autocommit'] |
|
29
|
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'] |
|
0
|
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 |
|
29
|
142 |
if new_connection: |
|
|
143 |
if set_tz: |
|
|
144 |
cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']]) |
|
0
|
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 |
|
29
|
161 |
return CursorWrapper(cursor) |
|
0
|
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) |