diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 70cd525f30..d4edf2ce0f 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -508,21 +508,16 @@ class YearExact(YearLookup, Exact): lookup_name = 'exact' def as_sql(self, compiler, connection): - # We will need to skip the extract part and instead go - # directly with the originating field, that is self.lhs.lhs. - lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs) - rhs_sql, rhs_params = self.process_rhs(compiler, connection) - try: - # Check that rhs_params[0] exists (IndexError), - # it isn't None (TypeError), and is a number (ValueError) - int(rhs_params[0]) - except (IndexError, TypeError, ValueError): - # Can't determine the bounds before executing the query, so skip - # optimizations by falling back to a standard exact comparison. - return super().as_sql(compiler, connection) - bounds = self.year_lookup_bounds(connection, rhs_params[0]) - params.extend(bounds) - return '%s BETWEEN %%s AND %%s' % lhs_sql, params + # Avoid the extract operation if the rhs is a direct value to allow + # indexes to be used. + if self.rhs_is_direct_value(): + # Skip the extract part by directly using the originating field, + # that is self.lhs.lhs. + lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs) + bounds = self.year_lookup_bounds(connection, self.rhs) + params.extend(bounds) + return '%s BETWEEN %%s AND %%s' % lhs_sql, params + return super().as_sql(compiler, connection) class YearGt(YearComparisonLookup): diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py index 2088d09d06..f62bd0f0b2 100644 --- a/tests/db_functions/datetime/test_extract_trunc.py +++ b/tests/db_functions/datetime/test_extract_trunc.py @@ -4,7 +4,8 @@ import pytz from django.conf import settings from django.db.models import ( - DateField, DateTimeField, IntegerField, Max, OuterRef, Subquery, TimeField, + DateField, DateTimeField, F, IntegerField, Max, OuterRef, Subquery, + TimeField, ) from django.db.models.functions import ( Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute, @@ -108,6 +109,14 @@ class DateFunctionTests(TestCase): query_string = str(qs.query).lower() self.assertEqual(query_string.count(' between '), 1) self.assertEqual(query_string.count('extract'), 0) + # an expression rhs cannot use the between optimization. + qs = DTModel.objects.annotate( + start_year=ExtractYear('start_datetime'), + ).filter(end_datetime__year=F('start_year') + 1) + self.assertEqual(qs.count(), 1) + query_string = str(qs.query).lower() + self.assertEqual(query_string.count(' between '), 0) + self.assertEqual(query_string.count('extract'), 3) def test_extract_year_greaterthan_lookup(self): start_datetime = datetime(2015, 6, 15, 14, 10)