]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
selectinload omit join
authorJayson Reis <santosdosreis@gmail.com>
Mon, 1 Oct 2018 16:58:46 +0000 (12:58 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 10 Oct 2018 17:28:02 +0000 (13:28 -0400)
The "selectin" loader strategy now omits the JOIN in the case of a
simple one-to-many load, where it instead relies upon the foreign key
columns of the related table in order to match up to primary keys in
the parent table.   This optimization can be disabled by setting
the :paramref:`.relationship.omit_join` flag to False.
Many thanks to Jayson Reis for the efforts on this.

As part of this change, horizontal shard no longer relies upon
the _mapper_zero() method to get the query-bound mapper, instead
using the more generalized _bind_mapper() (which will use mapper_zero
if no explicit FROM is present).  A short check for the particular
recursive condition is added to BundleEntity and it no longer assigns
itself as the "namespace" to its ColumnEntity objects which creates
a reference cycle.

Co-authored-by: Mike Bayer <mike_mp@zzzcomputing.com>
Fixes: #4340
Change-Id: I649587e1c07b684ecd63f7d10054cd165891baf4
Pull-request: https://bitbucket.org/zzzeek/sqlalchemy/pull-requests/7

doc/build/changelog/migration_13.rst
doc/build/changelog/unreleased_13/4340.rst [new file with mode: 0644]
doc/build/orm/loading_relationships.rst
doc/build/orm/tutorial.rst
lib/sqlalchemy/ext/horizontal_shard.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/strategies.py
test/ext/test_horizontal_shard.py
test/orm/inheritance/test_poly_loading.py
test/orm/test_selectin_relations.py

index 725c8854401924374e2dcf3b32eb41b4940e270a..d1ebe846c9351c23d3c0b383f585be2ac6ed0125 100644 (file)
@@ -20,6 +20,70 @@ potentially backwards-incompatible changes in behavior.
 New Features and Improvements - ORM
 ===================================
 
+.. _change_4340:
+
+selectin loading no longer uses JOIN for simple one-to-many
+------------------------------------------------------------
+
+The "selectin" loading feature added in 1.2 introduced an extremely
+performant new way to eagerly load collections, in many cases much faster
+than that of "subquery" eager loading, as it does not rely upon restating
+the original SELECT query and instead uses a simple IN clause.  However,
+the "selectin" load still relied upon rendering a JOIN between the
+parent and related tables, since it needs the parent primary key values
+in the row in order to match rows up.     In 1.3, a new optimization
+is added which will omit this JOIN in the most common case of a simple
+one-to-many load, where the related row already contains the primary key
+of the parent row expressed in its foreign key columns.   This again provides
+for a dramatic performance improvement as the ORM now can load large numbers
+of collections all in one query without using JOIN or subqueries at all.
+
+Given a mapping::
+
+    class A(Base):
+        __tablename__ = 'a'
+
+        id = Column(Integer, primary_key=True)
+        bs = relationship("B", lazy="selectin")
+
+
+    class B(Base):
+        __tablename__ = 'b'
+        id = Column(Integer, primary_key=True)
+        a_id = Column(ForeignKey("a.id"))
+
+In the 1.2 version of "selectin" loading, a load of A to B looks like:
+
+.. sourcecode:: sql
+
+    SELECT a.id AS a_id FROM a
+    SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id
+    FROM a AS a_1 JOIN b ON a_1.id = b.a_id
+    WHERE a_1.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ORDER BY a_1.id
+    (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+With the new behavior, the load looks like:
+
+.. sourcecode:: sql
+
+
+    SELECT a.id AS a_id FROM a
+    SELECT b.a_id AS b_a_id, b.id AS b_id FROM b
+    WHERE b.a_id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ORDER BY b.a_id
+    (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+The behavior is being released as automatic, using a similar heuristic that
+lazy loading uses in order to determine if related entities can be fetched
+directly from the identity map.   However, as with most querying features,
+the feature's implementation became more complex as a result of advanced
+scenarios regarding polymorphic loading.   If problems are encountered,
+users should report a bug, however the change also incldues a flag
+:paramref:`.relationship.omit_join` which can be set to False on the
+:func:`.relationship` to disable the optimization.
+
+
+:ticket:`4340`
+
 .. _change_4257:
 
 info dictionary added to InstanceState
diff --git a/doc/build/changelog/unreleased_13/4340.rst b/doc/build/changelog/unreleased_13/4340.rst
new file mode 100644 (file)
index 0000000..3e14d63
--- /dev/null
@@ -0,0 +1,14 @@
+.. change::
+    :tags: feature, orm
+    :tickets: 4340
+
+    The "selectin" loader strategy now omits the JOIN in the case of a simple
+    one-to-many load, where it instead relies loads only from the related
+    table, relying upon the foreign key columns of the related table in order
+    to match up to primary keys in the parent table.   This optimization can be
+    disabled by setting the :paramref:`.relationship.omit_join` flag to False.
+    Many thanks to Jayson Reis for the efforts on this.
+
+    .. seealso::
+
+        :ref:`change_4340`
index 3699133169813e7a34c735b4f19d0000cd42295a..f883897d8644d7360092c7aa3809147e2cbbf8bc 100644 (file)
@@ -692,6 +692,13 @@ eager loading, is compatible with batching of results using
 :meth:`.Query.yield_per`, provided the database driver supports simultaneous
 cursors.
 
+Overall, especially as of the 1.3 series of SQLAlchemy, selectin loading
+is the most simple and efficient way to eagerly load collections of objects
+in most cases.  The only scenario in which selectin eager loading is not feasible
+is when the model is using composite primary keys, and the backend database
+does not support tuples with IN, which includes SQLite, Oracle and
+SQL Server.
+
 .. versionadded:: 1.2
 
 "Select IN" eager loading is provided using the ``"selectin"`` argument
@@ -714,20 +721,49 @@ in order to load related associations:
     WHERE users.name = ? OR users.name = ?
     ('jack', 'ed')
     SELECT
-        users_1.id AS users_1_id,
         addresses.id AS addresses_id,
         addresses.email_address AS addresses_email_address,
         addresses.user_id AS addresses_user_id
-    FROM users AS users_1
-    JOIN addresses ON users_1.id = addresses.user_id
-    WHERE users_1.id IN (?, ?)
-    ORDER BY users_1.id, addresses.id
+    FROM addresses
+    WHERE addresses.user_id IN (?, ?)
+    ORDER BY addresses.user_id, addresses.id
     (5, 7)
 
-Above, the second SELECT refers to ``users_1.id IN (5, 7)``, where the
+Above, the second SELECT refers to ``addresses.user_id IN (5, 7)``, where the
 "5" and "7" are the primary key values for the previous two ``User``
 objects loaded; after a batch of objects are completely loaded, their primary
 key values are injected into the ``IN`` clause for the second SELECT.
+Because the relatonship between ``User`` and ``Address`` provides that the
+primary key values for ``User`` can be derived from ``Address.user_id``, the
+statement has no joins or subqueries at all.
+
+.. versionchanged:: 1.3 selectin loading can omit the JOIN for a simple
+   one-to-many collection.
+
+In the case where the primary key of the parent object isn't present in
+the related row, "selectin" loading will also JOIN to the parent table so that
+the parent primary key values are present:
+
+.. sourcecode:: python+sql
+
+    >>> session.query(Address).\
+    ... options(selectinload('user')).all()
+    {opensql}SELECT
+        addresses.id AS addresses_id,
+        addresses.email_address AS addresses_email_address,
+        addresses.user_id AS addresses_user_id
+        FROM addresses
+    SELECT
+        addresses_1.id AS addresses_1_id,
+        users.id AS users_id,
+        users.name AS users_name,
+        users.fullname AS users_fullname,
+        users.password AS users_password
+    FROM addresses AS addresses_1
+    JOIN users ON users.id = addresses_1.user_id
+    WHERE addresses_1.id IN (?, ?)
+    ORDER BY addresses_1.id
+    (1, 2)
 
 "Select IN" loading is the newest form of eager loading added to SQLAlchemy
 as of the 1.2 series.   Things to know about this kind of loading include:
@@ -746,7 +782,8 @@ as of the 1.2 series.   Things to know about this kind of loading include:
 * "selectin" loading, unlike joined or subquery eager loading, always emits
   its SELECT in terms of the immediate parent objects just loaded, and
   not the original type of object at the top of the chain.  So if eager loading
-  many levels deep, "selectin" loading still uses exactly one JOIN in the statement.
+  many levels deep, "selectin" loading still uses no more than one JOIN,
+  and usually no JOINs, in the statement.   In comparison,
   joined and subquery eager loading always refer to multiple JOINs up to
   the original parent.
 
index 79315cada229ab3214d6e7f0487b7412ce608302..87486b7b3254c21ab2d0b8d6a98e69568f752cc2 100644 (file)
@@ -1643,23 +1643,22 @@ which involves custom criterion.   All three are usually invoked via functions k
 as :term:`query options` which give additional instructions to the :class:`.Query` on how
 we would like various attributes to be loaded, via the :meth:`.Query.options` method.
 
-Subquery Load
+Selectin Load
 -------------
 
 In this case we'd like to indicate that ``User.addresses`` should load eagerly.
 A good choice for loading a set of objects as well as their related collections
-is the :func:`.orm.subqueryload` option, which emits a second SELECT statement
+is the :func:`.orm.selectinload` option, which emits a second SELECT statement
 that fully loads the collections associated with the results just loaded.
-The name "subquery" originates from the fact that the SELECT statement
-constructed directly via the :class:`.Query` is re-used, embedded as a subquery
-into a SELECT against the related table.   This is a little elaborate but
-very easy to use:
+The name "selectin" originates from the fact that the SELECT statement
+uses an IN clause in order to locate related rows for multiple objects
+at once:
 
 .. sourcecode:: python+sql
 
-    >>> from sqlalchemy.orm import subqueryload
+    >>> from sqlalchemy.orm import selectinload
     {sql}>>> jack = session.query(User).\
-    ...                 options(subqueryload(User.addresses)).\
+    ...                 options(selectinload(User.addresses)).\
     ...                 filter_by(name='jack').one()
     SELECT users.id AS users_id,
             users.name AS users_name,
@@ -1668,27 +1667,19 @@ very easy to use:
     FROM users
     WHERE users.name = ?
     ('jack',)
-    SELECT addresses.id AS addresses_id,
-            addresses.email_address AS addresses_email_address,
-            addresses.user_id AS addresses_user_id,
-            anon_1.users_id AS anon_1_users_id
-    FROM (SELECT users.id AS users_id
-        FROM users WHERE users.name = ?) AS anon_1
-    JOIN addresses ON anon_1.users_id = addresses.user_id
-    ORDER BY anon_1.users_id, addresses.id
-    ('jack',)
+    SELECT addresses.user_id AS addresses_user_id,
+            addresses.id AS addresses_id,
+            addresses.email_address AS addresses_email_address
+    FROM addresses
+    WHERE addresses.user_id IN (?)
+    ORDER BY addresses.user_id, addresses.id
+    (5,)
     {stop}>>> jack
     <User(name='jack', fullname='Jack Bean', password='gjffdd')>
 
     >>> jack.addresses
     [<Address(email_address='jack@google.com')>, <Address(email_address='j25@yahoo.com')>]
 
-.. note::
-
-   :func:`.subqueryload` when used in conjunction with limiting such as
-   :meth:`.Query.first`, :meth:`.Query.limit` or :meth:`.Query.offset`
-   should also include :meth:`.Query.order_by` on a unique column in order to
-   ensure correct results.  See :ref:`subqueryload_ordering`.
 
 Joined Load
 -----------
@@ -1731,11 +1722,14 @@ one instance of ``User`` back.  This is because :class:`.Query` applies a "uniqu
 strategy, based on object identity, to the returned entities.  This is specifically
 so that joined eager loading can be applied without affecting the query results.
 
-While :func:`.joinedload` has been around for a long time, :func:`.subqueryload`
-is a newer form of eager loading.   :func:`.subqueryload` tends to be more appropriate
+While :func:`.joinedload` has been around for a long time, :func:`.selectinload`
+is a newer form of eager loading.   :func:`.selectinload` tends to be more appropriate
 for loading related collections while :func:`.joinedload` tends to be better suited
 for many-to-one relationships, due to the fact that only one row is loaded
-for both the lead and the related object.
+for both the lead and the related object.   Another form of loading,
+:func:`.subqueryload`, also exists, which can be used in place of
+:func:`.selectinload` when making use of composite primary keys on certain
+backends.
 
 .. topic:: ``joinedload()`` is not a replacement for ``join()``
 
index 425d289637ad66adaf8238bb7a9ab184fa19e216..f86e4fc93c5c1bc396522b2665ebec117297cd48 100644 (file)
@@ -45,7 +45,7 @@ class ShardedQuery(Query):
         def iter_for_shard(shard_id):
             context.attributes['shard_id'] = context.identity_token = shard_id
             result = self._connection_from_session(
-                mapper=self._mapper_zero(),
+                mapper=self._bind_mapper(),
                 shard_id=shard_id).execute(
                 context.statement,
                 self._params)
index e96996a3951f662f6107c53ed013b352cad774b2..bfddb5cfec3392614953bc7d77084e3fe1f0dc0e 100644 (file)
@@ -4020,13 +4020,17 @@ class _BundleEntity(_QueryEntity):
                 if isinstance(expr, Bundle):
                     _BundleEntity(self, expr)
                 else:
-                    _ColumnEntity(self, expr, namespace=self)
+                    _ColumnEntity(self, expr)
 
         self.supports_single_entity = self.bundle.single_entity
 
     @property
     def mapper(self):
-        return self.entity_zero.mapper
+        ezero = self.entity_zero
+        if ezero is not None:
+            return ezero.mapper
+        else:
+            return None
 
     @property
     def entities(self):
index 27a75d45c42c963181d6fa232227baa67f3f3ae8..818f1c0ae2fd66a0507183dedb75a5e0b9f59ba5 100644 (file)
@@ -116,7 +116,8 @@ class RelationshipProperty(StrategizedProperty):
                  bake_queries=True,
                  _local_remote_pairs=None,
                  query_class=None,
-                 info=None):
+                 info=None,
+                 omit_join=None):
         """Provide a relationship between two mapped classes.
 
         This corresponds to a parent-child or associative table relationship.
@@ -816,6 +817,13 @@ class RelationshipProperty(StrategizedProperty):
           the full set of related objects, to prevent modifications of the
           collection from resulting in persistence operations.
 
+        :param omit_join:
+          Allows manual control over the "selectin" automatic join
+          optimization.  Set to ``False`` to disable the "omit join" feature
+          added in SQLAlchemy 1.3.
+
+          .. versionadded:: 1.3
+
 
         """
         super(RelationshipProperty, self).__init__()
@@ -843,6 +851,7 @@ class RelationshipProperty(StrategizedProperty):
         self.doc = doc
         self.active_history = active_history
         self.join_depth = join_depth
+        self.omit_join = omit_join
         self.local_remote_pairs = _local_remote_pairs
         self.extension = extension
         self.bake_queries = bake_queries
index d7597d3b2aeb3625281352a30351d7f0cd6a2379..b9abf0647b54e43ce7a416117cf6a24232234043 100644 (file)
@@ -1837,8 +1837,8 @@ class JoinedLoader(AbstractRelationshipLoader):
 @properties.RelationshipProperty.strategy_for(lazy="selectin")
 class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
     __slots__ = (
-        'join_depth', '_parent_alias', '_in_expr', '_parent_pk_cols',
-        '_zero_idx', '_bakery'
+        'join_depth', 'omit_join', '_parent_alias', '_in_expr',
+        '_pk_cols', '_zero_idx', '_bakery'
     )
 
     _chunksize = 500
@@ -1846,9 +1846,46 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
     def __init__(self, parent, strategy_key):
         super(SelectInLoader, self).__init__(parent, strategy_key)
         self.join_depth = self.parent_property.join_depth
+
+        if self.parent_property.omit_join is not None:
+            self.omit_join = self.parent_property.omit_join
+        else:
+            lazyloader = self.parent_property._get_strategy(
+                (("lazy", "select"),))
+            self.omit_join = self.parent._get_clause[0].compare(
+                lazyloader._rev_lazywhere,
+                use_proxies=True,
+                equivalents=self.parent._equivalent_columns
+            )
+        if self.omit_join:
+            self._init_for_omit_join()
+        else:
+            self._init_for_join()
+
+    def _init_for_omit_join(self):
+        pk_to_fk = dict(
+            self.parent_property._join_condition.local_remote_pairs
+        )
+        pk_to_fk.update(
+            (equiv, pk_to_fk[k])
+            for k in list(pk_to_fk)
+            for equiv in self.parent._equivalent_columns.get(k, ())
+        )
+
+        self._pk_cols = fk_cols = [
+            pk_to_fk[col]
+            for col in self.parent.primary_key if col in pk_to_fk]
+        if len(fk_cols) > 1:
+            self._in_expr = sql.tuple_(*fk_cols)
+            self._zero_idx = False
+        else:
+            self._in_expr = fk_cols[0]
+            self._zero_idx = True
+
+    def _init_for_join(self):
         self._parent_alias = aliased(self.parent.class_)
         pa_insp = inspect(self._parent_alias)
-        self._parent_pk_cols = pk_cols = [
+        self._pk_cols = pk_cols = [
             pa_insp._adapt_element(col) for col in self.parent.primary_key]
         if len(pk_cols) > 1:
             self._in_expr = sql.tuple_(*pk_cols)
@@ -1922,8 +1959,24 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             for state, overwrite in states
         ]
 
-        pk_cols = self._parent_pk_cols
-        pa = self._parent_alias
+        pk_cols = self._pk_cols
+        in_expr = self._in_expr
+
+        if self.omit_join:
+            # in "omit join" mode, the primary key column and the
+            # "in" expression are in terms of the related entity.  So
+            # if the related entity is polymorphic or otherwise aliased,
+            # we need to adapt our "_pk_cols" and "_in_expr" to that
+            # entity.   in non-"omit join" mode, these are against the
+            # parent entity and do not need adaption.
+            insp = inspect(effective_entity)
+            if insp.is_aliased_class:
+                pk_cols = [
+                    insp._adapt_element(col)
+                    for col in pk_cols
+                ]
+                in_expr = insp._adapt_element(in_expr)
+                pk_cols = [insp._adapt_element(col) for col in pk_cols]
 
         q = self._bakery(
             lambda session: session.query(
@@ -1931,15 +1984,30 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             ), self
         )
 
+        if self.omit_join:
+            # the Bundle we have in the "omit_join" case is against raw, non
+            # annotated columns, so to ensure the Query knows its primary
+            # entity, we add it explictly.  If we made the Bundle against
+            # annotated columns, we hit a performance issue in this specific
+            # case, which is detailed in issue #4347.
+            q.add_criteria(lambda q: q.select_from(effective_entity))
+        else:
+            # in the non-omit_join case, the Bundle is against the annotated/
+            # mapped column of the parent entity, but the #4347 issue does not
+            # occur in this case.
+            pa = self._parent_alias
+            q.add_criteria(
+                lambda q: q.select_from(pa).join(
+                    getattr(pa, self.parent_property.key).of_type(
+                        effective_entity)
+                )
+            )
+
         q.add_criteria(
-            lambda q: q.select_from(pa).join(
-                getattr(pa,
-                        self.parent_property.key).of_type(effective_entity)).
-            filter(
-                self._in_expr.in_(
-                    sql.bindparam('primary_keys', expanding=True))
-            ).order_by(*pk_cols)
-        )
+            lambda q: q.filter(
+                in_expr.in_(
+                    sql.bindparam("primary_keys", expanding=True))
+            ).order_by(*pk_cols))
 
         orig_query = context.query
 
@@ -1954,23 +2022,30 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             )
 
         if self.parent_property.order_by:
-            def _setup_outermost_orderby(q):
-                # imitate the same method that
-                # subquery eager loading does it, looking for the
-                # adapted "secondary" table
-                eagerjoin = q._from_obj[0]
-                eager_order_by = \
-                    eagerjoin._target_adapter.\
-                    copy_and_process(
-                        util.to_list(
-                            self.parent_property.order_by
+            if self.omit_join:
+                eager_order_by = self.parent_property.order_by
+                if insp.is_aliased_class:
+                    eager_order_by = [
+                        insp._adapt_element(elem) for elem in
+                        eager_order_by
+                    ]
+                q.add_criteria(
+                    lambda q: q.order_by(*eager_order_by)
+                )
+            else:
+                def _setup_outermost_orderby(q):
+                    # imitate the same method that subquery eager loading uses,
+                    # looking for the adapted "secondary" table
+                    eagerjoin = q._from_obj[0]
+                    eager_order_by = \
+                        eagerjoin._target_adapter.\
+                        copy_and_process(
+                            util.to_list(self.parent_property.order_by)
                         )
-                    )
-                return q.order_by(*eager_order_by)
-
-            q.add_criteria(
-                _setup_outermost_orderby
-            )
+                    return q.order_by(*eager_order_by)
+                q.add_criteria(
+                    _setup_outermost_orderby
+                )
 
         uselist = self.uselist
         _empty_result = () if uselist else None
index bd41594757dd130f7d89fe9b3d4e1460e8bab5f5..164162a5df6056574f0f302b2dec373df843f06f 100644 (file)
@@ -426,6 +426,8 @@ class AttachedFileShardTest(ShardTest, fixtures.TestBase):
 
 
 class SelectinloadRegressionTest(fixtures.DeclarativeMappedTest):
+    """test #4175
+    """
     @classmethod
     def setup_classes(cls):
         Base = cls.DeclarativeBasic
index b724e8dba607a385d44ce20622c4dd21baaa6bfb..48117593be410d08a06f5741fffbd2727948853c 100644 (file)
@@ -91,21 +91,22 @@ class BaseAndSubFixture(object):
                         {"primary_keys": [2]}
                     ),
                     CompiledSQL(
-                        "SELECT anon_1.a_id AS anon_1_a_id, c.id AS c_id, "
-                        "c.a_sub_id AS c_a_sub_id FROM (SELECT a.id AS a_id, a.adata "
-                        "AS a_adata, a.type AS a_type, asub.id AS asub_id, "
-                        "asub.asubdata AS asub_asubdata FROM a JOIN asub "
-                        "ON a.id = asub.id) AS anon_1 JOIN c "
-                        "ON anon_1.asub_id = c.a_sub_id "
-                        "WHERE anon_1.a_id IN ([EXPANDING_primary_keys]) "
-                        "ORDER BY anon_1.a_id",
+                        # note this links c.a_sub_id to a.id, even though
+                        # primaryjoin is to asub.id.  this is because the
+                        # cols a.id / asub.id are listed in the mapper's
+                        # equivalent_columns so they are guaranteed to store
+                        # the same value.
+                        "SELECT c.a_sub_id AS c_a_sub_id, "
+                        "c.id AS c_id "
+                        "FROM c WHERE c.a_sub_id "
+                        "IN ([EXPANDING_primary_keys]) ORDER BY c.a_sub_id",
                         {"primary_keys": [2]}
                     ),
                 ),
                 CompiledSQL(
-                    "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
-                    "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
-                    "WHERE a_1.id IN ([EXPANDING_primary_keys]) ORDER BY a_1.id",
+                    "SELECT b.a_id AS b_a_id, b.id AS b_id FROM b "
+                    "WHERE b.a_id IN ([EXPANDING_primary_keys]) "
+                    "ORDER BY b.a_id",
                     {"primary_keys": [1, 2]}
                 )
             )
@@ -216,14 +217,12 @@ class FixtureLoadTest(_Polymorphic, testing.AssertsExecutionResults):
                 {}
             ),
             CompiledSQL(
-                "SELECT companies_1.company_id AS companies_1_company_id, "
+                "SELECT people.company_id AS people_company_id, "
                 "people.person_id AS people_person_id, "
-                "people.company_id AS people_company_id, "
                 "people.name AS people_name, people.type AS people_type "
-                "FROM companies AS companies_1 JOIN people "
-                "ON companies_1.company_id = people.company_id "
-                "WHERE companies_1.company_id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY companies_1.company_id, people.person_id",
+                "FROM people WHERE people.company_id "
+                "IN ([EXPANDING_primary_keys]) "
+                "ORDER BY people.company_id, people.person_id",
                 {"primary_keys": [1, 2]}
             ),
             AllOf(
index 57bca5a74ce843a6b10fdf24f7a6e19a33626b46..7891f71f0e428e460ffaf717370769f9226ceb90 100644 (file)
@@ -1135,14 +1135,12 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
                 {"primary_language_1": "java"}
             ),
             CompiledSQL(
-                "SELECT people_1.person_id AS people_1_person_id, "
+                "SELECT paperwork.person_id AS paperwork_person_id, "
                 "paperwork.paperwork_id AS paperwork_paperwork_id, "
-                "paperwork.description AS paperwork_description, "
-                "paperwork.person_id AS paperwork_person_id "
-                "FROM people AS people_1 JOIN paperwork "
-                "ON people_1.person_id = paperwork.person_id "
-                "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY people_1.person_id, paperwork.paperwork_id",
+                "paperwork.description AS paperwork_description "
+                "FROM paperwork WHERE paperwork.person_id "
+                "IN ([EXPANDING_primary_keys]) "
+                "ORDER BY paperwork.person_id, paperwork.paperwork_id",
                 [{'primary_keys': [1]}]
             )
         )
@@ -1180,15 +1178,12 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
                     "description_1": "tps report #2"}
             ),
             CompiledSQL(
-
-                "SELECT people_1.person_id AS people_1_person_id, "
+                "SELECT paperwork.person_id AS paperwork_person_id, "
                 "paperwork.paperwork_id AS paperwork_paperwork_id, "
-                "paperwork.description AS paperwork_description, "
-                "paperwork.person_id AS paperwork_person_id "
-                "FROM people AS people_1 JOIN paperwork "
-                "ON people_1.person_id = paperwork.person_id "
-                "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY people_1.person_id, paperwork.paperwork_id",
+                "paperwork.description AS paperwork_description "
+                "FROM paperwork WHERE paperwork.person_id "
+                "IN ([EXPANDING_primary_keys]) "
+                "ORDER BY paperwork.person_id, paperwork.paperwork_id",
                 [{'primary_keys': [1]}]
             )
         )
@@ -1224,14 +1219,12 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
                 "engineers.engineer_id ORDER BY engineers.primary_language "
                 "DESC LIMIT :param_1"),
             CompiledSQL(
-                "SELECT people_1.person_id AS people_1_person_id, "
+                "SELECT paperwork.person_id AS paperwork_person_id, "
                 "paperwork.paperwork_id AS paperwork_paperwork_id, "
-                "paperwork.description AS paperwork_description, "
-                "paperwork.person_id AS paperwork_person_id "
-                "FROM people AS people_1 "
-                "JOIN paperwork ON people_1.person_id = paperwork.person_id "
-                "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY people_1.person_id, paperwork.paperwork_id",
+                "paperwork.description AS paperwork_description "
+                "FROM paperwork WHERE paperwork.person_id "
+                "IN ([EXPANDING_primary_keys]) "
+                "ORDER BY paperwork.person_id, paperwork.paperwork_id",
                 [{'primary_keys': [1]}]
             )
         )
@@ -1275,14 +1268,12 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
                 "ORDER BY anon_1.engineers_primary_language DESC "
                 "LIMIT :param_1"),
             CompiledSQL(
-                "SELECT people_1.person_id AS people_1_person_id, "
+                "SELECT paperwork.person_id AS paperwork_person_id, "
                 "paperwork.paperwork_id AS paperwork_paperwork_id, "
-                "paperwork.description AS paperwork_description, "
-                "paperwork.person_id AS paperwork_person_id "
-                "FROM people AS people_1 JOIN paperwork "
-                "ON people_1.person_id = paperwork.person_id "
-                "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY people_1.person_id, paperwork.paperwork_id",
+                "paperwork.description AS paperwork_description "
+                "FROM paperwork WHERE paperwork.person_id "
+                "IN ([EXPANDING_primary_keys]) "
+                "ORDER BY paperwork.person_id, paperwork.paperwork_id",
                 [{'primary_keys': [1]}]
             )
         )
@@ -1320,14 +1311,12 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
                 "ON people_1.person_id = engineers_1.engineer_id "
                 "ORDER BY engineers_1.primary_language DESC LIMIT :param_1"),
             CompiledSQL(
-                "SELECT people_1.person_id AS people_1_person_id, "
+                "SELECT paperwork.person_id AS paperwork_person_id, "
                 "paperwork.paperwork_id AS paperwork_paperwork_id, "
-                "paperwork.description AS paperwork_description, "
-                "paperwork.person_id AS paperwork_person_id "
-                "FROM people AS people_1 JOIN paperwork "
-                "ON people_1.person_id = paperwork.person_id "
-                "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY people_1.person_id, paperwork.paperwork_id",
+                "paperwork.description AS paperwork_description "
+                "FROM paperwork WHERE paperwork.person_id "
+                "IN ([EXPANDING_primary_keys]) "
+                "ORDER BY paperwork.person_id, paperwork.paperwork_id",
                 [{'primary_keys': [1]}]
 
             )
@@ -1454,6 +1443,8 @@ class HeterogeneousSubtypesTest(fixtures.DeclarativeMappedTest):
             selectinload(Manager.golf_swing),
         ).one()
 
+        # NOTE: we *MUST* do a SQL compare on this one because the adaption
+        # is very sensitive
         def go():
             eq_(company.employees[0].golf_swing.name, "clubs")
 
@@ -1532,24 +1523,21 @@ class ChunkingTest(fixtures.DeclarativeMappedTest):
                 {}
             ),
             CompiledSQL(
-                "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
-                "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
-                "WHERE a_1.id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY a_1.id, b.id",
+                "SELECT b.a_id AS b_a_id, b.id AS b_id "
+                "FROM b WHERE b.a_id IN "
+                "([EXPANDING_primary_keys]) ORDER BY b.a_id, b.id",
                 {"primary_keys": list(range(1, 48))}
             ),
             CompiledSQL(
-                "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
-                "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
-                "WHERE a_1.id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY a_1.id, b.id",
+                "SELECT b.a_id AS b_a_id, b.id AS b_id "
+                "FROM b WHERE b.a_id IN "
+                "([EXPANDING_primary_keys]) ORDER BY b.a_id, b.id",
                 {"primary_keys": list(range(48, 95))}
             ),
             CompiledSQL(
-                "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
-                "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
-                "WHERE a_1.id IN ([EXPANDING_primary_keys]) "
-                "ORDER BY a_1.id, b.id",
+                "SELECT b.a_id AS b_a_id, b.id AS b_id "
+                "FROM b WHERE b.a_id IN "
+                "([EXPANDING_primary_keys]) ORDER BY b.a_id, b.id",
                 {"primary_keys": list(range(95, 101))}
             )
         )
@@ -2177,11 +2165,9 @@ class SingleInhSubclassTest(
                 {'type_1': 'employer'}
             ),
             CompiledSQL(
-                'SELECT user_1.id AS user_1_id, role.id AS role_id, '
-                'role.user_id AS role_user_id FROM "user" AS user_1 '
-                'JOIN role ON user_1.id = role.user_id WHERE user_1.id IN '
-                '([EXPANDING_primary_keys]) '
-                'AND user_1.type IN (:type_1) ORDER BY user_1.id',
-                {'primary_keys': [1], 'type_1': 'employer'}
+                "SELECT role.user_id AS role_user_id, role.id AS role_id "
+                "FROM role WHERE role.user_id "
+                "IN ([EXPANDING_primary_keys]) ORDER BY role.user_id",
+                {'primary_keys': [1]}
             ),
-        )
\ No newline at end of file
+        )