From: Gord Thompson Date: Sun, 13 Sep 2020 18:37:40 +0000 (-0600) Subject: Add deprecation warning for .join().alias() X-Git-Tag: rel_1_4_0b1~69 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=75ac0abc7d5653d10006769a881374a46b706db5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add deprecation warning for .join().alias() 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 --- diff --git a/doc/build/changelog/unreleased_14/5010.rst b/doc/build/changelog/unreleased_14/5010.rst new file mode 100644 index 0000000000..31976974d6 --- /dev/null +++ b/doc/build/changelog/unreleased_14/5010.rst @@ -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. + diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 1c95b6e064..cd1502073e 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -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 diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index fbf153dc5f..325bd4dc1e 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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: diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index c8d639e8c9..170e4487e5 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -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 ) 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 diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index b8525925b7..154564a081 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -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): diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py index 4205d9f0d3..b88625b88f 100644 --- a/lib/sqlalchemy/sql/roles.py +++ b/lib/sqlalchemy/sql/roles.py @@ -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): diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 9440fc48b1..2e8f41cc83 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -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 diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index 52a04f9b05..c32b2749b2 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -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")) diff --git a/test/orm/inheritance/test_poly_linked_list.py b/test/orm/inheritance/test_poly_linked_list.py index 91be4d1671..266fb4b10c 100644 --- a/test/orm/inheritance/test_poly_linked_list.py +++ b/test/orm/inheritance/test_poly_linked_list.py @@ -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): diff --git a/test/orm/inheritance/test_selects.py b/test/orm/inheritance/test_selects.py index a7ae39db33..c9a78db081 100644 --- a/test/orm/inheritance/test_selects.py +++ b/test/orm/inheritance/test_selects.py @@ -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() diff --git a/test/sql/test_deprecations.py b/test/sql/test_deprecations.py index 30e4338b5d..d078b36b8e 100644 --- a/test/sql/test_deprecations.py +++ b/test/sql/test_deprecations.py @@ -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() diff --git a/test/sql/test_external_traversal.py b/test/sql/test_external_traversal.py index 970c39cefd..6b07ebba96 100644 --- a/test/sql/test_external_traversal.py +++ b/test/sql/test_external_traversal.py @@ -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 diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 329cc39f6d..d09fe76e1b 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -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),