From 27b399d23531541d091886f683991e321c8fa314 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 18 Aug 2023 14:16:57 +0100 Subject: [PATCH] Fixed #34547 -- Deprecated DatabaseOperations.field_cast_sql(). --- django/db/backends/base/operations.py | 9 +++++++++ django/db/models/lookups.py | 21 +++++++++++++++++++-- docs/internals/deprecation.txt | 2 ++ docs/releases/5.0.txt | 5 +++++ tests/backends/base/test_operations.py | 24 ++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py index 6f10e31cd5b..0ee2ffc5c49 100644 --- a/django/db/backends/base/operations.py +++ b/django/db/backends/base/operations.py @@ -1,6 +1,7 @@ import datetime import decimal import json +import warnings from importlib import import_module import sqlparse @@ -10,6 +11,7 @@ from django.db import NotSupportedError, transaction from django.db.backends import utils from django.db.models.expressions import Col from django.utils import timezone +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.encoding import force_str @@ -220,6 +222,13 @@ class BaseDatabaseOperations: it in a WHERE statement. The resulting string should contain a '%s' placeholder for the column being searched against. """ + warnings.warn( + ( + "DatabaseOperations.field_cast_sql() is deprecated use " + "DatabaseOperations.lookup_cast() instead." + ), + RemovedInDjango60Warning, + ) return "%s" def force_no_ordering(self): diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 8528fcda5ce..896d3eee440 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -1,7 +1,9 @@ import itertools import math +import warnings from django.core.exceptions import EmptyResultSet, FullResultSet +from django.db.backends.base.operations import BaseDatabaseOperations from django.db.models.expressions import Case, Expression, Func, Value, When from django.db.models.fields import ( BooleanField, @@ -13,6 +15,7 @@ from django.db.models.fields import ( ) from django.db.models.query_utils import RegisterLookupMixin from django.utils.datastructures import OrderedSet +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.functional import cached_property from django.utils.hashable import make_hashable @@ -217,8 +220,22 @@ class BuiltinLookup(Lookup): def process_lhs(self, compiler, connection, lhs=None): lhs_sql, params = super().process_lhs(compiler, connection, lhs) field_internal_type = self.lhs.output_field.get_internal_type() - db_type = self.lhs.output_field.db_type(connection=connection) - lhs_sql = connection.ops.field_cast_sql(db_type, field_internal_type) % lhs_sql + if ( + hasattr(connection.ops.__class__, "field_cast_sql") + and connection.ops.__class__.field_cast_sql + is not BaseDatabaseOperations.field_cast_sql + ): + warnings.warn( + ( + "The usage of DatabaseOperations.field_cast_sql() is deprecated. " + "Implement DatabaseOperations.lookup_cast() instead." + ), + RemovedInDjango60Warning, + ) + db_type = self.lhs.output_field.db_type(connection=connection) + lhs_sql = ( + connection.ops.field_cast_sql(db_type, field_internal_type) % lhs_sql + ) lhs_sql = ( connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql ) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index c9a8b056378..98b027cd1a2 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -40,6 +40,8 @@ details on these changes. * Support for ``cx_Oracle`` will be removed. +* ``BaseDatabaseOperations.field_cast_sql()`` will be removed. + .. _deprecation-removed-in-5.1: 5.1 diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index cae3668bfca..28814f141fa 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -663,6 +663,11 @@ Miscellaneous * Support for ``cx_Oracle`` is deprecated in favor of `oracledb`_ 1.3.2+ Python driver. +* ``DatabaseOperations.field_cast_sql()`` is deprecated in favor of + ``DatabaseOperations.lookup_cast()``. Starting with Django 6.0, + ``BuiltinLookup.process_lhs()`` will no longer call ``field_cast_sql()``. + Third-party database backends should implement ``lookup_cast()`` instead. + .. _`oracledb`: https://oracle.github.io/python-oracledb/ Features removed in 5.0 diff --git a/tests/backends/base/test_operations.py b/tests/backends/base/test_operations.py index 9d2828c8ce4..d2a3fb67657 100644 --- a/tests/backends/base/test_operations.py +++ b/tests/backends/base/test_operations.py @@ -1,10 +1,12 @@ import decimal +from unittest import mock from django.core.management.color import no_style from django.db import NotSupportedError, connection, transaction from django.db.backends.base.operations import BaseDatabaseOperations from django.db.models import DurationField, Value from django.db.models.expressions import Col +from django.db.models.lookups import Exact from django.test import ( SimpleTestCase, TestCase, @@ -13,6 +15,7 @@ from django.test import ( skipIfDBFeature, ) from django.utils import timezone +from django.utils.deprecation import RemovedInDjango60Warning from ..models import Author, Book @@ -235,3 +238,24 @@ class SqlFlushTests(TransactionTestCase): self.assertEqual(author.pk, 1) book = Book.objects.create(author=author) self.assertEqual(book.pk, 1) + + +class DeprecationTests(TestCase): + def test_field_cast_sql_warning(self): + base_ops = BaseDatabaseOperations(connection=connection) + msg = ( + "DatabaseOperations.field_cast_sql() is deprecated use " + "DatabaseOperations.lookup_cast() instead." + ) + with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + base_ops.field_cast_sql("integer", "IntegerField") + + def test_field_cast_sql_usage_warning(self): + compiler = Author.objects.all().query.get_compiler(connection.alias) + msg = ( + "The usage of DatabaseOperations.field_cast_sql() is deprecated. Implement " + "DatabaseOperations.lookup_cast() instead." + ) + with mock.patch.object(connection.ops.__class__, "field_cast_sql"): + with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + Exact("name", "book__author__name").as_sql(compiler, connection)