1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

Fixed #21604 -- Embed raw queries as subqueries

Added functionallity so RawQueries can be used as subquries when using __in filter
This commit is contained in:
William 2022-11-25 15:37:06 +02:00
parent e580b891cb
commit 3def262723
4 changed files with 46 additions and 4 deletions

View File

@ -2048,6 +2048,9 @@ class RawQuerySet:
model_init_names = [f.attname for f in model_init_fields] model_init_names = [f.attname for f in model_init_fields]
return model_init_names, model_init_order, annotation_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): def prefetch_related(self, *lookups):
"""Same as QuerySet.prefetch_related()""" """Same as QuerySet.prefetch_related()"""
clone = self._clone() clone = self._clone()

View File

@ -82,6 +82,11 @@ JoinInfo = namedtuple(
class RawQuery: class RawQuery:
"""A single raw SQL query.""" """A single raw SQL query."""
has_select_fields = True
contains_aggregate = []
contains_over_clause = False
subquery = False
def __init__(self, sql, using, params=()): def __init__(self, sql, using, params=()):
self.params = params self.params = params
self.sql = sql self.sql = sql
@ -98,7 +103,9 @@ class RawQuery:
return self.clone(using) return self.clone(using)
def clone(self, 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): def get_columns(self):
if self.cursor is None: if self.cursor is None:
@ -151,6 +158,17 @@ class RawQuery:
self.cursor = connection.cursor() self.cursor = connection.cursor()
self.cursor.execute(self.sql, params) 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")) ExplainInfo = namedtuple("ExplainInfo", ("format", "options"))
@ -1243,7 +1261,7 @@ class Query(BaseExpression):
) )
elif hasattr(value, "_meta"): elif hasattr(value, "_meta"):
self.check_query_object_type(value, opts, field) 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: for v in value:
self.check_query_object_type(v, opts, field) self.check_query_object_type(v, opts, field)

View File

@ -355,6 +355,9 @@ Miscellaneous
* The undocumented ``negated`` parameter of the * The undocumented ``negated`` parameter of the
:class:`~django.db.models.Exists` expression is removed. :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: .. _deprecated-features-4.2:
Features deprecated in 4.2 Features deprecated in 4.2

View File

@ -3,6 +3,7 @@ from decimal import Decimal
from django.core.exceptions import FieldDoesNotExist from django.core.exceptions import FieldDoesNotExist
from django.db.models.query import RawQuerySet from django.db.models.query import RawQuerySet
from django.db.models.sql.query import RawQuery
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from .models import ( from .models import (
@ -62,6 +63,12 @@ class RawQueryTests(TestCase):
paperback=True, paperback=True,
opening_line="It was the day my grandmother exploded.", 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.c1 = Coffee.objects.create(brand="dunkin doughnuts")
cls.c2 = Coffee.objects.create(brand="starbucks") cls.c2 = Coffee.objects.create(brand="starbucks")
cls.r1 = Reviewer.objects.create() cls.r1 = Reviewer.objects.create()
@ -308,7 +315,7 @@ class RawQueryTests(TestCase):
("book_count", 3), ("book_count", 3),
("book_count", 0), ("book_count", 0),
("book_count", 1), ("book_count", 1),
("book_count", 0), ("book_count", 1),
) )
authors = Author.objects.order_by("pk") authors = Author.objects.order_by("pk")
self.assertSuccessfulRawQuery(Author, query, authors, expected_annotations) self.assertSuccessfulRawQuery(Author, query, authors, expected_annotations)
@ -413,7 +420,18 @@ class RawQueryTests(TestCase):
) )
def test_len(self): 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( self.assertEqual(
len(Book.objects.raw("SELECT * FROM raw_query_book WHERE id = 0")), 0 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
)