1from sqlalchemy.testing import assert_raises_message, eq_, \
2    AssertsCompiledSQL, is_
3from sqlalchemy.testing import fixtures
4from sqlalchemy.orm import relationships, foreign, remote
5from sqlalchemy import MetaData, Table, Column, ForeignKey, Integer, \
6    select, ForeignKeyConstraint, exc, func, and_, String, Boolean
7from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE, MANYTOMANY
8from sqlalchemy.testing import mock
9
10
11class _JoinFixtures(object):
12    @classmethod
13    def setup_class(cls):
14        m = MetaData()
15        cls.left = Table('lft', m,
16                         Column('id', Integer, primary_key=True),
17                         Column('x', Integer),
18                         Column('y', Integer))
19        cls.right = Table('rgt', m,
20                          Column('id', Integer, primary_key=True),
21                          Column('lid', Integer, ForeignKey('lft.id')),
22                          Column('x', Integer),
23                          Column('y', Integer))
24        cls.right_multi_fk = Table('rgt_multi_fk', m,
25                                   Column('id', Integer, primary_key=True),
26                                   Column('lid1', Integer,
27                                          ForeignKey('lft.id')),
28                                   Column('lid2', Integer,
29                                          ForeignKey('lft.id')))
30
31        cls.selfref = Table('selfref', m,
32                            Column('id', Integer, primary_key=True),
33                            Column('sid', Integer, ForeignKey('selfref.id')))
34        cls.composite_selfref = Table('composite_selfref', m,
35                                      Column('id', Integer, primary_key=True),
36                                      Column('group_id', Integer,
37                                             primary_key=True),
38                                      Column('parent_id', Integer),
39                                      ForeignKeyConstraint(
40                                          ['parent_id', 'group_id'],
41                                          ['composite_selfref.id',
42                                              'composite_selfref.group_id']))
43        cls.m2mleft = Table('m2mlft', m,
44                            Column('id', Integer, primary_key=True))
45        cls.m2mright = Table('m2mrgt', m,
46                             Column('id', Integer, primary_key=True))
47        cls.m2msecondary = Table('m2msecondary', m,
48                                 Column('lid', Integer, ForeignKey(
49                                     'm2mlft.id'), primary_key=True),
50                                 Column('rid', Integer, ForeignKey(
51                                     'm2mrgt.id'), primary_key=True))
52        cls.m2msecondary_no_fks = Table('m2msecondary_no_fks', m,
53                                        Column('lid', Integer,
54                                               primary_key=True),
55                                        Column('rid', Integer,
56                                               primary_key=True))
57        cls.m2msecondary_ambig_fks = Table('m2msecondary_ambig_fks', m,
58                                           Column('lid1', Integer, ForeignKey(
59                                               'm2mlft.id'), primary_key=True),
60                                           Column('rid1', Integer, ForeignKey(
61                                               'm2mrgt.id'), primary_key=True),
62                                           Column('lid2', Integer, ForeignKey(
63                                               'm2mlft.id'), primary_key=True),
64                                           Column('rid2', Integer, ForeignKey(
65                                               'm2mrgt.id'), primary_key=True))
66        cls.base_w_sub_rel = Table('base_w_sub_rel', m,
67                                   Column('id', Integer, primary_key=True),
68                                   Column('sub_id', Integer,
69                                          ForeignKey('rel_sub.id')))
70        cls.rel_sub = Table('rel_sub', m,
71                            Column('id', Integer,
72                                   ForeignKey('base_w_sub_rel.id'),
73                                   primary_key=True))
74        cls.base = Table('base', m,
75                         Column('id', Integer, primary_key=True),
76                         Column('flag', Boolean))
77        cls.sub = Table('sub', m,
78                        Column('id', Integer, ForeignKey('base.id'),
79                               primary_key=True))
80        cls.sub_w_base_rel = Table('sub_w_base_rel', m,
81                                   Column('id', Integer, ForeignKey('base.id'),
82                                          primary_key=True),
83                                   Column('base_id', Integer,
84                                          ForeignKey('base.id')))
85        cls.sub_w_sub_rel = Table('sub_w_sub_rel', m,
86                                  Column('id', Integer, ForeignKey('base.id'),
87                                         primary_key=True),
88                                  Column('sub_id', Integer,
89                                         ForeignKey('sub.id'))
90                                  )
91        cls.right_w_base_rel = Table('right_w_base_rel', m,
92                                     Column('id', Integer, primary_key=True),
93                                     Column('base_id', Integer,
94                                            ForeignKey('base.id')))
95
96        cls.three_tab_a = Table('three_tab_a', m,
97                                Column('id', Integer, primary_key=True))
98        cls.three_tab_b = Table('three_tab_b', m,
99                                Column('id', Integer, primary_key=True),
100                                Column('aid', Integer, ForeignKey(
101                                    'three_tab_a.id')))
102        cls.three_tab_c = Table('three_tab_c', m,
103                                Column('id', Integer, primary_key=True),
104                                Column('aid', Integer, ForeignKey(
105                                    'three_tab_a.id')),
106                                Column('bid', Integer, ForeignKey(
107                                    'three_tab_b.id')))
108
109        cls.composite_target = Table('composite_target', m,
110                                     Column('uid', Integer, primary_key=True),
111                                     Column('oid', Integer, primary_key=True))
112
113        cls.composite_multi_ref = Table(
114            'composite_multi_ref', m,
115            Column('uid1', Integer),
116            Column('uid2', Integer),
117            Column('oid', Integer),
118            ForeignKeyConstraint(("uid1", "oid"),
119                                 ("composite_target.uid",
120                                  "composite_target.oid")),
121            ForeignKeyConstraint(("uid2", "oid"),
122                                 ("composite_target.uid",
123                                  "composite_target.oid")))
124
125        cls.purely_single_col = Table('purely_single_col', m,
126                                      Column('path', String))
127
128    def _join_fixture_overlapping_three_tables(self, **kw):
129        def _can_sync(*cols):
130            for c in cols:
131                if self.three_tab_c.c.contains_column(c):
132                    return False
133            else:
134                return True
135        return relationships.JoinCondition(
136            self.three_tab_a,
137            self.three_tab_b,
138            self.three_tab_a,
139            self.three_tab_b,
140            support_sync=False,
141            can_be_synced_fn=_can_sync,
142            primaryjoin=and_(
143                self.three_tab_a.c.id == self.three_tab_b.c.aid,
144                self.three_tab_c.c.bid == self.three_tab_b.c.id,
145                self.three_tab_c.c.aid == self.three_tab_a.c.id
146            )
147        )
148
149    def _join_fixture_m2m(self, **kw):
150        return relationships.JoinCondition(
151            self.m2mleft,
152            self.m2mright,
153            self.m2mleft,
154            self.m2mright,
155            secondary=self.m2msecondary,
156            **kw
157        )
158
159    def _join_fixture_m2m_backref(self, **kw):
160        """return JoinCondition in the same way RelationshipProperty
161        calls it for a backref on an m2m.
162
163        """
164        j1 = self._join_fixture_m2m()
165        return j1, relationships.JoinCondition(
166            self.m2mright,
167            self.m2mleft,
168            self.m2mright,
169            self.m2mleft,
170            secondary=self.m2msecondary,
171            primaryjoin=j1.secondaryjoin_minus_local,
172            secondaryjoin=j1.primaryjoin_minus_local
173        )
174
175    def _join_fixture_o2m(self, **kw):
176        return relationships.JoinCondition(
177            self.left,
178            self.right,
179            self.left,
180            self.right,
181            **kw
182        )
183
184    def _join_fixture_m2o(self, **kw):
185        return relationships.JoinCondition(
186            self.right,
187            self.left,
188            self.right,
189            self.left,
190            **kw
191        )
192
193    def _join_fixture_o2m_selfref(self, **kw):
194        return relationships.JoinCondition(
195            self.selfref,
196            self.selfref,
197            self.selfref,
198            self.selfref,
199            **kw
200        )
201
202    def _join_fixture_m2o_selfref(self, **kw):
203        return relationships.JoinCondition(
204            self.selfref,
205            self.selfref,
206            self.selfref,
207            self.selfref,
208            remote_side=set([self.selfref.c.id]),
209            **kw
210        )
211
212    def _join_fixture_o2m_composite_selfref(self, **kw):
213        return relationships.JoinCondition(
214            self.composite_selfref,
215            self.composite_selfref,
216            self.composite_selfref,
217            self.composite_selfref,
218            **kw
219        )
220
221    def _join_fixture_m2o_composite_selfref(self, **kw):
222        return relationships.JoinCondition(
223            self.composite_selfref,
224            self.composite_selfref,
225            self.composite_selfref,
226            self.composite_selfref,
227            remote_side=set([self.composite_selfref.c.id,
228                             self.composite_selfref.c.group_id]),
229            **kw
230        )
231
232    def _join_fixture_o2m_composite_selfref_func(self, **kw):
233        return relationships.JoinCondition(
234            self.composite_selfref,
235            self.composite_selfref,
236            self.composite_selfref,
237            self.composite_selfref,
238            primaryjoin=and_(
239                self.composite_selfref.c.group_id ==
240                func.foo(self.composite_selfref.c.group_id),
241                self.composite_selfref.c.parent_id ==
242                self.composite_selfref.c.id
243            ),
244            **kw
245        )
246
247    def _join_fixture_o2m_composite_selfref_func_remote_side(self, **kw):
248        return relationships.JoinCondition(
249            self.composite_selfref,
250            self.composite_selfref,
251            self.composite_selfref,
252            self.composite_selfref,
253            primaryjoin=and_(
254                self.composite_selfref.c.group_id ==
255                func.foo(self.composite_selfref.c.group_id),
256                self.composite_selfref.c.parent_id ==
257                self.composite_selfref.c.id
258            ),
259            remote_side=set([self.composite_selfref.c.parent_id]),
260            **kw
261        )
262
263    def _join_fixture_o2m_composite_selfref_func_annotated(self, **kw):
264        return relationships.JoinCondition(
265            self.composite_selfref,
266            self.composite_selfref,
267            self.composite_selfref,
268            self.composite_selfref,
269            primaryjoin=and_(
270                remote(self.composite_selfref.c.group_id) ==
271                func.foo(self.composite_selfref.c.group_id),
272                remote(self.composite_selfref.c.parent_id) ==
273                self.composite_selfref.c.id
274            ),
275            **kw
276        )
277
278    def _join_fixture_compound_expression_1(self, **kw):
279        return relationships.JoinCondition(
280            self.left,
281            self.right,
282            self.left,
283            self.right,
284            primaryjoin=(self.left.c.x + self.left.c.y) ==
285            relationships.remote(relationships.foreign(
286                self.right.c.x * self.right.c.y
287            )),
288            **kw
289        )
290
291    def _join_fixture_compound_expression_2(self, **kw):
292        return relationships.JoinCondition(
293            self.left,
294            self.right,
295            self.left,
296            self.right,
297            primaryjoin=(self.left.c.x + self.left.c.y) ==
298            relationships.foreign(
299                self.right.c.x * self.right.c.y
300            ),
301            **kw
302        )
303
304    def _join_fixture_compound_expression_1_non_annotated(self, **kw):
305        return relationships.JoinCondition(
306            self.left,
307            self.right,
308            self.left,
309            self.right,
310            primaryjoin=(self.left.c.x + self.left.c.y) ==
311            (
312                self.right.c.x * self.right.c.y
313            ),
314            **kw
315        )
316
317    def _join_fixture_base_to_joined_sub(self, **kw):
318        # see test/orm/inheritance/test_abc_inheritance:TestaTobM2O
319        # and others there
320        right = self.base_w_sub_rel.join(
321            self.rel_sub,
322            self.base_w_sub_rel.c.id == self.rel_sub.c.id
323        )
324        return relationships.JoinCondition(
325            self.base_w_sub_rel,
326            right,
327            self.base_w_sub_rel,
328            self.rel_sub,
329            primaryjoin=self.base_w_sub_rel.c.sub_id ==
330            self.rel_sub.c.id,
331            **kw
332        )
333
334    def _join_fixture_o2m_joined_sub_to_base(self, **kw):
335        left = self.base.join(self.sub_w_base_rel,
336                              self.base.c.id == self.sub_w_base_rel.c.id)
337        return relationships.JoinCondition(
338            left,
339            self.base,
340            self.sub_w_base_rel,
341            self.base,
342            primaryjoin=self.sub_w_base_rel.c.base_id == self.base.c.id
343        )
344
345    def _join_fixture_m2o_joined_sub_to_sub_on_base(self, **kw):
346        # this is a late add - a variant of the test case
347        # in #2491 where we join on the base cols instead.  only
348        # m2o has a problem at the time of this test.
349        left = self.base.join(self.sub, self.base.c.id == self.sub.c.id)
350        right = self.base.join(self.sub_w_base_rel,
351                               self.base.c.id == self.sub_w_base_rel.c.id)
352        return relationships.JoinCondition(
353            left,
354            right,
355            self.sub,
356            self.sub_w_base_rel,
357            primaryjoin=self.sub_w_base_rel.c.base_id == self.base.c.id,
358        )
359
360    def _join_fixture_o2m_joined_sub_to_sub(self, **kw):
361        left = self.base.join(self.sub, self.base.c.id == self.sub.c.id)
362        right = self.base.join(self.sub_w_sub_rel,
363                               self.base.c.id == self.sub_w_sub_rel.c.id)
364        return relationships.JoinCondition(
365            left,
366            right,
367            self.sub,
368            self.sub_w_sub_rel,
369            primaryjoin=self.sub.c.id == self.sub_w_sub_rel.c.sub_id
370        )
371
372    def _join_fixture_m2o_sub_to_joined_sub(self, **kw):
373        # see test.orm.test_mapper:MapperTest.test_add_column_prop_deannotate,
374        right = self.base.join(self.right_w_base_rel,
375                               self.base.c.id == self.right_w_base_rel.c.id)
376        return relationships.JoinCondition(
377            self.right_w_base_rel,
378            right,
379            self.right_w_base_rel,
380            self.right_w_base_rel,
381        )
382
383    def _join_fixture_m2o_sub_to_joined_sub_func(self, **kw):
384        # see test.orm.test_mapper:MapperTest.test_add_column_prop_deannotate,
385        right = self.base.join(self.right_w_base_rel,
386                               self.base.c.id == self.right_w_base_rel.c.id)
387        return relationships.JoinCondition(
388            self.right_w_base_rel,
389            right,
390            self.right_w_base_rel,
391            self.right_w_base_rel,
392            primaryjoin=self.right_w_base_rel.c.base_id ==
393            func.foo(self.base.c.id)
394        )
395
396    def _join_fixture_o2o_joined_sub_to_base(self, **kw):
397        left = self.base.join(self.sub,
398                              self.base.c.id == self.sub.c.id)
399
400        # see test_relationships->AmbiguousJoinInterpretedAsSelfRef
401        return relationships.JoinCondition(
402            left,
403            self.sub,
404            left,
405            self.sub,
406        )
407
408    def _join_fixture_o2m_to_annotated_func(self, **kw):
409        return relationships.JoinCondition(
410            self.left,
411            self.right,
412            self.left,
413            self.right,
414            primaryjoin=self.left.c.id ==
415            foreign(func.foo(self.right.c.lid)),
416            **kw
417        )
418
419    def _join_fixture_o2m_to_oldstyle_func(self, **kw):
420        return relationships.JoinCondition(
421            self.left,
422            self.right,
423            self.left,
424            self.right,
425            primaryjoin=self.left.c.id ==
426            func.foo(self.right.c.lid),
427            consider_as_foreign_keys=[self.right.c.lid],
428            **kw
429        )
430
431    def _join_fixture_overlapping_composite_fks(self, **kw):
432        return relationships.JoinCondition(
433            self.composite_target,
434            self.composite_multi_ref,
435            self.composite_target,
436            self.composite_multi_ref,
437            consider_as_foreign_keys=[self.composite_multi_ref.c.uid2,
438                                      self.composite_multi_ref.c.oid],
439            **kw
440        )
441
442        cls.left = Table('lft', m,
443                         Column('id', Integer, primary_key=True),
444                         Column('x', Integer),
445                         Column('y', Integer))
446        cls.right = Table('rgt', m,
447                          Column('id', Integer, primary_key=True),
448                          Column('lid', Integer, ForeignKey('lft.id')),
449                          Column('x', Integer),
450                          Column('y', Integer))
451
452    def _join_fixture_o2m_o_side_none(self, **kw):
453        return relationships.JoinCondition(
454            self.left,
455            self.right,
456            self.left,
457            self.right,
458            primaryjoin=and_(self.left.c.id == self.right.c.lid,
459                             self.left.c.x == 5),
460            **kw
461        )
462
463    def _join_fixture_purely_single_o2m(self, **kw):
464        return relationships.JoinCondition(
465            self.purely_single_col,
466            self.purely_single_col,
467            self.purely_single_col,
468            self.purely_single_col,
469            support_sync=False,
470            primaryjoin=self.purely_single_col.c.path.like(
471                remote(
472                    foreign(
473                        self.purely_single_col.c.path.concat('%')
474                    )
475                )
476            )
477        )
478
479    def _join_fixture_purely_single_m2o(self, **kw):
480        return relationships.JoinCondition(
481            self.purely_single_col,
482            self.purely_single_col,
483            self.purely_single_col,
484            self.purely_single_col,
485            support_sync=False,
486            primaryjoin=remote(self.purely_single_col.c.path).like(
487                foreign(self.purely_single_col.c.path.concat('%'))
488            )
489        )
490
491    def _join_fixture_remote_local_multiple_ref(self, **kw):
492        def fn(a, b): return ((a == b) | (b == a))
493        return relationships.JoinCondition(
494            self.selfref, self.selfref,
495            self.selfref, self.selfref,
496            support_sync=False,
497            primaryjoin=fn(
498                # we're putting a do-nothing annotation on
499                # "a" so that the left/right is preserved;
500                # annotation vs. non seems to affect __eq__ behavior
501                self.selfref.c.sid._annotate({"foo": "bar"}),
502                foreign(remote(self.selfref.c.sid)))
503        )
504
505    def _join_fixture_inh_selfref_w_entity(self, **kw):
506        fake_logger = mock.Mock(info=lambda *arg, **kw: None)
507        prop = mock.Mock(
508            parent=mock.Mock(),
509            mapper=mock.Mock(),
510            logger=fake_logger
511        )
512        local_selectable = self.base.join(self.sub)
513        remote_selectable = self.base.join(self.sub_w_sub_rel)
514
515        sub_w_sub_rel__sub_id = self.sub_w_sub_rel.c.sub_id._annotate(
516            {'parentmapper': prop.mapper})
517        sub__id = self.sub.c.id._annotate({'parentmapper': prop.parent})
518        sub_w_sub_rel__flag = self.base.c.flag._annotate(
519            {"parentmapper": prop.mapper})
520        return relationships.JoinCondition(
521            local_selectable, remote_selectable,
522            local_selectable, remote_selectable,
523            primaryjoin=and_(
524                sub_w_sub_rel__sub_id == sub__id,
525                sub_w_sub_rel__flag == True  # noqa
526            ),
527            prop=prop
528        )
529
530    def _assert_non_simple_warning(self, fn):
531        assert_raises_message(
532            exc.SAWarning,
533            "Non-simple column elements in "
534            "primary join condition for property "
535            r"None - consider using remote\(\) "
536            "annotations to mark the remote side.",
537            fn
538        )
539
540    def _assert_raises_no_relevant_fks(self, fn, expr, relname,
541                                       primary, *arg, **kw):
542        assert_raises_message(
543            exc.ArgumentError,
544            r"Could not locate any relevant foreign key columns "
545            r"for %s join condition '%s' on relationship %s.  "
546            r"Ensure that referencing columns are associated with "
547            r"a ForeignKey or ForeignKeyConstraint, or are annotated "
548            r"in the join condition with the foreign\(\) annotation."
549            % (
550                primary, expr, relname
551            ),
552            fn, *arg, **kw
553        )
554
555    def _assert_raises_no_equality(self, fn, expr, relname,
556                                   primary, *arg, **kw):
557        assert_raises_message(
558            exc.ArgumentError,
559            "Could not locate any simple equality expressions "
560            "involving locally mapped foreign key columns for %s join "
561            "condition '%s' on relationship %s.  "
562            "Ensure that referencing columns are associated with a "
563            "ForeignKey or ForeignKeyConstraint, or are annotated in "
564            r"the join condition with the foreign\(\) annotation. "
565            "To allow comparison operators other than '==', "
566            "the relationship can be marked as viewonly=True." % (
567                primary, expr, relname
568            ),
569            fn, *arg, **kw
570        )
571
572    def _assert_raises_ambig_join(self, fn, relname, secondary_arg,
573                                  *arg, **kw):
574        if secondary_arg is not None:
575            assert_raises_message(
576                exc.AmbiguousForeignKeysError,
577                "Could not determine join condition between "
578                "parent/child tables on relationship %s - "
579                "there are multiple foreign key paths linking the "
580                "tables via secondary table '%s'.  "
581                "Specify the 'foreign_keys' argument, providing a list "
582                "of those columns which should be counted as "
583                "containing a foreign key reference from the "
584                "secondary table to each of the parent and child tables."
585                % (relname, secondary_arg),
586                fn, *arg, **kw)
587        else:
588            assert_raises_message(
589                exc.AmbiguousForeignKeysError,
590                "Could not determine join condition between "
591                "parent/child tables on relationship %s - "
592                "there are no foreign keys linking these tables.  "
593                % (relname,),
594                fn, *arg, **kw)
595
596    def _assert_raises_no_join(self, fn, relname, secondary_arg,
597                               *arg, **kw):
598        if secondary_arg is not None:
599            assert_raises_message(
600                exc.NoForeignKeysError,
601                "Could not determine join condition between "
602                "parent/child tables on relationship %s - "
603                "there are no foreign keys linking these tables "
604                "via secondary table '%s'.  "
605                "Ensure that referencing columns are associated "
606                "with a ForeignKey "
607                "or ForeignKeyConstraint, or specify 'primaryjoin' and "
608                "'secondaryjoin' expressions"
609                % (relname, secondary_arg),
610                fn, *arg, **kw)
611        else:
612            assert_raises_message(
613                exc.NoForeignKeysError,
614                "Could not determine join condition between "
615                "parent/child tables on relationship %s - "
616                "there are no foreign keys linking these tables.  "
617                "Ensure that referencing columns are associated "
618                "with a ForeignKey "
619                "or ForeignKeyConstraint, or specify a 'primaryjoin' "
620                "expression."
621                % (relname,),
622                fn, *arg, **kw)
623
624
625class ColumnCollectionsTest(_JoinFixtures, fixtures.TestBase,
626                            AssertsCompiledSQL):
627    def test_determine_local_remote_pairs_o2o_joined_sub_to_base(self):
628        joincond = self._join_fixture_o2o_joined_sub_to_base()
629        eq_(
630            joincond.local_remote_pairs,
631            [(self.base.c.id, self.sub.c.id)]
632        )
633
634    def test_determine_synchronize_pairs_o2m_to_annotated_func(self):
635        joincond = self._join_fixture_o2m_to_annotated_func()
636        eq_(
637            joincond.synchronize_pairs,
638            [(self.left.c.id, self.right.c.lid)]
639        )
640
641    def test_determine_synchronize_pairs_o2m_to_oldstyle_func(self):
642        joincond = self._join_fixture_o2m_to_oldstyle_func()
643        eq_(
644            joincond.synchronize_pairs,
645            [(self.left.c.id, self.right.c.lid)]
646        )
647
648    def test_determinelocal_remote_m2o_joined_sub_to_sub_on_base(self):
649        joincond = self._join_fixture_m2o_joined_sub_to_sub_on_base()
650        eq_(
651            joincond.local_remote_pairs,
652            [(self.base.c.id, self.sub_w_base_rel.c.base_id)]
653        )
654
655    def test_determine_local_remote_base_to_joined_sub(self):
656        joincond = self._join_fixture_base_to_joined_sub()
657        eq_(
658            joincond.local_remote_pairs,
659            [
660                (self.base_w_sub_rel.c.sub_id, self.rel_sub.c.id)
661            ]
662        )
663
664    def test_determine_local_remote_o2m_joined_sub_to_base(self):
665        joincond = self._join_fixture_o2m_joined_sub_to_base()
666        eq_(
667            joincond.local_remote_pairs,
668            [
669                (self.sub_w_base_rel.c.base_id, self.base.c.id)
670            ]
671        )
672
673    def test_determine_local_remote_m2o_sub_to_joined_sub(self):
674        joincond = self._join_fixture_m2o_sub_to_joined_sub()
675        eq_(
676            joincond.local_remote_pairs,
677            [
678                (self.right_w_base_rel.c.base_id, self.base.c.id)
679            ]
680        )
681
682    def test_determine_remote_columns_o2m_joined_sub_to_sub(self):
683        joincond = self._join_fixture_o2m_joined_sub_to_sub()
684        eq_(
685            joincond.local_remote_pairs,
686            [
687                (self.sub.c.id, self.sub_w_sub_rel.c.sub_id)
688            ]
689        )
690
691    def test_determine_remote_columns_compound_1(self):
692        joincond = self._join_fixture_compound_expression_1(
693            support_sync=False)
694        eq_(
695            joincond.remote_columns,
696            set([self.right.c.x, self.right.c.y])
697        )
698
699    def test_determine_local_remote_compound_1(self):
700        joincond = self._join_fixture_compound_expression_1(
701            support_sync=False)
702        eq_(
703            joincond.local_remote_pairs,
704            [
705                (self.left.c.x, self.right.c.x),
706                (self.left.c.x, self.right.c.y),
707                (self.left.c.y, self.right.c.x),
708                (self.left.c.y, self.right.c.y)
709            ]
710        )
711
712    def test_determine_local_remote_compound_2(self):
713        joincond = self._join_fixture_compound_expression_2(
714            support_sync=False)
715        eq_(
716            joincond.local_remote_pairs,
717            [
718                (self.left.c.x, self.right.c.x),
719                (self.left.c.x, self.right.c.y),
720                (self.left.c.y, self.right.c.x),
721                (self.left.c.y, self.right.c.y)
722            ]
723        )
724
725    def test_determine_local_remote_compound_3(self):
726        joincond = self._join_fixture_compound_expression_1()
727        eq_(
728            joincond.local_remote_pairs,
729            [
730                (self.left.c.x, self.right.c.x),
731                (self.left.c.x, self.right.c.y),
732                (self.left.c.y, self.right.c.x),
733                (self.left.c.y, self.right.c.y),
734            ]
735        )
736
737    def test_err_local_remote_compound_1(self):
738        self._assert_raises_no_relevant_fks(
739            self._join_fixture_compound_expression_1_non_annotated,
740            r'lft.x \+ lft.y = rgt.x \* rgt.y',
741            "None", "primary"
742        )
743
744    def test_determine_remote_columns_compound_2(self):
745        joincond = self._join_fixture_compound_expression_2(
746            support_sync=False)
747        eq_(
748            joincond.remote_columns,
749            set([self.right.c.x, self.right.c.y])
750        )
751
752    def test_determine_remote_columns_o2m(self):
753        joincond = self._join_fixture_o2m()
754        eq_(
755            joincond.remote_columns,
756            set([self.right.c.lid])
757        )
758
759    def test_determine_remote_columns_o2m_selfref(self):
760        joincond = self._join_fixture_o2m_selfref()
761        eq_(
762            joincond.remote_columns,
763            set([self.selfref.c.sid])
764        )
765
766    def test_determine_local_remote_pairs_o2m_composite_selfref(self):
767        joincond = self._join_fixture_o2m_composite_selfref()
768        eq_(
769            joincond.local_remote_pairs,
770            [
771                (self.composite_selfref.c.group_id,
772                 self.composite_selfref.c.group_id),
773                (self.composite_selfref.c.id,
774                 self.composite_selfref.c.parent_id),
775            ]
776        )
777
778    def test_determine_local_remote_pairs_o2m_composite_selfref_func_warning(
779            self):
780        self._assert_non_simple_warning(
781            self._join_fixture_o2m_composite_selfref_func
782        )
783
784    def test_determine_local_remote_pairs_o2m_composite_selfref_func_rs(self):
785        # no warning
786        self._join_fixture_o2m_composite_selfref_func_remote_side()
787
788    def test_determine_local_remote_pairs_o2m_overlap_func_warning(self):
789        self._assert_non_simple_warning(
790            self._join_fixture_m2o_sub_to_joined_sub_func
791        )
792
793    def test_determine_local_remote_pairs_o2m_composite_selfref_func_annotated(
794            self):
795        joincond = self._join_fixture_o2m_composite_selfref_func_annotated()
796        eq_(
797            joincond.local_remote_pairs,
798            [
799                (self.composite_selfref.c.group_id,
800                 self.composite_selfref.c.group_id),
801                (self.composite_selfref.c.id,
802                 self.composite_selfref.c.parent_id),
803            ]
804        )
805
806    def test_determine_remote_columns_m2o_composite_selfref(self):
807        joincond = self._join_fixture_m2o_composite_selfref()
808        eq_(
809            joincond.remote_columns,
810            set([self.composite_selfref.c.id,
811                 self.composite_selfref.c.group_id])
812        )
813
814    def test_determine_remote_columns_m2o(self):
815        joincond = self._join_fixture_m2o()
816        eq_(
817            joincond.remote_columns,
818            set([self.left.c.id])
819        )
820
821    def test_determine_local_remote_pairs_o2m(self):
822        joincond = self._join_fixture_o2m()
823        eq_(
824            joincond.local_remote_pairs,
825            [(self.left.c.id, self.right.c.lid)]
826        )
827
828    def test_determine_synchronize_pairs_m2m(self):
829        joincond = self._join_fixture_m2m()
830        eq_(
831            joincond.synchronize_pairs,
832            [(self.m2mleft.c.id, self.m2msecondary.c.lid)]
833        )
834        eq_(
835            joincond.secondary_synchronize_pairs,
836            [(self.m2mright.c.id, self.m2msecondary.c.rid)]
837        )
838
839    def test_determine_local_remote_pairs_o2m_backref(self):
840        joincond = self._join_fixture_o2m()
841        joincond2 = self._join_fixture_m2o(
842            primaryjoin=joincond.primaryjoin_reverse_remote,
843        )
844        eq_(
845            joincond2.local_remote_pairs,
846            [(self.right.c.lid, self.left.c.id)]
847        )
848
849    def test_determine_local_remote_pairs_m2m(self):
850        joincond = self._join_fixture_m2m()
851        eq_(
852            joincond.local_remote_pairs,
853            [(self.m2mleft.c.id, self.m2msecondary.c.lid),
854             (self.m2mright.c.id, self.m2msecondary.c.rid)]
855        )
856
857    def test_determine_local_remote_pairs_m2m_backref(self):
858        j1, j2 = self._join_fixture_m2m_backref()
859        eq_(
860            j1.local_remote_pairs,
861            [(self.m2mleft.c.id, self.m2msecondary.c.lid),
862             (self.m2mright.c.id, self.m2msecondary.c.rid)]
863        )
864        eq_(
865            j2.local_remote_pairs,
866            [
867                (self.m2mright.c.id, self.m2msecondary.c.rid),
868                (self.m2mleft.c.id, self.m2msecondary.c.lid),
869            ]
870        )
871
872    def test_determine_local_columns_m2m_backref(self):
873        j1, j2 = self._join_fixture_m2m_backref()
874        eq_(
875            j1.local_columns,
876            set([self.m2mleft.c.id])
877        )
878        eq_(
879            j2.local_columns,
880            set([self.m2mright.c.id])
881        )
882
883    def test_determine_remote_columns_m2m_backref(self):
884        j1, j2 = self._join_fixture_m2m_backref()
885        eq_(
886            j1.remote_columns,
887            set([self.m2msecondary.c.lid, self.m2msecondary.c.rid])
888        )
889        eq_(
890            j2.remote_columns,
891            set([self.m2msecondary.c.lid, self.m2msecondary.c.rid])
892        )
893
894    def test_determine_remote_columns_m2o_selfref(self):
895        joincond = self._join_fixture_m2o_selfref()
896        eq_(
897            joincond.remote_columns,
898            set([self.selfref.c.id])
899        )
900
901    def test_determine_local_remote_cols_three_tab_viewonly(self):
902        joincond = self._join_fixture_overlapping_three_tables()
903        eq_(
904            joincond.local_remote_pairs,
905            [(self.three_tab_a.c.id, self.three_tab_b.c.aid)]
906        )
907        eq_(
908            joincond.remote_columns,
909            set([self.three_tab_b.c.id, self.three_tab_b.c.aid])
910        )
911
912    def test_determine_local_remote_overlapping_composite_fks(self):
913        joincond = self._join_fixture_overlapping_composite_fks()
914
915        eq_(
916            joincond.local_remote_pairs,
917            [
918                (self.composite_target.c.uid,
919                 self.composite_multi_ref.c.uid2,),
920                (self.composite_target.c.oid, self.composite_multi_ref.c.oid,)
921            ]
922        )
923
924    def test_determine_local_remote_pairs_purely_single_col_o2m(self):
925        joincond = self._join_fixture_purely_single_o2m()
926        eq_(
927            joincond.local_remote_pairs,
928            [(self.purely_single_col.c.path, self.purely_single_col.c.path)]
929        )
930
931    def test_determine_local_remote_pairs_inh_selfref_w_entities(self):
932        joincond = self._join_fixture_inh_selfref_w_entity()
933        eq_(
934            joincond.local_remote_pairs,
935            [(self.sub.c.id, self.sub_w_sub_rel.c.sub_id)]
936        )
937        eq_(
938            joincond.remote_columns,
939            set([self.base.c.flag, self.sub_w_sub_rel.c.sub_id])
940        )
941
942
943class DirectionTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
944    def test_determine_direction_compound_2(self):
945        joincond = self._join_fixture_compound_expression_2(
946            support_sync=False)
947        is_(
948            joincond.direction,
949            ONETOMANY
950        )
951
952    def test_determine_direction_o2m(self):
953        joincond = self._join_fixture_o2m()
954        is_(joincond.direction, ONETOMANY)
955
956    def test_determine_direction_o2m_selfref(self):
957        joincond = self._join_fixture_o2m_selfref()
958        is_(joincond.direction, ONETOMANY)
959
960    def test_determine_direction_m2o_selfref(self):
961        joincond = self._join_fixture_m2o_selfref()
962        is_(joincond.direction, MANYTOONE)
963
964    def test_determine_direction_o2m_composite_selfref(self):
965        joincond = self._join_fixture_o2m_composite_selfref()
966        is_(joincond.direction, ONETOMANY)
967
968    def test_determine_direction_m2o_composite_selfref(self):
969        joincond = self._join_fixture_m2o_composite_selfref()
970        is_(joincond.direction, MANYTOONE)
971
972    def test_determine_direction_m2o(self):
973        joincond = self._join_fixture_m2o()
974        is_(joincond.direction, MANYTOONE)
975
976    def test_determine_direction_purely_single_o2m(self):
977        joincond = self._join_fixture_purely_single_o2m()
978        is_(joincond.direction, ONETOMANY)
979
980    def test_determine_direction_purely_single_m2o(self):
981        joincond = self._join_fixture_purely_single_m2o()
982        is_(joincond.direction, MANYTOONE)
983
984
985class DetermineJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
986    __dialect__ = 'default'
987
988    def test_determine_join_o2m(self):
989        joincond = self._join_fixture_o2m()
990        self.assert_compile(
991            joincond.primaryjoin,
992            "lft.id = rgt.lid"
993        )
994
995    def test_determine_join_o2m_selfref(self):
996        joincond = self._join_fixture_o2m_selfref()
997        self.assert_compile(
998            joincond.primaryjoin,
999            "selfref.id = selfref.sid"
1000        )
1001
1002    def test_determine_join_m2o_selfref(self):
1003        joincond = self._join_fixture_m2o_selfref()
1004        self.assert_compile(
1005            joincond.primaryjoin,
1006            "selfref.id = selfref.sid"
1007        )
1008
1009    def test_determine_join_o2m_composite_selfref(self):
1010        joincond = self._join_fixture_o2m_composite_selfref()
1011        self.assert_compile(
1012            joincond.primaryjoin,
1013            "composite_selfref.group_id = composite_selfref.group_id "
1014            "AND composite_selfref.id = composite_selfref.parent_id"
1015        )
1016
1017    def test_determine_join_m2o_composite_selfref(self):
1018        joincond = self._join_fixture_m2o_composite_selfref()
1019        self.assert_compile(
1020            joincond.primaryjoin,
1021            "composite_selfref.group_id = composite_selfref.group_id "
1022            "AND composite_selfref.id = composite_selfref.parent_id"
1023        )
1024
1025    def test_determine_join_m2o(self):
1026        joincond = self._join_fixture_m2o()
1027        self.assert_compile(
1028            joincond.primaryjoin,
1029            "lft.id = rgt.lid"
1030        )
1031
1032    def test_determine_join_ambiguous_fks_o2m(self):
1033        assert_raises_message(
1034            exc.AmbiguousForeignKeysError,
1035            "Could not determine join condition between "
1036            "parent/child tables on relationship None - "
1037            "there are multiple foreign key paths linking "
1038            "the tables.  Specify the 'foreign_keys' argument, "
1039            "providing a list of those columns which "
1040            "should be counted as containing a foreign "
1041            "key reference to the parent table.",
1042            relationships.JoinCondition,
1043            self.left,
1044            self.right_multi_fk,
1045            self.left,
1046            self.right_multi_fk,
1047        )
1048
1049    def test_determine_join_no_fks_o2m(self):
1050        self._assert_raises_no_join(
1051            relationships.JoinCondition,
1052            "None", None,
1053            self.left,
1054            self.selfref,
1055            self.left,
1056            self.selfref,
1057        )
1058
1059    def test_determine_join_ambiguous_fks_m2m(self):
1060
1061        self._assert_raises_ambig_join(
1062            relationships.JoinCondition,
1063            "None", self.m2msecondary_ambig_fks,
1064            self.m2mleft,
1065            self.m2mright,
1066            self.m2mleft,
1067            self.m2mright,
1068            secondary=self.m2msecondary_ambig_fks
1069        )
1070
1071    def test_determine_join_no_fks_m2m(self):
1072        self._assert_raises_no_join(
1073            relationships.JoinCondition,
1074            "None", self.m2msecondary_no_fks,
1075            self.m2mleft,
1076            self.m2mright,
1077            self.m2mleft,
1078            self.m2mright,
1079            secondary=self.m2msecondary_no_fks
1080        )
1081
1082    def _join_fixture_fks_ambig_m2m(self):
1083        return relationships.JoinCondition(
1084            self.m2mleft,
1085            self.m2mright,
1086            self.m2mleft,
1087            self.m2mright,
1088            secondary=self.m2msecondary_ambig_fks,
1089            consider_as_foreign_keys=[
1090                self.m2msecondary_ambig_fks.c.lid1,
1091                self.m2msecondary_ambig_fks.c.rid1]
1092        )
1093
1094    def test_determine_join_w_fks_ambig_m2m(self):
1095        joincond = self._join_fixture_fks_ambig_m2m()
1096        self.assert_compile(
1097            joincond.primaryjoin,
1098            "m2mlft.id = m2msecondary_ambig_fks.lid1"
1099        )
1100        self.assert_compile(
1101            joincond.secondaryjoin,
1102            "m2mrgt.id = m2msecondary_ambig_fks.rid1"
1103        )
1104
1105
1106class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
1107    __dialect__ = 'default'
1108
1109    def test_join_targets_o2m_selfref(self):
1110        joincond = self._join_fixture_o2m_selfref()
1111        left = select([joincond.parent_selectable]).alias('pj')
1112        pj, sj, sec, adapter, ds = joincond.join_targets(
1113            left,
1114            joincond.child_selectable,
1115            True)
1116        self.assert_compile(
1117            pj, "pj.id = selfref.sid"
1118        )
1119
1120        right = select([joincond.child_selectable]).alias('pj')
1121        pj, sj, sec, adapter, ds = joincond.join_targets(
1122            joincond.parent_selectable,
1123            right,
1124            True)
1125        self.assert_compile(
1126            pj, "selfref.id = pj.sid"
1127        )
1128
1129    def test_join_targets_o2m_plain(self):
1130        joincond = self._join_fixture_o2m()
1131        pj, sj, sec, adapter, ds = joincond.join_targets(
1132            joincond.parent_selectable,
1133            joincond.child_selectable,
1134            False)
1135        self.assert_compile(
1136            pj, "lft.id = rgt.lid"
1137        )
1138
1139    def test_join_targets_o2m_left_aliased(self):
1140        joincond = self._join_fixture_o2m()
1141        left = select([joincond.parent_selectable]).alias('pj')
1142        pj, sj, sec, adapter, ds = joincond.join_targets(
1143            left,
1144            joincond.child_selectable,
1145            True)
1146        self.assert_compile(
1147            pj, "pj.id = rgt.lid"
1148        )
1149
1150    def test_join_targets_o2m_right_aliased(self):
1151        joincond = self._join_fixture_o2m()
1152        right = select([joincond.child_selectable]).alias('pj')
1153        pj, sj, sec, adapter, ds = joincond.join_targets(
1154            joincond.parent_selectable,
1155            right,
1156            True)
1157        self.assert_compile(
1158            pj, "lft.id = pj.lid"
1159        )
1160
1161    def test_join_targets_o2m_composite_selfref(self):
1162        joincond = self._join_fixture_o2m_composite_selfref()
1163        right = select([joincond.child_selectable]).alias('pj')
1164        pj, sj, sec, adapter, ds = joincond.join_targets(
1165            joincond.parent_selectable,
1166            right,
1167            True)
1168        self.assert_compile(
1169            pj,
1170            "pj.group_id = composite_selfref.group_id "
1171            "AND composite_selfref.id = pj.parent_id"
1172        )
1173
1174    def test_join_targets_m2o_composite_selfref(self):
1175        joincond = self._join_fixture_m2o_composite_selfref()
1176        right = select([joincond.child_selectable]).alias('pj')
1177        pj, sj, sec, adapter, ds = joincond.join_targets(
1178            joincond.parent_selectable,
1179            right,
1180            True)
1181        self.assert_compile(
1182            pj,
1183            "pj.group_id = composite_selfref.group_id "
1184            "AND pj.id = composite_selfref.parent_id"
1185        )
1186
1187
1188class LazyClauseTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
1189    __dialect__ = 'default'
1190
1191    def test_lazy_clause_o2m(self):
1192        joincond = self._join_fixture_o2m()
1193        lazywhere, bind_to_col, equated_columns = joincond.create_lazy_clause()
1194        self.assert_compile(
1195            lazywhere,
1196            ":param_1 = rgt.lid"
1197        )
1198
1199    def test_lazy_clause_o2m_reverse(self):
1200        joincond = self._join_fixture_o2m()
1201        lazywhere, bind_to_col, equated_columns =\
1202            joincond.create_lazy_clause(reverse_direction=True)
1203        self.assert_compile(
1204            lazywhere,
1205            "lft.id = :param_1"
1206        )
1207
1208    def test_lazy_clause_o2m_o_side_none(self):
1209        # test for #2948.  When the join is "o.id == m.oid
1210        # AND o.something == something",
1211        # we don't want 'o' brought into the lazy load for 'm'
1212        joincond = self._join_fixture_o2m_o_side_none()
1213        lazywhere, bind_to_col, equated_columns = joincond.create_lazy_clause()
1214        self.assert_compile(
1215            lazywhere,
1216            ":param_1 = rgt.lid AND :param_2 = :x_1",
1217            checkparams={'param_1': None, 'param_2': None, 'x_1': 5}
1218        )
1219
1220    def test_lazy_clause_o2m_o_side_none_reverse(self):
1221        # continued test for #2948.
1222        joincond = self._join_fixture_o2m_o_side_none()
1223        lazywhere, bind_to_col, equated_columns = joincond.create_lazy_clause(
1224            reverse_direction=True)
1225        self.assert_compile(
1226            lazywhere,
1227            "lft.id = :param_1 AND lft.x = :x_1",
1228            checkparams={'param_1': None, 'x_1': 5}
1229        )
1230
1231    def test_lazy_clause_remote_local_multiple_ref(self):
1232        joincond = self._join_fixture_remote_local_multiple_ref()
1233        lazywhere, bind_to_col, equated_columns = joincond.create_lazy_clause()
1234
1235        self.assert_compile(
1236            lazywhere,
1237            ":param_1 = selfref.sid OR selfref.sid = :param_1",
1238            checkparams={'param_1': None}
1239        )
1240