mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #34840 -- Avoided casting string base fields on PostgreSQL.
Thanks Alex Vandiver for the report.
Regression in 09ffc5c121
.
This commit is contained in:
parent
78b5c90753
commit
779cd28acb
@ -154,14 +154,6 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
|
||||
def lookup_cast(self, lookup_type, internal_type=None):
|
||||
lookup = "%s"
|
||||
|
||||
if lookup_type == "isnull" and internal_type in (
|
||||
"CharField",
|
||||
"EmailField",
|
||||
"TextField",
|
||||
):
|
||||
return "%s::text"
|
||||
|
||||
# Cast text lookups to text to allow things like filter(x__contains=4)
|
||||
if lookup_type in (
|
||||
"iexact",
|
||||
|
@ -624,11 +624,15 @@ class IsNull(BuiltinLookup):
|
||||
raise ValueError(
|
||||
"The QuerySet value for an isnull lookup must be True or False."
|
||||
)
|
||||
if isinstance(self.lhs, Value) and self.lhs.value is None:
|
||||
if self.rhs:
|
||||
raise FullResultSet
|
||||
if isinstance(self.lhs, Value):
|
||||
if self.lhs.value is None or (
|
||||
self.lhs.value == ""
|
||||
and connection.features.interprets_empty_strings_as_nulls
|
||||
):
|
||||
result_exception = FullResultSet if self.rhs else EmptyResultSet
|
||||
else:
|
||||
raise EmptyResultSet
|
||||
result_exception = EmptyResultSet if self.rhs else FullResultSet
|
||||
raise result_exception
|
||||
sql, params = self.process_lhs(compiler, connection)
|
||||
if self.rhs:
|
||||
return "%s IS NULL" % sql, params
|
||||
|
@ -12,3 +12,13 @@ Bugfixes
|
||||
* Fixed a regression in Django 4.2.5 where overriding the deprecated
|
||||
``DEFAULT_FILE_STORAGE`` and ``STATICFILES_STORAGE`` settings in tests caused
|
||||
the main ``STORAGES`` to mutate (:ticket:`34821`).
|
||||
|
||||
* Fixed a regression in Django 4.2 that caused unnecessary casting of string
|
||||
based fields (``CharField``, ``EmailField``, ``TextField``, ``CICharField``,
|
||||
``CIEmailField``, and ``CITextField``) used with the ``__isnull`` lookup on
|
||||
PostgreSQL. As a consequence, the pre-Django 4.2 indexes didn't match and
|
||||
were not used by the query planner (:ticket:`34840`).
|
||||
|
||||
You may need to recreate indexes propagated to the database with Django
|
||||
4.2 - 4.2.5 as they contain unnecessary ``::text`` casting that is avoided as
|
||||
of this release.
|
||||
|
@ -369,6 +369,20 @@ class Tests(TestCase):
|
||||
with self.subTest(lookup=lookup):
|
||||
self.assertIn("::text", do.lookup_cast(lookup))
|
||||
|
||||
def test_lookup_cast_isnull_noop(self):
|
||||
from django.db.backends.postgresql.operations import DatabaseOperations
|
||||
|
||||
do = DatabaseOperations(connection=None)
|
||||
# Using __isnull lookup doesn't require casting.
|
||||
tests = [
|
||||
"CharField",
|
||||
"EmailField",
|
||||
"TextField",
|
||||
]
|
||||
for field_type in tests:
|
||||
with self.subTest(field_type=field_type):
|
||||
self.assertEqual(do.lookup_cast("isnull", field_type), "%s")
|
||||
|
||||
def test_correct_extraction_psycopg_version(self):
|
||||
from django.db.backends.postgresql.base import Database, psycopg_version
|
||||
|
||||
|
@ -995,6 +995,42 @@ class UniqueConstraintTests(TestCase):
|
||||
exclude={"name"},
|
||||
)
|
||||
|
||||
def test_validate_nullable_textfield_with_isnull_true(self):
|
||||
is_null_constraint = models.UniqueConstraint(
|
||||
"price",
|
||||
"discounted_price",
|
||||
condition=models.Q(unit__isnull=True),
|
||||
name="uniq_prices_no_unit",
|
||||
)
|
||||
is_not_null_constraint = models.UniqueConstraint(
|
||||
"price",
|
||||
"discounted_price",
|
||||
condition=models.Q(unit__isnull=False),
|
||||
name="uniq_prices_unit",
|
||||
)
|
||||
|
||||
Product.objects.create(price=2, discounted_price=1)
|
||||
Product.objects.create(price=4, discounted_price=3, unit="ng/mL")
|
||||
|
||||
msg = "Constraint “uniq_prices_no_unit” is violated."
|
||||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
is_null_constraint.validate(
|
||||
Product, Product(price=2, discounted_price=1, unit=None)
|
||||
)
|
||||
is_null_constraint.validate(
|
||||
Product, Product(price=2, discounted_price=1, unit="ng/mL")
|
||||
)
|
||||
is_null_constraint.validate(Product, Product(price=4, discounted_price=3))
|
||||
|
||||
msg = "Constraint “uniq_prices_unit” is violated."
|
||||
with self.assertRaisesMessage(ValidationError, msg):
|
||||
is_not_null_constraint.validate(
|
||||
Product,
|
||||
Product(price=4, discounted_price=3, unit="μg/mL"),
|
||||
)
|
||||
is_not_null_constraint.validate(Product, Product(price=4, discounted_price=3))
|
||||
is_not_null_constraint.validate(Product, Product(price=2, discounted_price=1))
|
||||
|
||||
def test_name(self):
|
||||
constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
|
||||
expected_name = "name_color_uniq"
|
||||
|
@ -1337,6 +1337,16 @@ class LookupTests(TestCase):
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
qs.exists()
|
||||
|
||||
def test_isnull_textfield(self):
|
||||
self.assertSequenceEqual(
|
||||
Author.objects.filter(bio__isnull=True),
|
||||
[self.au2],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
Author.objects.filter(bio__isnull=False),
|
||||
[self.au1],
|
||||
)
|
||||
|
||||
def test_lookup_rhs(self):
|
||||
product = Product.objects.create(name="GME", qty_target=5000)
|
||||
stock_1 = Stock.objects.create(product=product, short=True, qty_available=180)
|
||||
|
Loading…
Reference in New Issue
Block a user