2 Oracle database backend for Django.
4 Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
9 from django
.db
.backends
import BaseDatabaseWrapper
, BaseDatabaseFeatures
, BaseDatabaseOperations
, util
10 from django
.db
.backends
.oracle
import query
11 from django
.utils
.datastructures
import SortedDict
12 from django
.utils
.encoding
import smart_str
, force_unicode
14 # Oracle takes client-side character set encoding from the environment.
15 os
.environ
['NLS_LANG'] = '.UTF8'
17 import cx_Oracle
as Database
18 except ImportError, e
:
19 from django
.core
.exceptions
import ImproperlyConfigured
20 raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e
)
22 DatabaseError
= Database
.Error
23 IntegrityError
= Database
.IntegrityError
25 class DatabaseFeatures(BaseDatabaseFeatures
):
26 allows_group_by_ordinal
= False
27 allows_unique_and_pk
= False # Suppress UNIQUE/PK for Oracle (ORA-02259)
28 empty_fetchmany_value
= ()
29 needs_datetime_string_cast
= False
30 needs_upper_for_iops
= True
31 supports_tablespaces
= True
32 uses_case_insensitive_names
= True
33 uses_custom_query_class
= True
35 class DatabaseOperations(BaseDatabaseOperations
):
36 def autoinc_sql(self
, table
, column
):
37 # To simulate auto-incrementing primary keys in Oracle, we have to
38 # create a sequence and a trigger.
39 sq_name
= get_sequence_name(table
)
40 tr_name
= get_trigger_name(table
)
41 tbl_name
= self
.quote_name(table
)
42 col_name
= self
.quote_name(column
)
43 sequence_sql
= 'CREATE SEQUENCE %s;' % sq_name
45 CREATE OR REPLACE TRIGGER %(tr_name)s
46 BEFORE INSERT ON %(tbl_name)s
48 WHEN (new.%(col_name)s IS NULL)
50 SELECT %(sq_name)s.nextval
51 INTO :new.%(col_name)s FROM dual;
54 return sequence_sql
, trigger_sql
56 def date_extract_sql(self
, lookup_type
, field_name
):
57 # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163
58 return "EXTRACT(%s FROM %s)" % (lookup_type
, field_name
)
60 def date_trunc_sql(self
, lookup_type
, field_name
):
61 # Oracle uses TRUNC() for both dates and numbers.
62 # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
63 if lookup_type
== 'day':
64 sql
= 'TRUNC(%s)' % field_name
66 sql
= "TRUNC(%s, '%s')" % (field_name
, lookup_type
)
69 def datetime_cast_sql(self
):
70 return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')"
72 def deferrable_sql(self
):
73 return " DEFERRABLE INITIALLY DEFERRED"
75 def drop_sequence_sql(self
, table
):
76 return "DROP SEQUENCE %s;" % self
.quote_name(get_sequence_name(table
))
78 def field_cast_sql(self
, db_type
):
79 if db_type
and db_type
.endswith('LOB'):
80 return "DBMS_LOB.SUBSTR(%s)"
84 def last_insert_id(self
, cursor
, table_name
, pk_name
):
85 sq_name
= util
.truncate_name(table_name
, self
.max_name_length() - 3)
86 cursor
.execute('SELECT %s_sq.currval FROM dual' % sq_name
)
87 return cursor
.fetchone()[0]
89 def limit_offset_sql(self
, limit
, offset
=None):
90 # Limits and offset are too complicated to be handled here.
91 # Instead, they are handled in django/db/backends/oracle/query.py.
94 def lookup_cast(self
, lookup_type
):
95 if lookup_type
in ('iexact', 'icontains', 'istartswith', 'iendswith'):
99 def max_name_length(self
):
102 def query_class(self
, DefaultQueryClass
):
103 return query
.query_class(DefaultQueryClass
, Database
)
105 def quote_name(self
, name
):
106 # SQL92 requires delimited (quoted) names to be case-sensitive. When
107 # not quoted, Oracle has case-insensitive behavior for identifiers, but
108 # always defaults to uppercase.
109 # We simplify things by making Oracle identifiers always uppercase.
110 if not name
.startswith('"') and not name
.endswith('"'):
111 name
= '"%s"' % util
.truncate_name(name
.upper(), self
.max_name_length())
114 def random_function_sql(self
):
115 return "DBMS_RANDOM.RANDOM"
117 def regex_lookup_9(self
, lookup_type
):
118 raise NotImplementedError("Regexes are not supported in Oracle before version 10g.")
120 def regex_lookup_10(self
, lookup_type
):
121 if lookup_type
== 'regex':
125 return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option
127 def regex_lookup(self
, lookup_type
):
128 # If regex_lookup is called before it's been initialized, then create
129 # a cursor to initialize it and recur.
130 from django
.db
import connection
132 return connection
.ops
.regex_lookup(lookup_type
)
134 def sql_flush(self
, style
, tables
, sequences
):
135 # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
136 # 'TRUNCATE z;'... style SQL statements
138 # Oracle does support TRUNCATE, but it seems to get us into
139 # FK referential trouble, whereas DELETE FROM table works.
140 sql
= ['%s %s %s;' % \
141 (style
.SQL_KEYWORD('DELETE'),
142 style
.SQL_KEYWORD('FROM'),
143 style
.SQL_FIELD(self
.quote_name(table
))
144 ) for table
in tables
]
145 # Since we've just deleted all the rows, running our sequence
146 # ALTER code will reset the sequence to 0.
147 for sequence_info
in sequences
:
148 table_name
= sequence_info
['table']
149 seq_name
= get_sequence_name(table_name
)
150 column_name
= self
.quote_name(sequence_info
['column'] or 'id')
151 query
= _get_sequence_reset_sql() % {'sequence': seq_name
,
152 'table': self
.quote_name(table_name
),
153 'column': column_name
}
159 def sequence_reset_sql(self
, style
, model_list
):
160 from django
.db
import models
162 query
= _get_sequence_reset_sql()
163 for model
in model_list
:
164 for f
in model
._meta
.fields
:
165 if isinstance(f
, models
.AutoField
):
166 sequence_name
= get_sequence_name(model
._meta
.db_table
)
167 column_name
= self
.quote_name(f
.db_column
or f
.name
)
168 output
.append(query
% {'sequence': sequence_name
,
169 'table': model
._meta
.db_table
,
170 'column': column_name
})
171 break # Only one AutoField is allowed per model, so don't bother continuing.
172 for f
in model
._meta
.many_to_many
:
173 sequence_name
= get_sequence_name(f
.m2m_db_table())
174 output
.append(query
% {'sequence': sequence_name
,
175 'table': f
.m2m_db_table(),
176 'column': self
.quote_name('id')})
179 def start_transaction_sql(self
):
182 def tablespace_sql(self
, tablespace
, inline
=False):
183 return "%sTABLESPACE %s" % ((inline
and "USING INDEX " or ""), self
.quote_name(tablespace
))
185 class DatabaseWrapper(BaseDatabaseWrapper
):
186 features
= DatabaseFeatures()
187 ops
= DatabaseOperations()
190 'iexact': '= UPPER(%s)',
191 'contains': "LIKEC %s ESCAPE '\\'",
192 'icontains': "LIKEC UPPER(%s) ESCAPE '\\'",
197 'startswith': "LIKEC %s ESCAPE '\\'",
198 'endswith': "LIKEC %s ESCAPE '\\'",
199 'istartswith': "LIKEC UPPER(%s) ESCAPE '\\'",
200 'iendswith': "LIKEC UPPER(%s) ESCAPE '\\'",
202 oracle_version
= None
204 def _valid_connection(self
):
205 return self
.connection
is not None
207 def _cursor(self
, settings
):
209 if not self
._valid
_connection
():
210 if len(settings
.DATABASE_HOST
.strip()) == 0:
211 settings
.DATABASE_HOST
= 'localhost'
212 if len(settings
.DATABASE_PORT
.strip()) != 0:
213 dsn
= Database
.makedsn(settings
.DATABASE_HOST
, int(settings
.DATABASE_PORT
), settings
.DATABASE_NAME
)
214 self
.connection
= Database
.connect(settings
.DATABASE_USER
, settings
.DATABASE_PASSWORD
, dsn
, **self
.options
)
216 conn_string
= "%s/%s@%s" % (settings
.DATABASE_USER
, settings
.DATABASE_PASSWORD
, settings
.DATABASE_NAME
)
217 self
.connection
= Database
.connect(conn_string
, **self
.options
)
218 cursor
= FormatStylePlaceholderCursor(self
.connection
)
219 # Set oracle date to ansi date format. This only needs to execute
220 # once when we create a new connection.
221 cursor
.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD' "
222 "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
224 self
.oracle_version
= int(self
.connection
.version
.split('.')[0])
225 # There's no way for the DatabaseOperations class to know the
226 # currently active Oracle version, so we do some setups here.
227 # TODO: Multi-db support will need a better solution (a way to
228 # communicate the current version).
229 if self
.oracle_version
<= 9:
230 self
.ops
.regex_lookup
= self
.ops
.regex_lookup_9
232 self
.ops
.regex_lookup
= self
.ops
.regex_lookup_10
236 self
.connection
.stmtcachesize
= 20
238 # Django docs specify cx_Oracle version 4.3.1 or higher, but
239 # stmtcachesize is available only in 4.3.2 and up.
242 cursor
= FormatStylePlaceholderCursor(self
.connection
)
243 # Default arraysize of 1 is highly sub-optimal.
244 cursor
.arraysize
= 100
247 class FormatStylePlaceholderCursor(Database
.Cursor
):
249 Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var"
250 style. This fixes it -- but note that if you want to use a literal "%s" in
251 a query, you'll need to use "%%s".
253 We also do automatic conversion between Unicode on the Python side and
254 UTF-8 -- for talking to Oracle -- in here.
258 def _format_params(self
, params
):
259 if isinstance(params
, dict):
261 charset
= self
.charset
262 for key
, value
in params
.items():
263 result
[smart_str(key
, charset
)] = smart_str(value
, charset
)
266 return tuple([smart_str(p
, self
.charset
, True) for p
in params
])
268 def _guess_input_sizes(self
, params_list
):
269 # Mark any string parameter greater than 4000 characters as an NCLOB.
270 if isinstance(params_list
[0], dict):
272 iterators
= [params
.iteritems() for params
in params_list
]
274 sizes
= [None] * len(params_list
[0])
275 iterators
= [enumerate(params
) for params
in params_list
]
276 for iterator
in iterators
:
277 for key
, value
in iterator
:
278 if isinstance(value
, basestring
) and len(value
) > 4000:
279 sizes
[key
] = Database
.NCLOB
280 if isinstance(sizes
, dict):
281 self
.setinputsizes(**sizes
)
283 self
.setinputsizes(*sizes
)
285 def execute(self
, query
, params
=None):
289 params
= self
._format
_params
(params
)
290 args
= [(':arg%d' % i
) for i
in range(len(params
))]
291 # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
292 # it does want a trailing ';' but not a trailing '/'. However, these
293 # characters must be included in the original query in case the query
294 # is being passed to SQL*Plus.
295 if query
.endswith(';') or query
.endswith('/'):
297 query
= smart_str(query
, self
.charset
) % tuple(args
)
298 self
._guess
_input
_sizes
([params
])
299 return Database
.Cursor
.execute(self
, query
, params
)
301 def executemany(self
, query
, params
=None):
303 args
= [(':arg%d' % i
) for i
in range(len(params
[0]))]
304 except (IndexError, TypeError):
305 # No params given, nothing to do
307 # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
308 # it does want a trailing ';' but not a trailing '/'. However, these
309 # characters must be included in the original query in case the query
310 # is being passed to SQL*Plus.
311 if query
.endswith(';') or query
.endswith('/'):
313 query
= smart_str(query
, self
.charset
) % tuple(args
)
314 new_param_list
= [self
._format
_params
(i
) for i
in params
]
315 self
._guess
_input
_sizes
(new_param_list
)
316 return Database
.Cursor
.executemany(self
, query
, new_param_list
)
319 row
= Database
.Cursor
.fetchone(self
)
322 return tuple([to_unicode(e
) for e
in row
])
324 def fetchmany(self
, size
=None):
326 size
= self
.arraysize
327 return tuple([tuple([to_unicode(e
) for e
in r
]) for r
in Database
.Cursor
.fetchmany(self
, size
)])
330 return tuple([tuple([to_unicode(e
) for e
in r
]) for r
in Database
.Cursor
.fetchall(self
)])
334 Convert strings to Unicode objects (and return all other data types
337 if isinstance(s
, basestring
):
338 return force_unicode(s
)
341 def _get_sequence_reset_sql():
342 # TODO: colorize this SQL code with style.SQL_KEYWORD(), etc.
348 LOCK TABLE %(table)s IN SHARE MODE;
349 SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
350 SELECT %(sequence)s.nextval INTO cval FROM dual;
351 cval := startvalue - cval;
353 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
354 SELECT %(sequence)s.nextval INTO cval FROM dual;
355 EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
361 def get_sequence_name(table
):
362 name_length
= DatabaseOperations().max_name_length() - 3
363 return '%s_SQ' % util
.truncate_name(table
, name_length
).upper()
365 def get_trigger_name(table
):
366 name_length
= DatabaseOperations().max_name_length() - 3
367 return '%s_TR' % util
.truncate_name(table
, name_length
).upper()