(left, right))
left_mapper, left_selectable, left_is_aliased = _entity_info(left)
- right_mapper, right_selectable, is_aliased_class = _entity_info(right)
+ right_mapper, right_selectable, right_is_aliased = _entity_info(right)
if right_mapper and prop and \
not right_mapper.common_parent(prop.mapper):
need_adapter = True
aliased_entity = right_mapper and \
- not is_aliased_class and \
+ not right_is_aliased and \
(
right_mapper.with_polymorphic or
isinstance(
)
)
- join_to_left = not is_aliased_class and not left_is_aliased
-
+ # this is an overly broad assumption here, but there's a
+ # very wide variety of situations where we rely upon orm.join's
+ # adaption to glue clauses together, with joined-table inheritance's
+ # wide array of variables taking up most of the space.
+ # Setting the flag here is still a guess, so it is a bug
+ # that we don't have definitive criterion to determine when
+ # adaption should be enabled (or perhaps that we're even doing the
+ # whole thing the way we are here).
+ join_to_left = not right_is_aliased and not left_is_aliased
+
if self._from_obj and left_selectable is not None:
replace_clause_index, clause = sql_util.find_join_source(
self._from_obj,
if clause is not None:
# the entire query's FROM clause is an alias of itself (i.e.
# from_self(), similar). if the left clause is that one,
- # ensure it aliases to the left side.
+ # ensure it adapts to the left side.
if self._from_obj_alias and clause is self._from_obj[0]:
join_to_left = True
-
+
+ # An exception case where adaption to the left edge is not
+ # desirable. See above note on join_to_left.
+ if join_to_left and isinstance(clause, expression.Join) and \
+ sql_util.clause_is_present(left_selectable, clause):
+ join_to_left = False
+
clause = orm_join(clause,
right,
onclause, isouter=outerjoin,
[User(id=7)]
)
+class SelfRefMixedTest(_base.MappedTest, AssertsCompiledSQL):
+ run_setup_mappers = 'once'
+
+ @classmethod
+ def define_tables(cls, metadata):
+ nodes = Table('nodes', metadata,
+ Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+ Column('parent_id', Integer, ForeignKey('nodes.id'))
+ )
+
+ sub_table = Table('sub_table', metadata,
+ Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+ Column('node_id', Integer, ForeignKey('nodes.id')),
+ )
+
+ assoc_table = Table('assoc_table', metadata,
+ Column('left_id', Integer, ForeignKey('nodes.id')),
+ Column('right_id', Integer, ForeignKey('nodes.id'))
+ )
+
+ @classmethod
+ @testing.resolve_artifact_names
+ def setup_classes(cls):
+ class Node(Base):
+ pass
+
+ class Sub(Base):
+ pass
+
+ mapper(Node, nodes, properties={
+ 'children':relationship(Node, lazy='select', join_depth=3,
+ backref=backref('parent', remote_side=[nodes.c.id])
+ ),
+ 'subs' : relationship(Sub),
+ 'assoc':relationship(Node,
+ secondary=assoc_table,
+ primaryjoin=nodes.c.id==assoc_table.c.left_id,
+ secondaryjoin=nodes.c.id==assoc_table.c.right_id)
+ })
+ mapper(Sub, sub_table)
+
+ @testing.resolve_artifact_names
+ def test_o2m_aliased_plus_o2m(self):
+ sess = create_session()
+ n1 = aliased(Node)
+
+ self.assert_compile(
+ sess.query(Node).join((n1, Node.children)).join((Sub, n1.subs)),
+ "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
+ "FROM nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
+ "JOIN sub_table ON nodes_1.id = sub_table.node_id"
+ )
+
+ self.assert_compile(
+ sess.query(Node).join((n1, Node.children)).join((Sub, Node.subs)),
+ "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
+ "FROM nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id "
+ "JOIN sub_table ON nodes.id = sub_table.node_id"
+ )
+
+ @testing.resolve_artifact_names
+ def test_m2m_aliased_plus_o2m(self):
+ sess = create_session()
+ n1 = aliased(Node)
+
+ self.assert_compile(
+ sess.query(Node).join((n1, Node.assoc)).join((Sub, n1.subs)),
+ "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
+ "FROM nodes JOIN assoc_table AS assoc_table_1 ON nodes.id = "
+ "assoc_table_1.left_id JOIN nodes AS nodes_1 ON nodes_1.id = "
+ "assoc_table_1.right_id JOIN sub_table ON nodes_1.id = sub_table.node_id",
+ )
+
+ self.assert_compile(
+ sess.query(Node).join((n1, Node.assoc)).join((Sub, Node.subs)),
+ "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id "
+ "FROM nodes JOIN assoc_table AS assoc_table_1 ON nodes.id = "
+ "assoc_table_1.left_id JOIN nodes AS nodes_1 ON nodes_1.id = "
+ "assoc_table_1.right_id JOIN sub_table ON nodes.id = sub_table.node_id",
+ )
+
+
class SelfReferentialTest(_base.MappedTest, AssertsCompiledSQL):
run_setup_mappers = 'once'
run_inserts = 'once'
Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
Column('parent_id', Integer, ForeignKey('nodes.id')),
Column('data', String(30)))
-
+
@classmethod
def insert_data(cls):
- global Node
-
+ # TODO: somehow using setup_classes()
+ # here normally is screwing up the other tests.
+
+ global Node, Sub
class Node(Base):
def append(self, node):
self.children.append(node)
-
+
mapper(Node, nodes, properties={
'children':relationship(Node, lazy='select', join_depth=3,
backref=backref('parent', remote_side=[nodes.c.id])
- )
+ ),
})
+
sess = create_session()
n1 = Node(data='n1')
n1.append(Node(data='n11'))
sess.flush()
sess.close()
+ @testing.resolve_artifact_names
def test_join(self):
sess = create_session()
join('parent', aliased=True, from_joinpoint=True).filter_by(data='n1').first()
assert node.data == 'n122'
+ @testing.resolve_artifact_names
def test_string_or_prop_aliased(self):
"""test that join('foo') behaves the same as join(Cls.foo) in a self
referential scenario.
use_default_dialect=True
)
+ @testing.resolve_artifact_names
def test_from_self_inside_excludes_outside(self):
"""test the propagation of aliased() from inside to outside
on a from_self()..
use_default_dialect=True
)
+ @testing.resolve_artifact_names
def test_explicit_join(self):
sess = create_session()
n1 = aliased(Node)
n2 = aliased(Node)
+
+ self.assert_compile(
+ join(Node, n1, 'children').join(n2, 'children'),
+ "nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id"
+ )
+
+ self.assert_compile(
+ join(Node, n1, Node.children).join(n2, n1.children),
+ "nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id"
+ )
+
+ # the join_to_left=False here is unfortunate. the default on this flag should
+ # be False.
+ self.assert_compile(
+ join(Node, n1, Node.children).join(n2, Node.children, join_to_left=False),
+ "nodes JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 ON nodes.id = nodes_2.parent_id"
+ )
+
+ self.assert_compile(
+ sess.query(Node).join((n1, Node.children)).join((n2, n1.children)),
+ "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.id = nodes_1.parent_id "
+ "JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id"
+ )
+
+ self.assert_compile(
+ sess.query(Node).join((n1, Node.children)).join((n2, Node.children)),
+ "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.id = nodes_1.parent_id "
+ "JOIN nodes AS nodes_2 ON nodes.id = nodes_2.parent_id"
+ )
node = sess.query(Node).select_from(join(Node, n1, 'children')).filter(n1.data=='n122').first()
assert node.data=='n12'
list(sess.query(Node).select_from(join(Node, n1, 'parent').join(n2, 'parent')).\
filter(and_(Node.data=='n122', n1.data=='n12', n2.data=='n1')).values(Node.data, n1.data, n2.data)),
[('n122', 'n12', 'n1')])
-
+
+ @testing.resolve_artifact_names
def test_join_to_nonaliased(self):
sess = create_session()
)
+ @testing.resolve_artifact_names
def test_multiple_explicit_entities(self):
sess = create_session()
)
+ @testing.resolve_artifact_names
def test_any(self):
sess = create_session()
eq_(sess.query(Node).filter(Node.children.any(Node.data=='n1')).all(), [])
eq_(sess.query(Node).filter(~Node.children.any()).order_by(Node.id).all(),
[Node(data='n11'), Node(data='n13'),Node(data='n121'),Node(data='n122'),Node(data='n123'),])
+ @testing.resolve_artifact_names
def test_has(self):
sess = create_session()
eq_(sess.query(Node).filter(Node.parent.has(Node.data=='n122')).all(), [])
eq_(sess.query(Node).filter(~Node.parent.has()).all(), [Node(data='n1')])
+ @testing.resolve_artifact_names
def test_contains(self):
sess = create_session()
n13 = sess.query(Node).filter(Node.data=='n13').one()
eq_(sess.query(Node).filter(Node.children.contains(n13)).all(), [Node(data='n1')])
+ @testing.resolve_artifact_names
def test_eq_ne(self):
sess = create_session()