diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-12-01 17:24:27 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-05-24 11:54:08 -0400 |
| commit | dce8c7a125cb99fad62c76cd145752d5afefae36 (patch) | |
| tree | 352dfa2c38005207ca64f45170bbba2c0f8c927e /test/orm/test_joins.py | |
| parent | 1502b5b3e4e4b93021eb927a6623f288ef006ba6 (diff) | |
| download | sqlalchemy-dce8c7a125cb99fad62c76cd145752d5afefae36.tar.gz | |
Unify Query and select() , move all processing to compile phase
Convert Query to do virtually all compile state computation
in the _compile_context() phase, and organize it all
such that a plain select() construct may also be used as the
source of information in order to generate ORM query state.
This makes it such that Query is not needed except for
its additional methods like from_self() which are all to
be deprecated.
The construction of ORM state will occur beyond the
caching boundary when the new execution model is integrated.
future select() gains a working join() and filter_by() method.
as we continue to rebase and merge each commit in the steps,
callcounts continue to bump around. will have to look at
the final result when it's all in.
References: #5159
References: #4705
References: #4639
References: #4871
References: #5010
Change-Id: I19e05b3424b07114cce6c439b05198ac47f7ac10
Diffstat (limited to 'test/orm/test_joins.py')
| -rw-r--r-- | test/orm/test_joins.py | 281 |
1 files changed, 215 insertions, 66 deletions
diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py index 1895a41e8..300670a70 100644 --- a/test/orm/test_joins.py +++ b/test/orm/test_joins.py @@ -586,6 +586,24 @@ class JoinOnSynonymTest(_fixtures.FixtureTest, AssertsCompiledSQL): class JoinTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" + def test_foo(self): + User = self.classes.User + + sess = create_session() + + # test overlapping paths. User->orders is used by both joins, but + # rendered once. + self.assert_compile( + sess.query(User).join("orders", "items").join("orders", "address"), + "SELECT users.id AS users_id, users.name AS users_name FROM users " + "JOIN orders " + "ON users.id = orders.user_id " + "JOIN order_items AS order_items_1 " + "ON orders.id = order_items_1.order_id " + "JOIN items ON items.id = order_items_1.item_id JOIN addresses " + "ON addresses.id = orders.address_id", + ) + def test_single_name(self): User = self.classes.User @@ -598,7 +616,8 @@ class JoinTest(QueryTest, AssertsCompiledSQL): ) assert_raises( - sa_exc.InvalidRequestError, sess.query(User).join, "user" + sa_exc.InvalidRequestError, + sess.query(User).join("user")._compile_context, ) self.assert_compile( @@ -623,6 +642,23 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "ON addresses.id = orders.address_id", ) + def test_filter_by_from_full_join(self): + User, Address = self.classes("User", "Address") + + sess = create_session() + + q = ( + sess.query(User) + .join(Address, User.addresses) + .filter_by(email_address="foo") + ) + self.assert_compile( + q, + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users JOIN addresses ON users.id = addresses.user_id " + "WHERE addresses.email_address = :email_address_1", + ) + def test_invalid_kwarg_join(self): User = self.classes.User sess = create_session() @@ -670,8 +706,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): sa_exc.InvalidRequestError, r"No entities to join from; please use select_from\(\) to " r"establish the left entity/selectable of this join", - sess.query().join, - Address, + sess.query().join(Address)._compile_context, ) def test_isouter_flag(self): @@ -692,51 +727,138 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "FROM users FULL OUTER JOIN orders ON users.id = orders.user_id", ) - def test_multi_tuple_form(self): + def test_multi_tuple_form_legacy_one(self): """test the 'tuple' form of join, now superseded by the two-element join() form. - Not deprecating this style as of yet. """ - Item, Order, User = ( - self.classes.Item, + Order, User = ( self.classes.Order, self.classes.User, ) sess = create_session() - # assert_raises( - # sa.exc.SADeprecationWarning, - # sess.query(User).join, (Order, User.id==Order.user_id) - # ) - + q = ( + sess.query(User) + .join((Order, User.id == Order.user_id)) + .filter_by(description="foo") + ) self.assert_compile( - sess.query(User).join((Order, User.id == Order.user_id)), + q, "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id", + "FROM users JOIN orders ON users.id = orders.user_id " + "WHERE orders.description = :description_1", + ) + + def test_multi_tuple_form_legacy_two(self): + """test the 'tuple' form of join, now superseded + by the two-element join() form. + + + """ + + Item, Order, User = ( + self.classes.Item, + self.classes.Order, + self.classes.User, ) + sess = create_session() + + q = ( + sess.query(User) + .join((Order, User.id == Order.user_id), (Item, Order.items)) + .filter_by(description="foo") + ) self.assert_compile( - sess.query(User).join( - (Order, User.id == Order.user_id), (Item, Order.items) - ), + q, "SELECT users.id AS users_id, users.name AS users_name " "FROM users JOIN orders ON users.id = orders.user_id " "JOIN order_items AS order_items_1 ON orders.id = " "order_items_1.order_id JOIN items ON items.id = " - "order_items_1.item_id", + "order_items_1.item_id WHERE items.description = :description_1", + ) + + def test_multi_tuple_form_legacy_three(self): + """test the 'tuple' form of join, now superseded + by the two-element join() form. + + + """ + + Order, User = ( + self.classes.Order, + self.classes.User, ) + sess = create_session() + # the old "backwards" form + q = ( + sess.query(User) + .join(("orders", Order)) + .filter_by(description="foo") + ) self.assert_compile( - sess.query(User).join(("orders", Order)), + q, "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id", + "FROM users JOIN orders ON users.id = orders.user_id " + "WHERE orders.description = :description_1", ) + def test_multi_tuple_form_legacy_three_point_five(self): + """test the 'tuple' form of join, now superseded + by the two-element join() form. + + + """ + + Order, User = ( + self.classes.Order, + self.classes.User, + ) + + sess = create_session() + + q = sess.query(User).join(Order, "orders").filter_by(description="foo") + self.assert_compile( + q, + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users JOIN orders ON users.id = orders.user_id " + "WHERE orders.description = :description_1", + ) + + def test_multi_tuple_form_legacy_four(self): + User, Order, Item, Keyword = self.classes( + "User", "Order", "Item", "Keyword" + ) + + sess = create_session() + + # ensure when the tokens are broken up that from_joinpoint + # is set between them + + expected = ( + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users JOIN orders ON users.id = orders.user_id " + "JOIN order_items AS order_items_1 ON orders.id = " + "order_items_1.order_id JOIN items ON items.id = " + "order_items_1.item_id JOIN item_keywords AS item_keywords_1 " + "ON items.id = item_keywords_1.item_id " + "JOIN keywords ON keywords.id = item_keywords_1.keyword_id" + ) + + q = sess.query(User).join( + (Order, "orders"), (Item, "items"), (Keyword, "keywords") + ) + self.assert_compile(q, expected) + + q = sess.query(User).join("orders", "items", "keywords") + self.assert_compile(q, expected) + def test_single_prop_1(self): User = self.classes.User @@ -995,19 +1117,6 @@ class JoinTest(QueryTest, AssertsCompiledSQL): ) assert [User(id=7, name="jack")] == result - def test_raises_on_dupe_target_rel(self): - User = self.classes.User - - assert_raises_message( - sa.exc.SAWarning, - "Pathed join target Order.items has already been joined to; " - "skipping", - lambda: create_session() - .query(User) - .outerjoin("orders", "items") - .outerjoin("orders", "items"), - ) - def test_from_joinpoint(self): Item, User, Order = ( self.classes.Item, @@ -1108,18 +1217,14 @@ class JoinTest(QueryTest, AssertsCompiledSQL): # before the error raise was added, this would silently work..... assert_raises( sa_exc.InvalidRequestError, - sess.query(User).join, - Address, - Address.user, + sess.query(User).join(Address, Address.user)._compile_context, ) # but this one would silently fail adalias = aliased(Address) assert_raises( sa_exc.InvalidRequestError, - sess.query(User).join, - adalias, - Address.user, + sess.query(User).join(adalias, Address.user)._compile_context, ) def test_multiple_with_aliases(self): @@ -1341,8 +1446,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "Don't know how to join to .*Item.*. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - q.join, - Item, + q.join(Item)._compile_context, ) def test_invalid_join_entity_from_no_from_clause(self): @@ -1356,8 +1460,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "Don't know how to join to .*Item.*. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - q.join, - Item, + q.join(Item)._compile_context, ) def test_invalid_join_entity_from_multiple_from_clause(self): @@ -1378,8 +1481,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "Don't know how to join to .*Item.*. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - q.join, - Item, + q.join(Item)._compile_context, ) def test_join_explicit_left_multiple_from_clause(self): @@ -1438,8 +1540,10 @@ class JoinTest(QueryTest, AssertsCompiledSQL): sa_exc.InvalidRequestError, "Can't identify which entity in which to assign the " "left side of this join.", - sess.query(u1, u2).select_from(u1, u2).join, - User.addresses, + sess.query(u1, u2) + .select_from(u1, u2) + .join(User.addresses) + ._compile_context, ) # more specific ON clause @@ -1475,8 +1579,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "multiple FROMS which can join to this entity. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - q.join, - a1, + q.join(a1)._compile_context, ) # to resolve, add an ON clause @@ -1532,8 +1635,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "multiple FROMS which can join to this entity. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - q.join, - a1, + q.join(a1)._compile_context, ) # to resolve, add an ON clause @@ -1584,8 +1686,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "multiple FROMS which can join to this entity. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - q.outerjoin, - a1, + q.outerjoin(a1)._compile_context, ) # the condition which occurs here is: Query._from_obj contains both @@ -2374,8 +2475,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "Don't know how to join to .*User.*. " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - sess.query(users.c.id).join, - User, + sess.query(users.c.id).join(User)._compile_context, ) assert_raises_message( @@ -2383,8 +2483,10 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "Don't know how to join to .*User.* " r"Please use the .select_from\(\) " "method to establish an explicit left side, as well as", - sess.query(users.c.id).select_from(users).join, - User, + sess.query(users.c.id) + .select_from(users) + .join(User) + ._compile_context, ) def test_on_clause_no_right_side(self): @@ -2395,8 +2497,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): assert_raises_message( sa_exc.ArgumentError, "Expected mapped entity or selectable/table as join target", - sess.query(User).join, - User.id == Address.user_id, + sess.query(User).join(User.id == Address.user_id)._compile_context, ) def test_select_from(self): @@ -2686,10 +2787,9 @@ class JoinFromSelectableTest(fixtures.MappedTest, AssertsCompiledSQL): r"The aliased=True parameter on query.join\(\) only works with " "an ORM entity, not a plain selectable, as the target.", # this doesn't work, so have it raise an error - sess.query(T1.id).join, - subq, - subq.c.t1_id == T1.id, - aliased=True, + sess.query(T1.id) + .join(subq, subq.c.t1_id == T1.id, aliased=True) + ._compile_context, ) @@ -3193,6 +3293,56 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL): node = q.first() eq_(node.data, "n122") + def test_join_4_explicit_join(self): + Node = self.classes.Node + sess = create_session() + + na = aliased(Node) + na2 = aliased(Node) + + # this one is a great example of how to show how the API changes; + # while it requires the explicitness of aliased(Node), the whole + # guesswork of joinpoint / aliased goes away and the whole thing + # is simpler + # + # .join("parent", aliased=True) + # .filter(Node.data == "n12") + # .join("parent", aliased=True, from_joinpoint=True) + # .filter(Node.data == "n1") + # + # becomes: + # + # na = aliased(Node) + # na2 = aliased(Node) + # + # ... + # .join(na, Node.parent) + # .filter(na.data == "n12") + # .join(na2, na.parent) + # .filter(na2.data == "n1") + # + q = ( + sess.query(Node) + .filter(Node.data == "n122") + .join(na, Node.parent) + .filter(na.data == "n12") + .join(na2, na.parent) + .filter(na2.data == "n1") + ) + + self.assert_compile( + q, + "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, " + "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 " + "ON nodes_1.id = nodes.parent_id JOIN nodes AS nodes_2 " + "ON nodes_2.id = nodes_1.parent_id WHERE nodes.data = :data_1 " + "AND nodes_1.data = :data_2 AND nodes_2.data = :data_3", + checkparams={"data_1": "n122", "data_2": "n12", "data_3": "n1"}, + ) + + node = q.first() + eq_(node.data, "n122") + def test_join_4_filter(self): Node = self.classes.Node sess = create_session() @@ -3390,8 +3540,7 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL): sa.exc.InvalidRequestError, "Can't construct a join from mapped class Node->nodes to mapped " "class Node->nodes, they are the same entity", - s.query(Node).join, - Node.children, + s.query(Node).join(Node.children)._compile_context, ) def test_explicit_join_1(self): @@ -3539,7 +3688,7 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL): Node.data == "n122", n1.data == "n12", n2.data == "n1" ) ) - .values(Node.data, n1.data, n2.data) + .with_entities(Node.data, n1.data, n2.data) ), [("n122", "n12", "n1")], ) |
