#36181: Composite primary key fields cannot use __in lookup with explicit
Subquery
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Owner: (none)
Type: Bug | Status: new
Component: Database layer | Version: 5.2
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):
* severity: Normal => Release blocker
* stage: Unreviewed => Accepted
Comment:
Well good news, I think the work on #36149 managed to make
`SubqueryConstraint` completely irrelevant while solving this issue.
{{{#!diff
diff --git a/django/db/backends/mysql/compiler.py
b/django/db/backends/mysql/compiler.py
index 2ec6bea2f1..0291b76c70 100644
--- a/django/db/backends/mysql/compiler.py
+++ b/django/db/backends/mysql/compiler.py
@@ -1,28 +1,20 @@
from django.core.exceptions import FieldError, FullResultSet
from django.db.models.expressions import Col
-from django.db.models.sql import compiler
+from django.db.models.sql.compiler import SQLAggregateCompiler,
SQLCompiler
+from django.db.models.sql.compiler import SQLDeleteCompiler as
BaseSQLDeleteCompiler
+from django.db.models.sql.compiler import SQLInsertCompiler
+from django.db.models.sql.compiler import SQLUpdateCompiler as
BaseSQLUpdateCompiler
+__all__ = [
+ "SQLAggregateCompiler",
+ "SQLCompiler",
+ "SQLDeleteCompiler",
+ "SQLInsertCompiler",
+ "SQLUpdateCompiler",
+]
-class SQLCompiler(compiler.SQLCompiler):
- def as_subquery_condition(self, alias, columns, compiler):
- qn = compiler.quote_name_unless_alias
- qn2 = self.connection.ops.quote_name
- sql, params = self.as_sql()
- return (
- "(%s) IN (%s)"
- % (
- ", ".join("%s.%s" % (qn(alias), qn2(column)) for column
in columns),
- sql,
- ),
- params,
- )
-
-
-class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
- pass
-
-class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
+class SQLDeleteCompiler(BaseSQLDeleteCompiler):
def as_sql(self):
# Prefer the non-standard DELETE FROM syntax over the SQL
generated by
# the SQLDeleteCompiler's default implementation when multiple
tables
@@ -52,7 +44,7 @@ def as_sql(self):
return " ".join(result), tuple(params)
-class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
+class SQLUpdateCompiler(BaseSQLUpdateCompiler):
def as_sql(self):
update_query, update_params = super().as_sql()
# MySQL and MariaDB support UPDATE ... ORDER BY syntax.
@@ -78,7 +70,3 @@ def as_sql(self):
# removed in .update() and cannot be resolved.
pass
return update_query, update_params
-
-
-class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
- pass
diff --git a/django/db/models/fields/related_lookups.py
b/django/db/models/fields/related_lookups.py
index 38d6308f53..9fc7db7c34 100644
--- a/django/db/models/fields/related_lookups.py
+++ b/django/db/models/fields/related_lookups.py
@@ -84,21 +84,12 @@ def get_prep_lookup(self):
def as_sql(self, compiler, connection):
if isinstance(self.lhs, ColPairs):
- from django.db.models.sql.where import SubqueryConstraint
-
if self.rhs_is_direct_value():
values = [get_normalized_value(value, self.lhs) for value
in self.rhs]
lookup = TupleIn(self.lhs, values)
- return compiler.compile(lookup)
else:
- return compiler.compile(
- SubqueryConstraint(
- self.lhs.alias,
- [target.column for target in self.lhs.targets],
- [source.name for source in self.lhs.sources],
- self.rhs,
- ),
- )
+ lookup = TupleIn(self.lhs, self.rhs)
+ return compiler.compile(lookup)
return super().as_sql(compiler, connection)
diff --git a/django/db/models/fields/tuple_lookups.py
b/django/db/models/fields/tuple_lookups.py
index 98959a6161..e701c7d3de 100644
--- a/django/db/models/fields/tuple_lookups.py
+++ b/django/db/models/fields/tuple_lookups.py
@@ -2,7 +2,13 @@
from django.core.exceptions import EmptyResultSet
from django.db.models import Field
-from django.db.models.expressions import ColPairs, Func,
ResolvedOuterRef, Value
+from django.db.models.expressions import (
+ ColPairs,
+ Func,
+ ResolvedOuterRef,
+ Subquery,
+ Value,
+)
from django.db.models.lookups import (
Exact,
GreaterThan,
@@ -301,7 +307,7 @@ def check_rhs_elements_length_equals_lhs_length(self):
)
def check_rhs_is_query(self):
- if not isinstance(self.rhs, Query):
+ if not isinstance(self.rhs, (Query, Subquery)):
lhs_str = self.get_lhs_str()
rhs_cls = self.rhs.__class__.__name__
raise ValueError(
diff --git a/django/db/models/sql/compiler.py
b/django/db/models/sql/compiler.py
index 3bfb3bd631..ef10f00f20 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1661,19 +1661,6 @@ def execute_sql(
return list(result)
return result
- def as_subquery_condition(self, alias, columns, compiler):
- qn = compiler.quote_name_unless_alias
- qn2 = self.connection.ops.quote_name
- query = self.query.clone()
-
- for index, select_col in enumerate(query.select):
- lhs_sql, lhs_params = self.compile(select_col)
- rhs = "%s.%s" % (qn(alias), qn2(columns[index]))
- query.where.add(RawSQL("%s = %s" % (lhs_sql, rhs),
lhs_params), AND)
-
- sql, params = query.as_sql(compiler, self.connection)
- return "EXISTS %s" % sql, params
-
def explain_query(self):
result = list(self.execute_sql())
# Some backends return 1 item tuples with strings, and others
return
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 0fded5cce3..82f96aa6ec 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -343,23 +343,3 @@ def __init__(self, sqls, params):
def as_sql(self, compiler=None, connection=None):
sqls = ["(%s)" % sql for sql in self.sqls]
return " AND ".join(sqls), list(self.params or ())
-
-
-class SubqueryConstraint:
- # Even if aggregates or windows would be used in a subquery,
- # the outer query isn't interested about those.
- contains_aggregate = False
- contains_over_clause = False
-
- def __init__(self, alias, columns, targets, query_object):
- self.alias = alias
- self.columns = columns
- self.targets = targets
- query_object.clear_ordering(clear_default=True)
- self.query_object = query_object
-
- def as_sql(self, compiler, connection):
- query = self.query_object
- query.set_values(self.targets)
- query_compiler = query.get_compiler(connection=connection)
- return query_compiler.as_subquery_condition(self.alias,
self.columns, compiler)
diff --git a/tests/composite_pk/test_filter.py
b/tests/composite_pk/test_filter.py
index 937dd86652..4b753fb396 100644
--- a/tests/composite_pk/test_filter.py
+++ b/tests/composite_pk/test_filter.py
@@ -442,6 +442,11 @@ def test_cannot_cast_pk(self):
with self.assertRaisesMessage(ValueError, msg):
Comment.objects.filter(text__gt=Cast(F("pk"),
TextField())).count()
+ def test_explicit_subquery(self):
+ subquery = Subquery(User.objects.values("pk"))
+ self.assertEqual(User.objects.filter(pk__in=subquery).count(), 4)
+
self.assertEqual(Comment.objects.filter(user__in=subquery).count(), 5)
+
def test_filter_case_when(self):
msg = "When expression does not support composite primary keys."
with self.assertRaisesMessage(ValueError, msg):
}}}
Getting rid of `SuqueryConstraint` and `as_subquery_condition` was a goal
of #373 so that's a great win!
--
Ticket URL: <https://code.djangoproject.com/ticket/36181#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/django-updates/01070194edbaf469-5d8f65b1-5b2c-4c9e-88f2-589b4087335b-000000%40eu-central-1.amazonses.com.