Ticket #18375: ticket_18375_master.diff

File ticket_18375_master.diff, 4.7 KB (added by Anssi Kääriäinen, 11 years ago)
  • django/db/models/sql/expressions.py

    diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
    index 3745099..cf38a17 100644
    a b  
    11from django.core.exceptions import FieldError
    22from django.db.models.constants import LOOKUP_SEP
    33from django.db.models.fields import FieldDoesNotExist
     4from django.db.models.sql.constants import REUSE_ALL
    45
    56class SQLEvaluator(object):
    67    def __init__(self, expression, query, allow_joins=True):
    class SQLEvaluator(object):  
    910        self.cols = []
    1011
    1112        self.contains_aggregate = False
     13        self.used_joins = []
    1214        self.expression.prepare(self, query, allow_joins)
    1315
    1416    def prepare(self):
    class SQLEvaluator(object):  
    5052            try:
    5153                field, source, opts, join_list, last, _ = query.setup_joins(
    5254                    field_list, query.get_meta(),
    53                     query.get_initial_alias(), False)
     55                    query.get_initial_alias(), REUSE_ALL)
     56                # Note that even if we trim some joins away, the joins are
     57                # still usable by this node.
     58                self.used_joins.extend(join_list)
    5459                col, _, join_list = query.trim_joins(source, join_list, last, False)
    55 
    5660                self.cols.append((node, (join_list[-1], col)))
    5761            except FieldDoesNotExist:
    5862                raise FieldError("Cannot resolve keyword %r into field. "
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    index 4cfb816..f857137 100644
    a b class Query(object):  
    10971097        elif isinstance(value, ExpressionNode):
    10981098            # If value is a query expression, evaluate it
    10991099            value = SQLEvaluator(value, self)
     1100            if value.used_joins and can_reuse is not None:
     1101                can_reuse.update(value.used_joins)
    11001102            having_clause = value.contains_aggregate
    11011103
    11021104        for alias, aggregate in self.aggregates.items():
  • docs/releases/1.5.txt

    diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
    index b73bb04..5bd4e8b 100644
    a b Miscellaneous  
    575575  :ref:`Q() expressions <complex-lookups-with-q>` and ``QuerySet`` combining where
    576576  the operators are used as boolean AND and OR operators.
    577577
     578* When :ref:`F() expressions <query-expressions>` were used in lookups spanning
     579  multi-valued relations it was possible that the F() and other lookups in a
     580  single filter() call didn't target the same relation. This was change and now
     581  F() expressions will reuse the same relation.
     582
    578583* The :ttag:`csrf_token` template tag is no longer enclosed in a div. If you need
    579584  HTML validation against pre-HTML5 Strict DTDs, you should add a div around it
    580585  in your pages.
  • tests/modeltests/expressions/tests.py

    diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py
    index 99eb07e..ba3f827 100644
    a b class ExpressionsTests(TestCase):  
    219219        )
    220220        acme.num_employees = F("num_employees") + 16
    221221        self.assertRaises(TypeError, acme.save)
     222
     223    def test_ticket_18375_join_reuse(self):
     224        # Test that reverse multijoin F() references and the lookup target
     225        # the same join. Pre #18375 the F() join was generated first, and the
     226        # lookup couldn't reuse that join.
     227        qs = Employee.objects.filter(
     228            company_ceo_set__num_chairs=F('company_ceo_set__num_employees'))
     229        self.assertEqual(str(qs.query).count('JOIN'), 1)
     230
     231    def test_ticket_18375_kwarg_ordering(self):
     232        # The next query was dict-randomization dependent - if the "gte=1"
     233        # was seen first, then the F() will reuse the join generated by the
     234        # gte lookup, if F() was seen first, then it generated a join the
     235        # other lookups could not reuse.
     236        qs = Employee.objects.filter(
     237            company_ceo_set__num_chairs=F('company_ceo_set__num_employees'),
     238            company_ceo_set__num_chairs__gte=1)
     239        self.assertEqual(str(qs.query).count('JOIN'), 1)
     240
     241    def test_ticket_18375_kwarg_ordering_2(self):
     242        # Another similar case for F() than above. Now we have the same join
     243        # in two filter kwargs, one in the lhs lookup, one in F. Here pre
     244        # #18375 the amount of joins generated was random if dict
     245        # randomization was enabled, that is the generated query dependend
     246        # on which clause was seen first.
     247        qs = Employee.objects.filter(
     248            company_ceo_set__num_employees=F('pk'),
     249            pk=F('company_ceo_set__num_employees')
     250        )
     251        self.assertEqual(str(qs.query).count('JOIN'), 1)
Back to Top