mirror of https://github.com/django/django.git
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 3543129822
.
Thanks Reupen Shah for the report.
This commit is contained in:
parent
372eaa395f
commit
9bcbcd599a
|
@ -626,7 +626,7 @@ class BaseDatabaseOperations:
|
||||||
if self.connection.features.supports_temporal_subtraction:
|
if self.connection.features.supports_temporal_subtraction:
|
||||||
lhs_sql, lhs_params = lhs
|
lhs_sql, lhs_params = lhs
|
||||||
rhs_sql, rhs_params = rhs
|
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)
|
raise NotSupportedError("This backend does not support %s subtraction." % internal_type)
|
||||||
|
|
||||||
def window_frame_start(self, start):
|
def window_frame_start(self, start):
|
||||||
|
|
|
@ -286,13 +286,13 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
# a decimal. MySQL returns an integer without microseconds.
|
# a decimal. MySQL returns an integer without microseconds.
|
||||||
return 'CAST((TIME_TO_SEC(%(lhs)s) - TIME_TO_SEC(%(rhs)s)) * 1000000 AS SIGNED)' % {
|
return 'CAST((TIME_TO_SEC(%(lhs)s) - TIME_TO_SEC(%(rhs)s)) * 1000000 AS SIGNED)' % {
|
||||||
'lhs': lhs_sql, 'rhs': rhs_sql
|
'lhs': lhs_sql, 'rhs': rhs_sql
|
||||||
}, lhs_params + rhs_params
|
}, (*lhs_params, *rhs_params)
|
||||||
return (
|
return (
|
||||||
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
|
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
|
||||||
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
|
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
|
||||||
) % {'lhs': lhs_sql, 'rhs': rhs_sql}, lhs_params * 2 + rhs_params * 2
|
) % {'lhs': lhs_sql, 'rhs': rhs_sql}, tuple(lhs_params) * 2 + tuple(rhs_params) * 2
|
||||||
else:
|
params = (*lhs_params, *rhs_params)
|
||||||
return "TIMESTAMPDIFF(MICROSECOND, %s, %s)" % (rhs_sql, lhs_sql), rhs_params + lhs_params
|
return "TIMESTAMPDIFF(MICROSECOND, %s, %s)" % (rhs_sql, lhs_sql), params
|
||||||
|
|
||||||
def explain_query_prefix(self, format=None, **options):
|
def explain_query_prefix(self, format=None, **options):
|
||||||
# Alias MySQL's TRADITIONAL to TEXT for consistency with other backends.
|
# Alias MySQL's TRADITIONAL to TEXT for consistency with other backends.
|
||||||
|
|
|
@ -619,7 +619,8 @@ END;
|
||||||
if internal_type == 'DateField':
|
if internal_type == 'DateField':
|
||||||
lhs_sql, lhs_params = lhs
|
lhs_sql, lhs_params = lhs
|
||||||
rhs_sql, rhs_params = rhs
|
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)
|
return super().subtract_temporals(internal_type, lhs, rhs)
|
||||||
|
|
||||||
def bulk_batch_size(self, fields, objs):
|
def bulk_batch_size(self, fields, objs):
|
||||||
|
|
|
@ -271,7 +271,8 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
if internal_type == 'DateField':
|
if internal_type == 'DateField':
|
||||||
lhs_sql, lhs_params = lhs
|
lhs_sql, lhs_params = lhs
|
||||||
rhs_sql, rhs_params = rhs
|
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)
|
return super().subtract_temporals(internal_type, lhs, rhs)
|
||||||
|
|
||||||
def window_frame_range_start_end(self, start=None, end=None):
|
def window_frame_range_start_end(self, start=None, end=None):
|
||||||
|
|
|
@ -326,9 +326,10 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
def subtract_temporals(self, internal_type, lhs, rhs):
|
def subtract_temporals(self, internal_type, lhs, rhs):
|
||||||
lhs_sql, lhs_params = lhs
|
lhs_sql, lhs_params = lhs
|
||||||
rhs_sql, rhs_params = rhs
|
rhs_sql, rhs_params = rhs
|
||||||
|
params = (*lhs_params, *rhs_params)
|
||||||
if internal_type == 'TimeField':
|
if internal_type == 'TimeField':
|
||||||
return "django_time_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), lhs_params + rhs_params
|
return 'django_timestamp_diff(%s, %s)' % (lhs_sql, rhs_sql), params
|
||||||
|
|
||||||
def insert_statement(self, ignore_conflicts=False):
|
def insert_statement(self, ignore_conflicts=False):
|
||||||
return 'INSERT OR IGNORE INTO' if ignore_conflicts else super().insert_statement(ignore_conflicts)
|
return 'INSERT OR IGNORE INTO' if ignore_conflicts else super().insert_statement(ignore_conflicts)
|
||||||
|
|
|
@ -9,4 +9,6 @@ Django 3.0.3 fixes several bugs in 3.0.2.
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
* ...
|
* Fixed a regression in Django 3.0 that caused a crash when subtracting
|
||||||
|
``DateField``, ``DateTimeField``, or ``TimeField`` from a ``Subquery()``
|
||||||
|
annotation (:ticket:`31133`).
|
||||||
|
|
|
@ -1426,6 +1426,16 @@ class FTimeDeltaTests(TestCase):
|
||||||
))
|
))
|
||||||
self.assertIsNone(queryset.first().shifted)
|
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')
|
@skipUnlessDBFeature('supports_temporal_subtraction')
|
||||||
def test_time_subtraction(self):
|
def test_time_subtraction(self):
|
||||||
Time.objects.create(time=datetime.time(12, 30, 15, 2345))
|
Time.objects.create(time=datetime.time(12, 30, 15, 2345))
|
||||||
|
@ -1452,6 +1462,17 @@ class FTimeDeltaTests(TestCase):
|
||||||
))
|
))
|
||||||
self.assertIsNone(queryset.first().shifted)
|
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')
|
@skipUnlessDBFeature('supports_temporal_subtraction')
|
||||||
def test_datetime_subtraction(self):
|
def test_datetime_subtraction(self):
|
||||||
under_estimate = [
|
under_estimate = [
|
||||||
|
@ -1476,6 +1497,16 @@ class FTimeDeltaTests(TestCase):
|
||||||
))
|
))
|
||||||
self.assertIsNone(queryset.first().shifted)
|
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')
|
@skipUnlessDBFeature('supports_temporal_subtraction')
|
||||||
def test_datetime_subtraction_microseconds(self):
|
def test_datetime_subtraction_microseconds(self):
|
||||||
delta = datetime.timedelta(microseconds=8999999999999999)
|
delta = datetime.timedelta(microseconds=8999999999999999)
|
||||||
|
|
Loading…
Reference in New Issue