--- /dev/null
+.. 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.
+
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
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,
).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:
if alias is None:
alias = mapper._with_polymorphic_selectable._anonymous_fromclause(
- name=name, flat=flat
+ name=name, flat=flat,
)
self._aliased_insp = AliasedInsp(
: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
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
return (
not isinstance(
element,
- (Visitable, schema.SchemaEventTarget, HasCacheKey, Options,),
+ (Visitable, schema.SchemaEventTarget, HasCacheKey, Options),
)
and not hasattr(element, "__clause_element__")
and (
__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):
# 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):
class Selectable(ReturnsRows):
- """Mark a class as being selectable.
-
- """
+ """Mark a class as being selectable."""
__visit_name__ = "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
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`.
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(
: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):
@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)
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
_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),
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")
("_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),
else:
return cls._create_future_select(*args)
- def __init__(self,):
+ def __init__(self):
raise NotImplementedError()
def _scalar_type(self):
: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):
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)
class Exists(UnaryExpression):
- """Represent an ``EXISTS`` clause.
-
- """
+ """Represent an ``EXISTS`` clause."""
_from_objects = []
inherit_cache = True
""" # noqa
return _expect_warnings(
- (sa_exc.SAWarning, sa_exc.RemovedIn20Warning), messages, **kw
+ (sa_exc.RemovedIn20Warning, sa_exc.SAWarning), messages, **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):
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"))
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
# '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):
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),
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),
)
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()
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(
# 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()
"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
"""Test various algorithmic properties of selectables."""
-from sqlalchemy import alias
from sqlalchemy import and_
from sqlalchemy import bindparam
from sqlalchemy import Boolean
# 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
# 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):
)
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
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):
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",
)
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",
)
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",
)
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",
)
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):
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 "
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",
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),