]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add deprecation warning for .join().alias()
authorGord Thompson <gord@gordthompson.com>
Sun, 13 Sep 2020 18:37:40 +0000 (12:37 -0600)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 28 Sep 2020 16:08:04 +0000 (12:08 -0400)
The :meth:`_sql.Join.alias` method is deprecated and will be removed in
SQLAlchemy 2.0.   An explicit select + subquery, or aliasing of the inner
tables, should be used instead.

Fixes: #5010
Change-Id: Ic913afc31f0d70b0605f9a7af2742a0de1f9ad19

13 files changed:
doc/build/changelog/unreleased_14/5010.rst [new file with mode: 0644]
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/sql/coercions.py
lib/sqlalchemy/sql/roles.py
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/testing/assertions.py
test/orm/inheritance/test_poly_linked_list.py
test/orm/inheritance/test_selects.py
test/sql/test_deprecations.py
test/sql/test_external_traversal.py
test/sql/test_selectable.py

diff --git a/doc/build/changelog/unreleased_14/5010.rst b/doc/build/changelog/unreleased_14/5010.rst
new file mode 100644 (file)
index 0000000..3197697
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: change, sql
+    :tickets: 5010
+
+    The :meth:`_sql.Join.alias` method is deprecated and will be removed in
+    SQLAlchemy 2.0.   An explicit select + subquery, or aliasing of the inner
+    tables, should be used instead.
+
index 1c95b6e06472d4230aba136fc4ede30c19964ff8..cd1502073e11c1169651e302dc4bbe9ebf6bcdd6 100644 (file)
@@ -3636,11 +3636,12 @@ class JoinCondition(object):
 
 
 class _ColInAnnotations(object):
-    """Seralizable equivalent to:
+    """Seralizable object that tests for a name in c._annotations.
 
-        lambda c: "name" in c._annotations
     """
 
+    __slots__ = ("name",)
+
     def __init__(self, name):
         self.name = name
 
index fbf153dc5fa825717088a5c2180942e15340aabe..325bd4dc1e7570e3f2c3454c0b48785067260bc9 100644 (file)
@@ -1929,7 +1929,7 @@ class JoinedLoader(AbstractRelationshipLoader):
         if idx >= len(self._aliased_class_pool):
             to_adapt = orm_util.AliasedClass(
                 self.mapper,
-                alias=alt_selectable.alias(flat=True)
+                alias=alt_selectable._anonymous_fromclause(flat=True)
                 if alt_selectable is not None
                 else None,
                 flat=True,
@@ -2690,7 +2690,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             ).apply_labels(),
             lambda_cache=self._query_cache,
             global_track_bound_values=False,
-            track_on=(self, effective_entity,) + tuple(pk_cols),
+            track_on=(self, effective_entity) + tuple(pk_cols),
         )
 
         if not self.parent_property.bake_queries:
index c8d639e8c9cd553200421d8981aaf781559950ad..170e4487e5ec72a4b4000a7078eee2b0f492b425 100644 (file)
@@ -487,7 +487,7 @@ class AliasedClass(object):
 
         if alias is None:
             alias = mapper._with_polymorphic_selectable._anonymous_fromclause(
-                name=name, flat=flat
+                name=name, flat=flat,
             )
 
         self._aliased_insp = AliasedInsp(
@@ -1089,17 +1089,15 @@ def aliased(element, alias=None, name=None, flat=False, adapt_on_names=False):
     :param name: optional string name to use for the alias, if not specified
      by the ``alias`` parameter.  The name, among other things, forms the
      attribute name that will be accessible via tuples returned by a
-     :class:`_query.Query` object.
+     :class:`_query.Query` object.  Not supported when creating aliases
+     of :class:`_sql.Join` objects.
 
     :param flat: Boolean, will be passed through to the
      :meth:`_expression.FromClause.alias` call so that aliases of
-     :class:`_expression.Join` objects
-     don't include an enclosing SELECT.  This can lead to more efficient
-     queries in many circumstances.  A JOIN against a nested JOIN will be
-     rewritten as a JOIN against an aliased SELECT subquery on backends that
-     don't support this syntax.
-
-     .. seealso:: :meth:`_expression.Join.alias`
+     :class:`_expression.Join` objects will alias the individual tables
+     inside the join, rather than creating a subquery.  This is generally
+     supported by all modern databases with regards to right-nested joins
+     and generally produces more efficient queries.
 
     :param adapt_on_names: if True, more liberal "matching" will be used when
      mapping the mapped columns of the ORM entity to those of the
@@ -1180,31 +1178,18 @@ def with_polymorphic(
         Alternatively, it may also be the string ``'*'``, in which case
         all descending mapped classes will be added to the FROM clause.
 
-    :param aliased: when True, the selectable will be wrapped in an
-        alias, that is ``(SELECT * FROM <fromclauses>) AS anon_1``.
-        This can be important when using the with_polymorphic()
-        to create the target of a JOIN on a backend that does not
-        support parenthesized joins, such as SQLite and older
-        versions of MySQL.   However if the
-        :paramref:`.with_polymorphic.selectable` parameter is in use
-        with an existing :class:`_expression.Alias` construct,
-        then you should not
-        set this flag.
+    :param aliased: when True, the selectable will be aliased.   For a
+        JOIN, this means the JOIN will be SELECTed from inside of a subquery
+        unless the :paramref:`_orm.with_polymorphic.flat` flag is set to
+        True, which is recommended for simpler use cases.
 
     :param flat: Boolean, will be passed through to the
      :meth:`_expression.FromClause.alias` call so that aliases of
-     :class:`_expression.Join`
-     objects don't include an enclosing SELECT.  This can lead to more
-     efficient queries in many circumstances.  A JOIN against a nested JOIN
-     will be rewritten as a JOIN against an aliased SELECT subquery on
-     backends that don't support this syntax.
-
-     Setting ``flat`` to ``True`` implies the ``aliased`` flag is
-     also ``True``.
-
-     .. versionadded:: 0.9.0
-
-     .. seealso:: :meth:`_expression.Join.alias`
+     :class:`_expression.Join` objects will alias the individual tables
+     inside the join, rather than creating a subquery.  This is generally
+     supported by all modern databases with regards to right-nested joins
+     and generally produces more efficient queries.  Setting this flag is
+     recommended as long as the resulting SQL is functional.
 
     :param selectable: a table or subquery that will
         be used in place of the generated FROM clause. This argument is
index b8525925b7e2cb3be8f13ce0cf939ba022a9beb1..154564a081d0bb1b74881079ae1be4095bea65f1 100644 (file)
@@ -55,7 +55,7 @@ def _deep_is_literal(element):
     return (
         not isinstance(
             element,
-            (Visitable, schema.SchemaEventTarget, HasCacheKey, Options,),
+            (Visitable, schema.SchemaEventTarget, HasCacheKey, Options),
         )
         and not hasattr(element, "__clause_element__")
         and (
@@ -881,7 +881,9 @@ class AnonymizedFromClauseImpl(StrictFromClauseImpl):
     __slots__ = ()
 
     def _post_coercion(self, element, flat=False, name=None, **kw):
-        return element.alias(name=name, flat=flat)
+        assert name is None
+
+        return element._anonymous_fromclause(flat=flat)
 
 
 class DMLTableImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl):
index 4205d9f0d3d772e79033c6517ea696a491a40cb4..b88625b88fdc161b2645819d9d14505ca7679e15 100644 (file)
@@ -144,19 +144,7 @@ class AnonymizedFromClauseRole(StrictFromClauseRole):
     # calls .alias() as a post processor
 
     def _anonymous_fromclause(self, name=None, flat=False):
-        """A synonym for ``.alias()`` that is only present on objects of this
-        role.
-
-        This is an implicit assurance of the target object being part of the
-        role where anonymous aliasing without any warnings is allowed,
-        as opposed to other kinds of SELECT objects that may or may not have
-        an ``.alias()`` method.
-
-        The method is used by the ORM but is currently semi-private to
-        preserve forwards-compatibility.
-
-        """
-        return self.alias(name=name, flat=flat)
+        raise NotImplementedError()
 
 
 class CoerceTextStatementRole(SQLRole):
index 9440fc48b18146e42ffdb30ce8655f3f09299f78..2e8f41cc8369a7183dcbc95e405aa60d2e212630 100644 (file)
@@ -155,9 +155,7 @@ class ReturnsRows(roles.ReturnsRowsRole, ClauseElement):
 
 
 class Selectable(ReturnsRows):
-    """Mark a class as being selectable.
-
-    """
+    """Mark a class as being selectable."""
 
     __visit_name__ = "selectable"
 
@@ -825,6 +823,9 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
         """
         self._reset_column_collection()
 
+    def _anonymous_fromclause(self, name=None, flat=False):
+        return self.alias(name=name)
+
 
 class Join(roles.DMLTableRole, FromClause):
     """Represent a ``JOIN`` construct between two
@@ -1224,6 +1225,33 @@ class Join(roles.DMLTableRole, FromClause):
         return self.left.bind or self.right.bind
 
     @util.preload_module("sqlalchemy.sql.util")
+    def _anonymous_fromclause(self, name=None, flat=False):
+        sqlutil = util.preloaded.sql_util
+        if flat:
+            if name is not None:
+                raise exc.ArgumentError("Can't send name argument with flat")
+            left_a, right_a = (
+                self.left._anonymous_fromclause(flat=True),
+                self.right._anonymous_fromclause(flat=True),
+            )
+            adapter = sqlutil.ClauseAdapter(left_a).chain(
+                sqlutil.ClauseAdapter(right_a)
+            )
+
+            return left_a.join(
+                right_a,
+                adapter.traverse(self.onclause),
+                isouter=self.isouter,
+                full=self.full,
+            )
+        else:
+            return self.select().apply_labels().correlate(None).alias(name)
+
+    @util.deprecated_20(
+        ":meth:`_sql.Join.alias`",
+        alternative="Create a select + subquery, or alias the "
+        "individual tables inside the join, instead.",
+    )
     def alias(self, name=None, flat=False):
         r"""Return an alias of this :class:`_expression.Join`.
 
@@ -1246,8 +1274,7 @@ class Join(roles.DMLTableRole, FromClause):
                 JOIN table_b ON table_a.id = table_b.a_id) AS anon_1
 
         The equivalent long-hand form, given a :class:`_expression.Join`
-        object
-        ``j``, is::
+        object ``j``, is::
 
             from sqlalchemy import select, alias
             j = alias(
@@ -1322,25 +1349,7 @@ class Join(roles.DMLTableRole, FromClause):
             :func:`_expression.alias`
 
         """
-        sqlutil = util.preloaded.sql_util
-        if flat:
-            assert name is None, "Can't send name argument with flat"
-            left_a, right_a = (
-                self.left.alias(flat=True),
-                self.right.alias(flat=True),
-            )
-            adapter = sqlutil.ClauseAdapter(left_a).chain(
-                sqlutil.ClauseAdapter(right_a)
-            )
-
-            return left_a.join(
-                right_a,
-                adapter.traverse(self.onclause),
-                isouter=self.isouter,
-                full=self.full,
-            )
-        else:
-            return self.select().apply_labels().correlate(None).alias(name)
+        return self._anonymous_fromclause(flat=flat, name=name)
 
     @property
     def _hide_froms(self):
@@ -1983,9 +1992,7 @@ class Subquery(AliasedReturnsRows):
 
     @classmethod
     def _factory(cls, selectable, name=None):
-        """Return a :class:`.Subquery` object.
-
-        """
+        """Return a :class:`.Subquery` object."""
         return coercions.expect(
             roles.SelectStatementRole, selectable
         ).subquery(name=name)
@@ -2035,6 +2042,9 @@ class FromGrouping(GroupedElement, FromClause):
     def alias(self, **kw):
         return FromGrouping(self.element.alias(**kw))
 
+    def _anonymous_fromclause(self, **kw):
+        return FromGrouping(self.element._anonymous_fromclause(**kw))
+
     @property
     def _hide_froms(self):
         return self.element._hide_froms
@@ -2294,7 +2304,7 @@ class Values(Generative, FromClause):
     _data = ()
 
     _traverse_internals = [
-        ("_column_args", InternalTraversal.dp_clauseelement_list,),
+        ("_column_args", InternalTraversal.dp_clauseelement_list),
         ("_data", InternalTraversal.dp_dml_multi_values),
         ("name", InternalTraversal.dp_string),
         ("literal_binds", InternalTraversal.dp_boolean),
@@ -3741,7 +3751,7 @@ class SelectState(util.MemoizedSlots, CompileState):
             else:
 
                 self.from_clauses = self.from_clauses + (
-                    Join(left, right, onclause, isouter=isouter, full=full,),
+                    Join(left, right, onclause, isouter=isouter, full=full),
                 )
 
     @util.preload_module("sqlalchemy.sql.util")
@@ -3908,12 +3918,12 @@ class Select(
             ("_from_obj", InternalTraversal.dp_clauseelement_list),
             ("_where_criteria", InternalTraversal.dp_clauseelement_tuple),
             ("_having_criteria", InternalTraversal.dp_clauseelement_tuple),
-            ("_order_by_clauses", InternalTraversal.dp_clauseelement_tuple,),
-            ("_group_by_clauses", InternalTraversal.dp_clauseelement_tuple,),
-            ("_setup_joins", InternalTraversal.dp_setup_join_tuple,),
-            ("_legacy_setup_joins", InternalTraversal.dp_setup_join_tuple,),
+            ("_order_by_clauses", InternalTraversal.dp_clauseelement_tuple),
+            ("_group_by_clauses", InternalTraversal.dp_clauseelement_tuple),
+            ("_setup_joins", InternalTraversal.dp_setup_join_tuple),
+            ("_legacy_setup_joins", InternalTraversal.dp_setup_join_tuple),
             ("_correlate", InternalTraversal.dp_clauseelement_tuple),
-            ("_correlate_except", InternalTraversal.dp_clauseelement_tuple,),
+            ("_correlate_except", InternalTraversal.dp_clauseelement_tuple),
             ("_limit_clause", InternalTraversal.dp_clauseelement),
             ("_offset_clause", InternalTraversal.dp_clauseelement),
             ("_for_update_arg", InternalTraversal.dp_clauseelement),
@@ -4312,7 +4322,7 @@ class Select(
         else:
             return cls._create_future_select(*args)
 
-    def __init__(self,):
+    def __init__(self):
         raise NotImplementedError()
 
     def _scalar_type(self):
@@ -4537,7 +4547,7 @@ class Select(
             :meth:`_expression.Select.join`
 
         """
-        return self.join(target, onclause=onclause, isouter=True, full=full,)
+        return self.join(target, onclause=onclause, isouter=True, full=full)
 
     @property
     def froms(self):
@@ -4762,7 +4772,7 @@ class Select(
         for c in coercions._expression_collection_was_a_list(
             "columns", "Select.with_only_columns", columns
         ):
-            c = coercions.expect(roles.ColumnsClauseRole, c,)
+            c = coercions.expect(roles.ColumnsClauseRole, c)
             # TODO: why are we doing this here?
             if isinstance(c, ScalarSelect):
                 c = c.self_group(against=operators.comma_op)
@@ -5312,9 +5322,7 @@ class ScalarSelect(roles.InElementRole, Generative, Grouping):
 
 
 class Exists(UnaryExpression):
-    """Represent an ``EXISTS`` clause.
-
-    """
+    """Represent an ``EXISTS`` clause."""
 
     _from_objects = []
     inherit_cache = True
index 52a04f9b05f79df4aab88072b9f604d461cbe279..c32b2749b2a54ce2e9443afb3a874a975e924e0a 100644 (file)
@@ -43,7 +43,7 @@ def expect_warnings(*messages, **kw):
 
     """  # noqa
     return _expect_warnings(
-        (sa_exc.SAWarning, sa_exc.RemovedIn20Warning), messages, **kw
+        (sa_exc.RemovedIn20Warning, sa_exc.SAWarning), messages, **kw
     )
 
 
@@ -305,7 +305,7 @@ def assert_raises(except_cls, callable_, *args, **kw):
 
 
 def assert_raises_context_ok(except_cls, callable_, *args, **kw):
-    return _assert_raises(except_cls, callable_, args, kw,)
+    return _assert_raises(except_cls, callable_, args, kw)
 
 
 def assert_raises_message(except_cls, msg, callable_, *args, **kwargs):
@@ -347,7 +347,7 @@ def _expect_raises(except_cls, msg=None, check_context=False):
         if msg is not None:
             assert re.search(
                 msg, util.text_type(err), re.UNICODE
-            ), "%r !~ %s" % (msg, err,)
+            ), "%r !~ %s" % (msg, err)
         if check_context and not are_we_already_in_a_traceback:
             _assert_proper_exception_context(err)
         print(util.text_type(err).encode("utf-8"))
index 91be4d16712a45c073f14692227d442c4f0b452b..266fb4b10ca5d18ff799296cd85aa119157754b0 100644 (file)
@@ -1,6 +1,7 @@
 from sqlalchemy import ForeignKey
 from sqlalchemy import Integer
 from sqlalchemy import String
+from sqlalchemy import testing
 from sqlalchemy.orm import backref
 from sqlalchemy.orm import clear_mappers
 from sqlalchemy.orm import configure_mappers
@@ -60,8 +61,11 @@ class PolymorphicCircularTest(fixtures.MappedTest):
         #   'table1' : table1.select(table1.c.type.in_(['table1', 'table1b'])),
         #   }, None, 'pjoin')
 
-        join = table1.outerjoin(table2).outerjoin(table3).alias("pjoin")
-        # join = None
+        with testing.expect_deprecated_20(
+            r"The Join.alias\(\) function/method is considered legacy"
+        ):
+            join = table1.outerjoin(table2).outerjoin(table3).alias("pjoin")
+            # join = None
 
         class Table1(object):
             def __init__(self, name, data=None):
index a7ae39db33b8d41deecaeb46e88be31c057ffadc..c9a78db081db0c672d47145dfd75577072bc7a73 100644 (file)
@@ -48,7 +48,7 @@ class InheritingSelectablesTest(fixtures.MappedTest):
             baz,
             with_polymorphic=(
                 "*",
-                foo.join(baz, foo.c.b == "baz").alias("baz"),
+                foo.join(baz, foo.c.b == "baz").select().subquery("baz"),
             ),
             inherits=Foo,
             inherit_condition=(foo.c.a == baz.c.a),
@@ -61,7 +61,7 @@ class InheritingSelectablesTest(fixtures.MappedTest):
             bar,
             with_polymorphic=(
                 "*",
-                foo.join(bar, foo.c.b == "bar").alias("bar"),
+                foo.join(bar, foo.c.b == "bar").select().subquery("bar"),
             ),
             inherits=Foo,
             inherit_condition=(foo.c.a == bar.c.a),
@@ -70,13 +70,6 @@ class InheritingSelectablesTest(fixtures.MappedTest):
         )
 
         s = Session()
-
-        #        assert [Baz(), Baz(), Bar(), Bar()] == s.query(Foo).order_by(
-        #            Foo.b.desc()
-        #       ).all()
-
-        #      import pdb
-        #      pdb.set_trace()
         assert [Bar(), Bar()] == s.query(Bar).all()
 
 
index 30e4338b5d540ac5874a6bd0c9c95aacb0791e5e..d078b36b8eb9b47b73df575903d407bb0f03d86a 100644 (file)
@@ -693,6 +693,31 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL):
             assert u.corresponding_column(s2.c.table2_coly) is u.c.coly
             assert s2.c.corresponding_column(u.c.coly) is s2.c.table2_coly
 
+    def test_join_alias(self):
+        j1 = self.table1.join(self.table2)
+
+        with testing.expect_deprecated_20(
+            r"The Join.alias\(\) function/method is considered legacy"
+        ):
+            self.assert_compile(
+                j1.alias(),
+                "SELECT table1.col1 AS table1_col1, table1.col2 AS "
+                "table1_col2, table1.col3 AS table1_col3, table1.colx "
+                "AS table1_colx, table2.col1 AS table2_col1, "
+                "table2.col2 AS table2_col2, table2.col3 AS table2_col3, "
+                "table2.coly AS table2_coly FROM table1 JOIN table2 "
+                "ON table1.col1 = table2.col2",
+            )
+
+        with testing.expect_deprecated_20(
+            r"The Join.alias\(\) function/method is considered legacy"
+        ):
+            self.assert_compile(
+                j1.alias(flat=True),
+                "table1 AS table1_1 JOIN table2 AS table2_1 "
+                "ON table1_1.col1 = table2_1.col2",
+            )
+
     def test_join_against_self_implicit_subquery(self):
         jj = select(self.table1.c.col1.label("bar_col1"))
         with testing.expect_deprecated(
@@ -716,8 +741,13 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL):
 
         # test alias of the join
 
-        j2 = jjj.alias("foo")
-        assert j2.corresponding_column(self.table1.c.col1) is j2.c.table1_col1
+        with testing.expect_deprecated(
+            r"The Join.alias\(\) function/method is considered legacy"
+        ):
+            j2 = jjj.alias("foo")
+            assert (
+                j2.corresponding_column(self.table1.c.col1) is j2.c.table1_col1
+            )
 
     def test_select_labels(self):
         a = self.table1.select().apply_labels()
index 970c39cefd4d833d18c04957e2e363f1060fa6b1..6b07ebba961bc302bdacb3e0020f5b8cbe4271ba 100644 (file)
@@ -1730,7 +1730,7 @@ class ClauseAdapterTest(fixtures.TestBase, AssertsCompiledSQL):
             "JOIN b ON a.id = b.aid) AS anon_1 ON anon_1.b_id = c.bid "
             "LEFT OUTER JOIN d ON anon_1.a_id = d.aid",
         )
-        j5 = j3.alias("foo")
+        j5 = j3.select().apply_labels().subquery("foo")
         j6 = sql_util.ClauseAdapter(j5).copy_and_process([j4])[0]
 
         # this statement takes c join(a join b), wraps it inside an
index 329cc39f6d1e2dea903ae5ec16277711d0e8f47e..d09fe76e1b15b431ac7535a595ea57391399f4ea 100644 (file)
@@ -1,6 +1,5 @@
 """Test various algorithmic properties of selectables."""
 
-from sqlalchemy import alias
 from sqlalchemy import and_
 from sqlalchemy import bindparam
 from sqlalchemy import Boolean
@@ -386,11 +385,11 @@ class SelectableTest(
 
         # joins necessarily have to prefix column names with the name
         # of the selectable, else the same-named columns will overwrite
-        # one another.  In this case, we unfortunately have this unfriendly
-        # "anonymous" name, whereas before when select() could be a FROM
-        # the "bar_col1" label would be directly in the join() object.  However
-        # this was a useless join() object because PG and MySQL don't accept
-        # unnamed subqueries in joins in any case.
+        # one another.  In this case, we unfortunately have this
+        # unfriendly "anonymous" name, whereas before when select() could
+        # be a FROM the "bar_col1" label would be directly in the join()
+        # object.  However this was a useless join() object because PG and
+        # MySQL don't accept unnamed subqueries in joins in any case.
         name = "%s_bar_col1" % (jj.name,)
 
         assert jjj.corresponding_column(jjj.c.table1_col1) is jjj.c.table1_col1
@@ -398,7 +397,7 @@ class SelectableTest(
 
         # test alias of the join
 
-        j2 = jjj.alias("foo")
+        j2 = jjj.select().apply_labels().subquery("foo")
         assert j2.corresponding_column(table1.c.col1) is j2.c.table1_col1
 
     def test_clone_append_column(self):
@@ -522,13 +521,16 @@ class SelectableTest(
         )
 
     def test_join_against_join(self):
+
         j = outerjoin(table1, table2, table1.c.col1 == table2.c.col2)
         jj = (
-            select(table1.c.col1.label("bar_col1")).select_from(j).alias("foo")
+            select(table1.c.col1.label("bar_col1"))
+            .select_from(j)
+            .alias(name="foo")
         )
         jjj = join(table1, jj, table1.c.col1 == jj.c.bar_col1)
         assert jjj.corresponding_column(jjj.c.table1_col1) is jjj.c.table1_col1
-        j2 = jjj.alias("foo")
+        j2 = jjj._anonymous_fromclause("foo")
         assert j2.corresponding_column(jjj.c.table1_col1) is j2.c.table1_col1
         assert jjj.corresponding_column(jj.c.bar_col1) is jj.c.bar_col1
 
@@ -1459,7 +1461,15 @@ class AnonLabelTest(fixtures.TestBase):
         eq_(str(select(c1.label("y"))), "SELECT x AS y")
 
 
-class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
+class JoinAnonymizingTest(fixtures.TestBase, AssertsCompiledSQL):
+    """test anonymous_fromclause for aliases.
+
+    In 1.4 this function is only for ORM internal use.   The public version
+    join.alias() is deprecated.
+
+
+    """
+
     __dialect__ = "default"
 
     def test_flat_ok_on_non_join(self):
@@ -1474,7 +1484,7 @@ class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
         a = table("a", column("a"))
         b = table("b", column("b"))
         self.assert_compile(
-            a.join(b, a.c.a == b.c.b).alias(),
+            a.join(b, a.c.a == b.c.b)._anonymous_fromclause(),
             "SELECT a.a AS a_a, b.b AS b_b FROM a JOIN b ON a.a = b.b",
         )
 
@@ -1482,7 +1492,7 @@ class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
         a = table("a", column("a"))
         b = table("b", column("b"))
         self.assert_compile(
-            alias(a.join(b, a.c.a == b.c.b)),
+            a.join(b, a.c.a == b.c.b)._anonymous_fromclause(),
             "SELECT a.a AS a_a, b.b AS b_b FROM a JOIN b ON a.a = b.b",
         )
 
@@ -1490,7 +1500,7 @@ class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
         a = table("a", column("a"))
         b = table("b", column("b"))
         self.assert_compile(
-            a.join(b, a.c.a == b.c.b).alias(flat=True),
+            a.join(b, a.c.a == b.c.b)._anonymous_fromclause(flat=True),
             "a AS a_1 JOIN b AS b_1 ON a_1.a = b_1.b",
         )
 
@@ -1498,7 +1508,7 @@ class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
         a = table("a", column("a"))
         b = table("b", column("b"))
         self.assert_compile(
-            alias(a.join(b, a.c.a == b.c.b), flat=True),
+            a.join(b, a.c.a == b.c.b)._anonymous_fromclause(flat=True),
             "a AS a_1 JOIN b AS b_1 ON a_1.a = b_1.b",
         )
 
@@ -1510,10 +1520,14 @@ class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
 
         j1 = a.join(b, a.c.a == b.c.b)
         j2 = c.join(d, c.c.c == d.c.d)
+
+        # note in 1.4 the flat=True flag now descends into the whole join,
+        # as it should
         self.assert_compile(
-            j1.join(j2, b.c.b == c.c.c).alias(flat=True),
+            j1.join(j2, b.c.b == c.c.c)._anonymous_fromclause(flat=True),
             "a AS a_1 JOIN b AS b_1 ON a_1.a = b_1.b JOIN "
-            "(c AS c_1 JOIN d AS d_1 ON c_1.c = d_1.d) ON b_1.b = c_1.c",
+            "(c AS c_1 JOIN d AS d_1 ON c_1.c = d_1.d) "
+            "ON b_1.b = c_1.c",
         )
 
     def test_composed_join_alias(self):
@@ -1525,7 +1539,7 @@ class JoinAliasingTest(fixtures.TestBase, AssertsCompiledSQL):
         j1 = a.join(b, a.c.a == b.c.b)
         j2 = c.join(d, c.c.c == d.c.d)
         self.assert_compile(
-            select(j1.join(j2, b.c.b == c.c.c).alias()),
+            select(j1.join(j2, b.c.b == c.c.c)._anonymous_fromclause()),
             "SELECT anon_1.a_a, anon_1.b_b, anon_1.c_c, anon_1.d_d "
             "FROM (SELECT a.a AS a_a, b.b AS b_b, c.c AS c_c, d.d AS d_d "
             "FROM a JOIN b ON a.a = b.b "
@@ -1616,7 +1630,10 @@ class JoinConditionTest(fixtures.TestBase, AssertsCompiledSQL):
         m = MetaData()
         t1 = Table("t1", m, Column("id", Integer))
         t2 = Table(
-            "t2", m, Column("id", Integer), Column("t1id", ForeignKey("t1.id"))
+            "t2",
+            m,
+            Column("id", Integer),
+            Column("t1id", ForeignKey("t1.id")),
         )
         t3 = Table(
             "t3",
@@ -1626,11 +1643,14 @@ class JoinConditionTest(fixtures.TestBase, AssertsCompiledSQL):
             Column("t2id", ForeignKey("t2.id")),
         )
         t4 = Table(
-            "t4", m, Column("id", Integer), Column("t2id", ForeignKey("t2.id"))
+            "t4",
+            m,
+            Column("id", Integer),
+            Column("t2id", ForeignKey("t2.id")),
         )
         t1t2 = t1.join(t2)
         t2t3 = t2.join(t3)
-        als = t2t3.alias()
+        als = t2t3._anonymous_fromclause()
         # test join's behavior, including natural
         for left, right, expected in [
             (t1, t2, t1.c.id == t2.c.t1id),