Code

Ticket #10790: django1.1-ticket10790v2.patch

File django1.1-ticket10790v2.patch, 9.1 KB (added by milosu, 4 years ago)

improved patch with regression test and bug fix

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

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

     
    991991        pieces = name.split(LOOKUP_SEP) 
    992992        if not alias: 
    993993            alias = self.get_initial_alias() 
    994         field, target, opts, joins, last, extra = self.setup_joins(pieces, 
     994        field, target, opts, joins, last, extra, allow_trim_join = self.setup_joins(pieces, 
    995995                opts, alias, False) 
    996996        alias = joins[-1] 
    997997        col = target.column 
     
    10851085            self.alias_map[alias] = tuple(data) 
    10861086            return True 
    10871087        return False 
     1088    def demote_alias(self, alias): 
     1089        """ 
     1090        Demotes the join type of an alias to an inner join 
     1091        """ 
     1092        data = list(self.alias_map[alias]) 
     1093        data[JOIN_TYPE] = self.INNER 
     1094        self.alias_map[alias] = tuple(data) 
    10881095 
    10891096    def promote_alias_chain(self, chain, must_promote=False): 
    10901097        """ 
     
    14741481            #   - this is an annotation over a model field 
    14751482            # then we need to explore the joins that are required. 
    14761483 
    1477             field, source, opts, join_list, last, _ = self.setup_joins( 
     1484            field, source, opts, join_list, last, _, allow_trim_join = self.setup_joins( 
    14781485                field_list, opts, self.get_initial_alias(), False) 
    14791486 
    14801487            # Process the join chain to see if it can be trimmed 
     
    15711578        allow_many = trim or not negate 
    15721579 
    15731580        try: 
    1574             field, target, opts, join_list, last, extra_filters = self.setup_joins( 
     1581            field, target, opts, join_list, last, extra_filters, allow_trim_join = self.setup_joins( 
    15751582                    parts, opts, alias, True, allow_many, can_reuse=can_reuse, 
    15761583                    negate=negate, process_extras=process_extras) 
    15771584        except MultiJoin, e: 
     
    15861593            # needed, as it's less efficient at the database level. 
    15871594            self.promote_alias_chain(join_list) 
    15881595 
     1596            # If we have a one2one or many2one field, we can trim the left outer 
     1597            # join from the end of a list of joins. 
     1598            # In order to do this, we convert alias join type back to INNER and 
     1599            # trim_joins later will do the strip for us. 
     1600            if allow_trim_join and field.rel: 
     1601                self.demote_alias(join_list[-1]) 
     1602 
    15891603        # Process the join list to see if we can remove any inner joins from 
    15901604        # the far end (fewer tables in a query is better). 
    15911605        col, alias, join_list = self.trim_joins(target, join_list, last, trim) 
     
    17191733        dupe_set = set() 
    17201734        exclusions = set() 
    17211735        extra_filters = [] 
     1736        allow_trim_join = True 
    17221737        for pos, name in enumerate(names): 
    17231738            try: 
    17241739                exclusions.add(int_alias) 
     
    17431758                    raise FieldError("Cannot resolve keyword %r into field. " 
    17441759                            "Choices are: %s" % (name, ", ".join(names))) 
    17451760 
     1761            # presence of indirect field in the filter requires 
     1762            # left outer join for isnull 
     1763            if not direct and allow_trim_join: 
     1764                allow_trim_join = False 
     1765 
    17461766            if not allow_many and (m2m or not direct): 
    17471767                for alias in joins: 
    17481768                    self.unref_alias(alias) 
     
    17841804                extra_filters.extend(field.extra_filters(names, pos, negate)) 
    17851805            if direct: 
    17861806                if m2m: 
     1807                    # null query on m2mfield requires outer join 
     1808                    allow_trim_join = False 
    17871809                    # Many-to-many field defined on the current model. 
    17881810                    if cached_data: 
    17891811                        (table1, from_col1, to_col1, table2, from_col2, 
     
    18931915            else: 
    18941916                raise FieldError("Join on field %r not permitted." % name) 
    18951917 
    1896         return field, target, opts, joins, last, extra_filters 
     1918        return field, target, opts, joins, last, extra_filters, allow_trim_join 
    18971919 
    18981920    def trim_joins(self, target, join_list, last, trim): 
    18991921        """ 
     
    20422064 
    20432065        try: 
    20442066            for name in field_names: 
    2045                 field, target, u2, joins, u3, u4 = self.setup_joins( 
     2067                field, target, u2, joins, u3, u4, allow_trim_join = self.setup_joins( 
    20462068                        name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 
    20472069                        True) 
    20482070                final_alias = joins[-1] 
     
    23262348        """ 
    23272349        opts = self.model._meta 
    23282350        alias = self.get_initial_alias() 
    2329         field, col, opts, joins, last, extra = self.setup_joins( 
     2351        field, col, opts, joins, last, extra, allow_trim_join = self.setup_joins( 
    23302352                start.split(LOOKUP_SEP), opts, alias, False) 
    23312353        select_col = self.alias_map[joins[1]][LHS_JOIN_COL] 
    23322354        select_alias = alias 
  • 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 
     32__test__ = {'API_TESTS':""" 
     33# Create a Reporter. 
     34>>> r = Reporter(name='John Smith') 
     35>>> r.save() 
     36 
     37# Create an Article. 
     38>>> a = Article(headline="First", reporter=r) 
     39>>> a.save() 
     40 
     41# Create an Article without reporter 
     42>>> a2 = Article(headline="Second") 
     43>>> a2.save() 
     44 
     45# querying with isnull should not join Reporter table 
     46>>> q = Article.objects.filter(reporter=None) 
     47>>> q.query.as_sql()[0] 
     48'SELECT "null_trimjoin_article"."id", "null_trimjoin_article"."headline", "null_trimjoin_article"."reporter_id" FROM "null_trimjoin_article" WHERE "null_trimjoin_article"."reporter_id" IS NULL ORDER BY "null_trimjoin_article"."headline" ASC' 
     49 
     50# repoter table however should be in q.query.tables 
     51>>> q.query.tables 
     52['null_trimjoin_article', 'null_trimjoin_reporter'] 
     53 
     54# querying across several tables should strip only the last join, while preserving 
     55# the preceeding left outer joins 
     56>>> q = Article.objects.filter(reporter__type=None) 
     57>>> print q 
     58[<Article: First>, <Article: Second>] 
     59>>> q.query.as_sql()[0] 
     60'SELECT "null_trimjoin_article"."id", "null_trimjoin_article"."headline", "null_trimjoin_article"."reporter_id" FROM "null_trimjoin_article" LEFT OUTER JOIN "null_trimjoin_reporter" ON ("null_trimjoin_article"."reporter_id" = "null_trimjoin_reporter"."id") WHERE "null_trimjoin_reporter"."type_id" IS NULL ORDER BY "null_trimjoin_article"."headline" ASC' 
     61 
     62# querying across m2m field should not strip the m2m table from join 
     63>>> q = Article.objects.filter(reporter__category__isnull=True) 
     64>>> q.query.as_sql()[0] 
     65'SELECT "null_trimjoin_article"."id", "null_trimjoin_article"."headline", "null_trimjoin_article"."reporter_id" FROM "null_trimjoin_article" LEFT OUTER JOIN "null_trimjoin_reporter" ON ("null_trimjoin_article"."reporter_id" = "null_trimjoin_reporter"."id") LEFT OUTER JOIN "null_trimjoin_reporter_category" ON ("null_trimjoin_reporter"."id" = "null_trimjoin_reporter_category"."reporter_id") LEFT OUTER JOIN "null_trimjoin_category" ON ("null_trimjoin_reporter_category"."category_id" = "null_trimjoin_category"."id") WHERE "null_trimjoin_category"."id" IS NULL ORDER BY "null_trimjoin_article"."headline" ASC' 
     66 
     67# reverse querying with isnull should not strip the join 
     68>>> q = Reporter.objects.filter(article__isnull=True) 
     69>>> q.query.as_sql()[0] 
     70'SELECT "null_trimjoin_reporter"."id", "null_trimjoin_reporter"."name", "null_trimjoin_reporter"."type_id" FROM "null_trimjoin_reporter" LEFT OUTER JOIN "null_trimjoin_article" ON ("null_trimjoin_reporter"."id" = "null_trimjoin_article"."reporter_id") WHERE "null_trimjoin_article"."id" IS NULL' 
     71 
     72"""}