Ticket #10790: django1.1-ticket10790v2.patch

File django1.1-ticket10790v2.patch, 9.1 KB (added by milosu, 14 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"""}
Back to Top