summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2020-07-08 15:07:44 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2020-07-08 15:07:44 +0000
commitb330ffbc13ddb4274a004eab6a13ce40d641e555 (patch)
tree88605188d6adac26c77defa544fc5cde208952f5 /doc
parenta6d8b674e92ef1cabdb2ab85490397f3ed12a42c (diff)
parent91f376692d472a5bf0c4b4033816250ec1ce3ab6 (diff)
downloadsqlalchemy-b330ffbc13ddb4274a004eab6a13ce40d641e555.tar.gz
Merge "Add future=True to create_engine/Session; unify select()"
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