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