diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-06-26 16:15:19 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-07-08 11:05:11 -0400 |
| commit | 91f376692d472a5bf0c4b4033816250ec1ce3ab6 (patch) | |
| tree | 31f7f72cbe981eb73ed0ba11808d4fb5ae6b7d51 /doc | |
| parent | 3dc9a4a2392d033f9d1bd79dd6b6ecea6281a61c (diff) | |
| download | sqlalchemy-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.rst | 49 | ||||
| -rw-r--r-- | doc/build/changelog/migration_20.rst | 34 | ||||
| -rw-r--r-- | doc/build/changelog/unreleased_14/5284.rst | 16 | ||||
| -rw-r--r-- | doc/build/core/tutorial.rst | 147 | ||||
| -rw-r--r-- | doc/build/errors.rst | 51 |
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 |
