Skip to content

Commit 3610121

Browse files
committed
Add regression test codifying behavior for outer join population
1 parent 0667d1a commit 3610121

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

tests/models.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6862,6 +6862,182 @@ def test_exists_compound_predicate(self):
68626862
self.assertEqual([u.username for u in query], ['u1', 'u2'])
68636863

68646864

6865+
class OJOrg(TestModel):
6866+
name = TextField()
6867+
6868+
6869+
class OJUser(TestModel):
6870+
org = ForeignKeyField(OJOrg, null=True)
6871+
name = TextField()
6872+
6873+
6874+
class OJNote(TestModel):
6875+
author = ForeignKeyField(OJUser, null=True)
6876+
content = TextField()
6877+
6878+
6879+
class OJNoteNoFK(TestModel):
6880+
# Same table as OJNote, but the author column is a plain integer, so the
6881+
# join attribute is not a field descriptor.
6882+
author_id = IntegerField(null=True)
6883+
content = TextField()
6884+
class Meta:
6885+
table_name = 'oj_note'
6886+
6887+
6888+
class TestOuterJoinHydratesNone(ModelTestCase):
6889+
requires = [OJOrg, OJUser, OJNote]
6890+
6891+
def setUp(self):
6892+
super(TestOuterJoinHydratesNone, self).setUp()
6893+
acme = OJOrg.create(name='acme')
6894+
huey = OJUser.create(org=acme, name='huey')
6895+
zaizee = OJUser.create(org=None, name='zaizee') # No org.
6896+
OJUser.create(org=acme, name='mickey') # No notes.
6897+
OJNote.create(author=huey, content='meow')
6898+
OJNote.create(author=None, content='???') # No author.
6899+
OJNote.create(author=zaizee, content='zzz')
6900+
6901+
def assertNotes(self, query, expected, attr='author', model=OJNote):
6902+
# Populate the relation as None.
6903+
with self.assertQueryCount(1):
6904+
accum = []
6905+
for note in query.order_by(model.id):
6906+
rel = getattr(note, attr)
6907+
accum.append((note.content,
6908+
rel.name if rel is not None else None))
6909+
self.assertEqual(accum, expected)
6910+
6911+
def test_join_fk(self):
6912+
expected = [('meow', 'huey'), ('???', None), ('zzz', 'zaizee')]
6913+
self.assertNotes(
6914+
OJNote.select(OJNote, OJUser).join(OJUser, JOIN.LEFT_OUTER),
6915+
expected)
6916+
6917+
UA = OJUser.alias()
6918+
query = OJNote.select(OJNote, UA).join(UA, JOIN.LEFT_OUTER)
6919+
self.assertNotes(query, expected)
6920+
6921+
# The subquery holds only huey, so zaizee's note misses as well.
6922+
subq = OJUser.select().where(OJUser.name == 'huey')
6923+
query = (OJNote.select(OJNote, subq.c.id, subq.c.name)
6924+
.join(subq, JOIN.LEFT_OUTER, on=OJNote.author == subq.c.id))
6925+
self.assertNotes(
6926+
query,
6927+
[('meow', 'huey'), ('???', None), ('zzz', None)])
6928+
6929+
def test_join_custom_attr(self):
6930+
# attr= is a plain attribute, not a field descriptor.
6931+
expected = [('meow', 'huey'), ('???', None), ('zzz', 'zaizee')]
6932+
6933+
query = (OJNote.select(OJNote, OJUser)
6934+
.join(OJUser, JOIN.LEFT_OUTER, attr='u'))
6935+
self.assertNotes(query, expected, attr='u')
6936+
6937+
UA = OJUser.alias()
6938+
query = OJNote.select(OJNote, UA).join(UA, JOIN.LEFT_OUTER, attr='u')
6939+
self.assertNotes(query, expected, attr='u')
6940+
6941+
subq = OJUser.select().where(OJUser.name == 'huey')
6942+
query = (OJNote.select(OJNote, subq.c.id, subq.c.name)
6943+
.join(subq, JOIN.LEFT_OUTER, on=OJNote.author == subq.c.id,
6944+
attr='u'))
6945+
self.assertNotes(
6946+
query,
6947+
[('meow', 'huey'), ('???', None), ('zzz', None)],
6948+
attr='u')
6949+
6950+
def test_join_without_fk(self):
6951+
expected = [('meow', 'huey'), ('???', None), ('zzz', 'zaizee')]
6952+
6953+
query = (OJNoteNoFK.select(OJNoteNoFK, OJUser)
6954+
.join(OJUser, JOIN.LEFT_OUTER,
6955+
on=OJNoteNoFK.author_id == OJUser.id, attr='author'))
6956+
self.assertNotes(query, expected, model=OJNoteNoFK)
6957+
6958+
UA = OJUser.alias()
6959+
query = (OJNoteNoFK.select(OJNoteNoFK, UA)
6960+
.join(UA, JOIN.LEFT_OUTER, on=OJNoteNoFK.author_id == UA.id,
6961+
attr='author'))
6962+
self.assertNotes(query, expected, model=OJNoteNoFK)
6963+
6964+
subq = OJUser.select().where(OJUser.name == 'huey')
6965+
query = (OJNoteNoFK.select(OJNoteNoFK, subq.c.id, subq.c.name)
6966+
.join(subq, JOIN.LEFT_OUTER,
6967+
on=OJNoteNoFK.author_id == subq.c.id, attr='author'))
6968+
self.assertNotes(
6969+
query,
6970+
[('meow', 'huey'), ('???', None), ('zzz', None)],
6971+
model=OJNoteNoFK)
6972+
6973+
def assertUsers(self, query, attr='ojnote'):
6974+
with self.assertQueryCount(1):
6975+
accum = []
6976+
for user in query.order_by(OJUser.id):
6977+
note = getattr(user, attr)
6978+
accum.append((user.name,
6979+
note.content if note is not None else None))
6980+
self.assertEqual(accum, [('huey', 'meow'), ('zaizee', 'zzz'),
6981+
('mickey', None)])
6982+
6983+
def test_join_backref(self):
6984+
# The foreign key is on the right-hand model, so the attribute
6985+
# defaults to the model name and is not a descriptor on OJUser.
6986+
self.assertUsers(
6987+
OJUser.select(OJUser, OJNote).join(OJNote, JOIN.LEFT_OUTER))
6988+
6989+
NA = OJNote.alias()
6990+
self.assertUsers(
6991+
OJUser.select(OJUser, NA).join(NA, JOIN.LEFT_OUTER))
6992+
6993+
query = (OJUser.select(OJUser, OJNote)
6994+
.join(OJNote, JOIN.LEFT_OUTER, attr='n'))
6995+
self.assertUsers(query, attr='n')
6996+
6997+
def assertOrgs(self, query, expected, model=OJNote):
6998+
with self.assertQueryCount(1):
6999+
accum = []
7000+
for note in query.order_by(model.id):
7001+
author = note.author
7002+
org = author.org if author is not None else None
7003+
accum.append((
7004+
note.content,
7005+
author.name if author is not None else None,
7006+
org.name if org is not None else None))
7007+
self.assertEqual(accum, expected)
7008+
7009+
def test_multi_hop(self):
7010+
# "???" has no author at all (the intervening model is absent), and
7011+
# "zzz" has an author with no org (the leaf is absent).
7012+
expected = [('meow', 'huey', 'acme'), ('???', None, None),
7013+
('zzz', 'zaizee', None)]
7014+
self.assertOrgs(
7015+
(OJNote.select(OJNote, OJUser, OJOrg)
7016+
.join(OJUser, JOIN.LEFT_OUTER)
7017+
.join(OJOrg, JOIN.LEFT_OUTER)), expected)
7018+
7019+
UA = OJUser.alias()
7020+
self.assertOrgs(
7021+
(OJNote.select(OJNote, UA, OJOrg)
7022+
.join(UA, JOIN.LEFT_OUTER)
7023+
.join(OJOrg, JOIN.LEFT_OUTER)), expected)
7024+
7025+
u_subq = OJUser.select().where(OJUser.name == 'huey')
7026+
o_subq = OJOrg.select()
7027+
query = (OJNoteNoFK
7028+
.select(OJNoteNoFK, u_subq.c.id, u_subq.c.name,
7029+
u_subq.c.org_id, o_subq.c.id, o_subq.c.name)
7030+
.join(u_subq, JOIN.LEFT_OUTER,
7031+
on=OJNoteNoFK.author_id == u_subq.c.id, attr='author')
7032+
.join(o_subq, JOIN.LEFT_OUTER,
7033+
on=u_subq.c.org_id == o_subq.c.id, attr='org'))
7034+
self.assertOrgs(
7035+
query,
7036+
[('meow', 'huey', 'acme'), ('???', None, None),
7037+
('zzz', None, None)],
7038+
model=OJNoteNoFK)
7039+
7040+
68657041
class TestLikeColumnValue(ModelTestCase):
68667042
requires = [User, Tweet]
68677043

0 commit comments

Comments
 (0)