Commit 57554442 authored by Marc Tamlyn's avatar Marc Tamlyn
Browse files

Fixed #2443 -- Added DurationField.

A field for storing periods of time - modeled in Python by timedelta. It
is stored in the native interval data type on PostgreSQL and as a bigint
of microseconds on other backends.

Also includes significant changes to the internals of time related maths
in expressions, including the removal of DateModifierNode.

Thanks to Tim and Josh in particular for reviews.
parent a3d96bee
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
from collections import deque
import datetime
import decimal
import time
import warnings

@@ -18,6 +19,7 @@ from django.db.backends.signals import connection_created
from django.db.backends import utils
from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseError, DatabaseErrorWrapper, ProgrammingError
from django.utils.dateparse import parse_duration
from django.utils.deprecation import RemovedInDjango19Warning
from django.utils.functional import cached_property
from django.utils import six
@@ -575,6 +577,9 @@ class BaseDatabaseFeatures(object):

    supports_binary_field = True

    # Is there a true datatype for timedeltas?
    has_native_duration_field = False

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

@@ -1251,8 +1256,16 @@ class BaseDatabaseOperations(object):
        Some field types on some backends do not provide data in the correct
        format, this is the hook for coverter functions.
        """
        if not self.connection.features.has_native_duration_field and internal_type == 'DurationField':
            return [self.convert_durationfield_value]
        return []

    def convert_durationfield_value(self, value, field):
        if value is not None:
            value = str(decimal.Decimal(value) / decimal.Decimal(1000000))
            value = parse_duration(value)
        return value

    def check_aggregate_support(self, aggregate_func):
        """Check that the backend supports the provided aggregate

@@ -1272,6 +1285,9 @@ class BaseDatabaseOperations(object):
        conn = ' %s ' % connector
        return conn.join(sub_expressions)

    def combine_duration_expression(self, connector, sub_expressions):
        return self.combine_expression(connector, sub_expressions)

    def modify_insert_params(self, placeholders, params):
        """Allow modification of insert parameters. Needed for Oracle Spatial
        backend due to #10888.
+6 −3
Original line number Diff line number Diff line
@@ -289,9 +289,12 @@ class DatabaseOperations(BaseDatabaseOperations):
            sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
        return sql, params

    def date_interval_sql(self, sql, connector, timedelta):
        return "(%s %s INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND)" % (sql, connector,
                timedelta.days, timedelta.seconds, timedelta.microseconds)
    def date_interval_sql(self, timedelta):
        return "INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND" % (
            timedelta.days, timedelta.seconds, timedelta.microseconds), []

    def format_for_duration_arithmetic(self, sql):
        return 'INTERVAL %s MICROSECOND' % sql

    def drop_foreignkey_sql(self):
        return "DROP FOREIGN KEY"
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ class DatabaseCreation(BaseDatabaseCreation):
        'DateField': 'date',
        'DateTimeField': 'datetime',
        'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
        'DurationField': 'bigint',
        'FileField': 'varchar(%(max_length)s)',
        'FilePathField': 'varchar(%(max_length)s)',
        'FloatField': 'double precision',
+8 −5
Original line number Diff line number Diff line
@@ -198,19 +198,22 @@ WHEN (new.%(col_name)s IS NULL)
            # http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm
            return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)

    def date_interval_sql(self, sql, connector, timedelta):
    def date_interval_sql(self, timedelta):
        """
        Implements the interval functionality for expressions
        format for Oracle:
        (datefield + INTERVAL '3 00:03:20.000000' DAY(1) TO SECOND(6))
        INTERVAL '3 00:03:20.000000' DAY(1) TO SECOND(6)
        """
        minutes, seconds = divmod(timedelta.seconds, 60)
        hours, minutes = divmod(minutes, 60)
        days = str(timedelta.days)
        day_precision = len(days)
        fmt = "(%s %s INTERVAL '%s %02d:%02d:%02d.%06d' DAY(%d) TO SECOND(6))"
        return fmt % (sql, connector, days, hours, minutes, seconds,
                timedelta.microseconds, day_precision)
        fmt = "INTERVAL '%s %02d:%02d:%02d.%06d' DAY(%d) TO SECOND(6)"
        return fmt % (days, hours, minutes, seconds, timedelta.microseconds,
                day_precision), []

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

    def date_trunc_sql(self, lookup_type, field_name):
        # http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm#i1002084
+1 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ class DatabaseCreation(BaseDatabaseCreation):
        'DateField': 'DATE',
        'DateTimeField': 'TIMESTAMP',
        'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
        'DurationField': 'NUMBER(19)',
        'FileField': 'NVARCHAR2(%(max_length)s)',
        'FilePathField': 'NVARCHAR2(%(max_length)s)',
        'FloatField': 'DOUBLE PRECISION',
Loading