Commit 5ca82e71 authored by Marc Tamlyn's avatar Marc Tamlyn
Browse files

Fixed #24033 -- Use interval type on Oracle.

Use INTERVAL DAY(9) TO SECOND(6) for Durationfield on Oracle rather than
storing as a NUMBER(19) of microseconds.

There are issues with cx_Oracle which require some extra data
manipulation in the database backend when constructing queries, but it
handles the conversion back to timedelta objects cleanly.

Thanks to Shai for the review.
parent 80394716
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -580,6 +580,12 @@ class BaseDatabaseFeatures(object):
    # Is there a true datatype for timedeltas?
    has_native_duration_field = False

    # Does the database driver support timedeltas as arguments?
    # This is only relevant when there is a native duration field.
    # Specifically, there is a bug with cx_Oracle:
    # https://bitbucket.org/anthony_tuininga/cx_oracle/issue/7/
    driver_supports_timedelta_args = False

    # Do time/datetime fields have microsecond precision?
    supports_microsecond_precision = True

+7 −3
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ from django.db.backends.oracle.introspection import DatabaseIntrospection
from django.db.backends.oracle.schema import DatabaseSchemaEditor
from django.db.utils import InterfaceError
from django.utils import six, timezone
from django.utils.duration import duration_string
from django.utils.encoding import force_bytes, force_text
from django.utils.functional import cached_property

@@ -106,6 +107,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
    supports_timezones = False
    has_zoneinfo_database = pytz is not None
    supports_bitwise_or = False
    has_native_duration_field = True
    can_defer_constraint_checks = True
    supports_partially_nullable_unique_constraints = False
    truncates_names = True
@@ -212,9 +214,6 @@ WHEN (new.%(col_name)s IS NULL)
        return fmt % (days, hours, minutes, seconds, timedelta.microseconds,
                day_precision), []

    def format_for_duration_arithmetic(self, sql):
        return "NUMTODSINTERVAL(%s / 1000000, 'SECOND')" % sql

    def date_trunc_sql(self, lookup_type, field_name):
        # http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm#i1002084
        if lookup_type in ('year', 'month'):
@@ -796,6 +795,11 @@ class OracleParam(object):
                param = timezone.make_aware(param, default_timezone)
            param = Oracle_datetime.from_datetime(param.astimezone(timezone.utc))

        if isinstance(param, datetime.timedelta):
            param = duration_string(param)
            if ' ' not in param:
                param = '0 ' + param

        string_size = 0
        # Oracle doesn't recognize True and False correctly in Python 3.
        # The conversion done below works both in 2 and 3.
+1 −1
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ class DatabaseCreation(BaseDatabaseCreation):
        'DateField': 'DATE',
        'DateTimeField': 'TIMESTAMP',
        'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
        'DurationField': 'NUMBER(19)',
        'DurationField': 'INTERVAL DAY(9) TO SECOND(6)',
        'FileField': 'NVARCHAR2(%(max_length)s)',
        'FilePathField': 'NVARCHAR2(%(max_length)s)',
        'FloatField': 'DOUBLE PRECISION',
+1 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
    can_return_id_from_insert = True
    has_real_datatype = True
    has_native_duration_field = True
    driver_supports_timedelta_args = True
    can_defer_constraint_checks = True
    has_select_for_update = True
    has_select_for_update_nowait = True
+2 −1
Original line number Diff line number Diff line
@@ -488,7 +488,8 @@ class Value(ExpressionNode):

class DurationValue(Value):
    def as_sql(self, compiler, connection):
        if connection.features.has_native_duration_field:
        if (connection.features.has_native_duration_field and
                connection.features.driver_supports_timedelta_args):
            return super(DurationValue, self).as_sql(compiler, connection)
        return connection.ops.date_interval_sql(self.value)

Loading