From 0856c765f51173d393a875cc174e303ea1d678ba Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 13:42:09 +0000 Subject: [PATCH] Migrated extra_regress doctests. Thanks to Stephan Jaekel. git-svn-id: http://code.djangoproject.com/svn/django/trunk@13951 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/extra_regress/models.py | 177 ---------- tests/regressiontests/extra_regress/tests.py | 314 ++++++++++++++++++ 2 files changed, 314 insertions(+), 177 deletions(-) create mode 100644 tests/regressiontests/extra_regress/tests.py diff --git a/tests/regressiontests/extra_regress/models.py b/tests/regressiontests/extra_regress/models.py index b68a37348cb..073157a38a7 100644 --- a/tests/regressiontests/extra_regress/models.py +++ b/tests/regressiontests/extra_regress/models.py @@ -4,9 +4,6 @@ import django.utils.copycompat as copy from django.contrib.auth.models import User from django.db import models -from django.db.models.query import Q -from django.utils.datastructures import SortedDict - class RevisionableModel(models.Model): base = models.ForeignKey('self', null=True) @@ -41,177 +38,3 @@ class TestObject(models.Model): def __unicode__(self): return u'TestObject: %s,%s,%s' % (self.first,self.second,self.third) -__test__ = {"API_TESTS": """ -# Regression tests for #7314 and #7372 - ->>> rm = RevisionableModel.objects.create(title='First Revision', when=datetime.datetime(2008, 9, 28, 10, 30, 0)) ->>> rm.pk, rm.base.pk -(1, 1) - ->>> rm2 = rm.new_revision() ->>> rm2.title = "Second Revision" ->>> rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0) ->>> rm2.save() ->>> print u"%s of %s" % (rm2.title, rm2.base.title) -Second Revision of First Revision - ->>> rm2.pk, rm2.base.pk -(2, 1) - -Queryset to match most recent revision: ->>> qs = RevisionableModel.objects.extra(where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % {'table': RevisionableModel._meta.db_table,}],) ->>> qs -[] - -Queryset to search for string in title: ->>> qs2 = RevisionableModel.objects.filter(title__contains="Revision") ->>> qs2 -[, ] - -Following queryset should return the most recent revision: ->>> qs & qs2 -[] - ->>> u = User.objects.create_user(username="fred", password="secret", email="fred@example.com") - -# General regression tests: extra select parameters should stay tied to their -# corresponding select portions. Applies when portions are updated or otherwise -# moved around. ->>> qs = User.objects.extra(select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), select_params=(1, 3)) ->>> qs = qs.extra(select={"beta": 4}) ->>> qs = qs.extra(select={"alpha": "%s"}, select_params=[5]) ->>> result = {'alpha': 5, 'beta': 4, 'gamma': 3} ->>> list(qs.filter(id=u.id).values('alpha', 'beta', 'gamma')) == [result] -True - -# Regression test for #7957: Combining extra() calls should leave the -# corresponding parameters associated with the right extra() bit. I.e. internal -# dictionary must remain sorted. ->>> User.objects.extra(select={"alpha": "%s"}, select_params=(1,)).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha -1 ->>> User.objects.extra(select={"beta": "%s"}, select_params=(1,)).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha -2 - -# Regression test for #7961: When not using a portion of an extra(...) in a -# query, remove any corresponding parameters from the query as well. ->>> list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,)).filter(id=u.id).values_list('id', flat=True)) == [u.id] -True - -# Regression test for #8063: limiting a query shouldn't discard any extra() -# bits. ->>> qs = User.objects.all().extra(where=['id=%s'], params=[u.id]) ->>> qs -[] ->>> qs[:1] -[] - -# Regression test for #8039: Ordering sometimes removed relevant tables from -# extra(). This test is the critical case: ordering uses a table, but then -# removes the reference because of an optimisation. The table should still be -# present because of the extra() call. ->>> Order.objects.extra(where=["username=%s"], params=["fred"], tables=["auth_user"]).order_by('created_by') -[] - -# Regression test for #8819: Fields in the extra(select=...) list should be -# available to extra(order_by=...). ->>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}).distinct() -[] ->>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}, order_by=['extra_field']) -[] ->>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct() -[] - -# When calling the dates() method on a queryset with extra selection columns, -# we can (and should) ignore those columns. They don't change the result and -# cause incorrect SQL to be produced otherwise. ->>> RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month') -[datetime.datetime(2008, 9, 1, 0, 0)] - -# Regression test for #10256... If there is a values() clause, Extra columns are -# only returned if they are explicitly mentioned. ->>> TestObject(first='first', second='second', third='third').save() - ->>> list(TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values()) == [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': 1, 'first': u'first'}] -True - -# Extra clauses after an empty values clause are still included ->>> list(TestObject.objects.values().extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third'))))) == [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': 1, 'first': u'first'}] -True - -# Extra columns are ignored if not mentioned in the values() clause ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('first', 'second') -[{'second': u'second', 'first': u'first'}] - -# Extra columns after a non-empty values() clause are ignored ->>> TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))) -[{'second': u'second', 'first': u'first'}] - -# Extra columns can be partially returned ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('first', 'second', 'foo') -[{'second': u'second', 'foo': u'first', 'first': u'first'}] - -# Also works if only extra columns are included ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('foo', 'whiz') -[{'foo': u'first', 'whiz': u'third'}] - -# Values list works the same way -# All columns are returned for an empty values_list() ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list() -[(u'first', u'second', u'third', 1, u'first', u'second', u'third')] - -# Extra columns after an empty values_list() are still included ->>> TestObject.objects.values_list().extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))) -[(u'first', u'second', u'third', 1, u'first', u'second', u'third')] - -# Extra columns ignored completely if not mentioned in values_list() ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first', 'second') -[(u'first', u'second')] - -# Extra columns after a non-empty values_list() clause are ignored completely ->>> TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))) -[(u'first', u'second')] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('second', flat=True) -[u'second'] - -# Only the extra columns specified in the values_list() are returned ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first', 'second', 'whiz') -[(u'first', u'second', u'third')] - -# ...also works if only extra columns are included ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('foo','whiz') -[(u'first', u'third')] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz', flat=True) -[u'third'] - -# ... and values are returned in the order they are specified ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz','foo') -[(u'third', u'first')] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first','id') -[(u'first', 1)] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz', 'first', 'bar', 'id') -[(u'third', u'first', u'second', 1)] - -# Regression for #10847: the list of extra columns can always be accurately evaluated. -# Using an inner query ensures that as_sql() is producing correct output -# without requiring full evaluation and execution of the inner query. ->>> TestObject.objects.extra(select={'extra': 1}).values('pk') -[{'pk': 1}] - ->>> TestObject.objects.filter(pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk')) -[] - ->>> TestObject.objects.values('pk').extra(select={'extra': 1}) -[{'pk': 1}] - ->>> TestObject.objects.filter(pk__in=TestObject.objects.values('pk').extra(select={'extra': 1})) -[] - ->>> pk = TestObject.objects.get().pk ->>> TestObject.objects.filter(pk=pk) | TestObject.objects.extra(where=["id > %s"], params=[pk]) -[] - -"""} diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/regressiontests/extra_regress/tests.py new file mode 100644 index 00000000000..ef7cbb8b9ba --- /dev/null +++ b/tests/regressiontests/extra_regress/tests.py @@ -0,0 +1,314 @@ +from django.test import TestCase + +from django.utils.datastructures import SortedDict + +from django.contrib.auth.models import User +from regressiontests.extra_regress.models import TestObject, Order, \ + RevisionableModel + +import datetime + +class ExtraRegressTests(TestCase): + + def setUp(self): + self.u = User.objects.create_user( + username="fred", + password="secret", + email="fred@example.com" + ) + + def test_regression_7314_7372(self): + """ + Regression tests for #7314 and #7372 + """ + rm = RevisionableModel.objects.create( + title='First Revision', + when=datetime.datetime(2008, 9, 28, 10, 30, 0) + ) + self.assertEqual(rm.pk, rm.base.pk) + + rm2 = rm.new_revision() + rm2.title = "Second Revision" + rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0) + rm2.save() + + self.assertEqual(rm2.title, 'Second Revision') + self.assertEqual(rm2.base.title, 'First Revision') + + self.assertNotEqual(rm2.pk, rm.pk) + self.assertEqual(rm2.base.pk, rm.pk) + + # Queryset to match most recent revision: + qs = RevisionableModel.objects.extra( + where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % { + 'table': RevisionableModel._meta.db_table, + }] + ) + + self.assertQuerysetEqual(qs, + [('Second Revision', 'First Revision')], + transform=lambda r: (r.title, r.base.title) + ) + + # Queryset to search for string in title: + qs2 = RevisionableModel.objects.filter(title__contains="Revision") + self.assertQuerysetEqual(qs2, + [ + ('First Revision', 'First Revision'), + ('Second Revision', 'First Revision'), + ], + transform=lambda r: (r.title, r.base.title) + ) + + # Following queryset should return the most recent revision: + self.assertQuerysetEqual(qs & qs2, + [('Second Revision', 'First Revision')], + transform=lambda r: (r.title, r.base.title) + ) + + def test_extra_stay_tied(self): + # Extra select parameters should stay tied to their corresponding + # select portions. Applies when portions are updated or otherwise + # moved around. + qs = User.objects.extra( + select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), + select_params=(1, 3) + ) + qs = qs.extra(select={"beta": 4}) + qs = qs.extra(select={"alpha": "%s"}, select_params=[5]) + self.assertEqual( + list(qs.filter(id=self.u.id).values('alpha', 'beta', 'gamma')), + [{'alpha': 5, 'beta': 4, 'gamma': 3}] + ) + + def test_regression_7957(self): + """ + Regression test for #7957: Combining extra() calls should leave the + corresponding parameters associated with the right extra() bit. I.e. + internal dictionary must remain sorted. + """ + self.assertEqual( + User.objects.extra(select={"alpha": "%s"}, select_params=(1,) + ).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha, + 1) + + self.assertEqual( + User.objects.extra(select={"beta": "%s"}, select_params=(1,) + ).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha, + 2) + + def test_regression_7961(self): + """ + Regression test for #7961: When not using a portion of an + extra(...) in a query, remove any corresponding parameters from the + query as well. + """ + self.assertEqual( + list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,) + ).filter(id=self.u.id).values_list('id', flat=True)), + [self.u.id] + ) + + def test_regression_8063(self): + """ + Regression test for #8063: limiting a query shouldn't discard any + extra() bits. + """ + qs = User.objects.all().extra(where=['id=%s'], params=[self.u.id]) + self.assertQuerysetEqual(qs, ['']) + self.assertQuerysetEqual(qs[:1], ['']) + + def test_regression_8039(self): + """ + Regression test for #8039: Ordering sometimes removed relevant tables + from extra(). This test is the critical case: ordering uses a table, + but then removes the reference because of an optimisation. The table + should still be present because of the extra() call. + """ + self.assertQuerysetEqual( + Order.objects.extra(where=["username=%s"], + params=["fred"], + tables=["auth_user"] + ).order_by('created_by'), + [] + ) + + def test_regression_8819(self): + """ + Regression test for #8819: Fields in the extra(select=...) list + should be available to extra(order_by=...). + """ + self.assertQuerysetEqual( + User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}).distinct(), + [''] + ) + self.assertQuerysetEqual( + User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']), + [''] + ) + self.assertQuerysetEqual( + User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct(), + [''] + ) + + def test_dates_query(self): + """ + When calling the dates() method on a queryset with extra selection + columns, we can (and should) ignore those columns. They don't change + the result and cause incorrect SQL to be produced otherwise. + """ + rm = RevisionableModel.objects.create( + title='First Revision', + when=datetime.datetime(2008, 9, 28, 10, 30, 0) + ) + + self.assertQuerysetEqual( + RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month'), + ['datetime.datetime(2008, 9, 1, 0, 0)'] + ) + + def test_values_with_extra(self): + """ + Regression test for #10256... If there is a values() clause, Extra + columns are only returned if they are explicitly mentioned. + """ + obj = TestObject(first='first', second='second', third='third') + obj.save() + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values()), + [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}] + ) + + # Extra clauses after an empty values clause are still included + self.assertEqual( + list(TestObject.objects.values().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}] + ) + + # Extra columns are ignored if not mentioned in the values() clause + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second')), + [{'second': u'second', 'first': u'first'}] + ) + + # Extra columns after a non-empty values() clause are ignored + self.assertEqual( + list(TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [{'second': u'second', 'first': u'first'}] + ) + + # Extra columns can be partially returned + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second', 'foo')), + [{'second': u'second', 'foo': u'first', 'first': u'first'}] + ) + + # Also works if only extra columns are included + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('foo', 'whiz')), + [{'foo': u'first', 'whiz': u'third'}] + ) + + # Values list works the same way + # All columns are returned for an empty values_list() + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()), + [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] + ) + + # Extra columns after an empty values_list() are still included + self.assertEqual( + list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] + ) + + # Extra columns ignored completely if not mentioned in values_list() + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')), + [(u'first', u'second')] + ) + + # Extra columns after a non-empty values_list() clause are ignored completely + self.assertEqual( + list(TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [(u'first', u'second')] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('second', flat=True)), + [u'second'] + ) + + # Only the extra columns specified in the values_list() are returned + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second', 'whiz')), + [(u'first', u'second', u'third')] + ) + + # ...also works if only extra columns are included + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('foo','whiz')), + [(u'first', u'third')] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', flat=True)), + [u'third'] + ) + + # ... and values are returned in the order they are specified + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz','foo')), + [(u'third', u'first')] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first','id')), + [(u'first', obj.pk)] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')), + [(u'third', u'first', u'second', obj.pk)] + ) + + def test_regression_10847(self): + """ + Regression for #10847: the list of extra columns can always be + accurately evaluated. Using an inner query ensures that as_sql() is + producing correct output without requiring full evaluation and + execution of the inner query. + """ + obj = TestObject(first='first', second='second', third='third') + obj.save() + + self.assertEqual( + list(TestObject.objects.extra(select={'extra': 1}).values('pk')), + [{'pk': obj.pk}] + ) + + self.assertQuerysetEqual( + TestObject.objects.filter( + pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk') + ), + [''] + ) + + self.assertEqual( + list(TestObject.objects.values('pk').extra(select={'extra': 1})), + [{'pk': obj.pk}] + ) + + self.assertQuerysetEqual( + TestObject.objects.filter( + pk__in=TestObject.objects.values('pk').extra(select={'extra': 1}) + ), + [''] + ) + + self.assertQuerysetEqual( + TestObject.objects.filter(pk=obj.pk) | + TestObject.objects.extra(where=["id > %s"], params=[obj.pk]), + [''] + )