summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-04-26 15:04:54 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-04-26 15:04:54 -0400
commit34dc02773b6bdb2751b301146f2f3c9a938d4cbb (patch)
treedabfc3b285816838d45a8cf447b25fa017c8a0e2
parent0f26f549191fc74bfebac319b39323cb865f7a50 (diff)
parent22c4ae0aaf3a00e9020c3950a53d2a3238b2091c (diff)
downloadsqlalchemy-34dc02773b6bdb2751b301146f2f3c9a938d4cbb.tar.gz
merge default
-rw-r--r--doc/build/changelog/changelog_08.rst21
-rw-r--r--lib/sqlalchemy/orm/query.py84
-rw-r--r--lib/sqlalchemy/orm/strategies.py7
-rw-r--r--lib/sqlalchemy/orm/util.py108
-rw-r--r--lib/sqlalchemy/sql/expression.py2
-rw-r--r--lib/sqlalchemy/sql/util.py2
-rw-r--r--test/ext/declarative/test_inheritance.py95
-rw-r--r--test/orm/inheritance/test_relationship.py264
-rw-r--r--test/orm/test_joins.py212
-rw-r--r--test/orm/test_query.py15
10 files changed, 613 insertions, 197 deletions
diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst
index 97b3626af..9d392e778 100644
--- a/doc/build/changelog/changelog_08.rst
+++ b/doc/build/changelog/changelog_08.rst
@@ -8,6 +8,27 @@
.. change::
:tags: bug, orm
+ :tickets: 2714
+
+ A significant improvement to the inner workings of query.join(),
+ such that the decisionmaking involved on how to join has been
+ dramatically simplified. New test cases now pass such as
+ multiple joins extending from the middle of an already complex
+ series of joins involving inheritance and such. Joining from
+ deeply nested subquery structures is still complicated and
+ not without caveats, but with these improvements the edge
+ cases are hopefully pushed even farther out to the edges.
+
+ .. change::
+ :tags: feature, orm
+ :tickets: 2673
+
+ Added a convenience method to Query that turns a query into an
+ EXISTS subquery of the form
+ ``EXISTS (SELECT 1 FROM ... WHERE ...)``.
+
+ .. change::
+ :tags: bug, orm
Added a conditional to the unpickling process for ORM
mapped objects, such that if the reference to the object
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index b18e28abb..c9f3a2699 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -1812,9 +1812,9 @@ class Query(object):
"are the same entity" %
(left, right))
- right, right_is_aliased, onclause = self._prepare_right_side(
+ right, onclause = self._prepare_right_side(
right, onclause,
- outerjoin, create_aliases,
+ create_aliases,
prop)
# if joining on a MapperProperty path,
@@ -1825,16 +1825,11 @@ class Query(object):
'prev': ((left, right, prop.key), self._joinpoint)
})
else:
- self._joinpoint = {
- '_joinpoint_entity': right
- }
+ self._joinpoint = {'_joinpoint_entity': right}
- self._join_to_left(left, right,
- right_is_aliased,
- onclause, outerjoin)
+ self._join_to_left(left, right, onclause, outerjoin)
- def _prepare_right_side(self, right, onclause, outerjoin,
- create_aliases, prop):
+ def _prepare_right_side(self, right, onclause, create_aliases, prop):
info = inspect(right)
right_mapper, right_selectable, right_is_aliased = \
@@ -1911,48 +1906,22 @@ class Query(object):
)
)
- return right, right_is_aliased, onclause
+ return right, onclause
- def _join_to_left(self, left, right, right_is_aliased,
- onclause, outerjoin):
+ def _join_to_left(self, left, right, onclause, outerjoin):
info = inspect(left)
- left_mapper, left_selectable, left_is_aliased = \
- getattr(info, 'mapper', None),\
- info.selectable,\
- getattr(info, 'is_aliased_class', False)
+ left_mapper = getattr(info, 'mapper', None)
+ left_selectable = info.selectable
- # 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:
+ if self._from_obj:
replace_clause_index, clause = sql_util.find_join_source(
self._from_obj,
left_selectable)
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 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
-
try:
clause = orm_join(clause,
right,
- onclause, isouter=outerjoin,
- join_to_left=join_to_left)
+ onclause, isouter=outerjoin)
except sa_exc.ArgumentError, ae:
raise sa_exc.InvalidRequestError(
"Could not find a FROM clause to join from. "
@@ -1971,18 +1940,13 @@ class Query(object):
break
else:
clause = left
- elif left_selectable is not None:
- clause = left_selectable
else:
- clause = None
+ clause = left_selectable
- if clause is None:
- raise sa_exc.InvalidRequestError(
- "Could not find a FROM clause to join from")
+ assert clause is not None
try:
- clause = orm_join(clause, right, onclause,
- isouter=outerjoin, join_to_left=join_to_left)
+ clause = orm_join(clause, right, onclause, isouter=outerjoin)
except sa_exc.ArgumentError, ae:
raise sa_exc.InvalidRequestError(
"Could not find a FROM clause to join from. "
@@ -2373,6 +2337,26 @@ class Query(object):
kwargs.get('offset') is not None or
kwargs.get('distinct', False))
+ def exists(self):
+ """A convenience method that turns a query into an EXISTS subquery
+ of the form EXISTS (SELECT 1 FROM ... WHERE ...).
+
+ e.g.::
+
+ q = session.query(User).filter(User.name == 'fred')
+ session.query(q.exists())
+
+ Producing SQL similar to::
+
+ SELECT EXISTS (
+ SELECT 1 FROM users WHERE users.name = :name_1
+ ) AS anon_1
+
+ .. versionadded:: 0.8.1
+
+ """
+ return sql.exists(self.with_entities('1').statement)
+
def count(self):
"""Return a count of rows this Query would return.
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 0eed50ea4..6660a39ef 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -1152,7 +1152,6 @@ class JoinedLoader(AbstractRelationshipLoader):
towrap = context.eager_joins.setdefault(entity_key, default_towrap)
- join_to_left = False
if adapter:
if getattr(adapter, 'aliased_class', None):
onclause = getattr(
@@ -1168,11 +1167,6 @@ class JoinedLoader(AbstractRelationshipLoader):
self.key, self.parent_property
)
- if onclause is self.parent_property:
- # TODO: this is a temporary hack to
- # account for polymorphic eager loads where
- # the eagerload is referencing via of_type().
- join_to_left = True
else:
onclause = self.parent_property
@@ -1182,7 +1176,6 @@ class JoinedLoader(AbstractRelationshipLoader):
towrap,
clauses.aliased_class,
onclause,
- join_to_left=join_to_left,
isouter=not innerjoin
)
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index f3b8e271d..35cb0bdf5 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -870,73 +870,59 @@ class _ORMJoin(expression.Join):
__visit_name__ = expression.Join.__visit_name__
- def __init__(self, left, right, onclause=None,
- isouter=False, join_to_left=True):
- adapt_from = None
-
- if hasattr(left, '_orm_mappers'):
- left_mapper = left._orm_mappers[1]
- if join_to_left:
- adapt_from = left.right
- else:
- info = inspection.inspect(left)
- left_mapper = getattr(info, 'mapper', None)
- left = info.selectable
- left_is_aliased = getattr(info, 'is_aliased_class', False)
+ def __init__(self, left, right, onclause=None, isouter=False):
+
+ left_info = inspection.inspect(left)
+ left_orm_info = getattr(left, '_joined_from_info', left_info)
- if join_to_left and (left_is_aliased or not left_mapper):
- adapt_from = left
+ right_info = inspection.inspect(right)
+ adapt_to = right_info.selectable
- info = inspection.inspect(right)
- right_mapper = getattr(info, 'mapper', None)
- right = info.selectable
- right_is_aliased = getattr(info, 'is_aliased_class', False)
+ self._joined_from_info = right_info
- if right_is_aliased:
- adapt_to = right
+ if isinstance(onclause, basestring):
+ onclause = getattr(left_orm_info.entity, onclause)
+
+ if isinstance(onclause, attributes.QueryableAttribute):
+ on_selectable = onclause.comparator._source_selectable()
+ prop = onclause.property
+ elif isinstance(onclause, MapperProperty):
+ prop = onclause
+ on_selectable = prop.parent.selectable
else:
- adapt_to = None
-
- if left_mapper or right_mapper:
- self._orm_mappers = (left_mapper, right_mapper)
-
- if isinstance(onclause, basestring):
- prop = left_mapper.get_property(onclause)
- elif isinstance(onclause, attributes.QueryableAttribute):
- if adapt_from is None:
- adapt_from = onclause.comparator._source_selectable()
- prop = onclause.property
- elif isinstance(onclause, MapperProperty):
- prop = onclause
+ prop = None
+
+ if prop:
+ if sql_util.clause_is_present(on_selectable, left_info.selectable):
+ adapt_from = on_selectable
else:
- prop = None
+ adapt_from = left_info.selectable
- if prop:
- pj, sj, source, dest, \
+ pj, sj, source, dest, \
secondary, target_adapter = prop._create_joins(
- source_selectable=adapt_from,
- dest_selectable=adapt_to,
- source_polymorphic=True,
- dest_polymorphic=True,
- of_type=right_mapper)
-
- if sj is not None:
- left = sql.join(left, secondary, pj, isouter)
- onclause = sj
- else:
- onclause = pj
- self._target_adapter = target_adapter
+ source_selectable=adapt_from,
+ dest_selectable=adapt_to,
+ source_polymorphic=True,
+ dest_polymorphic=True,
+ of_type=right_info.mapper)
+
+ if sj is not None:
+ left = sql.join(left, secondary, pj, isouter)
+ onclause = sj
+ else:
+ onclause = pj
+ self._target_adapter = target_adapter
expression.Join.__init__(self, left, right, onclause, isouter)
- def join(self, right, onclause=None, isouter=False, join_to_left=True):
- return _ORMJoin(self, right, onclause, isouter, join_to_left)
+ def join(self, right, onclause=None, isouter=False, join_to_left=None):
+ return _ORMJoin(self, right, onclause, isouter)
- def outerjoin(self, right, onclause=None, join_to_left=True):
- return _ORMJoin(self, right, onclause, True, join_to_left)
+ def outerjoin(self, right, onclause=None, join_to_left=None):
+ return _ORMJoin(self, right, onclause, True)
-def join(left, right, onclause=None, isouter=False, join_to_left=True):
+def join(left, right, onclause=None, isouter=False, join_to_left=None):
"""Produce an inner join between left and right clauses.
:func:`.orm.join` is an extension to the core join interface
@@ -947,11 +933,6 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
be a SQL expression, or an attribute or string name
referencing a configured :func:`.relationship`.
- ``join_to_left`` indicates to attempt aliasing the ON clause,
- in whatever form it is passed, to the selectable
- passed as the left side. If False, the onclause
- is used as is.
-
:func:`.orm.join` is not commonly needed in modern usage,
as its functionality is encapsulated within that of the
:meth:`.Query.join` method, which features a
@@ -975,11 +956,14 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
See :meth:`.Query.join` for information on modern usage
of ORM level joins.
+ .. versionchanged:: 0.8.1 - the ``join_to_left`` parameter
+ is no longer used, and is deprecated.
+
"""
- return _ORMJoin(left, right, onclause, isouter, join_to_left)
+ return _ORMJoin(left, right, onclause, isouter)
-def outerjoin(left, right, onclause=None, join_to_left=True):
+def outerjoin(left, right, onclause=None, join_to_left=None):
"""Produce a left outer join between left and right clauses.
This is the "outer join" version of the :func:`.orm.join` function,
@@ -987,7 +971,7 @@ def outerjoin(left, right, onclause=None, join_to_left=True):
See that function's documentation for other usage details.
"""
- return _ORMJoin(left, right, onclause, True, join_to_left)
+ return _ORMJoin(left, right, onclause, True)
def with_parent(instance, prop):
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index d2e644ce2..7846ac3b2 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -3909,7 +3909,7 @@ class Join(FromClause):
def is_derived_from(self, fromclause):
return fromclause is self or \
- self.left.is_derived_from(fromclause) or\
+ self.left.is_derived_from(fromclause) or \
self.right.is_derived_from(fromclause)
def self_group(self, against=None):
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 520c90f99..4aa2d7496 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -203,7 +203,7 @@ def clause_is_present(clause, search):
stack = [search]
while stack:
elem = stack.pop()
- if clause is elem:
+ if clause == elem: # use == here so that Annotated's compare
return True
elif isinstance(elem, expression.Join):
stack.extend((elem.left, elem.right))
diff --git a/test/ext/declarative/test_inheritance.py b/test/ext/declarative/test_inheritance.py
index f0372e8ee..1a3a4aba5 100644
--- a/test/ext/declarative/test_inheritance.py
+++ b/test/ext/declarative/test_inheritance.py
@@ -2,18 +2,13 @@
from sqlalchemy.testing import eq_, assert_raises, \
assert_raises_message, is_
from sqlalchemy.ext import declarative as decl
-from sqlalchemy import exc
import sqlalchemy as sa
from sqlalchemy import testing
-from sqlalchemy import MetaData, Integer, String, ForeignKey, \
- ForeignKeyConstraint, Index
+from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.testing.schema import Table, Column
from sqlalchemy.orm import relationship, create_session, class_mapper, \
- joinedload, configure_mappers, backref, clear_mappers, \
- polymorphic_union, deferred, column_property, composite,\
- Session
-from sqlalchemy.testing import eq_
-from sqlalchemy.util import classproperty
+ configure_mappers, clear_mappers, \
+ polymorphic_union, deferred, Session
from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, \
ConcreteBase, has_inherited_table
from sqlalchemy.testing import fixtures
@@ -56,10 +51,10 @@ class DeclarativeInheritanceTest(DeclarativeTestBase):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
- a=Column(Integer)
- b=Column(Integer)
- c=Column(Integer)
- d=Column(Integer)
+ a = Column(Integer)
+ b = Column(Integer)
+ c = Column(Integer)
+ d = Column(Integer)
discriminator = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': discriminator,
'polymorphic_identity': 'person',
@@ -149,7 +144,10 @@ class DeclarativeInheritanceTest(DeclarativeTestBase):
# is appropriately treated as the "id" column in the "manager"
# table (reversed from 0.6's behavior.)
- assert Manager.id.property.columns == [Manager.__table__.c.id, Person.__table__.c.id]
+ eq_(
+ Manager.id.property.columns,
+ [Manager.__table__.c.id, Person.__table__.c.id]
+ )
# assert that the "id" column is available without a second
# load. as of 0.7, the ColumnProperty tests all columns
@@ -195,8 +193,8 @@ class DeclarativeInheritanceTest(DeclarativeTestBase):
sess.add(e1)
sess.flush()
sess.expunge_all()
- eq_(sess.query(Person).first(), Engineer(primary_language='java'
- , name='dilbert'))
+ eq_(sess.query(Person).first(),
+ Engineer(primary_language='java', name='dilbert'))
def test_add_parentcol_after_the_fact(self):
@@ -815,8 +813,8 @@ class DeclarativeInheritanceTest(DeclarativeTestBase):
String(50))
foo_bar = Column(Integer, primary_key=True)
- assert_raises_message(sa.exc.ArgumentError, 'place primary key'
- , go)
+ assert_raises_message(sa.exc.ArgumentError,
+ 'place primary key', go)
def test_single_no_table_args(self):
@@ -848,13 +846,12 @@ class DeclarativeInheritanceTest(DeclarativeTestBase):
@testing.emits_warning("This declarative")
def test_dupe_name_in_hierarchy(self):
class A(Base):
- __tablename__ = "a"
- id = Column( Integer, primary_key=True)
+ __tablename__ = "a"
+ id = Column(Integer, primary_key=True)
a_1 = A
class A(a_1):
- __tablename__ = 'b'
-
- id = Column(Integer(),ForeignKey(a_1.id), primary_key = True)
+ __tablename__ = 'b'
+ id = Column(Integer(), ForeignKey(a_1.id), primary_key=True)
assert A.__mapper__.inherits is a_1.__mapper__
@@ -897,7 +894,8 @@ class OverlapColPrecedenceTest(DeclarativeTestBase):
class Engineer(Person):
__tablename__ = 'engineer'
- id = Column("eid", Integer, ForeignKey('person.id'), primary_key=True)
+ id = Column("eid", Integer, ForeignKey('person.id'),
+ primary_key=True)
self._run_test(Engineer, "eid", "id")
@@ -908,7 +906,8 @@ class OverlapColPrecedenceTest(DeclarativeTestBase):
class Engineer(Person):
__tablename__ = 'engineer'
- id = Column("eid", Integer, ForeignKey('person.pid'), primary_key=True)
+ id = Column("eid", Integer, ForeignKey('person.pid'),
+ primary_key=True)
self._run_test(Engineer, "eid", "pid")
@@ -959,22 +958,24 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
def test_explicit(self):
engineers = Table('engineers', Base.metadata, Column('id',
Integer, primary_key=True,
- test_needs_autoincrement=True), Column('name'
- , String(50)), Column('primary_language',
- String(50)))
+ test_needs_autoincrement=True),
+ Column('name', String(50)),
+ Column('primary_language', String(50)))
managers = Table('managers', Base.metadata,
- Column('id',Integer, primary_key=True, test_needs_autoincrement=True),
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
Column('name', String(50)),
Column('golf_swing', String(50))
)
boss = Table('boss', Base.metadata,
- Column('id',Integer, primary_key=True, test_needs_autoincrement=True),
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
Column('name', String(50)),
Column('golf_swing', String(50))
)
punion = polymorphic_union({
'engineer': engineers,
- 'manager' : managers,
+ 'manager': managers,
'boss': boss}, 'type', 'punion')
class Employee(Base, fixtures.ComparableEntity):
@@ -1050,8 +1051,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
name = Column(String(50))
golf_swing = Column(String(40))
__mapper_args__ = {
- 'polymorphic_identity':'manager',
- 'concrete':True}
+ 'polymorphic_identity': 'manager',
+ 'concrete': True}
class Boss(Manager):
__tablename__ = 'boss'
@@ -1060,8 +1061,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
name = Column(String(50))
golf_swing = Column(String(40))
__mapper_args__ = {
- 'polymorphic_identity':'boss',
- 'concrete':True}
+ 'polymorphic_identity': 'boss',
+ 'concrete': True}
class Engineer(Employee):
__tablename__ = 'engineer'
@@ -1069,8 +1070,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
test_needs_autoincrement=True)
name = Column(String(50))
primary_language = Column(String(40))
- __mapper_args__ = {'polymorphic_identity':'engineer',
- 'concrete':True}
+ __mapper_args__ = {'polymorphic_identity': 'engineer',
+ 'concrete': True}
self._roundtrip(Employee, Manager, Engineer, Boss)
@@ -1081,8 +1082,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
test_needs_autoincrement=True)
name = Column(String(50))
__mapper_args__ = {
- 'polymorphic_identity':'employee',
- 'concrete':True}
+ 'polymorphic_identity': 'employee',
+ 'concrete': True}
class Manager(Employee):
__tablename__ = 'manager'
employee_id = Column(Integer, primary_key=True,
@@ -1090,8 +1091,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
name = Column(String(50))
golf_swing = Column(String(40))
__mapper_args__ = {
- 'polymorphic_identity':'manager',
- 'concrete':True}
+ 'polymorphic_identity': 'manager',
+ 'concrete': True}
class Boss(Manager):
__tablename__ = 'boss'
@@ -1100,8 +1101,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
name = Column(String(50))
golf_swing = Column(String(40))
__mapper_args__ = {
- 'polymorphic_identity':'boss',
- 'concrete':True}
+ 'polymorphic_identity': 'boss',
+ 'concrete': True}
class Engineer(Employee):
__tablename__ = 'engineer'
@@ -1109,8 +1110,8 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
test_needs_autoincrement=True)
name = Column(String(50))
primary_language = Column(String(40))
- __mapper_args__ = {'polymorphic_identity':'engineer',
- 'concrete':True}
+ __mapper_args__ = {'polymorphic_identity': 'engineer',
+ 'concrete': True}
self._roundtrip(Employee, Manager, Engineer, Boss)
@@ -1170,7 +1171,7 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
__mapper_args__ = {
'polymorphic_identity': "manager",
- 'concrete':True}
+ 'concrete': True}
class Boss(Manager):
__tablename__ = 'boss'
@@ -1185,7 +1186,7 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
__mapper_args__ = {
'polymorphic_identity': "boss",
- 'concrete':True}
+ 'concrete': True}
class Engineer(Employee):
__tablename__ = 'engineer'
@@ -1198,5 +1199,5 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
def type(self):
return "engineer"
__mapper_args__ = {'polymorphic_identity': "engineer",
- 'concrete':True}
+ 'concrete': True}
self._roundtrip(Employee, Manager, Engineer, Boss, explicit_type=True)
diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py
index 509d540ef..36dbb7d27 100644
--- a/test/orm/inheritance/test_relationship.py
+++ b/test/orm/inheritance/test_relationship.py
@@ -1113,3 +1113,267 @@ class SubClassToSubClassFromParentTest(fixtures.MappedTest):
a1 = session.query(A).first()
eq_(a1.related, [])
self.assert_sql_count(testing.db, go, 3)
+
+
+class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest):
+ """
+ Two different joined-inh subclasses, led by a
+ parent, with two distinct endpoints:
+
+ parent -> subcl1 -> subcl2 -> (ep1, ep2)
+
+ the join to ep2 indicates we need to join
+ from the middle of the joinpoint, skipping ep1
+
+ """
+
+ run_create_tables = None
+ run_deletes = None
+ __dialect__ = 'default'
+
+ @classmethod
+ def define_tables(cls, metadata):
+ Table('parent', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('data', String(30))
+ )
+ Table('base1', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('data', String(30))
+ )
+ Table('sub1', metadata,
+ Column('id', Integer, ForeignKey('base1.id'), primary_key=True),
+ Column('parent_id', ForeignKey('parent.id')),
+ Column('subdata', String(30))
+ )
+
+ Table('base2', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('base1_id', ForeignKey('base1.id')),
+ Column('data', String(30))
+ )
+ Table('sub2', metadata,
+ Column('id', Integer, ForeignKey('base2.id'), primary_key=True),
+ Column('subdata', String(30))
+ )
+ Table('ep1', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('base2_id', Integer, ForeignKey('base2.id')),
+ Column('data', String(30))
+ )
+ Table('ep2', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('base2_id', Integer, ForeignKey('base2.id')),
+ Column('data', String(30))
+ )
+
+ @classmethod
+ def setup_classes(cls):
+ class Parent(cls.Comparable):
+ pass
+ class Base1(cls.Comparable):
+ pass
+ class Sub1(Base1):
+ pass
+ class Base2(cls.Comparable):
+ pass
+ class Sub2(Base2):
+ pass
+ class EP1(cls.Comparable):
+ pass
+ class EP2(cls.Comparable):
+ pass
+
+ @classmethod
+ def _classes(cls):
+ return cls.classes.Parent, cls.classes.Base1,\
+ cls.classes.Base2, cls.classes.Sub1,\
+ cls.classes.Sub2, cls.classes.EP1,\
+ cls.classes.EP2
+
+ @classmethod
+ def setup_mappers(cls):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = cls._classes()
+
+ mapper(Parent, cls.tables.parent, properties={
+ 'sub1': relationship(Sub1)
+ })
+ mapper(Base1, cls.tables.base1, properties={
+ 'sub2': relationship(Sub2)
+ })
+ mapper(Sub1, cls.tables.sub1, inherits=Base1)
+ mapper(Base2, cls.tables.base2, properties={
+ 'ep1': relationship(EP1),
+ 'ep2': relationship(EP2)
+ })
+ mapper(Sub2, cls.tables.sub2, inherits=Base2)
+ mapper(EP1, cls.tables.ep1)
+ mapper(EP2, cls.tables.ep2)
+
+ def test_one(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s = Session()
+ self.assert_compile(
+ s.query(Parent).join(Parent.sub1, Sub1.sub2).
+ join(Sub2.ep1).
+ join(Sub2.ep2),
+ "SELECT parent.id AS parent_id, parent.data AS parent_data "
+ "FROM parent JOIN (SELECT base1.id AS base1_id, "
+ "base1.data AS base1_data, sub1.id AS sub1_id, "
+ "sub1.parent_id AS sub1_parent_id, sub1.subdata AS sub1_subdata "
+ "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_1 "
+ "ON parent.id = anon_1.sub1_parent_id JOIN "
+ "(SELECT base2.id AS base2_id, base2.base1_id AS base2_base1_id, "
+ "base2.data AS base2_data, sub2.id AS sub2_id, "
+ "sub2.subdata AS sub2_subdata FROM base2 JOIN sub2 "
+ "ON base2.id = sub2.id) AS anon_2 "
+ "ON anon_1.base1_id = anon_2.base2_base1_id "
+ "JOIN ep1 ON anon_2.base2_id = ep1.base2_id "
+ "JOIN ep2 ON anon_2.base2_id = ep2.base2_id"
+ )
+
+ def test_two(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s2a = aliased(Sub2)
+
+ s = Session()
+ self.assert_compile(
+ s.query(Parent).join(Parent.sub1).
+ join(s2a, Sub1.sub2),
+ "SELECT parent.id AS parent_id, parent.data AS parent_data "
+ "FROM parent JOIN (SELECT base1.id AS base1_id, "
+ "base1.data AS base1_data, sub1.id AS sub1_id, "
+ "sub1.parent_id AS sub1_parent_id, sub1.subdata AS sub1_subdata "
+ "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_1 "
+ "ON parent.id = anon_1.sub1_parent_id JOIN "
+ "(SELECT base2.id AS base2_id, base2.base1_id AS base2_base1_id, "
+ "base2.data AS base2_data, sub2.id AS sub2_id, "
+ "sub2.subdata AS sub2_subdata FROM base2 JOIN sub2 "
+ "ON base2.id = sub2.id) AS anon_2 "
+ "ON anon_1.base1_id = anon_2.base2_base1_id"
+ )
+
+ def test_three(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s = Session()
+ self.assert_compile(
+ s.query(Base1).join(Base1.sub2).
+ join(Sub2.ep1).\
+ join(Sub2.ep2),
+ "SELECT base1.id AS base1_id, base1.data AS base1_data "
+ "FROM base1 JOIN (SELECT base2.id AS base2_id, base2.base1_id "
+ "AS base2_base1_id, base2.data AS base2_data, sub2.id AS sub2_id, "
+ "sub2.subdata AS sub2_subdata FROM base2 JOIN sub2 "
+ "ON base2.id = sub2.id) AS anon_1 ON base1.id = "
+ "anon_1.base2_base1_id "
+ "JOIN ep1 ON anon_1.base2_id = ep1.base2_id "
+ "JOIN ep2 ON anon_1.base2_id = ep2.base2_id"
+ )
+
+ def test_four(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s = Session()
+ self.assert_compile(
+ s.query(Sub2).join(Base1, Base1.id == Sub2.base1_id).
+ join(Sub2.ep1).\
+ join(Sub2.ep2),
+ "SELECT sub2.id AS sub2_id, base2.id AS base2_id, "
+ "base2.base1_id AS base2_base1_id, base2.data AS base2_data, "
+ "sub2.subdata AS sub2_subdata "
+ "FROM base2 JOIN sub2 ON base2.id = sub2.id "
+ "JOIN base1 ON base1.id = base2.base1_id "
+ "JOIN ep1 ON base2.id = ep1.base2_id "
+ "JOIN ep2 ON base2.id = ep2.base2_id"
+ )
+
+ def test_five(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s = Session()
+ self.assert_compile(
+ s.query(Sub2).join(Sub1, Sub1.id == Sub2.base1_id).
+ join(Sub2.ep1).\
+ join(Sub2.ep2),
+ "SELECT sub2.id AS sub2_id, base2.id AS base2_id, "
+ "base2.base1_id AS base2_base1_id, base2.data AS base2_data, "
+ "sub2.subdata AS sub2_subdata "
+ "FROM base2 JOIN sub2 ON base2.id = sub2.id "
+ "JOIN "
+ "(SELECT base1.id AS base1_id, base1.data AS base1_data, "
+ "sub1.id AS sub1_id, sub1.parent_id AS sub1_parent_id, "
+ "sub1.subdata AS sub1_subdata "
+ "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_1 "
+ "ON anon_1.sub1_id = base2.base1_id "
+ "JOIN ep1 ON base2.id = ep1.base2_id "
+ "JOIN ep2 ON base2.id = ep2.base2_id"
+ )
+
+ def test_six(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s = Session()
+ self.assert_compile(
+ s.query(Sub2).from_self().\
+ join(Sub2.ep1).
+ join(Sub2.ep2),
+ "SELECT anon_1.sub2_id AS anon_1_sub2_id, "
+ "anon_1.base2_id AS anon_1_base2_id, "
+ "anon_1.base2_base1_id AS anon_1_base2_base1_id, "
+ "anon_1.base2_data AS anon_1_base2_data, "
+ "anon_1.sub2_subdata AS anon_1_sub2_subdata "
+ "FROM (SELECT sub2.id AS sub2_id, base2.id AS base2_id, "
+ "base2.base1_id AS base2_base1_id, base2.data AS base2_data, "
+ "sub2.subdata AS sub2_subdata "
+ "FROM base2 JOIN sub2 ON base2.id = sub2.id) AS anon_1 "
+ "JOIN ep1 ON anon_1.base2_id = ep1.base2_id "
+ "JOIN ep2 ON anon_1.base2_id = ep2.base2_id"
+ )
+
+ def test_seven(self):
+ Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes()
+
+ s = Session()
+ self.assert_compile(
+ # adding Sub2 to the entities list helps it,
+ # otherwise the joins for Sub2.ep1/ep2 don't have columns
+ # to latch onto. Can't really make it better than this
+ s.query(Parent, Sub2).join(Parent.sub1).\
+ join(Sub1.sub2).from_self().\
+ join(Sub2.ep1).
+ join(Sub2.ep2),
+ "SELECT anon_1.parent_id AS anon_1_parent_id, "
+ "anon_1.parent_data AS anon_1_parent_data, "
+ "anon_1.anon_2_sub2_id AS anon_1_anon_2_sub2_id, "
+ "anon_1.anon_2_base2_id AS anon_1_anon_2_base2_id, "
+ "anon_1.anon_2_base2_base1_id AS anon_1_anon_2_base2_base1_id, "
+ "anon_1.anon_2_base2_data AS anon_1_anon_2_base2_data, "
+ "anon_1.anon_2_sub2_subdata AS anon_1_anon_2_sub2_subdata "
+ "FROM (SELECT parent.id AS parent_id, parent.data AS parent_data, "
+ "anon_2.sub2_id AS anon_2_sub2_id, "
+ "anon_2.base2_id AS anon_2_base2_id, "
+ "anon_2.base2_base1_id AS anon_2_base2_base1_id, "
+ "anon_2.base2_data AS anon_2_base2_data, "
+ "anon_2.sub2_subdata AS anon_2_sub2_subdata "
+ "FROM parent JOIN (SELECT base1.id AS base1_id, "
+ "base1.data AS base1_data, sub1.id AS sub1_id, "
+ "sub1.parent_id AS sub1_parent_id, sub1.subdata AS sub1_subdata "
+ "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_3 "
+ "ON parent.id = anon_3.sub1_parent_id JOIN "
+ "(SELECT base2.id AS base2_id, base2.base1_id AS base2_base1_id, "
+ "base2.data AS base2_data, sub2.id AS sub2_id, "
+ "sub2.subdata AS sub2_subdata "
+ "FROM base2 JOIN sub2 ON base2.id = sub2.id) AS anon_2 "
+ "ON anon_3.base1_id = anon_2.base2_base1_id) AS anon_1 "
+ "JOIN ep1 ON anon_1.anon_2_base2_id = ep1.base2_id "
+ "JOIN ep2 ON anon_1.anon_2_base2_id = ep2.base2_id"
+ )
+
diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py
index 4c0a193a0..2bf0d8d92 100644
--- a/test/orm/test_joins.py
+++ b/test/orm/test_joins.py
@@ -215,7 +215,7 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL):
, use_default_dialect = True
)
- def test_prop_with_polymorphic(self):
+ def test_prop_with_polymorphic_1(self):
Person, Manager, Paperwork = (self.classes.Person,
self.classes.Manager,
self.classes.Paperwork)
@@ -238,6 +238,13 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL):
, use_default_dialect=True
)
+ def test_prop_with_polymorphic_2(self):
+ Person, Manager, Paperwork = (self.classes.Person,
+ self.classes.Manager,
+ self.classes.Paperwork)
+
+ sess = create_session()
+
self.assert_compile(
sess.query(Person).with_polymorphic(Manager).
join('paperwork', aliased=True).
@@ -426,14 +433,14 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
#)
self.assert_compile(
- sess.query(User).join((Order, User.id==Order.user_id)),
+ sess.query(User).join((Order, User.id == Order.user_id)),
"SELECT users.id AS users_id, users.name AS users_name "
"FROM users JOIN orders ON users.id = orders.user_id",
)
self.assert_compile(
sess.query(User).join(
- (Order, User.id==Order.user_id),
+ (Order, User.id == Order.user_id),
(Item, Order.items)),
"SELECT users.id AS users_id, users.name AS users_name "
"FROM users JOIN orders ON users.id = orders.user_id "
@@ -449,7 +456,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"FROM users JOIN orders ON users.id = orders.user_id",
)
- def test_single_prop(self):
+ def test_single_prop_1(self):
Item, Order, User, Address = (self.classes.Item,
self.classes.Order,
self.classes.User,
@@ -462,14 +469,27 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"FROM users JOIN orders ON users.id = orders.user_id"
)
+ def test_single_prop_2(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
self.assert_compile(
sess.query(User).join(Order.user),
"SELECT users.id AS users_id, users.name AS users_name "
"FROM orders JOIN users ON users.id = orders.user_id"
)
+ def test_single_prop_3(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
oalias1 = aliased(Order)
- oalias2 = aliased(Order)
self.assert_compile(
sess.query(User).join(oalias1.user),
@@ -477,6 +497,15 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"FROM orders AS orders_1 JOIN users ON users.id = orders_1.user_id"
)
+ def test_single_prop_4(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
+ oalias1 = aliased(Order)
+ oalias2 = aliased(Order)
# another nonsensical query. (from [ticket:1537]).
# in this case, the contract of "left to right" is honored
self.assert_compile(
@@ -486,6 +515,13 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"orders AS orders_2 JOIN users ON users.id = orders_2.user_id"
)
+ def test_single_prop_5(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
self.assert_compile(
sess.query(User).join(User.orders, Order.items),
"SELECT users.id AS users_id, users.name AS users_name FROM users "
@@ -493,6 +529,13 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"ON orders.id = order_items_1.order_id JOIN items ON items.id = order_items_1.item_id"
)
+ def test_single_prop_6(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
ualias = aliased(User)
self.assert_compile(
sess.query(ualias).join(ualias.orders),
@@ -500,6 +543,13 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"FROM users AS users_1 JOIN orders ON users_1.id = orders.user_id"
)
+ def test_single_prop_7(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
# this query is somewhat nonsensical. the old system didn't render a correct
# query for this. In this case its the most faithful to what was asked -
# there's no linkage between User.orders and "oalias", so two FROM elements
@@ -513,8 +563,16 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"JOIN items ON items.id = order_items_1.item_id"
)
+ def test_single_prop_8(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
# same as before using an aliased() for User as well
ualias = aliased(User)
+ oalias = aliased(Order)
self.assert_compile(
sess.query(ualias).join(ualias.orders, oalias.items),
"SELECT users_1.id AS users_1_id, users_1.name AS users_1_name FROM users AS users_1 "
@@ -523,23 +581,47 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"JOIN items ON items.id = order_items_1.item_id"
)
+ def test_single_prop_9(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
self.assert_compile(
- sess.query(User).filter(User.name=='ed').from_self().join(User.orders),
+ sess.query(User).filter(User.name == 'ed').from_self().
+ join(User.orders),
"SELECT anon_1.users_id AS anon_1_users_id, anon_1.users_name AS anon_1_users_name "
"FROM (SELECT users.id AS users_id, users.name AS users_name "
"FROM users "
"WHERE users.name = :name_1) AS anon_1 JOIN orders ON anon_1.users_id = orders.user_id"
)
+ def test_single_prop_10(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
self.assert_compile(
- sess.query(User).join(User.addresses, aliased=True).filter(Address.email_address=='foo'),
+ sess.query(User).join(User.addresses, aliased=True).
+ filter(Address.email_address == 'foo'),
"SELECT users.id AS users_id, users.name AS users_name "
"FROM users JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id "
"WHERE addresses_1.email_address = :email_address_1"
)
+ def test_single_prop_11(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
self.assert_compile(
- sess.query(User).join(User.orders, Order.items, aliased=True).filter(Item.id==10),
+ sess.query(User).join(User.orders, Order.items, aliased=True).
+ filter(Item.id == 10),
"SELECT users.id AS users_id, users.name AS users_name "
"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 "
@@ -547,6 +629,14 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"WHERE items_1.id = :id_1"
)
+ def test_single_prop_12(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
+ oalias1 = aliased(Order)
# test #1 for [ticket:1706]
ualias = aliased(User)
self.assert_compile(
@@ -559,7 +649,15 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
"= addresses.user_id"
)
+ def test_single_prop_13(self):
+ Item, Order, User, Address = (self.classes.Item,
+ self.classes.Order,
+ self.classes.User,
+ self.classes.Address)
+
+ sess = create_session()
# test #2 for [ticket:1706]
+ ualias = aliased(User)
ualias2 = aliased(User)
self.assert_compile(
sess.query(ualias).
@@ -1837,34 +1935,50 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
use_default_dialect=True
)
- def test_explicit_join(self):
+ def test_explicit_join_1(self):
Node = self.classes.Node
-
- 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",
+ "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",
use_default_dialect=True
)
+ def test_explicit_join_2(self):
+ Node = self.classes.Node
+ n1 = aliased(Node)
+ n2 = aliased(Node)
+
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",
+ "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",
use_default_dialect=True
)
+ def test_explicit_join_3(self):
+ Node = self.classes.Node
+ n1 = aliased(Node)
+ n2 = aliased(Node)
+
# 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",
+ "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",
use_default_dialect=True
)
+ def test_explicit_join_4(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
+ n2 = aliased(Node)
+
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 "
@@ -1873,6 +1987,12 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
use_default_dialect=True
)
+ def test_explicit_join_5(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
+ n2 = aliased(Node)
+
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 "
@@ -1881,25 +2001,59 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
use_default_dialect=True
)
- node = sess.query(Node).select_from(join(Node, n1, 'children')).filter(n1.data=='n122').first()
- assert node.data=='n12'
+ def test_explicit_join_6(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
- node = sess.query(Node).select_from(join(Node, n1, 'children').join(n2, 'children')).\
- filter(n2.data=='n122').first()
- assert node.data=='n1'
+ node = sess.query(Node).select_from(join(Node, n1, 'children')).\
+ filter(n1.data == 'n122').first()
+ assert node.data == 'n12'
+
+ def test_explicit_join_7(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
+ n2 = aliased(Node)
+
+ node = sess.query(Node).select_from(
+ join(Node, n1, 'children').join(n2, 'children')).\
+ filter(n2.data == 'n122').first()
+ assert node.data == 'n1'
+
+ def test_explicit_join_8(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
+ n2 = aliased(Node)
# mix explicit and named onclauses
- node = sess.query(Node).select_from(join(Node, n1, Node.id==n1.parent_id).join(n2, 'children')).\
- filter(n2.data=='n122').first()
- assert node.data=='n1'
+ node = sess.query(Node).select_from(
+ join(Node, n1, Node.id == n1.parent_id).join(n2, 'children')).\
+ filter(n2.data == 'n122').first()
+ assert node.data == 'n1'
+
+ def test_explicit_join_9(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
+ n2 = aliased(Node)
node = sess.query(Node).select_from(join(Node, n1, 'parent').join(n2, 'parent')).\
- filter(and_(Node.data=='n122', n1.data=='n12', n2.data=='n1')).first()
+ filter(and_(Node.data == 'n122', n1.data == 'n12', n2.data == 'n1')).first()
assert node.data == 'n122'
+ def test_explicit_join_10(self):
+ Node = self.classes.Node
+ sess = create_session()
+ n1 = aliased(Node)
+ n2 = aliased(Node)
+
eq_(
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)),
+ filter(and_(Node.data == 'n122',
+ n1.data == 'n12',
+ n2.data == 'n1')).values(Node.data, n1.data, n2.data)),
[('n122', 'n12', 'n1')])
def test_join_to_nonaliased(self):
@@ -1949,8 +2103,8 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
sess.query(Node, parent, grandparent).\
join(parent, Node.parent).\
join(grandparent, parent.parent).\
- filter(Node.data=='n122').filter(parent.data=='n12').\
- filter(grandparent.data=='n1').from_self().first(),
+ filter(Node.data == 'n122').filter(parent.data == 'n12').\
+ filter(grandparent.data == 'n1').from_self().first(),
(Node(data='n122'), Node(data='n12'), Node(data='n1'))
)
@@ -1966,8 +2120,8 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL):
sess.query(parent, grandparent, Node).\
join(parent, Node.parent).\
join(grandparent, parent.parent).\
- filter(Node.data=='n122').filter(parent.data=='n12').\
- filter(grandparent.data=='n1').from_self().first(),
+ filter(Node.data == 'n122').filter(parent.data == 'n12').\
+ filter(grandparent.data == 'n1').from_self().first(),
(Node(data='n12'), Node(data='n1'), Node(data='n122'))
)
diff --git a/test/orm/test_query.py b/test/orm/test_query.py
index ac9c95f41..3882ec4b5 100644
--- a/test/orm/test_query.py
+++ b/test/orm/test_query.py
@@ -1620,6 +1620,21 @@ class AggregateTest(QueryTest):
assert [User(name=u'jack',id=7), User(name=u'fred',id=9)] == sess.query(User).order_by(User.id).group_by(User).join('addresses').having(func.count(Address.id)< 2).all()
+
+class ExistsTest(QueryTest, AssertsCompiledSQL):
+
+ def test_exists(self):
+ User = self.classes.User
+ sess = create_session()
+ q1 = sess.query(User).filter(User.name == 'fred')
+ self.assert_compile(sess.query(q1.exists()),
+ 'SELECT EXISTS ('
+ 'SELECT 1 FROM users WHERE users.name = :name_1'
+ ') AS anon_1',
+ dialect=default.DefaultDialect()
+ )
+
+
class CountTest(QueryTest):
def test_basic(self):
users, User = self.tables.users, self.classes.User