Ticket #10154: 10154_dateexpressions_3.diff

File 10154_dateexpressions_3.diff, 8.0 KB (added by Ian Kelly, 10 years ago)

Updated patch with Oracle syntax

  • django/db/models/expressions.py

     
    11from copy import deepcopy
    2 from datetime import datetime
     2import datetime
     3from django.db import connection
    34
    45from django.utils import tree
    56
     
    2627        super(ExpressionNode, self).__init__(children, connector, negated)
    2728
    2829    def _combine(self, other, connector, reversed, node=None):
     30        if isinstance(other, datetime.timedelta):
     31            return DateModifierNode([self, other], connector)
     32
    2933        if reversed:
    3034            obj = ExpressionNode([other], connector)
    3135            obj.add(node or self, connector)
     
    108112
    109113    def evaluate(self, evaluator, qn):
    110114        return evaluator.evaluate_leaf(self, qn)
     115
     116class DateModifierNode(ExpressionNode):
     117    """
     118    Node that implements the following syntax:
     119    filter(end_date__gt=F('start_date') + datetime.timedelta(days=3, seconds=200))
     120
     121    which translates into:
     122        SQLITE:
     123            where end_date > date(start_date, "3 days", "200 seconds")
     124
     125        POSTGRES:
     126            where end_date > (start_date + interval '3 days 200 seconds')
     127
     128    """
     129    def __init__(self, children, connector, negated=False):
     130        if len(children) != 2:
     131            raise TypeError('You have to specify a node and a timedelta.')
     132        if not isinstance(children[1], datetime.timedelta):
     133            raise TypeError('Second child must be a timedelta.')
     134        if connector is None:
     135            raise TypeError('You have to specify a connector.')
     136        super(DateModifierNode, self).__init__(children, connector, negated)
     137
     138    def prepare(self, evaluator, query, allow_joins):
     139        return evaluator.prepare_node(self, query, allow_joins)
     140
     141    def evaluate(self, evaluator, qn):
     142        if not qn:
     143            qn = connection.ops.quote_name
     144
     145        timedelta = self.children.pop()
     146        sql, params = evaluator.evaluate_node(self, qn)
     147
     148        return connection.ops.date_interval_sql(sql, self.connector, timedelta), params
     149
  • django/db/backends/postgresql/operations.py

     
    3333        else:
    3434            return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)
    3535
     36    def date_interval_sql(self, sql, connector, timedelta):
     37        """
     38        implements the interval functionality for expressions
     39        format for Postgres: (datefield + interval '3 days 200 seconds')
     40        """
     41        modifiers = []
     42        if timedelta.days:
     43            modifiers.append(u'%s days' % timedelta.days)
     44        if timedelta.seconds:
     45            modifiers.append(u'%s seconds' % timedelta.seconds)
     46        mods = u' '.join(modifiers)
     47        conn = u' %s ' % connector
     48        return u'(%s)' % conn.join([sql, u'interval \'%s\'' % mods])
     49
    3650    def date_trunc_sql(self, lookup_type, field_name):
    3751        # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
    3852        return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
  • django/db/backends/oracle/base.py

     
    7878        else:
    7979            return "EXTRACT(%s FROM %s)" % (lookup_type, field_name)
    8080
     81    def date_interval_sql(self, sql, connector, timedelta):
     82        """
     83        Implements the interval functionality for expressions
     84        format for Oracle:
     85        (datefield + INTERVAL '3 00:03:20.000000' DAY(1) TO SECOND(6))
     86        """
     87        minutes, seconds = divmod(timedelta.seconds, 60)
     88        hours, minutes = divmod(minutes, 60)
     89        days = str(timedelta.days)
     90        day_precision = len(days)
     91        fmt = "(%s %s INTERVAL '%s %02d:%02d:%02d.%06d' DAY(%d) TO SECOND(6))"
     92        return fmt % (sql, connector, days, hours, minutes, seconds,
     93                      timedelta.microseconds, day_precision)
     94
    8195    def date_trunc_sql(self, lookup_type, field_name):
    8296        # Oracle uses TRUNC() for both dates and numbers.
    8397        # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
  • django/db/backends/__init__.py

     
    104104        """
    105105        raise NotImplementedError()
    106106
     107    def date_interval_sql(self, sql, connector, timedelta):
     108        """
     109        Implements the interval functionality for expressions
     110        format for sqlite: DATE(datefield, "3 days", "200 seconds")
     111        """
     112        modifiers = []
     113        if timedelta.days:
     114            modifiers.append(u'%s days' % timedelta.days)
     115        if timedelta.seconds:
     116            modifiers.append(u'%s seconds' % timedelta.seconds)
     117        mods = u','.join([u'"%s%s"' % (connector, modifier) for modifier in modifiers])
     118        return u'DATE(%s, %s)' % (sql, mods)
     119
    107120    def date_trunc_sql(self, lookup_type, field_name):
    108121        """
    109122        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
  • tests/modeltests/expressions/models.py

     
    77class Employee(models.Model):
    88    firstname = models.CharField(max_length=50)
    99    lastname = models.CharField(max_length=50)
     10    contract_date = models.DateField(null=True)
     11    start_date = models.DateField(null=True)
    1012
    1113    def __unicode__(self):
    1214        return u'%s %s' % (self.firstname, self.lastname)
     
    3032__test__ = {'API_TESTS': """
    3133>>> from django.db.models import F
    3234
     35>>> import datetime
     36
    3337>>> Company(name='Example Inc.', num_employees=2300, num_chairs=5,
    34 ...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith')).save()
     38...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith',
     39...     contract_date=datetime.date(2008,1,1), start_date=datetime.date(2008,3,1))).save()
    3540>>> Company(name='Foobar Ltd.', num_employees=3, num_chairs=3,
    36 ...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer')).save()
     41...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer',
     42...     contract_date=datetime.date(2008,1,1), start_date=datetime.date(2008,5,1))).save()
    3743>>> Company(name='Test GmbH', num_employees=32, num_chairs=1,
    38 ...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save()
     44...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann',
     45...     contract_date=datetime.date(2008,1,1), start_date=datetime.date(2008,7,1))).save()
    3946
    4047# We can filter for companies where the number of employees is greater than the
    4148# number of chairs.
     
    6875...
    6976FieldError: Joined field references are not permitted in this query
    7077
     78# F expressions for dates may be combined with timedelta
     79# in filter:
     80>>> Employee.objects.filter(start_date__gt=F('contract_date')+datetime.timedelta(days=80, seconds=500))
     81[<Employee: Frank Meyer>, <Employee: Max Mustermann>]
     82
     83>>> Employee.objects.filter(contract_date__gt=F('start_date')-datetime.timedelta(days=80, seconds=500))
     84[<Employee: Joe Smith>]
     85
     86# chaining multiple timedeltas:
     87>>> Employee.objects.filter(start_date__gt=F('contract_date')+datetime.timedelta(days=80)+datetime.timedelta(seconds=500))
     88[<Employee: Frank Meyer>, <Employee: Max Mustermann>]
     89
     90# in update:
     91# move contractdates to 14 days before startdate
     92>>> Employee.objects.update(contract_date=F('start_date')-datetime.timedelta(days=14))
     934
     94>>> [e.contract_date for e in Employee.objects.all()]
     95[datetime.date(2008, 2, 16), datetime.date(2008, 4, 17), datetime.date(2008, 6, 17), None]
     96
    7197"""}
Back to Top