summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMatthew Schinckel <matt@schinckel.net>2016-04-20 16:26:51 +0930
committerTim Graham <timograham@gmail.com>2017-01-14 09:12:24 -0500
commit236ebe94bfe24d394d5b49f4405da445550e8aa6 (patch)
treeff76a7831bd4494b888f0a53a8f4b8eb34fb54de /tests
parent84c1826ded17b2d74f66717fb745fc36e37949fd (diff)
Fixed #27149 -- Added Subquery and Exists database expressions.
Thanks Josh Smeaton for Oracle fixes.
Diffstat (limited to 'tests')
-rw-r--r--tests/expressions/tests.py140
1 files changed, 135 insertions, 5 deletions
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 18d003d57d..8399e3c0a9 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -12,8 +12,8 @@ from django.db.models.aggregates import (
Avg, Count, Max, Min, StdDev, Sum, Variance,
)
from django.db.models.expressions import (
- Case, Col, ExpressionWrapper, F, Func, OrderBy, Random, RawSQL, Ref, Value,
- When,
+ Case, Col, Exists, ExpressionWrapper, F, Func, OrderBy, OuterRef, Random,
+ RawSQL, Ref, Subquery, Value, When,
)
from django.db.models.functions import (
Coalesce, Concat, Length, Lower, Substr, Upper,
@@ -32,15 +32,15 @@ from .models import (
class BasicExpressionsTests(TestCase):
@classmethod
def setUpTestData(cls):
- Company.objects.create(
+ cls.example_inc = Company.objects.create(
name="Example Inc.", num_employees=2300, num_chairs=5,
ceo=Employee.objects.create(firstname="Joe", lastname="Smith", salary=10)
)
- Company.objects.create(
+ cls.foobar_ltd = Company.objects.create(
name="Foobar Ltd.", num_employees=3, num_chairs=4,
ceo=Employee.objects.create(firstname="Frank", lastname="Meyer", salary=20)
)
- Company.objects.create(
+ cls.gmbh = Company.objects.create(
name="Test GmbH", num_employees=32, num_chairs=1,
ceo=Employee.objects.create(firstname="Max", lastname="Mustermann", salary=30)
)
@@ -387,6 +387,136 @@ class BasicExpressionsTests(TestCase):
)
self.assertEqual(str(qs.query).count('JOIN'), 2)
+ def test_outerref(self):
+ inner = Company.objects.filter(point_of_contact=OuterRef('pk'))
+ msg = (
+ 'This queryset contains a reference to an outer query and may only '
+ 'be used in a subquery.'
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ inner.exists()
+
+ outer = Employee.objects.annotate(is_point_of_contact=Exists(inner))
+ self.assertIs(outer.exists(), True)
+
+ def test_subquery(self):
+ Company.objects.filter(name='Example Inc.').update(
+ point_of_contact=Employee.objects.get(firstname='Joe', lastname='Smith'),
+ ceo=Employee.objects.get(firstname='Max', lastname='Mustermann'),
+ )
+ Employee.objects.create(firstname='Bob', lastname='Brown', salary=40)
+ qs = Employee.objects.annotate(
+ is_point_of_contact=Exists(Company.objects.filter(point_of_contact=OuterRef('pk'))),
+ is_not_point_of_contact=~Exists(Company.objects.filter(point_of_contact=OuterRef('pk'))),
+ is_ceo_of_small_company=Exists(Company.objects.filter(num_employees__lt=200, ceo=OuterRef('pk'))),
+ is_ceo_small_2=~~Exists(Company.objects.filter(num_employees__lt=200, ceo=OuterRef('pk'))),
+ largest_company=Subquery(Company.objects.order_by('-num_employees').filter(
+ models.Q(ceo=OuterRef('pk')) | models.Q(point_of_contact=OuterRef('pk'))
+ ).values('name')[:1], output_field=models.CharField())
+ ).values(
+ 'firstname',
+ 'is_point_of_contact',
+ 'is_not_point_of_contact',
+ 'is_ceo_of_small_company',
+ 'is_ceo_small_2',
+ 'largest_company',
+ ).order_by('firstname')
+
+ results = list(qs)
+ # Could use Coalesce(subq, Value('')) instead except for the bug in
+ # cx_Oracle mentioned in #23843.
+ bob = results[0]
+ if bob['largest_company'] == '' and connection.features.interprets_empty_strings_as_nulls:
+ bob['largest_company'] = None
+
+ self.assertEqual(results, [
+ {
+ 'firstname': 'Bob',
+ 'is_point_of_contact': False,
+ 'is_not_point_of_contact': True,
+ 'is_ceo_of_small_company': False,
+ 'is_ceo_small_2': False,
+ 'largest_company': None,
+ },
+ {
+ 'firstname': 'Frank',
+ 'is_point_of_contact': False,
+ 'is_not_point_of_contact': True,
+ 'is_ceo_of_small_company': True,
+ 'is_ceo_small_2': True,
+ 'largest_company': 'Foobar Ltd.',
+ },
+ {
+ 'firstname': 'Joe',
+ 'is_point_of_contact': True,
+ 'is_not_point_of_contact': False,
+ 'is_ceo_of_small_company': False,
+ 'is_ceo_small_2': False,
+ 'largest_company': 'Example Inc.',
+ },
+ {
+ 'firstname': 'Max',
+ 'is_point_of_contact': False,
+ 'is_not_point_of_contact': True,
+ 'is_ceo_of_small_company': True,
+ 'is_ceo_small_2': True,
+ 'largest_company': 'Example Inc.'
+ }
+ ])
+ # A less elegant way to write the same query: this uses a LEFT OUTER
+ # JOIN and an IS NULL, inside a WHERE NOT IN which is probably less
+ # efficient than EXISTS.
+ self.assertCountEqual(
+ qs.filter(is_point_of_contact=True).values('pk'),
+ Employee.objects.exclude(company_point_of_contact_set=None).values('pk')
+ )
+
+ def test_in_subquery(self):
+ # This is a contrived test (and you really wouldn't write this query),
+ # but it is a succinct way to test the __in=Subquery() construct.
+ small_companies = Company.objects.filter(num_employees__lt=200).values('pk')
+ subquery_test = Company.objects.filter(pk__in=Subquery(small_companies))
+ self.assertCountEqual(subquery_test, [self.foobar_ltd, self.gmbh])
+ subquery_test2 = Company.objects.filter(pk=Subquery(small_companies.filter(num_employees=3)))
+ self.assertCountEqual(subquery_test2, [self.foobar_ltd])
+
+ def test_nested_subquery(self):
+ inner = Company.objects.filter(point_of_contact=OuterRef('pk'))
+ outer = Employee.objects.annotate(is_point_of_contact=Exists(inner))
+ contrived = Employee.objects.annotate(
+ is_point_of_contact=Subquery(
+ outer.filter(pk=OuterRef('pk')).values('is_point_of_contact'),
+ output_field=models.BooleanField(),
+ ),
+ )
+ self.assertCountEqual(contrived.values_list(), outer.values_list())
+
+ def test_nested_subquery_outer_ref_2(self):
+ first = Time.objects.create(time='09:00')
+ second = Time.objects.create(time='17:00')
+ third = Time.objects.create(time='21:00')
+ SimulationRun.objects.bulk_create([
+ SimulationRun(start=first, end=second, midpoint='12:00'),
+ SimulationRun(start=first, end=third, midpoint='15:00'),
+ SimulationRun(start=second, end=first, midpoint='00:00'),
+ ])
+ inner = Time.objects.filter(time=OuterRef(OuterRef('time')), pk=OuterRef('start')).values('time')
+ middle = SimulationRun.objects.annotate(other=Subquery(inner)).values('other')[:1]
+ outer = Time.objects.annotate(other=Subquery(middle, output_field=models.TimeField()))
+ # This is a contrived example. It exercises the double OuterRef form.
+ self.assertCountEqual(outer, [first, second, third])
+
+ def test_annotations_within_subquery(self):
+ Company.objects.filter(num_employees__lt=50).update(ceo=Employee.objects.get(firstname='Frank'))
+ inner = Company.objects.filter(
+ ceo=OuterRef('pk')
+ ).values('ceo').annotate(total_employees=models.Sum('num_employees')).values('total_employees')
+ outer = Employee.objects.annotate(total_employees=Subquery(inner)).filter(salary__lte=Subquery(inner))
+ self.assertSequenceEqual(
+ outer.order_by('-total_employees').values('salary', 'total_employees'),
+ [{'salary': 10, 'total_employees': 2300}, {'salary': 20, 'total_employees': 35}],
+ )
+
class IterableLookupInnerExpressionsTests(TestCase):
@classmethod