1import sqlalchemy as sa
2from sqlalchemy import and_
3from sqlalchemy import desc
4from sqlalchemy import exc as sa_exc
5from sqlalchemy import ForeignKey
6from sqlalchemy import func
7from sqlalchemy import Integer
8from sqlalchemy import lateral
9from sqlalchemy import literal_column
10from sqlalchemy import MetaData
11from sqlalchemy import not_
12from sqlalchemy import or_
13from sqlalchemy import select
14from sqlalchemy import String
15from sqlalchemy import Table
16from sqlalchemy import true
17from sqlalchemy.engine import default
18from sqlalchemy.orm import aliased
19from sqlalchemy.orm import backref
20from sqlalchemy.orm import configure_mappers
21from sqlalchemy.orm import create_session
22from sqlalchemy.orm import join
23from sqlalchemy.orm import joinedload
24from sqlalchemy.orm import mapper
25from sqlalchemy.orm import outerjoin
26from sqlalchemy.orm import relationship
27from sqlalchemy.orm import Session
28from sqlalchemy.orm import synonym
29from sqlalchemy.testing import assert_raises
30from sqlalchemy.testing import assert_raises_message
31from sqlalchemy.testing import AssertsCompiledSQL
32from sqlalchemy.testing import eq_
33from sqlalchemy.testing import fixtures
34from sqlalchemy.testing.schema import Column
35from test.orm import _fixtures
36
37
38class QueryTest(_fixtures.FixtureTest):
39    run_setup_mappers = "once"
40    run_inserts = "once"
41    run_deletes = None
42
43    @classmethod
44    def setup_mappers(cls):
45        (
46            Node,
47            composite_pk_table,
48            users,
49            Keyword,
50            items,
51            Dingaling,
52            order_items,
53            item_keywords,
54            Item,
55            User,
56            dingalings,
57            Address,
58            keywords,
59            CompositePk,
60            nodes,
61            Order,
62            orders,
63            addresses,
64        ) = (
65            cls.classes.Node,
66            cls.tables.composite_pk_table,
67            cls.tables.users,
68            cls.classes.Keyword,
69            cls.tables.items,
70            cls.classes.Dingaling,
71            cls.tables.order_items,
72            cls.tables.item_keywords,
73            cls.classes.Item,
74            cls.classes.User,
75            cls.tables.dingalings,
76            cls.classes.Address,
77            cls.tables.keywords,
78            cls.classes.CompositePk,
79            cls.tables.nodes,
80            cls.classes.Order,
81            cls.tables.orders,
82            cls.tables.addresses,
83        )
84
85        mapper(
86            User,
87            users,
88            properties={
89                "addresses": relationship(
90                    Address, backref="user", order_by=addresses.c.id
91                ),
92                # o2m, m2o
93                "orders": relationship(
94                    Order, backref="user", order_by=orders.c.id
95                ),
96            },
97        )
98        mapper(
99            Address,
100            addresses,
101            properties={
102                # o2o
103                "dingaling": relationship(
104                    Dingaling, uselist=False, backref="address"
105                )
106            },
107        )
108        mapper(Dingaling, dingalings)
109        mapper(
110            Order,
111            orders,
112            properties={
113                # m2m
114                "items": relationship(
115                    Item, secondary=order_items, order_by=items.c.id
116                ),
117                "address": relationship(Address),  # m2o
118            },
119        )
120        mapper(
121            Item,
122            items,
123            properties={
124                "keywords": relationship(
125                    Keyword, secondary=item_keywords
126                )  # m2m
127            },
128        )
129        mapper(Keyword, keywords)
130
131        mapper(
132            Node,
133            nodes,
134            properties={
135                "children": relationship(
136                    Node, backref=backref("parent", remote_side=[nodes.c.id])
137                )
138            },
139        )
140
141        mapper(CompositePk, composite_pk_table)
142
143        configure_mappers()
144
145
146class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL):
147    run_setup_mappers = "once"
148
149    @classmethod
150    def define_tables(cls, metadata):
151        Table(
152            "companies",
153            metadata,
154            Column(
155                "company_id",
156                Integer,
157                primary_key=True,
158                test_needs_autoincrement=True,
159            ),
160            Column("name", String(50)),
161        )
162
163        Table(
164            "people",
165            metadata,
166            Column(
167                "person_id",
168                Integer,
169                primary_key=True,
170                test_needs_autoincrement=True,
171            ),
172            Column("company_id", Integer, ForeignKey("companies.company_id")),
173            Column("name", String(50)),
174            Column("type", String(30)),
175        )
176
177        Table(
178            "engineers",
179            metadata,
180            Column(
181                "person_id",
182                Integer,
183                ForeignKey("people.person_id"),
184                primary_key=True,
185            ),
186            Column("status", String(30)),
187            Column("engineer_name", String(50)),
188            Column("primary_language", String(50)),
189        )
190
191        Table(
192            "machines",
193            metadata,
194            Column(
195                "machine_id",
196                Integer,
197                primary_key=True,
198                test_needs_autoincrement=True,
199            ),
200            Column("name", String(50)),
201            Column("engineer_id", Integer, ForeignKey("engineers.person_id")),
202        )
203
204        Table(
205            "managers",
206            metadata,
207            Column(
208                "person_id",
209                Integer,
210                ForeignKey("people.person_id"),
211                primary_key=True,
212            ),
213            Column("status", String(30)),
214            Column("manager_name", String(50)),
215        )
216
217        Table(
218            "boss",
219            metadata,
220            Column(
221                "boss_id",
222                Integer,
223                ForeignKey("managers.person_id"),
224                primary_key=True,
225            ),
226            Column("golf_swing", String(30)),
227        )
228
229        Table(
230            "paperwork",
231            metadata,
232            Column(
233                "paperwork_id",
234                Integer,
235                primary_key=True,
236                test_needs_autoincrement=True,
237            ),
238            Column("description", String(50)),
239            Column("person_id", Integer, ForeignKey("people.person_id")),
240        )
241
242    @classmethod
243    def setup_classes(cls):
244        paperwork, people, companies, boss, managers, machines, engineers = (
245            cls.tables.paperwork,
246            cls.tables.people,
247            cls.tables.companies,
248            cls.tables.boss,
249            cls.tables.managers,
250            cls.tables.machines,
251            cls.tables.engineers,
252        )
253
254        class Company(cls.Comparable):
255            pass
256
257        class Person(cls.Comparable):
258            pass
259
260        class Engineer(Person):
261            pass
262
263        class Manager(Person):
264            pass
265
266        class Boss(Manager):
267            pass
268
269        class Machine(cls.Comparable):
270            pass
271
272        class Paperwork(cls.Comparable):
273            pass
274
275        mapper(
276            Company,
277            companies,
278            properties={
279                "employees": relationship(Person, order_by=people.c.person_id)
280            },
281        )
282
283        mapper(Machine, machines)
284
285        mapper(
286            Person,
287            people,
288            polymorphic_on=people.c.type,
289            polymorphic_identity="person",
290            properties={
291                "paperwork": relationship(
292                    Paperwork, order_by=paperwork.c.paperwork_id
293                )
294            },
295        )
296        mapper(
297            Engineer,
298            engineers,
299            inherits=Person,
300            polymorphic_identity="engineer",
301            properties={
302                "machines": relationship(
303                    Machine, order_by=machines.c.machine_id
304                )
305            },
306        )
307        mapper(
308            Manager, managers, inherits=Person, polymorphic_identity="manager"
309        )
310        mapper(Boss, boss, inherits=Manager, polymorphic_identity="boss")
311        mapper(Paperwork, paperwork)
312
313    def test_single_prop(self):
314        Company = self.classes.Company
315
316        sess = create_session()
317
318        self.assert_compile(
319            sess.query(Company).join(Company.employees),
320            "SELECT companies.company_id AS companies_company_id, "
321            "companies.name AS companies_name "
322            "FROM companies JOIN people "
323            "ON companies.company_id = people.company_id",
324            use_default_dialect=True,
325        )
326
327    def test_force_via_select_from(self):
328        Company, Engineer = self.classes.Company, self.classes.Engineer
329
330        sess = create_session()
331
332        self.assert_compile(
333            sess.query(Company)
334            .filter(Company.company_id == Engineer.company_id)
335            .filter(Engineer.primary_language == "java"),
336            "SELECT companies.company_id AS companies_company_id, "
337            "companies.name AS companies_name "
338            "FROM companies, people, engineers "
339            "WHERE companies.company_id = people.company_id "
340            "AND engineers.primary_language "
341            "= :primary_language_1",
342            use_default_dialect=True,
343        )
344
345        self.assert_compile(
346            sess.query(Company)
347            .select_from(Company, Engineer)
348            .filter(Company.company_id == Engineer.company_id)
349            .filter(Engineer.primary_language == "java"),
350            "SELECT companies.company_id AS companies_company_id, "
351            "companies.name AS companies_name "
352            "FROM companies, people JOIN engineers "
353            "ON people.person_id = engineers.person_id "
354            "WHERE companies.company_id = people.company_id "
355            "AND engineers.primary_language ="
356            " :primary_language_1",
357            use_default_dialect=True,
358        )
359
360    def test_single_prop_of_type(self):
361        Company, Engineer = self.classes.Company, self.classes.Engineer
362
363        sess = create_session()
364
365        self.assert_compile(
366            sess.query(Company).join(Company.employees.of_type(Engineer)),
367            "SELECT companies.company_id AS companies_company_id, "
368            "companies.name AS companies_name "
369            "FROM companies JOIN "
370            "(people JOIN engineers "
371            "ON people.person_id = engineers.person_id) "
372            "ON companies.company_id = people.company_id",
373            use_default_dialect=True,
374        )
375
376    def test_prop_with_polymorphic_1(self):
377        Person, Manager, Paperwork = (
378            self.classes.Person,
379            self.classes.Manager,
380            self.classes.Paperwork,
381        )
382
383        sess = create_session()
384
385        self.assert_compile(
386            sess.query(Person)
387            .with_polymorphic(Manager)
388            .order_by(Person.person_id)
389            .join("paperwork")
390            .filter(Paperwork.description.like("%review%")),
391            "SELECT people.person_id AS people_person_id, people.company_id AS"
392            " people_company_id, "
393            "people.name AS people_name, people.type AS people_type, "
394            "managers.person_id AS managers_person_id, "
395            "managers.status AS managers_status, managers.manager_name AS "
396            "managers_manager_name FROM people "
397            "LEFT OUTER JOIN managers "
398            "ON people.person_id = managers.person_id "
399            "JOIN paperwork "
400            "ON people.person_id = paperwork.person_id "
401            "WHERE paperwork.description LIKE :description_1 "
402            "ORDER BY people.person_id",
403            use_default_dialect=True,
404        )
405
406    def test_prop_with_polymorphic_2(self):
407        Person, Manager, Paperwork = (
408            self.classes.Person,
409            self.classes.Manager,
410            self.classes.Paperwork,
411        )
412
413        sess = create_session()
414
415        self.assert_compile(
416            sess.query(Person)
417            .with_polymorphic(Manager)
418            .order_by(Person.person_id)
419            .join("paperwork", aliased=True)
420            .filter(Paperwork.description.like("%review%")),
421            "SELECT people.person_id AS people_person_id, "
422            "people.company_id AS people_company_id, "
423            "people.name AS people_name, people.type AS people_type, "
424            "managers.person_id AS managers_person_id, "
425            "managers.status AS managers_status, "
426            "managers.manager_name AS managers_manager_name "
427            "FROM people LEFT OUTER JOIN managers "
428            "ON people.person_id = managers.person_id "
429            "JOIN paperwork AS paperwork_1 "
430            "ON people.person_id = paperwork_1.person_id "
431            "WHERE paperwork_1.description "
432            "LIKE :description_1 ORDER BY people.person_id",
433            use_default_dialect=True,
434        )
435
436    def test_explicit_polymorphic_join_one(self):
437        Company, Engineer = self.classes.Company, self.classes.Engineer
438
439        sess = create_session()
440
441        self.assert_compile(
442            sess.query(Company)
443            .join(Engineer)
444            .filter(Engineer.engineer_name == "vlad"),
445            "SELECT companies.company_id AS companies_company_id, "
446            "companies.name AS companies_name "
447            "FROM companies JOIN (people JOIN engineers "
448            "ON people.person_id = engineers.person_id) "
449            "ON "
450            "companies.company_id = people.company_id "
451            "WHERE engineers.engineer_name = :engineer_name_1",
452            use_default_dialect=True,
453        )
454
455    def test_explicit_polymorphic_join_two(self):
456        Company, Engineer = self.classes.Company, self.classes.Engineer
457
458        sess = create_session()
459        self.assert_compile(
460            sess.query(Company)
461            .join(Engineer, Company.company_id == Engineer.company_id)
462            .filter(Engineer.engineer_name == "vlad"),
463            "SELECT companies.company_id AS companies_company_id, "
464            "companies.name AS companies_name "
465            "FROM companies JOIN "
466            "(people JOIN engineers "
467            "ON people.person_id = engineers.person_id) "
468            "ON "
469            "companies.company_id = people.company_id "
470            "WHERE engineers.engineer_name = :engineer_name_1",
471            use_default_dialect=True,
472        )
473
474    def test_multiple_adaption(self):
475        """test that multiple filter() adapters get chained together "
476        and work correctly within a multiple-entry join()."""
477
478        people, Company, Machine, engineers, machines, Engineer = (
479            self.tables.people,
480            self.classes.Company,
481            self.classes.Machine,
482            self.tables.engineers,
483            self.tables.machines,
484            self.classes.Engineer,
485        )
486
487        sess = create_session()
488
489        self.assert_compile(
490            sess.query(Company)
491            .join(people.join(engineers), Company.employees)
492            .filter(Engineer.name == "dilbert"),
493            "SELECT companies.company_id AS companies_company_id, "
494            "companies.name AS companies_name "
495            "FROM companies JOIN (people "
496            "JOIN engineers ON people.person_id = "
497            "engineers.person_id) ON companies.company_id = "
498            "people.company_id WHERE people.name = :name_1",
499            use_default_dialect=True,
500        )
501
502        mach_alias = machines.select()
503        self.assert_compile(
504            sess.query(Company)
505            .join(people.join(engineers), Company.employees)
506            .join(mach_alias, Engineer.machines, from_joinpoint=True)
507            .filter(Engineer.name == "dilbert")
508            .filter(Machine.name == "foo"),
509            "SELECT companies.company_id AS companies_company_id, "
510            "companies.name AS companies_name "
511            "FROM companies JOIN (people "
512            "JOIN engineers ON people.person_id = "
513            "engineers.person_id) ON companies.company_id = "
514            "people.company_id JOIN "
515            "(SELECT machines.machine_id AS machine_id, "
516            "machines.name AS name, "
517            "machines.engineer_id AS engineer_id "
518            "FROM machines) AS anon_1 "
519            "ON engineers.person_id = anon_1.engineer_id "
520            "WHERE people.name = :name_1 AND anon_1.name = :name_2",
521            use_default_dialect=True,
522        )
523
524    def test_auto_aliasing_multi_link(self):
525        # test [ticket:2903]
526        sess = create_session()
527
528        Company, Engineer, Manager, Boss = (
529            self.classes.Company,
530            self.classes.Engineer,
531            self.classes.Manager,
532            self.classes.Boss,
533        )
534        q = (
535            sess.query(Company)
536            .join(Company.employees.of_type(Engineer))
537            .join(Company.employees.of_type(Manager))
538            .join(Company.employees.of_type(Boss))
539        )
540
541        self.assert_compile(
542            q,
543            "SELECT companies.company_id AS companies_company_id, "
544            "companies.name AS companies_name FROM companies "
545            "JOIN (people JOIN engineers "
546            "ON people.person_id = engineers.person_id) "
547            "ON companies.company_id = people.company_id "
548            "JOIN (people AS people_1 JOIN managers AS managers_1 "
549            "ON people_1.person_id = managers_1.person_id) "
550            "ON companies.company_id = people_1.company_id "
551            "JOIN (people AS people_2 JOIN managers AS managers_2 "
552            "ON people_2.person_id = managers_2.person_id JOIN boss AS boss_1 "
553            "ON managers_2.person_id = boss_1.boss_id) "
554            "ON companies.company_id = people_2.company_id",
555            use_default_dialect=True,
556        )
557
558
559class JoinOnSynonymTest(_fixtures.FixtureTest, AssertsCompiledSQL):
560    __dialect__ = "default"
561
562    @classmethod
563    def setup_mappers(cls):
564        User = cls.classes.User
565        Address = cls.classes.Address
566        users, addresses = (cls.tables.users, cls.tables.addresses)
567        mapper(
568            User,
569            users,
570            properties={
571                "addresses": relationship(Address),
572                "ad_syn": synonym("addresses"),
573            },
574        )
575        mapper(Address, addresses)
576
577    def test_join_on_synonym(self):
578        User = self.classes.User
579        self.assert_compile(
580            Session().query(User).join(User.ad_syn),
581            "SELECT users.id AS users_id, users.name AS users_name "
582            "FROM users JOIN addresses ON users.id = addresses.user_id",
583        )
584
585
586class JoinTest(QueryTest, AssertsCompiledSQL):
587    __dialect__ = "default"
588
589    def test_single_name(self):
590        User = self.classes.User
591
592        sess = create_session()
593
594        self.assert_compile(
595            sess.query(User).join("orders"),
596            "SELECT users.id AS users_id, users.name AS users_name "
597            "FROM users JOIN orders ON users.id = orders.user_id",
598        )
599
600        assert_raises(
601            sa_exc.InvalidRequestError, sess.query(User).join, "user"
602        )
603
604        self.assert_compile(
605            sess.query(User).join("orders", "items"),
606            "SELECT users.id AS users_id, users.name AS users_name FROM users "
607            "JOIN orders ON users.id = orders.user_id "
608            "JOIN order_items AS order_items_1 "
609            "ON orders.id = order_items_1.order_id JOIN items "
610            "ON items.id = order_items_1.item_id",
611        )
612
613        # test overlapping paths.   User->orders is used by both joins, but
614        # rendered once.
615        self.assert_compile(
616            sess.query(User).join("orders", "items").join("orders", "address"),
617            "SELECT users.id AS users_id, users.name AS users_name FROM users "
618            "JOIN orders "
619            "ON users.id = orders.user_id "
620            "JOIN order_items AS order_items_1 "
621            "ON orders.id = order_items_1.order_id "
622            "JOIN items ON items.id = order_items_1.item_id JOIN addresses "
623            "ON addresses.id = orders.address_id",
624        )
625
626    def test_invalid_kwarg_join(self):
627        User = self.classes.User
628        sess = create_session()
629        assert_raises_message(
630            TypeError,
631            "unknown arguments: bar, foob",
632            sess.query(User).join,
633            "address",
634            foob="bar",
635            bar="bat",
636        )
637        assert_raises_message(
638            TypeError,
639            "unknown arguments: bar, foob",
640            sess.query(User).outerjoin,
641            "address",
642            foob="bar",
643            bar="bat",
644        )
645
646    def test_left_w_no_entity(self):
647        User = self.classes.User
648        Address = self.classes.Address
649
650        sess = create_session()
651
652        self.assert_compile(
653            sess.query(User, literal_column("x")).join(Address),
654            "SELECT users.id AS users_id, users.name AS users_name, x "
655            "FROM users JOIN addresses ON users.id = addresses.user_id",
656        )
657
658        self.assert_compile(
659            sess.query(literal_column("x"), User).join(Address),
660            "SELECT x, users.id AS users_id, users.name AS users_name "
661            "FROM users JOIN addresses ON users.id = addresses.user_id",
662        )
663
664    def test_left_is_none_and_query_has_no_entities(self):
665        Address = self.classes.Address
666
667        sess = create_session()
668
669        assert_raises_message(
670            sa_exc.InvalidRequestError,
671            r"No entities to join from; please use select_from\(\) to "
672            r"establish the left entity/selectable of this join",
673            sess.query().join,
674            Address,
675        )
676
677    def test_isouter_flag(self):
678        User = self.classes.User
679
680        self.assert_compile(
681            create_session().query(User).join("orders", isouter=True),
682            "SELECT users.id AS users_id, users.name AS users_name "
683            "FROM users LEFT OUTER JOIN orders ON users.id = orders.user_id",
684        )
685
686    def test_full_flag(self):
687        User = self.classes.User
688
689        self.assert_compile(
690            create_session().query(User).outerjoin("orders", full=True),
691            "SELECT users.id AS users_id, users.name AS users_name "
692            "FROM users FULL OUTER JOIN orders ON users.id = orders.user_id",
693        )
694
695    def test_multi_tuple_form(self):
696        """test the 'tuple' form of join, now superseded
697        by the two-element join() form.
698
699        Not deprecating this style as of yet.
700
701        """
702
703        Item, Order, User = (
704            self.classes.Item,
705            self.classes.Order,
706            self.classes.User,
707        )
708
709        sess = create_session()
710
711        # assert_raises(
712        #    sa.exc.SADeprecationWarning,
713        #    sess.query(User).join, (Order, User.id==Order.user_id)
714        # )
715
716        self.assert_compile(
717            sess.query(User).join((Order, User.id == Order.user_id)),
718            "SELECT users.id AS users_id, users.name AS users_name "
719            "FROM users JOIN orders ON users.id = orders.user_id",
720        )
721
722        self.assert_compile(
723            sess.query(User).join(
724                (Order, User.id == Order.user_id), (Item, Order.items)
725            ),
726            "SELECT users.id AS users_id, users.name AS users_name "
727            "FROM users JOIN orders ON users.id = orders.user_id "
728            "JOIN order_items AS order_items_1 ON orders.id = "
729            "order_items_1.order_id JOIN items ON items.id = "
730            "order_items_1.item_id",
731        )
732
733        # the old "backwards" form
734        self.assert_compile(
735            sess.query(User).join(("orders", Order)),
736            "SELECT users.id AS users_id, users.name AS users_name "
737            "FROM users JOIN orders ON users.id = orders.user_id",
738        )
739
740    def test_single_prop_1(self):
741        User = self.classes.User
742
743        sess = create_session()
744        self.assert_compile(
745            sess.query(User).join(User.orders),
746            "SELECT users.id AS users_id, users.name AS users_name "
747            "FROM users JOIN orders ON users.id = orders.user_id",
748        )
749
750    def test_single_prop_2(self):
751        Order, User = (self.classes.Order, self.classes.User)
752
753        sess = create_session()
754        self.assert_compile(
755            sess.query(User).join(Order.user),
756            "SELECT users.id AS users_id, users.name AS users_name "
757            "FROM orders JOIN users ON users.id = orders.user_id",
758        )
759
760    def test_single_prop_3(self):
761        Order, User = (self.classes.Order, self.classes.User)
762
763        sess = create_session()
764        oalias1 = aliased(Order)
765
766        self.assert_compile(
767            sess.query(User).join(oalias1.user),
768            "SELECT users.id AS users_id, users.name AS users_name "
769            "FROM orders AS orders_1 JOIN users "
770            "ON users.id = orders_1.user_id",
771        )
772
773    def test_single_prop_4(self):
774        (
775            Order,
776            User,
777        ) = (self.classes.Order, self.classes.User)
778
779        sess = create_session()
780        oalias1 = aliased(Order)
781        oalias2 = aliased(Order)
782        # another nonsensical query.  (from [ticket:1537]).
783        # in this case, the contract of "left to right" is honored
784        self.assert_compile(
785            sess.query(User).join(oalias1.user).join(oalias2.user),
786            "SELECT users.id AS users_id, users.name AS users_name "
787            "FROM orders AS orders_1 JOIN users "
788            "ON users.id = orders_1.user_id, "
789            "orders AS orders_2 JOIN users ON users.id = orders_2.user_id",
790        )
791
792    def test_single_prop_5(self):
793        (
794            Order,
795            User,
796        ) = (self.classes.Order, self.classes.User)
797
798        sess = create_session()
799        self.assert_compile(
800            sess.query(User).join(User.orders, Order.items),
801            "SELECT users.id AS users_id, users.name AS users_name FROM users "
802            "JOIN orders ON users.id = orders.user_id "
803            "JOIN order_items AS order_items_1 "
804            "ON orders.id = order_items_1.order_id JOIN items "
805            "ON items.id = order_items_1.item_id",
806        )
807
808    def test_single_prop_6(self):
809        User = self.classes.User
810
811        sess = create_session()
812        ualias = aliased(User)
813        self.assert_compile(
814            sess.query(ualias).join(ualias.orders),
815            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name "
816            "FROM users AS users_1 JOIN orders ON users_1.id = orders.user_id",
817        )
818
819    def test_single_prop_7(self):
820        Order, User = (self.classes.Order, self.classes.User)
821
822        sess = create_session()
823        # this query is somewhat nonsensical.  the old system didn't render a
824        # correct query for this. In this case its the most faithful to what
825        # was asked - there's no linkage between User.orders and "oalias",
826        # so two FROM elements are generated.
827        oalias = aliased(Order)
828        self.assert_compile(
829            sess.query(User).join(User.orders, oalias.items),
830            "SELECT users.id AS users_id, users.name AS users_name FROM users "
831            "JOIN orders ON users.id = orders.user_id, "
832            "orders AS orders_1 JOIN order_items AS order_items_1 "
833            "ON orders_1.id = order_items_1.order_id "
834            "JOIN items ON items.id = order_items_1.item_id",
835        )
836
837    def test_single_prop_8(self):
838        (
839            Order,
840            User,
841        ) = (self.classes.Order, self.classes.User)
842
843        sess = create_session()
844        # same as before using an aliased() for User as well
845        ualias = aliased(User)
846        oalias = aliased(Order)
847        self.assert_compile(
848            sess.query(ualias).join(ualias.orders, oalias.items),
849            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name "
850            "FROM users AS users_1 "
851            "JOIN orders ON users_1.id = orders.user_id, "
852            "orders AS orders_1 JOIN order_items AS order_items_1 "
853            "ON orders_1.id = order_items_1.order_id "
854            "JOIN items ON items.id = order_items_1.item_id",
855        )
856
857    def test_single_prop_9(self):
858        User = self.classes.User
859
860        sess = create_session()
861        self.assert_compile(
862            sess.query(User)
863            .filter(User.name == "ed")
864            .from_self()
865            .join(User.orders),
866            "SELECT anon_1.users_id AS anon_1_users_id, "
867            "anon_1.users_name AS anon_1_users_name "
868            "FROM (SELECT users.id AS users_id, users.name AS users_name "
869            "FROM users "
870            "WHERE users.name = :name_1) AS anon_1 JOIN orders "
871            "ON anon_1.users_id = orders.user_id",
872        )
873
874    def test_single_prop_10(self):
875        User, Address = (self.classes.User, self.classes.Address)
876
877        sess = create_session()
878        self.assert_compile(
879            sess.query(User)
880            .join(User.addresses, aliased=True)
881            .filter(Address.email_address == "foo"),
882            "SELECT users.id AS users_id, users.name AS users_name "
883            "FROM users JOIN addresses AS addresses_1 "
884            "ON users.id = addresses_1.user_id "
885            "WHERE addresses_1.email_address = :email_address_1",
886        )
887
888    def test_single_prop_11(self):
889        Item, Order, User, = (
890            self.classes.Item,
891            self.classes.Order,
892            self.classes.User,
893        )
894
895        sess = create_session()
896        self.assert_compile(
897            sess.query(User)
898            .join(User.orders, Order.items, aliased=True)
899            .filter(Item.id == 10),
900            "SELECT users.id AS users_id, users.name AS users_name "
901            "FROM users JOIN orders AS orders_1 "
902            "ON users.id = orders_1.user_id "
903            "JOIN order_items AS order_items_1 "
904            "ON orders_1.id = order_items_1.order_id "
905            "JOIN items AS items_1 ON items_1.id = order_items_1.item_id "
906            "WHERE items_1.id = :id_1",
907        )
908
909    def test_single_prop_12(self):
910        Order, User, Address = (
911            self.classes.Order,
912            self.classes.User,
913            self.classes.Address,
914        )
915
916        sess = create_session()
917        oalias1 = aliased(Order)
918        # test #1 for [ticket:1706]
919        ualias = aliased(User)
920        self.assert_compile(
921            sess.query(ualias)
922            .join(oalias1, ualias.orders)
923            .join(Address, ualias.addresses),
924            "SELECT users_1.id AS users_1_id, users_1.name AS "
925            "users_1_name FROM users AS users_1 JOIN orders AS orders_1 "
926            "ON users_1.id = orders_1.user_id JOIN addresses ON users_1.id "
927            "= addresses.user_id",
928        )
929
930    def test_single_prop_13(self):
931        Order, User, Address = (
932            self.classes.Order,
933            self.classes.User,
934            self.classes.Address,
935        )
936
937        sess = create_session()
938        # test #2 for [ticket:1706]
939        ualias = aliased(User)
940        ualias2 = aliased(User)
941        self.assert_compile(
942            sess.query(ualias)
943            .join(Address, ualias.addresses)
944            .join(ualias2, Address.user)
945            .join(Order, ualias.orders),
946            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name "
947            "FROM users "
948            "AS users_1 JOIN addresses ON users_1.id = addresses.user_id "
949            "JOIN users AS users_2 "
950            "ON users_2.id = addresses.user_id JOIN orders "
951            "ON users_1.id = orders.user_id",
952        )
953
954    def test_overlapping_paths(self):
955        User = self.classes.User
956
957        for aliased_ in (True, False):
958            # load a user who has an order that contains item id 3 and address
959            # id 1 (order 3, owned by jack)
960            result = (
961                create_session()
962                .query(User)
963                .join("orders", "items", aliased=aliased_)
964                .filter_by(id=3)
965                .join("orders", "address", aliased=aliased_)
966                .filter_by(id=1)
967                .all()
968            )
969            assert [User(id=7, name="jack")] == result
970
971    def test_overlapping_paths_multilevel(self):
972        User = self.classes.User
973
974        s = Session()
975        q = (
976            s.query(User)
977            .join("orders")
978            .join("addresses")
979            .join("orders", "items")
980            .join("addresses", "dingaling")
981        )
982        self.assert_compile(
983            q,
984            "SELECT users.id AS users_id, users.name AS users_name "
985            "FROM users JOIN orders ON users.id = orders.user_id "
986            "JOIN addresses ON users.id = addresses.user_id "
987            "JOIN order_items AS order_items_1 ON orders.id = "
988            "order_items_1.order_id "
989            "JOIN items ON items.id = order_items_1.item_id "
990            "JOIN dingalings ON addresses.id = dingalings.address_id",
991        )
992
993    def test_overlapping_paths_outerjoin(self):
994        User = self.classes.User
995
996        result = (
997            create_session()
998            .query(User)
999            .outerjoin("orders", "items")
1000            .filter_by(id=3)
1001            .outerjoin("orders", "address")
1002            .filter_by(id=1)
1003            .all()
1004        )
1005        assert [User(id=7, name="jack")] == result
1006
1007    def test_raises_on_dupe_target_rel(self):
1008        User = self.classes.User
1009
1010        assert_raises_message(
1011            sa.exc.SAWarning,
1012            "Pathed join target Order.items has already been joined to; "
1013            "skipping",
1014            lambda: create_session()
1015            .query(User)
1016            .outerjoin("orders", "items")
1017            .outerjoin("orders", "items"),
1018        )
1019
1020    def test_from_joinpoint(self):
1021        Item, User, Order = (
1022            self.classes.Item,
1023            self.classes.User,
1024            self.classes.Order,
1025        )
1026
1027        sess = create_session()
1028
1029        for oalias, ialias in [
1030            (True, True),
1031            (False, False),
1032            (True, False),
1033            (False, True),
1034        ]:
1035            eq_(
1036                sess.query(User)
1037                .join("orders", aliased=oalias)
1038                .join("items", from_joinpoint=True, aliased=ialias)
1039                .filter(Item.description == "item 4")
1040                .all(),
1041                [User(name="jack")],
1042            )
1043
1044            # use middle criterion
1045            eq_(
1046                sess.query(User)
1047                .join("orders", aliased=oalias)
1048                .filter(Order.user_id == 9)
1049                .join("items", from_joinpoint=True, aliased=ialias)
1050                .filter(Item.description == "item 4")
1051                .all(),
1052                [],
1053            )
1054
1055        orderalias = aliased(Order)
1056        itemalias = aliased(Item)
1057        eq_(
1058            sess.query(User)
1059            .join(orderalias, "orders")
1060            .join(itemalias, "items", from_joinpoint=True)
1061            .filter(itemalias.description == "item 4")
1062            .all(),
1063            [User(name="jack")],
1064        )
1065        eq_(
1066            sess.query(User)
1067            .join(orderalias, "orders")
1068            .join(itemalias, "items", from_joinpoint=True)
1069            .filter(orderalias.user_id == 9)
1070            .filter(itemalias.description == "item 4")
1071            .all(),
1072            [],
1073        )
1074
1075    def test_join_nonmapped_column(self):
1076        """test that the search for a 'left' doesn't trip on non-mapped cols"""
1077
1078        Order, User = self.classes.Order, self.classes.User
1079
1080        sess = create_session()
1081
1082        # intentionally join() with a non-existent "left" side
1083        self.assert_compile(
1084            sess.query(User.id, literal_column("foo")).join(Order.user),
1085            "SELECT users.id AS users_id, foo FROM "
1086            "orders JOIN users ON users.id = orders.user_id",
1087        )
1088
1089    def test_backwards_join(self):
1090        User, Address = self.classes.User, self.classes.Address
1091
1092        # a more controversial feature.  join from
1093        # User->Address, but the onclause is Address.user.
1094
1095        sess = create_session()
1096
1097        eq_(
1098            sess.query(User)
1099            .join(Address.user)
1100            .filter(Address.email_address == "ed@wood.com")
1101            .all(),
1102            [User(id=8, name="ed")],
1103        )
1104
1105        # its actually not so controversial if you view it in terms
1106        # of multiple entities.
1107        eq_(
1108            sess.query(User, Address)
1109            .join(Address.user)
1110            .filter(Address.email_address == "ed@wood.com")
1111            .all(),
1112            [(User(id=8, name="ed"), Address(email_address="ed@wood.com"))],
1113        )
1114
1115        # this was the controversial part.  now, raise an error if the feature
1116        # is abused.
1117        # before the error raise was added, this would silently work.....
1118        assert_raises(
1119            sa_exc.InvalidRequestError,
1120            sess.query(User).join,
1121            Address,
1122            Address.user,
1123        )
1124
1125        # but this one would silently fail
1126        adalias = aliased(Address)
1127        assert_raises(
1128            sa_exc.InvalidRequestError,
1129            sess.query(User).join,
1130            adalias,
1131            Address.user,
1132        )
1133
1134    def test_multiple_with_aliases(self):
1135        Order, User = self.classes.Order, self.classes.User
1136
1137        sess = create_session()
1138
1139        ualias = aliased(User)
1140        oalias1 = aliased(Order)
1141        oalias2 = aliased(Order)
1142        self.assert_compile(
1143            sess.query(ualias)
1144            .join(oalias1, ualias.orders)
1145            .join(oalias2, ualias.orders)
1146            .filter(or_(oalias1.user_id == 9, oalias2.user_id == 7)),
1147            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name "
1148            "FROM users AS users_1 "
1149            "JOIN orders AS orders_1 ON users_1.id = orders_1.user_id "
1150            "JOIN orders AS orders_2 ON "
1151            "users_1.id = orders_2.user_id "
1152            "WHERE orders_1.user_id = :user_id_1 "
1153            "OR orders_2.user_id = :user_id_2",
1154            use_default_dialect=True,
1155        )
1156
1157    def test_select_from_orm_joins(self):
1158        User, Order = self.classes.User, self.classes.Order
1159
1160        sess = create_session()
1161
1162        ualias = aliased(User)
1163        oalias1 = aliased(Order)
1164        oalias2 = aliased(Order)
1165
1166        self.assert_compile(
1167            join(User, oalias2, User.id == oalias2.user_id),
1168            "users JOIN orders AS orders_1 ON users.id = orders_1.user_id",
1169            use_default_dialect=True,
1170        )
1171
1172        self.assert_compile(
1173            join(User, oalias2, User.id == oalias2.user_id, full=True),
1174            "users FULL OUTER JOIN orders AS orders_1 "
1175            "ON users.id = orders_1.user_id",
1176            use_default_dialect=True,
1177        )
1178
1179        self.assert_compile(
1180            join(User, oalias2, User.id == oalias2.user_id, isouter=True),
1181            "users LEFT OUTER JOIN orders AS orders_1 "
1182            "ON users.id = orders_1.user_id",
1183            use_default_dialect=True,
1184        )
1185
1186        self.assert_compile(
1187            join(
1188                User,
1189                oalias2,
1190                User.id == oalias2.user_id,
1191                isouter=True,
1192                full=True,
1193            ),
1194            "users FULL OUTER JOIN orders AS orders_1 "
1195            "ON users.id = orders_1.user_id",
1196            use_default_dialect=True,
1197        )
1198
1199        self.assert_compile(
1200            join(User, oalias1).join(oalias2),
1201            "users JOIN orders AS orders_1 ON users.id = orders_1.user_id "
1202            "JOIN orders AS orders_2 ON users.id = orders_2.user_id",
1203            use_default_dialect=True,
1204        )
1205
1206        self.assert_compile(
1207            join(User, oalias1).join(oalias2, isouter=True),
1208            "users JOIN orders AS orders_1 ON users.id = orders_1.user_id "
1209            "LEFT OUTER JOIN orders AS orders_2 "
1210            "ON users.id = orders_2.user_id",
1211            use_default_dialect=True,
1212        )
1213
1214        self.assert_compile(
1215            join(User, oalias1).join(oalias2, full=True),
1216            "users JOIN orders AS orders_1 ON users.id = orders_1.user_id "
1217            "FULL OUTER JOIN orders AS orders_2 "
1218            "ON users.id = orders_2.user_id",
1219            use_default_dialect=True,
1220        )
1221
1222        self.assert_compile(
1223            join(User, oalias1).join(oalias2, full=True, isouter=True),
1224            "users JOIN orders AS orders_1 ON users.id = orders_1.user_id "
1225            "FULL OUTER JOIN orders AS orders_2 "
1226            "ON users.id = orders_2.user_id",
1227            use_default_dialect=True,
1228        )
1229
1230        self.assert_compile(
1231            join(ualias, oalias1, ualias.orders),
1232            "users AS users_1 JOIN orders AS orders_1 "
1233            "ON users_1.id = orders_1.user_id",
1234            use_default_dialect=True,
1235        )
1236
1237        self.assert_compile(
1238            sess.query(ualias).select_from(
1239                join(ualias, oalias1, ualias.orders)
1240            ),
1241            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name "
1242            "FROM users AS users_1 "
1243            "JOIN orders AS orders_1 ON users_1.id = orders_1.user_id",
1244            use_default_dialect=True,
1245        )
1246
1247        self.assert_compile(
1248            sess.query(User, ualias).select_from(
1249                join(ualias, oalias1, ualias.orders)
1250            ),
1251            "SELECT users.id AS users_id, users.name AS users_name, "
1252            "users_1.id AS users_1_id, "
1253            "users_1.name AS users_1_name FROM users, users AS users_1 "
1254            "JOIN orders AS orders_1 ON users_1.id = orders_1.user_id",
1255            use_default_dialect=True,
1256        )
1257
1258        # this fails (and we cant quite fix right now).
1259        if False:
1260            self.assert_compile(
1261                sess.query(User, ualias)
1262                .join(oalias1, ualias.orders)
1263                .join(oalias2, User.id == oalias2.user_id)
1264                .filter(or_(oalias1.user_id == 9, oalias2.user_id == 7)),
1265                "SELECT users.id AS users_id, users.name AS users_name, "
1266                "users_1.id AS users_1_id, users_1.name AS "
1267                "users_1_name FROM users JOIN orders AS orders_2 "
1268                "ON users.id = orders_2.user_id, "
1269                "users AS users_1 JOIN orders AS orders_1 "
1270                "ON users_1.id = orders_1.user_id  "
1271                "WHERE orders_1.user_id = :user_id_1 "
1272                "OR orders_2.user_id = :user_id_2",
1273                use_default_dialect=True,
1274            )
1275
1276        # this is the same thing using explicit orm.join() (which now offers
1277        # multiple again)
1278        self.assert_compile(
1279            sess.query(User, ualias)
1280            .select_from(
1281                join(ualias, oalias1, ualias.orders),
1282                join(User, oalias2, User.id == oalias2.user_id),
1283            )
1284            .filter(or_(oalias1.user_id == 9, oalias2.user_id == 7)),
1285            "SELECT users.id AS users_id, users.name AS users_name, "
1286            "users_1.id AS users_1_id, users_1.name AS "
1287            "users_1_name FROM users AS users_1 JOIN orders AS orders_1 "
1288            "ON users_1.id = orders_1.user_id, "
1289            "users JOIN orders AS orders_2 ON users.id = orders_2.user_id "
1290            "WHERE orders_1.user_id = :user_id_1 "
1291            "OR orders_2.user_id = :user_id_2",
1292            use_default_dialect=True,
1293        )
1294
1295    def test_overlapping_backwards_joins(self):
1296        User, Order = self.classes.User, self.classes.Order
1297
1298        sess = create_session()
1299
1300        oalias1 = aliased(Order)
1301        oalias2 = aliased(Order)
1302
1303        # this is invalid SQL - joins from orders_1/orders_2 to User twice.
1304        # but that is what was asked for so they get it !
1305        self.assert_compile(
1306            sess.query(User).join(oalias1.user).join(oalias2.user),
1307            "SELECT users.id AS users_id, users.name AS users_name "
1308            "FROM orders AS orders_1 "
1309            "JOIN users ON users.id = orders_1.user_id, orders AS orders_2 "
1310            "JOIN users ON users.id = orders_2.user_id",
1311            use_default_dialect=True,
1312        )
1313
1314    def test_replace_multiple_from_clause(self):
1315        """test adding joins onto multiple FROM clauses"""
1316
1317        User, Order, Address = (
1318            self.classes.User,
1319            self.classes.Order,
1320            self.classes.Address,
1321        )
1322
1323        sess = create_session()
1324
1325        self.assert_compile(
1326            sess.query(Address, User)
1327            .join(Address.dingaling)
1328            .join(User.orders, Order.items),
1329            "SELECT addresses.id AS addresses_id, "
1330            "addresses.user_id AS addresses_user_id, "
1331            "addresses.email_address AS addresses_email_address, "
1332            "users.id AS users_id, "
1333            "users.name AS users_name FROM addresses JOIN dingalings "
1334            "ON addresses.id = dingalings.address_id, "
1335            "users JOIN orders ON users.id = orders.user_id "
1336            "JOIN order_items AS order_items_1 "
1337            "ON orders.id = order_items_1.order_id JOIN items "
1338            "ON items.id = order_items_1.item_id",
1339            use_default_dialect=True,
1340        )
1341
1342    def test_invalid_join_entity_from_single_from_clause(self):
1343        Address, Item = (self.classes.Address, self.classes.Item)
1344        sess = create_session()
1345
1346        q = sess.query(Address).select_from(Address)
1347
1348        assert_raises_message(
1349            sa.exc.InvalidRequestError,
1350            "Don't know how to join to .*Item.*. "
1351            r"Please use the .select_from\(\) "
1352            "method to establish an explicit left side, as well as",
1353            q.join,
1354            Item,
1355        )
1356
1357    def test_invalid_join_entity_from_no_from_clause(self):
1358        Address, Item = (self.classes.Address, self.classes.Item)
1359        sess = create_session()
1360
1361        q = sess.query(Address)
1362
1363        assert_raises_message(
1364            sa.exc.InvalidRequestError,
1365            "Don't know how to join to .*Item.*. "
1366            r"Please use the .select_from\(\) "
1367            "method to establish an explicit left side, as well as",
1368            q.join,
1369            Item,
1370        )
1371
1372    def test_invalid_join_entity_from_multiple_from_clause(self):
1373        """test adding joins onto multiple FROM clauses where
1374        we still need to say there's nothing to JOIN from"""
1375
1376        User, Address, Item = (
1377            self.classes.User,
1378            self.classes.Address,
1379            self.classes.Item,
1380        )
1381        sess = create_session()
1382
1383        q = sess.query(Address, User).join(Address.dingaling).join(User.orders)
1384
1385        assert_raises_message(
1386            sa.exc.InvalidRequestError,
1387            "Don't know how to join to .*Item.*. "
1388            r"Please use the .select_from\(\) "
1389            "method to establish an explicit left side, as well as",
1390            q.join,
1391            Item,
1392        )
1393
1394    def test_join_explicit_left_multiple_from_clause(self):
1395        """test adding joins onto multiple FROM clauses where
1396        it is ambiguous which FROM should be used when an
1397        ON clause is given"""
1398
1399        User = self.classes.User
1400
1401        sess = create_session()
1402
1403        u1 = aliased(User)
1404
1405        # in this case, two FROM objects, one
1406        # is users, the other is u1_alias.
1407        # User.addresses looks for the "users" table and can match
1408        # to both u1_alias and users if the match is not specific enough
1409        q = sess.query(User, u1).select_from(User, u1).join(User.addresses)
1410
1411        self.assert_compile(
1412            q,
1413            "SELECT users.id AS users_id, users.name AS users_name, "
1414            "users_1.id AS users_1_id, users_1.name AS users_1_name "
1415            "FROM users AS users_1, "
1416            "users JOIN addresses ON users.id = addresses.user_id",
1417        )
1418
1419        q = sess.query(User, u1).select_from(User, u1).join(u1.addresses)
1420
1421        self.assert_compile(
1422            q,
1423            "SELECT users.id AS users_id, users.name AS users_name, "
1424            "users_1.id AS users_1_id, users_1.name AS users_1_name "
1425            "FROM users, "
1426            "users AS users_1 JOIN addresses "
1427            "ON users_1.id = addresses.user_id",
1428        )
1429
1430    def test_join_explicit_left_multiple_adapted(self):
1431        """test adding joins onto multiple FROM clauses where
1432        it is ambiguous which FROM should be used when an
1433        ON clause is given"""
1434
1435        User = self.classes.User
1436
1437        sess = create_session()
1438
1439        u1 = aliased(User)
1440        u2 = aliased(User)
1441
1442        # in this case, two FROM objects, one
1443        # is users, the other is u1_alias.
1444        # User.addresses looks for the "users" table and can match
1445        # to both u1_alias and users if the match is not specific enough
1446        assert_raises_message(
1447            sa_exc.InvalidRequestError,
1448            "Can't identify which entity in which to assign the "
1449            "left side of this join.",
1450            sess.query(u1, u2).select_from(u1, u2).join,
1451            User.addresses,
1452        )
1453
1454        # more specific ON clause
1455        self.assert_compile(
1456            sess.query(u1, u2).select_from(u1, u2).join(u2.addresses),
1457            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name, "
1458            "users_2.id AS users_2_id, users_2.name AS users_2_name "
1459            "FROM users AS users_1, "
1460            "users AS users_2 JOIN addresses "
1461            "ON users_2.id = addresses.user_id",
1462        )
1463
1464    def test_join_entity_from_multiple_from_clause(self):
1465        """test adding joins onto multiple FROM clauses where
1466        it is ambiguous which FROM should be used"""
1467
1468        User, Order, Address, Dingaling = (
1469            self.classes.User,
1470            self.classes.Order,
1471            self.classes.Address,
1472            self.classes.Dingaling,
1473        )
1474
1475        sess = create_session()
1476
1477        q = sess.query(Address, User).join(Address.dingaling).join(User.orders)
1478
1479        a1 = aliased(Address)
1480
1481        assert_raises_message(
1482            sa.exc.InvalidRequestError,
1483            "Can't determine which FROM clause to join from, there are "
1484            "multiple FROMS which can join to this entity. "
1485            r"Please use the .select_from\(\) "
1486            "method to establish an explicit left side, as well as",
1487            q.join,
1488            a1,
1489        )
1490
1491        # to resolve, add an ON clause
1492
1493        # the user->orders join is chosen to join to a1
1494        self.assert_compile(
1495            q.join(a1, Order.address_id == a1.id),
1496            "SELECT addresses.id AS addresses_id, "
1497            "addresses.user_id AS addresses_user_id, "
1498            "addresses.email_address AS addresses_email_address, "
1499            "users.id AS users_id, users.name AS users_name "
1500            "FROM addresses JOIN dingalings "
1501            "ON addresses.id = dingalings.address_id, "
1502            "users JOIN orders "
1503            "ON users.id = orders.user_id "
1504            "JOIN addresses AS addresses_1 "
1505            "ON orders.address_id = addresses_1.id",
1506        )
1507
1508        # the address->dingalings join is chosen to join to a1
1509        self.assert_compile(
1510            q.join(a1, Dingaling.address_id == a1.id),
1511            "SELECT addresses.id AS addresses_id, "
1512            "addresses.user_id AS addresses_user_id, "
1513            "addresses.email_address AS addresses_email_address, "
1514            "users.id AS users_id, users.name AS users_name "
1515            "FROM addresses JOIN dingalings "
1516            "ON addresses.id = dingalings.address_id "
1517            "JOIN addresses AS addresses_1 "
1518            "ON dingalings.address_id = addresses_1.id, "
1519            "users JOIN orders ON users.id = orders.user_id",
1520        )
1521
1522    def test_join_entity_from_multiple_entities(self):
1523        """test adding joins onto multiple FROM clauses where
1524        it is ambiguous which FROM should be used"""
1525
1526        Order, Address, Dingaling = (
1527            self.classes.Order,
1528            self.classes.Address,
1529            self.classes.Dingaling,
1530        )
1531
1532        sess = create_session()
1533
1534        q = sess.query(Order, Dingaling)
1535
1536        a1 = aliased(Address)
1537
1538        assert_raises_message(
1539            sa.exc.InvalidRequestError,
1540            "Can't determine which FROM clause to join from, there are "
1541            "multiple FROMS which can join to this entity. "
1542            r"Please use the .select_from\(\) "
1543            "method to establish an explicit left side, as well as",
1544            q.join,
1545            a1,
1546        )
1547
1548        # to resolve, add an ON clause
1549
1550        # Order is chosen to join to a1
1551        self.assert_compile(
1552            q.join(a1, Order.address_id == a1.id),
1553            "SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
1554            "orders.address_id AS orders_address_id, "
1555            "orders.description AS orders_description, "
1556            "orders.isopen AS orders_isopen, dingalings.id AS dingalings_id, "
1557            "dingalings.address_id AS dingalings_address_id, "
1558            "dingalings.data AS dingalings_data "
1559            "FROM dingalings, orders "
1560            "JOIN addresses AS addresses_1 "
1561            "ON orders.address_id = addresses_1.id",
1562        )
1563
1564        # Dingaling is chosen to join to a1
1565        self.assert_compile(
1566            q.join(a1, Dingaling.address_id == a1.id),
1567            "SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
1568            "orders.address_id AS orders_address_id, "
1569            "orders.description AS orders_description, "
1570            "orders.isopen AS orders_isopen, dingalings.id AS dingalings_id, "
1571            "dingalings.address_id AS dingalings_address_id, "
1572            "dingalings.data AS dingalings_data "
1573            "FROM orders, dingalings JOIN addresses AS addresses_1 "
1574            "ON dingalings.address_id = addresses_1.id",
1575        )
1576
1577    def test_clause_present_in_froms_twice_w_onclause(self):
1578        # test [ticket:4584]
1579        Order, Address, User = (
1580            self.classes.Order,
1581            self.classes.Address,
1582            self.classes.User,
1583        )
1584
1585        sess = create_session()
1586
1587        a1 = aliased(Address)
1588
1589        q = sess.query(Order).select_from(Order, a1, User)
1590        assert_raises_message(
1591            sa.exc.InvalidRequestError,
1592            "Can't determine which FROM clause to join from, there are "
1593            "multiple FROMS which can join to this entity. "
1594            r"Please use the .select_from\(\) "
1595            "method to establish an explicit left side, as well as",
1596            q.outerjoin,
1597            a1,
1598        )
1599
1600        # the condition which occurs here is: Query._from_obj contains both
1601        # "a1" by itself as well as a join that "a1" is part of.
1602        # find_left_clause_to_join_from() needs to include removal of froms
1603        # that are in the _hide_froms of joins the same way
1604        # Selectable._get_display_froms does.
1605        q = sess.query(Order).select_from(Order, a1, User)
1606        q = q.outerjoin(a1, a1.id == Order.address_id)
1607        q = q.outerjoin(User, a1.user_id == User.id)
1608
1609        self.assert_compile(
1610            q,
1611            "SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
1612            "orders.address_id AS orders_address_id, "
1613            "orders.description AS orders_description, "
1614            "orders.isopen AS orders_isopen "
1615            "FROM orders "
1616            "LEFT OUTER JOIN addresses AS addresses_1 "
1617            "ON addresses_1.id = orders.address_id "
1618            "LEFT OUTER JOIN users ON addresses_1.user_id = users.id",
1619        )
1620
1621    def test_clause_present_in_froms_twice_wo_onclause(self):
1622        # test [ticket:4584]
1623        Address, Dingaling, User = (
1624            self.classes.Address,
1625            self.classes.Dingaling,
1626            self.classes.User,
1627        )
1628
1629        sess = create_session()
1630
1631        a1 = aliased(Address)
1632
1633        # the condition which occurs here is: Query._from_obj contains both
1634        # "a1" by itself as well as a join that "a1" is part of.
1635        # find_left_clause_to_join_from() needs to include removal of froms
1636        # that are in the _hide_froms of joins the same way
1637        # Selectable._get_display_froms does.
1638        q = sess.query(User).select_from(Dingaling, a1, User)
1639        q = q.outerjoin(a1, User.id == a1.user_id)
1640        q = q.outerjoin(Dingaling)
1641
1642        self.assert_compile(
1643            q,
1644            "SELECT users.id AS users_id, users.name AS users_name "
1645            "FROM users LEFT OUTER JOIN addresses AS addresses_1 "
1646            "ON users.id = addresses_1.user_id "
1647            "LEFT OUTER JOIN dingalings "
1648            "ON addresses_1.id = dingalings.address_id",
1649        )
1650
1651    def test_multiple_adaption(self):
1652        Item, Order, User = (
1653            self.classes.Item,
1654            self.classes.Order,
1655            self.classes.User,
1656        )
1657
1658        sess = create_session()
1659
1660        self.assert_compile(
1661            sess.query(User)
1662            .join(User.orders, Order.items, aliased=True)
1663            .filter(Order.id == 7)
1664            .filter(Item.id == 8),
1665            "SELECT users.id AS users_id, users.name AS users_name FROM users "
1666            "JOIN orders AS orders_1 "
1667            "ON users.id = orders_1.user_id JOIN order_items AS order_items_1 "
1668            "ON orders_1.id = order_items_1.order_id "
1669            "JOIN items AS items_1 ON items_1.id = order_items_1.item_id "
1670            "WHERE orders_1.id = :id_1 AND items_1.id = :id_2",
1671            use_default_dialect=True,
1672        )
1673
1674    def test_onclause_conditional_adaption(self):
1675        Item, Order, orders, order_items, User = (
1676            self.classes.Item,
1677            self.classes.Order,
1678            self.tables.orders,
1679            self.tables.order_items,
1680            self.classes.User,
1681        )
1682
1683        sess = create_session()
1684
1685        # this is now a very weird test, nobody should really
1686        # be using the aliased flag in this way.
1687        self.assert_compile(
1688            sess.query(User)
1689            .join(User.orders, aliased=True)
1690            .join(
1691                Item,
1692                and_(
1693                    Order.id == order_items.c.order_id,
1694                    order_items.c.item_id == Item.id,
1695                ),
1696                from_joinpoint=True,
1697                aliased=True,
1698            ),
1699            "SELECT users.id AS users_id, users.name AS users_name FROM users "
1700            "JOIN orders AS orders_1 ON users.id = orders_1.user_id "
1701            "JOIN items AS items_1 "
1702            "ON orders_1.id = order_items.order_id "
1703            "AND order_items.item_id = items_1.id",
1704            use_default_dialect=True,
1705        )
1706
1707        oalias = orders.select()
1708        self.assert_compile(
1709            sess.query(User)
1710            .join(oalias, User.orders)
1711            .join(
1712                Item,
1713                and_(
1714                    Order.id == order_items.c.order_id,
1715                    order_items.c.item_id == Item.id,
1716                ),
1717                from_joinpoint=True,
1718            ),
1719            "SELECT users.id AS users_id, users.name AS users_name "
1720            "FROM users JOIN "
1721            "(SELECT orders.id AS id, orders.user_id AS user_id, "
1722            "orders.address_id AS address_id, orders.description "
1723            "AS description, orders.isopen AS isopen FROM orders) AS anon_1 "
1724            "ON users.id = anon_1.user_id JOIN items "
1725            "ON anon_1.id = order_items.order_id "
1726            "AND order_items.item_id = items.id",
1727            use_default_dialect=True,
1728        )
1729
1730        # query.join(<stuff>, aliased=True).join(target, sql_expression)
1731        # or: query.join(path_to_some_joined_table_mapper).join(target,
1732        # sql_expression)
1733
1734    def test_pure_expression_error(self):
1735        addresses, users = self.tables.addresses, self.tables.users
1736
1737        sess = create_session()
1738
1739        self.assert_compile(
1740            sess.query(users).join(addresses),
1741            "SELECT users.id AS users_id, users.name AS users_name "
1742            "FROM users JOIN addresses ON users.id = addresses.user_id",
1743        )
1744
1745    def test_orderby_arg_bug(self):
1746        User, users, Order = (
1747            self.classes.User,
1748            self.tables.users,
1749            self.classes.Order,
1750        )
1751
1752        sess = create_session()
1753        # no arg error
1754        (
1755            sess.query(User)
1756            .join("orders", aliased=True)
1757            .order_by(Order.id)
1758            .reset_joinpoint()
1759            .order_by(users.c.id)
1760            .all()
1761        )
1762
1763    def test_no_onclause(self):
1764        Item, User, Order = (
1765            self.classes.Item,
1766            self.classes.User,
1767            self.classes.Order,
1768        )
1769
1770        sess = create_session()
1771
1772        eq_(
1773            sess.query(User)
1774            .select_from(join(User, Order).join(Item, Order.items))
1775            .filter(Item.description == "item 4")
1776            .all(),
1777            [User(name="jack")],
1778        )
1779
1780        eq_(
1781            sess.query(User.name)
1782            .select_from(join(User, Order).join(Item, Order.items))
1783            .filter(Item.description == "item 4")
1784            .all(),
1785            [("jack",)],
1786        )
1787
1788        eq_(
1789            sess.query(User)
1790            .join(Order)
1791            .join(Item, Order.items)
1792            .filter(Item.description == "item 4")
1793            .all(),
1794            [User(name="jack")],
1795        )
1796
1797    def test_clause_onclause(self):
1798        Item, Order, users, order_items, User = (
1799            self.classes.Item,
1800            self.classes.Order,
1801            self.tables.users,
1802            self.tables.order_items,
1803            self.classes.User,
1804        )
1805
1806        sess = create_session()
1807
1808        eq_(
1809            sess.query(User)
1810            .join(Order, User.id == Order.user_id)
1811            .join(order_items, Order.id == order_items.c.order_id)
1812            .join(Item, order_items.c.item_id == Item.id)
1813            .filter(Item.description == "item 4")
1814            .all(),
1815            [User(name="jack")],
1816        )
1817
1818        eq_(
1819            sess.query(User.name)
1820            .join(Order, User.id == Order.user_id)
1821            .join(order_items, Order.id == order_items.c.order_id)
1822            .join(Item, order_items.c.item_id == Item.id)
1823            .filter(Item.description == "item 4")
1824            .all(),
1825            [("jack",)],
1826        )
1827
1828        ualias = aliased(User)
1829        eq_(
1830            sess.query(ualias.name)
1831            .join(Order, ualias.id == Order.user_id)
1832            .join(order_items, Order.id == order_items.c.order_id)
1833            .join(Item, order_items.c.item_id == Item.id)
1834            .filter(Item.description == "item 4")
1835            .all(),
1836            [("jack",)],
1837        )
1838
1839        # explicit onclause with from_self(), means
1840        # the onclause must be aliased against the query's custom
1841        # FROM object
1842        eq_(
1843            sess.query(User)
1844            .order_by(User.id)
1845            .offset(2)
1846            .from_self()
1847            .join(Order, User.id == Order.user_id)
1848            .all(),
1849            [User(name="fred")],
1850        )
1851
1852        # same with an explicit select_from()
1853        eq_(
1854            sess.query(User)
1855            .select_entity_from(
1856                select([users]).order_by(User.id).offset(2).alias()
1857            )
1858            .join(Order, User.id == Order.user_id)
1859            .all(),
1860            [User(name="fred")],
1861        )
1862
1863    def test_aliased_classes(self):
1864        User, Address = self.classes.User, self.classes.Address
1865
1866        sess = create_session()
1867
1868        (user7, user8, user9, user10) = sess.query(User).all()
1869        (address1, address2, address3, address4, address5) = sess.query(
1870            Address
1871        ).all()
1872        expected = [
1873            (user7, address1),
1874            (user8, address2),
1875            (user8, address3),
1876            (user8, address4),
1877            (user9, address5),
1878            (user10, None),
1879        ]
1880
1881        q = sess.query(User)
1882        AdAlias = aliased(Address)
1883        q = q.add_entity(AdAlias).select_from(outerjoin(User, AdAlias))
1884        result = q.order_by(User.id, AdAlias.id).all()
1885        eq_(result, expected)
1886
1887        sess.expunge_all()
1888
1889        q = sess.query(User).add_entity(AdAlias)
1890        result = (
1891            q.select_from(outerjoin(User, AdAlias))
1892            .filter(AdAlias.email_address == "ed@bettyboop.com")
1893            .all()
1894        )
1895        eq_(result, [(user8, address3)])
1896
1897        result = (
1898            q.select_from(outerjoin(User, AdAlias, "addresses"))
1899            .filter(AdAlias.email_address == "ed@bettyboop.com")
1900            .all()
1901        )
1902        eq_(result, [(user8, address3)])
1903
1904        result = (
1905            q.select_from(outerjoin(User, AdAlias, User.id == AdAlias.user_id))
1906            .filter(AdAlias.email_address == "ed@bettyboop.com")
1907            .all()
1908        )
1909        eq_(result, [(user8, address3)])
1910
1911        # this is the first test where we are joining "backwards" - from
1912        # AdAlias to User even though
1913        # the query is against User
1914        q = sess.query(User, AdAlias)
1915        result = (
1916            q.join(AdAlias.user)
1917            .filter(User.name == "ed")
1918            .order_by(User.id, AdAlias.id)
1919        )
1920        eq_(
1921            result.all(),
1922            [(user8, address2), (user8, address3), (user8, address4)],
1923        )
1924
1925        q = (
1926            sess.query(User, AdAlias)
1927            .select_from(join(AdAlias, User, AdAlias.user))
1928            .filter(User.name == "ed")
1929        )
1930        eq_(
1931            result.all(),
1932            [(user8, address2), (user8, address3), (user8, address4)],
1933        )
1934
1935    def test_expression_onclauses(self):
1936        Order, User = self.classes.Order, self.classes.User
1937
1938        sess = create_session()
1939
1940        subq = sess.query(User).subquery()
1941
1942        self.assert_compile(
1943            sess.query(User).join(subq, User.name == subq.c.name),
1944            "SELECT users.id AS users_id, users.name AS users_name "
1945            "FROM users JOIN (SELECT users.id AS id, users.name "
1946            "AS name FROM users) AS anon_1 ON users.name = anon_1.name",
1947            use_default_dialect=True,
1948        )
1949
1950        subq = sess.query(Order).subquery()
1951        self.assert_compile(
1952            sess.query(User).join(subq, User.id == subq.c.user_id),
1953            "SELECT users.id AS users_id, users.name AS users_name FROM "
1954            "users JOIN (SELECT orders.id AS id, orders.user_id AS user_id, "
1955            "orders.address_id AS address_id, orders.description AS "
1956            "description, orders.isopen AS isopen FROM orders) AS "
1957            "anon_1 ON users.id = anon_1.user_id",
1958            use_default_dialect=True,
1959        )
1960
1961        self.assert_compile(
1962            sess.query(User).join(Order, User.id == Order.user_id),
1963            "SELECT users.id AS users_id, users.name AS users_name "
1964            "FROM users JOIN orders ON users.id = orders.user_id",
1965            use_default_dialect=True,
1966        )
1967
1968    def test_implicit_joins_from_aliases(self):
1969        Item, User, Order = (
1970            self.classes.Item,
1971            self.classes.User,
1972            self.classes.Order,
1973        )
1974
1975        sess = create_session()
1976        OrderAlias = aliased(Order)
1977
1978        eq_(
1979            sess.query(OrderAlias)
1980            .join("items")
1981            .filter_by(description="item 3")
1982            .order_by(OrderAlias.id)
1983            .all(),
1984            [
1985                Order(
1986                    address_id=1,
1987                    description="order 1",
1988                    isopen=0,
1989                    user_id=7,
1990                    id=1,
1991                ),
1992                Order(
1993                    address_id=4,
1994                    description="order 2",
1995                    isopen=0,
1996                    user_id=9,
1997                    id=2,
1998                ),
1999                Order(
2000                    address_id=1,
2001                    description="order 3",
2002                    isopen=1,
2003                    user_id=7,
2004                    id=3,
2005                ),
2006            ],
2007        )
2008
2009        eq_(
2010            sess.query(User, OrderAlias, Item.description)
2011            .join(OrderAlias, "orders")
2012            .join("items", from_joinpoint=True)
2013            .filter_by(description="item 3")
2014            .order_by(User.id, OrderAlias.id)
2015            .all(),
2016            [
2017                (
2018                    User(name="jack", id=7),
2019                    Order(
2020                        address_id=1,
2021                        description="order 1",
2022                        isopen=0,
2023                        user_id=7,
2024                        id=1,
2025                    ),
2026                    "item 3",
2027                ),
2028                (
2029                    User(name="jack", id=7),
2030                    Order(
2031                        address_id=1,
2032                        description="order 3",
2033                        isopen=1,
2034                        user_id=7,
2035                        id=3,
2036                    ),
2037                    "item 3",
2038                ),
2039                (
2040                    User(name="fred", id=9),
2041                    Order(
2042                        address_id=4,
2043                        description="order 2",
2044                        isopen=0,
2045                        user_id=9,
2046                        id=2,
2047                    ),
2048                    "item 3",
2049                ),
2050            ],
2051        )
2052
2053    def test_aliased_classes_m2m(self):
2054        Item, Order = self.classes.Item, self.classes.Order
2055
2056        sess = create_session()
2057
2058        (order1, order2, order3, order4, order5) = sess.query(Order).all()
2059        (item1, item2, item3, item4, item5) = sess.query(Item).all()
2060        expected = [
2061            (order1, item1),
2062            (order1, item2),
2063            (order1, item3),
2064            (order2, item1),
2065            (order2, item2),
2066            (order2, item3),
2067            (order3, item3),
2068            (order3, item4),
2069            (order3, item5),
2070            (order4, item1),
2071            (order4, item5),
2072            (order5, item5),
2073        ]
2074
2075        q = sess.query(Order)
2076        q = (
2077            q.add_entity(Item)
2078            .select_from(join(Order, Item, "items"))
2079            .order_by(Order.id, Item.id)
2080        )
2081        result = q.all()
2082        eq_(result, expected)
2083
2084        IAlias = aliased(Item)
2085        q = (
2086            sess.query(Order, IAlias)
2087            .select_from(join(Order, IAlias, "items"))
2088            .filter(IAlias.description == "item 3")
2089        )
2090        result = q.all()
2091        eq_(result, [(order1, item3), (order2, item3), (order3, item3)])
2092
2093    def test_joins_from_adapted_entities(self):
2094        User = self.classes.User
2095
2096        # test for #1853
2097
2098        session = create_session()
2099        first = session.query(User)
2100        second = session.query(User)
2101        unioned = first.union(second)
2102        subquery = session.query(User.id).subquery()
2103        join = subquery, subquery.c.id == User.id
2104        joined = unioned.outerjoin(*join)
2105        self.assert_compile(
2106            joined,
2107            "SELECT anon_1.users_id AS "
2108            "anon_1_users_id, anon_1.users_name AS "
2109            "anon_1_users_name FROM (SELECT users.id "
2110            "AS users_id, users.name AS users_name "
2111            "FROM users UNION SELECT users.id AS "
2112            "users_id, users.name AS users_name FROM "
2113            "users) AS anon_1 LEFT OUTER JOIN (SELECT "
2114            "users.id AS id FROM users) AS anon_2 ON "
2115            "anon_2.id = anon_1.users_id",
2116            use_default_dialect=True,
2117        )
2118
2119        first = session.query(User.id)
2120        second = session.query(User.id)
2121        unioned = first.union(second)
2122        subquery = session.query(User.id).subquery()
2123        join = subquery, subquery.c.id == User.id
2124        joined = unioned.outerjoin(*join)
2125        self.assert_compile(
2126            joined,
2127            "SELECT anon_1.users_id AS anon_1_users_id "
2128            "FROM (SELECT users.id AS users_id FROM "
2129            "users UNION SELECT users.id AS users_id "
2130            "FROM users) AS anon_1 LEFT OUTER JOIN "
2131            "(SELECT users.id AS id FROM users) AS "
2132            "anon_2 ON anon_2.id = anon_1.users_id",
2133            use_default_dialect=True,
2134        )
2135
2136    def test_joins_from_adapted_entities_isouter(self):
2137        User = self.classes.User
2138
2139        # test for #1853
2140
2141        session = create_session()
2142        first = session.query(User)
2143        second = session.query(User)
2144        unioned = first.union(second)
2145        subquery = session.query(User.id).subquery()
2146        join = subquery, subquery.c.id == User.id
2147        joined = unioned.join(*join, isouter=True)
2148        self.assert_compile(
2149            joined,
2150            "SELECT anon_1.users_id AS "
2151            "anon_1_users_id, anon_1.users_name AS "
2152            "anon_1_users_name FROM (SELECT users.id "
2153            "AS users_id, users.name AS users_name "
2154            "FROM users UNION SELECT users.id AS "
2155            "users_id, users.name AS users_name FROM "
2156            "users) AS anon_1 LEFT OUTER JOIN (SELECT "
2157            "users.id AS id FROM users) AS anon_2 ON "
2158            "anon_2.id = anon_1.users_id",
2159            use_default_dialect=True,
2160        )
2161
2162        first = session.query(User.id)
2163        second = session.query(User.id)
2164        unioned = first.union(second)
2165        subquery = session.query(User.id).subquery()
2166        join = subquery, subquery.c.id == User.id
2167        joined = unioned.join(*join, isouter=True)
2168        self.assert_compile(
2169            joined,
2170            "SELECT anon_1.users_id AS anon_1_users_id "
2171            "FROM (SELECT users.id AS users_id FROM "
2172            "users UNION SELECT users.id AS users_id "
2173            "FROM users) AS anon_1 LEFT OUTER JOIN "
2174            "(SELECT users.id AS id FROM users) AS "
2175            "anon_2 ON anon_2.id = anon_1.users_id",
2176            use_default_dialect=True,
2177        )
2178
2179    def test_reset_joinpoint(self):
2180        User = self.classes.User
2181
2182        for aliased_ in (True, False):
2183            # load a user who has an order that contains item id 3 and address
2184            # id 1 (order 3, owned by jack)
2185            result = (
2186                create_session()
2187                .query(User)
2188                .join("orders", "items", aliased=aliased_)
2189                .filter_by(id=3)
2190                .reset_joinpoint()
2191                .join("orders", "address", aliased=aliased_)
2192                .filter_by(id=1)
2193                .all()
2194            )
2195            assert [User(id=7, name="jack")] == result
2196
2197            result = (
2198                create_session()
2199                .query(User)
2200                .join("orders", "items", aliased=aliased_, isouter=True)
2201                .filter_by(id=3)
2202                .reset_joinpoint()
2203                .join("orders", "address", aliased=aliased_, isouter=True)
2204                .filter_by(id=1)
2205                .all()
2206            )
2207            assert [User(id=7, name="jack")] == result
2208
2209            result = (
2210                create_session()
2211                .query(User)
2212                .outerjoin("orders", "items", aliased=aliased_)
2213                .filter_by(id=3)
2214                .reset_joinpoint()
2215                .outerjoin("orders", "address", aliased=aliased_)
2216                .filter_by(id=1)
2217                .all()
2218            )
2219            assert [User(id=7, name="jack")] == result
2220
2221    def test_overlap_with_aliases(self):
2222        orders, User, users = (
2223            self.tables.orders,
2224            self.classes.User,
2225            self.tables.users,
2226        )
2227
2228        oalias = orders.alias("oalias")
2229
2230        result = (
2231            create_session()
2232            .query(User)
2233            .select_from(users.join(oalias))
2234            .filter(
2235                oalias.c.description.in_(["order 1", "order 2", "order 3"])
2236            )
2237            .join("orders", "items")
2238            .order_by(User.id)
2239            .all()
2240        )
2241        assert [User(id=7, name="jack"), User(id=9, name="fred")] == result
2242
2243        result = (
2244            create_session()
2245            .query(User)
2246            .select_from(users.join(oalias))
2247            .filter(
2248                oalias.c.description.in_(["order 1", "order 2", "order 3"])
2249            )
2250            .join("orders", "items")
2251            .filter_by(id=4)
2252            .all()
2253        )
2254        assert [User(id=7, name="jack")] == result
2255
2256    def test_aliased(self):
2257        """test automatic generation of aliased joins."""
2258
2259        Item, Order, User, Address = (
2260            self.classes.Item,
2261            self.classes.Order,
2262            self.classes.User,
2263            self.classes.Address,
2264        )
2265
2266        sess = create_session()
2267
2268        # test a basic aliasized path
2269        q = (
2270            sess.query(User)
2271            .join("addresses", aliased=True)
2272            .filter_by(email_address="jack@bean.com")
2273        )
2274        assert [User(id=7)] == q.all()
2275
2276        q = (
2277            sess.query(User)
2278            .join("addresses", aliased=True)
2279            .filter(Address.email_address == "jack@bean.com")
2280        )
2281        assert [User(id=7)] == q.all()
2282
2283        q = (
2284            sess.query(User)
2285            .join("addresses", aliased=True)
2286            .filter(
2287                or_(
2288                    Address.email_address == "jack@bean.com",
2289                    Address.email_address == "fred@fred.com",
2290                )
2291            )
2292        )
2293        assert [User(id=7), User(id=9)] == q.all()
2294
2295        # test two aliasized paths, one to 'orders' and the other to
2296        # 'orders','items'. one row is returned because user 7 has order 3 and
2297        # also has order 1 which has item 1
2298        # this tests a o2m join and a m2m join.
2299        q = (
2300            sess.query(User)
2301            .join("orders", aliased=True)
2302            .filter(Order.description == "order 3")
2303            .join("orders", "items", aliased=True)
2304            .filter(Item.description == "item 1")
2305        )
2306        assert q.count() == 1
2307        assert [User(id=7)] == q.all()
2308
2309        # test the control version - same joins but not aliased.  rows are not
2310        # returned because order 3 does not have item 1
2311        q = (
2312            sess.query(User)
2313            .join("orders")
2314            .filter(Order.description == "order 3")
2315            .join("orders", "items")
2316            .filter(Item.description == "item 1")
2317        )
2318        assert [] == q.all()
2319        assert q.count() == 0
2320
2321        # the left half of the join condition of the any() is aliased.
2322        q = (
2323            sess.query(User)
2324            .join("orders", aliased=True)
2325            .filter(Order.items.any(Item.description == "item 4"))
2326        )
2327        assert [User(id=7)] == q.all()
2328
2329        # test that aliasing gets reset when join() is called
2330        q = (
2331            sess.query(User)
2332            .join("orders", aliased=True)
2333            .filter(Order.description == "order 3")
2334            .join("orders", aliased=True)
2335            .filter(Order.description == "order 5")
2336        )
2337        assert q.count() == 1
2338        assert [User(id=7)] == q.all()
2339
2340    def test_aliased_order_by(self):
2341        User = self.classes.User
2342
2343        sess = create_session()
2344
2345        ualias = aliased(User)
2346        eq_(
2347            sess.query(User, ualias)
2348            .filter(User.id > ualias.id)
2349            .order_by(desc(ualias.id), User.name)
2350            .all(),
2351            [
2352                (User(id=10, name="chuck"), User(id=9, name="fred")),
2353                (User(id=10, name="chuck"), User(id=8, name="ed")),
2354                (User(id=9, name="fred"), User(id=8, name="ed")),
2355                (User(id=10, name="chuck"), User(id=7, name="jack")),
2356                (User(id=8, name="ed"), User(id=7, name="jack")),
2357                (User(id=9, name="fred"), User(id=7, name="jack")),
2358            ],
2359        )
2360
2361    def test_plain_table(self):
2362        addresses, User = self.tables.addresses, self.classes.User
2363
2364        sess = create_session()
2365
2366        eq_(
2367            sess.query(User.name)
2368            .join(addresses, User.id == addresses.c.user_id)
2369            .order_by(User.id)
2370            .all(),
2371            [("jack",), ("ed",), ("ed",), ("ed",), ("fred",)],
2372        )
2373
2374    def test_no_joinpoint_expr(self):
2375        User, users = self.classes.User, self.tables.users
2376
2377        sess = create_session()
2378
2379        # these are consistent regardless of
2380        # select_from() being present.
2381
2382        assert_raises_message(
2383            sa_exc.InvalidRequestError,
2384            "Don't know how to join to .*User.*. "
2385            r"Please use the .select_from\(\) "
2386            "method to establish an explicit left side, as well as",
2387            sess.query(users.c.id).join,
2388            User,
2389        )
2390
2391        assert_raises_message(
2392            sa_exc.InvalidRequestError,
2393            "Don't know how to join to .*User.* "
2394            r"Please use the .select_from\(\) "
2395            "method to establish an explicit left side, as well as",
2396            sess.query(users.c.id).select_from(users).join,
2397            User,
2398        )
2399
2400    def test_on_clause_no_right_side(self):
2401        User = self.classes.User
2402        Address = self.classes.Address
2403        sess = create_session()
2404
2405        assert_raises_message(
2406            sa_exc.ArgumentError,
2407            "Expected mapped entity or selectable/table as join target",
2408            sess.query(User).join,
2409            User.id == Address.user_id,
2410        )
2411
2412    def test_on_clause_no_right_side_two(self):
2413        User = self.classes.User
2414        Address = self.classes.Address
2415        sess = create_session()
2416
2417        right = Address.user_id
2418
2419        assert_raises_message(
2420            sa_exc.ArgumentError,
2421            "Join target %s does not refer to a mapped entity" % right,
2422            sess.query(User).join,
2423            Address.user_id,
2424        )
2425
2426    def test_select_from(self):
2427        """Test that the left edge of the join can be set reliably with
2428        select_from()."""
2429
2430        Item, Order, User = (
2431            self.classes.Item,
2432            self.classes.Order,
2433            self.classes.User,
2434        )
2435
2436        sess = create_session()
2437        self.assert_compile(
2438            sess.query(Item.id)
2439            .select_from(User)
2440            .join(User.orders)
2441            .join(Order.items),
2442            "SELECT items.id AS items_id FROM users JOIN orders ON "
2443            "users.id = orders.user_id JOIN order_items AS order_items_1 "
2444            "ON orders.id = order_items_1.order_id JOIN items ON items.id = "
2445            "order_items_1.item_id",
2446            use_default_dialect=True,
2447        )
2448
2449        # here, the join really wants to add a second FROM clause
2450        # for "Item".  but select_from disallows that
2451        self.assert_compile(
2452            sess.query(Item.id)
2453            .select_from(User)
2454            .join(Item, User.id == Item.id),
2455            "SELECT items.id AS items_id FROM users JOIN items "
2456            "ON users.id = items.id",
2457            use_default_dialect=True,
2458        )
2459
2460    def test_from_self_resets_joinpaths(self):
2461        """test a join from from_self() doesn't confuse joins inside the subquery
2462        with the outside.
2463        """
2464
2465        Item, Keyword = self.classes.Item, self.classes.Keyword
2466
2467        sess = create_session()
2468
2469        self.assert_compile(
2470            sess.query(Item)
2471            .join(Item.keywords)
2472            .from_self(Keyword)
2473            .join(Item.keywords),
2474            "SELECT keywords.id AS keywords_id, "
2475            "keywords.name AS keywords_name "
2476            "FROM (SELECT items.id AS items_id, "
2477            "items.description AS items_description "
2478            "FROM items JOIN item_keywords AS item_keywords_1 ON items.id = "
2479            "item_keywords_1.item_id JOIN keywords "
2480            "ON keywords.id = item_keywords_1.keyword_id) "
2481            "AS anon_1 JOIN item_keywords AS item_keywords_2 ON "
2482            "anon_1.items_id = item_keywords_2.item_id "
2483            "JOIN keywords ON "
2484            "keywords.id = item_keywords_2.keyword_id",
2485            use_default_dialect=True,
2486        )
2487
2488
2489class JoinFromSelectableTest(fixtures.MappedTest, AssertsCompiledSQL):
2490    __dialect__ = "default"
2491    run_setup_mappers = "once"
2492
2493    @classmethod
2494    def define_tables(cls, metadata):
2495        Table("table1", metadata, Column("id", Integer, primary_key=True))
2496        Table(
2497            "table2",
2498            metadata,
2499            Column("id", Integer, primary_key=True),
2500            Column("t1_id", Integer),
2501        )
2502
2503    @classmethod
2504    def setup_classes(cls):
2505        table1, table2 = cls.tables.table1, cls.tables.table2
2506
2507        class T1(cls.Comparable):
2508            pass
2509
2510        class T2(cls.Comparable):
2511            pass
2512
2513        mapper(T1, table1)
2514        mapper(T2, table2)
2515
2516    def test_select_mapped_to_mapped_explicit_left(self):
2517        T1, T2 = self.classes.T1, self.classes.T2
2518
2519        sess = Session()
2520        subq = (
2521            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2522            .group_by(T2.t1_id)
2523            .subquery()
2524        )
2525
2526        self.assert_compile(
2527            sess.query(subq.c.count, T1.id)
2528            .select_from(subq)
2529            .join(T1, subq.c.t1_id == T1.id),
2530            "SELECT anon_1.count AS anon_1_count, table1.id AS table1_id "
2531            "FROM (SELECT table2.t1_id AS t1_id, "
2532            "count(table2.id) AS count FROM table2 "
2533            "GROUP BY table2.t1_id) AS anon_1 JOIN table1 "
2534            "ON anon_1.t1_id = table1.id",
2535        )
2536
2537    def test_select_mapped_to_mapped_implicit_left(self):
2538        T1, T2 = self.classes.T1, self.classes.T2
2539
2540        sess = Session()
2541        subq = (
2542            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2543            .group_by(T2.t1_id)
2544            .subquery()
2545        )
2546
2547        self.assert_compile(
2548            sess.query(subq.c.count, T1.id).join(T1, subq.c.t1_id == T1.id),
2549            "SELECT anon_1.count AS anon_1_count, table1.id AS table1_id "
2550            "FROM (SELECT table2.t1_id AS t1_id, "
2551            "count(table2.id) AS count FROM table2 "
2552            "GROUP BY table2.t1_id) AS anon_1 JOIN table1 "
2553            "ON anon_1.t1_id = table1.id",
2554        )
2555
2556    def test_select_mapped_to_select_explicit_left(self):
2557        T1, T2 = self.classes.T1, self.classes.T2
2558
2559        sess = Session()
2560        subq = (
2561            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2562            .group_by(T2.t1_id)
2563            .subquery()
2564        )
2565
2566        self.assert_compile(
2567            sess.query(subq.c.count, T1.id)
2568            .select_from(T1)
2569            .join(subq, subq.c.t1_id == T1.id),
2570            "SELECT anon_1.count AS anon_1_count, table1.id AS table1_id "
2571            "FROM table1 JOIN (SELECT table2.t1_id AS t1_id, "
2572            "count(table2.id) AS count FROM table2 GROUP BY table2.t1_id) "
2573            "AS anon_1 ON anon_1.t1_id = table1.id",
2574        )
2575
2576    def test_select_mapped_to_select_implicit_left(self):
2577        T1, T2 = self.classes.T1, self.classes.T2
2578
2579        sess = Session()
2580        subq = (
2581            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2582            .group_by(T2.t1_id)
2583            .subquery()
2584        )
2585
2586        # without select_from
2587        self.assert_compile(
2588            sess.query(subq.c.count, T1.id).join(subq, subq.c.t1_id == T1.id),
2589            "SELECT anon_1.count AS anon_1_count, table1.id AS table1_id "
2590            "FROM table1 JOIN "
2591            "(SELECT table2.t1_id AS t1_id, count(table2.id) AS count "
2592            "FROM table2 GROUP BY table2.t1_id) "
2593            "AS anon_1 ON anon_1.t1_id = table1.id",
2594        )
2595
2596        # with select_from, same query
2597        self.assert_compile(
2598            sess.query(subq.c.count, T1.id)
2599            .select_from(T1)
2600            .join(subq, subq.c.t1_id == T1.id),
2601            "SELECT anon_1.count AS anon_1_count, table1.id AS table1_id "
2602            "FROM table1 JOIN "
2603            "(SELECT table2.t1_id AS t1_id, count(table2.id) AS count "
2604            "FROM table2 GROUP BY table2.t1_id) "
2605            "AS anon_1 ON anon_1.t1_id = table1.id",
2606        )
2607
2608    def test_mapped_select_to_mapped_implicit_left(self):
2609        T1, T2 = self.classes.T1, self.classes.T2
2610
2611        sess = Session()
2612        subq = (
2613            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2614            .group_by(T2.t1_id)
2615            .subquery()
2616        )
2617
2618        # without select_from
2619        self.assert_compile(
2620            sess.query(T1.id, subq.c.count).join(T1, subq.c.t1_id == T1.id),
2621            "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count "
2622            "FROM (SELECT table2.t1_id AS t1_id, count(table2.id) AS count "
2623            "FROM table2 GROUP BY table2.t1_id) AS anon_1 "
2624            "JOIN table1 ON anon_1.t1_id = table1.id",
2625        )
2626
2627        # with select_from, same query
2628        self.assert_compile(
2629            sess.query(T1.id, subq.c.count)
2630            .select_from(subq)
2631            .join(T1, subq.c.t1_id == T1.id),
2632            "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count "
2633            "FROM (SELECT table2.t1_id AS t1_id, count(table2.id) AS count "
2634            "FROM table2 GROUP BY table2.t1_id) AS anon_1 "
2635            "JOIN table1 ON anon_1.t1_id = table1.id",
2636        )
2637
2638    def test_mapped_select_to_mapped_explicit_left(self):
2639        T1, T2 = self.classes.T1, self.classes.T2
2640
2641        sess = Session()
2642        subq = (
2643            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2644            .group_by(T2.t1_id)
2645            .subquery()
2646        )
2647
2648        self.assert_compile(
2649            sess.query(T1.id, subq.c.count)
2650            .select_from(subq)
2651            .join(T1, subq.c.t1_id == T1.id),
2652            "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count "
2653            "FROM (SELECT table2.t1_id AS t1_id, count(table2.id) AS count "
2654            "FROM table2 GROUP BY table2.t1_id) AS anon_1 JOIN table1 "
2655            "ON anon_1.t1_id = table1.id",
2656        )
2657
2658    def test_mapped_select_to_select_explicit_left(self):
2659        T1, T2 = self.classes.T1, self.classes.T2
2660
2661        sess = Session()
2662        subq = (
2663            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2664            .group_by(T2.t1_id)
2665            .subquery()
2666        )
2667
2668        self.assert_compile(
2669            sess.query(T1.id, subq.c.count)
2670            .select_from(T1)
2671            .join(subq, subq.c.t1_id == T1.id),
2672            "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count "
2673            "FROM table1 JOIN (SELECT table2.t1_id AS t1_id, "
2674            "count(table2.id) AS count "
2675            "FROM table2 GROUP BY table2.t1_id) AS anon_1 "
2676            "ON anon_1.t1_id = table1.id",
2677        )
2678
2679    def test_mapped_select_to_select_implicit_left(self):
2680        T1, T2 = self.classes.T1, self.classes.T2
2681
2682        sess = Session()
2683        subq = (
2684            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2685            .group_by(T2.t1_id)
2686            .subquery()
2687        )
2688
2689        self.assert_compile(
2690            sess.query(T1.id, subq.c.count).join(subq, subq.c.t1_id == T1.id),
2691            "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count "
2692            "FROM table1 JOIN (SELECT table2.t1_id AS t1_id, "
2693            "count(table2.id) AS count "
2694            "FROM table2 GROUP BY table2.t1_id) AS anon_1 "
2695            "ON anon_1.t1_id = table1.id",
2696        )
2697
2698    def test_mapped_to_select_implicit_left_w_aliased(self):
2699        T1, T2 = self.classes.T1, self.classes.T2
2700
2701        sess = Session()
2702        subq = (
2703            sess.query(T2.t1_id, func.count(T2.id).label("count"))
2704            .group_by(T2.t1_id)
2705            .subquery()
2706        )
2707
2708        assert_raises_message(
2709            sa_exc.InvalidRequestError,
2710            r"The aliased=True parameter on query.join\(\) only works with "
2711            "an ORM entity, not a plain selectable, as the target.",
2712            # this doesn't work, so have it raise an error
2713            sess.query(T1.id).join,
2714            subq,
2715            subq.c.t1_id == T1.id,
2716            aliased=True,
2717        )
2718
2719
2720class MultiplePathTest(fixtures.MappedTest, AssertsCompiledSQL):
2721    @classmethod
2722    def define_tables(cls, metadata):
2723        Table(
2724            "t1",
2725            metadata,
2726            Column(
2727                "id", Integer, primary_key=True, test_needs_autoincrement=True
2728            ),
2729            Column("data", String(30)),
2730        )
2731        Table(
2732            "t2",
2733            metadata,
2734            Column(
2735                "id", Integer, primary_key=True, test_needs_autoincrement=True
2736            ),
2737            Column("data", String(30)),
2738        )
2739
2740        Table(
2741            "t1t2_1",
2742            metadata,
2743            Column("t1id", Integer, ForeignKey("t1.id")),
2744            Column("t2id", Integer, ForeignKey("t2.id")),
2745        )
2746
2747        Table(
2748            "t1t2_2",
2749            metadata,
2750            Column("t1id", Integer, ForeignKey("t1.id")),
2751            Column("t2id", Integer, ForeignKey("t2.id")),
2752        )
2753
2754    def test_basic(self):
2755        t2, t1t2_1, t1t2_2, t1 = (
2756            self.tables.t2,
2757            self.tables.t1t2_1,
2758            self.tables.t1t2_2,
2759            self.tables.t1,
2760        )
2761
2762        class T1(object):
2763            pass
2764
2765        class T2(object):
2766            pass
2767
2768        mapper(
2769            T1,
2770            t1,
2771            properties={
2772                "t2s_1": relationship(T2, secondary=t1t2_1),
2773                "t2s_2": relationship(T2, secondary=t1t2_2),
2774            },
2775        )
2776        mapper(T2, t2)
2777
2778        q = (
2779            create_session()
2780            .query(T1)
2781            .join("t2s_1")
2782            .filter(t2.c.id == 5)
2783            .reset_joinpoint()
2784            .join("t2s_2")
2785        )
2786        self.assert_compile(
2787            q,
2788            "SELECT t1.id AS t1_id, t1.data AS t1_data FROM t1 "
2789            "JOIN t1t2_1 AS t1t2_1_1 "
2790            "ON t1.id = t1t2_1_1.t1id JOIN t2 ON t2.id = t1t2_1_1.t2id "
2791            "JOIN t1t2_2 AS t1t2_2_1 "
2792            "ON t1.id = t1t2_2_1.t1id JOIN t2 ON t2.id = t1t2_2_1.t2id "
2793            "WHERE t2.id = :id_1",
2794            use_default_dialect=True,
2795        )
2796
2797
2798class SelfRefMixedTest(fixtures.MappedTest, AssertsCompiledSQL):
2799    run_setup_mappers = "once"
2800    __dialect__ = default.DefaultDialect()
2801
2802    @classmethod
2803    def define_tables(cls, metadata):
2804        Table(
2805            "nodes",
2806            metadata,
2807            Column(
2808                "id", Integer, primary_key=True, test_needs_autoincrement=True
2809            ),
2810            Column("parent_id", Integer, ForeignKey("nodes.id")),
2811        )
2812
2813        Table(
2814            "sub_table",
2815            metadata,
2816            Column(
2817                "id", Integer, primary_key=True, test_needs_autoincrement=True
2818            ),
2819            Column("node_id", Integer, ForeignKey("nodes.id")),
2820        )
2821
2822        Table(
2823            "assoc_table",
2824            metadata,
2825            Column("left_id", Integer, ForeignKey("nodes.id")),
2826            Column("right_id", Integer, ForeignKey("nodes.id")),
2827        )
2828
2829    @classmethod
2830    def setup_classes(cls):
2831        nodes, assoc_table, sub_table = (
2832            cls.tables.nodes,
2833            cls.tables.assoc_table,
2834            cls.tables.sub_table,
2835        )
2836
2837        class Node(cls.Comparable):
2838            pass
2839
2840        class Sub(cls.Comparable):
2841            pass
2842
2843        mapper(
2844            Node,
2845            nodes,
2846            properties={
2847                "children": relationship(
2848                    Node,
2849                    lazy="select",
2850                    join_depth=3,
2851                    backref=backref("parent", remote_side=[nodes.c.id]),
2852                ),
2853                "subs": relationship(Sub),
2854                "assoc": relationship(
2855                    Node,
2856                    secondary=assoc_table,
2857                    primaryjoin=nodes.c.id == assoc_table.c.left_id,
2858                    secondaryjoin=nodes.c.id == assoc_table.c.right_id,
2859                ),
2860            },
2861        )
2862        mapper(Sub, sub_table)
2863
2864    def test_o2m_aliased_plus_o2m(self):
2865        Node, Sub = self.classes.Node, self.classes.Sub
2866
2867        sess = create_session()
2868        n1 = aliased(Node)
2869
2870        self.assert_compile(
2871            sess.query(Node).join(n1, Node.children).join(Sub, n1.subs),
2872            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
2873            "FROM nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
2874            "JOIN sub_table ON nodes_1.id = sub_table.node_id",
2875        )
2876
2877        self.assert_compile(
2878            sess.query(Node).join(n1, Node.children).join(Sub, Node.subs),
2879            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
2880            "FROM nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
2881            "JOIN sub_table ON nodes.id = sub_table.node_id",
2882        )
2883
2884    def test_m2m_aliased_plus_o2m(self):
2885        Node, Sub = self.classes.Node, self.classes.Sub
2886
2887        sess = create_session()
2888        n1 = aliased(Node)
2889
2890        self.assert_compile(
2891            sess.query(Node).join(n1, Node.assoc).join(Sub, n1.subs),
2892            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
2893            "FROM nodes JOIN assoc_table AS assoc_table_1 ON nodes.id = "
2894            "assoc_table_1.left_id JOIN nodes AS nodes_1 ON nodes_1.id = "
2895            "assoc_table_1.right_id JOIN sub_table "
2896            "ON nodes_1.id = sub_table.node_id",
2897        )
2898
2899        self.assert_compile(
2900            sess.query(Node).join(n1, Node.assoc).join(Sub, Node.subs),
2901            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
2902            "FROM nodes JOIN assoc_table AS assoc_table_1 ON nodes.id = "
2903            "assoc_table_1.left_id JOIN nodes AS nodes_1 ON nodes_1.id = "
2904            "assoc_table_1.right_id JOIN sub_table "
2905            "ON nodes.id = sub_table.node_id",
2906        )
2907
2908
2909class CreateJoinsTest(fixtures.ORMTest, AssertsCompiledSQL):
2910    __dialect__ = "default"
2911
2912    def _inherits_fixture(self):
2913        m = MetaData()
2914        base = Table("base", m, Column("id", Integer, primary_key=True))
2915        a = Table(
2916            "a",
2917            m,
2918            Column("id", Integer, ForeignKey("base.id"), primary_key=True),
2919            Column("b_id", Integer, ForeignKey("b.id")),
2920        )
2921        b = Table(
2922            "b",
2923            m,
2924            Column("id", Integer, ForeignKey("base.id"), primary_key=True),
2925            Column("c_id", Integer, ForeignKey("c.id")),
2926        )
2927        c = Table(
2928            "c",
2929            m,
2930            Column("id", Integer, ForeignKey("base.id"), primary_key=True),
2931        )
2932
2933        class Base(object):
2934            pass
2935
2936        class A(Base):
2937            pass
2938
2939        class B(Base):
2940            pass
2941
2942        class C(Base):
2943            pass
2944
2945        mapper(Base, base)
2946        mapper(
2947            A,
2948            a,
2949            inherits=Base,
2950            properties={"b": relationship(B, primaryjoin=a.c.b_id == b.c.id)},
2951        )
2952        mapper(
2953            B,
2954            b,
2955            inherits=Base,
2956            properties={"c": relationship(C, primaryjoin=b.c.c_id == c.c.id)},
2957        )
2958        mapper(C, c, inherits=Base)
2959        return A, B, C, Base
2960
2961    def test_double_level_aliased_exists(self):
2962        A, B, C, Base = self._inherits_fixture()
2963        s = Session()
2964        self.assert_compile(
2965            s.query(A).filter(A.b.has(B.c.has(C.id == 5))),
2966            "SELECT a.id AS a_id, base.id AS base_id, a.b_id AS a_b_id "
2967            "FROM base JOIN a ON base.id = a.id WHERE "
2968            "EXISTS (SELECT 1 FROM (SELECT base.id AS base_id, b.id AS "
2969            "b_id, b.c_id AS b_c_id FROM base JOIN b ON base.id = b.id) "
2970            "AS anon_1 WHERE a.b_id = anon_1.b_id AND (EXISTS "
2971            "(SELECT 1 FROM (SELECT base.id AS base_id, c.id AS c_id "
2972            "FROM base JOIN c ON base.id = c.id) AS anon_2 "
2973            "WHERE anon_1.b_c_id = anon_2.c_id AND anon_2.c_id = :id_1"
2974            ")))",
2975        )
2976
2977
2978class JoinToNonPolyAliasesTest(fixtures.MappedTest, AssertsCompiledSQL):
2979    """test joins to an aliased selectable and that we can refer to that
2980    aliased selectable in filter criteria.
2981
2982    Basically testing that the aliasing Query applies to with_polymorphic
2983    targets doesn't leak into non-polymorphic mappers.
2984
2985
2986    """
2987
2988    __dialect__ = "default"
2989    run_create_tables = None
2990    run_deletes = None
2991
2992    @classmethod
2993    def define_tables(cls, metadata):
2994        Table(
2995            "parent",
2996            metadata,
2997            Column("id", Integer, primary_key=True),
2998            Column("data", String(50)),
2999        )
3000        Table(
3001            "child",
3002            metadata,
3003            Column("id", Integer, primary_key=True),
3004            Column("parent_id", Integer, ForeignKey("parent.id")),
3005            Column("data", String(50)),
3006        )
3007
3008    @classmethod
3009    def setup_mappers(cls):
3010        parent, child = cls.tables.parent, cls.tables.child
3011
3012        class Parent(cls.Comparable):
3013            pass
3014
3015        class Child(cls.Comparable):
3016            pass
3017
3018        mp = mapper(Parent, parent)
3019        mapper(Child, child)
3020
3021        derived = select([child]).alias()
3022        npc = aliased(Child, derived)
3023        cls.npc = npc
3024        cls.derived = derived
3025        mp.add_property("npc", relationship(npc))
3026
3027    def test_join_parent_child(self):
3028        Parent = self.classes.Parent
3029
3030        sess = Session()
3031        self.assert_compile(
3032            sess.query(Parent)
3033            .join(Parent.npc)
3034            .filter(self.derived.c.data == "x"),
3035            "SELECT parent.id AS parent_id, parent.data AS parent_data "
3036            "FROM parent JOIN (SELECT child.id AS id, "
3037            "child.parent_id AS parent_id, "
3038            "child.data AS data "
3039            "FROM child) AS anon_1 ON parent.id = anon_1.parent_id "
3040            "WHERE anon_1.data = :data_1",
3041        )
3042
3043    def test_join_parent_child_select_from(self):
3044        Parent = self.classes.Parent
3045        npc = self.npc
3046        sess = Session()
3047        self.assert_compile(
3048            sess.query(npc)
3049            .select_from(Parent)
3050            .join(Parent.npc)
3051            .filter(self.derived.c.data == "x"),
3052            "SELECT anon_1.id AS anon_1_id, anon_1.parent_id "
3053            "AS anon_1_parent_id, anon_1.data AS anon_1_data "
3054            "FROM parent JOIN (SELECT child.id AS id, child.parent_id AS "
3055            "parent_id, child.data AS data FROM child) AS anon_1 ON "
3056            "parent.id = anon_1.parent_id WHERE anon_1.data = :data_1",
3057        )
3058
3059    def test_join_select_parent_child(self):
3060        Parent = self.classes.Parent
3061        npc = self.npc
3062        sess = Session()
3063        self.assert_compile(
3064            sess.query(Parent, npc)
3065            .join(Parent.npc)
3066            .filter(self.derived.c.data == "x"),
3067            "SELECT parent.id AS parent_id, parent.data AS parent_data, "
3068            "anon_1.id AS anon_1_id, anon_1.parent_id AS anon_1_parent_id, "
3069            "anon_1.data AS anon_1_data FROM parent JOIN "
3070            "(SELECT child.id AS id, child.parent_id AS parent_id, "
3071            "child.data AS data FROM child) AS anon_1 ON parent.id = "
3072            "anon_1.parent_id WHERE anon_1.data = :data_1",
3073        )
3074
3075
3076class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
3077    run_setup_mappers = "once"
3078    run_inserts = "once"
3079    run_deletes = None
3080    __dialect__ = "default"
3081
3082    @classmethod
3083    def define_tables(cls, metadata):
3084        Table(
3085            "nodes",
3086            metadata,
3087            Column(
3088                "id", Integer, primary_key=True, test_needs_autoincrement=True
3089            ),
3090            Column("parent_id", Integer, ForeignKey("nodes.id")),
3091            Column("data", String(30)),
3092        )
3093
3094    @classmethod
3095    def setup_classes(cls):
3096        class Node(cls.Comparable):
3097            def append(self, node):
3098                self.children.append(node)
3099
3100    @classmethod
3101    def setup_mappers(cls):
3102        Node, nodes = cls.classes.Node, cls.tables.nodes
3103
3104        mapper(
3105            Node,
3106            nodes,
3107            properties={
3108                "children": relationship(
3109                    Node,
3110                    lazy="select",
3111                    join_depth=3,
3112                    backref=backref("parent", remote_side=[nodes.c.id]),
3113                )
3114            },
3115        )
3116
3117    @classmethod
3118    def insert_data(cls, connection):
3119        Node = cls.classes.Node
3120
3121        sess = create_session(connection)
3122        n1 = Node(data="n1")
3123        n1.append(Node(data="n11"))
3124        n1.append(Node(data="n12"))
3125        n1.append(Node(data="n13"))
3126        n1.children[1].append(Node(data="n121"))
3127        n1.children[1].append(Node(data="n122"))
3128        n1.children[1].append(Node(data="n123"))
3129        sess.add(n1)
3130        sess.flush()
3131        sess.close()
3132
3133    def test_join_1(self):
3134        Node = self.classes.Node
3135        sess = create_session()
3136
3137        node = (
3138            sess.query(Node)
3139            .join("children", aliased=True)
3140            .filter_by(data="n122")
3141            .first()
3142        )
3143        assert node.data == "n12"
3144
3145    def test_join_2(self):
3146        Node = self.classes.Node
3147        sess = create_session()
3148        ret = (
3149            sess.query(Node.data)
3150            .join(Node.children, aliased=True)
3151            .filter_by(data="n122")
3152            .all()
3153        )
3154        assert ret == [("n12",)]
3155
3156    def test_join_3_filter_by(self):
3157        Node = self.classes.Node
3158        sess = create_session()
3159        q = (
3160            sess.query(Node)
3161            .join("children", "children", aliased=True)
3162            .filter_by(data="n122")
3163        )
3164        self.assert_compile(
3165            q,
3166            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, "
3167            "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 "
3168            "ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 "
3169            "ON nodes_1.id = nodes_2.parent_id WHERE nodes_2.data = :data_1",
3170            checkparams={"data_1": "n122"},
3171        )
3172        node = q.first()
3173        eq_(node.data, "n1")
3174
3175    def test_join_3_filter(self):
3176        Node = self.classes.Node
3177        sess = create_session()
3178        q = (
3179            sess.query(Node)
3180            .join("children", "children", aliased=True)
3181            .filter(Node.data == "n122")
3182        )
3183        self.assert_compile(
3184            q,
3185            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, "
3186            "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 "
3187            "ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 "
3188            "ON nodes_1.id = nodes_2.parent_id WHERE nodes_2.data = :data_1",
3189            checkparams={"data_1": "n122"},
3190        )
3191        node = q.first()
3192        eq_(node.data, "n1")
3193
3194    def test_join_4_filter_by(self):
3195        Node = self.classes.Node
3196        sess = create_session()
3197
3198        q = (
3199            sess.query(Node)
3200            .filter_by(data="n122")
3201            .join("parent", aliased=True)
3202            .filter_by(data="n12")
3203            .join("parent", aliased=True, from_joinpoint=True)
3204            .filter_by(data="n1")
3205        )
3206
3207        self.assert_compile(
3208            q,
3209            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, "
3210            "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 "
3211            "ON nodes_1.id = nodes.parent_id JOIN nodes AS nodes_2 "
3212            "ON nodes_2.id = nodes_1.parent_id WHERE nodes.data = :data_1 "
3213            "AND nodes_1.data = :data_2 AND nodes_2.data = :data_3",
3214            checkparams={"data_1": "n122", "data_2": "n12", "data_3": "n1"},
3215        )
3216
3217        node = q.first()
3218        eq_(node.data, "n122")
3219
3220    def test_join_4_filter(self):
3221        Node = self.classes.Node
3222        sess = create_session()
3223
3224        q = (
3225            sess.query(Node)
3226            .filter(Node.data == "n122")
3227            .join("parent", aliased=True)
3228            .filter(Node.data == "n12")
3229            .join("parent", aliased=True, from_joinpoint=True)
3230            .filter(Node.data == "n1")
3231        )
3232
3233        self.assert_compile(
3234            q,
3235            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, "
3236            "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 "
3237            "ON nodes_1.id = nodes.parent_id JOIN nodes AS nodes_2 "
3238            "ON nodes_2.id = nodes_1.parent_id WHERE nodes.data = :data_1 "
3239            "AND nodes_1.data = :data_2 AND nodes_2.data = :data_3",
3240            checkparams={"data_1": "n122", "data_2": "n12", "data_3": "n1"},
3241        )
3242
3243        node = q.first()
3244        eq_(node.data, "n122")
3245
3246    def test_string_or_prop_aliased_one(self):
3247        """test that join('foo') behaves the same as join(Cls.foo) in a self
3248        referential scenario.
3249
3250        """
3251
3252        Node = self.classes.Node
3253
3254        sess = create_session()
3255        nalias = aliased(
3256            Node, sess.query(Node).filter_by(data="n1").subquery()
3257        )
3258
3259        q1 = (
3260            sess.query(nalias)
3261            .join(nalias.children, aliased=True)
3262            .join(Node.children, from_joinpoint=True)
3263            .filter(Node.data == "n1")
3264        )
3265
3266        q2 = (
3267            sess.query(nalias)
3268            .join(nalias.children, aliased=True)
3269            .join("children", from_joinpoint=True)
3270            .filter(Node.data == "n1")
3271        )
3272
3273        for q in (q1, q2):
3274            self.assert_compile(
3275                q,
3276                "SELECT anon_1.id AS anon_1_id, anon_1.parent_id AS "
3277                "anon_1_parent_id, anon_1.data AS anon_1_data FROM "
3278                "(SELECT nodes.id AS id, nodes.parent_id AS parent_id, "
3279                "nodes.data AS data FROM nodes WHERE nodes.data = :data_1) "
3280                "AS anon_1 JOIN nodes AS nodes_1 ON anon_1.id = "
3281                "nodes_1.parent_id JOIN nodes "
3282                "ON nodes_1.id = nodes.parent_id "
3283                "WHERE nodes_1.data = :data_2",
3284                use_default_dialect=True,
3285                checkparams={"data_1": "n1", "data_2": "n1"},
3286            )
3287
3288    def test_string_or_prop_aliased_two(self):
3289        Node = self.classes.Node
3290
3291        sess = create_session()
3292        nalias = aliased(
3293            Node, sess.query(Node).filter_by(data="n1").subquery()
3294        )
3295
3296        q1 = (
3297            sess.query(Node)
3298            .filter(Node.data == "n1")
3299            .join(nalias.children, aliased=True)
3300            .filter(nalias.data == "n2")
3301            .join(Node.children, aliased=True, from_joinpoint=True)
3302            .filter(Node.data == "n3")
3303            .join(Node.children, from_joinpoint=True)
3304            .filter(Node.data == "n4")
3305        )
3306
3307        q2 = (
3308            sess.query(Node)
3309            .filter(Node.data == "n1")
3310            .join(nalias.children, aliased=True)
3311            .filter(nalias.data == "n2")
3312            .join("children", aliased=True, from_joinpoint=True)
3313            .filter(Node.data == "n3")
3314            .join("children", from_joinpoint=True)
3315            .filter(Node.data == "n4")
3316        )
3317
3318        for q in (q1, q2):
3319            self.assert_compile(
3320                q,
3321                "SELECT nodes.id AS nodes_id, nodes.parent_id "
3322                "AS nodes_parent_id, nodes.data AS nodes_data "
3323                "FROM (SELECT nodes.id AS id, nodes.parent_id AS parent_id, "
3324                "nodes.data AS data FROM nodes WHERE nodes.data = :data_1) "
3325                "AS anon_1 JOIN nodes AS nodes_1 "
3326                "ON anon_1.id = nodes_1.parent_id JOIN nodes AS nodes_2 "
3327                "ON nodes_1.id = nodes_2.parent_id JOIN nodes "
3328                "ON nodes_2.id = nodes.parent_id WHERE nodes.data = :data_2 "
3329                "AND anon_1.data = :data_3 AND nodes_2.data = :data_4 "
3330                "AND nodes_2.data = :data_5",
3331                use_default_dialect=True,
3332                checkparams={
3333                    "data_1": "n1",
3334                    "data_2": "n1",
3335                    "data_3": "n2",
3336                    "data_4": "n3",
3337                    "data_5": "n4",
3338                },
3339            )
3340
3341    def test_from_self_inside_excludes_outside(self):
3342        """test the propagation of aliased() from inside to outside
3343        on a from_self()..
3344        """
3345
3346        Node = self.classes.Node
3347
3348        sess = create_session()
3349
3350        n1 = aliased(Node)
3351
3352        # n1 is not inside the from_self(), so all cols must be maintained
3353        # on the outside
3354        self.assert_compile(
3355            sess.query(Node)
3356            .filter(Node.data == "n122")
3357            .from_self(n1, Node.id),
3358            "SELECT nodes_1.id AS nodes_1_id, "
3359            "nodes_1.parent_id AS nodes_1_parent_id, "
3360            "nodes_1.data AS nodes_1_data, anon_1.nodes_id AS anon_1_nodes_id "
3361            "FROM nodes AS nodes_1, (SELECT nodes.id AS nodes_id, "
3362            "nodes.parent_id AS nodes_parent_id, "
3363            "nodes.data AS nodes_data FROM "
3364            "nodes WHERE nodes.data = :data_1) AS anon_1",
3365            use_default_dialect=True,
3366        )
3367
3368        parent = aliased(Node)
3369        grandparent = aliased(Node)
3370        q = (
3371            sess.query(Node, parent, grandparent)
3372            .join(parent, Node.parent)
3373            .join(grandparent, parent.parent)
3374            .filter(Node.data == "n122")
3375            .filter(parent.data == "n12")
3376            .filter(grandparent.data == "n1")
3377            .from_self()
3378            .limit(1)
3379        )
3380
3381        # parent, grandparent *are* inside the from_self(), so they
3382        # should get aliased to the outside.
3383        self.assert_compile(
3384            q,
3385            "SELECT anon_1.nodes_id AS anon_1_nodes_id, "
3386            "anon_1.nodes_parent_id AS anon_1_nodes_parent_id, "
3387            "anon_1.nodes_data AS anon_1_nodes_data, "
3388            "anon_1.nodes_1_id AS anon_1_nodes_1_id, "
3389            "anon_1.nodes_1_parent_id AS anon_1_nodes_1_parent_id, "
3390            "anon_1.nodes_1_data AS anon_1_nodes_1_data, "
3391            "anon_1.nodes_2_id AS anon_1_nodes_2_id, "
3392            "anon_1.nodes_2_parent_id AS anon_1_nodes_2_parent_id, "
3393            "anon_1.nodes_2_data AS anon_1_nodes_2_data "
3394            "FROM (SELECT nodes.id AS nodes_id, nodes.parent_id "
3395            "AS nodes_parent_id, nodes.data AS nodes_data, "
3396            "nodes_1.id AS nodes_1_id, "
3397            "nodes_1.parent_id AS nodes_1_parent_id, "
3398            "nodes_1.data AS nodes_1_data, nodes_2.id AS nodes_2_id, "
3399            "nodes_2.parent_id AS nodes_2_parent_id, nodes_2.data AS "
3400            "nodes_2_data FROM nodes JOIN nodes AS nodes_1 ON "
3401            "nodes_1.id = nodes.parent_id JOIN nodes AS nodes_2 "
3402            "ON nodes_2.id = nodes_1.parent_id "
3403            "WHERE nodes.data = :data_1 AND nodes_1.data = :data_2 AND "
3404            "nodes_2.data = :data_3) AS anon_1 LIMIT :param_1",
3405            {"param_1": 1},
3406            use_default_dialect=True,
3407        )
3408
3409    def test_join_to_self_no_aliases_raises(self):
3410        Node = self.classes.Node
3411
3412        s = Session()
3413        assert_raises_message(
3414            sa.exc.InvalidRequestError,
3415            "Can't construct a join from mapped class Node->nodes to mapped "
3416            "class Node->nodes, they are the same entity",
3417            s.query(Node).join,
3418            Node.children,
3419        )
3420
3421    def test_explicit_join_1(self):
3422        Node = self.classes.Node
3423        n1 = aliased(Node)
3424        n2 = aliased(Node)
3425
3426        self.assert_compile(
3427            join(Node, n1, "children").join(n2, "children"),
3428            "nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
3429            "JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id",
3430            use_default_dialect=True,
3431        )
3432
3433    def test_explicit_join_2(self):
3434        Node = self.classes.Node
3435        n1 = aliased(Node)
3436        n2 = aliased(Node)
3437
3438        self.assert_compile(
3439            join(Node, n1, Node.children).join(n2, n1.children),
3440            "nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
3441            "JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id",
3442            use_default_dialect=True,
3443        )
3444
3445    def test_explicit_join_3(self):
3446        Node = self.classes.Node
3447        n1 = aliased(Node)
3448        n2 = aliased(Node)
3449
3450        # the join_to_left=False here is unfortunate.   the default on this
3451        # flag should be False.
3452        self.assert_compile(
3453            join(Node, n1, Node.children).join(
3454                n2, Node.children, join_to_left=False
3455            ),
3456            "nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
3457            "JOIN nodes AS nodes_2 ON nodes.id = nodes_2.parent_id",
3458            use_default_dialect=True,
3459        )
3460
3461    def test_explicit_join_4(self):
3462        Node = self.classes.Node
3463        sess = create_session()
3464        n1 = aliased(Node)
3465        n2 = aliased(Node)
3466
3467        self.assert_compile(
3468            sess.query(Node).join(n1, Node.children).join(n2, n1.children),
3469            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, "
3470            "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 "
3471            "ON nodes.id = nodes_1.parent_id "
3472            "JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id",
3473            use_default_dialect=True,
3474        )
3475
3476    def test_explicit_join_5(self):
3477        Node = self.classes.Node
3478        sess = create_session()
3479        n1 = aliased(Node)
3480        n2 = aliased(Node)
3481
3482        self.assert_compile(
3483            sess.query(Node).join(n1, Node.children).join(n2, Node.children),
3484            "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, "
3485            "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 "
3486            "ON nodes.id = nodes_1.parent_id "
3487            "JOIN nodes AS nodes_2 ON nodes.id = nodes_2.parent_id",
3488            use_default_dialect=True,
3489        )
3490
3491    def test_explicit_join_6(self):
3492        Node = self.classes.Node
3493        sess = create_session()
3494        n1 = aliased(Node)
3495
3496        node = (
3497            sess.query(Node)
3498            .select_from(join(Node, n1, "children"))
3499            .filter(n1.data == "n122")
3500            .first()
3501        )
3502        assert node.data == "n12"
3503
3504    def test_explicit_join_7(self):
3505        Node = self.classes.Node
3506        sess = create_session()
3507        n1 = aliased(Node)
3508        n2 = aliased(Node)
3509
3510        node = (
3511            sess.query(Node)
3512            .select_from(join(Node, n1, "children").join(n2, "children"))
3513            .filter(n2.data == "n122")
3514            .first()
3515        )
3516        assert node.data == "n1"
3517
3518    def test_explicit_join_8(self):
3519        Node = self.classes.Node
3520        sess = create_session()
3521        n1 = aliased(Node)
3522        n2 = aliased(Node)
3523
3524        # mix explicit and named onclauses
3525        node = (
3526            sess.query(Node)
3527            .select_from(
3528                join(Node, n1, Node.id == n1.parent_id).join(n2, "children")
3529            )
3530            .filter(n2.data == "n122")
3531            .first()
3532        )
3533        assert node.data == "n1"
3534
3535    def test_explicit_join_9(self):
3536        Node = self.classes.Node
3537        sess = create_session()
3538        n1 = aliased(Node)
3539        n2 = aliased(Node)
3540
3541        node = (
3542            sess.query(Node)
3543            .select_from(join(Node, n1, "parent").join(n2, "parent"))
3544            .filter(
3545                and_(Node.data == "n122", n1.data == "n12", n2.data == "n1")
3546            )
3547            .first()
3548        )
3549        assert node.data == "n122"
3550
3551    def test_explicit_join_10(self):
3552        Node = self.classes.Node
3553        sess = create_session()
3554        n1 = aliased(Node)
3555        n2 = aliased(Node)
3556
3557        eq_(
3558            list(
3559                sess.query(Node)
3560                .select_from(join(Node, n1, "parent").join(n2, "parent"))
3561                .filter(
3562                    and_(
3563                        Node.data == "n122", n1.data == "n12", n2.data == "n1"
3564                    )
3565                )
3566                .values(Node.data, n1.data, n2.data)
3567            ),
3568            [("n122", "n12", "n1")],
3569        )
3570
3571    def test_join_to_nonaliased(self):
3572        Node = self.classes.Node
3573
3574        sess = create_session()
3575
3576        n1 = aliased(Node)
3577
3578        # using 'n1.parent' implicitly joins to unaliased Node
3579        eq_(
3580            sess.query(n1).join(n1.parent).filter(Node.data == "n1").all(),
3581            [
3582                Node(parent_id=1, data="n11", id=2),
3583                Node(parent_id=1, data="n12", id=3),
3584                Node(parent_id=1, data="n13", id=4),
3585            ],
3586        )
3587
3588        # explicit (new syntax)
3589        eq_(
3590            sess.query(n1)
3591            .join(Node, n1.parent)
3592            .filter(Node.data == "n1")
3593            .all(),
3594            [
3595                Node(parent_id=1, data="n11", id=2),
3596                Node(parent_id=1, data="n12", id=3),
3597                Node(parent_id=1, data="n13", id=4),
3598            ],
3599        )
3600
3601    def test_multiple_explicit_entities_one(self):
3602        Node = self.classes.Node
3603
3604        sess = create_session()
3605
3606        parent = aliased(Node)
3607        grandparent = aliased(Node)
3608        eq_(
3609            sess.query(Node, parent, grandparent)
3610            .join(parent, Node.parent)
3611            .join(grandparent, parent.parent)
3612            .filter(Node.data == "n122")
3613            .filter(parent.data == "n12")
3614            .filter(grandparent.data == "n1")
3615            .first(),
3616            (Node(data="n122"), Node(data="n12"), Node(data="n1")),
3617        )
3618
3619    def test_multiple_explicit_entities_two(self):
3620        Node = self.classes.Node
3621
3622        sess = create_session()
3623
3624        parent = aliased(Node)
3625        grandparent = aliased(Node)
3626        eq_(
3627            sess.query(Node, parent, grandparent)
3628            .join(parent, Node.parent)
3629            .join(grandparent, parent.parent)
3630            .filter(Node.data == "n122")
3631            .filter(parent.data == "n12")
3632            .filter(grandparent.data == "n1")
3633            .from_self()
3634            .first(),
3635            (Node(data="n122"), Node(data="n12"), Node(data="n1")),
3636        )
3637
3638    def test_multiple_explicit_entities_three(self):
3639        Node = self.classes.Node
3640
3641        sess = create_session()
3642
3643        parent = aliased(Node)
3644        grandparent = aliased(Node)
3645        # same, change order around
3646        eq_(
3647            sess.query(parent, grandparent, Node)
3648            .join(parent, Node.parent)
3649            .join(grandparent, parent.parent)
3650            .filter(Node.data == "n122")
3651            .filter(parent.data == "n12")
3652            .filter(grandparent.data == "n1")
3653            .from_self()
3654            .first(),
3655            (Node(data="n12"), Node(data="n1"), Node(data="n122")),
3656        )
3657
3658    def test_multiple_explicit_entities_four(self):
3659        Node = self.classes.Node
3660
3661        sess = create_session()
3662
3663        parent = aliased(Node)
3664        grandparent = aliased(Node)
3665        eq_(
3666            sess.query(Node, parent, grandparent)
3667            .join(parent, Node.parent)
3668            .join(grandparent, parent.parent)
3669            .filter(Node.data == "n122")
3670            .filter(parent.data == "n12")
3671            .filter(grandparent.data == "n1")
3672            .options(joinedload(Node.children))
3673            .first(),
3674            (Node(data="n122"), Node(data="n12"), Node(data="n1")),
3675        )
3676
3677    def test_multiple_explicit_entities_five(self):
3678        Node = self.classes.Node
3679
3680        sess = create_session()
3681
3682        parent = aliased(Node)
3683        grandparent = aliased(Node)
3684        eq_(
3685            sess.query(Node, parent, grandparent)
3686            .join(parent, Node.parent)
3687            .join(grandparent, parent.parent)
3688            .filter(Node.data == "n122")
3689            .filter(parent.data == "n12")
3690            .filter(grandparent.data == "n1")
3691            .from_self()
3692            .options(joinedload(Node.children))
3693            .first(),
3694            (Node(data="n122"), Node(data="n12"), Node(data="n1")),
3695        )
3696
3697    def test_any(self):
3698        Node = self.classes.Node
3699
3700        sess = create_session()
3701        eq_(
3702            sess.query(Node)
3703            .filter(Node.children.any(Node.data == "n1"))
3704            .all(),
3705            [],
3706        )
3707        eq_(
3708            sess.query(Node)
3709            .filter(Node.children.any(Node.data == "n12"))
3710            .all(),
3711            [Node(data="n1")],
3712        )
3713        eq_(
3714            sess.query(Node)
3715            .filter(~Node.children.any())
3716            .order_by(Node.id)
3717            .all(),
3718            [
3719                Node(data="n11"),
3720                Node(data="n13"),
3721                Node(data="n121"),
3722                Node(data="n122"),
3723                Node(data="n123"),
3724            ],
3725        )
3726
3727    def test_has(self):
3728        Node = self.classes.Node
3729
3730        sess = create_session()
3731
3732        eq_(
3733            sess.query(Node)
3734            .filter(Node.parent.has(Node.data == "n12"))
3735            .order_by(Node.id)
3736            .all(),
3737            [Node(data="n121"), Node(data="n122"), Node(data="n123")],
3738        )
3739        eq_(
3740            sess.query(Node)
3741            .filter(Node.parent.has(Node.data == "n122"))
3742            .all(),
3743            [],
3744        )
3745        eq_(
3746            sess.query(Node).filter(~Node.parent.has()).all(),
3747            [Node(data="n1")],
3748        )
3749
3750    def test_contains(self):
3751        Node = self.classes.Node
3752
3753        sess = create_session()
3754
3755        n122 = sess.query(Node).filter(Node.data == "n122").one()
3756        eq_(
3757            sess.query(Node).filter(Node.children.contains(n122)).all(),
3758            [Node(data="n12")],
3759        )
3760
3761        n13 = sess.query(Node).filter(Node.data == "n13").one()
3762        eq_(
3763            sess.query(Node).filter(Node.children.contains(n13)).all(),
3764            [Node(data="n1")],
3765        )
3766
3767    def test_eq_ne(self):
3768        Node = self.classes.Node
3769
3770        sess = create_session()
3771
3772        n12 = sess.query(Node).filter(Node.data == "n12").one()
3773        eq_(
3774            sess.query(Node).filter(Node.parent == n12).all(),
3775            [Node(data="n121"), Node(data="n122"), Node(data="n123")],
3776        )
3777
3778        eq_(
3779            sess.query(Node).filter(Node.parent != n12).all(),
3780            [
3781                Node(data="n1"),
3782                Node(data="n11"),
3783                Node(data="n12"),
3784                Node(data="n13"),
3785            ],
3786        )
3787
3788
3789class SelfReferentialM2MTest(fixtures.MappedTest):
3790    run_setup_mappers = "once"
3791    run_inserts = "once"
3792    run_deletes = None
3793
3794    @classmethod
3795    def define_tables(cls, metadata):
3796        Table(
3797            "nodes",
3798            metadata,
3799            Column(
3800                "id", Integer, primary_key=True, test_needs_autoincrement=True
3801            ),
3802            Column("data", String(30)),
3803        )
3804
3805        Table(
3806            "node_to_nodes",
3807            metadata,
3808            Column(
3809                "left_node_id",
3810                Integer,
3811                ForeignKey("nodes.id"),
3812                primary_key=True,
3813            ),
3814            Column(
3815                "right_node_id",
3816                Integer,
3817                ForeignKey("nodes.id"),
3818                primary_key=True,
3819            ),
3820        )
3821
3822    @classmethod
3823    def setup_classes(cls):
3824        class Node(cls.Comparable):
3825            pass
3826
3827    @classmethod
3828    def insert_data(cls, connection):
3829        Node, nodes, node_to_nodes = (
3830            cls.classes.Node,
3831            cls.tables.nodes,
3832            cls.tables.node_to_nodes,
3833        )
3834
3835        mapper(
3836            Node,
3837            nodes,
3838            properties={
3839                "children": relationship(
3840                    Node,
3841                    lazy="select",
3842                    secondary=node_to_nodes,
3843                    primaryjoin=nodes.c.id == node_to_nodes.c.left_node_id,
3844                    secondaryjoin=nodes.c.id == node_to_nodes.c.right_node_id,
3845                )
3846            },
3847        )
3848        sess = create_session(connection)
3849        n1 = Node(data="n1")
3850        n2 = Node(data="n2")
3851        n3 = Node(data="n3")
3852        n4 = Node(data="n4")
3853        n5 = Node(data="n5")
3854        n6 = Node(data="n6")
3855        n7 = Node(data="n7")
3856
3857        n1.children = [n2, n3, n4]
3858        n2.children = [n3, n6, n7]
3859        n3.children = [n5, n4]
3860
3861        sess.add(n1)
3862        sess.add(n2)
3863        sess.add(n3)
3864        sess.add(n4)
3865        sess.flush()
3866        sess.close()
3867
3868    def test_any(self):
3869        Node = self.classes.Node
3870
3871        sess = create_session()
3872        eq_(
3873            sess.query(Node)
3874            .filter(Node.children.any(Node.data == "n3"))
3875            .order_by(Node.data)
3876            .all(),
3877            [Node(data="n1"), Node(data="n2")],
3878        )
3879
3880    def test_contains(self):
3881        Node = self.classes.Node
3882
3883        sess = create_session()
3884        n4 = sess.query(Node).filter_by(data="n4").one()
3885
3886        eq_(
3887            sess.query(Node)
3888            .filter(Node.children.contains(n4))
3889            .order_by(Node.data)
3890            .all(),
3891            [Node(data="n1"), Node(data="n3")],
3892        )
3893        eq_(
3894            sess.query(Node)
3895            .filter(not_(Node.children.contains(n4)))
3896            .order_by(Node.data)
3897            .all(),
3898            [
3899                Node(data="n2"),
3900                Node(data="n4"),
3901                Node(data="n5"),
3902                Node(data="n6"),
3903                Node(data="n7"),
3904            ],
3905        )
3906
3907    def test_explicit_join(self):
3908        Node = self.classes.Node
3909
3910        sess = create_session()
3911
3912        n1 = aliased(Node)
3913        eq_(
3914            sess.query(Node)
3915            .select_from(join(Node, n1, "children"))
3916            .filter(n1.data.in_(["n3", "n7"]))
3917            .order_by(Node.id)
3918            .all(),
3919            [Node(data="n1"), Node(data="n2")],
3920        )
3921
3922
3923class AliasFromCorrectLeftTest(
3924    fixtures.DeclarativeMappedTest, AssertsCompiledSQL
3925):
3926    run_create_tables = None
3927    __dialect__ = "default"
3928
3929    @classmethod
3930    def setup_classes(cls):
3931        Base = cls.DeclarativeBasic
3932
3933        class Object(Base):
3934            __tablename__ = "object"
3935
3936            type = Column(String(30))
3937            __mapper_args__ = {
3938                "polymorphic_identity": "object",
3939                "polymorphic_on": type,
3940            }
3941
3942            id = Column(Integer, primary_key=True)
3943            name = Column(String(256))
3944
3945        class A(Object):
3946            __tablename__ = "a"
3947
3948            __mapper_args__ = {"polymorphic_identity": "a"}
3949
3950            id = Column(Integer, ForeignKey("object.id"), primary_key=True)
3951
3952            b_list = relationship(
3953                "B", secondary="a_b_association", backref="a_list"
3954            )
3955
3956        class B(Object):
3957            __tablename__ = "b"
3958
3959            __mapper_args__ = {"polymorphic_identity": "b"}
3960
3961            id = Column(Integer, ForeignKey("object.id"), primary_key=True)
3962
3963        class ABAssociation(Base):
3964            __tablename__ = "a_b_association"
3965
3966            a_id = Column(Integer, ForeignKey("a.id"), primary_key=True)
3967            b_id = Column(Integer, ForeignKey("b.id"), primary_key=True)
3968
3969        class X(Base):
3970            __tablename__ = "x"
3971
3972            id = Column(Integer, primary_key=True)
3973            name = Column(String(30))
3974
3975            obj_id = Column(Integer, ForeignKey("object.id"))
3976            obj = relationship("Object", backref="x_list")
3977
3978    def test_join_prop_to_string(self):
3979        A, B, X = self.classes("A", "B", "X")
3980
3981        s = Session()
3982
3983        q = s.query(B).join(B.a_list, "x_list").filter(X.name == "x1")
3984
3985        self.assert_compile(
3986            q,
3987            "SELECT object.type AS object_type, b.id AS b_id, "
3988            "object.id AS object_id, object.name AS object_name "
3989            "FROM object JOIN b ON object.id = b.id "
3990            "JOIN a_b_association AS a_b_association_1 "
3991            "ON b.id = a_b_association_1.b_id "
3992            "JOIN ("
3993            "object AS object_1 "
3994            "JOIN a AS a_1 ON object_1.id = a_1.id"
3995            ") ON a_1.id = a_b_association_1.a_id "
3996            "JOIN x ON object_1.id = x.obj_id WHERE x.name = :name_1",
3997        )
3998
3999    def test_join_prop_to_prop(self):
4000        A, B, X = self.classes("A", "B", "X")
4001
4002        s = Session()
4003
4004        # B -> A, but both are Object.  So when we say A.x_list, make sure
4005        # we pick the correct right side
4006        q = s.query(B).join(B.a_list, A.x_list).filter(X.name == "x1")
4007
4008        self.assert_compile(
4009            q,
4010            "SELECT object.type AS object_type, b.id AS b_id, "
4011            "object.id AS object_id, object.name AS object_name "
4012            "FROM object JOIN b ON object.id = b.id "
4013            "JOIN a_b_association AS a_b_association_1 "
4014            "ON b.id = a_b_association_1.b_id "
4015            "JOIN ("
4016            "object AS object_1 "
4017            "JOIN a AS a_1 ON object_1.id = a_1.id"
4018            ") ON a_1.id = a_b_association_1.a_id "
4019            "JOIN x ON object_1.id = x.obj_id WHERE x.name = :name_1",
4020        )
4021
4022
4023class JoinLateralTest(fixtures.MappedTest, AssertsCompiledSQL):
4024    __dialect__ = default.DefaultDialect(supports_native_boolean=True)
4025
4026    run_setup_bind = None
4027    run_setup_mappers = "once"
4028
4029    run_create_tables = None
4030
4031    @classmethod
4032    def define_tables(cls, metadata):
4033        Table(
4034            "people",
4035            metadata,
4036            Column("people_id", Integer, primary_key=True),
4037            Column("age", Integer),
4038            Column("name", String(30)),
4039        )
4040        Table(
4041            "bookcases",
4042            metadata,
4043            Column("bookcase_id", Integer, primary_key=True),
4044            Column(
4045                "bookcase_owner_id", Integer, ForeignKey("people.people_id")
4046            ),
4047            Column("bookcase_shelves", Integer),
4048            Column("bookcase_width", Integer),
4049        )
4050        Table(
4051            "books",
4052            metadata,
4053            Column("book_id", Integer, primary_key=True),
4054            Column(
4055                "bookcase_id", Integer, ForeignKey("bookcases.bookcase_id")
4056            ),
4057            Column("book_owner_id", Integer, ForeignKey("people.people_id")),
4058            Column("book_weight", Integer),
4059        )
4060
4061    @classmethod
4062    def setup_classes(cls):
4063        people, bookcases, books = cls.tables("people", "bookcases", "books")
4064
4065        class Person(cls.Comparable):
4066            pass
4067
4068        class Bookcase(cls.Comparable):
4069            pass
4070
4071        class Book(cls.Comparable):
4072            pass
4073
4074        mapper(Person, people)
4075        mapper(
4076            Bookcase,
4077            bookcases,
4078            properties={
4079                "owner": relationship(Person),
4080                "books": relationship(Book),
4081            },
4082        )
4083        mapper(Book, books)
4084
4085    def test_select_subquery(self):
4086        Person, Book = self.classes("Person", "Book")
4087
4088        s = Session()
4089
4090        subq = (
4091            s.query(Book.book_id)
4092            .correlate(Person)
4093            .filter(Person.people_id == Book.book_owner_id)
4094            .subquery()
4095            .lateral()
4096        )
4097
4098        stmt = s.query(Person, subq.c.book_id).join(subq, true())
4099
4100        self.assert_compile(
4101            stmt,
4102            "SELECT people.people_id AS people_people_id, "
4103            "people.age AS people_age, people.name AS people_name, "
4104            "anon_1.book_id AS anon_1_book_id "
4105            "FROM people JOIN LATERAL "
4106            "(SELECT books.book_id AS book_id FROM books "
4107            "WHERE people.people_id = books.book_owner_id) AS anon_1 ON true",
4108        )
4109
4110    # sef == select_entity_from
4111    def test_select_subquery_sef_implicit_correlate(self):
4112        Person, Book = self.classes("Person", "Book")
4113
4114        s = Session()
4115
4116        stmt = s.query(Person).subquery()
4117
4118        subq = (
4119            s.query(Book.book_id)
4120            .filter(Person.people_id == Book.book_owner_id)
4121            .subquery()
4122            .lateral()
4123        )
4124
4125        stmt = (
4126            s.query(Person, subq.c.book_id)
4127            .select_entity_from(stmt)
4128            .join(subq, true())
4129        )
4130
4131        self.assert_compile(
4132            stmt,
4133            "SELECT anon_1.people_id AS anon_1_people_id, "
4134            "anon_1.age AS anon_1_age, anon_1.name AS anon_1_name, "
4135            "anon_2.book_id AS anon_2_book_id "
4136            "FROM "
4137            "(SELECT people.people_id AS people_id, people.age AS age, "
4138            "people.name AS name FROM people) AS anon_1 "
4139            "JOIN LATERAL "
4140            "(SELECT books.book_id AS book_id FROM books "
4141            "WHERE anon_1.people_id = books.book_owner_id) AS anon_2 ON true",
4142        )
4143
4144    def test_select_subquery_sef_implicit_correlate_coreonly(self):
4145        Person, Book = self.classes("Person", "Book")
4146
4147        s = Session()
4148
4149        stmt = s.query(Person).subquery()
4150
4151        subq = (
4152            select([Book.book_id])
4153            .where(Person.people_id == Book.book_owner_id)
4154            .lateral()
4155        )
4156
4157        stmt = (
4158            s.query(Person, subq.c.book_id)
4159            .select_entity_from(stmt)
4160            .join(subq, true())
4161        )
4162
4163        self.assert_compile(
4164            stmt,
4165            "SELECT anon_1.people_id AS anon_1_people_id, "
4166            "anon_1.age AS anon_1_age, anon_1.name AS anon_1_name, "
4167            "anon_2.book_id AS anon_2_book_id "
4168            "FROM "
4169            "(SELECT people.people_id AS people_id, people.age AS age, "
4170            "people.name AS name FROM people) AS anon_1 "
4171            "JOIN LATERAL "
4172            "(SELECT books.book_id AS book_id FROM books "
4173            "WHERE anon_1.people_id = books.book_owner_id) AS anon_2 ON true",
4174        )
4175
4176    def test_select_subquery_sef_explicit_correlate_coreonly(self):
4177        Person, Book = self.classes("Person", "Book")
4178
4179        s = Session()
4180
4181        stmt = s.query(Person).subquery()
4182
4183        subq = (
4184            select([Book.book_id])
4185            .correlate(Person)
4186            .where(Person.people_id == Book.book_owner_id)
4187            .lateral()
4188        )
4189
4190        stmt = (
4191            s.query(Person, subq.c.book_id)
4192            .select_entity_from(stmt)
4193            .join(subq, true())
4194        )
4195
4196        self.assert_compile(
4197            stmt,
4198            "SELECT anon_1.people_id AS anon_1_people_id, "
4199            "anon_1.age AS anon_1_age, anon_1.name AS anon_1_name, "
4200            "anon_2.book_id AS anon_2_book_id "
4201            "FROM "
4202            "(SELECT people.people_id AS people_id, people.age AS age, "
4203            "people.name AS name FROM people) AS anon_1 "
4204            "JOIN LATERAL "
4205            "(SELECT books.book_id AS book_id FROM books "
4206            "WHERE anon_1.people_id = books.book_owner_id) AS anon_2 ON true",
4207        )
4208
4209    def test_select_subquery_sef_explicit_correlate(self):
4210        Person, Book = self.classes("Person", "Book")
4211
4212        s = Session()
4213
4214        stmt = s.query(Person).subquery()
4215
4216        subq = (
4217            s.query(Book.book_id)
4218            .correlate(Person)
4219            .filter(Person.people_id == Book.book_owner_id)
4220            .subquery()
4221            .lateral()
4222        )
4223
4224        stmt = (
4225            s.query(Person, subq.c.book_id)
4226            .select_entity_from(stmt)
4227            .join(subq, true())
4228        )
4229
4230        self.assert_compile(
4231            stmt,
4232            "SELECT anon_1.people_id AS anon_1_people_id, "
4233            "anon_1.age AS anon_1_age, anon_1.name AS anon_1_name, "
4234            "anon_2.book_id AS anon_2_book_id "
4235            "FROM "
4236            "(SELECT people.people_id AS people_id, people.age AS age, "
4237            "people.name AS name FROM people) AS anon_1 "
4238            "JOIN LATERAL "
4239            "(SELECT books.book_id AS book_id FROM books "
4240            "WHERE anon_1.people_id = books.book_owner_id) AS anon_2 ON true",
4241        )
4242
4243    def test_from_function(self):
4244        Bookcase = self.classes.Bookcase
4245
4246        s = Session()
4247
4248        srf = lateral(func.generate_series(1, Bookcase.bookcase_shelves))
4249
4250        self.assert_compile(
4251            s.query(Bookcase).join(srf, true()),
4252            "SELECT bookcases.bookcase_id AS bookcases_bookcase_id, "
4253            "bookcases.bookcase_owner_id AS bookcases_bookcase_owner_id, "
4254            "bookcases.bookcase_shelves AS bookcases_bookcase_shelves, "
4255            "bookcases.bookcase_width AS bookcases_bookcase_width "
4256            "FROM bookcases JOIN "
4257            "LATERAL generate_series(:generate_series_1, "
4258            "bookcases.bookcase_shelves) AS anon_1 ON true",
4259        )
4260
4261    def test_from_function_select_entity_from(self):
4262        Bookcase = self.classes.Bookcase
4263
4264        s = Session()
4265
4266        subq = s.query(Bookcase).subquery()
4267
4268        srf = lateral(func.generate_series(1, Bookcase.bookcase_shelves))
4269
4270        self.assert_compile(
4271            s.query(Bookcase).select_entity_from(subq).join(srf, true()),
4272            "SELECT anon_1.bookcase_id AS anon_1_bookcase_id, "
4273            "anon_1.bookcase_owner_id AS anon_1_bookcase_owner_id, "
4274            "anon_1.bookcase_shelves AS anon_1_bookcase_shelves, "
4275            "anon_1.bookcase_width AS anon_1_bookcase_width "
4276            "FROM (SELECT bookcases.bookcase_id AS bookcase_id, "
4277            "bookcases.bookcase_owner_id AS bookcase_owner_id, "
4278            "bookcases.bookcase_shelves AS bookcase_shelves, "
4279            "bookcases.bookcase_width AS bookcase_width FROM bookcases) "
4280            "AS anon_1 "
4281            "JOIN LATERAL "
4282            "generate_series(:generate_series_1, anon_1.bookcase_shelves) "
4283            "AS anon_2 ON true",
4284        )
4285