summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2020-01-02 11:33:01 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-01-03 10:35:44 +0100
commit02cda09b13e677db01863fa0a7112dba631b9c5c (patch)
tree6b2b6a464778b0a804570b17ce00b3ccceffa0cb
parentd975415852749814e781425b595c9463c4391c19 (diff)
[3.0.x] Fixed #31133 -- Fixed crash when subtracting against a subquery annotation.
The subtract_temporals() database operation was not handling expressions returning SQL params in mixed database types. Regression in 35431298226165986ad07e91f9d3aca721ff38ec. Thanks Reupen Shah for the report. Backport of 9bcbcd599abac91ea853b2fe10b784ba32df043e from master
-rw-r--r--django/db/backends/base/operations.py2
-rw-r--r--django/db/backends/mysql/operations.py8
-rw-r--r--django/db/backends/oracle/operations.py3
-rw-r--r--django/db/backends/postgresql/operations.py3
-rw-r--r--django/db/backends/sqlite3/operations.py5
-rw-r--r--docs/releases/3.0.3.txt4
-rw-r--r--tests/expressions/tests.py31
7 files changed, 46 insertions, 10 deletions
diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py
index 51370ef2ac..07ab32d544 100644
--- a/django/db/backends/base/operations.py
+++ b/django/db/backends/base/operations.py
@@ -625,7 +625,7 @@ class BaseDatabaseOperations:
if self.connection.features.supports_temporal_subtraction:
lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs
- return "(%s - %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
+ return '(%s - %s)' % (lhs_sql, rhs_sql), (*lhs_params, *rhs_params)
raise NotSupportedError("This backend does not support %s subtraction." % internal_type)
def window_frame_start(self, start):
diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py
index e940286720..241e6e2135 100644
--- a/django/db/backends/mysql/operations.py
+++ b/django/db/backends/mysql/operations.py
@@ -282,13 +282,13 @@ class DatabaseOperations(BaseDatabaseOperations):
# a decimal. MySQL returns an integer without microseconds.
return 'CAST((TIME_TO_SEC(%(lhs)s) - TIME_TO_SEC(%(rhs)s)) * 1000000 AS SIGNED)' % {
'lhs': lhs_sql, 'rhs': rhs_sql
- }, lhs_params + rhs_params
+ }, (*lhs_params, *rhs_params)
return (
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
- ) % {'lhs': lhs_sql, 'rhs': rhs_sql}, lhs_params * 2 + rhs_params * 2
- else:
- return "TIMESTAMPDIFF(MICROSECOND, %s, %s)" % (rhs_sql, lhs_sql), rhs_params + lhs_params
+ ) % {'lhs': lhs_sql, 'rhs': rhs_sql}, tuple(lhs_params) * 2 + tuple(rhs_params) * 2
+ params = (*lhs_params, *rhs_params)
+ return "TIMESTAMPDIFF(MICROSECOND, %s, %s)" % (rhs_sql, lhs_sql), params
def explain_query_prefix(self, format=None, **options):
# Alias MySQL's TRADITIONAL to TEXT for consistency with other backends.
diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py
index 74b1e62b7c..3b00040c10 100644
--- a/django/db/backends/oracle/operations.py
+++ b/django/db/backends/oracle/operations.py
@@ -607,7 +607,8 @@ END;
if internal_type == 'DateField':
lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs
- return "NUMTODSINTERVAL(TO_NUMBER(%s - %s), 'DAY')" % (lhs_sql, rhs_sql), lhs_params + rhs_params
+ params = (*lhs_params, *rhs_params)
+ return "NUMTODSINTERVAL(TO_NUMBER(%s - %s), 'DAY')" % (lhs_sql, rhs_sql), params
return super().subtract_temporals(internal_type, lhs, rhs)
def bulk_batch_size(self, fields, objs):
diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
index fe5b208c6a..0b326f0e66 100644
--- a/django/db/backends/postgresql/operations.py
+++ b/django/db/backends/postgresql/operations.py
@@ -269,7 +269,8 @@ class DatabaseOperations(BaseDatabaseOperations):
if internal_type == 'DateField':
lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs
- return "(interval '1 day' * (%s - %s))" % (lhs_sql, rhs_sql), lhs_params + rhs_params
+ params = (*lhs_params, *rhs_params)
+ return "(interval '1 day' * (%s - %s))" % (lhs_sql, rhs_sql), params
return super().subtract_temporals(internal_type, lhs, rhs)
def window_frame_range_start_end(self, start=None, end=None):
diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py
index afbd67aa16..73db4d0e80 100644
--- a/django/db/backends/sqlite3/operations.py
+++ b/django/db/backends/sqlite3/operations.py
@@ -326,9 +326,10 @@ class DatabaseOperations(BaseDatabaseOperations):
def subtract_temporals(self, internal_type, lhs, rhs):
lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs
+ params = (*lhs_params, *rhs_params)
if internal_type == 'TimeField':
- return "django_time_diff(%s, %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
- return "django_timestamp_diff(%s, %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params
+ return 'django_time_diff(%s, %s)' % (lhs_sql, rhs_sql), params
+ return 'django_timestamp_diff(%s, %s)' % (lhs_sql, rhs_sql), params
def insert_statement(self, ignore_conflicts=False):
return 'INSERT OR IGNORE INTO' if ignore_conflicts else super().insert_statement(ignore_conflicts)
diff --git a/docs/releases/3.0.3.txt b/docs/releases/3.0.3.txt
index db9f0fd562..ecc5ba9294 100644
--- a/docs/releases/3.0.3.txt
+++ b/docs/releases/3.0.3.txt
@@ -9,4 +9,6 @@ Django 3.0.3 fixes several bugs in 3.0.2.
Bugfixes
========
-* ...
+* Fixed a regression in Django 3.0 that caused a crash when subtracting
+ ``DateField``, ``DateTimeField``, or ``TimeField`` from a ``Subquery()``
+ annotation (:ticket:`31133`).
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 126cd586bb..005e9150e7 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -1425,6 +1425,16 @@ class FTimeDeltaTests(TestCase):
self.assertIsNone(queryset.first().shifted)
@skipUnlessDBFeature('supports_temporal_subtraction')
+ def test_date_subquery_subtraction(self):
+ subquery = Experiment.objects.filter(pk=OuterRef('pk')).values('completed')
+ queryset = Experiment.objects.annotate(
+ difference=ExpressionWrapper(
+ subquery - F('completed'), output_field=models.DurationField(),
+ ),
+ ).filter(difference=datetime.timedelta())
+ self.assertTrue(queryset.exists())
+
+ @skipUnlessDBFeature('supports_temporal_subtraction')
def test_time_subtraction(self):
Time.objects.create(time=datetime.time(12, 30, 15, 2345))
queryset = Time.objects.annotate(
@@ -1451,6 +1461,17 @@ class FTimeDeltaTests(TestCase):
self.assertIsNone(queryset.first().shifted)
@skipUnlessDBFeature('supports_temporal_subtraction')
+ def test_time_subquery_subtraction(self):
+ Time.objects.create(time=datetime.time(12, 30, 15, 2345))
+ subquery = Time.objects.filter(pk=OuterRef('pk')).values('time')
+ queryset = Time.objects.annotate(
+ difference=ExpressionWrapper(
+ subquery - F('time'), output_field=models.DurationField(),
+ ),
+ ).filter(difference=datetime.timedelta())
+ self.assertTrue(queryset.exists())
+
+ @skipUnlessDBFeature('supports_temporal_subtraction')
def test_datetime_subtraction(self):
under_estimate = [
e.name for e in Experiment.objects.filter(estimated_time__gt=F('end') - F('start'))
@@ -1475,6 +1496,16 @@ class FTimeDeltaTests(TestCase):
self.assertIsNone(queryset.first().shifted)
@skipUnlessDBFeature('supports_temporal_subtraction')
+ def test_datetime_subquery_subtraction(self):
+ subquery = Experiment.objects.filter(pk=OuterRef('pk')).values('start')
+ queryset = Experiment.objects.annotate(
+ difference=ExpressionWrapper(
+ subquery - F('start'), output_field=models.DurationField(),
+ ),
+ ).filter(difference=datetime.timedelta())
+ self.assertTrue(queryset.exists())
+
+ @skipUnlessDBFeature('supports_temporal_subtraction')
def test_datetime_subtraction_microseconds(self):
delta = datetime.timedelta(microseconds=8999999999999999)
Experiment.objects.update(end=F('start') + delta)