Commit b1f6a4d6 authored by Karen Tracey's avatar Karen Tracey
Browse files

Fixed #10154: Allow combining F expressions with timedelta values.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@15018 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent f0cd656e
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -154,6 +154,9 @@ class BaseDatabaseFeatures(object):
    # deferred
    can_defer_constraint_checks = False

    # date_interval_sql can properly handle mixed Date/DateTime fields and timedeltas
    supports_mixed_date_datetime_comparisons = True

    # Features that need to be confirmed at runtime
    # Cache whether the confirmation has been performed.
    _confirmed = False
@@ -220,6 +223,12 @@ class BaseDatabaseOperations(object):
        """
        raise NotImplementedError()

    def date_interval_sql(self, sql, connector, timedelta):
        """
        Implements the date interval functionality for expressions
        """
        raise NotImplementedError()

    def date_trunc_sql(self, lookup_type, field_name):
        """
        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
+4 −0
Original line number Diff line number Diff line
@@ -158,6 +158,10 @@ class DatabaseOperations(BaseDatabaseOperations):
            sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
        return sql

    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 drop_foreignkey_sql(self):
        return "DROP FOREIGN KEY"

+14 −0
Original line number Diff line number Diff line
@@ -118,6 +118,20 @@ WHEN (new.%(col_name)s IS NULL)
        else:
            return "EXTRACT(%s FROM %s)" % (lookup_type, field_name)

    def date_interval_sql(self, sql, connector, timedelta):
        """
        Implements the interval functionality for expressions
        format for Oracle:
        (datefield + 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)

    def date_trunc_sql(self, lookup_type, field_name):
        # Oracle uses TRUNC() for both dates and numbers.
        # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
+17 −0
Original line number Diff line number Diff line
@@ -27,6 +27,23 @@ class DatabaseOperations(BaseDatabaseOperations):
        else:
            return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)

    def date_interval_sql(self, sql, connector, timedelta):
        """
        implements the interval functionality for expressions
        format for Postgres:
            (datefield + interval '3 days 200 seconds 5 microseconds')
        """
        modifiers = []
        if timedelta.days:
            modifiers.append(u'%s days' % timedelta.days)
        if timedelta.seconds:
            modifiers.append(u'%s seconds' % timedelta.seconds)
        if timedelta.microseconds:
            modifiers.append(u'%s microseconds' % timedelta.microseconds)
        mods = u' '.join(modifiers)
        conn = u' %s ' % connector
        return u'(%s)' % conn.join([sql, u'interval \'%s\'' % mods])

    def date_trunc_sql(self, lookup_type, field_name):
        # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
        return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
+32 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ standard library.

import re
import sys
import datetime

from django.db import utils
from django.db.backends import *
@@ -63,6 +64,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
    test_db_allows_multiple_connections = False
    supports_unspecified_pk = True
    supports_1000_query_parameters = False
    supports_mixed_date_datetime_comparisons = False

    def _supports_stddev(self):
        """Confirm support for STDDEV and related stats functions
@@ -90,6 +92,16 @@ class DatabaseOperations(BaseDatabaseOperations):
        # cause a collision with a field name).
        return "django_extract('%s', %s)" % (lookup_type.lower(), field_name)

    def date_interval_sql(self, sql, connector, timedelta):
        # It would be more straightforward if we could use the sqlite strftime
        # function, but it does not allow for keeping six digits of fractional
        # second information, nor does it allow for formatting date and datetime
        # values differently. So instead we register our own function that 
        # formats the datetime combined with the delta in a manner suitable 
        # for comparisons.
        return  u'django_format_dtdelta(%s, "%s", "%d", "%d", "%d")' % (sql, 
            connector, timedelta.days, timedelta.seconds, timedelta.microseconds)

    def date_trunc_sql(self, lookup_type, field_name):
        # sqlite doesn't support DATE_TRUNC, so we fake it with a user-defined
        # function django_date_trunc that's registered in connect(). Note that
@@ -197,6 +209,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
            self.connection.create_function("django_extract", 2, _sqlite_extract)
            self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
            self.connection.create_function("regexp", 2, _sqlite_regexp)
            self.connection.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta)
            connection_created.send(sender=self.__class__, connection=self)
        return self.connection.cursor(factory=SQLiteCursorWrapper)

@@ -260,6 +273,25 @@ def _sqlite_date_trunc(lookup_type, dt):
    elif lookup_type == 'day':
        return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)

def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
    try:
        dt = util.typecast_timestamp(dt)
        delta = datetime.timedelta(int(days), int(secs), int(usecs))
        if conn.strip() == '+':
            dt = dt + delta
        else:
            dt = dt - delta
    except (ValueError, TypeError):
        return None

    if isinstance(dt, datetime.datetime):
        rv = dt.strftime("%Y-%m-%d %H:%M:%S")
        if dt.microsecond:
            rv = "%s.%0.6d" % (rv, dt.microsecond)
    else:
        rv = dt.strftime("%Y-%m-%d")
    return rv

def _sqlite_regexp(re_pattern, re_string):
    import re
    try:
Loading