[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
This commit is contained in:
Simon Charette 2020-01-02 11:33:01 -05:00 committed by Mariusz Felisiak
parent d975415852
commit 02cda09b13
7 changed files with 46 additions and 10 deletions

View File

@ -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):

View File

@ -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.

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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`).

View File

@ -1424,6 +1424,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))
@ -1450,6 +1460,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 = [
@ -1474,6 +1495,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)