Django

Code

Changeset 7217

Show
Ignore:
Timestamp:
03/10/08 20:15:15 (9 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Reworked exclude() handling to fix a few merging problems.

Fixed #6704.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r7179 r7217  
    2020from datastructures import EmptyResultSet, Empty 
    2121from constants import * 
     22 
     23try: 
     24    set 
     25except NameError: 
     26    from sets import Set as set     # Python 2.3 fallback 
    2227 
    2328__all__ = ['Query'] 
     
    574579        Promotes the join type of an alias to an outer join if it's possible 
    575580        for the join to contain NULL values on the left. 
    576  
    577         Returns True if the aliased join was promoted. 
    578581        """ 
    579582        if self.alias_map[alias][ALIAS_NULLABLE]: 
    580583            self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER 
    581             return True 
    582         return False 
    583584 
    584585    def change_alias(self, old_alias, new_alias): 
     
    754755                    used, next, restricted) 
    755756 
    756     def add_filter(self, filter_expr, connector=AND, negate=False, trim=False): 
     757    def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 
     758            merge_negated=False): 
    757759        """ 
    758760        Add a single filter to the query. The 'filter_expr' is a pair: 
     
    762764        automatically trim the final join group (used internally when 
    763765        constructing nested queries). 
     766 
     767        If 'merge_negated' is True, this negated filter will be merged with the 
     768        existing negated where node (if it exists). This is used when 
     769        constructing an exclude filter from combined subfilters. 
    764770        """ 
    765771        arg, value = filter_expr 
     
    814820                alias = join[LHS_ALIAS] 
    815821                col = join[LHS_JOIN_COL] 
    816  
    817         if lookup_type == 'isnull' and value is True and (len(join_list) > 1 or 
    818                 len(join_list[0]) > 1): 
     822                if len(join_list[-1]) == 1: 
     823                    join_list = join_list[:-1] 
     824                else: 
     825                    join_list[-1] = join_list[-1][:-1] 
     826 
     827        if (lookup_type == 'isnull' and value is True and not negate and 
     828                (len(join_list) > 1 or len(join_list[0]) > 1)): 
    819829            # If the comparison is against NULL, we need to use a left outer 
    820830            # join when connecting to the previous model. We make that 
     
    847857                self.promote_alias(table) 
    848858 
    849         self.where.add([alias, col, field, lookup_type, value], connector) 
     859        entry = [alias, col, field, lookup_type, value] 
     860        if merge_negated: 
     861            # This case is when we're doing the Q2 filter in exclude(Q1, Q2). 
     862            # It's different from exclude(Q1).exclude(Q2). 
     863            for node in self.where.children: 
     864                if getattr(node, 'negated', False): 
     865                    node.add(entry, connector) 
     866                    merged = True 
     867                    break 
     868        else: 
     869            self.where.add(entry, connector) 
     870            merged = False 
    850871 
    851872        if negate: 
    852             flag = False 
    853             for seq in join_list: 
    854                 for join in seq: 
    855                     if self.promote_alias(join): 
    856                         flag = True 
    857             self.where.negate() 
    858             if flag: 
    859                 # XXX: Change this to the field we joined against to allow 
    860                 # for node sharing and where-tree optimisation? 
    861                 self.where.add([alias, col, field, 'isnull', True], OR) 
     873            count = 0 
     874            for join in join_list: 
     875                count += len(join) 
     876                for alias in join: 
     877                    self.promote_alias(alias) 
     878            if not merged: 
     879                self.where.negate() 
     880            if count > 1 and lookup_type != 'isnull': 
     881                j_col = self.alias_map[alias][ALIAS_JOIN][RHS_JOIN_COL] 
     882                entry = Node([[alias, j_col, None, 'isnull', True]]) 
     883                entry.negate() 
     884                self.where.add(entry, AND) 
    862885 
    863886    def add_q(self, q_object): 
     
    878901            subtree = False 
    879902        connector = AND 
     903        merge = False 
    880904        for child in q_object.children: 
    881905            if isinstance(child, Node): 
     
    884908                self.where.end_subtree() 
    885909            else: 
    886                 self.add_filter(child, connector, q_object.negated) 
     910                self.add_filter(child, connector, q_object.negated, 
     911                        merge_negated=merge) 
     912                merge = q_object.negated 
    887913            connector = q_object.connector 
    888914        if subtree: 
     
    903929        """ 
    904930        joins = [[alias]] 
     931        used = set() 
    905932        for pos, name in enumerate(names): 
     933            used.update(joins[-1]) 
    906934            if name == 'pk': 
    907935                name = opts.pk.name 
     
    925953                    opts = int_model._meta 
    926954                    alias = self.join((alias, opts.db_table, lhs_col, 
    927                             opts.pk.column)
     955                            opts.pk.column), exclusions=used
    928956                    alias_list.append(alias) 
    929957                joins.append(alias_list) 
     
    951979 
    952980                    int_alias = self.join((alias, table1, from_col1, to_col1), 
    953                             dupe_multis, nullable=True) 
     981                            dupe_multis, used, nullable=True) 
    954982                    alias = self.join((int_alias, table2, from_col2, to_col2), 
    955                             dupe_multis, nullable=True) 
     983                            dupe_multis, used, nullable=True) 
    956984                    joins.append([int_alias, alias]) 
    957985                elif field.rel: 
     
    969997 
    970998                    alias = self.join((alias, table, from_col, to_col), 
    971                             nullable=field.null) 
     999                            exclusions=used, nullable=field.null) 
    9721000                    joins.append([alias]) 
    9731001                else: 
     
    9971025 
    9981026                    int_alias = self.join((alias, table1, from_col1, to_col1), 
    999                             dupe_multis, nullable=True) 
     1027                            dupe_multis, used, nullable=True) 
    10001028                    alias = self.join((int_alias, table2, from_col2, to_col2), 
    1001                             dupe_multis, nullable=True) 
     1029                            dupe_multis, used, nullable=True) 
    10021030                    joins.append([int_alias, alias]) 
    10031031                else: 
     
    10171045 
    10181046                    alias = self.join((alias, table, from_col, to_col), 
    1019                             dupe_multis, nullable=True) 
     1047                            dupe_multis, used, nullable=True) 
    10201048                    joins.append([alias]) 
    10211049 
  • django/branches/queryset-refactor/django/db/models/sql/where.py

    r7170 r7217  
    66from django.utils import tree 
    77from django.db import connection 
     8from django.db.models.fields import Field 
    89from datastructures import EmptyResultSet, FullResultSet 
    910 
     
    106107            cast_sql = '%s' 
    107108 
    108         params = field.get_db_prep_lookup(lookup_type, value) 
     109        if field: 
     110            params = field.get_db_prep_lookup(lookup_type, value) 
     111        else: 
     112            params = Field().get_db_prep_lookup(lookup_type, value) 
    109113        if isinstance(params, tuple): 
    110114            extra, params = params 
  • django/branches/queryset-refactor/django/utils/tree.py

    r7169 r7217  
    1717 
    1818    def __init__(self, children=None, connector=None, negated=False): 
     19        """ 
     20        Constructs a new Node. If no connector is given, the default will be 
     21        used. 
     22 
     23        Warning: You probably don't want to pass in the 'negated' parameter. It 
     24        is NOT the same as constructing a node and calling negate() on the 
     25        result. 
     26        """ 
    1927        self.children = children and children[:] or [] 
    2028        self.connector = connector or self.default 
     
    6472        connector is created, connecting the existing tree and the new node. 
    6573        """ 
     74        if node in self.children: 
     75            return 
    6676        if len(self.children) < 2: 
    6777            self.connector = conn_type 
     
    7989    def negate(self): 
    8090        """ 
    81         Negate the sense of the root connector. 
     91        Negate the sense of the root connector. This reorganises the children 
     92        so that the current node has a single child: a negated node containing 
     93        all the previous children. This slightly odd construction makes adding 
     94        new children behave more intuitively. 
    8295 
    8396        Interpreting the meaning of this negate is up to client code. This 
  • django/branches/queryset-refactor/tests/regressiontests/queries/models.py

    r7174 r7217  
    313313[<Author: a1>] 
    314314 
    315 Bug #5324 
     315Bug #5324, #6704 
    316316>>> Item.objects.filter(tags__name='t4') 
    317317[<Item: four>] 
     
    340340>>> len([x[2][2] for x in qs.query.alias_map.values() if x[2][2] == query.LOUTER]) 
    3413411 
     342 
     343The previous changes shouldn't affect nullable foreign key joins. 
     344>>> Tag.objects.filter(parent__isnull=True).order_by('name') 
     345[<Tag: t1>] 
     346>>> Tag.objects.exclude(parent__isnull=True).order_by('name') 
     347[<Tag: t2>, <Tag: t3>, <Tag: t4>, <Tag: t5>] 
     348>>> Tag.objects.exclude(Q(parent__name='t1') | Q(parent__isnull=True)).order_by('name') 
     349[<Tag: t4>, <Tag: t5>] 
     350>>> Tag.objects.exclude(Q(parent__isnull=True) | Q(parent__name='t1')).order_by('name') 
     351[<Tag: t4>, <Tag: t5>] 
     352>>> Tag.objects.exclude(Q(parent__parent__isnull=True)).order_by('name') 
     353[<Tag: t4>, <Tag: t5>] 
     354>>> Tag.objects.filter(~Q(parent__parent__isnull=True)).order_by('name') 
     355[<Tag: t4>, <Tag: t5>] 
    342356 
    343357Bug #2091