diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py index d9d59b4109..43220a18bc 100644 --- a/django/db/backends/base/operations.py +++ b/django/db/backends/base/operations.py @@ -626,7 +626,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 5069939689..5342957767 100644 --- a/django/db/backends/mysql/operations.py +++ b/django/db/backends/mysql/operations.py @@ -286,13 +286,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 493854ce04..358c91505f 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -619,7 +619,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 56e1331f75..ee7787c560 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -271,7 +271,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 ce8fe7209f..951aca0938 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 1e6d4c967c..180153f555 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -1426,6 +1426,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)) @@ -1452,6 +1462,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 = [ @@ -1476,6 +1497,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)