Ticket #10790: 107090v3.diff

File 107090v3.diff, 9.2 KB (added by danielr, 4 years ago)

Updated patch which applies against trunk, fixes tabbing, and converts doctests to unit tests.

  • django/db/models/sql/compiler.py

     
    374374        pieces = name.split(LOOKUP_SEP)
    375375        if not alias:
    376376            alias = self.query.get_initial_alias()
    377         field, target, opts, joins, last, extra = self.query.setup_joins(pieces,
    378                 opts, alias, False)
     377        field, target, opts, joins, last, extra, allow_trim_join = self.query.setup_joins(
     378                pieces, opts, alias, False)
    379379        alias = joins[-1]
    380380        col = target.column
    381381        if not field.rel:
  • django/db/models/sql/expressions.py

     
    4141            self.cols[node] = query.aggregate_select[node.name]
    4242        else:
    4343            try:
    44                 field, source, opts, join_list, last, _ = query.setup_joins(
     44                field, source, opts, join_list, last, _, allow_trim_join = query.setup_joins(
    4545                    field_list, query.get_meta(),
    4646                    query.get_initial_alias(), False)
    4747                col, _, join_list = query.trim_joins(source, join_list, last, False)
  • django/db/models/sql/query.py

     
    651651            return True
    652652        return False
    653653
     654    def demote_alias(self, alias):
     655        """
     656        Demotes the join type of an alias to an inner join.
     657        """
     658        data = list(self.alias_map[alias])
     659        data[JOIN_TYPE] = self.INNER
     660        self.alias_map[alias] = tuple(data)
     661
    654662    def promote_alias_chain(self, chain, must_promote=False):
    655663        """
    656664        Walks along a chain of aliases, promoting the first nullable join and
     
    929937            #   - this is an annotation over a model field
    930938            # then we need to explore the joins that are required.
    931939
    932             field, source, opts, join_list, last, _ = self.setup_joins(
     940            field, source, opts, join_list, last, _, allow_trim_join = self.setup_joins(
    933941                field_list, opts, self.get_initial_alias(), False)
    934942
    935943            # Process the join chain to see if it can be trimmed
     
    10211029        allow_many = trim or not negate
    10221030
    10231031        try:
    1024             field, target, opts, join_list, last, extra_filters = self.setup_joins(
     1032            field, target, opts, join_list, last, extra_filters, allow_trim_join = self.setup_joins(
    10251033                    parts, opts, alias, True, allow_many, can_reuse=can_reuse,
    10261034                    negate=negate, process_extras=process_extras)
    10271035        except MultiJoin, e:
     
    10361044            # needed, as it's less efficient at the database level.
    10371045            self.promote_alias_chain(join_list)
    10381046
     1047        # If we have a one2one or many2one field, we can trim the left outer
     1048        # join from the end of a list of joins.
     1049        # In order to do this, we convert alias join type back to INNER and
     1050        # trim_joins later will do the strip for us.
     1051        if allow_trim_join and field.rel:
     1052            self.demote_alias(join_list[-1])
     1053
    10391054        # Process the join list to see if we can remove any inner joins from
    10401055        # the far end (fewer tables in a query is better).
    10411056        col, alias, join_list = self.trim_joins(target, join_list, last, trim)
     
    11661181        dupe_set = set()
    11671182        exclusions = set()
    11681183        extra_filters = []
     1184        allow_trim_join = True
    11691185        for pos, name in enumerate(names):
    11701186            try:
    11711187                exclusions.add(int_alias)
     
    11901206                    raise FieldError("Cannot resolve keyword %r into field. "
    11911207                            "Choices are: %s" % (name, ", ".join(names)))
    11921208
     1209            # presence of indirect field in the filter requires
     1210            # left outer join for isnull
     1211            if not direct and allow_trim_join:
     1212                allow_trim_join = False
     1213
    11931214            if not allow_many and (m2m or not direct):
    11941215                for alias in joins:
    11951216                    self.unref_alias(alias)
     
    12311252                extra_filters.extend(field.extra_filters(names, pos, negate))
    12321253            if direct:
    12331254                if m2m:
     1255                    # null query on m2mfield requires outer join
     1256                    allow_trim_join = False
    12341257                    # Many-to-many field defined on the current model.
    12351258                    if cached_data:
    12361259                        (table1, from_col1, to_col1, table2, from_col2,
     
    13401363            else:
    13411364                raise FieldError("Join on field %r not permitted." % name)
    13421365
    1343         return field, target, opts, joins, last, extra_filters
     1366        return field, target, opts, joins, last, extra_filters, allow_trim_join
    13441367
    13451368    def trim_joins(self, target, join_list, last, trim):
    13461369        """
     
    14891512
    14901513        try:
    14911514            for name in field_names:
    1492                 field, target, u2, joins, u3, u4 = self.setup_joins(
     1515                field, target, u2, joins, u3, u4, allow_trim_join = self.setup_joins(
    14931516                        name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
    14941517                        True)
    14951518                final_alias = joins[-1]
     
    17661789        """
    17671790        opts = self.model._meta
    17681791        alias = self.get_initial_alias()
    1769         field, col, opts, joins, last, extra = self.setup_joins(
     1792        field, col, opts, joins, last, extra, allow_trim_join = self.setup_joins(
    17701793                start.split(LOOKUP_SEP), opts, alias, False)
    17711794        select_col = self.alias_map[joins[1]][LHS_JOIN_COL]
    17721795        select_alias = alias
  • tests/modeltests/null_trimjoin/tests.py

     
     1from django.test import TestCase
     2from models import Article, Reporter
     3
     4class OneToOneTests(TestCase):
     5
     6    def setUp(self):
     7        self.r = Reporter(name='John Smith')
     8        self.r.save()
     9        self.a = Article(headline="First", reporter=self.r)
     10        self.a.save()
     11        self.a2 = Article(headline="Second")
     12        self.a2.save()
     13
     14    def test_query_with_isnull(self):
     15        """Querying with isnull should not join Reporter table."""
     16        q = Article.objects.filter(reporter=None)
     17        # check that reporter is not in the query's used_aliases
     18        self.assertFalse('null_trimjoin_reporter' in q.query.used_aliases)
     19        self.assertTrue('null_trimjoin_article' in q.query.used_aliases)
     20        # but it should still be in query.tables
     21        self.assertTrue('null_trimjoin_article' in q.query.tables)
     22
     23    def test_query_across_tables(self):
     24        """Querying across several tables should strip only the last join, while
     25        preserving the preceding left outer joins."""
     26        q = Article.objects.filter(reporter__type=None)
     27        self.assertEquals(len(q), 2)
     28        self.assertTrue('null_trimjoin_article' in q.query.used_aliases)
     29        self.assertTrue('null_trimjoin_reporter' in q.query.used_aliases)
     30        self.assertFalse('null_trimjoin_reportertype' in q.query.used_aliases)
     31
     32    def test_m2m_query(self):
     33        """Querying across m2m field should not strip the m2m table from join."""
     34        q = Article.objects.filter(reporter__category__isnull=True)
     35        self.assertTrue('null_trimjoin_article' in q.query.used_aliases)
     36        self.assertTrue('null_trimjoin_reporter' in q.query.used_aliases)
     37        self.assertTrue('null_trimjoin_category' in q.query.used_aliases)
     38
     39    def test_reverse_query(self):
     40        """Reverse querying with isnull should not strip the join."""
     41        q = Reporter.objects.filter(article__isnull=True)
     42        self.assertTrue('null_trimjoin_reporter' in q.query.used_aliases)
     43
     44
  • tests/modeltests/null_trimjoin/models.py

     
     1"""
     2Do not join table when querying on isnull
     3
     4"""
     5
     6from django.db import models
     7
     8class Category(models.Model):
     9    name = models.CharField(max_length=30)
     10
     11class ReporterType(models.Model):
     12    name = models.CharField(max_length=30)
     13
     14class Reporter(models.Model):
     15    name = models.CharField(max_length=30)
     16    type = models.ForeignKey(ReporterType, null=True)
     17    category = models.ManyToManyField(Category, null=True)
     18
     19    def __unicode__(self):
     20        return self.name
     21
     22class Article(models.Model):
     23    headline = models.CharField(max_length=100)
     24    reporter = models.ForeignKey(Reporter, null=True)
     25
     26    class Meta:
     27        ordering = ('headline',)
     28
     29    def __unicode__(self):
     30        return self.headline
     31
Back to Top