Loading django/db/models/sql/datastructures.py +15 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,21 @@ the SQL domain. """ class Col(object): def __init__(self, alias, col): self.alias = alias self.col = col def as_sql(self, qn, connection): return '%s.%s' % (qn(self.alias), self.col), [] def prepare(self): return self def relabeled_clone(self, relabels): return self.__class__(relabels.get(self.alias, self.alias), self.col) class EmptyResultSet(Exception): pass Loading django/db/models/sql/query.py +15 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ from django.db.models.related import PathInfo from django.db.models.sql import aggregates as base_aggregates_module from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, JoinInfo, SelectInfo) from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin, Col from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode, ExtraWhere, AND, OR, EmptyWhere) Loading Loading @@ -820,6 +820,9 @@ class Query(object): conflict. Even tables that previously had no alias will get an alias after this call. """ if self.alias_prefix != outer_query.alias_prefix: # No clashes between self and outer query should be possible. return self.alias_prefix = chr(ord(self.alias_prefix) + 1) while self.alias_prefix in self.subq_aliases: self.alias_prefix = chr(ord(self.alias_prefix) + 1) Loading Loading @@ -1514,9 +1517,19 @@ class Query(object): # since we are adding a IN <subquery> clause. This prevents the # database from tripping over IN (...,NULL,...) selects and returning # nothing if self.is_nullable(query.select[0].field): alias, col = query.select[0].col if self.is_nullable(query.select[0].field): query.where.add((Constraint(alias, col, query.select[0].field), 'isnull', False), AND) if alias in can_reuse: pk = query.select[0].field.model._meta.pk # Need to add a restriction so that outer query's filters are in effect for # the subquery, too. query.bump_prefix(self) query.where.add( (Constraint(query.select[0].col[0], pk.column, pk), 'exact', Col(alias, pk.column)), AND ) condition = self.build_filter( ('%s__in' % trimmed_prefix, query), Loading tests/queries/models.py +19 −0 Original line number Diff line number Diff line Loading @@ -544,3 +544,22 @@ class Ticket21203Parent(models.Model): class Ticket21203Child(models.Model): childid = models.AutoField(primary_key=True) parent = models.ForeignKey(Ticket21203Parent) class Person(models.Model): name = models.CharField(max_length=128) @python_2_unicode_compatible class Company(models.Model): name = models.CharField(max_length=128) employees = models.ManyToManyField(Person, related_name='employers', through='Employment') def __str__(self): return self.name class Employment(models.Model): employer = models.ForeignKey(Company) employee = models.ForeignKey(Person) title = models.CharField(max_length=128) tests/queries/tests.py +37 −2 Original line number Diff line number Diff line Loading @@ -25,7 +25,8 @@ from .models import ( ModelA, ModelB, ModelC, ModelD, Responsibility, Job, JobResponsibilities, BaseA, FK1, Identifier, Program, Channel, Page, Paragraph, Chapter, Book, MyObject, Order, OrderItem, SharedConnection, Task, Staff, StaffUser, CategoryRelationship, Ticket21203Parent, Ticket21203Child) CategoryRelationship, Ticket21203Parent, Ticket21203Child, Person, Company, Employment) class BaseQuerysetTest(TestCase): def assertValueQuerysetEqual(self, qs, values): Loading Loading @@ -2352,7 +2353,7 @@ class DefaultValuesInsertTest(TestCase): except TypeError: self.fail("Creation of an instance of a model with only the PK field shouldn't error out after bulk insert refactoring (#17056)") class ExcludeTest(TestCase): class ExcludeTests(TestCase): def setUp(self): f1 = Food.objects.create(name='apples') Food.objects.create(name='oranges') Loading @@ -2375,6 +2376,37 @@ class ExcludeTest(TestCase): Responsibility.objects.exclude(jobs__name='Manager'), ['<Responsibility: Programming>']) def test_ticket14511(self): alex = Person.objects.get_or_create(name='Alex')[0] jane = Person.objects.get_or_create(name='Jane')[0] oracle = Company.objects.get_or_create(name='Oracle')[0] google = Company.objects.get_or_create(name='Google')[0] microsoft = Company.objects.get_or_create(name='Microsoft')[0] intel = Company.objects.get_or_create(name='Intel')[0] def employ(employer, employee, title): Employment.objects.get_or_create(employee=employee, employer=employer, title=title) employ(oracle, alex, 'Engineer') employ(oracle, alex, 'Developer') employ(google, alex, 'Engineer') employ(google, alex, 'Manager') employ(microsoft, alex, 'Manager') employ(intel, alex, 'Manager') employ(microsoft, jane, 'Developer') employ(intel, jane, 'Manager') alex_tech_employers = alex.employers.filter( employment__title__in=('Engineer', 'Developer')).distinct().order_by('name') self.assertQuerysetEqual(alex_tech_employers, [google, oracle], lambda x: x) alex_nontech_employers = alex.employers.exclude( employment__title__in=('Engineer', 'Developer')).distinct().order_by('name') self.assertQuerysetEqual(alex_nontech_employers, [google, intel, microsoft], lambda x: x) class ExcludeTest17600(TestCase): """ Some regressiontests for ticket #17600. Some of these likely duplicate Loading Loading @@ -3135,3 +3167,6 @@ class ValuesJoinPromotionTests(TestCase): def test_non_nullable_fk_not_promoted(self): qs = ObjectB.objects.values('objecta__name') self.assertTrue(' INNER JOIN ' in str(qs.query)) class ExcludeJoinTest(TestCase): Loading
django/db/models/sql/datastructures.py +15 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,21 @@ the SQL domain. """ class Col(object): def __init__(self, alias, col): self.alias = alias self.col = col def as_sql(self, qn, connection): return '%s.%s' % (qn(self.alias), self.col), [] def prepare(self): return self def relabeled_clone(self, relabels): return self.__class__(relabels.get(self.alias, self.alias), self.col) class EmptyResultSet(Exception): pass Loading
django/db/models/sql/query.py +15 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ from django.db.models.related import PathInfo from django.db.models.sql import aggregates as base_aggregates_module from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, JoinInfo, SelectInfo) from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin, Col from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode, ExtraWhere, AND, OR, EmptyWhere) Loading Loading @@ -820,6 +820,9 @@ class Query(object): conflict. Even tables that previously had no alias will get an alias after this call. """ if self.alias_prefix != outer_query.alias_prefix: # No clashes between self and outer query should be possible. return self.alias_prefix = chr(ord(self.alias_prefix) + 1) while self.alias_prefix in self.subq_aliases: self.alias_prefix = chr(ord(self.alias_prefix) + 1) Loading Loading @@ -1514,9 +1517,19 @@ class Query(object): # since we are adding a IN <subquery> clause. This prevents the # database from tripping over IN (...,NULL,...) selects and returning # nothing if self.is_nullable(query.select[0].field): alias, col = query.select[0].col if self.is_nullable(query.select[0].field): query.where.add((Constraint(alias, col, query.select[0].field), 'isnull', False), AND) if alias in can_reuse: pk = query.select[0].field.model._meta.pk # Need to add a restriction so that outer query's filters are in effect for # the subquery, too. query.bump_prefix(self) query.where.add( (Constraint(query.select[0].col[0], pk.column, pk), 'exact', Col(alias, pk.column)), AND ) condition = self.build_filter( ('%s__in' % trimmed_prefix, query), Loading
tests/queries/models.py +19 −0 Original line number Diff line number Diff line Loading @@ -544,3 +544,22 @@ class Ticket21203Parent(models.Model): class Ticket21203Child(models.Model): childid = models.AutoField(primary_key=True) parent = models.ForeignKey(Ticket21203Parent) class Person(models.Model): name = models.CharField(max_length=128) @python_2_unicode_compatible class Company(models.Model): name = models.CharField(max_length=128) employees = models.ManyToManyField(Person, related_name='employers', through='Employment') def __str__(self): return self.name class Employment(models.Model): employer = models.ForeignKey(Company) employee = models.ForeignKey(Person) title = models.CharField(max_length=128)
tests/queries/tests.py +37 −2 Original line number Diff line number Diff line Loading @@ -25,7 +25,8 @@ from .models import ( ModelA, ModelB, ModelC, ModelD, Responsibility, Job, JobResponsibilities, BaseA, FK1, Identifier, Program, Channel, Page, Paragraph, Chapter, Book, MyObject, Order, OrderItem, SharedConnection, Task, Staff, StaffUser, CategoryRelationship, Ticket21203Parent, Ticket21203Child) CategoryRelationship, Ticket21203Parent, Ticket21203Child, Person, Company, Employment) class BaseQuerysetTest(TestCase): def assertValueQuerysetEqual(self, qs, values): Loading Loading @@ -2352,7 +2353,7 @@ class DefaultValuesInsertTest(TestCase): except TypeError: self.fail("Creation of an instance of a model with only the PK field shouldn't error out after bulk insert refactoring (#17056)") class ExcludeTest(TestCase): class ExcludeTests(TestCase): def setUp(self): f1 = Food.objects.create(name='apples') Food.objects.create(name='oranges') Loading @@ -2375,6 +2376,37 @@ class ExcludeTest(TestCase): Responsibility.objects.exclude(jobs__name='Manager'), ['<Responsibility: Programming>']) def test_ticket14511(self): alex = Person.objects.get_or_create(name='Alex')[0] jane = Person.objects.get_or_create(name='Jane')[0] oracle = Company.objects.get_or_create(name='Oracle')[0] google = Company.objects.get_or_create(name='Google')[0] microsoft = Company.objects.get_or_create(name='Microsoft')[0] intel = Company.objects.get_or_create(name='Intel')[0] def employ(employer, employee, title): Employment.objects.get_or_create(employee=employee, employer=employer, title=title) employ(oracle, alex, 'Engineer') employ(oracle, alex, 'Developer') employ(google, alex, 'Engineer') employ(google, alex, 'Manager') employ(microsoft, alex, 'Manager') employ(intel, alex, 'Manager') employ(microsoft, jane, 'Developer') employ(intel, jane, 'Manager') alex_tech_employers = alex.employers.filter( employment__title__in=('Engineer', 'Developer')).distinct().order_by('name') self.assertQuerysetEqual(alex_tech_employers, [google, oracle], lambda x: x) alex_nontech_employers = alex.employers.exclude( employment__title__in=('Engineer', 'Developer')).distinct().order_by('name') self.assertQuerysetEqual(alex_nontech_employers, [google, intel, microsoft], lambda x: x) class ExcludeTest17600(TestCase): """ Some regressiontests for ticket #17600. Some of these likely duplicate Loading Loading @@ -3135,3 +3167,6 @@ class ValuesJoinPromotionTests(TestCase): def test_non_nullable_fk_not_promoted(self): qs = ObjectB.objects.values('objecta__name') self.assertTrue(' INNER JOIN ' in str(qs.query)) class ExcludeJoinTest(TestCase):