|
0
|
1 |
""" |
|
|
2 |
Oracle database backend for Django. |
|
|
3 |
|
|
|
4 |
Requires cx_Oracle: http://cx-oracle.sourceforge.net/ |
|
|
5 |
""" |
|
|
6 |
|
|
29
|
7 |
|
|
|
8 |
import datetime |
|
0
|
9 |
import os |
|
29
|
10 |
import sys |
|
0
|
11 |
import time |
|
29
|
12 |
from decimal import Decimal |
|
0
|
13 |
|
|
|
14 |
# Oracle takes client-side character set encoding from the environment. |
|
|
15 |
os.environ['NLS_LANG'] = '.UTF8' |
|
|
16 |
# This prevents unicode from getting mangled by getting encoded into the |
|
|
17 |
# potentially non-unicode database character set. |
|
|
18 |
os.environ['ORA_NCHAR_LITERAL_REPLACE'] = 'TRUE' |
|
|
19 |
|
|
|
20 |
try: |
|
|
21 |
import cx_Oracle as Database |
|
|
22 |
except ImportError, e: |
|
|
23 |
from django.core.exceptions import ImproperlyConfigured |
|
|
24 |
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e) |
|
|
25 |
|
|
29
|
26 |
from django.db import utils |
|
0
|
27 |
from django.db.backends import * |
|
|
28 |
from django.db.backends.signals import connection_created |
|
|
29 |
from django.db.backends.oracle.client import DatabaseClient |
|
|
30 |
from django.db.backends.oracle.creation import DatabaseCreation |
|
|
31 |
from django.db.backends.oracle.introspection import DatabaseIntrospection |
|
|
32 |
from django.utils.encoding import smart_str, force_unicode |
|
|
33 |
|
|
|
34 |
DatabaseError = Database.DatabaseError |
|
|
35 |
IntegrityError = Database.IntegrityError |
|
|
36 |
|
|
|
37 |
|
|
|
38 |
# Check whether cx_Oracle was compiled with the WITH_UNICODE option. This will |
|
|
39 |
# also be True in Python 3.0. |
|
|
40 |
if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'): |
|
|
41 |
convert_unicode = force_unicode |
|
|
42 |
else: |
|
|
43 |
convert_unicode = smart_str |
|
|
44 |
|
|
|
45 |
|
|
|
46 |
class DatabaseFeatures(BaseDatabaseFeatures): |
|
|
47 |
empty_fetchmany_value = () |
|
|
48 |
needs_datetime_string_cast = False |
|
|
49 |
interprets_empty_strings_as_nulls = True |
|
|
50 |
uses_savepoints = True |
|
|
51 |
can_return_id_from_insert = True |
|
29
|
52 |
allow_sliced_subqueries = False |
|
0
|
53 |
|
|
|
54 |
|
|
|
55 |
class DatabaseOperations(BaseDatabaseOperations): |
|
29
|
56 |
compiler_module = "django.db.backends.oracle.compiler" |
|
0
|
57 |
|
|
|
58 |
def autoinc_sql(self, table, column): |
|
|
59 |
# To simulate auto-incrementing primary keys in Oracle, we have to |
|
|
60 |
# create a sequence and a trigger. |
|
|
61 |
sq_name = get_sequence_name(table) |
|
|
62 |
tr_name = get_trigger_name(table) |
|
|
63 |
tbl_name = self.quote_name(table) |
|
|
64 |
col_name = self.quote_name(column) |
|
|
65 |
sequence_sql = """ |
|
|
66 |
DECLARE |
|
|
67 |
i INTEGER; |
|
|
68 |
BEGIN |
|
|
69 |
SELECT COUNT(*) INTO i FROM USER_CATALOG |
|
|
70 |
WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE'; |
|
|
71 |
IF i = 0 THEN |
|
|
72 |
EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"'; |
|
|
73 |
END IF; |
|
|
74 |
END; |
|
|
75 |
/""" % locals() |
|
|
76 |
trigger_sql = """ |
|
|
77 |
CREATE OR REPLACE TRIGGER "%(tr_name)s" |
|
|
78 |
BEFORE INSERT ON %(tbl_name)s |
|
|
79 |
FOR EACH ROW |
|
|
80 |
WHEN (new.%(col_name)s IS NULL) |
|
|
81 |
BEGIN |
|
|
82 |
SELECT "%(sq_name)s".nextval |
|
|
83 |
INTO :new.%(col_name)s FROM dual; |
|
|
84 |
END; |
|
|
85 |
/""" % locals() |
|
|
86 |
return sequence_sql, trigger_sql |
|
|
87 |
|
|
|
88 |
def date_extract_sql(self, lookup_type, field_name): |
|
|
89 |
# http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163 |
|
|
90 |
if lookup_type == 'week_day': |
|
|
91 |
# TO_CHAR(field, 'D') returns an integer from 1-7, where 1=Sunday. |
|
|
92 |
return "TO_CHAR(%s, 'D')" % field_name |
|
|
93 |
else: |
|
|
94 |
return "EXTRACT(%s FROM %s)" % (lookup_type, field_name) |
|
|
95 |
|
|
|
96 |
def date_trunc_sql(self, lookup_type, field_name): |
|
|
97 |
# Oracle uses TRUNC() for both dates and numbers. |
|
|
98 |
# http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151 |
|
|
99 |
if lookup_type == 'day': |
|
|
100 |
sql = 'TRUNC(%s)' % field_name |
|
|
101 |
else: |
|
|
102 |
sql = "TRUNC(%s, '%s')" % (field_name, lookup_type) |
|
|
103 |
return sql |
|
|
104 |
|
|
29
|
105 |
def convert_values(self, value, field): |
|
|
106 |
if isinstance(value, Database.LOB): |
|
|
107 |
value = value.read() |
|
|
108 |
if field and field.get_internal_type() == 'TextField': |
|
|
109 |
value = force_unicode(value) |
|
|
110 |
|
|
|
111 |
# Oracle stores empty strings as null. We need to undo this in |
|
|
112 |
# order to adhere to the Django convention of using the empty |
|
|
113 |
# string instead of null, but only if the field accepts the |
|
|
114 |
# empty string. |
|
|
115 |
if value is None and field and field.empty_strings_allowed: |
|
|
116 |
value = u'' |
|
|
117 |
# Convert 1 or 0 to True or False |
|
|
118 |
elif value in (1, 0) and field and field.get_internal_type() in ('BooleanField', 'NullBooleanField'): |
|
|
119 |
value = bool(value) |
|
|
120 |
# Force floats to the correct type |
|
|
121 |
elif value is not None and field and field.get_internal_type() == 'FloatField': |
|
|
122 |
value = float(value) |
|
|
123 |
# Convert floats to decimals |
|
|
124 |
elif value is not None and field and field.get_internal_type() == 'DecimalField': |
|
|
125 |
value = util.typecast_decimal(field.format_number(value)) |
|
|
126 |
# cx_Oracle always returns datetime.datetime objects for |
|
|
127 |
# DATE and TIMESTAMP columns, but Django wants to see a |
|
|
128 |
# python datetime.date, .time, or .datetime. We use the type |
|
|
129 |
# of the Field to determine which to cast to, but it's not |
|
|
130 |
# always available. |
|
|
131 |
# As a workaround, we cast to date if all the time-related |
|
|
132 |
# values are 0, or to time if the date is 1/1/1900. |
|
|
133 |
# This could be cleaned a bit by adding a method to the Field |
|
|
134 |
# classes to normalize values from the database (the to_python |
|
|
135 |
# method is used for validation and isn't what we want here). |
|
|
136 |
elif isinstance(value, Database.Timestamp): |
|
|
137 |
# In Python 2.3, the cx_Oracle driver returns its own |
|
|
138 |
# Timestamp object that we must convert to a datetime class. |
|
|
139 |
if not isinstance(value, datetime.datetime): |
|
|
140 |
value = datetime.datetime(value.year, value.month, |
|
|
141 |
value.day, value.hour, value.minute, value.second, |
|
|
142 |
value.fsecond) |
|
|
143 |
if field and field.get_internal_type() == 'DateTimeField': |
|
|
144 |
pass |
|
|
145 |
elif field and field.get_internal_type() == 'DateField': |
|
|
146 |
value = value.date() |
|
|
147 |
elif field and field.get_internal_type() == 'TimeField' or (value.year == 1900 and value.month == value.day == 1): |
|
|
148 |
value = value.time() |
|
|
149 |
elif value.hour == value.minute == value.second == value.microsecond == 0: |
|
|
150 |
value = value.date() |
|
|
151 |
return value |
|
|
152 |
|
|
0
|
153 |
def datetime_cast_sql(self): |
|
|
154 |
return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')" |
|
|
155 |
|
|
|
156 |
def deferrable_sql(self): |
|
|
157 |
return " DEFERRABLE INITIALLY DEFERRED" |
|
|
158 |
|
|
|
159 |
def drop_sequence_sql(self, table): |
|
|
160 |
return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) |
|
|
161 |
|
|
|
162 |
def fetch_returned_insert_id(self, cursor): |
|
|
163 |
return long(cursor._insert_id_var.getvalue()) |
|
|
164 |
|
|
|
165 |
def field_cast_sql(self, db_type): |
|
|
166 |
if db_type and db_type.endswith('LOB'): |
|
|
167 |
return "DBMS_LOB.SUBSTR(%s)" |
|
|
168 |
else: |
|
|
169 |
return "%s" |
|
|
170 |
|
|
|
171 |
def last_insert_id(self, cursor, table_name, pk_name): |
|
|
172 |
sq_name = get_sequence_name(table_name) |
|
|
173 |
cursor.execute('SELECT "%s".currval FROM dual' % sq_name) |
|
|
174 |
return cursor.fetchone()[0] |
|
|
175 |
|
|
|
176 |
def lookup_cast(self, lookup_type): |
|
|
177 |
if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'): |
|
|
178 |
return "UPPER(%s)" |
|
|
179 |
return "%s" |
|
|
180 |
|
|
|
181 |
def max_name_length(self): |
|
|
182 |
return 30 |
|
|
183 |
|
|
|
184 |
def prep_for_iexact_query(self, x): |
|
|
185 |
return x |
|
|
186 |
|
|
|
187 |
def process_clob(self, value): |
|
|
188 |
if value is None: |
|
|
189 |
return u'' |
|
|
190 |
return force_unicode(value.read()) |
|
|
191 |
|
|
|
192 |
def quote_name(self, name): |
|
|
193 |
# SQL92 requires delimited (quoted) names to be case-sensitive. When |
|
|
194 |
# not quoted, Oracle has case-insensitive behavior for identifiers, but |
|
|
195 |
# always defaults to uppercase. |
|
|
196 |
# We simplify things by making Oracle identifiers always uppercase. |
|
|
197 |
if not name.startswith('"') and not name.endswith('"'): |
|
|
198 |
name = '"%s"' % util.truncate_name(name.upper(), |
|
|
199 |
self.max_name_length()) |
|
|
200 |
return name.upper() |
|
|
201 |
|
|
|
202 |
def random_function_sql(self): |
|
|
203 |
return "DBMS_RANDOM.RANDOM" |
|
|
204 |
|
|
|
205 |
def regex_lookup_9(self, lookup_type): |
|
|
206 |
raise NotImplementedError("Regexes are not supported in Oracle before version 10g.") |
|
|
207 |
|
|
|
208 |
def regex_lookup_10(self, lookup_type): |
|
|
209 |
if lookup_type == 'regex': |
|
|
210 |
match_option = "'c'" |
|
|
211 |
else: |
|
|
212 |
match_option = "'i'" |
|
|
213 |
return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option |
|
|
214 |
|
|
|
215 |
def regex_lookup(self, lookup_type): |
|
|
216 |
# If regex_lookup is called before it's been initialized, then create |
|
|
217 |
# a cursor to initialize it and recur. |
|
|
218 |
from django.db import connection |
|
|
219 |
connection.cursor() |
|
|
220 |
return connection.ops.regex_lookup(lookup_type) |
|
|
221 |
|
|
|
222 |
def return_insert_id(self): |
|
|
223 |
return "RETURNING %s INTO %%s", (InsertIdVar(),) |
|
|
224 |
|
|
|
225 |
def savepoint_create_sql(self, sid): |
|
|
226 |
return convert_unicode("SAVEPOINT " + self.quote_name(sid)) |
|
|
227 |
|
|
|
228 |
def savepoint_rollback_sql(self, sid): |
|
|
229 |
return convert_unicode("ROLLBACK TO SAVEPOINT " + self.quote_name(sid)) |
|
|
230 |
|
|
|
231 |
def sql_flush(self, style, tables, sequences): |
|
|
232 |
# Return a list of 'TRUNCATE x;', 'TRUNCATE y;', |
|
|
233 |
# 'TRUNCATE z;'... style SQL statements |
|
|
234 |
if tables: |
|
|
235 |
# Oracle does support TRUNCATE, but it seems to get us into |
|
|
236 |
# FK referential trouble, whereas DELETE FROM table works. |
|
|
237 |
sql = ['%s %s %s;' % \ |
|
|
238 |
(style.SQL_KEYWORD('DELETE'), |
|
|
239 |
style.SQL_KEYWORD('FROM'), |
|
|
240 |
style.SQL_FIELD(self.quote_name(table))) |
|
|
241 |
for table in tables] |
|
|
242 |
# Since we've just deleted all the rows, running our sequence |
|
|
243 |
# ALTER code will reset the sequence to 0. |
|
|
244 |
for sequence_info in sequences: |
|
|
245 |
sequence_name = get_sequence_name(sequence_info['table']) |
|
|
246 |
table_name = self.quote_name(sequence_info['table']) |
|
|
247 |
column_name = self.quote_name(sequence_info['column'] or 'id') |
|
|
248 |
query = _get_sequence_reset_sql() % {'sequence': sequence_name, |
|
|
249 |
'table': table_name, |
|
|
250 |
'column': column_name} |
|
|
251 |
sql.append(query) |
|
|
252 |
return sql |
|
|
253 |
else: |
|
|
254 |
return [] |
|
|
255 |
|
|
|
256 |
def sequence_reset_sql(self, style, model_list): |
|
|
257 |
from django.db import models |
|
|
258 |
output = [] |
|
|
259 |
query = _get_sequence_reset_sql() |
|
|
260 |
for model in model_list: |
|
|
261 |
for f in model._meta.local_fields: |
|
|
262 |
if isinstance(f, models.AutoField): |
|
|
263 |
table_name = self.quote_name(model._meta.db_table) |
|
|
264 |
sequence_name = get_sequence_name(model._meta.db_table) |
|
|
265 |
column_name = self.quote_name(f.column) |
|
|
266 |
output.append(query % {'sequence': sequence_name, |
|
|
267 |
'table': table_name, |
|
|
268 |
'column': column_name}) |
|
|
269 |
# Only one AutoField is allowed per model, so don't |
|
|
270 |
# continue to loop |
|
|
271 |
break |
|
|
272 |
for f in model._meta.many_to_many: |
|
|
273 |
if not f.rel.through: |
|
|
274 |
table_name = self.quote_name(f.m2m_db_table()) |
|
|
275 |
sequence_name = get_sequence_name(f.m2m_db_table()) |
|
|
276 |
column_name = self.quote_name('id') |
|
|
277 |
output.append(query % {'sequence': sequence_name, |
|
|
278 |
'table': table_name, |
|
|
279 |
'column': column_name}) |
|
|
280 |
return output |
|
|
281 |
|
|
|
282 |
def start_transaction_sql(self): |
|
|
283 |
return '' |
|
|
284 |
|
|
|
285 |
def tablespace_sql(self, tablespace, inline=False): |
|
|
286 |
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), |
|
|
287 |
self.quote_name(tablespace)) |
|
|
288 |
|
|
|
289 |
def value_to_db_time(self, value): |
|
|
290 |
if value is None: |
|
|
291 |
return None |
|
|
292 |
if isinstance(value, basestring): |
|
|
293 |
return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6])) |
|
|
294 |
return datetime.datetime(1900, 1, 1, value.hour, value.minute, |
|
|
295 |
value.second, value.microsecond) |
|
|
296 |
|
|
|
297 |
def year_lookup_bounds_for_date_field(self, value): |
|
|
298 |
first = '%s-01-01' |
|
|
299 |
second = '%s-12-31' |
|
|
300 |
return [first % value, second % value] |
|
|
301 |
|
|
|
302 |
def combine_expression(self, connector, sub_expressions): |
|
|
303 |
"Oracle requires special cases for %% and & operators in query expressions" |
|
|
304 |
if connector == '%%': |
|
|
305 |
return 'MOD(%s)' % ','.join(sub_expressions) |
|
|
306 |
elif connector == '&': |
|
|
307 |
return 'BITAND(%s)' % ','.join(sub_expressions) |
|
|
308 |
elif connector == '|': |
|
|
309 |
raise NotImplementedError("Bit-wise or is not supported in Oracle.") |
|
|
310 |
return super(DatabaseOperations, self).combine_expression(connector, sub_expressions) |
|
|
311 |
|
|
|
312 |
|
|
|
313 |
class DatabaseWrapper(BaseDatabaseWrapper): |
|
|
314 |
|
|
|
315 |
operators = { |
|
|
316 |
'exact': '= %s', |
|
|
317 |
'iexact': '= UPPER(%s)', |
|
29
|
318 |
'contains': "LIKE TRANSLATE(%s USING NCHAR_CS) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
|
|
319 |
'icontains': "LIKE UPPER(TRANSLATE(%s USING NCHAR_CS)) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
|
0
|
320 |
'gt': '> %s', |
|
|
321 |
'gte': '>= %s', |
|
|
322 |
'lt': '< %s', |
|
|
323 |
'lte': '<= %s', |
|
29
|
324 |
'startswith': "LIKE TRANSLATE(%s USING NCHAR_CS) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
|
|
325 |
'endswith': "LIKE TRANSLATE(%s USING NCHAR_CS) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
|
|
326 |
'istartswith': "LIKE UPPER(TRANSLATE(%s USING NCHAR_CS)) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
|
|
327 |
'iendswith': "LIKE UPPER(TRANSLATE(%s USING NCHAR_CS)) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
|
0
|
328 |
} |
|
|
329 |
oracle_version = None |
|
|
330 |
|
|
|
331 |
def __init__(self, *args, **kwargs): |
|
|
332 |
super(DatabaseWrapper, self).__init__(*args, **kwargs) |
|
|
333 |
|
|
|
334 |
self.features = DatabaseFeatures() |
|
|
335 |
self.ops = DatabaseOperations() |
|
|
336 |
self.client = DatabaseClient(self) |
|
|
337 |
self.creation = DatabaseCreation(self) |
|
|
338 |
self.introspection = DatabaseIntrospection(self) |
|
29
|
339 |
self.validation = BaseDatabaseValidation(self) |
|
0
|
340 |
|
|
|
341 |
def _valid_connection(self): |
|
|
342 |
return self.connection is not None |
|
|
343 |
|
|
|
344 |
def _connect_string(self): |
|
|
345 |
settings_dict = self.settings_dict |
|
29
|
346 |
if len(settings_dict['HOST'].strip()) == 0: |
|
|
347 |
settings_dict['HOST'] = 'localhost' |
|
|
348 |
if len(settings_dict['PORT'].strip()) != 0: |
|
|
349 |
dsn = Database.makedsn(settings_dict['HOST'], |
|
|
350 |
int(settings_dict['PORT']), |
|
|
351 |
settings_dict['NAME']) |
|
0
|
352 |
else: |
|
29
|
353 |
dsn = settings_dict['NAME'] |
|
|
354 |
return "%s/%s@%s" % (settings_dict['USER'], |
|
|
355 |
settings_dict['PASSWORD'], dsn) |
|
0
|
356 |
|
|
|
357 |
def _cursor(self): |
|
|
358 |
cursor = None |
|
|
359 |
if not self._valid_connection(): |
|
|
360 |
conn_string = convert_unicode(self._connect_string()) |
|
29
|
361 |
self.connection = Database.connect(conn_string, **self.settings_dict['OPTIONS']) |
|
0
|
362 |
cursor = FormatStylePlaceholderCursor(self.connection) |
|
|
363 |
# Set oracle date to ansi date format. This only needs to execute |
|
|
364 |
# once when we create a new connection. We also set the Territory |
|
|
365 |
# to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR(). |
|
|
366 |
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' " |
|
|
367 |
"NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF' " |
|
|
368 |
"NLS_TERRITORY = 'AMERICA'") |
|
|
369 |
try: |
|
|
370 |
self.oracle_version = int(self.connection.version.split('.')[0]) |
|
|
371 |
# There's no way for the DatabaseOperations class to know the |
|
|
372 |
# currently active Oracle version, so we do some setups here. |
|
|
373 |
# TODO: Multi-db support will need a better solution (a way to |
|
|
374 |
# communicate the current version). |
|
|
375 |
if self.oracle_version <= 9: |
|
|
376 |
self.ops.regex_lookup = self.ops.regex_lookup_9 |
|
|
377 |
else: |
|
|
378 |
self.ops.regex_lookup = self.ops.regex_lookup_10 |
|
|
379 |
except ValueError: |
|
|
380 |
pass |
|
|
381 |
try: |
|
|
382 |
self.connection.stmtcachesize = 20 |
|
|
383 |
except: |
|
|
384 |
# Django docs specify cx_Oracle version 4.3.1 or higher, but |
|
|
385 |
# stmtcachesize is available only in 4.3.2 and up. |
|
|
386 |
pass |
|
|
387 |
connection_created.send(sender=self.__class__) |
|
|
388 |
if not cursor: |
|
|
389 |
cursor = FormatStylePlaceholderCursor(self.connection) |
|
|
390 |
return cursor |
|
|
391 |
|
|
|
392 |
# Oracle doesn't support savepoint commits. Ignore them. |
|
|
393 |
def _savepoint_commit(self, sid): |
|
|
394 |
pass |
|
|
395 |
|
|
|
396 |
|
|
|
397 |
class OracleParam(object): |
|
|
398 |
""" |
|
|
399 |
Wrapper object for formatting parameters for Oracle. If the string |
|
|
400 |
representation of the value is large enough (greater than 4000 characters) |
|
|
401 |
the input size needs to be set as CLOB. Alternatively, if the parameter |
|
|
402 |
has an `input_size` attribute, then the value of the `input_size` attribute |
|
|
403 |
will be used instead. Otherwise, no input size will be set for the |
|
|
404 |
parameter when executing the query. |
|
|
405 |
""" |
|
|
406 |
|
|
|
407 |
def __init__(self, param, cursor, strings_only=False): |
|
|
408 |
if hasattr(param, 'bind_parameter'): |
|
|
409 |
self.smart_str = param.bind_parameter(cursor) |
|
|
410 |
else: |
|
|
411 |
self.smart_str = convert_unicode(param, cursor.charset, |
|
|
412 |
strings_only) |
|
|
413 |
if hasattr(param, 'input_size'): |
|
|
414 |
# If parameter has `input_size` attribute, use that. |
|
|
415 |
self.input_size = param.input_size |
|
|
416 |
elif isinstance(param, basestring) and len(param) > 4000: |
|
|
417 |
# Mark any string param greater than 4000 characters as a CLOB. |
|
|
418 |
self.input_size = Database.CLOB |
|
|
419 |
else: |
|
|
420 |
self.input_size = None |
|
|
421 |
|
|
|
422 |
|
|
29
|
423 |
class VariableWrapper(object): |
|
|
424 |
""" |
|
|
425 |
An adapter class for cursor variables that prevents the wrapped object |
|
|
426 |
from being converted into a string when used to instanciate an OracleParam. |
|
|
427 |
This can be used generally for any other object that should be passed into |
|
|
428 |
Cursor.execute as-is. |
|
|
429 |
""" |
|
|
430 |
|
|
|
431 |
def __init__(self, var): |
|
|
432 |
self.var = var |
|
|
433 |
|
|
|
434 |
def bind_parameter(self, cursor): |
|
|
435 |
return self.var |
|
|
436 |
|
|
|
437 |
def __getattr__(self, key): |
|
|
438 |
return getattr(self.var, key) |
|
|
439 |
|
|
|
440 |
def __setattr__(self, key, value): |
|
|
441 |
if key == 'var': |
|
|
442 |
self.__dict__[key] = value |
|
|
443 |
else: |
|
|
444 |
setattr(self.var, key, value) |
|
|
445 |
|
|
|
446 |
|
|
0
|
447 |
class InsertIdVar(object): |
|
|
448 |
""" |
|
|
449 |
A late-binding cursor variable that can be passed to Cursor.execute |
|
|
450 |
as a parameter, in order to receive the id of the row created by an |
|
|
451 |
insert statement. |
|
|
452 |
""" |
|
|
453 |
|
|
|
454 |
def bind_parameter(self, cursor): |
|
29
|
455 |
param = cursor.cursor.var(Database.NUMBER) |
|
0
|
456 |
cursor._insert_id_var = param |
|
|
457 |
return param |
|
|
458 |
|
|
|
459 |
|
|
|
460 |
class FormatStylePlaceholderCursor(object): |
|
|
461 |
""" |
|
|
462 |
Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" |
|
|
463 |
style. This fixes it -- but note that if you want to use a literal "%s" in |
|
|
464 |
a query, you'll need to use "%%s". |
|
|
465 |
|
|
|
466 |
We also do automatic conversion between Unicode on the Python side and |
|
|
467 |
UTF-8 -- for talking to Oracle -- in here. |
|
|
468 |
""" |
|
|
469 |
charset = 'utf-8' |
|
|
470 |
|
|
|
471 |
def __init__(self, connection): |
|
|
472 |
self.cursor = connection.cursor() |
|
|
473 |
# Necessary to retrieve decimal values without rounding error. |
|
|
474 |
self.cursor.numbersAsStrings = True |
|
|
475 |
# Default arraysize of 1 is highly sub-optimal. |
|
|
476 |
self.cursor.arraysize = 100 |
|
|
477 |
|
|
|
478 |
def _format_params(self, params): |
|
|
479 |
return tuple([OracleParam(p, self, True) for p in params]) |
|
|
480 |
|
|
|
481 |
def _guess_input_sizes(self, params_list): |
|
|
482 |
sizes = [None] * len(params_list[0]) |
|
|
483 |
for params in params_list: |
|
|
484 |
for i, value in enumerate(params): |
|
|
485 |
if value.input_size: |
|
|
486 |
sizes[i] = value.input_size |
|
|
487 |
self.setinputsizes(*sizes) |
|
|
488 |
|
|
|
489 |
def _param_generator(self, params): |
|
|
490 |
return [p.smart_str for p in params] |
|
|
491 |
|
|
|
492 |
def execute(self, query, params=None): |
|
|
493 |
if params is None: |
|
|
494 |
params = [] |
|
|
495 |
else: |
|
|
496 |
params = self._format_params(params) |
|
|
497 |
args = [(':arg%d' % i) for i in range(len(params))] |
|
|
498 |
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it |
|
|
499 |
# it does want a trailing ';' but not a trailing '/'. However, these |
|
|
500 |
# characters must be included in the original query in case the query |
|
|
501 |
# is being passed to SQL*Plus. |
|
|
502 |
if query.endswith(';') or query.endswith('/'): |
|
|
503 |
query = query[:-1] |
|
|
504 |
query = convert_unicode(query % tuple(args), self.charset) |
|
|
505 |
self._guess_input_sizes([params]) |
|
|
506 |
try: |
|
|
507 |
return self.cursor.execute(query, self._param_generator(params)) |
|
29
|
508 |
except Database.IntegrityError, e: |
|
|
509 |
raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] |
|
|
510 |
except Database.DatabaseError, e: |
|
0
|
511 |
# cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. |
|
29
|
512 |
if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): |
|
|
513 |
raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] |
|
|
514 |
raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] |
|
0
|
515 |
|
|
|
516 |
def executemany(self, query, params=None): |
|
|
517 |
try: |
|
|
518 |
args = [(':arg%d' % i) for i in range(len(params[0]))] |
|
|
519 |
except (IndexError, TypeError): |
|
|
520 |
# No params given, nothing to do |
|
|
521 |
return None |
|
|
522 |
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it |
|
|
523 |
# it does want a trailing ';' but not a trailing '/'. However, these |
|
|
524 |
# characters must be included in the original query in case the query |
|
|
525 |
# is being passed to SQL*Plus. |
|
|
526 |
if query.endswith(';') or query.endswith('/'): |
|
|
527 |
query = query[:-1] |
|
|
528 |
query = convert_unicode(query % tuple(args), self.charset) |
|
|
529 |
formatted = [self._format_params(i) for i in params] |
|
|
530 |
self._guess_input_sizes(formatted) |
|
|
531 |
try: |
|
|
532 |
return self.cursor.executemany(query, |
|
|
533 |
[self._param_generator(p) for p in formatted]) |
|
29
|
534 |
except Database.IntegrityError, e: |
|
|
535 |
raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] |
|
|
536 |
except Database.DatabaseError, e: |
|
0
|
537 |
# cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. |
|
29
|
538 |
if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): |
|
|
539 |
raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] |
|
|
540 |
raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] |
|
0
|
541 |
|
|
|
542 |
def fetchone(self): |
|
|
543 |
row = self.cursor.fetchone() |
|
|
544 |
if row is None: |
|
|
545 |
return row |
|
29
|
546 |
return _rowfactory(row, self.cursor) |
|
0
|
547 |
|
|
|
548 |
def fetchmany(self, size=None): |
|
|
549 |
if size is None: |
|
|
550 |
size = self.arraysize |
|
29
|
551 |
return tuple([_rowfactory(r, self.cursor) |
|
0
|
552 |
for r in self.cursor.fetchmany(size)]) |
|
|
553 |
|
|
|
554 |
def fetchall(self): |
|
29
|
555 |
return tuple([_rowfactory(r, self.cursor) |
|
0
|
556 |
for r in self.cursor.fetchall()]) |
|
|
557 |
|
|
29
|
558 |
def var(self, *args): |
|
|
559 |
return VariableWrapper(self.cursor.var(*args)) |
|
|
560 |
|
|
|
561 |
def arrayvar(self, *args): |
|
|
562 |
return VariableWrapper(self.cursor.arrayvar(*args)) |
|
0
|
563 |
|
|
|
564 |
def __getattr__(self, attr): |
|
|
565 |
if attr in self.__dict__: |
|
|
566 |
return self.__dict__[attr] |
|
|
567 |
else: |
|
|
568 |
return getattr(self.cursor, attr) |
|
|
569 |
|
|
|
570 |
def __iter__(self): |
|
29
|
571 |
return CursorIterator(self.cursor) |
|
|
572 |
|
|
|
573 |
|
|
|
574 |
class CursorIterator(object): |
|
|
575 |
|
|
|
576 |
"""Cursor iterator wrapper that invokes our custom row factory.""" |
|
|
577 |
|
|
|
578 |
def __init__(self, cursor): |
|
|
579 |
self.cursor = cursor |
|
|
580 |
self.iter = iter(cursor) |
|
|
581 |
|
|
|
582 |
def __iter__(self): |
|
|
583 |
return self |
|
|
584 |
|
|
|
585 |
def next(self): |
|
|
586 |
return _rowfactory(self.iter.next(), self.cursor) |
|
|
587 |
|
|
|
588 |
|
|
|
589 |
def _rowfactory(row, cursor): |
|
|
590 |
# Cast numeric values as the appropriate Python type based upon the |
|
|
591 |
# cursor description, and convert strings to unicode. |
|
|
592 |
casted = [] |
|
|
593 |
for value, desc in zip(row, cursor.description): |
|
|
594 |
if value is not None and desc[1] is Database.NUMBER: |
|
|
595 |
precision, scale = desc[4:6] |
|
|
596 |
if scale == -127: |
|
|
597 |
if precision == 0: |
|
|
598 |
# NUMBER column: decimal-precision floating point |
|
|
599 |
# This will normally be an integer from a sequence, |
|
|
600 |
# but it could be a decimal value. |
|
|
601 |
if '.' in value: |
|
|
602 |
value = Decimal(value) |
|
|
603 |
else: |
|
|
604 |
value = int(value) |
|
|
605 |
else: |
|
|
606 |
# FLOAT column: binary-precision floating point. |
|
|
607 |
# This comes from FloatField columns. |
|
|
608 |
value = float(value) |
|
|
609 |
elif precision > 0: |
|
|
610 |
# NUMBER(p,s) column: decimal-precision fixed point. |
|
|
611 |
# This comes from IntField and DecimalField columns. |
|
|
612 |
if scale == 0: |
|
|
613 |
value = int(value) |
|
|
614 |
else: |
|
|
615 |
value = Decimal(value) |
|
|
616 |
elif '.' in value: |
|
|
617 |
# No type information. This normally comes from a |
|
|
618 |
# mathematical expression in the SELECT list. Guess int |
|
|
619 |
# or Decimal based on whether it has a decimal point. |
|
|
620 |
value = Decimal(value) |
|
|
621 |
else: |
|
|
622 |
value = int(value) |
|
|
623 |
elif desc[1] in (Database.STRING, Database.FIXED_CHAR, |
|
|
624 |
Database.LONG_STRING): |
|
|
625 |
value = to_unicode(value) |
|
|
626 |
casted.append(value) |
|
|
627 |
return tuple(casted) |
|
0
|
628 |
|
|
|
629 |
|
|
|
630 |
def to_unicode(s): |
|
|
631 |
""" |
|
|
632 |
Convert strings to Unicode objects (and return all other data types |
|
|
633 |
unchanged). |
|
|
634 |
""" |
|
|
635 |
if isinstance(s, basestring): |
|
|
636 |
return force_unicode(s) |
|
|
637 |
return s |
|
|
638 |
|
|
|
639 |
|
|
|
640 |
def _get_sequence_reset_sql(): |
|
|
641 |
# TODO: colorize this SQL code with style.SQL_KEYWORD(), etc. |
|
|
642 |
return """ |
|
|
643 |
DECLARE |
|
|
644 |
startvalue integer; |
|
|
645 |
cval integer; |
|
|
646 |
BEGIN |
|
|
647 |
LOCK TABLE %(table)s IN SHARE MODE; |
|
|
648 |
SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s; |
|
|
649 |
SELECT "%(sequence)s".nextval INTO cval FROM dual; |
|
|
650 |
cval := startvalue - cval; |
|
|
651 |
IF cval != 0 THEN |
|
|
652 |
EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval; |
|
|
653 |
SELECT "%(sequence)s".nextval INTO cval FROM dual; |
|
|
654 |
EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1'; |
|
|
655 |
END IF; |
|
|
656 |
COMMIT; |
|
|
657 |
END; |
|
|
658 |
/""" |
|
|
659 |
|
|
|
660 |
|
|
|
661 |
def get_sequence_name(table): |
|
|
662 |
name_length = DatabaseOperations().max_name_length() - 3 |
|
|
663 |
return '%s_SQ' % util.truncate_name(table, name_length).upper() |
|
|
664 |
|
|
|
665 |
|
|
|
666 |
def get_trigger_name(table): |
|
|
667 |
name_length = DatabaseOperations().max_name_length() - 3 |
|
|
668 |
return '%s_TR' % util.truncate_name(table, name_length).upper() |