from .. import exc as sa_exc, inspect
from .. import util, log, event
from ..sql import util as sql_util, visitors
+from .. import sql
from . import (
attributes, interfaces, exc as orm_exc, loading,
unitofwork, util as orm_util
def setup_query(self, context, entity, path, loadopt, adapter, \
column_collection=None, parentmapper=None,
- allow_innerjoin=True,
**kwargs):
"""Add a left outer join to the statement thats being constructed."""
elif path.contains_mapper(self.mapper):
return
- clauses, adapter, add_to_collection, \
- allow_innerjoin = self._generate_row_adapter(
+ clauses, adapter, add_to_collection = self._generate_row_adapter(
context, entity, path, loadopt, adapter,
- column_collection, parentmapper, allow_innerjoin
+ column_collection, parentmapper
)
with_poly_info = path.get(
path,
clauses,
parentmapper=self.mapper,
- column_collection=add_to_collection,
- allow_innerjoin=allow_innerjoin)
+ column_collection=add_to_collection)
if with_poly_info is not None and \
None in set(context.secondary_columns):
def _generate_row_adapter(self,
context, entity, path, loadopt, adapter,
- column_collection, parentmapper, allow_innerjoin
+ column_collection, parentmapper
):
with_poly_info = path.get(
context.attributes,
if self.parent_property.direction != interfaces.MANYTOONE:
context.multi_row_eager_loaders = True
- innerjoin = allow_innerjoin and (
+ innerjoin = (
loadopt.local_opts.get(
'innerjoin', self.parent_property.innerjoin)
if loadopt is not None
else self.parent_property.innerjoin
)
- if not innerjoin:
- # if this is an outer join, all eager joins from
- # here must also be outer joins
- allow_innerjoin = False
context.create_eager_joins.append(
(self._create_eager_join, context,
add_to_collection = context.secondary_columns
path.set(context.attributes, "eager_row_processor", clauses)
- return clauses, adapter, add_to_collection, allow_innerjoin
+ return clauses, adapter, add_to_collection
def _create_eager_join(self, context, entity,
path, adapter, parentmapper,
onclause = self.parent_property
assert clauses.aliased_class is not None
- context.eager_joins[entity_key] = eagerjoin = \
- orm_util.join(
- towrap,
- clauses.aliased_class,
- onclause,
- isouter=not innerjoin
- )
+
+ join_to_outer = innerjoin and isinstance(towrap, sql.Join) and towrap.isouter
+
+ if join_to_outer and innerjoin == 'nested':
+ inner = orm_util.join(
+ towrap.right,
+ clauses.aliased_class,
+ onclause,
+ isouter=False
+ )
+
+ eagerjoin = orm_util.join(
+ towrap.left,
+ inner,
+ towrap.onclause,
+ isouter=True
+ )
+ eagerjoin._target_adapter = inner._target_adapter
+ else:
+ if join_to_outer:
+ innerjoin = False
+ eagerjoin = orm_util.join(
+ towrap,
+ clauses.aliased_class,
+ onclause,
+ isouter=not innerjoin
+ )
+ context.eager_joins[entity_key] = eagerjoin
# send a hint to the Query as to where it may "splice" this join
eagerjoin.stop_on = entity.selectable
)
+ def test_inner_join_nested_chaining_negative_options(self):
+ users, items, order_items, Order, Item, User, orders = (self.tables.users,
+ self.tables.items,
+ self.tables.order_items,
+ self.classes.Order,
+ self.classes.Item,
+ self.classes.User,
+ self.tables.orders)
+
+ mapper(User, users, properties=dict(
+ orders=relationship(Order, innerjoin='nested',
+ lazy=False, order_by=orders.c.id)
+ ))
+ mapper(Order, orders, properties=dict(
+ items=relationship(Item, secondary=order_items, lazy=False,
+ innerjoin='nested', order_by=items.c.id)
+ ))
+ mapper(Item, items)
+
+ sess = create_session()
+ self.assert_compile(
+ sess.query(User),
+ "SELECT users.id AS users_id, users.name AS users_name, items_1.id AS "
+ "items_1_id, items_1.description AS items_1_description, orders_1.id AS "
+ "orders_1_id, orders_1.user_id AS orders_1_user_id, orders_1.address_id AS "
+ "orders_1_address_id, orders_1.description AS orders_1_description, "
+ "orders_1.isopen AS orders_1_isopen FROM users JOIN orders AS orders_1 ON "
+ "users.id = orders_1.user_id JOIN order_items AS order_items_1 ON orders_1.id = "
+ "order_items_1.order_id JOIN items AS items_1 ON items_1.id = "
+ "order_items_1.item_id ORDER BY orders_1.id, items_1.id"
+ )
+
+ q = sess.query(User).options(joinedload(User.orders, innerjoin=False))
+ self.assert_compile(
+ q,
+ "SELECT users.id AS users_id, users.name AS users_name, items_1.id AS "
+ "items_1_id, items_1.description AS items_1_description, orders_1.id AS "
+ "orders_1_id, orders_1.user_id AS orders_1_user_id, orders_1.address_id AS "
+ "orders_1_address_id, orders_1.description AS orders_1_description, "
+ "orders_1.isopen AS orders_1_isopen "
+ "FROM users LEFT OUTER JOIN "
+ "(orders AS orders_1 JOIN order_items AS order_items_1 "
+ "ON orders_1.id = order_items_1.order_id "
+ "JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
+ "ON users.id = orders_1.user_id ORDER BY orders_1.id, items_1.id"
+ )
+
+ eq_(
+ [
+ User(id=7,
+ orders=[
+ Order(id=1, items=[Item(id=1), Item(id=2), Item(id=3)]),
+ Order(id=3, items=[Item(id=3), Item(id=4), Item(id=5)]),
+ Order(id=5, items=[Item(id=5)])]),
+ User(id=8, orders=[]),
+ User(id=9, orders=[
+ Order(id=2, items=[Item(id=1), Item(id=2), Item(id=3)]),
+ Order(id=4, items=[Item(id=1), Item(id=5)])
+ ]
+ ),
+ User(id=10, orders=[])
+ ],
+ q.order_by(User.id).all()
+ )
+
+ self.assert_compile(
+ sess.query(User).options(joinedload(User.orders, Order.items, innerjoin=False)),
+ "SELECT users.id AS users_id, users.name AS users_name, items_1.id AS "
+ "items_1_id, items_1.description AS items_1_description, orders_1.id AS "
+ "orders_1_id, orders_1.user_id AS orders_1_user_id, orders_1.address_id AS "
+ "orders_1_address_id, orders_1.description AS orders_1_description, "
+ "orders_1.isopen AS orders_1_isopen "
+ "FROM users JOIN orders AS orders_1 ON users.id = orders_1.user_id "
+ "LEFT OUTER JOIN (order_items AS order_items_1 "
+ "JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
+ "ON orders_1.id = order_items_1.order_id ORDER BY orders_1.id, items_1.id"
+
+ )
+
+ def test_inner_join_nested_chaining_positive_options(self):
+ users, items, order_items, Order, Item, User, orders = (self.tables.users,
+ self.tables.items,
+ self.tables.order_items,
+ self.classes.Order,
+ self.classes.Item,
+ self.classes.User,
+ self.tables.orders)
+
+ mapper(User, users, properties=dict(
+ orders=relationship(Order, order_by=orders.c.id)
+ ))
+ mapper(Order, orders, properties=dict(
+ items=relationship(Item, secondary=order_items, order_by=items.c.id)
+ ))
+ mapper(Item, items)
+
+ sess = create_session()
+ q = sess.query(User).options(
+ joinedload("orders", innerjoin=False).\
+ joinedload("items", innerjoin="nested")
+ )
+
+ self.assert_compile(
+ q,
+ "SELECT users.id AS users_id, users.name AS users_name, "
+ "items_1.id AS items_1_id, items_1.description AS items_1_description, "
+ "orders_1.id AS orders_1_id, orders_1.user_id AS orders_1_user_id, "
+ "orders_1.address_id AS orders_1_address_id, orders_1.description AS "
+ "orders_1_description, orders_1.isopen AS orders_1_isopen "
+ "FROM users LEFT OUTER JOIN (orders AS orders_1 JOIN order_items AS "
+ "order_items_1 ON orders_1.id = order_items_1.order_id JOIN items AS "
+ "items_1 ON items_1.id = order_items_1.item_id) ON users.id = orders_1.user_id "
+ "ORDER BY orders_1.id, items_1.id"
+ )
+
+ eq_(
+ [
+ User(id=7,
+ orders=[
+ Order(id=1, items=[Item(id=1), Item(id=2), Item(id=3)]),
+ Order(id=3, items=[Item(id=3), Item(id=4), Item(id=5)]),
+ Order(id=5, items=[Item(id=5)])]),
+ User(id=8, orders=[]),
+ User(id=9, orders=[
+ Order(id=2, items=[Item(id=1), Item(id=2), Item(id=3)]),
+ Order(id=4, items=[Item(id=1), Item(id=5)])
+ ]
+ ),
+ User(id=10, orders=[])
+ ],
+ q.order_by(User.id).all()
+ )
+
def test_catch_the_right_target(self):
# test eager join chaining to the "nested" join on the left,
# a new feature as of [ticket:2369]
self.tables.item_keywords)
mapper(User, users, properties={
- 'orders':relationship(Order, backref='user'), # o2m, m2o
+ 'orders': relationship(Order, backref='user'), # o2m, m2o
})
mapper(Order, orders, properties={
- 'items':relationship(Item, secondary=order_items,
+ 'items': relationship(Item, secondary=order_items,
order_by=items.c.id), #m2m
})
mapper(Item, items, properties={
- 'keywords':relationship(Keyword, secondary=item_keywords,
+ 'keywords': relationship(Keyword, secondary=item_keywords,
order_by=keywords.c.id) #m2m
})
mapper(Keyword, keywords)
self.classes.User,
self.tables.orders)
- mapper(User, users, properties = dict(
+ mapper(User, users, properties=dict(
orders =relationship(Order, lazy=False)
))
mapper(Order, orders, properties=dict(
)
+ def test_inner_join_nested_chaining_fixed(self):
+ users, items, order_items, Order, Item, User, orders = (self.tables.users,
+ self.tables.items,
+ self.tables.order_items,
+ self.classes.Order,
+ self.classes.Item,
+ self.classes.User,
+ self.tables.orders)
+
+ mapper(User, users, properties=dict(
+ orders = relationship(Order, lazy=False)
+ ))
+ mapper(Order, orders, properties=dict(
+ items=relationship(Item, secondary=order_items, lazy=False,
+ innerjoin='nested')
+ ))
+ mapper(Item, items)
+
+ sess = create_session()
+
+ self.assert_compile(
+ sess.query(User),
+ "SELECT users.id AS users_id, users.name AS users_name, items_1.id AS "
+ "items_1_id, items_1.description AS items_1_description, orders_1.id AS "
+ "orders_1_id, orders_1.user_id AS orders_1_user_id, orders_1.address_id AS "
+ "orders_1_address_id, orders_1.description AS orders_1_description, "
+ "orders_1.isopen AS orders_1_isopen "
+ "FROM users LEFT OUTER JOIN "
+ "(orders AS orders_1 JOIN order_items AS order_items_1 "
+ "ON orders_1.id = order_items_1.order_id "
+ "JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
+ "ON users.id = orders_1.user_id"
+ )
+
+
def test_inner_join_options(self):
users, items, order_items, Order, Item, User, orders = (self.tables.users,
self.tables.orders)
mapper(User, users, properties = dict(
- orders =relationship(Order, backref=backref('user', innerjoin=True), order_by=orders.c.id)
+ orders =relationship(Order, backref=backref('user', innerjoin=True),
+ order_by=orders.c.id)
))
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, order_by=items.c.id)