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