]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Implement relationship to AliasedClass; deprecate non primary mappers
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 30 Dec 2018 01:54:29 +0000 (20:54 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Jan 2019 14:28:28 +0000 (09:28 -0500)
Implemented a new feature whereby the :class:`.AliasedClass` construct can
now be used as the target of a :func:`.relationship`.  This allows the
concept of "non primary mappers" to no longer be necessary, as the
:class:`.AliasedClass` is much easier to configure and automatically inherits
all the relationships of the mapped class, as well as preserves the
ability for loader options to work normally.

- introduce new name for mapped_table, "persist_selectable".  this is
the selectable that selects against the local mapper and its superclasses,
but does not include columns local only to subclasses.

- relationship gains "entity" which is the mapper or aliasedinsp.

- clarfiy name "entity" vs. "query_entity" in loader strategies.

Fixes: #4423
Fixes: #4422
Fixes: #4421
Fixes: #3348
Change-Id: Ic3609b43dc4ed115006da9ad9189e574dc0c72d9

30 files changed:
doc/build/changelog/migration_13.rst
doc/build/changelog/unreleased_13/4423.rst [new file with mode: 0644]
doc/build/orm/join_conditions.rst
lib/sqlalchemy/ext/declarative/base.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/strategy_options.py
lib/sqlalchemy/orm/util.py
test/aaa_profiling/test_memusage.py
test/ext/test_deprecations.py [new file with mode: 0644]
test/ext/test_mutable.py
test/orm/test_ac_relationships.py [new file with mode: 0644]
test/orm/test_deprecations.py
test/orm/test_eager_relations.py
test/orm/test_froms.py
test/orm/test_inspect.py
test/orm/test_joins.py
test/orm/test_lazy_relations.py
test/orm/test_mapper.py
test/orm/test_rel_fn.py
test/orm/test_selectable.py
test/orm/test_selectin_relations.py
test/orm/test_subquery_relations.py
test/orm/test_unitofwork.py
test/requirements.py

index 94e7c857a762f4dfedafec4ca18b617f0bd7b0be..bf752527234ef09dc24074616300ce47f7896bbe 100644 (file)
@@ -72,13 +72,98 @@ below.
 
     :ref:`change_4393_convertunicode`
 
-    :ref:`FIXME` - FIXME - link to non-primary mapper deprecation
+    :ref:`change_4423`
 
 :ticket:`4393`
 
 New Features and Improvements - ORM
 ===================================
 
+.. _change_4423:
+
+Relationship to AliasedClass replaces the need for non primary mappers
+-----------------------------------------------------------------------
+
+The "non primary mapper" is a :func:`.mapper` created in the
+:ref:`classical_mapping` style, which acts as an additional mapper against an
+already mapped class against a different kind of selectable.  The non primary
+mapper has its roots in the 0.1, 0.2 series of SQLAlchemy where it was
+anticipated that the :func:`.mapper` object was to be the primary query
+construction interface, before the :class:`.Query` object existed.
+
+With the advent of :class:`.Query` and later the :class:`.AliasedClass`
+construct, most use cases for the non primary mapper went away.  This was a
+good thing since SQLAlchemy also moved away from "classical" mappings altogether
+around the 0.5 series in favor of the declarative system.
+
+One use case remained around for non primary mappers when it was realized that
+some very hard-to-define :func:`.relationship` configurations could be made
+possible when a non-primary mapper with an alternative selectable was made as
+the mapping target, rather than trying to construct a
+:paramref:`.relationship.primaryjoin` that encompassed all the complexity of a
+particular inter-object relationship.
+
+As this use case became more popular, its limitations became apparent,
+including that the non primary mapper is difficult to configure against a
+selectable that adds new columns, that the mapper does not inherit the
+relationships of the original mapping, that relationships which are configured
+explicitly on the non primary mapper do  not function well with loader options,
+and that the non primary mapper also doesn't provide a fully functional
+namespace of column-based attributes which can be used in queries (which again,
+in the old 0.1 - 0.4 days, one would use :class:`.Table` objects directly with
+the ORM).
+
+The missing piece was to allow the :func:`.relationship` to refer directly
+to the :class:`.AliasedClass`.  The :class:`.AliasedClass` already does
+everything we want the non primary mapper to do; it allows an existing mapped
+class to be loaded from an alternative selectable, it inherits all the
+attributes and relationships of the existing mapper, it works
+extremely well with loader options, and it provides a class-like
+object that can be mixed into queries just like the class itself.
+With this change, the recipes that
+were formerly for non primary mappers at :ref:`relationship_configure_joins`
+are changed to aliased class.
+
+At :ref:`relationship_aliased_class`, the original non primary mapper looked
+like::
+
+    j = join(B, D, D.b_id == B.id).join(C, C.id == D.c_id)
+
+    B_viacd = mapper(
+        B, j, non_primary=True, primary_key=[j.c.b_id],
+        properties={
+            "id": j.c.b_id,  # so that 'id' looks the same as before
+            "c_id": j.c.c_id,   # needed for disambiguation
+            "d_c_id": j.c.d_c_id,  # needed for disambiguation
+            "b_id": [j.c.b_id, j.c.d_b_id],
+            "d_id": j.c.d_id,
+        }
+    )
+
+    A.b = relationship(B_viacd, primaryjoin=A.b_id == B_viacd.c.b_id)
+
+The properties were necessary in order to re-map the additional columns
+so that they did not conflict with the existing columns mapped to ``B``, as
+well as it was necessary to define a new primary key.
+
+With the new approach, all of this verbosity goes away, and the additional
+columns are referred towards directly when making the relationship::
+
+    j = join(B, D, D.b_id == B.id).join(C, C.id == D.c_id)
+
+    B_viacd = aliased(B, j, flat=True)
+
+    A.b = relationship(B_viacd, primaryjoin=A.b_id == j.c.b_id)
+
+The non primary mapper is now deprecated with the eventual goal to be that
+classical mappings as a feature go away entirely.  The Declarative API would
+become the single means of mapping which hopefully will allow internal
+improvements and simplifications, as well as a clearer documentation story.
+
+
+:ticket:`4423`
+
+
 .. _change_4340:
 
 selectin loading no longer uses JOIN for simple one-to-many
diff --git a/doc/build/changelog/unreleased_13/4423.rst b/doc/build/changelog/unreleased_13/4423.rst
new file mode 100644 (file)
index 0000000..7a03fb7
--- /dev/null
@@ -0,0 +1,14 @@
+.. change::
+   :tags: feature, orm
+   :tickets: 4423
+
+   Implemented a new feature whereby the :class:`.AliasedClass` construct can
+   now be used as the target of a :func:`.relationship`.  This allows the
+   concept of "non primary mappers" to no longer be necessary, as the
+   :class:`.AliasedClass` is much easier to configure and automatically inherits
+   all the relationships of the mapped class, as well as preserves the
+   ability for loader options to work normally.
+
+   .. seealso::
+
+        :ref:`change_4423`
\ No newline at end of file
index f54d2c04953fa1ede8f14ef9e756d524bbaefa62..62da15aa88511d01730b8424a4a7da0df02551e0 100644 (file)
@@ -634,11 +634,19 @@ complexity is kept within the middle.
    as well as support within declarative to specify complex conditions such
    as joins involving class names as targets.
 
-.. _relationship_non_primary_mapper:
+.. _relationship_aliased_class:
 
-Relationship to Non Primary Mapper
+Relationship to Aliased Class
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 1.3
+    The :class:`.AliasedClass` construct can now be specified as the
+    target of a :func:`.relationship`, replacing the previous approach
+    of using non-primary mappers, which had limitations such that they did
+    not inherit sub-relationships of the mapped entity as well as that they
+    required complex configuration against an alternate selectable.  The
+    recipes in this section are now updated to use :class:`.AliasedClass`.
+
 In the previous section, we illustrated a technique where we used
 :paramref:`~.relationship.secondary` in order to place additional
 tables within a join condition.   There is one complex join case where
@@ -648,15 +656,17 @@ however there are also join conditions between ``A`` and ``B``
 *directly*.  In this case, the join from ``A`` to ``B`` may be
 difficult to express with just a complex
 :paramref:`~.relationship.primaryjoin` condition, as the intermediary
-tables may need special handling, and it is also not expressable with
+tables may need special handling, and it is also not expressible with
 a :paramref:`~.relationship.secondary` object, since the
 ``A->secondary->B`` pattern does not support any references between
 ``A`` and ``B`` directly.  When this **extremely advanced** case
 arises, we can resort to creating a second mapping as a target for the
-relationship.  This is where we use :func:`.mapper` in order to make a
+relationship.  This is where we use :class:`.AliasedClass` in order to make a
 mapping to a class that includes all the additional tables we need for
 this join. In order to produce this mapper as an "alternative" mapping
-for our class, we use the :paramref:`~.mapper.non_primary` flag.
+for our class, we use the :func:`.aliased` function to produce the new
+construct, then use :func:`.relationship` against the object as though it
+were a plain mapped class.
 
 Below illustrates a :func:`.relationship` with a simple join from ``A`` to
 ``B``, however the primaryjoin condition is augmented with two additional
@@ -691,26 +701,12 @@ the rows in both ``A`` and ``B`` simultaneously::
     # to it in the mapping multiple times.
     j = join(B, D, D.b_id == B.id).join(C, C.id == D.c_id)
 
-    # 2. Create a new mapper() to B, with non_primary=True.
-    # Columns in the join with the same name must be
-    # disambiguated within the mapping, using named properties.
-    # we also have to make sure the primary key of the regular "B"
-    # mapping is maintained.
-    B_viacd = mapper(
-        B, j, non_primary=True, primary_key=[j.c.b_id],
-        properties={
-            "id": j.c.b_id,  # so that 'id' looks the same as before
-            "c_id": j.c.c_id,   # needed for disambiguation
-            "d_c_id": j.c.d_c_id,  # needed for disambiguation
-            "b_id": [j.c.b_id, j.c.d_b_id],
-            "d_id": j.c.d_id,
-        }
-    )
+    # 2. Create an AliasedClass to B
+    B_viacd = aliased(B, j, flat=True)
 
-    A.b = relationship(B_viacd, primaryjoin=A.b_id == B_viacd.c.b_id)
+    A.b = relationship(B_viacd, primaryjoin=A.b_id == j.c.b_id)
 
-In the above case, our non-primary mapper for ``B`` will emit for
-additional columns when we query; these can be ignored:
+With the above mapping, a simple join looks like:
 
 .. sourcecode:: python+sql
 
@@ -719,10 +715,13 @@ additional columns when we query; these can be ignored:
     {opensql}SELECT a.id AS a_id, a.b_id AS a_b_id
     FROM a JOIN (b JOIN d ON d.b_id = b.id JOIN c ON c.id = d.c_id) ON a.b_id = b.id
 
+.. _relationship_to_window_function:
+
 Row-Limited Relationships with Window Functions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Another interesting use case for non-primary mappers are situations where
+Another interesting use case for relationships to :class:`.AliasedClass`
+objects are situations where
 the relationship needs to join to a specialized SELECT of any form.   One
 scenario is when the use of a window function is desired, such as to limit
 how many rows should be returned for a relationship.  The example below
@@ -747,11 +746,11 @@ ten items for each collection::
         ).label('index')
     ]).alias()
 
-    partitioned_b = mapper(B, partition, non_primary=True)
+    partitioned_b = aliased(B, partition)
 
     A.partitioned_bs = relationship(
         partitioned_b,
-        primaryjoin=and_(partitioned_b.c.a_id == A.id, partitioned_b.c.index < 10)
+        primaryjoin=and_(partitioned_b.a_id == A.id, partition.c.index < 10)
     )
 
 We can use the above ``partitioned_bs`` relationship with most of the loader
index 269cfce008f18e3be55039f270593d341b20636b..80cd23bc8d929f0a411dc9e128b6ffa62985b67e 100644 (file)
@@ -163,7 +163,7 @@ class _MapperConfig(object):
         # dict_ will be a dictproxy, which we can't write to, and we need to!
         self.dict_ = dict(dict_)
         self.classname = classname
-        self.mapped_table = None
+        self.persist_selectable = None
         self.properties = util.OrderedDict()
         self.declared_columns = set()
         self.column_copies = {}
@@ -584,7 +584,7 @@ class _MapperConfig(object):
         elif self.inherits:
             inherited_mapper = _declared_mapping_info(self.inherits)
             inherited_table = inherited_mapper.local_table
-            inherited_mapped_table = inherited_mapper.mapped_table
+            inherited_persist_selectable = inherited_mapper.persist_selectable
 
             if table is None:
                 # single table inheritance.
@@ -611,10 +611,10 @@ class _MapperConfig(object):
                         )
                     inherited_table.append_column(c)
                     if (
-                        inherited_mapped_table is not None
-                        and inherited_mapped_table is not inherited_table
+                        inherited_persist_selectable is not None
+                        and inherited_persist_selectable is not inherited_table
                     ):
-                        inherited_mapped_table._refresh_for_new_column(c)
+                        inherited_persist_selectable._refresh_for_new_column(c)
 
     def _prepare_mapper_arguments(self):
         properties = self.properties
index c1e5866b5fcbcea66a341e9c1112c25df2f0d75e..a0ddf3a8b65e039bf153cdd68b7aeb14c0df771c 100644 (file)
@@ -673,16 +673,21 @@ class SynonymProperty(DescriptorProperty):
     def set_parent(self, parent, init):
         if self.map_column:
             # implement the 'map_column' option.
-            if self.key not in parent.mapped_table.c:
+            if self.key not in parent.persist_selectable.c:
                 raise sa_exc.ArgumentError(
                     "Can't compile synonym '%s': no column on table "
                     "'%s' named '%s'"
-                    % (self.name, parent.mapped_table.description, self.key)
+                    % (
+                        self.name,
+                        parent.persist_selectable.description,
+                        self.key,
+                    )
                 )
             elif (
-                parent.mapped_table.c[self.key] in parent._columntoproperty
+                parent.persist_selectable.c[self.key]
+                in parent._columntoproperty
                 and parent._columntoproperty[
-                    parent.mapped_table.c[self.key]
+                    parent.persist_selectable.c[self.key]
                 ].key
                 == self.name
             ):
@@ -692,7 +697,9 @@ class SynonymProperty(DescriptorProperty):
                     "%r for column %r"
                     % (self.key, self.name, self.name, self.key)
                 )
-            p = properties.ColumnProperty(parent.mapped_table.c[self.key])
+            p = properties.ColumnProperty(
+                parent.persist_selectable.c[self.key]
+            )
             parent._configure_property(self.name, p, init=init, setparent=True)
             p._mapped_by_synonym = self.key
 
index 02eed516b23a06626d0b06df2396e8bef57d1dcb..87b4cfcde87d79bf8da341a52af5e5fb9b72147c 100644 (file)
@@ -118,7 +118,7 @@ class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots):
         """
         return {}
 
-    def setup(self, context, entity, path, adapter, **kwargs):
+    def setup(self, context, query_entity, path, adapter, **kwargs):
         """Called by Query for the purposes of constructing a SQL statement.
 
         Each MapperProperty associated with the target mapper processes the
@@ -542,13 +542,15 @@ class StrategizedProperty(MapperProperty):
             )
             return strategy
 
-    def setup(self, context, entity, path, adapter, **kwargs):
+    def setup(self, context, query_entity, path, adapter, **kwargs):
         loader = self._get_context_loader(context, path)
         if loader and loader.strategy:
             strat = self._get_strategy(loader.strategy)
         else:
             strat = self.strategy
-        strat.setup_query(context, entity, path, loader, adapter, **kwargs)
+        strat.setup_query(
+            context, query_entity, path, loader, adapter, **kwargs
+        )
 
     def create_row_processor(
         self, context, path, mapper, result, adapter, populators
@@ -722,7 +724,9 @@ class LoaderStrategy(object):
     def init_class_attribute(self, mapper):
         pass
 
-    def setup_query(self, context, entity, path, loadopt, adapter, **kwargs):
+    def setup_query(
+        self, context, query_entity, path, loadopt, adapter, **kwargs
+    ):
         """Establish column and other state for a given QueryContext.
 
         This method fulfills the contract specified by MapperProperty.setup().
index 0c8ab0b10eed616187683049b424bb4c8887dcf0..ff5148ed1c35a924c8c6aee9d850e9bc24f6e2f8 100644 (file)
@@ -114,11 +114,19 @@ class Mapper(InspectionAttr):
         ),
         order_by=(
             "1.1",
-            "The :paramref:`.Mapper.order_by` parameter "
+            "The :paramref:`.mapper.order_by` parameter "
             "is deprecated, and will be removed in a future release. "
             "Use :meth:`.Query.order_by` to determine the ordering of a "
             "result set.",
         ),
+        non_primary=(
+            "1.3",
+            "The :paramref:`.mapper.non_primary` parameter is deprecated, "
+            "and will be removed in a future release.  The functionality "
+            "of non primary mappers is now better suited using the "
+            ":class:`.AliasedClass` construct, which can also be used "
+            "as the target of a :func:`.relationship` in 1.3.",
+        ),
     )
     def __init__(
         self,
@@ -755,26 +763,34 @@ class Mapper(InspectionAttr):
 
     .. seealso::
 
-        :attr:`~.Mapper.mapped_table`.
+        :attr:`~.Mapper.persist_selectable`.
 
     """
 
-    mapped_table = None
+    persist_selectable = None
     """The :class:`.Selectable` to which this :class:`.Mapper` is mapped.
 
     Typically an instance of :class:`.Table`, :class:`.Join`, or
     :class:`.Alias`.
 
-    The "mapped" table is the selectable that
-    the mapper selects from during queries.   For non-inheriting
-    mappers, the mapped table is the same as the "local" table.
-    For joined-table inheritance mappers, mapped_table references the
-    full :class:`.Join` representing full rows for this particular
-    subclass.  For single-table inheritance mappers, mapped_table
-    references the base table.
+    The :attr:`.Mapper.persist_selectable` is separate from
+    :attr:`.Mapper.selectable` in that the former represents columns
+    that are mapped on this class or its superclasses, whereas the
+    latter may be a "polymorphic" selectable that contains additional columns
+    which are in fact mapped on subclasses only.
+
+    "persist selectable" is the "thing the mapper writes to" and
+    "selectable" is the "thing the mapper selects from".
+
+    :attr:`.Mapper.persist_selectable` is also separate from
+    :attr:`.Mapper.local_table`, which represents the set of columns that
+    are locally mapped on this class directly.
+
 
     .. seealso::
 
+        :attr:`~.Mapper.selectable`.
+
         :attr:`~.Mapper.local_table`.
 
     """
@@ -827,8 +843,8 @@ class Mapper(InspectionAttr):
     which comprise the 'primary key' of the mapped table, from the
     perspective of this :class:`.Mapper`.
 
-    This list is against the selectable in :attr:`~.Mapper.mapped_table`. In
-    the case of inheriting mappers, some columns may be managed by a
+    This list is against the selectable in :attr:`~.Mapper.persist_selectable`.
+    In the case of inheriting mappers, some columns may be managed by a
     superclass mapper.  For example, in the case of a :class:`.Join`, the
     primary key is determined by all of the primary key columns across all
     tables referenced by the :class:`.Join`.
@@ -965,6 +981,11 @@ class Mapper(InspectionAttr):
     c = None
     """A synonym for :attr:`~.Mapper.columns`."""
 
+    @property
+    @util.deprecated("1.3", "Use .persist_selectable")
+    def mapped_table(self):
+        return self.persist_selectable
+
     @util.memoized_property
     def _path_registry(self):
         return PathRegistry.per_mapper(self)
@@ -994,11 +1015,11 @@ class Mapper(InspectionAttr):
             # inherit_condition is optional.
             if self.local_table is None:
                 self.local_table = self.inherits.local_table
-                self.mapped_table = self.inherits.mapped_table
+                self.persist_selectable = self.inherits.persist_selectable
                 self.single = True
             elif self.local_table is not self.inherits.local_table:
                 if self.concrete:
-                    self.mapped_table = self.local_table
+                    self.persist_selectable = self.local_table
                     for mapper in self.iterate_to_root():
                         if mapper.polymorphic_on is not None:
                             mapper._requires_row_aliasing = True
@@ -1011,19 +1032,19 @@ class Mapper(InspectionAttr):
                         self.inherit_condition = sql_util.join_condition(
                             self.inherits.local_table, self.local_table
                         )
-                    self.mapped_table = sql.join(
-                        self.inherits.mapped_table,
+                    self.persist_selectable = sql.join(
+                        self.inherits.persist_selectable,
                         self.local_table,
                         self.inherit_condition,
                     )
 
                     fks = util.to_set(self.inherit_foreign_keys)
                     self._inherits_equated_pairs = sql_util.criterion_as_pairs(
-                        self.mapped_table.onclause,
+                        self.persist_selectable.onclause,
                         consider_as_foreign_keys=fks,
                     )
             else:
-                self.mapped_table = self.local_table
+                self.persist_selectable = self.local_table
 
             if self.polymorphic_identity is not None and not self.concrete:
                 self._identity_class = self.inherits._identity_class
@@ -1099,14 +1120,15 @@ class Mapper(InspectionAttr):
         else:
             self._all_tables = set()
             self.base_mapper = self
-            self.mapped_table = self.local_table
+            self.persist_selectable = self.local_table
             if self.polymorphic_identity is not None:
                 self.polymorphic_map[self.polymorphic_identity] = self
             self._identity_class = self.class_
 
-        if self.mapped_table is None:
+        if self.persist_selectable is None:
             raise sa_exc.ArgumentError(
-                "Mapper '%s' does not have a mapped_table specified." % self
+                "Mapper '%s' does not have a persist_selectable specified."
+                % self
             )
 
     def _set_with_polymorphic(self, with_polymorphic):
@@ -1331,7 +1353,7 @@ class Mapper(InspectionAttr):
             instrumentation.unregister_class(self.class_)
 
     def _configure_pks(self):
-        self.tables = sql_util.find_tables(self.mapped_table)
+        self.tables = sql_util.find_tables(self.persist_selectable)
 
         self._pks_by_table = {}
         self._cols_by_table = {}
@@ -1343,7 +1365,7 @@ class Mapper(InspectionAttr):
         pk_cols = util.column_set(c for c in all_cols if c.primary_key)
 
         # identify primary key columns which are also mapped by this mapper.
-        tables = set(self.tables + [self.mapped_table])
+        tables = set(self.tables + [self.persist_selectable])
         self._all_tables.update(tables)
         for t in tables:
             if t.primary_key and pk_cols.issuperset(t.primary_key):
@@ -1366,13 +1388,13 @@ class Mapper(InspectionAttr):
 
         # otherwise, see that we got a full PK for the mapped table
         elif (
-            self.mapped_table not in self._pks_by_table
-            or len(self._pks_by_table[self.mapped_table]) == 0
+            self.persist_selectable not in self._pks_by_table
+            or len(self._pks_by_table[self.persist_selectable]) == 0
         ):
             raise sa_exc.ArgumentError(
                 "Mapper %s could not assemble any primary "
                 "key columns for mapped table '%s'"
-                % (self, self.mapped_table.description)
+                % (self, self.persist_selectable.description)
             )
         elif self.local_table not in self._pks_by_table and isinstance(
             self.local_table, schema.Table
@@ -1393,19 +1415,19 @@ class Mapper(InspectionAttr):
             # that of the inheriting (unless concrete or explicit)
             self.primary_key = self.inherits.primary_key
         else:
-            # determine primary key from argument or mapped_table pks -
+            # determine primary key from argument or persist_selectable pks -
             # reduce to the minimal set of columns
             if self._primary_key_argument:
                 primary_key = sql_util.reduce_columns(
                     [
-                        self.mapped_table.corresponding_column(c)
+                        self.persist_selectable.corresponding_column(c)
                         for c in self._primary_key_argument
                     ],
                     ignore_nonexistent_tables=True,
                 )
             else:
                 primary_key = sql_util.reduce_columns(
-                    self._pks_by_table[self.mapped_table],
+                    self._pks_by_table[self.persist_selectable],
                     ignore_nonexistent_tables=True,
                 )
 
@@ -1413,7 +1435,7 @@ class Mapper(InspectionAttr):
                 raise sa_exc.ArgumentError(
                     "Mapper %s could not assemble any primary "
                     "key columns for mapped table '%s'"
-                    % (self, self.mapped_table.description)
+                    % (self, self.persist_selectable.description)
                 )
 
             self.primary_key = tuple(primary_key)
@@ -1458,7 +1480,7 @@ class Mapper(InspectionAttr):
 
         # create properties for each column in the mapped table,
         # for those columns which don't already map to a property
-        for column in self.mapped_table.columns:
+        for column in self.persist_selectable.columns:
             if column in self._columntoproperty:
                 continue
 
@@ -1539,8 +1561,8 @@ class Mapper(InspectionAttr):
                 # doesn't appear to be mapped. this means it can be 1.
                 # only present in the with_polymorphic selectable or
                 # 2. a totally standalone SQL expression which we'd
-                # hope is compatible with this mapper's mapped_table
-                col = self.mapped_table.corresponding_column(
+                # hope is compatible with this mapper's persist_selectable
+                col = self.persist_selectable.corresponding_column(
                     self.polymorphic_on
                 )
                 if col is None:
@@ -1550,7 +1572,7 @@ class Mapper(InspectionAttr):
                     # for it. Just check that if it's directly a
                     # schema.Column and we have with_polymorphic, it's
                     # likely a user error if the schema.Column isn't
-                    # represented somehow in either mapped_table or
+                    # represented somehow in either persist_selectable or
                     # with_polymorphic.   Otherwise as of 0.7.4 we
                     # just go with it and assume the user wants it
                     # that way (i.e. a CASE statement)
@@ -1607,12 +1629,12 @@ class Mapper(InspectionAttr):
                 # table is the same as the parent (i.e. single table
                 # inheritance), we can use it
                 if mapper.polymorphic_on is not None:
-                    if self.mapped_table is mapper.mapped_table:
+                    if self.persist_selectable is mapper.persist_selectable:
                         self.polymorphic_on = mapper.polymorphic_on
                     else:
                         self.polymorphic_on = (
-                            self.mapped_table.corresponding_column
-                        )(mapper.polymorphic_on)
+                            self.persist_selectable
+                        ).corresponding_column(mapper.polymorphic_on)
                     # we can use the parent mapper's _set_polymorphic_identity
                     # directly; it ensures the polymorphic_identity of the
                     # instance's mapper is used so is portable to subclasses.
@@ -1674,7 +1696,7 @@ class Mapper(InspectionAttr):
         stack = deque([self])
         while stack:
             item = stack.popleft()
-            if item.mapped_table is self.mapped_table:
+            if item.persist_selectable is self.persist_selectable:
                 identities.add(item.polymorphic_identity)
                 stack.extend(item._inheriting_mappers)
 
@@ -1717,7 +1739,7 @@ class Mapper(InspectionAttr):
             prop = self._property_from_column(key, prop)
 
         if isinstance(prop, properties.ColumnProperty):
-            col = self.mapped_table.corresponding_column(prop.columns[0])
+            col = self.persist_selectable.corresponding_column(prop.columns[0])
 
             # if the column is not present in the mapped table,
             # test if a column has been added after the fact to the
@@ -1728,8 +1750,8 @@ class Mapper(InspectionAttr):
                     col = m.local_table.corresponding_column(prop.columns[0])
                     if col is not None:
                         for m2 in path:
-                            m2.mapped_table._reset_exported()
-                        col = self.mapped_table.corresponding_column(
+                            m2.persist_selectable._reset_exported()
+                        col = self.persist_selectable.corresponding_column(
                             prop.columns[0]
                         )
                         break
@@ -1874,7 +1896,7 @@ class Mapper(InspectionAttr):
         ):
             mapped_column = []
             for c in columns:
-                mc = self.mapped_table.corresponding_column(c)
+                mc = self.persist_selectable.corresponding_column(c)
                 if mc is None:
                     mc = self.local_table.corresponding_column(c)
                     if mc is not None:
@@ -1882,8 +1904,8 @@ class Mapper(InspectionAttr):
                         # mapped table, this corresponds to adding a
                         # column after the fact to the local table.
                         # [ticket:1523]
-                        self.mapped_table._reset_exported()
-                    mc = self.mapped_table.corresponding_column(c)
+                        self.persist_selectable._reset_exported()
+                    mc = self.persist_selectable.corresponding_column(c)
                     if mc is None:
                         raise sa_exc.ArgumentError(
                             "When configuring property '%s' on %s, "
@@ -2077,7 +2099,7 @@ class Mapper(InspectionAttr):
         mapped tables.
 
         """
-        from_obj = self.mapped_table
+        from_obj = self.persist_selectable
         for m in mappers:
             if m is self:
                 continue
@@ -2118,7 +2140,7 @@ class Mapper(InspectionAttr):
     @_memoized_configured_property
     def _with_polymorphic_selectable(self):
         if not self.with_polymorphic:
-            return self.mapped_table
+            return self.persist_selectable
 
         spec, selectable = self.with_polymorphic
         if selectable is not None:
@@ -2243,7 +2265,7 @@ class Mapper(InspectionAttr):
         """The :func:`.select` construct this :class:`.Mapper` selects from
         by default.
 
-        Normally, this is equivalent to :attr:`.mapped_table`, unless
+        Normally, this is equivalent to :attr:`.persist_selectable`, unless
         the ``with_polymorphic`` feature is in use, in which case the
         full "polymorphic" selectable is returned.
 
index 150347995178cc7f4cb0b843f88ac590d053ace7..3bb36f1e2620226c1d06d4c0517f29a391e82d7e 100644 (file)
@@ -211,7 +211,7 @@ class Query(object):
                         and ext_info.mapper.with_polymorphic
                     ):
                         if (
-                            ext_info.mapper.mapped_table
+                            ext_info.mapper.persist_selectable
                             not in self._polymorphic_adapters
                         ):
                             self._mapper_loads_polymorphically_with(
@@ -2304,7 +2304,7 @@ class Query(object):
                     if of_type:
                         right = of_type
                     else:
-                        right = onclause.property.mapper
+                        right = onclause.property.entity
 
                 left = onclause._parententity
 
@@ -2610,7 +2610,7 @@ class Query(object):
             # be more liberal about auto-aliasing.
             if right_mapper and (
                 right_mapper.with_polymorphic
-                or isinstance(right_mapper.mapped_table, expression.Join)
+                or isinstance(right_mapper.persist_selectable, expression.Join)
             ):
                 for from_obj in self._from_obj or [l_info.selectable]:
                     if sql_util.selectables_overlap(
@@ -2669,13 +2669,13 @@ class Query(object):
                 # as the ON clause
 
                 if not right_selectable.is_derived_from(
-                    right_mapper.mapped_table
+                    right_mapper.persist_selectable
                 ):
                     raise sa_exc.InvalidRequestError(
                         "Selectable '%s' is not derived from '%s'"
                         % (
                             right_selectable.description,
-                            right_mapper.mapped_table.description,
+                            right_mapper.persist_selectable.description,
                         )
                     )
 
@@ -4660,7 +4660,7 @@ class AliasOption(interfaces.MapperOption):
 
     def process_query(self, query):
         if isinstance(self.alias, util.string_types):
-            alias = query._mapper_zero().mapped_table.alias(self.alias)
+            alias = query._mapper_zero().persist_selectable.alias(self.alias)
         else:
             alias = self.alias
         query._from_obj_alias = sql_util.ColumnAdapter(alias)
index be2093fb9afa13082970c63dedc9f4551e74930f..530a9bd89e669bf3e77f232c157e3f7cedf274c3 100644 (file)
@@ -943,6 +943,20 @@ class RelationshipProperty(StrategizedProperty):
                 of_type=self._of_type,
             )
 
+        @util.memoized_property
+        def entity(self):
+            """The target entity referred to by this
+            :class:`.RelationshipProperty.Comparator`.
+
+            This is either a :class:`.Mapper` or :class:`.AliasedInsp`
+            object.
+
+            This is the "target" or "remote" side of the
+            :func:`.relationship`.
+
+            """
+            return self.property.entity
+
         @util.memoized_property
         def mapper(self):
             """The target :class:`.Mapper` referred to by this
@@ -967,9 +981,9 @@ class RelationshipProperty(StrategizedProperty):
         def __clause_element__(self):
             adapt_from = self._source_selectable()
             if self._of_type:
-                of_type = inspect(self._of_type).mapper
+                of_type_mapper = inspect(self._of_type).mapper
             else:
-                of_type = None
+                of_type_mapper = None
 
             (
                 pj,
@@ -981,7 +995,7 @@ class RelationshipProperty(StrategizedProperty):
             ) = self.property._create_joins(
                 source_selectable=adapt_from,
                 source_polymorphic=True,
-                of_type=of_type,
+                of_type_mapper=of_type_mapper,
             )
             if sj is not None:
                 return pj & sj
@@ -1795,11 +1809,9 @@ class RelationshipProperty(StrategizedProperty):
             )
 
     @util.memoized_property
-    def mapper(self):
-        """Return the targeted :class:`.Mapper` for this
-        :class:`.RelationshipProperty`.
-
-        This is a lazy-initializing static attribute.
+    def entity(self):  # type: () -> Union[AliasedInsp, Mapper]
+        """Return the target mapped entity, which is an inspect() of the
+        class or aliased class tha is referred towards.
 
         """
         if util.callable(self.argument) and not isinstance(
@@ -1810,25 +1822,31 @@ class RelationshipProperty(StrategizedProperty):
             argument = self.argument
 
         if isinstance(argument, type):
-            mapper_ = mapperlib.class_mapper(argument, configure=False)
-        elif isinstance(self.argument, mapperlib.Mapper):
-            mapper_ = argument
+            return mapperlib.class_mapper(argument, configure=False)
+
+        try:
+            entity = inspect(argument)
+        except sa_exc.NoInspectionAvailable:
+            pass
         else:
-            raise sa_exc.ArgumentError(
-                "relationship '%s' expects "
-                "a class or a mapper argument (received: %s)"
-                % (self.key, type(argument))
-            )
-        return mapper_
+            if hasattr(entity, "mapper"):
+                return entity
+
+        raise sa_exc.ArgumentError(
+            "relationship '%s' expects "
+            "a class or a mapper argument (received: %s)"
+            % (self.key, type(argument))
+        )
 
     @util.memoized_property
-    @util.deprecated("0.7", "Use .target")
-    def table(self):
-        """Return the selectable linked to this
-        :class:`.RelationshipProperty` object's target
-        :class:`.Mapper`.
+    def mapper(self):
+        """Return the targeted :class:`.Mapper` for this
+        :class:`.RelationshipProperty`.
+
+        This is a lazy-initializing static attribute.
+
         """
-        return self.target
+        return self.entity.mapper
 
     def do_init(self):
         self._check_conflicts()
@@ -1894,14 +1912,14 @@ class RelationshipProperty(StrategizedProperty):
             for x in util.to_column_set(self.remote_side)
         )
 
-        self.target = self.mapper.mapped_table
+        self.target = self.entity.persist_selectable
 
     def _setup_join_conditions(self):
         self._join_condition = jc = JoinCondition(
-            parent_selectable=self.parent.mapped_table,
-            child_selectable=self.mapper.mapped_table,
+            parent_persist_selectable=self.parent.persist_selectable,
+            child_persist_selectable=self.entity.persist_selectable,
             parent_local_selectable=self.parent.local_table,
-            child_local_selectable=self.mapper.local_table,
+            child_local_selectable=self.entity.local_table,
             primaryjoin=self.primaryjoin,
             secondary=self.secondary,
             secondaryjoin=self.secondaryjoin,
@@ -2016,7 +2034,7 @@ class RelationshipProperty(StrategizedProperty):
                 and self.secondary.c.contains_column(c)
             ):
                 continue
-            if not self.parent.mapped_table.c.contains_column(
+            if not self.parent.persist_selectable.c.contains_column(
                 c
             ) and not self.target.c.contains_column(c):
                 return False
@@ -2124,7 +2142,7 @@ class RelationshipProperty(StrategizedProperty):
         source_selectable=None,
         dest_polymorphic=False,
         dest_selectable=None,
-        of_type=None,
+        of_type_mapper=None,
     ):
         if source_selectable is None:
             if source_polymorphic and self.parent.with_polymorphic:
@@ -2132,11 +2150,9 @@ class RelationshipProperty(StrategizedProperty):
 
         aliased = False
         if dest_selectable is None:
+            dest_selectable = self.entity.selectable
             if dest_polymorphic and self.mapper.with_polymorphic:
-                dest_selectable = self.mapper._with_polymorphic_selectable
                 aliased = True
-            else:
-                dest_selectable = self.mapper.mapped_table
 
             if self._is_self_referential and source_selectable is None:
                 dest_selectable = dest_selectable.alias()
@@ -2144,7 +2160,7 @@ class RelationshipProperty(StrategizedProperty):
         else:
             aliased = True
 
-        dest_mapper = of_type or self.mapper
+        dest_mapper = of_type_mapper or self.mapper
 
         single_crit = dest_mapper._single_table_criterion
         aliased = aliased or (source_selectable is not None)
@@ -2161,7 +2177,7 @@ class RelationshipProperty(StrategizedProperty):
         if source_selectable is None:
             source_selectable = self.parent.local_table
         if dest_selectable is None:
-            dest_selectable = self.mapper.local_table
+            dest_selectable = self.entity.local_table
         return (
             primaryjoin,
             secondaryjoin,
@@ -2187,8 +2203,8 @@ def _annotate_columns(element, annotations):
 class JoinCondition(object):
     def __init__(
         self,
-        parent_selectable,
-        child_selectable,
+        parent_persist_selectable,
+        child_persist_selectable,
         parent_local_selectable,
         child_local_selectable,
         primaryjoin=None,
@@ -2204,9 +2220,9 @@ class JoinCondition(object):
         support_sync=True,
         can_be_synced_fn=lambda *c: True,
     ):
-        self.parent_selectable = parent_selectable
+        self.parent_persist_selectable = parent_persist_selectable
         self.parent_local_selectable = parent_local_selectable
-        self.child_selectable = child_selectable
+        self.child_persist_selectable = child_persist_selectable
         self.child_local_selectable = child_local_selectable
         self.parent_equivalents = parent_equivalents
         self.child_equivalents = child_equivalents
@@ -2317,14 +2333,14 @@ class JoinCondition(object):
             if self.secondary is not None:
                 if self.secondaryjoin is None:
                     self.secondaryjoin = join_condition(
-                        self.child_selectable,
+                        self.child_persist_selectable,
                         self.secondary,
                         a_subset=self.child_local_selectable,
                         consider_as_foreign_keys=consider_as_foreign_keys,
                     )
                 if self.primaryjoin is None:
                     self.primaryjoin = join_condition(
-                        self.parent_selectable,
+                        self.parent_persist_selectable,
                         self.secondary,
                         a_subset=self.parent_local_selectable,
                         consider_as_foreign_keys=consider_as_foreign_keys,
@@ -2332,8 +2348,8 @@ class JoinCondition(object):
             else:
                 if self.primaryjoin is None:
                     self.primaryjoin = join_condition(
-                        self.parent_selectable,
-                        self.child_selectable,
+                        self.parent_persist_selectable,
+                        self.child_persist_selectable,
                         a_subset=self.parent_local_selectable,
                         consider_as_foreign_keys=consider_as_foreign_keys,
                     )
@@ -2519,8 +2535,8 @@ class JoinCondition(object):
         comparisons where both columns are in both tables.
 
         """
-        pt = self.parent_selectable
-        mt = self.child_selectable
+        pt = self.parent_persist_selectable
+        mt = self.child_persist_selectable
         result = [False]
 
         def visit_binary(binary):
@@ -2542,7 +2558,7 @@ class JoinCondition(object):
         """Return True if parent/child tables have some overlap."""
 
         return selectables_overlap(
-            self.parent_selectable, self.child_selectable
+            self.parent_persist_selectable, self.child_persist_selectable
         )
 
     def _annotate_remote(self):
@@ -2661,9 +2677,9 @@ class JoinCondition(object):
             if isinstance(left, expression.ColumnClause) and isinstance(
                 right, expression.ColumnClause
             ):
-                if self.child_selectable.c.contains_column(
+                if self.child_persist_selectable.c.contains_column(
                     right
-                ) and self.parent_selectable.c.contains_column(left):
+                ) and self.parent_persist_selectable.c.contains_column(left):
                     right = right._annotate({"remote": True})
             elif (
                 check_entities
@@ -2692,7 +2708,7 @@ class JoinCondition(object):
         """
 
         def repl(element):
-            if self.child_selectable.c.contains_column(element) and (
+            if self.child_persist_selectable.c.contains_column(element) and (
                 not self.parent_local_selectable.c.contains_column(element)
                 or self.child_local_selectable.c.contains_column(element)
             ):
@@ -2728,7 +2744,7 @@ class JoinCondition(object):
                 [l for (l, r) in self._local_remote_pairs]
             )
         else:
-            local_side = util.column_set(self.parent_selectable.c)
+            local_side = util.column_set(self.parent_persist_selectable.c)
 
         def locals_(elem):
             if "remote" not in elem._annotations and elem in local_side:
@@ -2839,8 +2855,8 @@ class JoinCondition(object):
         if self.secondaryjoin is not None:
             self.direction = MANYTOMANY
         else:
-            parentcols = util.column_set(self.parent_selectable.c)
-            targetcols = util.column_set(self.child_selectable.c)
+            parentcols = util.column_set(self.parent_persist_selectable.c)
+            targetcols = util.column_set(self.child_persist_selectable.c)
 
             # fk collection which suggests ONETOMANY.
             onetomany_fk = targetcols.intersection(self.foreign_key_columns)
index 53f99b99d04ef4d6d2dc3bf2d0566dd3a97e49be..a4cc071941a97f2cfb154bbafbbc0d4f573fed52 100644 (file)
@@ -1507,7 +1507,7 @@ class Session(_SessionClassMethods):
                     if cls in self.__binds:
                         return self.__binds[cls]
                 if clause is None:
-                    clause = mapper.mapped_table
+                    clause = mapper.persist_selectable
 
             if clause is not None:
                 for t in sql_util.find_tables(clause, include_crud=True):
@@ -1520,8 +1520,8 @@ class Session(_SessionClassMethods):
         if isinstance(clause, sql.expression.ClauseElement) and clause.bind:
             return clause.bind
 
-        if mapper and mapper.mapped_table.bind:
-            return mapper.mapped_table.bind
+        if mapper and mapper.persist_selectable.bind:
+            return mapper.persist_selectable.bind
 
         context = []
         if mapper is not None:
index f6862a40268992fbf517a5f53a93155ad6baf544..3e7372fac72cee98cfe2ede966f6736a545600af 100644 (file)
@@ -140,7 +140,7 @@ class UninstrumentedColumnLoader(LoaderStrategy):
     def setup_query(
         self,
         context,
-        entity,
+        query_entity,
         path,
         loadopt,
         adapter,
@@ -173,7 +173,7 @@ class ColumnLoader(LoaderStrategy):
     def setup_query(
         self,
         context,
-        entity,
+        query_entity,
         path,
         loadopt,
         adapter,
@@ -235,7 +235,7 @@ class ExpressionColumnLoader(ColumnLoader):
     def setup_query(
         self,
         context,
-        entity,
+        query_entity,
         path,
         loadopt,
         adapter,
@@ -335,7 +335,7 @@ class DeferredColumnLoader(LoaderStrategy):
     def setup_query(
         self,
         context,
-        entity,
+        query_entity,
         path,
         loadopt,
         adapter,
@@ -366,7 +366,7 @@ class DeferredColumnLoader(LoaderStrategy):
                 (("deferred", False), ("instrument", True))
             ).setup_query(
                 context,
-                entity,
+                query_entity,
                 path,
                 loadopt,
                 adapter,
@@ -440,11 +440,12 @@ class LoadDeferredColumns(object):
 class AbstractRelationshipLoader(LoaderStrategy):
     """LoaderStratgies which deal with related objects."""
 
-    __slots__ = "mapper", "target", "uselist"
+    __slots__ = "mapper", "target", "uselist", "entity"
 
     def __init__(self, parent, strategy_key):
         super(AbstractRelationshipLoader, self).__init__(parent, strategy_key)
         self.mapper = self.parent_property.mapper
+        self.entity = self.parent_property.entity
         self.target = self.parent_property.target
         self.uselist = self.parent_property.uselist
 
@@ -510,6 +511,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         "_lazywhere",
         "_rev_lazywhere",
         "use_get",
+        "is_aliased_class",
         "_bind_to_col",
         "_equated_columns",
         "_rev_bind_to_col",
@@ -525,6 +527,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         self._raise_always = self.strategy_opts["lazy"] == "raise"
         self._raise_on_sql = self.strategy_opts["lazy"] == "raise_on_sql"
 
+        self.is_aliased_class = inspect(self.entity).is_aliased_class
+
         join_condition = self.parent_property._join_condition
         self._lazywhere, self._bind_to_col, self._equated_columns = (
             join_condition.create_lazy_clause()
@@ -540,10 +544,14 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
 
         # determine if our "lazywhere" clause is the same as the mapper's
         # get() clause.  then we can just use mapper.get()
-        self.use_get = not self.uselist and self.mapper._get_clause[0].compare(
-            self._lazywhere,
-            use_proxies=True,
-            equivalents=self.mapper._equivalent_columns,
+        self.use_get = (
+            not self.is_aliased_class
+            and not self.uselist
+            and self.entity._get_clause[0].compare(
+                self._lazywhere,
+                use_proxies=True,
+                equivalents=self.mapper._equivalent_columns,
+            )
         )
 
         if self.use_get:
@@ -693,7 +701,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             # does this, including how it decides what the correct
             # identity_token would be for this identity.
             instance = session.query()._identity_lookup(
-                self.mapper,
+                self.entity,
                 primary_key_identity,
                 passive=passive,
                 lazy_loaded_from=state,
@@ -757,7 +765,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         # lazy loaders.   Currently the LRU cache is local to the LazyLoader,
         # however add ourselves to the initial cache key just to future
         # proof in case it moves
-        q = self._bakery(lambda session: session.query(self.mapper), self)
+        q = self._bakery(lambda session: session.query(self.entity), self)
 
         q.add_criteria(
             lambda q: q._adapt_all_clauses()._with_invoke_all_eagers(False),
@@ -992,7 +1000,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
         if with_poly_info is not None:
             effective_entity = with_poly_info.entity
         else:
-            effective_entity = self.mapper
+            effective_entity = self.entity
 
         subq_path = context.attributes.get(
             ("subquery_path", None), orm_util.PathRegistry.root
@@ -1422,7 +1430,7 @@ class JoinedLoader(AbstractRelationshipLoader):
     def setup_query(
         self,
         context,
-        entity,
+        query_entity,
         path,
         loadopt,
         adapter,
@@ -1454,7 +1462,7 @@ class JoinedLoader(AbstractRelationshipLoader):
                 adapter,
                 add_to_collection,
             ) = self._setup_query_on_user_defined_adapter(
-                context, entity, path, adapter, user_defined_adapter
+                context, query_entity, path, adapter, user_defined_adapter
             )
         else:
             # if not via query option, check for
@@ -1473,7 +1481,7 @@ class JoinedLoader(AbstractRelationshipLoader):
                 chained_from_outerjoin,
             ) = self._generate_row_adapter(
                 context,
-                entity,
+                query_entity,
                 path,
                 loadopt,
                 adapter,
@@ -1490,12 +1498,12 @@ class JoinedLoader(AbstractRelationshipLoader):
         else:
             with_polymorphic = None
 
-        path = path[self.mapper]
+        path = path[self.entity]
 
         loading._setup_entity_query(
             context,
             self.mapper,
-            entity,
+            query_entity,
             path,
             clauses,
             add_to_collection,
@@ -1536,11 +1544,6 @@ class JoinedLoader(AbstractRelationshipLoader):
 
         root_mapper, prop = path[-2:]
 
-        # from .mapper import Mapper
-        # from .interfaces import MapperProperty
-        # assert isinstance(root_mapper, Mapper)
-        # assert isinstance(prop, MapperProperty)
-
         if alias is not None:
             if isinstance(alias, str):
                 alias = prop.target.alias(alias)
@@ -1597,6 +1600,11 @@ class JoinedLoader(AbstractRelationshipLoader):
         # we need one unique AliasedClass per query per appearance of our
         # entity in the query.
 
+        if inspect(self.entity).is_aliased_class:
+            alt_selectable = inspect(self.entity).selectable
+        else:
+            alt_selectable = None
+
         key = ("joinedloader_ac", self)
         if key not in context.attributes:
             context.attributes[key] = idx = 0
@@ -1605,8 +1613,14 @@ class JoinedLoader(AbstractRelationshipLoader):
 
         if idx >= len(self._aliased_class_pool):
             to_adapt = orm_util.AliasedClass(
-                self.mapper, flat=True, use_mapper_path=True
+                self.mapper,
+                alias=alt_selectable.alias(flat=True)
+                if alt_selectable is not None
+                else None,
+                flat=True,
+                use_mapper_path=True,
             )
+
             # load up the .columns collection on the Alias() before
             # the object becomes shared among threads.  this prevents
             # races for column identities.
@@ -1683,7 +1697,7 @@ class JoinedLoader(AbstractRelationshipLoader):
     def _create_eager_join(
         self,
         context,
-        entity,
+        query_entity,
         path,
         adapter,
         parentmapper,
@@ -1693,7 +1707,7 @@ class JoinedLoader(AbstractRelationshipLoader):
     ):
 
         if parentmapper is None:
-            localparent = entity.mapper
+            localparent = query_entity.mapper
         else:
             localparent = parentmapper
 
@@ -1705,24 +1719,24 @@ class JoinedLoader(AbstractRelationshipLoader):
             and context.query._should_nest_selectable
         )
 
-        entity_key = None
+        query_entity_key = None
 
         if (
-            entity not in context.eager_joins
+            query_entity not in context.eager_joins
             and not should_nest_selectable
             and context.from_clause
         ):
             indexes = sql_util.find_left_clause_that_matches_given(
-                context.from_clause, entity.selectable
+                context.from_clause, query_entity.selectable
             )
 
             if len(indexes) > 1:
                 # for the eager load case, I can't reproduce this right
                 # now.   For query.join() I can.
                 raise sa_exc.InvalidRequestError(
-                    "Can't identify which entity in which to joined eager "
-                    "load from.   Please use an exact match when specifying "
-                    "the join path."
+                    "Can't identify which query entity in which to joined "
+                    "eager load from.   Please use an exact match when "
+                    "specifying the join path."
                 )
 
             if indexes:
@@ -1731,12 +1745,17 @@ class JoinedLoader(AbstractRelationshipLoader):
                 # key it to its list index in the eager_joins dict.
                 # Query._compile_context will adapt as needed and
                 # append to the FROM clause of the select().
-                entity_key, default_towrap = indexes[0], clause
+                query_entity_key, default_towrap = indexes[0], clause
 
-        if entity_key is None:
-            entity_key, default_towrap = entity, entity.selectable
+        if query_entity_key is None:
+            query_entity_key, default_towrap = (
+                query_entity,
+                query_entity.selectable,
+            )
 
-        towrap = context.eager_joins.setdefault(entity_key, default_towrap)
+        towrap = context.eager_joins.setdefault(
+            query_entity_key, default_towrap
+        )
 
         if adapter:
             if getattr(adapter, "aliased_class", None):
@@ -1771,7 +1790,7 @@ class JoinedLoader(AbstractRelationshipLoader):
             not chained_from_outerjoin
             or not innerjoin
             or innerjoin == "unnested"
-            or entity.entity_zero.represents_outer_join
+            or query_entity.entity_zero.represents_outer_join
         )
 
         if attach_on_outside:
@@ -1781,7 +1800,7 @@ class JoinedLoader(AbstractRelationshipLoader):
                 clauses.aliased_class,
                 onclause,
                 isouter=not innerjoin
-                or entity.entity_zero.represents_outer_join
+                or query_entity.entity_zero.represents_outer_join
                 or (chained_from_outerjoin and isinstance(towrap, sql.Join)),
                 _left_memo=self.parent,
                 _right_memo=self.mapper,
@@ -1792,10 +1811,10 @@ class JoinedLoader(AbstractRelationshipLoader):
                 path, towrap, clauses, onclause
             )
 
-        context.eager_joins[entity_key] = eagerjoin
+        context.eager_joins[query_entity_key] = eagerjoin
 
         # send a hint to the Query as to where it may "splice" this join
-        eagerjoin.stop_on = entity.selectable
+        eagerjoin.stop_on = query_entity.selectable
 
         if not parentmapper:
             # for parentclause that is the non-eager end of the join,
@@ -1808,7 +1827,7 @@ class JoinedLoader(AbstractRelationshipLoader):
             for col in sql_util._find_columns(
                 self.parent_property.primaryjoin
             ):
-                if localparent.mapped_table.c.contains_column(col):
+                if localparent.persist_selectable.c.contains_column(col):
                     if adapter:
                         col = adapter.columns[col]
                     context.primary_columns.append(col)
@@ -1938,7 +1957,7 @@ class JoinedLoader(AbstractRelationshipLoader):
                 self.mapper,
                 context,
                 result,
-                our_path[self.mapper],
+                our_path[self.entity],
                 eager_adapter,
             )
 
@@ -2145,7 +2164,7 @@ class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         if with_poly_info is not None:
             effective_entity = with_poly_info.entity
         else:
-            effective_entity = self.mapper
+            effective_entity = self.entity
 
         if not path_w_prop.contains(context.attributes, "loader"):
             if self.join_depth:
index 6f9746daaf1612550c70d7b804a013a2dbccaac3..759efe9f50857bf1d25f21bc448a62ebb09f5a21 100644 (file)
@@ -211,6 +211,7 @@ class Load(Generative, MapperOption):
                 ent = inspect(existing_of_type)
             else:
                 ent = path.entity
+
             try:
                 # use getattr on the class to work around
                 # synonyms, hybrids, etc.
index 776278469603cbc73f37453a5a076c76e8cdca00..8873a6a72af2220d5aa5099ea2dcd3d0db9e6050 100644 (file)
@@ -394,21 +394,30 @@ class ORMAdapter(sql_util.ColumnAdapter):
 class AliasedClass(object):
     r"""Represents an "aliased" form of a mapped class for usage with Query.
 
-    The ORM equivalent of a :func:`sqlalchemy.sql.expression.alias`
+    The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias`
     construct, this object mimics the mapped class using a
-    __getattr__ scheme and maintains a reference to a
+    ``__getattr__`` scheme and maintains a reference to a
     real :class:`~sqlalchemy.sql.expression.Alias` object.
 
-    Usage is via the :func:`.orm.aliased` function, or alternatively
-    via the :func:`.orm.with_polymorphic` function.
-
-    Usage example::
+    A primary purpose of :class:`.AliasedClass` is to serve as an alternate
+    within a SQL statement generated by the ORM, such that an existing
+    mapped entity can be used in multiple contexts.   A simple example::
 
         # find all pairs of users with the same name
         user_alias = aliased(User)
         session.query(User, user_alias).\
                         join((user_alias, User.id > user_alias.id)).\
-                        filter(User.name==user_alias.name)
+                        filter(User.name == user_alias.name)
+
+    :class:`.AliasedClass` is also capable of mapping an existing mapped
+    class to an entirely new selectable, provided this selectable is column-
+    compatible with the existing mapped selectable, and it can also be
+    configured in a mapping as the target of a :func:`.relationship`.
+    See the links below for examples.
+
+    The :class:`.AliasedClass` object is constructed typically using the
+    :func:`.orm.aliased` function.   It also is produced with additional
+    configuration when using the :func:`.orm.with_polymorphic` function.
 
     The resulting object is an instance of :class:`.AliasedClass`.
     This object implements an attribute scheme which produces the
@@ -427,8 +436,17 @@ class AliasedClass(object):
 
     The resulting inspection object is an instance of :class:`.AliasedInsp`.
 
-    See :func:`.aliased` and :func:`.with_polymorphic` for construction
-    argument descriptions.
+
+    .. seealso::
+
+        :func:`.aliased`
+
+        :func:`.with_polymorphic`
+
+        :ref:`relationship_aliased_class`
+
+        :ref:`relationship_to_window_function`
+
 
     """
 
@@ -564,7 +582,9 @@ class AliasedInsp(InspectionAttr):
     ):
         self.entity = entity
         self.mapper = mapper
-        self.selectable = selectable
+        self.selectable = (
+            self.persist_selectable
+        ) = self.local_table = selectable
         self.name = name
         self.with_polymorphic_mappers = with_polymorphic_mappers
         self.polymorphic_on = polymorphic_on
@@ -659,6 +679,17 @@ class AliasedInsp(InspectionAttr):
         else:
             assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
 
+    @util.memoized_property
+    def _get_clause(self):
+        onclause, replacemap = self.mapper._get_clause
+        return (
+            self._adapter.traverse(onclause),
+            {
+                self._adapter.traverse(col): param
+                for col, param in replacemap.items()
+            },
+        )
+
     @util.memoized_property
     def _memoized_values(self):
         return {}
@@ -970,7 +1001,7 @@ class _ORMJoin(expression.Join):
                 dest_selectable=adapt_to,
                 source_polymorphic=True,
                 dest_polymorphic=True,
-                of_type=right_info.mapper,
+                of_type_mapper=right_info.mapper,
             )
 
             if sj is not None:
index 0915244f74347222f3262ccd774f248371b9f1fb..36f28c063f87bdc47c30764c060e6c58c3956aa0 100644 (file)
@@ -321,8 +321,6 @@ class MemUsageWBackendTest(EnsureZeroed):
         )
         m2 = mapper(B, table2)
 
-        m3 = mapper(A, table1, non_primary=True)
-
         @profile_memory()
         def go():
             sess = create_session()
@@ -354,7 +352,7 @@ class MemUsageWBackendTest(EnsureZeroed):
         go()
 
         metadata.drop_all()
-        del m1, m2, m3
+        del m1, m2
         assert_no_mappers()
 
     def test_sessionmaker(self):
@@ -415,8 +413,6 @@ class MemUsageWBackendTest(EnsureZeroed):
         )
         m2 = mapper(B, table2, _compiled_cache_size=50)
 
-        m3 = mapper(A, table1, non_primary=True)
-
         @profile_memory()
         def go():
             engine = engines.testing_engine(
@@ -458,7 +454,7 @@ class MemUsageWBackendTest(EnsureZeroed):
         go()
 
         metadata.drop_all()
-        del m1, m2, m3
+        del m1, m2
         assert_no_mappers()
 
     @testing.emits_warning("Compiled statement cache for.*")
@@ -642,8 +638,6 @@ class MemUsageWBackendTest(EnsureZeroed):
             )
             mapper(B, table2)
 
-            mapper(A, table1, non_primary=True)
-
             sess = create_session()
             a1 = A(col2="a1")
             a2 = A(col2="a2")
diff --git a/test/ext/test_deprecations.py b/test/ext/test_deprecations.py
new file mode 100644 (file)
index 0000000..099393c
--- /dev/null
@@ -0,0 +1,37 @@
+from sqlalchemy import testing
+from sqlalchemy.orm import mapper
+from .test_mutable import Foo
+from .test_mutable import (
+    MutableAssociationScalarPickleTest as _MutableAssociationScalarPickleTest,
+)
+from .test_mutable import (
+    MutableWithScalarJSONTest as _MutableWithScalarJSONTest,
+)
+
+
+class MutableIncludeNonPrimaryTest(_MutableWithScalarJSONTest):
+    @classmethod
+    def setup_mappers(cls):
+        foo = cls.tables.foo
+
+        mapper(Foo, foo)
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            mapper(
+                Foo, foo, non_primary=True, properties={"foo_bar": foo.c.data}
+            )
+
+
+class MutableAssocIncludeNonPrimaryTest(_MutableAssociationScalarPickleTest):
+    @classmethod
+    def setup_mappers(cls):
+        foo = cls.tables.foo
+
+        mapper(Foo, foo)
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            mapper(
+                Foo, foo, non_primary=True, properties={"foo_bar": foo.c.data}
+            )
index 9b8f761e9c0feabc002835f1674390db8c6e3ab9..eae764d4064a9cacb30581e82c598dcdd40ea214 100644 (file)
@@ -824,15 +824,6 @@ class MutableWithScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest):
         self._test_non_mutable()
 
 
-class MutableIncludeNonPrimaryTest(MutableWithScalarJSONTest):
-    @classmethod
-    def setup_mappers(cls):
-        foo = cls.tables.foo
-
-        mapper(Foo, foo)
-        mapper(Foo, foo, non_primary=True, properties={"foo_bar": foo.c.data})
-
-
 class MutableColumnCopyJSONTest(_MutableDictTestBase, fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
@@ -1013,15 +1004,6 @@ class MutableAssociationScalarPickleTest(
         )
 
 
-class MutableAssocIncludeNonPrimaryTest(MutableAssociationScalarPickleTest):
-    @classmethod
-    def setup_mappers(cls):
-        foo = cls.tables.foo
-
-        mapper(Foo, foo)
-        mapper(Foo, foo, non_primary=True, properties={"foo_bar": foo.c.data})
-
-
 class MutableAssociationScalarJSONTest(
     _MutableDictTestBase, fixtures.MappedTest
 ):
diff --git a/test/orm/test_ac_relationships.py b/test/orm/test_ac_relationships.py
new file mode 100644 (file)
index 0000000..d5f9b01
--- /dev/null
@@ -0,0 +1,292 @@
+from sqlalchemy import and_
+from sqlalchemy import Column
+from sqlalchemy import ForeignKey
+from sqlalchemy import func
+from sqlalchemy import Integer
+from sqlalchemy import join
+from sqlalchemy import select
+from sqlalchemy import testing
+from sqlalchemy.orm import aliased
+from sqlalchemy.orm import joinedload
+from sqlalchemy.orm import noload
+from sqlalchemy.orm import relationship
+from sqlalchemy.orm import selectinload
+from sqlalchemy.orm import Session
+from sqlalchemy.testing import eq_
+from sqlalchemy.testing import fixtures
+from sqlalchemy.testing.assertsql import CompiledSQL
+from sqlalchemy.testing.fixtures import ComparableEntity
+
+
+class PartitionByFixture(fixtures.DeclarativeMappedTest):
+    @classmethod
+    def setup_classes(cls):
+        Base = cls.DeclarativeBasic
+
+        class A(Base):
+            __tablename__ = "a"
+
+            id = Column(Integer, primary_key=True)
+
+        class B(Base):
+            __tablename__ = "b"
+            id = Column(Integer, primary_key=True)
+            a_id = Column(ForeignKey("a.id"))
+            cs = relationship("C")
+
+        class C(Base):
+            __tablename__ = "c"
+            id = Column(Integer, primary_key=True)
+            b_id = Column(ForeignKey("b.id"))
+
+        partition = select(
+            [
+                B,
+                func.row_number()
+                .over(order_by=B.id, partition_by=B.a_id)
+                .label("index"),
+            ]
+        ).alias()
+
+        partitioned_b = aliased(B, alias=partition)
+
+        A.partitioned_bs = relationship(
+            partitioned_b,
+            primaryjoin=and_(
+                partitioned_b.a_id == A.id, partition.c.index < 10
+            ),
+        )
+
+    @classmethod
+    def insert_data(cls):
+        A, B, C = cls.classes("A", "B", "C")
+
+        s = Session(testing.db)
+        s.add_all([A(id=i) for i in range(1, 4)])
+        s.flush()
+        s.add_all(
+            [
+                B(a_id=i, cs=[C(), C()])
+                for i in range(1, 4)
+                for j in range(1, 21)
+            ]
+        )
+        s.commit()
+
+
+class AliasedClassRelationshipTest(
+    PartitionByFixture, testing.AssertsCompiledSQL
+):
+    # TODO: maybe make this more  backend agnostic
+    __requires__ = ("window_functions",)
+    __dialect__ = "default"
+
+    def test_lazyload(self):
+        A, B, C = self.classes("A", "B", "C")
+
+        s = Session(testing.db)
+
+        def go():
+            for a1 in s.query(A):  # 1 query
+                eq_(len(a1.partitioned_bs), 9)  # 3 queries
+                for b in a1.partitioned_bs:
+                    eq_(len(b.cs), 2)  # 9 * 3 = 27 queries
+
+        self.assert_sql_count(testing.db, go, 31)
+
+    def test_join_one(self):
+        A, B, C = self.classes("A", "B", "C")
+
+        s = Session(testing.db)
+
+        q = s.query(A).join(A.partitioned_bs)
+        self.assert_compile(
+            q,
+            "SELECT a.id AS a_id FROM a JOIN "
+            "(SELECT b.id AS id, b.a_id AS a_id, row_number() "
+            "OVER (PARTITION BY b.a_id ORDER BY b.id) "
+            "AS index FROM b) AS anon_1 "
+            "ON anon_1.a_id = a.id AND anon_1.index < :index_1",
+        )
+
+    def test_join_two(self):
+        A, B, C = self.classes("A", "B", "C")
+
+        s = Session(testing.db)
+
+        q = s.query(A, A.partitioned_bs.entity).join(A.partitioned_bs)
+        self.assert_compile(
+            q,
+            "SELECT a.id AS a_id, anon_1.id AS anon_1_id, "
+            "anon_1.a_id AS anon_1_a_id "
+            "FROM a JOIN "
+            "(SELECT b.id AS id, b.a_id AS a_id, row_number() "
+            "OVER (PARTITION BY b.a_id ORDER BY b.id) "
+            "AS index FROM b) AS anon_1 "
+            "ON anon_1.a_id = a.id AND anon_1.index < :index_1",
+        )
+
+    def test_selectinload_w_noload_after(self):
+        A, B, C = self.classes("A", "B", "C")
+
+        s = Session(testing.db)
+
+        def go():
+            for a1 in s.query(A).options(
+                noload("*"), selectinload(A.partitioned_bs)
+            ):
+                for b in a1.partitioned_bs:
+                    eq_(b.cs, [])
+
+        self.assert_sql_count(testing.db, go, 2)
+
+    def test_selectinload_w_joinedload_after(self):
+        A, B, C = self.classes("A", "B", "C")
+
+        s = Session(testing.db)
+
+        def go():
+            for a1 in s.query(A).options(
+                selectinload(A.partitioned_bs).joinedload("cs")
+            ):
+                for b in a1.partitioned_bs:
+                    eq_(len(b.cs), 2)
+
+        self.assert_sql_count(testing.db, go, 2)
+
+
+class AltSelectableTest(
+    fixtures.DeclarativeMappedTest, testing.AssertsCompiledSQL
+):
+    __dialect__ = "default"
+
+    @classmethod
+    def setup_classes(cls):
+        Base = cls.DeclarativeBasic
+
+        class A(ComparableEntity, Base):
+            __tablename__ = "a"
+
+            id = Column(Integer, primary_key=True)
+            b_id = Column(ForeignKey("b.id"))
+
+        class B(ComparableEntity, Base):
+            __tablename__ = "b"
+
+            id = Column(Integer, primary_key=True)
+
+        class C(ComparableEntity, Base):
+            __tablename__ = "c"
+
+            id = Column(Integer, primary_key=True)
+            a_id = Column(ForeignKey("a.id"))
+
+        class D(ComparableEntity, Base):
+            __tablename__ = "d"
+
+            id = Column(Integer, primary_key=True)
+            c_id = Column(ForeignKey("c.id"))
+            b_id = Column(ForeignKey("b.id"))
+
+        # 1. set up the join() as a variable, so we can refer
+        # to it in the mapping multiple times.
+        j = join(B, D, D.b_id == B.id).join(C, C.id == D.c_id)
+
+        # 2. Create an AliasedClass to B
+        B_viacd = aliased(B, j, flat=True)
+
+        A.b = relationship(B_viacd, primaryjoin=A.b_id == j.c.b_id)
+
+    @classmethod
+    def insert_data(cls):
+        A, B, C, D = cls.classes("A", "B", "C", "D")
+        sess = Session()
+
+        for obj in [
+            B(id=1),
+            A(id=1, b_id=1),
+            C(id=1, a_id=1),
+            D(id=1, c_id=1, b_id=1),
+        ]:
+            sess.add(obj)
+            sess.flush()
+        sess.commit()
+
+    def test_lazyload(self):
+        A, B = self.classes("A", "B")
+
+        sess = Session()
+        a1 = sess.query(A).first()
+
+        with self.sql_execution_asserter() as asserter:
+            # note this is many-to-one.  use_get is unconditionally turned
+            # off for relationship to aliased class for now.
+            eq_(a1.b, B(id=1))
+
+        asserter.assert_(
+            CompiledSQL(
+                "SELECT b.id AS b_id FROM b JOIN d ON d.b_id = b.id "
+                "JOIN c ON c.id = d.c_id WHERE :param_1 = b.id",
+                [{"param_1": 1}],
+            )
+        )
+
+    def test_joinedload(self):
+        A, B = self.classes("A", "B")
+
+        sess = Session()
+
+        with self.sql_execution_asserter() as asserter:
+            # note this is many-to-one.  use_get is unconditionally turned
+            # off for relationship to aliased class for now.
+            a1 = sess.query(A).options(joinedload(A.b)).first()
+            eq_(a1.b, B(id=1))
+
+        asserter.assert_(
+            CompiledSQL(
+                "SELECT a.id AS a_id, a.b_id AS a_b_id, b_1.id AS b_1_id "
+                "FROM a LEFT OUTER JOIN (b AS b_1 "
+                "JOIN d AS d_1 ON d_1.b_id = b_1.id "
+                "JOIN c AS c_1 ON c_1.id = d_1.c_id) ON a.b_id = b_1.id "
+                "LIMIT :param_1",
+                [{"param_1": 1}],
+            )
+        )
+
+    def test_selectinload(self):
+        A, B = self.classes("A", "B")
+
+        sess = Session()
+
+        with self.sql_execution_asserter() as asserter:
+            # note this is many-to-one.  use_get is unconditionally turned
+            # off for relationship to aliased class for now.
+            a1 = sess.query(A).options(selectinload(A.b)).first()
+            eq_(a1.b, B(id=1))
+
+        asserter.assert_(
+            CompiledSQL(
+                "SELECT a.id AS a_id, a.b_id AS a_b_id "
+                "FROM a LIMIT :param_1",
+                [{"param_1": 1}],
+            ),
+            CompiledSQL(
+                "SELECT a_1.id AS a_1_id, b.id AS b_id FROM a AS a_1 "
+                "JOIN (b JOIN d ON d.b_id = b.id JOIN c ON c.id = d.c_id) "
+                "ON a_1.b_id = b.id WHERE a_1.id "
+                "IN ([EXPANDING_primary_keys]) ORDER BY a_1.id",
+                [{"primary_keys": [1]}],
+            ),
+        )
+
+    def test_join(self):
+        A, B = self.classes("A", "B")
+
+        sess = Session()
+
+        self.assert_compile(
+            sess.query(A).join(A.b),
+            "SELECT a.id AS a_id, a.b_id AS a_b_id "
+            "FROM a JOIN (b JOIN d ON d.b_id = b.id "
+            "JOIN c ON c.id = d.c_id) ON a.b_id = b.id",
+        )
index 04dff0252b47a25570e1b3c3f41c00cfd8d12008..7ce05345bb12c1929c92994cef8be29e424dbc9c 100644 (file)
@@ -2162,3 +2162,369 @@ class InstrumentationTest(fixtures.ORMTest):
         l2 = Collection()
         f1.attr = l2
         eq_(canary, [adapter_1, f1.attr._sa_adapter, None])
+
+
+class NonPrimaryRelationshipLoaderTest(_fixtures.FixtureTest):
+    run_inserts = "once"
+    run_deletes = None
+
+    def test_selectload(self):
+        """tests lazy loading with two relationships simultaneously,
+        from the same table, using aliases.  """
+
+        users, orders, User, Address, Order, addresses = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+        )
+
+        openorders = sa.alias(orders, "openorders")
+        closedorders = sa.alias(orders, "closedorders")
+
+        mapper(Address, addresses)
+
+        mapper(Order, orders)
+
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            open_mapper = mapper(Order, openorders, non_primary=True)
+            closed_mapper = mapper(Order, closedorders, non_primary=True)
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(Address, lazy=True),
+                open_orders=relationship(
+                    open_mapper,
+                    primaryjoin=sa.and_(
+                        openorders.c.isopen == 1,
+                        users.c.id == openorders.c.user_id,
+                    ),
+                    lazy="select",
+                ),
+                closed_orders=relationship(
+                    closed_mapper,
+                    primaryjoin=sa.and_(
+                        closedorders.c.isopen == 0,
+                        users.c.id == closedorders.c.user_id,
+                    ),
+                    lazy="select",
+                ),
+            ),
+        )
+
+        self._run_double_test(10)
+
+    def test_joinedload(self):
+        """Eager loading with two relationships simultaneously,
+            from the same table, using aliases."""
+
+        users, orders, User, Address, Order, addresses = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+        )
+
+        openorders = sa.alias(orders, "openorders")
+        closedorders = sa.alias(orders, "closedorders")
+
+        mapper(Address, addresses)
+        mapper(Order, orders)
+
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            open_mapper = mapper(Order, openorders, non_primary=True)
+            closed_mapper = mapper(Order, closedorders, non_primary=True)
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    Address, lazy="joined", order_by=addresses.c.id
+                ),
+                open_orders=relationship(
+                    open_mapper,
+                    primaryjoin=sa.and_(
+                        openorders.c.isopen == 1,
+                        users.c.id == openorders.c.user_id,
+                    ),
+                    lazy="joined",
+                    order_by=openorders.c.id,
+                ),
+                closed_orders=relationship(
+                    closed_mapper,
+                    primaryjoin=sa.and_(
+                        closedorders.c.isopen == 0,
+                        users.c.id == closedorders.c.user_id,
+                    ),
+                    lazy="joined",
+                    order_by=closedorders.c.id,
+                ),
+            ),
+        )
+        self._run_double_test(1)
+
+    def test_selectin(self):
+
+        users, orders, User, Address, Order, addresses = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+        )
+
+        openorders = sa.alias(orders, "openorders")
+        closedorders = sa.alias(orders, "closedorders")
+
+        mapper(Address, addresses)
+        mapper(Order, orders)
+
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            open_mapper = mapper(Order, openorders, non_primary=True)
+            closed_mapper = mapper(Order, closedorders, non_primary=True)
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    Address, lazy="selectin", order_by=addresses.c.id
+                ),
+                open_orders=relationship(
+                    open_mapper,
+                    primaryjoin=sa.and_(
+                        openorders.c.isopen == 1,
+                        users.c.id == openorders.c.user_id,
+                    ),
+                    lazy="selectin",
+                    order_by=openorders.c.id,
+                ),
+                closed_orders=relationship(
+                    closed_mapper,
+                    primaryjoin=sa.and_(
+                        closedorders.c.isopen == 0,
+                        users.c.id == closedorders.c.user_id,
+                    ),
+                    lazy="selectin",
+                    order_by=closedorders.c.id,
+                ),
+            ),
+        )
+
+        self._run_double_test(4)
+
+    def test_subqueryload(self):
+
+        users, orders, User, Address, Order, addresses = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+        )
+
+        openorders = sa.alias(orders, "openorders")
+        closedorders = sa.alias(orders, "closedorders")
+
+        mapper(Address, addresses)
+        mapper(Order, orders)
+
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            open_mapper = mapper(Order, openorders, non_primary=True)
+            closed_mapper = mapper(Order, closedorders, non_primary=True)
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    Address, lazy="subquery", order_by=addresses.c.id
+                ),
+                open_orders=relationship(
+                    open_mapper,
+                    primaryjoin=sa.and_(
+                        openorders.c.isopen == 1,
+                        users.c.id == openorders.c.user_id,
+                    ),
+                    lazy="subquery",
+                    order_by=openorders.c.id,
+                ),
+                closed_orders=relationship(
+                    closed_mapper,
+                    primaryjoin=sa.and_(
+                        closedorders.c.isopen == 0,
+                        users.c.id == closedorders.c.user_id,
+                    ),
+                    lazy="subquery",
+                    order_by=closedorders.c.id,
+                ),
+            ),
+        )
+
+        self._run_double_test(4)
+
+    def _run_double_test(self, count):
+        User, Address, Order, Item = self.classes(
+            "User", "Address", "Order", "Item"
+        )
+        q = create_session().query(User).order_by(User.id)
+
+        def go():
+            eq_(
+                [
+                    User(
+                        id=7,
+                        addresses=[Address(id=1)],
+                        open_orders=[Order(id=3)],
+                        closed_orders=[Order(id=1), Order(id=5)],
+                    ),
+                    User(
+                        id=8,
+                        addresses=[
+                            Address(id=2),
+                            Address(id=3),
+                            Address(id=4),
+                        ],
+                        open_orders=[],
+                        closed_orders=[],
+                    ),
+                    User(
+                        id=9,
+                        addresses=[Address(id=5)],
+                        open_orders=[Order(id=4)],
+                        closed_orders=[Order(id=2)],
+                    ),
+                    User(id=10),
+                ],
+                q.all(),
+            )
+
+        self.assert_sql_count(testing.db, go, count)
+
+        sess = create_session()
+        user = sess.query(User).get(7)
+
+        closed_mapper = User.closed_orders.entity
+        open_mapper = User.open_orders.entity
+        eq_(
+            [Order(id=1), Order(id=5)],
+            create_session()
+            .query(closed_mapper)
+            .with_parent(user, property="closed_orders")
+            .all(),
+        )
+        eq_(
+            [Order(id=3)],
+            create_session()
+            .query(open_mapper)
+            .with_parent(user, property="open_orders")
+            .all(),
+        )
+
+
+class NonPrimaryMapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
+    __dialect__ = "default"
+
+    def test_non_primary_identity_class(self):
+        User = self.classes.User
+        users, addresses = self.tables.users, self.tables.addresses
+
+        class AddressUser(User):
+            pass
+
+        m1 = mapper(User, users, polymorphic_identity="user")
+        m2 = mapper(
+            AddressUser,
+            addresses,
+            inherits=User,
+            polymorphic_identity="address",
+            properties={"address_id": addresses.c.id},
+        )
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            m3 = mapper(AddressUser, addresses, non_primary=True)
+        assert m3._identity_class is m2._identity_class
+        eq_(
+            m2.identity_key_from_instance(AddressUser()),
+            m3.identity_key_from_instance(AddressUser()),
+        )
+
+    def test_illegal_non_primary(self):
+        users, Address, addresses, User = (
+            self.tables.users,
+            self.classes.Address,
+            self.tables.addresses,
+            self.classes.User,
+        )
+
+        mapper(User, users)
+        mapper(Address, addresses)
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            mapper(
+                User,
+                users,
+                non_primary=True,
+                properties={"addresses": relationship(Address)},
+            )
+        assert_raises_message(
+            sa.exc.ArgumentError,
+            "Attempting to assign a new relationship 'addresses' "
+            "to a non-primary mapper on class 'User'",
+            configure_mappers,
+        )
+
+    def test_illegal_non_primary_2(self):
+        User, users = self.classes.User, self.tables.users
+
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            assert_raises_message(
+                sa.exc.InvalidRequestError,
+                "Configure a primary mapper first",
+                mapper,
+                User,
+                users,
+                non_primary=True,
+            )
+
+    def test_illegal_non_primary_3(self):
+        users, addresses = self.tables.users, self.tables.addresses
+
+        class Base(object):
+            pass
+
+        class Sub(Base):
+            pass
+
+        mapper(Base, users)
+        with testing.expect_deprecated(
+            "The mapper.non_primary parameter is deprecated"
+        ):
+            assert_raises_message(
+                sa.exc.InvalidRequestError,
+                "Configure a primary mapper first",
+                mapper,
+                Sub,
+                addresses,
+                non_primary=True,
+            )
index fd272b1816aecf8f5084b426fed1ca0f88c38074..71010f02b66b5e24f4c92c14d6a292333f254e30 100644 (file)
@@ -718,27 +718,49 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
             sess.query(User).order_by(User.id).all(),
         )
 
-    def test_double(self):
+    def test_double_w_ac(self):
         """Eager loading with two relationships simultaneously,
             from the same table, using aliases."""
 
-        users, orders, User, Address, Order, addresses = (
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
             self.tables.users,
             self.tables.orders,
             self.classes.User,
             self.classes.Address,
             self.classes.Order,
             self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
         )
 
-        openorders = sa.alias(orders, "openorders")
-        closedorders = sa.alias(orders, "closedorders")
-
         mapper(Address, addresses)
-        mapper(Order, orders)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="joined",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
 
-        open_mapper = mapper(Order, openorders, non_primary=True)
-        closed_mapper = mapper(Order, closedorders, non_primary=True)
+        open_mapper = aliased(Order, orders)
+        closed_mapper = aliased(Order, orders)
 
         mapper(
             User,
@@ -750,57 +772,91 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                 open_orders=relationship(
                     open_mapper,
                     primaryjoin=sa.and_(
-                        openorders.c.isopen == 1,
-                        users.c.id == openorders.c.user_id,
+                        open_mapper.isopen == 1,
+                        users.c.id == open_mapper.user_id,
                     ),
                     lazy="joined",
-                    order_by=openorders.c.id,
+                    order_by=open_mapper.id,
                 ),
                 closed_orders=relationship(
                     closed_mapper,
                     primaryjoin=sa.and_(
-                        closedorders.c.isopen == 0,
-                        users.c.id == closedorders.c.user_id,
+                        closed_mapper.isopen == 0,
+                        users.c.id == closed_mapper.user_id,
                     ),
                     lazy="joined",
-                    order_by=closedorders.c.id,
+                    order_by=closed_mapper.id,
                 ),
             ),
         )
 
-        q = create_session().query(User).order_by(User.id)
+        self._run_double_test()
 
-        def go():
-            eq_(
-                [
-                    User(
-                        id=7,
-                        addresses=[Address(id=1)],
-                        open_orders=[Order(id=3)],
-                        closed_orders=[Order(id=1), Order(id=5)],
-                    ),
-                    User(
-                        id=8,
-                        addresses=[
-                            Address(id=2),
-                            Address(id=3),
-                            Address(id=4),
-                        ],
-                        open_orders=[],
-                        closed_orders=[],
-                    ),
-                    User(
-                        id=9,
-                        addresses=[Address(id=5)],
-                        open_orders=[Order(id=4)],
-                        closed_orders=[Order(id=2)],
-                    ),
-                    User(id=10),
-                ],
-                q.all(),
-            )
+    def test_double_w_ac_against_subquery(self):
+        """Eager loading with two relationships simultaneously,
+            from the same table, using aliases."""
 
-        self.assert_sql_count(testing.db, go, 1)
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
+
+        mapper(Address, addresses)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="joined",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
+
+        open_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 1).alias()
+        )
+        closed_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 0).alias()
+        )
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    Address, lazy="joined", order_by=addresses.c.id
+                ),
+                open_orders=relationship(
+                    open_mapper, lazy="joined", order_by=open_mapper.id
+                ),
+                closed_orders=relationship(
+                    closed_mapper, lazy="joined", order_by=closed_mapper.id
+                ),
+            ),
+        )
+
+        self._run_double_test()
 
     def test_double_same_mappers(self):
         """Eager loading with two relationships simultaneously,
@@ -867,26 +923,30 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                 ),
             ),
         )
+        self._run_double_test()
+
+    def _run_double_test(self, no_items=False):
+        User, Address, Order, Item = self.classes(
+            "User", "Address", "Order", "Item"
+        )
         q = create_session().query(User).order_by(User.id)
 
+        def items(*ids):
+            if no_items:
+                return {}
+            else:
+                return {"items": [Item(id=id_) for id_ in ids]}
+
         def go():
             eq_(
                 [
                     User(
                         id=7,
                         addresses=[Address(id=1)],
-                        open_orders=[
-                            Order(
-                                id=3,
-                                items=[Item(id=3), Item(id=4), Item(id=5)],
-                            )
-                        ],
+                        open_orders=[Order(id=3, **items(3, 4, 5))],
                         closed_orders=[
-                            Order(
-                                id=1,
-                                items=[Item(id=1), Item(id=2), Item(id=3)],
-                            ),
-                            Order(id=5, items=[Item(id=5)]),
+                            Order(id=1, **items(1, 2, 3)),
+                            Order(id=5, **items(5)),
                         ],
                     ),
                     User(
@@ -902,15 +962,8 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                     User(
                         id=9,
                         addresses=[Address(id=5)],
-                        open_orders=[
-                            Order(id=4, items=[Item(id=1), Item(id=5)])
-                        ],
-                        closed_orders=[
-                            Order(
-                                id=2,
-                                items=[Item(id=1), Item(id=2), Item(id=3)],
-                            )
-                        ],
+                        open_orders=[Order(id=4, **items(1, 5))],
+                        closed_orders=[Order(id=2, **items(1, 2, 3))],
                     ),
                     User(id=10),
                 ],
@@ -1739,9 +1792,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                     Order, backref="user", lazy="joined", order_by=orders.c.id
                 ),
                 "max_order": relationship(
-                    mapper(Order, max_orders, non_primary=True),
-                    lazy="joined",
-                    uselist=False,
+                    aliased(Order, max_orders), lazy="joined", uselist=False
                 ),
             },
         )
@@ -3021,7 +3072,7 @@ class InnerJoinSplicingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
         )
         self._assert_result(q)
 
-    def test_splice_onto_np_mapper(self):
+    def test_splice_onto_ac(self):
         A = self.classes.A
         B = self.classes.B
         C1 = self.classes.C1
@@ -3032,20 +3083,7 @@ class InnerJoinSplicingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
 
         weird_selectable = b_table.outerjoin(c1_table)
 
-        b_np = mapper(
-            B,
-            weird_selectable,
-            non_primary=True,
-            properties=odict(
-                [
-                    # note we need to make this fixed with lazy=False until
-                    # [ticket:3348] is resolved
-                    ("c1s", relationship(C1, lazy=False, innerjoin=True)),
-                    ("c_id", c1_table.c.id),
-                    ("b_value", b_table.c.value),
-                ]
-            ),
-        )
+        b_np = aliased(B, weird_selectable, flat=True)
 
         a_mapper = inspect(A)
         a_mapper.add_property("bs_np", relationship(b_np))
@@ -3055,14 +3093,10 @@ class InnerJoinSplicingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
         q = s.query(A).options(joinedload("bs_np", innerjoin=False))
         self.assert_compile(
             q,
-            "SELECT a.id AS a_id, c1_1.id AS c1_1_id, c1_1.b_id AS c1_1_b_id, "
-            "c1_1.value AS c1_1_value, c1_2.id AS c1_2_id, "
-            "b_1.value AS b_1_value, b_1.id AS b_1_id, "
-            "b_1.a_id AS b_1_a_id, c1_2.b_id AS c1_2_b_id, "
-            "c1_2.value AS c1_2_value "
-            "FROM a LEFT OUTER JOIN "
-            "(b AS b_1 LEFT OUTER JOIN c1 AS c1_2 ON b_1.id = c1_2.b_id "
-            "JOIN c1 AS c1_1 ON b_1.id = c1_1.b_id) ON a.id = b_1.a_id",
+            "SELECT a.id AS a_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id, "
+            "b_1.value AS b_1_value FROM a LEFT OUTER JOIN "
+            "(b AS b_1 LEFT OUTER JOIN c1 AS c1_1 ON b_1.id = c1_1.b_id) "
+            "ON a.id = b_1.a_id",
         )
 
 
index c5e1d1485c318703239f0dc5165b5384c1a0e167..1cccfff2684e3646c8a8b4ee05ec85d9d61725e9 100644 (file)
@@ -2399,7 +2399,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL):
         ]:
             q = s.query(crit)
             mzero = q._entity_zero()
-            is_(mzero.mapped_table, q._query_entity_zero().selectable)
+            is_(mzero.persist_selectable, q._query_entity_zero().selectable)
             q = q.join(j)
             self.assert_compile(q, exp)
 
index ca127580dc4234fb131668bd8146143c2f859ecd..368199b96fab41d950202b4e9771df0507ba06f5 100644 (file)
@@ -48,11 +48,11 @@ class TestORMInspection(_fixtures.FixtureTest):
         insp = inspect(User)
         is_(insp.local_table, user_table)
 
-    def test_mapped_table(self):
+    def test_persist_selectable(self):
         User = self.classes.User
         user_table = self.tables.users
         insp = inspect(User)
-        is_(insp.mapped_table, user_table)
+        is_(insp.persist_selectable, user_table)
 
     def test_mapper_selectable(self):
         User = self.classes.User
index 046d5c3e22bf9f92c49d78ae106716aacfcd1b8d..bb9747cf2fcfb5527339e5d02690ec4797699a50 100644 (file)
@@ -2912,7 +2912,7 @@ class JoinToNonPolyAliasesTest(fixtures.MappedTest, AssertsCompiledSQL):
         mapper(Child, child)
 
         derived = select([child]).alias()
-        npc = mapper(Child, derived, non_primary=True)
+        npc = aliased(Child, derived)
         cls.npc = npc
         cls.derived = derived
         mp.add_property("npc", relationship(npc))
index 4d9e460f9ad5009735b8c48725a72bd5ecbd4909..78680701e0bdb266b623c4c3d81aefa6ba50639d 100644 (file)
@@ -10,9 +10,11 @@ from sqlalchemy import ForeignKey
 from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import Integer
 from sqlalchemy import orm
+from sqlalchemy import select
 from sqlalchemy import SmallInteger
 from sqlalchemy import String
 from sqlalchemy import testing
+from sqlalchemy.orm import aliased
 from sqlalchemy.orm import attributes
 from sqlalchemy.orm import configure_mappers
 from sqlalchemy.orm import create_session
@@ -530,28 +532,107 @@ class LazyTest(_fixtures.FixtureTest):
             list(q),
         )
 
-    def test_double(self):
-        """tests lazy loading with two relationships simultaneously,
-        from the same table, using aliases.  """
+    def test_double_w_ac_against_subquery(self):
 
-        users, orders, User, Address, Order, addresses = (
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
             self.tables.users,
             self.tables.orders,
             self.classes.User,
             self.classes.Address,
             self.classes.Order,
             self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
+
+        mapper(Address, addresses)
+
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="select",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
+
+        open_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 1).alias()
+        )
+        closed_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 0).alias()
+        )
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(Address, lazy=True),
+                open_orders=relationship(open_mapper, lazy="select"),
+                closed_orders=relationship(closed_mapper, lazy="select"),
+            ),
         )
 
-        openorders = sa.alias(orders, "openorders")
-        closedorders = sa.alias(orders, "closedorders")
+        self._run_double_test()
+
+    def test_double_w_ac(self):
+
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
 
         mapper(Address, addresses)
 
-        mapper(Order, orders)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="select",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
 
-        open_mapper = mapper(Order, openorders, non_primary=True)
-        closed_mapper = mapper(Order, closedorders, non_primary=True)
+        open_mapper = aliased(Order, orders)
+        closed_mapper = aliased(Order, orders)
         mapper(
             User,
             users,
@@ -560,47 +641,79 @@ class LazyTest(_fixtures.FixtureTest):
                 open_orders=relationship(
                     open_mapper,
                     primaryjoin=sa.and_(
-                        openorders.c.isopen == 1,
-                        users.c.id == openorders.c.user_id,
+                        open_mapper.isopen == 1,
+                        users.c.id == open_mapper.user_id,
                     ),
                     lazy="select",
                 ),
                 closed_orders=relationship(
                     closed_mapper,
                     primaryjoin=sa.and_(
-                        closedorders.c.isopen == 0,
-                        users.c.id == closedorders.c.user_id,
+                        closed_mapper.isopen == 0,
+                        users.c.id == closed_mapper.user_id,
                     ),
                     lazy="select",
                 ),
             ),
         )
-        q = create_session().query(User)
 
-        assert [
-            User(
-                id=7,
-                addresses=[Address(id=1)],
-                open_orders=[Order(id=3)],
-                closed_orders=[Order(id=1), Order(id=5)],
-            ),
-            User(
-                id=8,
-                addresses=[Address(id=2), Address(id=3), Address(id=4)],
-                open_orders=[],
-                closed_orders=[],
-            ),
-            User(
-                id=9,
-                addresses=[Address(id=5)],
-                open_orders=[Order(id=4)],
-                closed_orders=[Order(id=2)],
-            ),
-            User(id=10),
-        ] == q.all()
+        self._run_double_test()
+
+    def _run_double_test(self, no_items=False):
+        User, Address, Order, Item = self.classes(
+            "User", "Address", "Order", "Item"
+        )
+        q = create_session().query(User).order_by(User.id)
+
+        def items(*ids):
+            if no_items:
+                return {}
+            else:
+                return {"items": [Item(id=id_) for id_ in ids]}
+
+        def go():
+            eq_(
+                [
+                    User(
+                        id=7,
+                        addresses=[Address(id=1)],
+                        open_orders=[Order(id=3, **items(3, 4, 5))],
+                        closed_orders=[
+                            Order(id=1, **items(1, 2, 3)),
+                            Order(id=5, **items(5)),
+                        ],
+                    ),
+                    User(
+                        id=8,
+                        addresses=[
+                            Address(id=2),
+                            Address(id=3),
+                            Address(id=4),
+                        ],
+                        open_orders=[],
+                        closed_orders=[],
+                    ),
+                    User(
+                        id=9,
+                        addresses=[Address(id=5)],
+                        open_orders=[Order(id=4, **items(1, 5))],
+                        closed_orders=[Order(id=2, **items(1, 2, 3))],
+                    ),
+                    User(id=10),
+                ],
+                q.all(),
+            )
+
+        if no_items:
+            self.assert_sql_count(testing.db, go, 10)
+        else:
+            self.assert_sql_count(testing.db, go, 15)
 
         sess = create_session()
         user = sess.query(User).get(7)
+
+        closed_mapper = User.closed_orders.entity
+        open_mapper = User.open_orders.entity
         eq_(
             [Order(id=1), Order(id=5)],
             create_session()
index 4e42e4f9aa9f56a75fe4917bea835f5f43313650..06929c6bc3dc441b291441c30ee293b94ac47e63 100644 (file)
@@ -853,28 +853,6 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
         assert n1.children[0] is n1._children[0] is n2
         eq_(str(Node.parent == n2), ":param_1 = nodes.parent_id")
 
-    def test_non_primary_identity_class(self):
-        User = self.classes.User
-        users, addresses = self.tables.users, self.tables.addresses
-
-        class AddressUser(User):
-            pass
-
-        m1 = mapper(User, users, polymorphic_identity="user")
-        m2 = mapper(
-            AddressUser,
-            addresses,
-            inherits=User,
-            polymorphic_identity="address",
-            properties={"address_id": addresses.c.id},
-        )
-        m3 = mapper(AddressUser, addresses, non_primary=True)
-        assert m3._identity_class is m2._identity_class
-        eq_(
-            m2.identity_key_from_instance(AddressUser()),
-            m3.identity_key_from_instance(AddressUser()),
-        )
-
     def test_reassign_polymorphic_identity_warns(self):
         User = self.classes.User
         users = self.tables.users
@@ -898,60 +876,6 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
             polymorphic_identity="user",
         )
 
-    def test_illegal_non_primary(self):
-        users, Address, addresses, User = (
-            self.tables.users,
-            self.classes.Address,
-            self.tables.addresses,
-            self.classes.User,
-        )
-
-        mapper(User, users)
-        mapper(Address, addresses)
-        mapper(
-            User,
-            users,
-            non_primary=True,
-            properties={"addresses": relationship(Address)},
-        )
-        assert_raises_message(
-            sa.exc.ArgumentError,
-            "Attempting to assign a new relationship 'addresses' "
-            "to a non-primary mapper on class 'User'",
-            configure_mappers,
-        )
-
-    def test_illegal_non_primary_2(self):
-        User, users = self.classes.User, self.tables.users
-
-        assert_raises_message(
-            sa.exc.InvalidRequestError,
-            "Configure a primary mapper first",
-            mapper,
-            User,
-            users,
-            non_primary=True,
-        )
-
-    def test_illegal_non_primary_3(self):
-        users, addresses = self.tables.users, self.tables.addresses
-
-        class Base(object):
-            pass
-
-        class Sub(Base):
-            pass
-
-        mapper(Base, users)
-        assert_raises_message(
-            sa.exc.InvalidRequestError,
-            "Configure a primary mapper first",
-            mapper,
-            Sub,
-            addresses,
-            non_primary=True,
-        )
-
     def test_prop_filters(self):
         t = Table(
             "person",
index 8fd521ae8c323de465d9af6000efe08de3e3bf0c..5e6ac53fe8582858dc07501b21c3a51e56b7d1ce 100644 (file)
@@ -1120,46 +1120,53 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
 
     def test_join_targets_o2m_selfref(self):
         joincond = self._join_fixture_o2m_selfref()
-        left = select([joincond.parent_selectable]).alias("pj")
+        left = select([joincond.parent_persist_selectable]).alias("pj")
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            left, joincond.child_selectable, True
+            left, joincond.child_persist_selectable, True
         )
         self.assert_compile(pj, "pj.id = selfref.sid")
+        self.assert_compile(pj, "pj.id = selfref.sid")
 
-        right = select([joincond.child_selectable]).alias("pj")
+        right = select([joincond.child_persist_selectable]).alias("pj")
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            joincond.parent_selectable, right, True
+            joincond.parent_persist_selectable, right, True
         )
         self.assert_compile(pj, "selfref.id = pj.sid")
+        self.assert_compile(pj, "selfref.id = pj.sid")
 
     def test_join_targets_o2m_plain(self):
         joincond = self._join_fixture_o2m()
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            joincond.parent_selectable, joincond.child_selectable, False
+            joincond.parent_persist_selectable,
+            joincond.child_persist_selectable,
+            False,
         )
         self.assert_compile(pj, "lft.id = rgt.lid")
+        self.assert_compile(pj, "lft.id = rgt.lid")
 
     def test_join_targets_o2m_left_aliased(self):
         joincond = self._join_fixture_o2m()
-        left = select([joincond.parent_selectable]).alias("pj")
+        left = select([joincond.parent_persist_selectable]).alias("pj")
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            left, joincond.child_selectable, True
+            left, joincond.child_persist_selectable, True
         )
         self.assert_compile(pj, "pj.id = rgt.lid")
+        self.assert_compile(pj, "pj.id = rgt.lid")
 
     def test_join_targets_o2m_right_aliased(self):
         joincond = self._join_fixture_o2m()
-        right = select([joincond.child_selectable]).alias("pj")
+        right = select([joincond.child_persist_selectable]).alias("pj")
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            joincond.parent_selectable, right, True
+            joincond.parent_persist_selectable, right, True
         )
         self.assert_compile(pj, "lft.id = pj.lid")
+        self.assert_compile(pj, "lft.id = pj.lid")
 
     def test_join_targets_o2m_composite_selfref(self):
         joincond = self._join_fixture_o2m_composite_selfref()
-        right = select([joincond.child_selectable]).alias("pj")
+        right = select([joincond.child_persist_selectable]).alias("pj")
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            joincond.parent_selectable, right, True
+            joincond.parent_persist_selectable, right, True
         )
         self.assert_compile(
             pj,
@@ -1169,9 +1176,9 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
 
     def test_join_targets_m2o_composite_selfref(self):
         joincond = self._join_fixture_m2o_composite_selfref()
-        right = select([joincond.child_selectable]).alias("pj")
+        right = select([joincond.child_persist_selectable]).alias("pj")
         pj, sj, sec, adapter, ds = joincond.join_targets(
-            joincond.parent_selectable, right, True
+            joincond.parent_persist_selectable, right, True
         )
         self.assert_compile(
             pj,
index 1fc2f3f16a8868b2db2c650182b87fa7bba8f072..35d32a444f87917853cf5842c3a04bf32cd07e19 100644 (file)
@@ -74,7 +74,7 @@ class SelectableNoFromsTest(fixtures.MappedTest, AssertsCompiledSQL):
         Subset, common = self.classes.Subset, self.tables.common
 
         subset_select = select([common.c.id, common.c.data]).alias()
-        subset_mapper = mapper(Subset, subset_select)
+        mapper(Subset, subset_select)
         sess = Session(bind=testing.db)
         sess.add(Subset(data=1))
         sess.flush()
@@ -84,7 +84,7 @@ class SelectableNoFromsTest(fixtures.MappedTest, AssertsCompiledSQL):
         eq_(sess.query(Subset).filter(Subset.data == 1).one(), Subset(data=1))
         eq_(sess.query(Subset).filter(Subset.data != 1).first(), None)
 
-        subset_select = sa.orm.class_mapper(Subset).mapped_table
+        subset_select = sa.orm.class_mapper(Subset).persist_selectable
         eq_(
             sess.query(Subset).filter(subset_select.c.data == 1).one(),
             Subset(data=1),
index b891835d1f0ba163ae93b5920a301ab4dc64abf7..e95fbb0503cf8e2d796f5d53e3816358223e1f4b 100644 (file)
@@ -2,6 +2,7 @@ import sqlalchemy as sa
 from sqlalchemy import bindparam
 from sqlalchemy import ForeignKey
 from sqlalchemy import Integer
+from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import testing
 from sqlalchemy.orm import aliased
@@ -776,27 +777,111 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
             sess.query(User).order_by(User.id).all(),
         )
 
-    def test_double(self):
-        """Eager loading with two relationships simultaneously,
-            from the same table, using aliases."""
+    def test_double_w_ac_against_subquery(self):
 
-        users, orders, User, Address, Order, addresses = (
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
             self.tables.users,
             self.tables.orders,
             self.classes.User,
             self.classes.Address,
             self.classes.Order,
             self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
+
+        mapper(Address, addresses)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="selectin",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
+
+        open_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 1).alias()
         )
+        closed_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 0).alias()
+        )
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    Address, lazy="selectin", order_by=addresses.c.id
+                ),
+                open_orders=relationship(
+                    open_mapper, lazy="selectin", order_by=open_mapper.id
+                ),
+                closed_orders=relationship(
+                    closed_mapper, lazy="selectin", order_by=closed_mapper.id
+                ),
+            ),
+        )
+
+        self._run_double_test()
 
-        openorders = sa.alias(orders, "openorders")
-        closedorders = sa.alias(orders, "closedorders")
+    def test_double_w_ac(self):
+
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
 
         mapper(Address, addresses)
-        mapper(Order, orders)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="selectin",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
 
-        open_mapper = mapper(Order, openorders, non_primary=True)
-        closed_mapper = mapper(Order, closedorders, non_primary=True)
+        open_mapper = aliased(Order, orders)
+        closed_mapper = aliased(Order, orders)
 
         mapper(
             User,
@@ -808,57 +893,25 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                 open_orders=relationship(
                     open_mapper,
                     primaryjoin=sa.and_(
-                        openorders.c.isopen == 1,
-                        users.c.id == openorders.c.user_id,
+                        open_mapper.isopen == 1,
+                        users.c.id == open_mapper.user_id,
                     ),
                     lazy="selectin",
-                    order_by=openorders.c.id,
+                    order_by=open_mapper.id,
                 ),
                 closed_orders=relationship(
                     closed_mapper,
                     primaryjoin=sa.and_(
-                        closedorders.c.isopen == 0,
-                        users.c.id == closedorders.c.user_id,
+                        closed_mapper.isopen == 0,
+                        users.c.id == closed_mapper.user_id,
                     ),
                     lazy="selectin",
-                    order_by=closedorders.c.id,
+                    order_by=closed_mapper.id,
                 ),
             ),
         )
 
-        q = create_session().query(User).order_by(User.id)
-
-        def go():
-            eq_(
-                [
-                    User(
-                        id=7,
-                        addresses=[Address(id=1)],
-                        open_orders=[Order(id=3)],
-                        closed_orders=[Order(id=1), Order(id=5)],
-                    ),
-                    User(
-                        id=8,
-                        addresses=[
-                            Address(id=2),
-                            Address(id=3),
-                            Address(id=4),
-                        ],
-                        open_orders=[],
-                        closed_orders=[],
-                    ),
-                    User(
-                        id=9,
-                        addresses=[Address(id=5)],
-                        open_orders=[Order(id=4)],
-                        closed_orders=[Order(id=2)],
-                    ),
-                    User(id=10),
-                ],
-                q.all(),
-            )
-
-        self.assert_sql_count(testing.db, go, 4)
+        self._run_double_test()
 
     def test_double_same_mappers(self):
         """Eager loading with two relationships simultaneously,
@@ -925,26 +978,31 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                 ),
             ),
         )
+
+        self._run_double_test()
+
+    def _run_double_test(self, no_items=False):
+        User, Address, Order, Item = self.classes(
+            "User", "Address", "Order", "Item"
+        )
         q = create_session().query(User).order_by(User.id)
 
+        def items(*ids):
+            if no_items:
+                return {}
+            else:
+                return {"items": [Item(id=id_) for id_ in ids]}
+
         def go():
             eq_(
                 [
                     User(
                         id=7,
                         addresses=[Address(id=1)],
-                        open_orders=[
-                            Order(
-                                id=3,
-                                items=[Item(id=3), Item(id=4), Item(id=5)],
-                            )
-                        ],
+                        open_orders=[Order(id=3, **items(3, 4, 5))],
                         closed_orders=[
-                            Order(
-                                id=1,
-                                items=[Item(id=1), Item(id=2), Item(id=3)],
-                            ),
-                            Order(id=5, items=[Item(id=5)]),
+                            Order(id=1, **items(1, 2, 3)),
+                            Order(id=5, **items(5)),
                         ],
                     ),
                     User(
@@ -960,22 +1018,18 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                     User(
                         id=9,
                         addresses=[Address(id=5)],
-                        open_orders=[
-                            Order(id=4, items=[Item(id=1), Item(id=5)])
-                        ],
-                        closed_orders=[
-                            Order(
-                                id=2,
-                                items=[Item(id=1), Item(id=2), Item(id=3)],
-                            )
-                        ],
+                        open_orders=[Order(id=4, **items(1, 5))],
+                        closed_orders=[Order(id=2, **items(1, 2, 3))],
                     ),
                     User(id=10),
                 ],
                 q.all(),
             )
 
-        self.assert_sql_count(testing.db, go, 6)
+        if no_items:
+            self.assert_sql_count(testing.db, go, 4)
+        else:
+            self.assert_sql_count(testing.db, go, 6)
 
     def test_limit(self):
         """Limit operations combined with lazy-load relationships."""
@@ -1119,9 +1173,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                     order_by=orders.c.id,
                 ),
                 "max_order": relationship(
-                    mapper(Order, max_orders, non_primary=True),
-                    lazy="selectin",
-                    uselist=False,
+                    aliased(Order, max_orders), lazy="selectin", uselist=False
                 ),
             },
         )
index b4be6debe4c0d3d6e3786fb02c584d39f455a445..117ab5be4197b51ff85a462eb9f38726feb1f6a8 100644 (file)
@@ -3,6 +3,7 @@ from sqlalchemy import bindparam
 from sqlalchemy import ForeignKey
 from sqlalchemy import inspect
 from sqlalchemy import Integer
+from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import testing
 from sqlalchemy.orm import aliased
@@ -796,27 +797,111 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
             "1 FROM users",
         )
 
-    def test_double(self):
-        """Eager loading with two relationships simultaneously,
-            from the same table, using aliases."""
+    def test_double_w_ac_against_subquery(self):
 
-        users, orders, User, Address, Order, addresses = (
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
             self.tables.users,
             self.tables.orders,
             self.classes.User,
             self.classes.Address,
             self.classes.Order,
             self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
+
+        mapper(Address, addresses)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="subquery",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
+
+        open_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 1).alias()
         )
+        closed_mapper = aliased(
+            Order, select([orders]).where(orders.c.isopen == 0).alias()
+        )
+
+        mapper(
+            User,
+            users,
+            properties=dict(
+                addresses=relationship(
+                    Address, lazy="subquery", order_by=addresses.c.id
+                ),
+                open_orders=relationship(
+                    open_mapper, lazy="subquery", order_by=open_mapper.id
+                ),
+                closed_orders=relationship(
+                    closed_mapper, lazy="subquery", order_by=closed_mapper.id
+                ),
+            ),
+        )
+
+        self._run_double_test()
 
-        openorders = sa.alias(orders, "openorders")
-        closedorders = sa.alias(orders, "closedorders")
+    def test_double_w_ac(self):
+
+        (
+            users,
+            orders,
+            User,
+            Address,
+            Order,
+            addresses,
+            Item,
+            items,
+            order_items,
+        ) = (
+            self.tables.users,
+            self.tables.orders,
+            self.classes.User,
+            self.classes.Address,
+            self.classes.Order,
+            self.tables.addresses,
+            self.classes.Item,
+            self.tables.items,
+            self.tables.order_items,
+        )
 
         mapper(Address, addresses)
-        mapper(Order, orders)
+        mapper(
+            Order,
+            orders,
+            properties={
+                "items": relationship(
+                    Item,
+                    secondary=order_items,
+                    lazy="subquery",
+                    order_by=items.c.id,
+                )
+            },
+        )
+        mapper(Item, items)
 
-        open_mapper = mapper(Order, openorders, non_primary=True)
-        closed_mapper = mapper(Order, closedorders, non_primary=True)
+        open_mapper = aliased(Order, orders)
+        closed_mapper = aliased(Order, orders)
 
         mapper(
             User,
@@ -828,57 +913,25 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                 open_orders=relationship(
                     open_mapper,
                     primaryjoin=sa.and_(
-                        openorders.c.isopen == 1,
-                        users.c.id == openorders.c.user_id,
+                        open_mapper.isopen == 1,
+                        users.c.id == open_mapper.user_id,
                     ),
                     lazy="subquery",
-                    order_by=openorders.c.id,
+                    order_by=open_mapper.id,
                 ),
                 closed_orders=relationship(
                     closed_mapper,
                     primaryjoin=sa.and_(
-                        closedorders.c.isopen == 0,
-                        users.c.id == closedorders.c.user_id,
+                        closed_mapper.isopen == 0,
+                        users.c.id == closed_mapper.user_id,
                     ),
                     lazy="subquery",
-                    order_by=closedorders.c.id,
+                    order_by=closed_mapper.id,
                 ),
             ),
         )
 
-        q = create_session().query(User).order_by(User.id)
-
-        def go():
-            eq_(
-                [
-                    User(
-                        id=7,
-                        addresses=[Address(id=1)],
-                        open_orders=[Order(id=3)],
-                        closed_orders=[Order(id=1), Order(id=5)],
-                    ),
-                    User(
-                        id=8,
-                        addresses=[
-                            Address(id=2),
-                            Address(id=3),
-                            Address(id=4),
-                        ],
-                        open_orders=[],
-                        closed_orders=[],
-                    ),
-                    User(
-                        id=9,
-                        addresses=[Address(id=5)],
-                        open_orders=[Order(id=4)],
-                        closed_orders=[Order(id=2)],
-                    ),
-                    User(id=10),
-                ],
-                q.all(),
-            )
-
-        self.assert_sql_count(testing.db, go, 4)
+        self._run_double_test()
 
     def test_double_same_mappers(self):
         """Eager loading with two relationships simultaneously,
@@ -945,26 +998,30 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                 ),
             ),
         )
+        self._run_double_test()
+
+    def _run_double_test(self, no_items=False):
+        User, Address, Order, Item = self.classes(
+            "User", "Address", "Order", "Item"
+        )
         q = create_session().query(User).order_by(User.id)
 
+        def items(*ids):
+            if no_items:
+                return {}
+            else:
+                return {"items": [Item(id=id_) for id_ in ids]}
+
         def go():
             eq_(
                 [
                     User(
                         id=7,
                         addresses=[Address(id=1)],
-                        open_orders=[
-                            Order(
-                                id=3,
-                                items=[Item(id=3), Item(id=4), Item(id=5)],
-                            )
-                        ],
+                        open_orders=[Order(id=3, **items(3, 4, 5))],
                         closed_orders=[
-                            Order(
-                                id=1,
-                                items=[Item(id=1), Item(id=2), Item(id=3)],
-                            ),
-                            Order(id=5, items=[Item(id=5)]),
+                            Order(id=1, **items(1, 2, 3)),
+                            Order(id=5, **items(5)),
                         ],
                     ),
                     User(
@@ -980,22 +1037,18 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                     User(
                         id=9,
                         addresses=[Address(id=5)],
-                        open_orders=[
-                            Order(id=4, items=[Item(id=1), Item(id=5)])
-                        ],
-                        closed_orders=[
-                            Order(
-                                id=2,
-                                items=[Item(id=1), Item(id=2), Item(id=3)],
-                            )
-                        ],
+                        open_orders=[Order(id=4, **items(1, 5))],
+                        closed_orders=[Order(id=2, **items(1, 2, 3))],
                     ),
                     User(id=10),
                 ],
                 q.all(),
             )
 
-        self.assert_sql_count(testing.db, go, 6)
+        if no_items:
+            self.assert_sql_count(testing.db, go, 4)
+        else:
+            self.assert_sql_count(testing.db, go, 6)
 
     def test_limit(self):
         """Limit operations combined with lazy-load relationships."""
@@ -1139,9 +1192,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
                     order_by=orders.c.id,
                 ),
                 "max_order": relationship(
-                    mapper(Order, max_orders, non_primary=True),
-                    lazy="subquery",
-                    uselist=False,
+                    aliased(Order, max_orders), lazy="subquery", uselist=False
                 ),
             },
         )
index f7a735e21598d47ed952954a4a5056c9b68c9854..6326f5f1a5c5a3eea88c92aa693c21e0361d286f 100644 (file)
@@ -2660,17 +2660,13 @@ class ManyToManyTest(_fixtures.FixtureTest):
 
         mapper(Keyword, keywords)
 
-        # note that we are breaking a rule here, making a second
-        # mapper(Keyword, keywords) the reorganization of mapper construction
-        # affected this, but was fixed again
-
         mapper(
             IKAssociation,
             item_keywords,
             primary_key=[item_keywords.c.item_id, item_keywords.c.keyword_id],
             properties=dict(
                 keyword=relationship(
-                    mapper(Keyword, keywords, non_primary=True),
+                    Keyword,
                     lazy="joined",
                     uselist=False,
                     # note here is a valid place where
index c265bb3c9d783bfa72ed9787545c542540e5435c..b9d61ed56b0e396222ed0b57ffd15eda363e56ff 100644 (file)
@@ -596,7 +596,7 @@ class DefaultRequirements(SuiteRequirements):
     @property
     def window_functions(self):
         return only_if(
-            ["postgresql>=8.4", "mssql", "oracle"],
+            ["postgresql>=8.4", "mssql", "oracle", "sqlite>=3.25.0"],
             "Backend does not support window functions",
         )