Changeset 6958
- Timestamp:
- 12/19/07 04:58:26 (9 months ago)
- Files:
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/branches/queryset-refactor/django/db/models/sql/query.py
r6957 r6958 53 53 ALIAS_REFCOUNT = 1 54 54 ALIAS_JOIN = 2 55 ALIAS_NULLABLE=3 55 56 56 57 # How many results to expect from a cursor.execute call … … 511 512 if not alias: 512 513 alias = self.join((None, opts.db_table, None, None)) 513 field, target, opts, joins , unused2 = self.setup_joins(pieces, opts,514 alias,False)514 field, target, opts, joins = self.setup_joins(pieces, opts, alias, 515 False) 515 516 alias = joins[-1][-1] 516 517 col = target.column … … 569 570 570 571 def promote_alias(self, alias): 571 """ Promotes the join type of an alias to an outer join. """ 572 self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER 572 """ 573 Promotes the join type of an alias to an outer join if it's possible 574 for the join to contain NULL values on the left. 575 576 Returns True if the aliased join was promoted. 577 """ 578 if self.alias_map[alias][ALIAS_NULLABLE]: 579 self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER 580 return True 581 return False 573 582 574 583 def join(self, (lhs, table, lhs_col, col), always_create=False, 575 exclusions=(), promote=False, outer_if_first=False ):584 exclusions=(), promote=False, outer_if_first=False, nullable=False): 576 585 """ 577 586 Returns an alias for a join between 'table' and 'lhs' on the given … … 594 603 LOUTER join type. This is used when joining certain types of querysets 595 604 and Q-objects together. 605 606 If 'nullable' is True, the join can potentially involve NULL values and 607 is a candidate for promotion (to "left outer") when combining querysets. 596 608 """ 597 609 if lhs is None: … … 611 623 if alias not in exclusions: 612 624 self.ref_alias(alias) 613 if promote :625 if promote and self.alias_map[alias][ALIAS_NULLABLE]: 614 626 self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = \ 615 627 self.LOUTER … … 632 644 join[JOIN_TYPE] = None 633 645 self.alias_map[alias][ALIAS_JOIN] = join 646 self.alias_map[alias][ALIAS_NULLABLE] = nullable 634 647 self.join_map.setdefault(t_ident, []).append(alias) 635 648 self.rev_join_map[alias] = t_ident … … 709 722 710 723 try: 711 field, target, unused, join_list , nullable = self.setup_joins(parts,712 opts,alias, (connector == AND))724 field, target, unused, join_list = self.setup_joins(parts, opts, 725 alias, (connector == AND)) 713 726 except TypeError, e: 714 727 if len(parts) != 1 or parts[0] not in self.extra_select: … … 753 766 if join == table and self.alias_map[join][ALIAS_REFCOUNT] > 1: 754 767 continue 755 # FIXME: Don't have to promote here (and in the other places in756 # this block) if the join isn't nullable. So I should be757 # checking this before promoting (avoiding left outer joins is758 # important).759 768 self.promote_alias(join) 760 769 if table != join: … … 772 781 if negate: 773 782 flag = False 774 for pos, null in enumerate(nullable): 775 if not null: 776 continue 777 flag = True 778 for join in join_list[pos]: 779 self.promote_alias(join) 783 for seq in join_list: 784 for join in seq: 785 if self.promote_alias(join): 786 flag = True 780 787 self.where.negate() 781 788 if flag: … … 798 805 else: 799 806 subtree = False 807 connector = AND 800 808 for child in q_object.children: 801 809 if isinstance(child, Node): 802 self.where.start_subtree( q_object.connector)810 self.where.start_subtree(connector) 803 811 self.add_q(child) 804 812 self.where.end_subtree() 805 813 else: 806 self.add_filter(child, q_object.connector, q_object.negated) 814 self.add_filter(child, connector, q_object.negated) 815 connector = q_object.connector 807 816 if subtree: 808 817 self.where.end_subtree() … … 823 832 """ 824 833 joins = [[alias]] 825 nullable = [False]826 834 for pos, name in enumerate(names): 827 835 if name == 'pk': … … 857 865 858 866 int_alias = self.join((alias, table1, from_col1, to_col1), 859 dupe_multis )867 dupe_multis, nullable=True) 860 868 alias = self.join((int_alias, table2, from_col2, to_col2), 861 dupe_multis )869 dupe_multis, nullable=True) 862 870 joins.append([int_alias, alias]) 863 nullable.append(field.null)864 871 elif field.rel: 865 872 # One-to-one or many-to-one field … … 875 882 opts, target) 876 883 877 alias = self.join((alias, table, from_col, to_col)) 884 alias = self.join((alias, table, from_col, to_col), 885 nullable=field.null) 878 886 joins.append([alias]) 879 nullable.append(field.null)880 887 else: 881 888 target = field … … 884 891 orig_field = field 885 892 field = field.field 886 nullable.append(True)887 893 if m2m: 888 894 # Many-to-many field defined on the target model. … … 904 910 905 911 int_alias = self.join((alias, table1, from_col1, to_col1), 906 dupe_multis )912 dupe_multis, nullable=True) 907 913 alias = self.join((int_alias, table2, from_col2, to_col2), 908 dupe_multis )914 dupe_multis, nullable=True) 909 915 joins.append([int_alias, alias]) 910 916 else: … … 924 930 925 931 alias = self.join((alias, table, from_col, to_col), 926 dupe_multis )932 dupe_multis, nullable=True) 927 933 joins.append([alias]) 928 934 … … 930 936 raise TypeError("Join on field %r not permitted." % name) 931 937 932 return field, target, opts, joins , nullable938 return field, target, opts, joins 933 939 934 940 def set_limits(self, low=None, high=None): django/branches/queryset-refactor/tests/regressiontests/queries/models.py
r6957 r6958 309 309 3 310 310 311 Similarly, when one of the joins cannot possibly, ever, involve NULL values (Author -> ExtraInfo, in the following), it should never be promoted to a left outer join. So hte following query should only involve one "left outer" join (Author -> Item is 0-to-many). 312 >>> qs = Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3)) 313 >>> len([x[2][2] for x in qs.query.alias_map.values() if x[2][2] == query.LOUTER]) 314 1 315 311 316 Bug #2091 312 317 >>> t = Tag.objects.get(name='t4')
