From 3def262723f1ca24bd51148b4b510946ea7a01c8 Mon Sep 17 00:00:00 2001 From: William Date: Fri, 25 Nov 2022 15:37:06 +0200 Subject: [PATCH] Fixed #21604 -- Embed raw queries as subqueries Added functionallity so RawQueries can be used as subquries when using __in filter --- django/db/models/query.py | 3 +++ django/db/models/sql/query.py | 22 ++++++++++++++++++++-- docs/releases/4.2.txt | 3 +++ tests/raw_query/tests.py | 22 ++++++++++++++++++++-- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 5673855c1c..9db4fabe97 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -2048,6 +2048,9 @@ class RawQuerySet: model_init_names = [f.attname for f in model_init_fields] return model_init_names, model_init_order, annotation_fields + def resolve_expression(self, *args, **kwargs): + return self.query.resolve_expression(self, *args, **kwargs) + def prefetch_related(self, *lookups): """Same as QuerySet.prefetch_related()""" clone = self._clone() diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 9735ce10c8..dc80bdf5cc 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -82,6 +82,11 @@ JoinInfo = namedtuple( class RawQuery: """A single raw SQL query.""" + has_select_fields = True + contains_aggregate = [] + contains_over_clause = False + subquery = False + def __init__(self, sql, using, params=()): self.params = params self.sql = sql @@ -98,7 +103,9 @@ class RawQuery: return self.clone(using) def clone(self, using): - return RawQuery(self.sql, using, params=self.params) + query = RawQuery(self.sql, using, params=self.params) + query.subquery = self.subquery + return query def get_columns(self): if self.cursor is None: @@ -151,6 +158,17 @@ class RawQuery: self.cursor = connection.cursor() self.cursor.execute(self.sql, params) + def as_sql(self, compiler, connection): + sql = self.sql + if self.subquery: + sql = "(%s)" % sql + return sql, self.params + + def resolve_expression(self, query, *args, **kwargs): + clone = self.clone(self.using) + clone.subquery = True + return clone + ExplainInfo = namedtuple("ExplainInfo", ("format", "options")) @@ -1243,7 +1261,7 @@ class Query(BaseExpression): ) elif hasattr(value, "_meta"): self.check_query_object_type(value, opts, field) - elif hasattr(value, "__iter__"): + elif hasattr(value, "__iter__") and not getattr(value, "subquery", False): for v in value: self.check_query_object_type(v, opts, field) diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index ba01bf12e5..0dcfc647bc 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -355,6 +355,9 @@ Miscellaneous * The undocumented ``negated`` parameter of the :class:`~django.db.models.Exists` expression is removed. +* Add functionality so :class:`~django.db.models.sql.RawQuery` + can be used as a subquery. + .. _deprecated-features-4.2: Features deprecated in 4.2 diff --git a/tests/raw_query/tests.py b/tests/raw_query/tests.py index 1dcc7ce740..e91f907490 100644 --- a/tests/raw_query/tests.py +++ b/tests/raw_query/tests.py @@ -3,6 +3,7 @@ from decimal import Decimal from django.core.exceptions import FieldDoesNotExist from django.db.models.query import RawQuerySet +from django.db.models.sql.query import RawQuery from django.test import TestCase, skipUnlessDBFeature from .models import ( @@ -62,6 +63,12 @@ class RawQueryTests(TestCase): paperback=True, opening_line="It was the day my grandmother exploded.", ) + cls.b5 = Book.objects.create( + title="Some other awesome book", + author=cls.a4, + paperback=True, + opening_line="Story of the book that was left behind", + ) cls.c1 = Coffee.objects.create(brand="dunkin doughnuts") cls.c2 = Coffee.objects.create(brand="starbucks") cls.r1 = Reviewer.objects.create() @@ -308,7 +315,7 @@ class RawQueryTests(TestCase): ("book_count", 3), ("book_count", 0), ("book_count", 1), - ("book_count", 0), + ("book_count", 1), ) authors = Author.objects.order_by("pk") self.assertSuccessfulRawQuery(Author, query, authors, expected_annotations) @@ -413,7 +420,18 @@ class RawQueryTests(TestCase): ) def test_len(self): - self.assertEqual(len(Book.objects.raw("SELECT * FROM raw_query_book")), 4) + self.assertEqual(len(Book.objects.raw("SELECT * FROM raw_query_book")), 5) self.assertEqual( len(Book.objects.raw("SELECT * FROM raw_query_book WHERE id = 0")), 0 ) + + def test_as_subquery(self): + author_id_sub_query = RawQuery( + "SELECT id FROM raw_query_author WHERE last_name LIKE %s", + using="default", + params=["Smith"], + ) + with self.assertNumQueries(1): + self.assertEqual( + len(Book.objects.filter(author_id__in=author_id_sub_query)), 4 + )