- Patched a case where query.join() would adapt the
right side to the right side of the left's join
inappropriately [ticket:1925]
+
+ - Query.select_from() has been beefed up to help
+ ensure that a subsequent call to query.join()
+ will use the select_from() entity, assuming it's
+ a mapped entity and not a plain selectable,
+ as the default "left" side, not the first entity
+ in the Query object's list of entities.
- The exception raised by Session when it is used
subsequent to a subtransaction rollback (which is what
_only_load_props = None
_refresh_state = None
_from_obj = ()
+ _select_from_entity = None
_filter_aliases = None
_from_obj_alias = None
_joinpath = _joinpoint = util.frozendict()
return self._entities[0]
def _mapper_zero(self):
- return self._entity_zero().entity_zero
+ return self._select_from_entity or \
+ self._entity_zero().entity_zero
def _extension_zero(self):
ent = self._entity_zero()
def _joinpoint_zero(self):
return self._joinpoint.get(
- '_joinpoint_entity',
- self._entity_zero().entity_zero)
+ '_joinpoint_entity',
+ self._mapper_zero()
+ )
def _mapper_zero_or_none(self):
if not getattr(self._entities[0], 'primary_entity', False):
arg1, arg2 = arg1
else:
arg2 = None
-
+
# determine onclause/right_entity. there
# is a little bit of legacy behavior still at work here
# which means they might be in either order. may possibly
@_generative(_no_clauseelement_condition)
def select_from(self, *from_obj):
- """Set the `from_obj` parameter of the query and return the newly
- resulting ``Query``. This replaces the table which this Query selects
- from with the given table.
+ """Set the FROM clause of this :class:`.Query` explicitly.
- ``select_from()`` also accepts class arguments. Though usually not
- necessary, can ensure that the full selectable of the given mapper is
- applied, e.g. for joined-table mappers.
-
- """
+ Sending a mapped class or entity here effectively replaces the
+ "left edge" of any calls to :meth:`.Query.join`, when no
+ joinpoint is otherwise established - usually, the default "join
+ point" is the leftmost entity in the :class:`.Query` object's
+ list of entities to be selected.
+ Mapped entities or plain :class:`.Table` or other selectables
+ can be sent here which will form the default FROM clause.
+
+ """
obj = []
for fo in from_obj:
if _is_mapped_class(fo):
mapper, selectable, is_aliased_class = _entity_info(fo)
+ self._select_from_entity = fo
obj.append(selectable)
elif not isinstance(fo, expression.FromClause):
raise sa_exc.ArgumentError(
obj.append(fo)
self._set_select_from(*obj)
-
+
def __getitem__(self, item):
if isinstance(item, slice):
start, stop, step = util.decode_slice(item)
"Could not find a FROM",
sess.query(users.c.id).select_from(users).join, User
)
+
+ def test_select_from(self):
+ """Test that the left edge of the join can be set reliably with select_from()."""
+
+ sess = create_session()
+ self.assert_compile(
+ sess.query(Item.id).select_from(User).join(User.orders).join(Order.items),
+ "SELECT items.id AS items_id 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",
+ use_default_dialect=True
+ )
+
+ # here, the join really wants to add a second FROM clause
+ # for "Item". but select_from disallows that
+ self.assert_compile(
+ sess.query(Item.id).select_from(User).join((Item, User.id==Item.id)),
+ "SELECT items.id AS items_id FROM users JOIN items ON users.id = items.id",
+ use_default_dialect=True
+ )
+
+
+
def test_from_self_resets_joinpaths(self):
"""test a join from from_self() doesn't confuse joins inside the subquery