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.
This commit is contained in:
parent
803947161b
commit
5ca82e710e
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -1577,7 +1577,8 @@ class DecimalField(Field):
|
|||
class DurationField(Field):
|
||||
"""Stores timedelta objects.
|
||||
|
||||
Uses interval on postgres, bigint of microseconds on other databases.
|
||||
Uses interval on postgres, INVERAL DAY TO SECOND on Oracle, and bigint of
|
||||
microseconds on other databases.
|
||||
"""
|
||||
empty_strings_allowed = False
|
||||
default_error_messages = {
|
||||
|
|
|
@ -551,7 +551,8 @@ The default form widget for this field is a :class:`~django.forms.TextInput`.
|
|||
|
||||
A field for storing periods of time - modeled in Python by
|
||||
:class:`~python:datetime.timedelta`. When used on PostgreSQL, the data type
|
||||
used is an ``interval``, otherwise a ``bigint`` of microseconds is used.
|
||||
used is an ``interval`` and on Oracle the data type is ``INTERVAL DAY(9) TO
|
||||
SECOND(6)``. Otherwise a ``bigint`` of microseconds is used.
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -60,9 +60,10 @@ New data types
|
|||
|
||||
* Django now has a :class:`~django.db.models.DurationField` for storing periods
|
||||
of time - modeled in Python by :class:`~python:datetime.timedelta`. It is
|
||||
stored in the native ``interval`` data type on PostgreSQL and as a ``bigint``
|
||||
of microseconds on other backends. Date and time related arithmetic has also
|
||||
been improved on all backends. There is a corresponding :class:`form field
|
||||
stored in the native ``interval`` data type on PostgreSQL, as a ``INTERVAL
|
||||
DAY(9) TO SECOND(6)`` on Oracle, and as a ``bigint`` of microseconds on other
|
||||
backends. Date and time related arithmetic has also been improved on all
|
||||
backends. There is a corresponding :class:`form field
|
||||
<django.forms.DurationField>`.
|
||||
|
||||
Query Expressions
|
||||
|
|
Loading…
Reference in New Issue