From 1a5e7c31e4e13c7836340261ea4c361a0ecd86e0 Mon Sep 17 00:00:00 2001 From: Samriddha9619 Date: Sat, 20 Sep 2025 11:22:17 +0530 Subject: [PATCH 1/7] fixing --- django/db/models/functions/text.py | 7 +++++++ django/db/models/lookups.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/django/db/models/functions/text.py b/django/db/models/functions/text.py index 28660c5e66..bcd8688907 100644 --- a/django/db/models/functions/text.py +++ b/django/db/models/functions/text.py @@ -375,3 +375,10 @@ class Trim(Transform): class Upper(Transform): function = "UPPER" lookup_name = "upper" + + def as_sql(self, compiler, connection, **extra_context): + lhs_sql, params = self.process_lhs(compiler, connection) + sql, sql_params = super().as_sql( + compiler, connection, **{**extra_context, "lhs": lhs_sql} + ) + return sql, (*params, *sql_params) diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 65128732fd..deb197f28f 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -215,6 +215,10 @@ class Transform(RegisterLookupMixin, Func): def lhs(self): return self.get_source_expressions()[0] + def process_lhs(self, compiler, connection, lhs=None): + lhs = lhs or self.lhs + return compiler.compile(lhs) + def get_bilateral_transforms(self): if hasattr(self.lhs, "get_bilateral_transforms"): bilateral_transforms = self.lhs.get_bilateral_transforms() From 3629e19c61021d40782c8e9ab29d6d5ac57d680a Mon Sep 17 00:00:00 2001 From: Samriddha9619 Date: Sat, 20 Sep 2025 11:32:04 +0530 Subject: [PATCH 2/7] Refs #24886 -- Added process_lhs() method to Transform. --- django/db/models/functions/text.py | 7 ------- docs/howto/custom-lookups.txt | 8 ++++++++ docs/ref/models/lookups.txt | 2 ++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/django/db/models/functions/text.py b/django/db/models/functions/text.py index bcd8688907..28660c5e66 100644 --- a/django/db/models/functions/text.py +++ b/django/db/models/functions/text.py @@ -375,10 +375,3 @@ class Trim(Transform): class Upper(Transform): function = "UPPER" lookup_name = "upper" - - def as_sql(self, compiler, connection, **extra_context): - lhs_sql, params = self.process_lhs(compiler, connection) - sql, sql_params = super().as_sql( - compiler, connection, **{**extra_context, "lhs": lhs_sql} - ) - return sql, (*params, *sql_params) diff --git a/docs/howto/custom-lookups.txt b/docs/howto/custom-lookups.txt index e287561834..fd2d09ac73 100644 --- a/docs/howto/custom-lookups.txt +++ b/docs/howto/custom-lookups.txt @@ -122,6 +122,14 @@ SQL function ``ABS()`` to transform the value before comparison:: lookup_name = "abs" function = "ABS" + def as_sql(self, compiler, connection): + lhs, lhs_params = self.process_lhs(compiler, connection) + return "%s(%s)" % (self.function, lhs), lhs_params + +This override uses ``process_lhs()`` to safely compile the left-hand side of +the expression. This is recommended when customizing SQL generation in +``Transform.as_sql()`` methods. + Next, let's register it for ``IntegerField``:: from django.db.models import IntegerField diff --git a/docs/ref/models/lookups.txt b/docs/ref/models/lookups.txt index d2d91deb4e..355d90725b 100644 --- a/docs/ref/models/lookups.txt +++ b/docs/ref/models/lookups.txt @@ -237,6 +237,8 @@ the following methods: can be used for compiling vendor specific SQL. If ``lhs`` is not ``None``, use it as the processed ``lhs`` instead of ``self.lhs``. + .. versionadded:: 6.1 + .. method:: process_rhs(compiler, connection) Behaves the same way as :meth:`process_lhs`, for the right-hand side. From b3c6aeb22751a819c06116d0a48486153ceba2a7 Mon Sep 17 00:00:00 2001 From: Samriddh Tripathi Date: Tue, 30 Sep 2025 15:57:45 +0530 Subject: [PATCH 3/7] Update docs/ref/models/lookups.txt Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> --- docs/ref/models/lookups.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/ref/models/lookups.txt b/docs/ref/models/lookups.txt index 355d90725b..d2d91deb4e 100644 --- a/docs/ref/models/lookups.txt +++ b/docs/ref/models/lookups.txt @@ -237,8 +237,6 @@ the following methods: can be used for compiling vendor specific SQL. If ``lhs`` is not ``None``, use it as the processed ``lhs`` instead of ``self.lhs``. - .. versionadded:: 6.1 - .. method:: process_rhs(compiler, connection) Behaves the same way as :meth:`process_lhs`, for the right-hand side. From c9871f86ff4be9fdd230296f7431c094cad5c753 Mon Sep 17 00:00:00 2001 From: Samriddh Tripathi Date: Tue, 30 Sep 2025 15:58:03 +0530 Subject: [PATCH 4/7] Update docs/howto/custom-lookups.txt Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> --- django/contrib/gis/db/models/functions.py | 2 +- django/contrib/postgres/fields/array.py | 6 +++--- django/contrib/postgres/fields/hstore.py | 2 +- docs/howto/custom-lookups.txt | 8 -------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 9e94d0f77a..1baa14cffc 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -429,7 +429,7 @@ class GeometryType(GeoFuncMixin, Transform): lookup_name = "geom_type" def as_oracle(self, compiler, connection, **extra_context): - lhs, params = compiler.compile(self.lhs) + lhs, params = self.process_lhs(compiler, connection) sql = ( "(SELECT DECODE(" f"SDO_GEOMETRY.GET_GTYPE({lhs})," diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index ac663830f8..ebd28321ec 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -296,7 +296,7 @@ class ArrayLenTransform(Transform): output_field = IntegerField() def as_sql(self, compiler, connection): - lhs, params = compiler.compile(self.lhs) + lhs, params = self.process_lhs(compiler, connection) # Distinguish NULL and empty arrays return ( "CASE WHEN %(lhs)s IS NULL THEN NULL ELSE " @@ -328,7 +328,7 @@ class IndexTransform(Transform): self.base_field = base_field def as_sql(self, compiler, connection): - lhs, params = compiler.compile(self.lhs) + lhs, params = self.process_lhs(compiler, connection) if not lhs.endswith("]"): lhs = "(%s)" % lhs return "%s[%%s]" % lhs, (*params, self.index) @@ -354,7 +354,7 @@ class SliceTransform(Transform): self.end = end def as_sql(self, compiler, connection): - lhs, params = compiler.compile(self.lhs) + lhs, params = self.process_lhs(compiler, connection) # self.start is set to 1 if slice start is not provided. if self.end is None: return f"({lhs})[%s:]", (*params, self.start) diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index e28b9f7dbe..dd3496674b 100644 --- a/django/contrib/postgres/fields/hstore.py +++ b/django/contrib/postgres/fields/hstore.py @@ -88,7 +88,7 @@ class KeyTransform(Transform): self.key_name = key_name def as_sql(self, compiler, connection): - lhs, params = compiler.compile(self.lhs) + lhs, params = self.process_lhs(compiler, connection) return "(%s -> %%s)" % lhs, (*params, self.key_name) diff --git a/docs/howto/custom-lookups.txt b/docs/howto/custom-lookups.txt index fd2d09ac73..e287561834 100644 --- a/docs/howto/custom-lookups.txt +++ b/docs/howto/custom-lookups.txt @@ -122,14 +122,6 @@ SQL function ``ABS()`` to transform the value before comparison:: lookup_name = "abs" function = "ABS" - def as_sql(self, compiler, connection): - lhs, lhs_params = self.process_lhs(compiler, connection) - return "%s(%s)" % (self.function, lhs), lhs_params - -This override uses ``process_lhs()`` to safely compile the left-hand side of -the expression. This is recommended when customizing SQL generation in -``Transform.as_sql()`` methods. - Next, let's register it for ``IntegerField``:: from django.db.models import IntegerField From d822a481fb59132721612047f2b9400b3bd19be8 Mon Sep 17 00:00:00 2001 From: Samriddha9619 Date: Fri, 14 Nov 2025 18:49:56 +0530 Subject: [PATCH 5/7] Refs #24886 -- Used Transform.process_lhs() in RasterBandTransform. --- django/contrib/gis/db/models/lookups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/gis/db/models/lookups.py b/django/contrib/gis/db/models/lookups.py index b9e5e47b27..57bca0a029 100644 --- a/django/contrib/gis/db/models/lookups.py +++ b/django/contrib/gis/db/models/lookups.py @@ -8,7 +8,7 @@ from django.utils.regex_helper import _lazy_re_compile class RasterBandTransform(Transform): def as_sql(self, compiler, connection): - return compiler.compile(self.lhs) + return self.process_lhs(compiler, connection) class GISLookup(Lookup): From 27667a2aca9682731a05cd9965e7e87f27bef550 Mon Sep 17 00:00:00 2001 From: Samriddha9619 Date: Fri, 14 Nov 2025 19:29:13 +0530 Subject: [PATCH 6/7] Refs #24886 -- Used Transform.process_lhs() in Extract and Trunc classes. --- django/db/models/functions/datetime.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django/db/models/functions/datetime.py b/django/db/models/functions/datetime.py index b536690c8a..276230ee5c 100644 --- a/django/db/models/functions/datetime.py +++ b/django/db/models/functions/datetime.py @@ -51,7 +51,7 @@ class Extract(TimezoneMixin, Transform): super().__init__(expression, **extra) def as_sql(self, compiler, connection): - sql, params = compiler.compile(self.lhs) + sql, params = self.process_lhs(compiler, connection) lhs_output_field = self.lhs.output_field if isinstance(lhs_output_field, DateTimeField): tzname = self.get_tzname() @@ -258,7 +258,7 @@ class TruncBase(TimezoneMixin, Transform): super().__init__(expression, output_field=output_field, **extra) def as_sql(self, compiler, connection): - sql, params = compiler.compile(self.lhs) + sql, params = self.process_lhs(compiler, connection) tzname = None if isinstance(self.lhs.output_field, DateTimeField): tzname = self.get_tzname() @@ -405,7 +405,7 @@ class TruncDate(TruncBase): def as_sql(self, compiler, connection): # Cast to date rather than truncate to date. - sql, params = compiler.compile(self.lhs) + sql, params = self.process_lhs(compiler, connection) tzname = self.get_tzname() return connection.ops.datetime_cast_date_sql(sql, tuple(params), tzname) @@ -417,7 +417,7 @@ class TruncTime(TruncBase): def as_sql(self, compiler, connection): # Cast to time rather than truncate to time. - sql, params = compiler.compile(self.lhs) + sql, params = self.process_lhs(compiler, connection) tzname = self.get_tzname() return connection.ops.datetime_cast_time_sql(sql, tuple(params), tzname) From 8cce20629241f91e128bb37d41ba9bfc2675f897 Mon Sep 17 00:00:00 2001 From: Samriddha9619 Date: Fri, 14 Nov 2025 19:29:24 +0530 Subject: [PATCH 7/7] Refs #24886 -- Used Transform.process_lhs() in test Transform subclasses. --- tests/custom_lookups/tests.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/custom_lookups/tests.py b/tests/custom_lookups/tests.py index db985240d7..0544c9d877 100644 --- a/tests/custom_lookups/tests.py +++ b/tests/custom_lookups/tests.py @@ -34,11 +34,11 @@ class Div3Transform(models.Transform): lookup_name = "div3" def as_sql(self, compiler, connection): - lhs, lhs_params = compiler.compile(self.lhs) + lhs, lhs_params = self.process_lhs(compiler, connection) return "(%s) %%%% 3" % lhs, lhs_params def as_oracle(self, compiler, connection, **extra_context): - lhs, lhs_params = compiler.compile(self.lhs) + lhs, lhs_params = self.process_lhs(compiler, connection) return "mod(%s, 3)" % lhs, lhs_params @@ -51,7 +51,7 @@ class Mult3BilateralTransform(models.Transform): lookup_name = "mult3" def as_sql(self, compiler, connection): - lhs, lhs_params = compiler.compile(self.lhs) + lhs, lhs_params = self.process_lhs(compiler, connection) return "3 * (%s)" % lhs, lhs_params @@ -59,7 +59,7 @@ class LastDigitTransform(models.Transform): lookup_name = "lastdigit" def as_sql(self, compiler, connection): - lhs, lhs_params = compiler.compile(self.lhs) + lhs, lhs_params = self.process_lhs(compiler, connection) return "SUBSTR(CAST(%s AS CHAR(2)), 2, 1)" % lhs, lhs_params @@ -68,7 +68,7 @@ class UpperBilateralTransform(models.Transform): lookup_name = "upper" def as_sql(self, compiler, connection): - lhs, lhs_params = compiler.compile(self.lhs) + lhs, lhs_params = self.process_lhs(compiler, connection) return "UPPER(%s)" % lhs, lhs_params @@ -77,7 +77,7 @@ class YearTransform(models.Transform): lookup_name = "testyear" def as_sql(self, compiler, connection): - lhs_sql, params = compiler.compile(self.lhs) + lhs_sql, params = self.process_lhs(compiler, connection) return connection.ops.date_extract_sql("year", lhs_sql, params) @property @@ -219,7 +219,7 @@ class DateTimeTransform(models.Transform): return models.DateTimeField() def as_sql(self, compiler, connection): - lhs, params = compiler.compile(self.lhs) + lhs, params = self.process_lhs(compiler, connection) return "from_unixtime({})".format(lhs), params @@ -618,7 +618,7 @@ class TrackCallsYearTransform(YearTransform): call_order = [] def as_sql(self, compiler, connection): - lhs_sql, params = compiler.compile(self.lhs) + lhs_sql, params = self.process_lhs(compiler, connection) return connection.ops.date_extract_sql("year", lhs_sql), params @property