Changeset 7170
- Timestamp:
- 02/28/08 06:57:10 (9 months ago)
- Files:
-
- django/branches/queryset-refactor/django/db/models/fields/related.py (modified) (1 diff)
- django/branches/queryset-refactor/django/db/models/sql/query.py (modified) (14 diffs)
- django/branches/queryset-refactor/django/db/models/sql/where.py (modified) (2 diffs)
- django/branches/queryset-refactor/tests/modeltests/many_to_many/models.py (modified) (1 diff)
- django/branches/queryset-refactor/tests/regressiontests/queries/models.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/branches/queryset-refactor/django/db/models/fields/related.py
r7142 r7170 101 101 return v 102 102 103 if hasattr(value, 'as_sql'): 104 sql, params = value.as_sql() 105 return ('(%s)' % sql), params 103 106 if lookup_type == 'exact': 104 107 return [pk_trace(value)] django/branches/queryset-refactor/django/db/models/sql/query.py
r7165 r7170 46 46 self.default_ordering = True 47 47 self.standard_ordering = True 48 self.start_meta = None 48 49 49 50 # SQL-related attributes … … 81 82 sql, params = self.as_sql() 82 83 return sql % params 84 85 def __deepcopy__(self, memo): 86 result= self.clone() 87 memo[id(self)] = result 88 return result 89 90 def get_meta(self): 91 """ 92 Returns the Options instance (the model._meta) from which to start 93 processing. Normally, this is self.model._meta, but it can change. 94 """ 95 if self.start_meta: 96 return self.start_meta 97 return self.model._meta 83 98 84 99 def quote_name_unless_alias(self, name): … … 115 130 obj.default_ordering = self.default_ordering 116 131 obj.standard_ordering = self.standard_ordering 132 obj.start_meta = self.start_meta 117 133 obj.select = self.select[:] 118 134 obj.tables = self.tables[:] … … 385 401 alias_str = '' 386 402 name = alias 387 if join_type :403 if join_type and not first: 388 404 result.append('%s %s%s ON (%s.%s = %s.%s)' 389 405 % (join_type, qn(name), alias_str, qn(lhs), … … 650 666 return 651 667 if not opts: 652 opts = self. model._meta668 opts = self.get_meta() 653 669 root_alias = self.tables[0] 654 670 self.select.extend([(root_alias, f.column) for f in opts.fields]) … … 682 698 used, next, restricted) 683 699 684 def add_filter(self, filter_expr, connector=AND, negate=False): 685 """ 686 Add a single filter to the query. 700 def add_filter(self, filter_expr, connector=AND, negate=False, trim=False): 701 """ 702 Add a single filter to the query. The 'filter_expr' is a pair: 703 (filter_string, value). E.g. ('name__contains', 'fred') 704 705 If 'negate' is True, this is an exclude() filter. If 'trim' is True, we 706 automatically trim the final join group (used internally when 707 constructing nested queries). 687 708 """ 688 709 arg, value = filter_expr … … 707 728 value = value() 708 729 709 opts = self. model._meta730 opts = self.get_meta() 710 731 alias = self.join((None, opts.db_table, None, None)) 711 712 field, target, opts, join_list, = self.setup_joins(parts, opts, 713 alias, (connector == AND)) 714 col = target.column 732 allow_many = trim or not negate 733 734 result = self.setup_joins(parts, opts, alias, (connector == AND), 735 allow_many) 736 if isinstance(result, int): 737 self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:result])) 738 return 739 field, target, opts, join_list = result 740 if trim and len(join_list) > 1: 741 extra = join_list[-1] 742 join_list = join_list[:-1] 743 col = self.alias_map[extra[0]][ALIAS_JOIN][LHS_JOIN_COL] 744 for alias in extra: 745 self.unref_alias(alias) 746 else: 747 col = target.column 715 748 alias = join_list[-1][-1] 716 749 … … 730 763 # If the comparison is against NULL, we need to use a left outer 731 764 # join when connecting to the previous model. We make that 732 # adjustment here. We don't do this unless needed becauseit's less765 # adjustment here. We don't do this unless needed as it's less 733 766 # efficient at the database level. 734 767 self.promote_alias(join_list[-1][0]) … … 768 801 self.where.negate() 769 802 if flag: 803 # XXX: Change this to the field we joined against to allow 804 # for node sharing and where-tree optimisation? 770 805 self.where.add([alias, col, field, 'isnull', True], OR) 771 806 … … 798 833 self.where.end_subtree() 799 834 800 def setup_joins(self, names, opts, alias, dupe_multis ):835 def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True): 801 836 """ 802 837 Compute the necessary table joins for the passage through the fields … … 808 843 809 844 Returns the final field involved in the join, the target database 810 column (used for any 'where' constraint), the final 'opts' value, the 811 list of tables joined and a list indicating whether or not each join 812 can be null. 845 column (used for any 'where' constraint), the final 'opts' value and the 846 list of tables joined. 813 847 """ 814 848 joins = [[alias]] … … 823 857 raise FieldError("Cannot resolve keyword %r into field. " 824 858 "Choices are: %s" % (name, ", ".join(names))) 859 if not allow_many and (m2m or not direct): 860 for join in joins: 861 for alias in join: 862 self.unref_alias(alias) 863 return pos + 1 825 864 if model: 826 865 # The field lives on a base class of the current model. … … 930 969 return field, target, opts, joins 931 970 971 def split_exclude(self, filter_expr, prefix): 972 """ 973 When doing an exclude against any kind of N-to-many relation, we need 974 to use a subquery. This method constructs the nested query, given the 975 original exclude filter (filter_expr) and the portion up to the first 976 N-to-many relation field. 977 """ 978 query = Query(self.model, self.connection) 979 query.add_filter(filter_expr) 980 query.set_start(prefix) 981 query.clear_ordering(True) 982 self.add_filter(('%s__in' % prefix, query), negate=True, trim=True) 983 932 984 def set_limits(self, low=None, high=None): 933 985 """ … … 1048 1100 self.select_related = field_dict 1049 1101 1102 def set_start(self, start): 1103 """ 1104 Sets the table from which to start joining. The start position is 1105 specified by the related attribute from the base model. This will 1106 automatically set to the select column to be the column linked from the 1107 previous table. 1108 1109 This method is primarily for internal use and the error checking isn't 1110 as friendly as add_filter(). Mostly useful for querying directly 1111 against the join table of many-to-many relation in a subquery. 1112 """ 1113 opts = self.model._meta 1114 alias = self.join((None, opts.db_table, None, None)) 1115 field, col, opts, joins = self.setup_joins(start.split(LOOKUP_SEP), 1116 opts, alias, False) 1117 alias = joins[-1][0] 1118 self.select = [(alias, self.alias_map[alias][ALIAS_JOIN][RHS_JOIN_COL])] 1119 self.start_meta = opts 1120 1121 # The call to setup_joins add an extra reference to everything in 1122 # joins. So we need to unref everything once, and everything prior to 1123 # the final join a second time. 1124 for join in joins[:-1]: 1125 for alias in join: 1126 self.unref_alias(alias) 1127 self.unref_alias(alias) 1128 for alias in joins[-1]: 1129 self.unref_alias(alias) 1130 1050 1131 def execute_sql(self, result_type=MULTI): 1051 1132 """ django/branches/queryset-refactor/django/db/models/sql/where.py
r7169 r7170 106 106 cast_sql = '%s' 107 107 108 format = "%s %%s" % connection.ops.lookup_cast(lookup_type)109 108 params = field.get_db_prep_lookup(lookup_type, value) 109 if isinstance(params, tuple): 110 extra, params = params 111 else: 112 extra = '' 110 113 111 114 if lookup_type in connection.operators: 115 format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type), 116 extra) 112 117 return (format % (field_sql, 113 118 connection.operators[lookup_type] % cast_sql), params) … … 116 121 if not value: 117 122 raise EmptyResultSet 123 if extra: 124 return ('%s IN %s' % (field_sql, extra), params) 118 125 return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))), 119 126 params) 120 127 elif lookup_type in ('range', 'year'): 121 return ('%s BETWEEN %%s and %%s' % field_sql, 122 params) 128 return ('%s BETWEEN %%s and %%s' % field_sql, params) 123 129 elif lookup_type in ('month', 'day'): 124 130 return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type, django/branches/queryset-refactor/tests/modeltests/many_to_many/models.py
r5876 r7170 127 127 [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>] 128 128 129 # Excluding a related item works as you would expect, too (although the SQL 130 # involved is a little complex). 131 >>> Article.objects.exclude(publications=p2) 132 [<Article: Django lets you build Web apps easily>] 133 129 134 # If we delete a Publication, its Articles won't be able to access it. 130 135 >>> p1.delete() django/branches/queryset-refactor/tests/regressiontests/queries/models.py
r7163 r7170 323 323 [<Author: a2>, <Author: a3>, <Author: a4>] 324 324 325 326 # Excluding across a m2m relation when there is more than one related object 327 # associated was problematic. 328 >>> Item.objects.exclude(tags__name='t1').order_by('name') 329 [<Item: four>, <Item: three>] 330 >>> Item.objects.exclude(tags__name='t1').exclude(tags__name='t4') 331 [<Item: three>] 332 325 333 # Excluding from a relation that cannot be NULL should not use outer joins. 326 334 >>> query = Item.objects.exclude(creator__in=[a1, a2]).query 327 335 >>> query.LOUTER not in [x[2][2] for x in query.alias_map.values()] 328 336 True 329 330 # When only one of the joins is nullable (here, the Author -> Item join), we331 # should only get outer joins after that point (one, in this case). We also332 # show that three tables (so, two joins) are involved.333 >>> qs = Report.objects.exclude(creator__item__name='one')334 >>> list(qs)335 [<Report: r2>]336 >>> len([x[2][2] for x in qs.query.alias_map.values() if x[2][2] == query.LOUTER])337 1338 >>> len(qs.query.alias_map)339 3340 337 341 338 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).
