summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-06-26 16:15:19 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-07-08 11:05:11 -0400
commit91f376692d472a5bf0c4b4033816250ec1ce3ab6 (patch)
tree31f7f72cbe981eb73ed0ba11808d4fb5ae6b7d51 /doc
parent3dc9a4a2392d033f9d1bd79dd6b6ecea6281a61c (diff)
downloadsqlalchemy-91f376692d472a5bf0c4b4033816250ec1ce3ab6.tar.gz
Add future=True to create_engine/Session; unify select()
Several weeks of using the future_select() construct has led to the proposal there be just one select() construct again which features the new join() method, and otherwise accepts both the 1.x and 2.x argument styles. This would make migration simpler and reduce confusion. However, confusion may be increased by the fact that select().join() is different Current thinking is we may be better off with a few hard behavioral changes to old and relatively unknown APIs rather than trying to play both sides within two extremely similar but subtly different APIs. At the moment, the .join() thing seems to be the only behavioral change that occurs without the user taking any explicit steps. Session.execute() will still behave the old way as we are adding a future flag. This change also adds the "future" flag to Session() and session.execute(), so that interpretation of the incoming statement, as well as that the new style result is returned, does not occur for existing applications unless they add the use of this flag. The change in general is moving the "removed in 2.0" system further along where we want the test suite to fully pass even if the SQLALCHEMY_WARN_20 flag is set. Get many tests to pass when SQLALCHEMY_WARN_20 is set; this should be ongoing after this patch merges. Improve the RemovedIn20 warning; these are all deprecated "since" 1.4, so ensure that's what the messages read. Make sure the inforamtion link is on all warnings. Add deprecation warnings for parameters present and add warnings to all FromClause.select() types of methods. Fixes: #5379 Fixes: #5284 Change-Id: I765a0b912b3dcd0e995426427d8bb7997cbffd51 References: #5159
Diffstat (limited to 'doc')
-rw-r--r--doc/build/changelog/migration_14.rst49
-rw-r--r--doc/build/changelog/migration_20.rst34
-rw-r--r--doc/build/changelog/unreleased_14/5284.rst16
-rw-r--r--doc/build/core/tutorial.rst147
-rw-r--r--doc/build/errors.rst51
5 files changed, 212 insertions, 85 deletions
diff --git a/doc/build/changelog/migration_14.rst b/doc/build/changelog/migration_14.rst
index 0ea6faf35..93fde1e8b 100644
--- a/doc/build/changelog/migration_14.rst
+++ b/doc/build/changelog/migration_14.rst
@@ -449,6 +449,55 @@ refined so that it is more compatible with Core.
:ticket:`4617`
+
+.. _change_5284:
+
+select() now accepts positional expressions
+-------------------------------------------
+
+The :func:`.select` construct will now accept "columns clause"
+arguments positionally::
+
+ # new way, supports 2.0
+ stmt = select(table.c.col1, table.c.col2, ...)
+
+When sending the arguments positionally, no other keyword arguments are permitted.
+In SQLAlchemy 2.0, the above calling style will be the only calling style
+supported.
+
+For the duration of 1.4, the previous calling style will still continue
+to function, which passes the list of columns or other expressions as a list::
+
+ # old way, still works in 1.4
+ stmt = select([table.c.col1, table.c.col2, ...])
+
+The above legacy calling style also accepts the old keyword arguments that have
+since been removed from most narrative documentation::
+
+ # very much the old way, but still works in 1.4
+ stmt = select([table.c.col1, table.c.col2, ...], whereclause=table.c.col1 == 5)
+
+The detection between the two styles is based on whether or not the first
+positional argument is a list. There are unfortunately still likely some
+usages that look like the following, where the keyword for the "whereclause"
+is omitted::
+
+ # very much the old way, but still works in 1.4
+ stmt = select([table.c.col1, table.c.col2, ...], table.c.col1 == 5)
+
+As part of this change, the :class:`.Select` construct also gains the 2.0-style
+"future" API which includes an updated :meth:`.Select.join` method as well
+as methods like :meth:`.Select.filter_by` and :meth:`.Select.join_from`.
+
+.. seealso::
+
+ :ref:`error_c9ae`
+
+ :ref:`migration_20_toplevel`
+
+
+:ticket:`5284`
+
.. _change_4645:
All IN expressions render parameters for each value in the list on the fly (e.g. expanding parameters)
diff --git a/doc/build/changelog/migration_20.rst b/doc/build/changelog/migration_20.rst
index 2a6ccfcdc..d7f9750c3 100644
--- a/doc/build/changelog/migration_20.rst
+++ b/doc/build/changelog/migration_20.rst
@@ -91,20 +91,24 @@ The steps to achieve this are as follows:
an application can gradually adjust all of its 1.4-style code to work fully
against 2.0 as well.
-* APIs which are explicitly incompatible with SQLAlchemy 1.x style will be
- available in two new packages ``sqlalchemy.future`` and
- ``sqlalchemy.future.orm``. The most prominent objects in these new packages
- will be the :func:`sqlalchemy.future.select` object, which now features
- a refined constructor, and additionally will be compatible with ORM
- querying, as well as the new declarative base construct in
- ``sqlalchemy.future.orm``.
-
-* SQLAlchemy 2.0 will include the same ``sqlalchemy.future`` and
- ``sqlalchemy.future.orm`` packages; once an application only needs to run on
- SQLAlchemy 2.0 (as well as Python 3 only of course :) ), the "future" imports
- can be changed to refer to the canonical import, for example ``from
- sqlalchemy.future import select`` becomes ``from sqlalchemy import select``.
-
+* Currently, the main API which is explicitly incompatible with SQLAlchemy 1.x
+ style is the behavior of the :class:`_engine.Engine` and
+ :class:`_engine.Connection` objects in terms connectionless execution as well
+ as "autocommit", in that the future API no longer has these behaviors, and
+ two new methods :meth:`_future.Connection.commit` and
+ :meth:`_future.Connection.rollback` are added in order to accommodate for
+ commit-as-you-go use. These new objects are currently in a separate package
+ ``sqlalchemy.future``; in order to access the future versions of these, pass
+ the parameter :paramref:`_engine.create_engine.future` to the
+ :func:`_engine.create_engine` function.
+
+* The :class:`_orm.Session` object also has a newer behavior when using the
+ :meth:`_orm.Session.execute` method, in that incoming statements are
+ interpreted in an ORM context if applicable, as well as that the
+ :class:`_engine.Result` object returned uses new-style tuples
+ (see :ref:`migration_20_result_rows`). Within 1.4 this newer style
+ is enabled by passing :paramref:`_orm.Session.future` to the session
+ constructor or :class:`_orm.sessionmaker` object.
Python 3 Only
=============
@@ -591,6 +595,8 @@ equally::
result[0].all() # same as result.scalars().all()
result[2:5].all() # same as result.columns('c', 'd', 'e').all()
+.. _migration_20_result_rows:
+
Result rows unified between Core and ORM on named-tuple interface
==================================================================
diff --git a/doc/build/changelog/unreleased_14/5284.rst b/doc/build/changelog/unreleased_14/5284.rst
new file mode 100644
index 000000000..379036e18
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/5284.rst
@@ -0,0 +1,16 @@
+.. change::
+ :tags: change, sql
+ :tickets: 5284
+
+ The :func:`_expression.select` construct is moving towards a new calling
+ form that is ``select(col1, col2, col3, ..)``, with all other keyword
+ arguments removed, as these are all suited using generative methods. The
+ single list of column or table arguments passed to ``select()`` is still
+ accepted, however is no longer necessary if expressions are passed in a
+ simple positional style. Other keyword arguments are disallowed when this
+ form is used.
+
+
+ .. seealso::
+
+ :ref:`change_5284`
diff --git a/doc/build/core/tutorial.rst b/doc/build/core/tutorial.rst
index 6d9ceb496..05a719326 100644
--- a/doc/build/core/tutorial.rst
+++ b/doc/build/core/tutorial.rst
@@ -380,7 +380,7 @@ statements is the :func:`_expression.select` function:
.. sourcecode:: pycon+sql
>>> from sqlalchemy.sql import select
- >>> s = select([users])
+ >>> s = select(users)
>>> result = conn.execute(s)
{opensql}SELECT users.id, users.name, users.fullname
FROM users
@@ -389,7 +389,14 @@ statements is the :func:`_expression.select` function:
Above, we issued a basic :func:`_expression.select` call, placing the ``users`` table
within the COLUMNS clause of the select, and then executing. SQLAlchemy
expanded the ``users`` table into the set of each of its columns, and also
-generated a FROM clause for us. The result returned is again a
+generated a FROM clause for us.
+
+.. versionchanged:: 1.4 The :func:`_expression.select` construct now accepts
+ column arguments positionally, as ``select(*args)``. The previous style
+ of ``select()`` accepting a list of column elements is now deprecated.
+ See :ref:`change_5284`.
+
+The result returned is again a
:class:`~sqlalchemy.engine.CursorResult` object, which acts much like a
DBAPI cursor, including methods such as
:func:`~sqlalchemy.engine.CursorResult.fetchone` and
@@ -524,7 +531,7 @@ the ``c`` attribute of the :class:`~sqlalchemy.schema.Table` object:
.. sourcecode:: pycon+sql
- >>> s = select([users.c.name, users.c.fullname])
+ >>> s = select(users.c.name, users.c.fullname)
{sql}>>> result = conn.execute(s)
SELECT users.name, users.fullname
FROM users
@@ -542,7 +549,7 @@ our :func:`_expression.select` statement:
.. sourcecode:: pycon+sql
- {sql}>>> for row in conn.execute(select([users, addresses])):
+ {sql}>>> for row in conn.execute(select(users, addresses)):
... print(row)
SELECT users.id, users.name, users.fullname, addresses.id, addresses.user_id, addresses.email_address
FROM users, addresses
@@ -564,7 +571,7 @@ WHERE clause. We do that using :meth:`_expression.Select.where`:
.. sourcecode:: pycon+sql
- >>> s = select([users, addresses]).where(users.c.id == addresses.c.user_id)
+ >>> s = select(users, addresses).where(users.c.id == addresses.c.user_id)
{sql}>>> for row in conn.execute(s):
... print(row)
SELECT users.id, users.name, users.fullname, addresses.id,
@@ -701,7 +708,7 @@ normally expected, using :func:`.type_coerce`::
from sqlalchemy import type_coerce
expr = type_coerce(somecolumn.op('-%>')('foo'), MySpecialType())
- stmt = select([expr])
+ stmt = select(expr)
For boolean operators, use the :meth:`.Operators.bool_op` method, which
@@ -783,9 +790,9 @@ not have a name:
.. sourcecode:: pycon+sql
- >>> s = select([(users.c.fullname +
+ >>> s = select((users.c.fullname +
... ", " + addresses.c.email_address).
- ... label('title')]).\
+ ... label('title')).\
... where(
... and_(
... users.c.id == addresses.c.user_id,
@@ -814,9 +821,9 @@ A shortcut to using :func:`.and_` is to chain together multiple
.. sourcecode:: pycon+sql
- >>> s = select([(users.c.fullname +
+ >>> s = select((users.c.fullname +
... ", " + addresses.c.email_address).
- ... label('title')]).\
+ ... label('title')).\
... where(users.c.id == addresses.c.user_id).\
... where(users.c.name.between('m', 'z')).\
... where(
@@ -920,7 +927,7 @@ When we call the :meth:`_expression.TextClause.columns` method, we get back a
j = stmt.join(addresses, stmt.c.id == addresses.c.user_id)
- new_stmt = select([stmt.c.id, addresses.c.id]).\
+ new_stmt = select(stmt.c.id, addresses.c.id).\
select_from(j).where(stmt.c.name == 'x')
The positional form of :meth:`_expression.TextClause.columns` is particularly useful
@@ -1003,9 +1010,9 @@ need to refer to any pre-established :class:`_schema.Table` metadata:
.. sourcecode:: pycon+sql
- >>> s = select([
+ >>> s = select(
... text("users.fullname || ', ' || addresses.email_address AS title")
- ... ]).\
+ ... ).\
... where(
... and_(
... text("users.id = addresses.user_id"),
@@ -1053,11 +1060,11 @@ be quoted:
>>> from sqlalchemy import select, and_, text, String
>>> from sqlalchemy.sql import table, literal_column
- >>> s = select([
+ >>> s = select(
... literal_column("users.fullname", String) +
... ', ' +
... literal_column("addresses.email_address").label("title")
- ... ]).\
+ ... ).\
... where(
... and_(
... literal_column("users.id") == literal_column("addresses.user_id"),
@@ -1093,9 +1100,9 @@ are rendered fully:
.. sourcecode:: pycon+sql
>>> from sqlalchemy import func
- >>> stmt = select([
+ >>> stmt = select(
... addresses.c.user_id,
- ... func.count(addresses.c.id).label('num_addresses')]).\
+ ... func.count(addresses.c.id).label('num_addresses')).\
... group_by("user_id").order_by("user_id", "num_addresses")
{sql}>>> conn.execute(stmt).fetchall()
@@ -1110,9 +1117,9 @@ name:
.. sourcecode:: pycon+sql
>>> from sqlalchemy import func, desc
- >>> stmt = select([
+ >>> stmt = select(
... addresses.c.user_id,
- ... func.count(addresses.c.id).label('num_addresses')]).\
+ ... func.count(addresses.c.id).label('num_addresses')).\
... group_by("user_id").order_by("user_id", desc("num_addresses"))
{sql}>>> conn.execute(stmt).fetchall()
@@ -1132,7 +1139,7 @@ by a column name that appears more than once:
.. sourcecode:: pycon+sql
>>> u1a, u1b = users.alias(), users.alias()
- >>> stmt = select([u1a, u1b]).\
+ >>> stmt = select(u1a, u1b).\
... where(u1a.c.name > u1b.c.name).\
... order_by(u1a.c.name) # using "name" here would be ambiguous
@@ -1179,7 +1186,7 @@ once for each address. We create two :class:`_expression.Alias` constructs aga
>>> a1 = addresses.alias()
>>> a2 = addresses.alias()
- >>> s = select([users]).\
+ >>> s = select(users).\
... where(and_(
... users.c.id == a1.c.user_id,
... users.c.id == a2.c.user_id,
@@ -1225,7 +1232,7 @@ by making :class:`.Subquery` of the entire statement:
.. sourcecode:: pycon+sql
>>> address_subq = s.subquery()
- >>> s = select([users.c.name]).where(users.c.id == address_subq.c.id)
+ >>> s = select(users.c.name).where(users.c.id == address_subq.c.id)
>>> conn.execute(s).fetchall()
{opensql}SELECT users.name
FROM users,
@@ -1284,7 +1291,7 @@ here we make use of the :meth:`_expression.Select.select_from` method:
.. sourcecode:: pycon+sql
- >>> s = select([users.c.fullname]).select_from(
+ >>> s = select(users.c.fullname).select_from(
... users.join(addresses,
... addresses.c.email_address.like(users.c.name + '%'))
... )
@@ -1299,7 +1306,7 @@ and is used in the same way as :meth:`_expression.FromClause.join`:
.. sourcecode:: pycon+sql
- >>> s = select([users.c.fullname]).select_from(users.outerjoin(addresses))
+ >>> s = select(users.c.fullname).select_from(users.outerjoin(addresses))
>>> print(s)
SELECT users.fullname
FROM users
@@ -1340,8 +1347,8 @@ typically acquires using the :meth:`_expression.Select.cte` method on a
.. sourcecode:: pycon+sql
- >>> users_cte = select([users.c.id, users.c.name]).where(users.c.name == 'wendy').cte()
- >>> stmt = select([addresses]).where(addresses.c.user_id == users_cte.c.id).order_by(addresses.c.id)
+ >>> users_cte = select(users.c.id, users.c.name).where(users.c.name == 'wendy').cte()
+ >>> stmt = select(addresses).where(addresses.c.user_id == users_cte.c.id).order_by(addresses.c.id)
>>> conn.execute(stmt).fetchall()
{opensql}WITH anon_1 AS
(SELECT users.id AS id, users.name AS name
@@ -1375,10 +1382,10 @@ this form looks like:
.. sourcecode:: pycon+sql
- >>> users_cte = select([users.c.id, users.c.name]).cte(recursive=True)
+ >>> users_cte = select(users.c.id, users.c.name).cte(recursive=True)
>>> users_recursive = users_cte.alias()
- >>> users_cte = users_cte.union(select([users.c.id, users.c.name]).where(users.c.id > users_recursive.c.id))
- >>> stmt = select([addresses]).where(addresses.c.user_id == users_cte.c.id).order_by(addresses.c.id)
+ >>> users_cte = users_cte.union(select(users.c.id, users.c.name).where(users.c.id > users_recursive.c.id))
+ >>> stmt = select(addresses).where(addresses.c.user_id == users_cte.c.id).order_by(addresses.c.id)
>>> conn.execute(stmt).fetchall()
{opensql}WITH RECURSIVE anon_1(id, name) AS
(SELECT users.id AS id, users.name AS name
@@ -1416,8 +1423,8 @@ at execution time, as here where it converts to positional for SQLite:
.. sourcecode:: pycon+sql
>>> from sqlalchemy.sql import bindparam
- >>> s = users.select(users.c.name == bindparam('username'))
- {sql}>>> conn.execute(s, username='wendy').fetchall()
+ >>> s = users.select().where(users.c.name == bindparam('username'))
+ {sql}>>> conn.execute(s, {"username": "wendy"}).fetchall()
SELECT users.id, users.name, users.fullname
FROM users
WHERE users.name = ?
@@ -1431,8 +1438,8 @@ off to the database:
.. sourcecode:: pycon+sql
- >>> s = users.select(users.c.name.like(bindparam('username', type_=String) + text("'%'")))
- {sql}>>> conn.execute(s, username='wendy').fetchall()
+ >>> s = users.select().where(users.c.name.like(bindparam('username', type_=String) + text("'%'")))
+ {sql}>>> conn.execute(s, {"username": "wendy"}).fetchall()
SELECT users.id, users.name, users.fullname
FROM users
WHERE users.name LIKE ? || '%'
@@ -1445,7 +1452,7 @@ single named value is needed in the execute parameters:
.. sourcecode:: pycon+sql
- >>> s = select([users, addresses]).\
+ >>> s = select(users, addresses).\
... where(
... or_(
... users.c.name.like(
@@ -1456,7 +1463,7 @@ single named value is needed in the execute parameters:
... ).\
... select_from(users.outerjoin(addresses)).\
... order_by(addresses.c.id)
- {sql}>>> conn.execute(s, name='jack').fetchall()
+ {sql}>>> conn.execute(s, {"name": "jack"}).fetchall()
SELECT users.id, users.name, users.fullname, addresses.id,
addresses.user_id, addresses.email_address
FROM users LEFT OUTER JOIN addresses ON users.id = addresses.user_id
@@ -1509,7 +1516,7 @@ However, in order for the column expression generated by the function to
have type-specific operator behavior as well as result-set behaviors, such
as date and numeric coercions, the type may need to be specified explicitly::
- stmt = select([func.date(some_table.c.date_string, type_=Date)])
+ stmt = select(func.date(some_table.c.date_string, type_=Date))
Functions are most typically used in the columns clause of a select statement,
@@ -1524,10 +1531,10 @@ not important in this case:
.. sourcecode:: pycon+sql
>>> conn.execute(
- ... select([
+ ... select(
... func.max(addresses.c.email_address, type_=String).
... label('maxemail')
- ... ])
+ ... )
... ).scalar()
{opensql}SELECT max(addresses.email_address) AS maxemail
FROM addresses
@@ -1544,7 +1551,7 @@ well as bind parameters:
.. sourcecode:: pycon+sql
>>> from sqlalchemy.sql import column
- >>> calculate = select([column('q'), column('z'), column('r')]).\
+ >>> calculate = select(column('q'), column('z'), column('r')).\
... select_from(
... func.calculate(
... bindparam('x'),
@@ -1552,7 +1559,7 @@ well as bind parameters:
... )
... )
>>> calc = calculate.alias()
- >>> print(select([users]).where(users.c.id > calc.c.z))
+ >>> print(select(users).where(users.c.id > calc.c.z))
SELECT users.id, users.name, users.fullname
FROM users, (SELECT q, z, r
FROM calculate(:x, :y)) AS anon_1
@@ -1568,7 +1575,7 @@ of our selectable:
>>> calc1 = calculate.alias('c1').unique_params(x=17, y=45)
>>> calc2 = calculate.alias('c2').unique_params(x=5, y=12)
- >>> s = select([users]).\
+ >>> s = select(users).\
... where(users.c.id.between(calc1.c.z, calc2.c.z))
>>> print(s)
SELECT users.id, users.name, users.fullname
@@ -1593,10 +1600,10 @@ Any :class:`.FunctionElement`, including functions generated by
:data:`~.expression.func`, can be turned into a "window function", that is an
OVER clause, using the :meth:`.FunctionElement.over` method::
- >>> s = select([
+ >>> s = select(
... users.c.id,
... func.row_number().over(order_by=users.c.name)
- ... ])
+ ... )
>>> print(s)
SELECT users.id, row_number() OVER (ORDER BY users.name) AS anon_1
FROM users
@@ -1605,12 +1612,12 @@ OVER clause, using the :meth:`.FunctionElement.over` method::
either the :paramref:`.expression.over.rows` or
:paramref:`.expression.over.range` parameters::
- >>> s = select([
+ >>> s = select(
... users.c.id,
... func.row_number().over(
... order_by=users.c.name,
... rows=(-2, None))
- ... ])
+ ... )
>>> print(s)
SELECT users.id, row_number() OVER
(ORDER BY users.name ROWS BETWEEN :param_1 PRECEDING AND UNBOUNDED FOLLOWING) AS anon_1
@@ -1644,7 +1651,7 @@ object as arguments:
.. sourcecode:: pycon+sql
>>> from sqlalchemy import cast
- >>> s = select([cast(users.c.id, String)])
+ >>> s = select(cast(users.c.id, String))
>>> conn.execute(s).fetchall()
{opensql}SELECT CAST(users.id AS VARCHAR) AS id
FROM users
@@ -1684,11 +1691,11 @@ string into one of MySQL's JSON functions:
>>> from sqlalchemy import JSON
>>> from sqlalchemy import type_coerce
>>> from sqlalchemy.dialects import mysql
- >>> s = select([
+ >>> s = select(
... type_coerce(
... {'some_key': {'foo': 'bar'}}, JSON
... )['some_key']
- ... ])
+ ... )
>>> print(s.compile(dialect=mysql.dialect()))
SELECT JSON_EXTRACT(%s, %s) AS anon_1
@@ -1770,7 +1777,7 @@ want the "union" to be stated as a subquery:
... addresses.select().
... where(addresses.c.email_address.like('%@msn.com'))
... ).subquery().select(), # apply subquery here
- ... addresses.select(addresses.c.email_address.like('%@msn.com'))
+ ... addresses.select().where(addresses.c.email_address.like('%@msn.com'))
... )
{sql}>>> conn.execute(u).fetchall()
SELECT anon_1.id, anon_1.user_id, anon_1.email_address
@@ -1851,7 +1858,7 @@ or :meth:`_expression.SelectBase.label` method:
.. sourcecode:: pycon+sql
- >>> subq = select([func.count(addresses.c.id)]).\
+ >>> subq = select(func.count(addresses.c.id)).\
... where(users.c.id == addresses.c.user_id).\
... scalar_subquery()
@@ -1863,7 +1870,7 @@ other column within another :func:`_expression.select`:
.. sourcecode:: pycon+sql
- >>> conn.execute(select([users.c.name, subq])).fetchall()
+ >>> conn.execute(select(users.c.name, subq)).fetchall()
{opensql}SELECT users.name, (SELECT count(addresses.id) AS count_1
FROM addresses
WHERE users.id = addresses.user_id) AS anon_1
@@ -1876,10 +1883,10 @@ it using :meth:`_expression.SelectBase.label` instead:
.. sourcecode:: pycon+sql
- >>> subq = select([func.count(addresses.c.id)]).\
+ >>> subq = select(func.count(addresses.c.id)).\
... where(users.c.id == addresses.c.user_id).\
... label("address_count")
- >>> conn.execute(select([users.c.name, subq])).fetchall()
+ >>> conn.execute(select(users.c.name, subq)).fetchall()
{opensql}SELECT users.name, (SELECT count(addresses.id) AS count_1
FROM addresses
WHERE users.id = addresses.user_id) AS address_count
@@ -1906,10 +1913,10 @@ still have at least one FROM clause of its own. For example:
.. sourcecode:: pycon+sql
- >>> stmt = select([addresses.c.user_id]).\
+ >>> stmt = select(addresses.c.user_id).\
... where(addresses.c.user_id == users.c.id).\
... where(addresses.c.email_address == 'jack@yahoo.com')
- >>> enclosing_stmt = select([users.c.name]).\
+ >>> enclosing_stmt = select(users.c.name).\
... where(users.c.id == stmt.scalar_subquery())
>>> conn.execute(enclosing_stmt).fetchall()
{opensql}SELECT users.name
@@ -1929,12 +1936,12 @@ may be correlated:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.id]).\
+ >>> stmt = select(users.c.id).\
... where(users.c.id == addresses.c.user_id).\
... where(users.c.name == 'jack').\
... correlate(addresses)
>>> enclosing_stmt = select(
- ... [users.c.name, addresses.c.email_address]).\
+ ... users.c.name, addresses.c.email_address).\
... select_from(users.join(addresses)).\
... where(users.c.id == stmt.scalar_subquery())
>>> conn.execute(enclosing_stmt).fetchall()
@@ -1951,10 +1958,10 @@ as the argument:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.id]).\
+ >>> stmt = select(users.c.id).\
... where(users.c.name == 'wendy').\
... correlate(None)
- >>> enclosing_stmt = select([users.c.name]).\
+ >>> enclosing_stmt = select(users.c.name).\
... where(users.c.id == stmt.scalar_subquery())
>>> conn.execute(enclosing_stmt).fetchall()
{opensql}SELECT users.name
@@ -1971,12 +1978,12 @@ by telling it to correlate all FROM clauses except for ``users``:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.id]).\
+ >>> stmt = select(users.c.id).\
... where(users.c.id == addresses.c.user_id).\
... where(users.c.name == 'jack').\
... correlate_except(users)
>>> enclosing_stmt = select(
- ... [users.c.name, addresses.c.email_address]).\
+ ... users.c.name, addresses.c.email_address).\
... select_from(users.join(addresses)).\
... where(users.c.id == stmt.scalar_subquery())
>>> conn.execute(enclosing_stmt).fetchall()
@@ -2021,9 +2028,9 @@ like the above using the :meth:`_expression.Select.lateral` method as follows::
>>> from sqlalchemy import table, column, select, true
>>> people = table('people', column('people_id'), column('age'), column('name'))
>>> books = table('books', column('book_id'), column('owner_id'))
- >>> subq = select([books.c.book_id]).\
+ >>> subq = select(books.c.book_id).\
... where(books.c.owner_id == people.c.people_id).lateral("book_subq")
- >>> print(select([people]).select_from(people.join(subq, true())))
+ >>> print(select(people).select_from(people.join(subq, true())))
SELECT people.people_id, people.age, people.name
FROM people JOIN LATERAL (SELECT books.book_id AS book_id
FROM books WHERE books.owner_id = people.people_id)
@@ -2066,7 +2073,7 @@ Ordering is done by passing column expressions to the
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.name]).order_by(users.c.name)
+ >>> stmt = select(users.c.name).order_by(users.c.name)
>>> conn.execute(stmt).fetchall()
{opensql}SELECT users.name
FROM users ORDER BY users.name
@@ -2078,7 +2085,7 @@ and :meth:`_expression.ColumnElement.desc` modifiers:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.name]).order_by(users.c.name.desc())
+ >>> stmt = select(users.c.name).order_by(users.c.name.desc())
>>> conn.execute(stmt).fetchall()
{opensql}SELECT users.name
FROM users ORDER BY users.name DESC
@@ -2091,7 +2098,7 @@ This is provided via the :meth:`_expression.SelectBase.group_by` method:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.name, func.count(addresses.c.id)]).\
+ >>> stmt = select(users.c.name, func.count(addresses.c.id)).\
... select_from(users.join(addresses)).\
... group_by(users.c.name)
>>> conn.execute(stmt).fetchall()
@@ -2108,7 +2115,7 @@ method:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.name, func.count(addresses.c.id)]).\
+ >>> stmt = select(users.c.name, func.count(addresses.c.id)).\
... select_from(users.join(addresses)).\
... group_by(users.c.name).\
... having(func.length(users.c.name) > 4)
@@ -2127,7 +2134,7 @@ is the DISTINCT modifier. A simple DISTINCT clause can be added using the
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.name]).\
+ >>> stmt = select(users.c.name).\
... where(addresses.c.email_address.
... contains(users.c.name)).\
... distinct()
@@ -2149,7 +2156,7 @@ into the current backend's methodology:
.. sourcecode:: pycon+sql
- >>> stmt = select([users.c.name, addresses.c.email_address]).\
+ >>> stmt = select(users.c.name, addresses.c.email_address).\
... select_from(users.join(addresses)).\
... limit(1).offset(1)
>>> conn.execute(stmt).fetchall()
@@ -2260,7 +2267,7 @@ subquery using :meth:`_expression.Select.scalar_subquery`:
.. sourcecode:: pycon+sql
- >>> stmt = select([addresses.c.email_address]).\
+ >>> stmt = select(addresses.c.email_address).\
... where(addresses.c.user_id == users.c.id).\
... limit(1)
>>> conn.execute(users.update().values(fullname=stmt.scalar_subquery()))
diff --git a/doc/build/errors.rst b/doc/build/errors.rst
index b6554962d..961aa4d70 100644
--- a/doc/build/errors.rst
+++ b/doc/build/errors.rst
@@ -36,9 +36,58 @@ most common runtime errors as well as programming time errors.
Legacy API Features
===================
+.. _error_c9ae:
+
+select() construct created in "legacy" mode; keyword arguments, etc.
+--------------------------------------------------------------------
+
+The :func:`_expression.select` construct has been updated as of SQLAlchemy
+1.4 to support the newer calling style that will be standard in
+:ref:`SQLAlchemy 2.0 <error_b8d9>`. For backwards compatibility in the
+interm, the construct accepts arguments in both the "legacy" style as well
+as the "new" style.
+
+The "new" style features that column and table expressions are passed
+positionally to the :func:`_expression.select` construct only; any other
+modifiers to the object must be passed using subsequent method chaining::
+
+ # this is the way to do it going forward
+ stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)
+
+For comparison, a :func:`_expression.select` in legacy forms of SQLAlchemy,
+before methods like :meth:`.Select.where` were even added, would like::
+
+ # this is how it was documented in original SQLAlchemy versions
+ # many years ago
+ stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid)
+
+Or even that the "whereclause" would be passed positionally::
+
+ # this is also how it was documented in original SQLAlchemy versions
+ # many years ago
+ stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid)
+
+For some years now, the additional "whereclause" and other arguments that are
+accepted have been removed from most narrative documentation, leading to a
+calling style that is most familiar as the list of column arguments passed
+as a list, but no further arguments::
+
+ # this is how it's been documented since around version 1.0 or so
+ stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid)
+
+.. seealso::
+
+ :ref:`error_b8d9`
+
+ :ref:`change_5284`
+
+ :ref:`migration_20_toplevel`
+
+
+
.. _error_b8d9:
-The <some function> in SQLAlchemy 2.0 will no longer <something>; use the "future" construct
+The <some function> in SQLAlchemy 2.0 will no longer <something>
--------------------------------------------------------------------------------------------
SQLAlchemy 2.0 is expected to be a major shift for a wide variety of key