1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #32673 -- Fixed lookups crash when comparing against lookups on PostgreSQL.

Regression in 3a505c70e7.

Nonlitteral right-hand-sides of lookups need to be wrapped in
parentheses to avoid operator precedence ambiguities.

Thanks Charles Lirsac for the detailed report.
This commit is contained in:
Simon Charette 2021-04-22 13:44:25 -04:00 committed by Mariusz Felisiak
parent 67bb1f516c
commit 170b006ce8
4 changed files with 34 additions and 3 deletions

View File

@ -87,6 +87,10 @@ class DatabaseFeatures(BaseDatabaseFeatures):
'Raises ORA-00600: internal error code.': { 'Raises ORA-00600: internal error code.': {
'model_fields.test_jsonfield.TestQuerying.test_usage_in_subquery', 'model_fields.test_jsonfield.TestQuerying.test_usage_in_subquery',
}, },
"Oracle doesn't allow filters to be compared to another expression in "
"the WHERE clause.": {
'lookup.tests.LookupTests.test_lookup_rhs',
},
} }
django_test_expected_failures = { django_test_expected_failures = {
# A bug in Django/cx_Oracle with respect to string handling (#23843). # A bug in Django/cx_Oracle with respect to string handling (#23843).

View File

@ -94,7 +94,13 @@ class Lookup:
value = self.apply_bilateral_transforms(value) value = self.apply_bilateral_transforms(value)
value = value.resolve_expression(compiler.query) value = value.resolve_expression(compiler.query)
if hasattr(value, 'as_sql'): if hasattr(value, 'as_sql'):
return compiler.compile(value) sql, params = compiler.compile(value)
# Ensure expression is wrapped in parentheses to respect operator
# precedence but avoid double wrapping as it can be misinterpreted
# on some backends (e.g. subqueries on SQLite).
if sql and sql[0] != '(':
sql = '(%s)' % sql
return sql, params
else: else:
return self.get_db_prep_lookup(value, connection) return self.get_db_prep_lookup(value, connection)

View File

@ -94,6 +94,7 @@ class Product(models.Model):
class Stock(models.Model): class Stock(models.Model):
product = models.ForeignKey(Product, models.CASCADE) product = models.ForeignKey(Product, models.CASCADE)
short = models.BooleanField(default=False)
qty_available = models.DecimalField(max_digits=6, decimal_places=2) qty_available = models.DecimalField(max_digits=6, decimal_places=2)

View File

@ -5,13 +5,16 @@ from operator import attrgetter
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db import connection, models from django.db import connection, models
from django.db.models import Exists, Max, OuterRef from django.db.models import (
BooleanField, Exists, ExpressionWrapper, F, Max, OuterRef, Q,
)
from django.db.models.functions import Substr from django.db.models.functions import Substr
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from django.test.utils import isolate_apps from django.test.utils import isolate_apps
from .models import ( from .models import (
Article, Author, Freebie, Game, IsNullWithNoneAsRHS, Player, Season, Tag, Article, Author, Freebie, Game, IsNullWithNoneAsRHS, Player, Product,
Season, Stock, Tag,
) )
@ -1000,3 +1003,20 @@ class LookupTests(TestCase):
with self.subTest(qs=qs): with self.subTest(qs=qs):
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
qs.exists() qs.exists()
def test_lookup_rhs(self):
product = Product.objects.create(name='GME', qty_target=5000)
stock_1 = Stock.objects.create(product=product, short=True, qty_available=180)
stock_2 = Stock.objects.create(product=product, short=False, qty_available=5100)
Stock.objects.create(product=product, short=False, qty_available=4000)
self.assertCountEqual(
Stock.objects.filter(short=Q(qty_available__lt=F('product__qty_target'))),
[stock_1, stock_2],
)
self.assertCountEqual(
Stock.objects.filter(short=ExpressionWrapper(
Q(qty_available__lt=F('product__qty_target')),
output_field=BooleanField(),
)),
[stock_1, stock_2],
)