web/lib/django/db/backends/oracle/query.py
changeset 0 0d40e90630ef
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 Custom Query class for Oracle.
       
     3 Derives from: django.db.models.sql.query.Query
       
     4 """
       
     5 
       
     6 import datetime
       
     7 
       
     8 from django.db.backends import util
       
     9 from django.utils.encoding import force_unicode
       
    10 
       
    11 # Cache. Maps default query class to new Oracle query class.
       
    12 _classes = {}
       
    13 
       
    14 def query_class(QueryClass, Database):
       
    15     """
       
    16     Returns a custom django.db.models.sql.query.Query subclass that is
       
    17     appropriate for Oracle.
       
    18 
       
    19     The 'Database' module (cx_Oracle) is passed in here so that all the setup
       
    20     required to import it only needs to be done by the calling module.
       
    21     """
       
    22     global _classes
       
    23     try:
       
    24         return _classes[QueryClass]
       
    25     except KeyError:
       
    26         pass
       
    27 
       
    28     class OracleQuery(QueryClass):
       
    29         def __reduce__(self):
       
    30             """
       
    31             Enable pickling for this class (normal pickling handling doesn't
       
    32             work as Python can only pickle module-level classes by default).
       
    33             """
       
    34             if hasattr(QueryClass, '__getstate__'):
       
    35                 assert hasattr(QueryClass, '__setstate__')
       
    36                 data = self.__getstate__()
       
    37             else:
       
    38                 data = self.__dict__
       
    39             return (unpickle_query_class, (QueryClass,), data)
       
    40 
       
    41         def resolve_columns(self, row, fields=()):
       
    42             # If this query has limit/offset information, then we expect the
       
    43             # first column to be an extra "_RN" column that we need to throw
       
    44             # away.
       
    45             if self.high_mark is not None or self.low_mark:
       
    46                 rn_offset = 1
       
    47             else:
       
    48                 rn_offset = 0
       
    49             index_start = rn_offset + len(self.extra_select.keys())
       
    50             values = [self.convert_values(v, None)
       
    51                       for v in row[rn_offset:index_start]]
       
    52             for value, field in map(None, row[index_start:], fields):
       
    53                 values.append(self.convert_values(value, field))
       
    54             return tuple(values)
       
    55 
       
    56         def convert_values(self, value, field):
       
    57             if isinstance(value, Database.LOB):
       
    58                 value = value.read()
       
    59                 if field and field.get_internal_type() == 'TextField':
       
    60                     value = force_unicode(value)
       
    61 
       
    62             # Oracle stores empty strings as null. We need to undo this in
       
    63             # order to adhere to the Django convention of using the empty
       
    64             # string instead of null, but only if the field accepts the
       
    65             # empty string.
       
    66             if value is None and field and field.empty_strings_allowed:
       
    67                 value = u''
       
    68             # Convert 1 or 0 to True or False
       
    69             elif value in (1, 0) and field and field.get_internal_type() in ('BooleanField', 'NullBooleanField'):
       
    70                 value = bool(value)
       
    71             # Force floats to the correct type
       
    72             elif value is not None and field and field.get_internal_type() == 'FloatField':
       
    73                 value = float(value)
       
    74             # Convert floats to decimals
       
    75             elif value is not None and field and field.get_internal_type() == 'DecimalField':
       
    76                 value = util.typecast_decimal(field.format_number(value))
       
    77             # cx_Oracle always returns datetime.datetime objects for
       
    78             # DATE and TIMESTAMP columns, but Django wants to see a
       
    79             # python datetime.date, .time, or .datetime.  We use the type
       
    80             # of the Field to determine which to cast to, but it's not
       
    81             # always available.
       
    82             # As a workaround, we cast to date if all the time-related
       
    83             # values are 0, or to time if the date is 1/1/1900.
       
    84             # This could be cleaned a bit by adding a method to the Field
       
    85             # classes to normalize values from the database (the to_python
       
    86             # method is used for validation and isn't what we want here).
       
    87             elif isinstance(value, Database.Timestamp):
       
    88                 # In Python 2.3, the cx_Oracle driver returns its own
       
    89                 # Timestamp object that we must convert to a datetime class.
       
    90                 if not isinstance(value, datetime.datetime):
       
    91                     value = datetime.datetime(value.year, value.month,
       
    92                             value.day, value.hour, value.minute, value.second,
       
    93                             value.fsecond)
       
    94                 if field and field.get_internal_type() == 'DateTimeField':
       
    95                     pass
       
    96                 elif field and field.get_internal_type() == 'DateField':
       
    97                     value = value.date()
       
    98                 elif field and field.get_internal_type() == 'TimeField' or (value.year == 1900 and value.month == value.day == 1):
       
    99                     value = value.time()
       
   100                 elif value.hour == value.minute == value.second == value.microsecond == 0:
       
   101                     value = value.date()
       
   102             return value
       
   103 
       
   104         def as_sql(self, with_limits=True, with_col_aliases=False):
       
   105             """
       
   106             Creates the SQL for this query. Returns the SQL string and list
       
   107             of parameters.  This is overriden from the original Query class
       
   108             to handle the additional SQL Oracle requires to emulate LIMIT
       
   109             and OFFSET.
       
   110 
       
   111             If 'with_limits' is False, any limit/offset information is not
       
   112             included in the query.
       
   113             """
       
   114 
       
   115             # The `do_offset` flag indicates whether we need to construct
       
   116             # the SQL needed to use limit/offset with Oracle.
       
   117             do_offset = with_limits and (self.high_mark is not None
       
   118                                          or self.low_mark)
       
   119             if not do_offset:
       
   120                 sql, params = super(OracleQuery, self).as_sql(with_limits=False,
       
   121                         with_col_aliases=with_col_aliases)
       
   122             else:
       
   123                 sql, params = super(OracleQuery, self).as_sql(with_limits=False,
       
   124                                                         with_col_aliases=True)
       
   125 
       
   126                 # Wrap the base query in an outer SELECT * with boundaries on
       
   127                 # the "_RN" column.  This is the canonical way to emulate LIMIT
       
   128                 # and OFFSET on Oracle.
       
   129                 high_where = ''
       
   130                 if self.high_mark is not None:
       
   131                     high_where = 'WHERE ROWNUM <= %d' % (self.high_mark,)
       
   132                 sql = 'SELECT * FROM (SELECT ROWNUM AS "_RN", "_SUB".* FROM (%s) "_SUB" %s) WHERE "_RN" > %d' % (sql, high_where, self.low_mark)
       
   133 
       
   134             return sql, params
       
   135 
       
   136     _classes[QueryClass] = OracleQuery
       
   137     return OracleQuery
       
   138 
       
   139 def unpickle_query_class(QueryClass):
       
   140     """
       
   141     Utility function, called by Python's unpickling machinery, that handles
       
   142     unpickling of Oracle Query subclasses.
       
   143     """
       
   144     # XXX: Would be nice to not have any dependency on cx_Oracle here. Since
       
   145     # modules can't be pickled, we need a way to know to load the right module.
       
   146     import cx_Oracle
       
   147 
       
   148     klass = query_class(QueryClass, cx_Oracle)
       
   149     return klass.__new__(klass)
       
   150 unpickle_query_class.__safe_for_unpickling__ = True