Ticket #6422: distinct_on.2.diff
File distinct_on.2.diff, 11.9 KB (added by , 13 years ago) |
---|
-
django/db/backends/__init__.py
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index b64fb01..ef301b7 100644
a b class BaseDatabaseFeatures(object): 342 342 supports_stddev = None 343 343 can_introspect_foreign_keys = None 344 344 345 # Support for the DISTINCT ON clause 346 can_distinct_on_fields = False 347 345 348 def __init__(self, connection): 346 349 self.connection = connection 347 350 … … class BaseDatabaseOperations(object): 495 498 """ 496 499 raise NotImplementedError('Full-text search is not implemented for this database backend') 497 500 501 def distinct(self, db_table, fields): 502 """ 503 Returns an SQL DISTINCT clause which removes duplicate rows from the 504 result set. If any fields are given, only the given fields are being 505 checked for duplicates. 506 """ 507 if fields: 508 raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') 509 else: 510 return 'DISTINCT' 511 498 512 def last_executed_query(self, cursor, sql, params): 499 513 """ 500 514 Returns a string of the query last executed by the given cursor, with -
django/db/backends/postgresql_psycopg2/base.py
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 67e2877..db7acc5 100644
a b class DatabaseFeatures(BaseDatabaseFeatures): 72 72 can_defer_constraint_checks = True 73 73 has_select_for_update = True 74 74 has_select_for_update_nowait = True 75 can_distinct_on_fields = True 75 76 76 77 77 78 class DatabaseWrapper(BaseDatabaseWrapper): -
django/db/backends/postgresql_psycopg2/operations.py
diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index 3315913..e24b0aa 100644
a b class DatabaseOperations(BaseDatabaseOperations): 201 201 202 202 return 63 203 203 204 def distinct(self, db_table, fields): 205 if fields: 206 table_name = self.quote_name(db_table) 207 fields = [table_name + "." + self.quote_name(field) for field in fields] 208 return 'DISTINCT ON (%s)' % ', '.join(fields) 209 else: 210 return 'DISTINCT' 211 204 212 def last_executed_query(self, cursor, sql, params): 205 213 # http://initd.org/psycopg/docs/cursor.html#cursor.query 206 214 # The query attribute is a Psycopg extension to the DB API 2.0. -
django/db/models/query.py
diff --git a/django/db/models/query.py b/django/db/models/query.py index 6a6a829..9e90c9a 100644
a b class QuerySet(object): 668 668 obj.query.add_ordering(*field_names) 669 669 return obj 670 670 671 def distinct(self, true_or_false=True):671 def distinct(self, *field_names): 672 672 """ 673 673 Returns a new QuerySet instance that will select only distinct results. 674 674 """ 675 675 obj = self._clone() 676 obj.query.distinct = true_or_false 676 obj.query.add_distinct_fields(field_names) 677 obj.query.distinct = True 678 677 679 return obj 678 680 679 681 def extra(self, select=None, where=None, params=None, tables=None, … … class EmptyQuerySet(QuerySet): 1093 1095 """ 1094 1096 return self 1095 1097 1096 def distinct(self, true_or_false=True):1098 def distinct(self, fields=None): 1097 1099 """ 1098 1100 Always returns EmptyQuerySet. 1099 1101 """ -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 841ec12..9d22f4a 100644
a b class SQLCompiler(object): 74 74 params.extend(val[1]) 75 75 76 76 result = ['SELECT'] 77 77 78 if self.query.distinct: 78 result.append('DISTINCT') 79 distinct_sql = self.connection.ops.distinct( 80 self.query.model._meta.db_table, self.query.distinct_fields) 81 result.append(distinct_sql) 82 79 83 result.append(', '.join(out_cols + self.query.ordering_aliases)) 80 84 81 85 result.append('FROM') -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 99663b6..662fc25 100644
a b class Query(object): 125 125 self.order_by = [] 126 126 self.low_mark, self.high_mark = 0, None # Used for offset/limit 127 127 self.distinct = False 128 self.distinct_fields = None 128 129 self.select_for_update = False 129 130 self.select_for_update_nowait = False 130 131 self.select_related = False … … class Query(object): 256 257 obj.order_by = self.order_by[:] 257 258 obj.low_mark, obj.high_mark = self.low_mark, self.high_mark 258 259 obj.distinct = self.distinct 260 obj.distinct_fields = self.distinct_fields 259 261 obj.select_for_update = self.select_for_update 260 262 obj.select_for_update_nowait = self.select_for_update_nowait 261 263 obj.select_related = self.select_related … … class Query(object): 384 386 Performs a COUNT() query using the current filter constraints. 385 387 """ 386 388 obj = self.clone() 387 if len(self.select) > 1 or self.aggregate_select :389 if len(self.select) > 1 or self.aggregate_select or (self.distinct and self.distinct_fields): 388 390 # If a select clause exists, then the query has already started to 389 391 # specify the columns that are to be returned. 390 392 # In this case, we need to use a subquery to evaluate the count. … … class Query(object): 1556 1558 self.select = [] 1557 1559 self.select_fields = [] 1558 1560 1561 def add_distinct_fields(self, field_names): 1562 self.distinct_fields = [] 1563 opts = self.get_meta() 1564 1565 for name in field_names: 1566 field, source, opts, join_list, last, _ = self.setup_joins( 1567 name.split(LOOKUP_SEP), opts, self.get_initial_alias(), False) 1568 self.distinct_fields.append(field.column) 1569 1559 1570 def add_fields(self, field_names, allow_m2m=True): 1560 1571 """ 1561 1572 Adds the given (model) fields to the select set. The field names are -
docs/ref/models/querysets.txt
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 2bd813d..9172569 100644
a b Though you usually won't create one manually -- you'll go through a 139 139 clause or a default ordering on the model. ``False`` otherwise. 140 140 141 141 .. attribute:: db 142 142 143 143 The database that will be used if this query is executed now. 144 144 145 145 .. note:: … … undefined afterward). 345 345 distinct 346 346 ~~~~~~~~ 347 347 348 .. method:: distinct( )348 .. method:: distinct(*fields) 349 349 350 350 Returns a new ``QuerySet`` that uses ``SELECT DISTINCT`` in its SQL query. This 351 351 eliminates duplicate rows from the query results. … … don't introduce the possibility of duplicate result rows. However, if your 356 356 query spans multiple tables, it's possible to get duplicate results when a 357 357 ``QuerySet`` is evaluated. That's when you'd use ``distinct()``. 358 358 359 .. versionadded:: 1.4 360 ``distinct()`` takes optional positional arguments, ``*fields``, which specify 361 field names to which the ``DISTINCT`` should be limited. This translates to 362 a ``SELECT DISTINCT ON`` SQL query. Note that this ``DISTINCT ON`` query is 363 only available in PostgreSQL. 364 359 365 .. note:: 360 366 Any fields used in an :meth:`order_by` call are included in the SQL 361 367 ``SELECT`` columns. This can sometimes lead to unexpected results when -
tests/regressiontests/queries/models.py
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index d1e5e6e..9cf3a09 100644
a b class Celebrity(models.Model): 208 208 name = models.CharField("Name", max_length=20) 209 209 greatest_fan = models.ForeignKey("Fan", null=True, unique=True) 210 210 211 def __unicode__(self): 212 return self.name 213 211 214 class TvChef(Celebrity): 212 215 pass 213 216 … … class ObjectC(models.Model): 317 320 318 321 def __unicode__(self): 319 322 return self.name 323 324 325 class Staff(models.Model): 326 name = models.CharField(max_length=50) 327 organisation = models.CharField(max_length=100) 328 tags = models.ManyToManyField(Tag, through='StaffTag') 329 330 def __unicode__(self): 331 return self.name 332 333 class StaffTag(models.Model): 334 staff = models.ForeignKey(Staff) 335 tag = models.ForeignKey(Tag) 336 337 def __unicode__(self): 338 return u"%s -> %s" % (self.tag, self.staff) 339 -
tests/regressiontests/queries/tests.py
diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 31856ba..619f755 100644
a b from models import (Annotation, Article, Author, Celebrity, Child, Cover, Detail 15 15 DumbCategory, ExtraInfo, Fan, Item, LeafA, LoopX, LoopZ, ManagedModel, 16 16 Member, NamedCategory, Note, Number, Plaything, PointerA, Ranking, Related, 17 17 Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten, Node, ObjectA, ObjectB, 18 ObjectC )18 ObjectC, Staff, StaffTag) 19 19 20 20 21 21 class BaseQuerysetTest(TestCase): … … class ConditionalTests(BaseQuerysetTest): 1606 1606 t4 = Tag.objects.create(name='t4', parent=t3) 1607 1607 t5 = Tag.objects.create(name='t5', parent=t3) 1608 1608 1609 p1_o1 = Staff.objects.create(name="p1", organisation="o1") 1610 p2_o1 = Staff.objects.create(name="p2", organisation="o1") 1611 p3_o1 = Staff.objects.create(name="p3", organisation="o1") 1612 p1_o2 = Staff.objects.create(name="p1", organisation="o2") 1613 1614 StaffTag.objects.create(staff=p1_o1, tag=t1) 1615 StaffTag.objects.create(staff=p1_o1, tag=t1) 1616 1617 celeb1 = Celebrity.objects.create(name="c1") 1618 celeb2 = Celebrity.objects.create(name="c2") 1619 1620 self.fan1 = Fan.objects.create(fan_of=celeb1) 1621 self.fan2 = Fan.objects.create(fan_of=celeb1) 1622 self.fan3 = Fan.objects.create(fan_of=celeb2) 1623 1609 1624 # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed 1610 1625 # (Python issue 1242657), so these cases return an empty list, rather than 1611 1626 # raising an exception. Not a lot we can do about that, unfortunately, due to … … class ConditionalTests(BaseQuerysetTest): 1677 1692 2500 1678 1693 ) 1679 1694 1695 @skipUnlessDBFeature('can_distinct_on_fields') 1696 def test_ticket6422(self): 1697 # (qset, expected) tuples 1698 qsets = ( 1699 ( 1700 Staff.objects.distinct().order_by('name'), 1701 ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], 1702 ), 1703 ( 1704 Staff.objects.distinct('name').order_by('name'), 1705 ['<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], 1706 ), 1707 ( 1708 Staff.objects.distinct('organisation').order_by('organisation', 'name'), 1709 ['<Staff: p1>', '<Staff: p1>'], 1710 ), 1711 ( 1712 Staff.objects.distinct('name', 'organisation').order_by('name', 'organisation'), 1713 ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], 1714 ), 1715 ( 1716 Celebrity.objects.filter(fan__in=[self.fan1, self.fan2, self.fan3]).\ 1717 distinct('name').order_by('name'), 1718 ['<Celebrity: c1>', '<Celebrity: c2>'], 1719 ), 1720 ( 1721 StaffTag.objects.distinct('staff','tag'), 1722 ['<StaffTag: t1 -> p1>'], 1723 ), 1724 ) 1725 1726 for qset, expected in qsets: 1727 self.assertQuerysetEqual(qset, expected) 1728 self.assertEqual(qset.count(), len(expected)) 1729 1730 # and check the fieldlookup 1731 self.assertRaises( 1732 FieldError, 1733 lambda: Staff.objects.distinct('shrubbery') 1734 ) 1735 1736 1680 1737 class UnionTests(unittest.TestCase): 1681 1738 """ 1682 1739 Tests for the union of two querysets. Bug #12252.