diff -r 6641e941ef1e -r ff1a9aa48cfd app/django/db/backends/oracle/base.py --- a/app/django/db/backends/oracle/base.py Tue Oct 14 12:36:55 2008 +0000 +++ b/app/django/db/backends/oracle/base.py Tue Oct 14 16:00:59 2008 +0000 @@ -5,11 +5,8 @@ """ import os - -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util -from django.db.backends.oracle import query -from django.utils.datastructures import SortedDict -from django.utils.encoding import smart_str, force_unicode +import datetime +import time # Oracle takes client-side character set encoding from the environment. os.environ['NLS_LANG'] = '.UTF8' @@ -19,18 +16,23 @@ from django.core.exceptions import ImproperlyConfigured raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e) -DatabaseError = Database.Error +from django.db.backends import * +from django.db.backends.oracle import query +from django.db.backends.oracle.client import DatabaseClient +from django.db.backends.oracle.creation import DatabaseCreation +from django.db.backends.oracle.introspection import DatabaseIntrospection +from django.utils.encoding import smart_str, force_unicode + +DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError + class DatabaseFeatures(BaseDatabaseFeatures): - allows_group_by_ordinal = False - allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) empty_fetchmany_value = () needs_datetime_string_cast = False - needs_upper_for_iops = True - supports_tablespaces = True - uses_case_insensitive_names = True uses_custom_query_class = True + interprets_empty_strings_as_nulls = True + class DatabaseOperations(BaseDatabaseOperations): def autoinc_sql(self, table, column): @@ -40,7 +42,17 @@ tr_name = get_trigger_name(table) tbl_name = self.quote_name(table) col_name = self.quote_name(column) - sequence_sql = 'CREATE SEQUENCE %s;' % sq_name + sequence_sql = """ + DECLARE + i INTEGER; + BEGIN + SELECT COUNT(*) INTO i FROM USER_CATALOG + WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE'; + IF i = 0 THEN + EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_name)s'; + END IF; + END; + /""" % locals() trigger_sql = """ CREATE OR REPLACE TRIGGER %(tr_name)s BEFORE INSERT ON %(tbl_name)s @@ -86,11 +98,6 @@ cursor.execute('SELECT %s_sq.currval FROM dual' % sq_name) return cursor.fetchone()[0] - def limit_offset_sql(self, limit, offset=None): - # Limits and offset are too complicated to be handled here. - # Instead, they are handled in django/db/backends/oracle/query.py. - return "" - def lookup_cast(self, lookup_type): if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'): return "UPPER(%s)" @@ -99,6 +106,9 @@ def max_name_length(self): return 30 + def prep_for_iexact_query(self, x): + return x + def query_class(self, DefaultQueryClass): return query.query_class(DefaultQueryClass, Database) @@ -145,11 +155,11 @@ # Since we've just deleted all the rows, running our sequence # ALTER code will reset the sequence to 0. for sequence_info in sequences: - table_name = sequence_info['table'] - seq_name = get_sequence_name(table_name) + sequence_name = get_sequence_name(sequence_info['table']) + table_name = self.quote_name(sequence_info['table']) column_name = self.quote_name(sequence_info['column'] or 'id') - query = _get_sequence_reset_sql() % {'sequence': seq_name, - 'table': self.quote_name(table_name), + query = _get_sequence_reset_sql() % {'sequence': sequence_name, + 'table': table_name, 'column': column_name} sql.append(query) return sql @@ -161,19 +171,22 @@ output = [] query = _get_sequence_reset_sql() for model in model_list: - for f in model._meta.fields: + for f in model._meta.local_fields: if isinstance(f, models.AutoField): + table_name = self.quote_name(model._meta.db_table) sequence_name = get_sequence_name(model._meta.db_table) - column_name = self.quote_name(f.db_column or f.name) + column_name = self.quote_name(f.column) output.append(query % {'sequence': sequence_name, - 'table': model._meta.db_table, + 'table': table_name, 'column': column_name}) break # Only one AutoField is allowed per model, so don't bother continuing. for f in model._meta.many_to_many: + table_name = self.quote_name(f.m2m_db_table()) sequence_name = get_sequence_name(f.m2m_db_table()) + column_name = self.quote_name('id') output.append(query % {'sequence': sequence_name, - 'table': f.m2m_db_table(), - 'column': self.quote_name('id')}) + 'table': table_name, + 'column': column_name}) return output def start_transaction_sql(self): @@ -182,9 +195,22 @@ def tablespace_sql(self, tablespace, inline=False): return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace)) + def value_to_db_time(self, value): + if value is None: + return None + if isinstance(value, basestring): + return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6])) + return datetime.datetime(1900, 1, 1, value.hour, value.minute, + value.second, value.microsecond) + + def year_lookup_bounds_for_date_field(self, value): + first = '%s-01-01' + second = '%s-12-31' + return [first % value, second % value] + + class DatabaseWrapper(BaseDatabaseWrapper): - features = DatabaseFeatures() - ops = DatabaseOperations() + operators = { 'exact': '= %s', 'iexact': '= UPPER(%s)', @@ -201,6 +227,16 @@ } oracle_version = None + def __init__(self, *args, **kwargs): + super(DatabaseWrapper, self).__init__(*args, **kwargs) + + self.features = DatabaseFeatures() + self.ops = DatabaseOperations() + self.client = DatabaseClient() + self.creation = DatabaseCreation(self) + self.introspection = DatabaseIntrospection(self) + self.validation = BaseDatabaseValidation() + def _valid_connection(self): return self.connection is not None @@ -244,6 +280,28 @@ cursor.arraysize = 100 return cursor + +class OracleParam(object): + """ + Wrapper object for formatting parameters for Oracle. If the string + representation of the value is large enough (greater than 4000 characters) + the input size needs to be set as NCLOB. Alternatively, if the parameter has + an `input_size` attribute, then the value of the `input_size` attribute will + be used instead. Otherwise, no input size will be set for the parameter when + executing the query. + """ + def __init__(self, param, charset, strings_only=False): + self.smart_str = smart_str(param, charset, strings_only) + if hasattr(param, 'input_size'): + # If parameter has `input_size` attribute, use that. + self.input_size = param.input_size + elif isinstance(param, basestring) and len(param) > 4000: + # Mark any string parameter greater than 4000 characters as an NCLOB. + self.input_size = Database.NCLOB + else: + self.input_size = None + + class FormatStylePlaceholderCursor(Database.Cursor): """ Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" @@ -258,15 +316,13 @@ def _format_params(self, params): if isinstance(params, dict): result = {} - charset = self.charset for key, value in params.items(): - result[smart_str(key, charset)] = smart_str(value, charset) + result[smart_str(key, self.charset)] = OracleParam(param, self.charset) return result else: - return tuple([smart_str(p, self.charset, True) for p in params]) + return tuple([OracleParam(p, self.charset, True) for p in params]) def _guess_input_sizes(self, params_list): - # Mark any string parameter greater than 4000 characters as an NCLOB. if isinstance(params_list[0], dict): sizes = {} iterators = [params.iteritems() for params in params_list] @@ -275,13 +331,18 @@ iterators = [enumerate(params) for params in params_list] for iterator in iterators: for key, value in iterator: - if isinstance(value, basestring) and len(value) > 4000: - sizes[key] = Database.NCLOB + if value.input_size: sizes[key] = value.input_size if isinstance(sizes, dict): self.setinputsizes(**sizes) else: self.setinputsizes(*sizes) + def _param_generator(self, params): + if isinstance(params, dict): + return dict([(k, p.smart_str) for k, p in params.iteritems()]) + else: + return [p.smart_str for p in params] + def execute(self, query, params=None): if params is None: params = [] @@ -296,7 +357,13 @@ query = query[:-1] query = smart_str(query, self.charset) % tuple(args) self._guess_input_sizes([params]) - return Database.Cursor.execute(self, query, params) + try: + return Database.Cursor.execute(self, query, self._param_generator(params)) + except DatabaseError, e: + # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. + if e.args[0].code == 1400 and not isinstance(e, IntegrityError): + e = IntegrityError(e.args[0]) + raise e def executemany(self, query, params=None): try: @@ -311,9 +378,15 @@ if query.endswith(';') or query.endswith('/'): query = query[:-1] query = smart_str(query, self.charset) % tuple(args) - new_param_list = [self._format_params(i) for i in params] - self._guess_input_sizes(new_param_list) - return Database.Cursor.executemany(self, query, new_param_list) + formatted = [self._format_params(i) for i in params] + self._guess_input_sizes(formatted) + try: + return Database.Cursor.executemany(self, query, [self._param_generator(p) for p in formatted]) + except DatabaseError, e: + # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. + if e.args[0].code == 1400 and not isinstance(e, IntegrityError): + e = IntegrityError(e.args[0]) + raise e def fetchone(self): row = Database.Cursor.fetchone(self)