diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index d34ca23bae..64b1f82071 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -296,12 +296,6 @@ END; columns.append(value[0]) return tuple(columns) - def field_cast_sql(self, db_type, internal_type): - if db_type and db_type.endswith("LOB") and internal_type != "JSONField": - return "DBMS_LOB.SUBSTR(%s)" - else: - return "%s" - def no_limit_value(self): return None @@ -344,7 +338,9 @@ END; def lookup_cast(self, lookup_type, internal_type=None): if lookup_type in ("iexact", "icontains", "istartswith", "iendswith"): return "UPPER(%s)" - if internal_type == "JSONField" and lookup_type == "exact": + if ( + lookup_type != "isnull" and internal_type in ("BinaryField", "TextField") + ) or (lookup_type == "exact" and internal_type == "JSONField"): return "DBMS_LOB.SUBSTR(%s)" return "%s" diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index aa68465df9..732b30b0a4 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -82,6 +82,9 @@ class DatabaseFeatures(BaseDatabaseFeatures): "indexes.tests.SchemaIndexesNotPostgreSQLTests." "test_create_index_ignores_opclasses", }, + "PostgreSQL requires casting to text.": { + "lookup.tests.LookupTests.test_textfield_exact_null", + }, } @cached_property diff --git a/docs/releases/4.2.2.txt b/docs/releases/4.2.2.txt index 31f854b004..9d16e7ef6f 100644 --- a/docs/releases/4.2.2.txt +++ b/docs/releases/4.2.2.txt @@ -9,4 +9,6 @@ Django 4.2.2 fixes several bugs in 4.2.1. Bugfixes ======== -* ... +* Fixed a regression in Django 4.2 that caused an unnecessary + ``DBMS_LOB.SUBSTR()`` wrapping in the ``__isnull`` and ``__exact=None`` + lookups for ``TextField()``/``BinaryField()`` on Oracle (:ticket:`34544`). diff --git a/tests/lookup/models.py b/tests/lookup/models.py index 9bb3412c33..75f3e3b6ba 100644 --- a/tests/lookup/models.py +++ b/tests/lookup/models.py @@ -19,6 +19,7 @@ class Alarm(models.Model): class Author(models.Model): name = models.CharField(max_length=100) alias = models.CharField(max_length=50, null=True, blank=True) + bio = models.TextField(null=True) class Meta: ordering = ("name",) @@ -50,22 +51,11 @@ class NulledTextField(models.TextField): return None if value == "" else value -class NullField(models.Field): - pass - - -NullField.register_lookup(IsNull) - - @NulledTextField.register_lookup class NulledTransform(models.Transform): lookup_name = "nulled" template = "NULL" - @property - def output_field(self): - return NullField() - @NulledTextField.register_lookup class IsNullWithNoneAsRHS(IsNull): diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index 2dddd826e3..c639fe073e 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -49,7 +49,7 @@ class LookupTests(TestCase): @classmethod def setUpTestData(cls): # Create a few Authors. - cls.au1 = Author.objects.create(name="Author 1", alias="a1") + cls.au1 = Author.objects.create(name="Author 1", alias="a1", bio="x" * 4001) cls.au2 = Author.objects.create(name="Author 2", alias="a2") # Create a few Articles. cls.a1 = Article.objects.create( @@ -1029,6 +1029,13 @@ class LookupTests(TestCase): Season.objects.create(year=2012, gt=None) self.assertQuerySetEqual(Season.objects.filter(gt__regex=r"^$"), []) + def test_textfield_exact_null(self): + with self.assertNumQueries(1) as ctx: + self.assertSequenceEqual(Author.objects.filter(bio=None), [self.au2]) + # Columns with IS NULL condition are not wrapped (except PostgreSQL). + bio_column = connection.ops.quote_name(Author._meta.get_field("bio").column) + self.assertIn(f"{bio_column} IS NULL", ctx.captured_queries[0]["sql"]) + def test_regex_non_string(self): """ A regex lookup does not fail on non-string fields