Opened 9 years ago
Last modified 4 years ago
#25287 new New feature
Multiplying and dividing connectors for duration expressions are not supported on SQLite and MySQL.
Reported by: | Ahmet DAL | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 3.0 |
Severity: | Normal | Keywords: | sqlite3, mysql, combine_duration_expression, F expressions, |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
I have a query using F expressions. When I run this query on PostgreSQL it works. But in tests with sqlite3, it does not work;
Here is my complex query.
expires = Expire.objects.filter(ticket__date_opened__lte=(datetime.now() - F("action__percent") * F("action__rule__duration") / 100))
Sqlite backend does not support connector *. When I looked into the code, it only supports + and - connectors.
DatabaseError: Invalid connector for timedelta: *.
def combine_duration_expression(self, connector, sub_expressions): if connector not in ['+', '-']: raise utils.DatabaseError('Invalid connector for timedelta: %s.' % connector) fn_params = ["'%s'" % connector] + sub_expressions if len(fn_params) > 3: raise ValueError('Too many params for timedelta operations.') return "django_format_dtdelta(%s)" % ', '.join(fn_params)
I tried to add * and / operator in the list; no problem occured. I think this can be fixed simply.
Change History (19)
comment:1 by , 9 years ago
Description: | modified (diff) |
---|
comment:2 by , 9 years ago
Summary: | Multiplying and dividing connectors are not supported by sqlite backed. → Multiplying and dividing connectors for datetime expressions are not supported by sqlite backed. |
---|---|
Triage Stage: | Unreviewed → Accepted |
Type: | Bug → New feature |
comment:4 by , 9 years ago
This looks like an order of precedence issue. Durations only support + and -, but F() expressions support them all. Try rewriting your query like this please?
expires = Expire.objects.filter( ticket__date_opened__lte= datetime.now() - ( (F("action__percent") * F("action__rule__duration")) / 100 ) )
I've broken up the query over multiple lines to show where the brackets should be placed. The query should be datetime() - ( calculation )
.
comment:5 by , 9 years ago
You may also need an ExpressionWrapper to provide the right hand side output type:
expires = Expire.objects.filter( ticket__date_opened__lte= datetime.now() - ExpressionWrapper( (F("action__percent") * F("action__rule__duration")) / 100, output_field=FloatField() ) )
comment:6 by , 9 years ago
It looks like there's an issue with the user-defined function used by the SQLite backend to make arithmetic calculations:
https://github.com/django/django/blob/master/django/db/backends/sqlite3/base.py#L416
It only expects microseconds, timedeltas and datetime objects. In the given example, it also would need to expect an integer (the percent). This works with the PostgreSQL backend but not with the SQLite backend.
comment:7 by , 5 years ago
Version: | 1.8 → 3.0 |
---|
I was able to reproduce the reported error on the latest master (a5855c8f0fe17b7e888bd8137874ef78012a7294) by using the following models:
from django.db import models class Ticket(models.Model): date_opened = models.DateTimeField() class Rule(models.Model): duration = models.DurationField() class Action(models.Model): percent = models.FloatField() rule = models.ForeignKey('Rule', on_delete=models.CASCADE) class Expire(models.Model): ticket = models.ForeignKey('Ticket', on_delete=models.CASCADE) action = models.ForeignKey('Action', on_delete=models.CASCADE)
And the following testcase:
from datetime import timedelta from django.db.models import F from django.test import TestCase from django.utils import timezone from .models import Ticket, Rule, Action, Expire class TicketTestCase(TestCase): def test_ticket(self): now = timezone.now() ticket1 = Ticket.objects.create(date_opened=now-timedelta(days=6)) ticket2 = Ticket.objects.create(date_opened=now-timedelta(days=4)) rule = Rule.objects.create(duration=timedelta(days=10)) action = Action.objects.create(rule=rule, percent=50) expire = Expire.objects.create(ticket=ticket1, action=action) expire = Expire.objects.create(ticket=ticket2, action=action) qs = Expire.objects.filter(ticket__date_opened__lte=(timezone.now() - F("action__percent") * F("action__rule__duration") / 100)) self.assertQuerysetEqual(qs, [ticket1.pk], transform=lambda x: x.ticket.pk)
As described in the original ticket, the testcase fails with sqlite (DatabaseError: Invalid connector for timedelta: *.
) but passes when using postgresql.
The proposed solution of adding the new *
and /
operators to sqlite3.DatebaseOperations.combine_duration_expression()
[1] only works in that the DatebaseError
disappears but the test still fails because the returned queryset is empty.
Josh's suggestions of wrapping everything in an ExpressionWrapper
don't seem to make a difference either.
[1] https://github.com/django/django/blob/a5855c8f0fe17b7e888bd8137874ef78012a7294/django/db/backends/sqlite3/operations.py#L315
comment:8 by , 4 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:9 by , 4 years ago
Summary: | Multiplying and dividing connectors for datetime expressions are not supported by sqlite backed. → Multiplying and dividing connectors for duration expressions are not supported by sqlite backed. |
---|
I have created a patch that seems to work. I know far too little about databases though to test and finish it properly. So I hope it is helpful to someone else:
https://github.com/xi/django/commit/240501051db0621a456101a9e599304e1be83faa
comment:10 by , 4 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:11 by , 4 years ago
Has patch: | set |
---|---|
Owner: | set to |
Patch needs improvement: | set |
Status: | new → assigned |
comment:12 by , 4 years ago
Keywords: | mysql added |
---|
I took a stab at a fix at https://github.com/django/django/pull/14237
It seems like this is not only an issue for sqlite but also for mysql
comment:13 by , 4 years ago
Needs documentation: | set |
---|---|
Needs tests: | set |
comment:14 by , 4 years ago
Summary: | Multiplying and dividing connectors for duration expressions are not supported by sqlite backed. → Multiplying and dividing connectors for duration expressions are not supported on SQLite and MySQL. |
---|
comment:15 by , 4 years ago
Needs documentation: | unset |
---|---|
Needs tests: | unset |
Patch needs improvement: | unset |
Triage Stage: | Accepted → Ready for checkin |
comment:19 by , 4 years ago
Has patch: | unset |
---|
comment:20 by , 4 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
Feel free to submit a tested patch if you are able.