1.. _migration_20_toplevel:
2
3=============================
4Migrating to SQLAlchemy 2.0
5=============================
6
7.. admonition:: About this document
8
9    SQLAlchemy 2.0 presents a major shift for a wide variety of key
10    SQLAlchemy usage patterns in both the Core and ORM components.   The goal
11    of this release is to make a slight readjustment in some of the most
12    fundamental assumptions of SQLAlchemy since its early beginnings, and to
13    deliver a newly streamlined usage model that is hoped to be significantly
14    more minimalist and consistent between the Core and ORM components, as well
15    as more capable.   The move of Python to be Python 3 only as well as the
16    emergence of gradual typing systems for Python 3 are the initial
17    inspirations for this shift, as is the changing nature of the Python
18    community which now includes not just hardcore database programmers but a
19    vast new community of data scientists and students of many different
20    disciplines.
21
22    SQLAlchemy started with Python 2.3 which had no context managers, no
23    function decorators, Unicode as a second class feature, and a variety of
24    other shortcomings that would be unknown today.  The biggest changes in
25    SQLAlchemy 2.0 are targeting the residual assumptions left over from this
26    early period in SQLAlchemy's development as well as the leftover artifacts
27    resulting from the incremental  introduction of key API features such as
28    :class:`.orm.query.Query`  and Declarative. It also hopes standardize some
29    newer capabilities that have proven to be very effective.
30
31
32Overview
33========
34
35The SQLAlchemy 2.0 transition presents itself in the SQLAlchemy 1.4 release as
36a series of steps that allow an application of any size or complexity to be
37migrated to SQLAlchemy 2.0 using a gradual, iterative process.  Lessons learned
38from the Python 2 to Python 3 transition have inspired a system that intends to
39as great a degree as possible to not require any "breaking" changes, or any
40change that would need to be made universally or not at all.
41
42As a means of both proving the 2.0 architecture as well as allowing a fully
43iterative transition environment, the entire scope of 2.0's new APIs and
44features are present and available within the 1.4 series; this includes
45major new areas of functionality such as the SQL caching system, the new ORM
46statement execution model, new transactional paradigms for both ORM and Core, a
47new ORM declarative system that unifies classical and declarative mapping,
48support for Python dataclasses, and asyncio support for Core and ORM.
49
50The steps to achieve 2.0 migration are in the following subsections; overall,
51the general strategy is that once an application runs on 1.4 with all
52warning flags turned on and does not emit any 2.0-deprecation warnings, it is
53now cross-compatible with SQLAlchemy 2.0.
54
55
56First Prerequisite, step one - A Working 1.3 Application
57---------------------------------------------------------
58
59The first step is getting an existing application onto 1.4, in the case of
60a typical non trivial application, is to ensure it runs on SQLAlchemy 1.3 with
61no deprecation warnings.   Release 1.4 does have a few changes linked to
62conditions that warn in previous version, including some warnings that were
63introduced in 1.3, in particular some changes to the behavior of the
64:paramref:`_orm.relationship.viewonly` and
65:paramref:`_orm.relationship.sync_backref` flags.
66
67For best results, the application should be able to run, or pass all of its
68tests, with the latest SQLAlchemy 1.3 release with no SQLAlchemy deprecation
69warnings; these are warnings emitted for the :class:`_exc.SADeprecationWarning`
70class.
71
72First Prerequisite, step two - A Working 1.4 Application
73--------------------------------------------------------
74
75Once the application is good to go on SQLAlchemy 1.3, the next step is to get
76it running on SQLAlchemy 1.4.  In the vast majority of cases, applications
77should run without problems from SQLAlchemy 1.3 to 1.4.   However, it's always
78the case between any 1.x and 1.y release, APIs and behaviors have changed
79either subtly or in some cases a little less subtly, and the SQLAlchemy
80project always gets a good deal of regression reports for the first few
81months.
82
83The 1.x->1.y release process usually has a few changes around the margins
84that are a little bit more dramatic and are based around use cases that are
85expected to be very seldom if at all used.   For 1.4, the changes identified
86as being in this realm are as follows:
87
88* :ref:`change_5526` - this impacts code that would be manipulating the
89  :class:`_engine.URL` object and may impact code that makes use of the
90  :class:`_engine.CreateEnginePlugin` extension point.   This is an uncommon
91  case but may affect in particular some test suites that are making use of
92  special database provisioning logic.   A github search for code that uses
93  the relatively new and little-known :class:`_engine.CreateEnginePlugin`
94  class found two projects that were unaffected by the change.
95
96* :ref:`change_4617` - this change may impact code that was somehow relying
97  upon behavior that was mostly unusable in the :class:`_sql.Select` construct,
98  where it would create unnamed subqueries that were usually confusing and
99  non-working.  These subqueries would be rejected by most databases in any
100  case as a name is usually required except on SQLite, however it is possible
101  some applications will need to adjust some queries that are inadvertently
102  relying upon this.
103
104* :ref:`change_select_join` - somewhat related, the :class:`_sql.Select` class
105  featured ``.join()`` and ``.outerjoin()`` methods that implicitly created a
106  subquery and then returned a :class:`_sql.Join` construct, which again would
107  be mostly useless and produced lots of confusion.  The decision was made to
108  move forward with the vastly more useful 2.0-style join-building approach
109  where these methods now work the same way as the ORM :meth:`_orm.Query.join`
110  method.
111
112* :ref:`change_deferred_construction` - some error messages related to
113  construction of a :class:`_orm.Query` or :class:`_sql.Select` may not be
114  emitted until compilation / execution, rather than at construction time.
115  This might impact some test suites that are testing against failure modes.
116
117For the full overview of SQLAlchemy 1.4 changes, see the
118:doc:`/changelog/migration_14` document.
119
120Migration to 2.0 Step One - Python 3 only (Python 3.6 minimum)
121--------------------------------------------------------------
122
123SQLAlchemy 2.0 was first inspired by the fact that Python 2's EOL was in
1242020.   SQLAlchemy is taking a longer period of time than other major
125projects to drop Python 2.7 support, since it is not too much in the way
126of things for the moment.   However, version 2.0 hopes to start embracing
127:pep:`484` and other new features to a great degree, so it is likely
128that release 1.4 will be the last Python 2 supporting version, even if
129there is a SQLAlchemy 1.5 (which is also unlikely at the moment).
130
131In order to use SQLAlchemy 2.0, the application will need to be runnable on
132at least **Python 3.6** as of this writing.  SQLAlchemy 1.4 now supports
133Python 3.6 or newer within the Python 3 series; throughout the 1.4 series,
134the application can remain running on Python 2.7 or on at least Python 3.6.
135
136.. _migration_20_deprecations_mode:
137
138Migration to 2.0 Step Two - Turn on RemovedIn20Warnings
139-------------------------------------------------------
140
141SQLAlchemy 1.4 features a conditional deprecation warning system inspired
142by the Python "-3" flag that would indicate legacy patterns in a running
143application.   For SQLAlchemy 1.4, the :class:`_exc.RemovedIn20Warning`
144deprecation class is emitted only when an environment variable
145``SQLALCHEMY_WARN_20`` is set to either of ``true`` or ``1``.
146
147Given the example program below::
148
149  from sqlalchemy import column
150  from sqlalchemy import create_engine
151  from sqlalchemy import select
152  from sqlalchemy import table
153
154
155  engine = create_engine("sqlite://")
156
157  engine.execute("CREATE TABLE foo (id integer)")
158  engine.execute("INSERT INTO foo (id) VALUES (1)")
159
160
161  foo = table("foo", column("id"))
162  result = engine.execute(select([foo.c.id]))
163
164  print(result.fetchall())
165
166The above program uses several patterns that many users will already identify
167as "legacy", namely the use of the :meth:`_engine.Engine.execute` method
168that's part of the :ref:`connectionless execution <dbengine_implicit>`
169system.  When we run the above program against 1.4, it returns a single line::
170
171  $ python test3.py
172  [(1,)]
173
174To enable "2.0 deprecations mode", we enable the ``SQLALCHEMY_WARN_20=1``
175variable, and additionally ensure that a `warnings filter`_ that will not
176suppress any warnings is selected::
177
178    SQLALCHEMY_WARN_20=1 python -W always::DeprecationWarning test3.py
179
180Since the reported warning location is not always in the correct place, locating
181the offending code may be difficult without the full stacktrace. This can be achieved
182by transforming the warnings to exceptions by specifying the ``error`` warning filter,
183using Python option ``-W error::DeprecationWarning``.
184
185.. _warnings filter: https://docs.python.org/3/library/warnings.html#the-warnings-filter
186
187With warnings turned on, our program now has a lot to say::
188
189  $ SQLALCHEMY_WARN_20=1 python2 -W always::DeprecationWarning test3.py
190  test3.py:9: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
191    engine.execute("CREATE TABLE foo (id integer)")
192  /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:2856: RemovedIn20Warning: Passing a string to Connection.execute() is deprecated and will be removed in version 2.0.  Use the text() construct, or the Connection.exec_driver_sql() method to invoke a driver-level SQL string. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
193    return connection.execute(statement, *multiparams, **params)
194  /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:1639: RemovedIn20Warning: The current statement is being autocommitted using implicit autocommit.Implicit autocommit will be removed in SQLAlchemy 2.0.   Use the .begin() method of Engine or Connection in order to use an explicit transaction for DML and DDL statements. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
195    self._commit_impl(autocommit=True)
196  test3.py:10: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
197    engine.execute("INSERT INTO foo (id) VALUES (1)")
198  /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:2856: RemovedIn20Warning: Passing a string to Connection.execute() is deprecated and will be removed in version 2.0.  Use the text() construct, or the Connection.exec_driver_sql() method to invoke a driver-level SQL string. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
199    return connection.execute(statement, *multiparams, **params)
200  /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:1639: RemovedIn20Warning: The current statement is being autocommitted using implicit autocommit.Implicit autocommit will be removed in SQLAlchemy 2.0.   Use the .begin() method of Engine or Connection in order to use an explicit transaction for DML and DDL statements. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
201    self._commit_impl(autocommit=True)
202  /home/classic/dev/sqlalchemy/lib/sqlalchemy/sql/selectable.py:4271: RemovedIn20Warning: The legacy calling style of select() is deprecated and will be removed in SQLAlchemy 2.0.  Please use the new calling style described at select(). (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
203    return cls.create_legacy_select(*args, **kw)
204  test3.py:14: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
205    result = engine.execute(select([foo.c.id]))
206  [(1,)]
207
208With the above guidance, we can migrate our program to use 2.0 styles, and
209as a bonus our program is much clearer::
210
211  from sqlalchemy import column
212  from sqlalchemy import create_engine
213  from sqlalchemy import select
214  from sqlalchemy import table
215  from sqlalchemy import text
216
217
218  engine = create_engine("sqlite://")
219
220  # don't rely on autocommit for DML and DDL
221  with engine.begin() as connection:
222      # use connection.execute(), not engine.execute()
223      # use the text() construct to execute textual SQL
224      connection.execute(text("CREATE TABLE foo (id integer)"))
225      connection.execute(text("INSERT INTO foo (id) VALUES (1)"))
226
227
228  foo = table("foo", column("id"))
229
230  with engine.connect() as connection:
231      # use connection.execute(), not engine.execute()
232      # select() now accepts column / table expressions positionally
233      result = connection.execute(select(foo.c.id))
234
235  print(result.fetchall())
236
237
238The goal of "2.0 deprecations mode" is that a program which runs with no
239:class:`_exc.RemovedIn20Warning` warnings with "2.0 deprecations mode" turned
240on is then ready to run in SQLAlchemy 2.0.
241
242
243Migration to 2.0 Step Three - Resolve all RemovedIn20Warnings
244--------------------------------------------------------------
245
246Code can be developed iteratively to resolve these warnings.  Within
247the SQLAlchemy project itself, the approach taken is as follows:
248
2491. enable the ``SQLALCHEMY_WARN_20=1`` environment variable in the test suite,
250   for SQLAlchemy this is in the tox.ini file
251
2522. Within the setup for the test suite, set up a series of warnings filters
253   that will select for particular subsets of warnings to either raise an
254   exception, or to be ignored (or logged).   Work with just one subgroup of warnings
255   at a time.  Below, a warnings filter is configured for an application where
256   the change to the Core level ``.execute()`` calls will be needed in order
257   for all tests to pass, but all other 2.0-style warnings will be suppressed:
258
259   .. sourcecode::
260
261        import warnings
262        from sqlalchemy import exc
263
264        # for warnings not included in regex-based filter below, just log
265        warnings.filterwarnings(
266          "always", category=exc.RemovedIn20Warning
267        )
268
269        # for warnings related to execute() / scalar(), raise
270        for msg in [
271            r"The (?:Executable|Engine)\.(?:execute|scalar)\(\) function",
272            r"The current statement is being autocommitted using implicit "
273            "autocommit,",
274            r"The connection.execute\(\) method in SQLAlchemy 2.0 will accept "
275            "parameters as a single dictionary or a single sequence of "
276            "dictionaries only.",
277            r"The Connection.connect\(\) function/method is considered legacy",
278            r".*DefaultGenerator.execute\(\)",
279        ]:
280          warnings.filterwarnings(
281              "error", message=msg, category=exc.RemovedIn20Warning,
282          )
283
2843. As each sub-category of warnings are resolved in the application, new
285   warnings that are caught by the "always" filter can be added to the list
286   of "errors" to be resolved.
287
2884. Once no more warnings are emitted, the filter can be removed.
289
290Migration to 2.0 Step Four - Use the ``future`` flag on Engine
291--------------------------------------------------------------
292
293The :class:`_engine.Engine` object features an updated
294transaction-level API in version 2.0.  In 1.4, this new API is available
295by passing the flag ``future=True`` to the :func:`_sa.create_engine`
296function.
297
298When the :paramref:`_sa.create_engine.future` flag is used, the :class:`_future.Engine`
299and :class:`_future.Connection` objects support the 2.0 API fully and not at all
300any legacy features, including the new argument format for :meth:`_future.Connection.execute`,
301the removal of "implicit autocommit", string statements require the
302:func:`_sql.text` construct unless the :meth:`_future.Connection.exec_driver_sql`
303method is used, and connectionless execution from the :class:`_future.Engine`
304is removed.
305
306If all :class:`_exc.RemovedIn20Warning` warnings have been resolved regarding
307use of the :class:`_engine.Engine` and :class:`_engine.Connection`, then the
308:paramref:`_sa.create_engine.future` flag may be enabled and there should be
309no errors raised.
310
311The new engine is described at :class:`_future.Engine` which delivers a new
312:class:`_future.Connection` object.    In addition to the above changes, the,
313:class:`_future.Connection` object features
314:meth:`_future.Connection.commit` and
315:meth:`_future.Connection.rollback` methods, to support the new
316"commit-as-you-go" mode of operation::
317
318
319    from sqlalchemy import create_engine
320
321    engine = create_engine("postgresql:///")
322
323    with engine.connect() as conn:
324        conn.execute(text("insert into table (x) values (:some_x)"), {"some_x": 10})
325
326        conn.commit()  # commit as you go
327
328
329
330Migration to 2.0 Step Four - Use the ``future`` flag on Session
331---------------------------------------------------------------
332
333The :class:`_orm.Session` object also features an updated transaction/connection
334level API in version 2.0.  This API is available in 1.4 using the
335:paramref:`_orm.Session.future` flag on :class:`_orm.Session` or on
336:class:`_orm.sessionmaker`.
337
338The :class:`_orm.Session` object supports "future" mode in place, and involves
339these changes:
340
3411. The :class:`_orm.Session` no longer supports "bound metadata" when it
342   resolves the engine to be used for connectivity.   This means that an
343   :class:`_engine.Engine` object **must** be passed to the constructor (this
344   may be either a legacy or future style object).
345
3462. The :paramref:`_orm.Session.begin.subtransactions` flag is no longer
347   supported.
348
3493. The :meth:`_orm.Session.commit` method always emits a COMMIT to the database,
350   rather than attempting to reconcile "subtransactions".
351
3524. The :meth:`_orm.Session.rollback` method always rolls back the full
353   stack of transactions at once, rather than attempting to keep
354   "subtransactions" in place.
355
356
357The :class:`_orm.Session` also supports more flexible creational patterns
358in 1.4 which are now closely matched to the patterns used by the
359:class:`_engine.Connection` object.   Highlights include that the
360:class:`_orm.Session` may be used as a context manager::
361
362    from sqlalchemy.orm import Session
363    with Session(engine) as session:
364        session.add(MyObject())
365        session.commit()
366
367In addition, the :class:`_orm.sessionmaker` object supports a
368:meth:`_orm.sessionmaker.begin` context manager that will create a
369:class:`_orm.Session` and begin /commit a transaction in one block::
370
371    from sqlalchemy.orm import sessionmaker
372
373    Session = sessionmaker(engine)
374
375    with Session.begin() as session:
376        session.add(MyObject())
377
378See the section :ref:`orm_session_vs_engine` for a comparison of
379:class:`_orm.Session` creational patterns compared to those of
380:class:`_engine.Connection`.
381
382Once the application passes all tests/ runs with ``SQLALCHEMY_WARN_20=1``
383and all ``exc.RemovedIn20Warning`` occurrences set to raise an error,
384**the application is ready!**.
385
386The sections that follow will detail the specific changes to make for all
387major API modifications.
388
389
3902.0 Migration - Core Connection / Transaction
391=============================================
392
393
394.. _migration_20_autocommit:
395
396Library-level (but not driver level) "Autocommit" removed from both Core and ORM
397--------------------------------------------------------------------------------
398
399**Synopsis**
400
401In SQLAlchemy 1.x, the following statements will automatically commit
402the underlying DBAPI transaction, but in SQLAlchemy
4032.0 this will not occur::
404
405    conn = engine.connect()
406
407    # won't autocommit in 2.0
408    conn.execute(some_table.insert().values(foo='bar'))
409
410Nor will this autocommit::
411
412    conn = engine.connect()
413
414    # won't autocommit in 2.0
415    conn.execute(text("INSERT INTO table (foo) VALUES ('bar')"))
416
417The common workaround for custom DML that requires commit, the "autocommit"
418execution option, will be removed::
419
420
421    conn = engine.connect()
422
423    # won't autocommit in 2.0
424    conn.execute(
425      text("EXEC my_procedural_thing()").execution_options(autocommit=True)
426    )
427
428
429**Migration to 2.0**
430
431The method that is cross-compatible with :term:`1.x style` and :term:`2.0
432style` execution is to make use of the :meth:`_engine.Connection.begin` method,
433or the :meth:`_engine.Engine.begin` context manager::
434
435    with engine.begin() as conn:
436        conn.execute(some_table.insert().values(foo='bar'))
437        conn.execute(some_other_table.insert().values(bat='hoho'))
438
439    with engine.connect() as conn:
440        with conn.begin():
441            conn.execute(some_table.insert().values(foo='bar'))
442            conn.execute(some_other_table.insert().values(bat='hoho'))
443
444    with engine.begin() as conn:
445        conn.execute(text("EXEC my_procedural_thing()"))
446
447When using :term:`2.0 style` with the :paramref:`_sa.create_engine.future`
448flag, "commit as you go" style may also be used, as the
449:class:`_future.Connection` features **autobegin** behavior, which takes place
450when a statement is first invoked in the absence of an explicit call to
451:meth:`_future.Connection.begin`::
452
453    with engine.connect() as conn:
454        conn.execute(some_table.insert().values(foo='bar'))
455        conn.execute(some_other_table.insert().values(bat='hoho'))
456
457        conn.commit()
458
459When :ref:`2.0 deprecations mode <migration_20_deprecations_mode>` is enabled,
460a warning will emit when the deprecated "autocommit" feature takes place,
461indicating those places where an explicit transaction should be noted.
462
463
464**Discussion**
465
466SQLAlchemy's first releases were at odds with the spirit of the Python DBAPI
467(:pep:`249`) in that it tried to hide :pep:`249`'s emphasis on "implicit begin"
468and "explicit commit" of transactions.    Fifteen years later we now see this
469was essentially a mistake, as SQLAlchemy's many patterns that attempt to "hide"
470the presence of a transaction make for a more complex API which works
471inconsistently and is extremely confusing to especially those users who are new
472to relational databases and ACID transactions in general.   SQLAlchemy 2.0 will
473do away with all attempts to implicitly commit transactions, and usage patterns
474will always require that the user demarcate the "beginning" and the "end" of a
475transaction in some way, in the same way as reading or writing to a file in
476Python has a "beginning" and an "end".
477
478In the case of autocommit for a pure textual statement, there is actually a
479regular expression that parses every statement in order to detect autocommit!
480Not surprisingly, this regex is continuously failing to accommodate for various
481kinds of statements and  stored procedures that imply a "write" to the
482database, leading to ongoing confusion as some statements produce results in
483the database and others don't.  By preventing the user from being aware of the
484transactional concept, we get a lot of bug reports on this one because users
485don't understand that databases always use a transaction, whether or not some
486layer is autocommitting it.
487
488SQLAlchemy 2.0 will require that all database actions at every level be
489explicit as to how the transaction should be used.    For the vast majority
490of Core use cases, it's the pattern that is already recommended::
491
492    with engine.begin() as conn:
493        conn.execute(some_table.insert().values(foo='bar'))
494
495For "commit as you go, or rollback instead" usage, which resembles how the
496:class:`_orm.Session` is normally used today, the "future" version of
497:class:`_future.Connection`, which is the one that is returned from an
498:class:`_future.Engine` that was created using the
499:paramref:`_sa.create_engine.future` flag, includes new
500:meth:`_future.Connection.commit` and :meth:`_future.Connection.rollback`
501methods, which act upon a transaction that is now begun automatically when
502a statement is first invoked::
503
504    # 1.4 / 2.0 code
505
506    from sqlalchemy import create_engine
507
508    engine = create_engine(..., future=True)
509
510    with engine.connect() as conn:
511        conn.execute(some_table.insert().values(foo='bar'))
512        conn.commit()
513
514        conn.execute(text("some other SQL"))
515        conn.rollback()
516
517Above, the ``engine.connect()`` method will return a :class:`_engine.Connection` that
518features **autobegin**, meaning the ``begin()`` event is emitted when the
519execute method is first used (note however that there is no actual "BEGIN" in
520the Python DBAPI).  "autobegin" is a new pattern in SQLAlchemy 1.4 that
521is featured both by :class:`_future.Connection` as well as the ORM
522:class:`_orm.Session` object; autobegin allows that the :meth:`_future.Connection.begin`
523method may be called explicitly when the object is first acquired, for schemes
524that wish to demarcate the beginning of the transaction, but if the method
525is not called, then it occurs implicitly when work is first done on the object.
526
527The removal of "autocommit" is closely related to the removal of
528"connectionless" execution discussed at :ref:`migration_20_implicit_execution`.
529All of these legacy patterns built up from the fact that Python did not have
530context managers or decorators when SQLAlchemy was first created, so there were
531no convenient idiomatic patterns for demarcating the use of a resource.
532
533Driver-level autocommit remains available
534^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
535
536True "autocommit" behavior is now widely available with most DBAPI
537implementations, and is supported by SQLAlchemy via the
538:paramref:`_engine.Connection.execution_options.isolation_level` parameter as
539discussed at :ref:`dbapi_autocommit`.  True autocommit is treated as an "isolation level"
540so that the structure of application code does not change when autocommit is
541used; the :meth:`_engine.Connection.begin` context manager as well as
542methods like :meth:`_future.Connection.commit` may still be used, they are
543simply no-ops at the database driver level when DBAPI-level autocommit
544is turned on.
545
546.. _migration_20_implicit_execution:
547
548"Implicit" and "Connectionless" execution, "bound metadata" removed
549--------------------------------------------------------------------
550
551**Synopsis**
552
553The ability to associate an :class:`_engine.Engine` with a :class:`_schema.MetaData`
554object, which then makes available a range of so-called "connectionless"
555execution patterns, is removed::
556
557    from sqlalchemy import MetaData
558
559    metadata_obj = MetaData(bind=engine)  # no longer supported
560
561    metadata_obj.create_all()   # requires Engine or Connection
562
563    metadata_obj.reflect()  # requires Engine or Connection
564
565    t = Table('t', metadata_obj, autoload=True)  # use autoload_with=engine
566
567    result = engine.execute(t.select())  # no longer supported
568
569    result = t.select().execute()  # no longer supported
570
571**Migration to 2.0**
572
573For schema level patterns, explicit use of an :class:`_engine.Engine`
574or :class:`_engine.Connection` is required.   The :class:`_engine.Engine`
575may still be used directly as the source of connectivity for a
576:meth:`_schema.MetaData.create_all` operation or autoload operation.
577For executing statements, only the :class:`_engine.Connection` object
578has a :meth:`_engine.Connection.execute` method (in addition to
579the ORM-level :meth:`_orm.Session.execute` method)::
580
581
582    from sqlalchemy import MetaData
583
584    metadata_obj = MetaData()
585
586    # engine level:
587
588    # create tables
589    metadata_obj.create_all(engine)
590
591    # reflect all tables
592    metadata_obj.reflect(engine)
593
594    # reflect individual table
595    t = Table('t', metadata_obj, autoload_with=engine)
596
597
598    # connection level:
599
600
601    with engine.connect() as connection:
602        # create tables, requires explicit begin and/or commit:
603        with connection.begin():
604            metadata_obj.create_all(connection)
605
606        # reflect all tables
607        metadata_obj.reflect(connection)
608
609        # reflect individual table
610        t = Table('t', metadata_obj, autoload_with=connection)
611
612        # execute SQL statements
613        result = conn.execute(t.select())
614
615
616**Discussion**
617
618
619The Core documentation has already standardized on the desired pattern here,
620so it is likely that most modern applications would not have to change
621much in any case, however there are likely many applications that still
622rely upon ``engine.execute()`` calls that will need to be adjusted.
623
624"Connectionless" execution refers to the still fairly popular pattern of
625invoking ``.execute()`` from the :class:`_engine.Engine`::
626
627  result = engine.execute(some_statement)
628
629The above operation implicitly procures a :class:`_engine.Connection` object,
630and runs the ``.execute()`` method on it.  While this appears to be a simple
631convenience feature, it has been shown to give rise to several issues:
632
633* Programs that feature extended strings of ``engine.execute()`` calls have
634  become prevalent, overusing a feature that was intended to be seldom used and
635  leading to inefficient non-transactional applications.  New users are
636  confused as to the difference between ``engine.execute()`` and
637  ``connection.execute()`` and the nuance between these two approaches is
638  often not understood.
639
640* The feature relies upon the "application level autocommit" feature in order
641  to make sense, which itself is also being removed as it is also
642  :ref:`inefficient and misleading <migration_20_autocommit>`.
643
644* In order to handle result sets, ``Engine.execute`` returns a result object
645  with unconsumed cursor results.  This cursor result necessarily still links
646  to the DBAPI connection which remains in an open transaction, all of which is
647  released once the result set has fully consumed the rows waiting within the
648  cursor.   This means that ``Engine.execute`` does not actually close out the
649  connection resources that it claims to be managing when the call is complete.
650  SQLAlchemy's "autoclose" behavior is well-tuned enough that users don't
651  generally report any negative effects from this system, however it remains
652  an overly implicit and inefficient system left over from SQLAlchemy's
653  earliest releases.
654
655The removal of "connectionless" execution then leads to the removal of
656an even more legacy pattern, that of "implicit, connectionless" execution::
657
658  result = some_statement.execute()
659
660The above pattern has all the issues of "connectionless" execution, plus it
661relies upon the "bound metadata" pattern, which SQLAlchemy has tried to
662de-emphasize for many years.   This was SQLAlchemy's very first advertised
663usage model in version 0.1, which became obsolete almost immediately when
664the :class:`_engine.Connection` object was introduced and later Python
665context managers provided a better pattern for using resources within a
666fixed scope.
667
668With implicit execution removed, "bound metadata" itself also no longer has
669a purpose within this system.   In modern use "bound metadata" tends to still
670be somewhat convenient for working within :meth:`_schema.MetaData.create_all`
671calls as well as with :class:`_orm.Session` objects, however having these
672functions receive an :class:`_engine.Engine` explicitly provides for clearer
673application design.
674
675Many Choices becomes One Choice
676^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
677
678Overall, the above executional patterns were introduced in SQLAlchemy's
679very first 0.1 release before the :class:`_engine.Connection` object even existed.
680After many years of de-emphasizing these patterns, "implicit, connectionless"
681execution and "bound metadata" are no longer as widely used so in 2.0 we seek
682to finally reduce the number of choices for how to execute a statement in
683Core from "many choices"::
684
685  # many choices
686
687  # bound metadata?
688  metadata_obj = MetaData(engine)
689
690  # or not?
691  metadata_obj = MetaData()
692
693  # execute from engine?
694  result = engine.execute(stmt)
695
696  # or execute the statement itself (but only if you did
697  # "bound metadata" above, which means you can't get rid of "bound" if any
698  # part of your program uses this form)
699  result = stmt.execute()
700
701  # execute from connection, but it autocommits?
702  conn = engine.connect()
703  conn.execute(stmt)
704
705  # execute from connection, but autocommit isn't working, so use the special
706  # option?
707  conn.execution_options(autocommit=True).execute(stmt)
708
709  # or on the statement ?!
710  conn.execute(stmt.execution_options(autocommit=True))
711
712  # or execute from connection, and we use explicit transaction?
713  with conn.begin():
714      conn.execute(stmt)
715
716to "one choice", where by "one choice" we mean "explicit connection with
717explicit transaction"; there are still a few ways to demarcate
718transaction blocks depending on need.  The "one choice" is to procure a
719:class:`_engine.Connection` and then to explicitly demarcate the transaction,
720in the case that the operation is a write operation::
721
722  # one choice - work with explicit connection, explicit transaction
723  # (there remain a few variants on how to demarcate the transaction)
724
725  # "begin once" - one transaction only per checkout
726  with engine.begin() as conn:
727      result = conn.execute(stmt)
728
729  # "commit as you go" - zero or more commits per checkout
730  with engine.connect() as conn:
731      result = conn.execute(stmt)
732      conn.commit()
733
734  # "commit as you go" but with a transaction block instead of autobegin
735  with engine.connect() as conn:
736      with conn.begin():
737          result = conn.execute(stmt)
738
739
740execute() method more strict, execution options are more prominent
741-------------------------------------------------------------------------------
742
743**Synopsis**
744
745The argument patterns that may be used with the :meth:`_engine.Connection`
746execute method in SQLAlchemy 2.0 are highly simplified, removing many previously
747available argument patterns.  The new API in the 1.4 series is described at
748:meth:`_future.Connection`. The examples below illustrate the patterns that
749require modification::
750
751
752  connection = engine.connect()
753
754  # direct string SQL not supported; use text() or exec_driver_sql() method
755  result = connection.execute("select * from table")
756
757  # positional parameters no longer supported, only named
758  # unless using exec_driver_sql()
759  result = connection.execute(table.insert(), ('x', 'y', 'z'))
760
761  # **kwargs no longer accepted, pass a single dictionary
762  result = connection.execute(table.insert(), x=10, y=5)
763
764  # multiple *args no longer accepted, pass a list
765  result = connection.execute(
766      table.insert(),
767      {"x": 10, "y": 5}, {"x": 15, "y": 12}, {"x": 9, "y": 8}
768  )
769
770
771**Migration to 2.0**
772
773The new :meth:`_future.Connection.execute` method now accepts a subset of the
774argument styles that are accepted by the 1.x :meth:`_engine.Connection.execute`
775method, so the following code is cross-compatible between 1.x and 2.0::
776
777
778  connection = engine.connect()
779
780  from sqlalchemy import text
781  result = connection.execute(text("select * from table"))
782
783  # pass a single dictionary for single statement execution
784  result = connection.execute(table.insert(), {"x": 10, "y": 5})
785
786  # pass a list of dictionaries for executemany
787  result = connection.execute(
788      table.insert(),
789      [{"x": 10, "y": 5}, {"x": 15, "y": 12}, {"x": 9, "y": 8}]
790  )
791
792
793
794**Discussion**
795
796The use of ``*args`` and ``**kwargs`` has been removed both to remove the
797complexity of guessing what kind of arguments were passed to the method, as
798well as to make room for other options, namely the
799:paramref:`_future.Connection.execute.execution_options` dictionary that is now
800available to provide options on a per statement basis. The method is also
801modified so that its use pattern matches that of the
802:meth:`_orm.Session.execute` method, which is a much more prominent API in 2.0
803style.
804
805The removal of direct string SQL is to resolve an inconsistency between
806:meth:`_engine.Connection.execute` and :meth:`_orm.Session.execute`,
807where in the former case the string is passed to the driver raw, and in the
808latter case it is first converted to a :func:`_sql.text` construct.  By
809allowing only :func:`_sql.text` this also limits the accepted parameter
810format to "named" and not "positional".  Finally, the string SQL use case
811is becoming more subject to scrutiny from a security perspective, and
812the :func:`_sql.text` construct has come to represent an explicit boundary
813into the textual SQL realm where attention to untrusted user input must be
814given.
815
816
817.. _migration_20_result_rows:
818
819Result rows act like named tuples
820---------------------------------
821
822**Synopsis**
823
824Version 1.4 introduces an :ref:`all new Result object <change_result_14_core>`
825that in turn returns :class:`_engine.Row` objects, which behave like named
826tuples when using "future" mode::
827
828    engine = create_engine(..., future=True)  # using future mode
829
830    with engine.connect() as conn:
831        result = conn.execute(text("select x, y from table"))
832
833        row = result.first()  # suppose the row is (1, 2)
834
835        "x" in row   # evaluates to False, in 1.x / future=False, this would be True
836
837        1 in row  # evaluates to True, in 1.x / future=False, this would be False
838
839
840**Migration to 2.0**
841
842Application code or test suites that are testing for a particular key
843being present in a row would need to test the ``row.keys()`` collection
844instead.  This is however an unusual use case as a result row is typically
845used by code that already knows what columns are present within it.
846
847**Discussion**
848
849Already part of 1.4, the previous ``KeyedTuple`` class that was used when
850selecting rows from the :class:`_query.Query` object has been replaced by the
851:class:`.Row` class, which is the base of the same :class:`.Row` that comes
852back with Core statement results when using the
853:paramref:`_sa.create_engine.future` flag with :class:`_engine.Engine` (when
854the :paramref:`_sa.create_engine.future` flag is not set, Core result sets use
855the :class:`.LegacyRow` subclass, which maintains backwards-compatible
856behaviors for the ``__contains__()`` method; ORM exclusively uses the
857:class:`.Row` class directly).
858
859This :class:`.Row` behaves like a named tuple, in that it acts as a sequence
860but also supports attribute name access, e.g. ``row.some_column``.  However,
861it also provides the previous "mapping" behavior via the special attribute
862``row._mapping``, which produces a Python mapping such that keyed access
863such as ``row["some_column"]`` can be used.
864
865In order to receive results as mappings up front, the ``mappings()`` modifier
866on the result can be used::
867
868    from sqlalchemy.future.orm import Session
869
870    session = Session(some_engine)
871
872    result = session.execute(stmt)
873    for row in result.mappings():
874        print("the user is: %s" % row["User"])
875
876The :class:`.Row` class as used by the ORM also supports access via entity
877or attribute::
878
879    from sqlalchemy.future import select
880
881    stmt = select(User, Address).join(User.addresses)
882
883    for row in session.execute(stmt).mappings():
884        print("the user is: %s  the address is: %s" % (
885            row[User],
886            row[Address]
887        ))
888
889.. seealso::
890
891    :ref:`change_4710_core`
892
893
8942.0 Migration - Core Usage
895=============================
896
897.. _migration_20_5284:
898
899select() no longer accepts varied constructor arguments, columns are passed positionally
900-----------------------------------------------------------------------------------------
901
902**synopsis**
903
904The :func:`_sql.select` construct as well as the related method :meth:`_sql.FromClause.select`
905will no longer accept keyword arguments to build up elements such as the
906WHERE clause, FROM list and ORDER BY.   The list of columns may now be
907sent positionally, rather than as a list.  Additionally, the :func:`_sql.case` construct
908now accepts its WHEN criteria positionally, rather than as a list::
909
910    # select_from / order_by keywords no longer supported
911    stmt = select([1], select_from=table, order_by=table.c.id)
912
913    # whereclause parameter no longer supported
914    stmt = select([table.c.x], table.c.id == 5)
915
916    # whereclause parameter no longer supported
917    stmt = table.select(table.c.id == 5)
918
919    # list emits a deprecation warning
920    stmt = select([table.c.x, table.c.y])
921
922    # list emits a deprecation warning
923    case_clause = case(
924      [
925        (table.c.x == 5, "five"),
926        (table.c.x == 7, "seven")
927      ],
928      else_="neither five nor seven"
929    )
930
931
932**Migration to 2.0**
933
934Only the "generative" style of :func:`_sql.select` will be supported.  The list
935of columns / tables to SELECT from should be passed positionally.  The
936:func:`_sql.select` construct in SQLAlchemy 1.4 accepts both the legacy
937styles and the new styles using an auto-detection scheme, so the code below
938is cross-compatible with 1.4 and 2.0::
939
940    # use generative methods
941    stmt = select(1).select_from(table).order_by(table.c.id)
942
943    # use generative methods
944    stmt = select(table).where(table.c.id == 5)
945
946    # use generative methods
947    stmt = table.select().where(table.c.id == 5)
948
949    # pass columns clause expressions positionally
950    stmt = select(table.c.x, table.c.y)
951
952    # case conditions passed positionally
953    case_clause = case(
954      (table.c.x == 5, "five"),
955      (table.c.x == 7, "seven"),
956      else_="neither five nor seven"
957    )
958
959**Discussion**
960
961SQLAlchemy has for many years developed a convention for SQL constructs
962accepting an argument either as a list or as positional arguments.   This
963convention states that **structural** elements, those that form the structure
964of a SQL statement, should be passed **positionally**.   Conversely,
965**data** elements, those that form the parameterized data of a SQL statement,
966should be passed **as lists**.   For many years, the :func:`_sql.select`
967construct could not participate in this convention smoothly because of the
968very legacy calling pattern where the "WHERE" clause would be passed positionally.
969SQLAlchemy 2.0 finally resolves this by changing the :func:`_sql.select` construct
970to only accept the "generative" style that has for many years been the only
971documented style in the Core tutorial.
972
973Examples of "structural" vs. "data" elements are as follows::
974
975  # table columns for CREATE TABLE - structural
976  table = Table("table", metadata_obj, Column('x', Integer), Column('y', Integer))
977
978  # columns in a SELECT statement - structural
979  stmt = select(table.c.x, table.c.y)
980
981  # literal elements in an IN clause - data
982  stmt = stmt.where(table.c.y.in_([1, 2, 3]))
983
984.. seealso::
985
986    :ref:`change_5284`
987
988    :ref:`error_c9ae`
989
990insert/update/delete DML no longer accept keyword constructor arguments
991-----------------------------------------------------------------------
992
993**Synopsis**
994
995In a similar way as to the previous change to :func:`_sql.select`, the
996constructor arguments to :func:`_sql.insert`, :func:`_sql.update` and
997:func:`_sql.delete` other than the table argument are essentially removed::
998
999    # no longer supported
1000    stmt = insert(table, values={"x": 10, "y": 15}, inline=True)
1001
1002    # no longer supported
1003    stmt = insert(table, values={"x": 10, "y": 15}, returning=[table.c.x])
1004
1005    # no longer supported
1006    stmt = table.delete(table.c.x > 15)
1007
1008    # no longer supported
1009    stmt = table.update(
1010        table.c.x < 15,
1011        preserve_parameter_order=True
1012    ).values(
1013        [(table.c.y, 20), (table.c.x, table.c.y + 10)]
1014    )
1015
1016**Migration to 2.0**
1017
1018The following examples illustrate generative method use for the above
1019examples::
1020
1021    # use generative methods, **kwargs OK for values()
1022    stmt = insert(table).values(x=10, y=15).inline()
1023
1024    # use generative methods, dictionary also still  OK for values()
1025    stmt = insert(table).values({"x": 10, "y": 15}).returning(table.c.x)
1026
1027    # use generative methods
1028    stmt = table.delete().where(table.c.x > 15)
1029
1030    # use generative methods, ordered_values() replaces preserve_parameter_order
1031    stmt = table.update().where(
1032        table.c.x < 15,
1033    ).ordered_values(
1034        (table.c.y, 20), (table.c.x, table.c.y + 10)
1035    )
1036
1037**Discussion**
1038
1039The API and internals is being simplified for the DML constructs in a similar
1040manner as that of the :func:`_sql.select` construct.
1041
1042
1043
10442.0 Migration - ORM Configuration
1045=============================================
1046
1047Declarative becomes a first class API
1048-------------------------------------
1049
1050**Synopsis**
1051
1052The ``sqlalchemy.ext.declarative`` package is mostly, with some exceptions,
1053moved to the ``sqlalchemy.orm`` package.  The :func:`_orm.declarative_base`
1054and :func:`_orm.declared_attr` functions are present without any behavioral
1055changes.  A new super-implementation of :func:`_orm.declarative_base`
1056known as :class:`_orm.registry` now serves as the top-level ORM configurational
1057construct, which also provides for decorator-based declarative and new
1058support for classical mappings that integrate with the declarative registry.
1059
1060**Migration to 2.0**
1061
1062Change imports::
1063
1064    from sqlalchemy.ext import declarative_base, declared_attr
1065
1066To::
1067
1068    from sqlalchemy.orm import declarative_base, declared_attr
1069
1070**Discussion**
1071
1072After ten years or so of popularity, the ``sqlalchemy.ext.declarative``
1073package is now integrated into the ``sqlalchemy.orm`` namespace, with the
1074exception of the declarative "extension" classes which remain as Declarative
1075extensions.   The change is detailed further in the 1.4 migration guide
1076at :ref:`change_5508`.
1077
1078
1079.. seealso::
1080
1081  :ref:`orm_mapping_classes_toplevel` - all new unified documentation for
1082  Declarative, classical mapping, dataclasses, attrs, etc.
1083
1084
1085  :ref:`change_5508`
1086
1087
1088The original "mapper()" function now a core element of Declarative, renamed
1089----------------------------------------------------------------------------
1090
1091**Synopsis**
1092
1093The :func:`_orm.mapper` function moves behind the scenes to be invoked
1094by higher level APIs.  The new version of this function is the method
1095:meth:`_orm.registry.map_imperatively` taken from a :class:`_orm.registry`
1096object.
1097
1098**Migration to 2.0**
1099
1100Code that works with classical mappings should change imports and code from::
1101
1102    from sqlalchemy.orm import mapper
1103
1104
1105    mapper(SomeClass, some_table, properties={
1106        "related": relationship(SomeRelatedClass)
1107    })
1108
1109To work from a central :class:`_orm.registry` object::
1110
1111    from sqlalchemy.orm import registry
1112
1113    mapper_reg = registry()
1114
1115    mapper_reg.map_imperatively(SomeClass, some_table, properties={
1116        "related": relationship(SomeRelatedClass)
1117    })
1118
1119The above :class:`_orm.registry` is also the source for declarative mappings,
1120and classical mappings now have access to this registry including string-based
1121configuration on :func:`_orm.relationship`::
1122
1123    from sqlalchemy.orm import registry
1124
1125    mapper_reg = registry()
1126
1127    Base = mapper_reg.generate_base()
1128
1129    class SomeRelatedClass(Base):
1130        __tablename__ = 'related'
1131
1132        # ...
1133
1134
1135    mapper_reg.map_imperatively(SomeClass, some_table, properties={
1136        "related": relationship(
1137            "SomeRelatedClass",
1138            primaryjoin="SomeRelatedClass.related_id == SomeClass.id"
1139        )
1140    })
1141
1142
1143**Discussion**
1144
1145By popular demand, "classical mapping" is staying around, however the new
1146form of it is based off of the :class:`_orm.registry` object and is available
1147as :meth:`_orm.registry.map_imperatively`.
1148
1149In addition, the primary rationale used for "classical mapping" is that of
1150keeping the :class:`_schema.Table` setup distinct from the class.  Declarative
1151has always allowed this style using so-called
1152:ref:`hybrid declarative <orm_imperative_table_configuration>`. However, to
1153remove the base class requirement, a first class :ref:`decorator
1154<declarative_config_toplevel>` form has been added.
1155
1156As yet another separate but related enhancement, support for :ref:`Python
1157dataclasses <orm_declarative_dataclasses>` is added as well to both
1158declarative decorator and classical mapping forms.
1159
1160.. seealso::
1161
1162  :ref:`orm_mapping_classes_toplevel` - all new unified documentation for
1163  Declarative, classical mapping, dataclasses, attrs, etc.
1164
11652.0 Migration - ORM Usage
1166=============================================
1167
1168The biggest visible change in SQLAlchemy 2.0 is the use of
1169:meth:`_orm.Session.execute` in conjunction with :func:`_sql.select` to run ORM
1170queries, instead of using :meth:`_orm.Session.query`.  As mentioned elsewhere,
1171there is no plan to actually remove the :meth:`_orm.Session.query` API itself,
1172as it is now implemented by using the new API internally it will remain as a
1173legacy API, and both APIs can be used freely.
1174
1175The table below provides an introduction to the general change in
1176calling form with links to documentation for each technique
1177presented.  The individual migration notes are in the embedded sections
1178following the table, and may include additional notes not summarized here.
1179
1180
1181.. container:: sliding-table
1182
1183  .. list-table:: **Overview of Major ORM Querying Patterns**
1184    :header-rows: 1
1185
1186    * - :term:`1.x style` form
1187      - :term:`2.0 style` form
1188      - See Also
1189
1190    * - ::
1191
1192          session.query(User).get(42)
1193
1194      - ::
1195
1196          session.get(User, 42)
1197
1198      - :ref:`migration_20_get_to_session`
1199
1200    * - ::
1201
1202          session.query(User).all()
1203
1204      - ::
1205
1206          session.execute(
1207              select(User)
1208          ).scalars().all()
1209
1210      - :ref:`migration_20_unify_select`
1211
1212        :meth:`_engine.Result.scalars`
1213
1214    * - ::
1215
1216          session.query(User).\
1217          filter_by(name='some user').one()
1218
1219      - ::
1220
1221          session.execute(
1222              select(User).
1223              filter_by(name="some user")
1224          ).scalar_one()
1225
1226      - :ref:`migration_20_unify_select`
1227
1228        :meth:`_engine.Result.scalar_one`
1229
1230    * - ::
1231
1232          session.query(User).\
1233          filter_by(name='some user').first()
1234
1235
1236      - ::
1237
1238          session.execute(
1239            select(User).
1240            filter_by(name="some user").
1241            limit(1)
1242          ).scalars().first()
1243
1244      - :ref:`migration_20_unify_select`
1245
1246        :meth:`_engine.Result.first`
1247
1248    * - ::
1249
1250            session.query(User).options(
1251                joinedload(User.addresses)
1252            ).all()
1253
1254      - ::
1255
1256            session.execute(
1257                select(User).
1258                options(
1259                  joinedload(User.addresses)
1260                )
1261            ).unique().all()
1262
1263      - :ref:`joinedload_not_uniqued`
1264
1265    * - ::
1266
1267          session.query(User).\
1268              join(Address).\
1269              filter(Address.email == 'e@sa.us').\
1270              all()
1271
1272      - ::
1273
1274          session.execute(
1275              select(User).
1276              join(Address).
1277              where(Address.email == 'e@sa.us')
1278          ).scalars().all()
1279
1280      - :ref:`migration_20_unify_select`
1281
1282        :ref:`orm_queryguide_joins`
1283
1284    * - ::
1285
1286          session.query(User).from_statement(
1287              text("select * from users")
1288          ).all()
1289
1290      - ::
1291
1292          session.execute(
1293              select(User).
1294              from_statement(
1295                  text("select * from users")
1296              )
1297          ).scalars().all()
1298
1299      - :ref:`orm_queryguide_selecting_text`
1300
1301    * - ::
1302
1303          session.query(User).\
1304              join(User.addresses).\
1305              options(
1306                contains_eager(User.addresses)
1307              ).\
1308              populate_existing().all()
1309
1310      - ::
1311
1312          session.execute(
1313              select(User).
1314              join(User.addresses).
1315              options(contains_eager(User.addresses)).
1316              execution_options(populate_existing=True)
1317          ).scalars().all()
1318
1319      -
1320
1321          :ref:`orm_queryguide_execution_options`
1322
1323          :ref:`orm_queryguide_populate_existing`
1324
1325    *
1326      - ::
1327
1328          session.query(User).\
1329              filter(User.name == 'foo').\
1330              update(
1331                  {"fullname": "Foo Bar"},
1332                  synchronize_session="evaluate"
1333              )
1334
1335
1336      - ::
1337
1338          session.execute(
1339              update(User).
1340              where(User.name == 'foo').
1341              values(fullname="Foo Bar").
1342              execution_options(synchronize_session="evaluate")
1343          )
1344
1345      - :ref:`orm_expression_update_delete`
1346
1347    *
1348      - ::
1349
1350          session.query(User).count()
1351
1352      - ::
1353
1354          session.scalar(select(func.count()).select_from(User))
1355          session.scalar(select(func.count(User.id)))
1356
1357      - :meth:`_orm.Session.scalar`
1358
1359.. _migration_20_unify_select:
1360
1361ORM Query Unified with Core Select
1362----------------------------------
1363
1364**Synopsis**
1365
1366The :class:`_orm.Query` object (as well as the :class:`_baked.BakedQuery` and
1367:class:`_horizontal.ShardedQuery` extensions) become long term legacy objects,
1368replaced by the direct usage of the :func:`_sql.select` construct in conjunction
1369with the :meth:`_orm.Session.execute` method.  Results
1370that are returned from :class:`_orm.Query` in the form of lists of objects
1371or tuples, or as scalar ORM objects are returned from :meth:`_orm.Session.execute`
1372uniformly as :class:`_engine.Result` objects, which feature an interface
1373consistent with that of Core execution.
1374
1375Legacy code examples are illustrated below::
1376
1377    session = Session(engine)
1378
1379    # becomes legacy use case
1380    user = session.query(User).filter_by(name='some user').one()
1381
1382    # becomes legacy use case
1383    user = session.query(User).filter_by(name='some user').first()
1384
1385    # becomes legacy use case
1386    user = session.query(User).get(5)
1387
1388    # becomes legacy use case
1389    for user in session.query(User).join(User.addresses).filter(Address.email == 'some@email.com'):
1390        # ...
1391
1392    # becomes legacy use case
1393    users = session.query(User).options(joinedload(User.addresses)).order_by(User.id).all()
1394
1395    # becomes legacy use case
1396    users = session.query(User).from_statement(
1397        text("select * from users")
1398    ).all()
1399
1400    # etc
1401
1402**Migration to 2.0**
1403
1404Because the vast majority of an ORM application is expected to make use of
1405:class:`_orm.Query` objects as well as that the :class:`_orm.Query` interface
1406being available does not impact the new interface, the object will stay
1407around in 2.0 but will no longer be part of documentation nor will it be
1408supported for the most part.  The :func:`_sql.select` construct now suits
1409both the Core and ORM use cases, which when invoked via the :meth:`_orm.Session.execute`
1410method will return ORM-oriented results, that is, ORM objects if that's what
1411was requested.
1412
1413The :func:`_sql.Select` construct **adds many new methods** for
1414compatibility with :class:`_orm.Query`, including :meth:`_sql.Select.filter`
1415:meth:`_sql.Select.filter_by`, newly reworked :meth:`_sql.Select.join`
1416and :meth:`_sql.Select.outerjoin` methods, :meth:`_sql.Select.options`,
1417etc.    Other more supplemental methods of :class:`_orm.Query` such as
1418:meth:`_orm.Query.populate_existing` are implemented via execution options.
1419
1420Return results are in terms of a
1421:class:`_result.Result` object, the new version of the SQLAlchemy
1422``ResultProxy`` object, which also adds many new methods for compatibility
1423with :class:`_orm.Query`, including :meth:`_engine.Result.one`, :meth:`_engine.Result.all`,
1424:meth:`_engine.Result.first`, :meth:`_engine.Result.one_or_none`, etc.
1425
1426The :class:`_engine.Result` object however does require some different calling
1427patterns, in that when first returned it will **always return tuples**
1428and it will **not deduplicate results in memory**.    In order to return
1429single ORM objects the way :class:`_orm.Query` does, the :meth:`_engine.Result.scalars`
1430modifier must be called first.  In order to return uniqued objects, as is
1431necessary when using joined eager loading, the :meth:`_engine.Result.unique`
1432modifier must be called first.
1433
1434Documentation for all new features of :func:`_sql.select` including execution
1435options, etc. are at :doc:`/orm/queryguide`.
1436
1437Below are some examples of how to migrate to :func:`_sql.select`::
1438
1439
1440    session = Session(engine)
1441
1442    user = session.execute(
1443        select(User).filter_by(name="some user")
1444    ).scalar_one()
1445
1446    # for first(), no LIMIT is applied automatically; add limit(1) if LIMIT
1447    # is desired on the query
1448    user = session.execute(
1449        select(User).filter_by(name="some user").limit(1)
1450    ).scalars().first()
1451
1452    # get() moves to the Session directly
1453    user = session.get(User, 5)
1454
1455    for user in session.execute(
1456        select(User).join(User.addresses).filter(Address.email == "some@email.case")
1457    ).scalars():
1458        # ...
1459
1460    # when using joinedload() against collections, use unique() on the result
1461    users = session.execute(
1462        select(User).options(joinedload(User.addresses)).order_by(User.id)
1463    ).unique().all()
1464
1465    # select() has ORM-ish methods like from_statement() that only work
1466    # if the statement is against ORM entities
1467    users = session.execute(
1468        select(User).from_statement(text("select * from users"))
1469    ).scalars().all()
1470
1471**Discussion**
1472
1473The fact that SQLAlchemy has both a :func:`_expression.select` construct
1474as well as a separate :class:`_orm.Query` object that features an extremely
1475similar, but fundamentally incompatible interface is likely the greatest
1476inconsistency in SQLAlchemy, one that arose as a result of small incremental
1477additions over time that added up to two major APIs that are divergent.
1478
1479In SQLAlchemy's first releases, the :class:`_orm.Query` object didn't exist
1480at all.  The original idea was that the :class:`_orm.Mapper` construct itself would
1481be able to select rows, and that :class:`_schema.Table` objects, not classes,
1482would be used to create the various criteria in a Core-style approach.   The
1483:class:`_query.Query` came along some months / years into SQLAlchemy's history
1484as a user proposal for a new, "buildable" querying object originally called ``SelectResults``
1485was accepted.
1486Concepts like a ``.where()`` method, which ``SelectResults`` called ``.filter()``,
1487were not present in SQLAlchemy previously, and the :func:`_sql.select` construct
1488used only the "all-at-once" construction style that's now deprecated
1489at :ref:`migration_20_5284`.
1490
1491As the new approach took off, the object evolved into the :class:`_orm.Query`
1492object as new features such as being able to select individual columns,
1493being able to select multiple entities at once, being able to build subqueries
1494from a :class:`_orm.Query` object rather than from a :class:`_sql.select`
1495object were added.   The goal became that :class:`_orm.Query` should have the
1496full functionality of :class:`_sql.select` in that it could be composed to
1497build SELECT statements fully with no explicit use of :func:`_sql.select`
1498needed.   At the same time, :func:`_sql.select` had also evolved "generative"
1499methods like :meth:`_sql.Select.where` and :meth:`_sql.Select.order_by`.
1500
1501In modern SQLAlchemy, this goal has been achieved and the two objects are now
1502completely overlapping in functionality.  The major challenge to unifying these
1503objects was that the :func:`_sql.select` object needed to remain **completely
1504agnostic of the ORM**.  To achieve this, the vast majority of logic from
1505:class:`_orm.Query` has been moved into the SQL compile phase, where
1506ORM-specific compiler plugins receive the
1507:class:`_sql.Select` construct and interpret its contents in terms of an
1508ORM-style query, before passing off to the core-level compiler in order to
1509create a SQL string.  With the advent of the new
1510`SQL compilation caching system <change_4639>`,
1511the majority of this ORM logic is also cached.
1512
1513
1514.. seealso::
1515
1516  :ref:`change_5159`
1517
1518.. _migration_20_get_to_session:
1519
1520ORM Query - get() method moves to Session
1521------------------------------------------
1522
1523**Synopsis**
1524
1525The :meth:`_orm.Query.get` method remains for legacy purposes, but the
1526primary interface is now the :meth:`_orm.Session.get` method::
1527
1528    # legacy usage
1529    user_obj = session.query(User).get(5)
1530
1531**Migration to 2.0**
1532
1533In 1.4 / 2.0, the :class:`_orm.Session` object adds a new
1534:meth:`_orm.Session.get` method::
1535
1536    # 1.4 / 2.0 cross-compatible use
1537    user_obj = session.get(User, 5)
1538
1539**Discussion**
1540
1541The :class:`_orm.Query` object is to be a legacy object in 2.0, as ORM
1542queries are now available using the :func:`_sql.select` object.  As the
1543:meth:`_orm.Query.get` method defines a special interaction with the
1544:class:`_orm.Session` and does not necessarily even emit a query, it's more
1545appropriate that it be part of :class:`_orm.Session`, where it is similar
1546to other "identity" methods such as :class:`_orm.Session.refresh` and
1547:class:`_orm.Session.merge`.
1548
1549SQLAlchemy originally included "get()" to resemble the Hibernate
1550``Session.load()`` method.  As is so often the case, we got it slightly
1551wrong as this method is really more about the :class:`_orm.Session` than
1552with writing a SQL query.
1553
1554.. _migration_20_orm_query_join_strings:
1555
1556ORM Query  - Joining / loading on relationships uses attributes, not strings
1557----------------------------------------------------------------------------
1558
1559**Synopsis**
1560
1561This refers to patterns such as that of :meth:`_query.Query.join` as well as
1562query options like :func:`_orm.joinedload` which currently accept a mixture of
1563string attribute names or actual class attributes.   The string forms
1564will all be removed in 2.0::
1565
1566    # string use removed
1567    q = session.query(User).join("addresses")
1568
1569    # string use removed
1570    q = session.query(User).options(joinedload("addresses"))
1571
1572    # string use removed
1573    q = session.query(Address).filter(with_parent(u1, "addresses"))
1574
1575
1576**Migration to 2.0**
1577
1578Modern SQLAlchemy 1.x versions support the recommended technique which
1579is to use mapped attributes::
1580
1581    # compatible with all modern SQLAlchemy versions
1582
1583    q = session.query(User).join(User.addresses)
1584
1585    q = session.query(User).options(joinedload(User.addresses))
1586
1587    q = session.query(Address).filter(with_parent(u1, User.addresses))
1588
1589The same techniques apply to :term:`2.0-style` style use::
1590
1591    # SQLAlchemy 1.4 / 2.0 cross compatible use
1592
1593    stmt = select(User).join(User.addresses)
1594    result = session.execute(stmt)
1595
1596    stmt = select(User).options(joinedload(User.addresses))
1597    result = session.execute(stmt)
1598
1599    stmt = select(Address).where(with_parent(u1, User.addresses))
1600    result = session.execute(stmt)
1601
1602**Discussion**
1603
1604The string calling form is ambiguous and requires that the internals do extra
1605work to determine the appropriate path and retrieve the correct mapped
1606property. By passing the ORM mapped attribute directly, not only is the
1607necessary information passed up front, the attribute is also typed and is
1608more potentially compatible with IDEs and pep-484 integrations.
1609
1610
1611ORM Query - Chaining using lists of attributes, rather than individual calls, removed
1612-------------------------------------------------------------------------------------
1613
1614**Synopsis**
1615
1616"Chained" forms of joining and loader options which accept multiple mapped
1617attributes in a list will be removed::
1618
1619    # chaining removed
1620    q = session.query(User).join("orders", "items", "keywords")
1621
1622
1623**Migration to 2.0**
1624
1625Use individual calls to :meth:`_orm.Query.join` for 1.x /2.0 cross compatible
1626use::
1627
1628    q = session.query(User).join(User.orders).join(Order.items).join(Item.keywords)
1629
1630For :term:`2.0-style` use, :class:`_sql.Select` has the same behavior of
1631:meth:`_sql.Select.join`, and also features a new :meth:`_sql.Select.join_from`
1632method that allows an explicit left side::
1633
1634    # 1.4 / 2.0 cross compatible
1635
1636    stmt = select(User).join(User.orders).join(Order.items).join(Item.keywords)
1637    result = session.execute(stmt)
1638
1639    # join_from can also be helpful
1640    stmt = select(User).join_from(User, Order).join_from(Order, Item, Order.items)
1641    result = session.execute(stmt)
1642
1643**Discussion**
1644
1645Removing the chaining of attributes is in line with simplifying the calling
1646interface of methods such as :meth:`_sql.Select.join`.
1647
1648.. _migration_20_query_join_options:
1649
1650ORM Query - join(..., aliased=True), from_joinpoint removed
1651-----------------------------------------------------------
1652
1653**Synopsis**
1654
1655The ``aliased=True`` option on :meth:`_query.Query.join` is removed, as is
1656the ``from_joinpoint`` flag::
1657
1658  # no longer supported
1659  q = session.query(Node).\
1660    join("children", aliased=True).filter(Node.name == "some sub child").
1661    join("children", from_joinpoint=True, aliased=True).\
1662    filter(Node.name == 'some sub sub child')
1663
1664**Migration to 2.0**
1665
1666Use explicit aliases instead::
1667
1668  n1 = aliased(Node)
1669  n2 = aliased(Node)
1670
1671  q = select(Node).join(Node.children.of_type(n1)).\
1672      where(n1.name == "some sub child").\
1673      join(n1.children.of_type(n2)).\
1674      where(n2.name == "some sub child")
1675
1676
1677**Discussion**
1678
1679The ``aliased=True`` option on :meth:`_query.Query.join` is another feature that
1680seems to be almost never used, based on extensive code searches to find
1681actual use of this feature.   The internal complexity that the ``aliased=True``
1682flag requires is **enormous**, and will be going away in 2.0.
1683
1684Most users aren't familiar with this flag, however it allows for automatic
1685aliasing of elements along a join, which then applies automatic aliasing
1686to filter conditions.  The original use case was to assist in long chains
1687of self-referential joins, as in the example shown above.  However,
1688the automatic adaption of the filter criteria is enormously
1689complicated internally and almost never used in real world applications.  The
1690pattern also leads to issues such as if filter criteria need to be added
1691at each link in the chain; the pattern then must use the ``from_joinpoint``
1692flag which SQLAlchemy developers could absolutely find no occurrence of this
1693parameter ever being used in real world applications.
1694
1695The ``aliased=True`` and ``from_joinpoint`` parameters were developed at a time
1696when the :class:`_query.Query` object didn't yet have good capabilities regarding
1697joining along relationship attributes, functions like
1698:meth:`.PropComparator.of_type` did not exist, and the :func:`.aliased`
1699construct itself didn't exist early on.
1700
1701.. _migration_20_query_distinct:
1702
1703Using DISTINCT with additional columns, but only select the entity
1704-------------------------------------------------------------------
1705
1706**Synopsis**
1707
1708:class:`_query.Query` will automatically add columns in the ORDER BY when
1709distinct is used.  The following query will select from all User columns
1710as well as "address.email_address" but only return User objects::
1711
1712    # 1.xx code
1713
1714    result = session.query(User).join(User.addresses).\
1715        distinct().order_by(Address.email_address).all()
1716
1717In version 2.0, the "email_address" column will not be automatically added
1718to the columns clause, and the above query will fail, since relational
1719databases won't allow you to ORDER BY "address.email_address" when using
1720DISTINCT if it isn't also in the columns clause.
1721
1722**Migration to 2.0**
1723
1724In 2.0, the column must be added explicitly.  To resolve the issue of only
1725returning the main entity object, and not the extra column, use the
1726:meth:`_result.Result.columns` method::
1727
1728    # 1.4 / 2.0 code
1729
1730    stmt = select(User, Address.email_address).join(User.addresses).\
1731        distinct().order_by(Address.email_address)
1732
1733    result = session.execute(stmt).columns(User).all()
1734
1735**Discussion**
1736
1737This case is an example of the limited flexibility of :class:`_orm.Query`
1738leading to the case where implicit, "magical" behavior needed to be added;
1739the "email_address" column is implicitly added to the columns clause, then
1740additional internal logic would omit that column from the actual results
1741returned.
1742
1743The new approach simplifies the interaction and makes what's going on
1744explicit, while still making it possible to fulfill the original use case
1745without inconvenience.
1746
1747
1748.. _migration_20_query_from_self:
1749
1750Selecting from the query itself as a subquery, e.g. "from_self()"
1751-------------------------------------------------------------------
1752
1753**Synopsis**
1754
1755The :meth:`_orm.Query.from_self` method will be removed from :class:`_orm.Query`::
1756
1757    # from_self is removed
1758    q = session.query(User, Address.email_address).\
1759      join(User.addresses).\
1760      from_self(User).order_by(Address.email_address)
1761
1762
1763**Migration to 2.0**
1764
1765The :func:`._orm.aliased` construct may be used to emit ORM queries against
1766an entity that is in terms of any arbitrary selectable.   It has been enhanced
1767in version 1.4 to smoothly accommodate being used multiple times against
1768the same subquery for different entities as well.  This can be
1769used in :term:`1.x style` with :class:`_orm.Query` as below; note that
1770since the final query wants to query in terms of both the ``User`` and
1771``Address`` entities, two separate :func:`_orm.aliased` constructs are created::
1772
1773    from sqlalchemy.orm import aliased
1774
1775    subq = session.query(User, Address.email_address).\
1776      join(User.addresses).subquery()
1777
1778    ua = aliased(User, subq)
1779
1780    aa = aliased(Address, subq)
1781
1782    q = session.query(ua, aa).order_by(aa.email_address)
1783
1784The same form may be used in :term:`2.0 style`::
1785
1786    from sqlalchemy.orm import aliased
1787
1788    subq = select(User, Address.email_address).\
1789      join(User.addresses).subquery()
1790
1791    ua = aliased(User, subq)
1792
1793    aa = aliased(Address, subq)
1794
1795    stmt = select(ua, aa).order_by(aa.email_address)
1796
1797    result = session.execute(stmt)
1798
1799
1800**Discussion**
1801
1802The :meth:`_query.Query.from_self` method is a very complicated method that is rarely
1803used.   The purpose of this method is to convert a :class:`_query.Query` into a
1804subquery, then return a new :class:`_query.Query` which SELECTs from that subquery.
1805The elaborate aspect of this method is that the returned query applies
1806automatic translation of ORM entities and columns to be stated in the SELECT in
1807terms of the subquery, as well as that it allows the entities and columns to be
1808SELECTed from to be modified.
1809
1810Because :meth:`_query.Query.from_self` packs an intense amount of implicit
1811translation into the SQL it produces, while it does allow a certain kind of
1812pattern to be executed very succinctly, real world use of this method is
1813infrequent as it is not simple to understand.
1814
1815The new approach makes use of the :func:`_orm.aliased` construct so that the
1816ORM internals don't need to guess which entities and columns should be adapted
1817and in what way; in the example above, the ``ua`` and ``aa`` objects, both
1818of which are :class:`_orm.AliasedClass` instances, provide to the internals
1819an unambiguous marker as to where the subquery should be referred towards
1820as well as what entity column or relationship is being considered for a given
1821component of the query.
1822
1823SQLAlchemy 1.4 also features an improved labeling style that no longer requires
1824the use of long labels that include the table name in order to disambiguate
1825columns of same names from different tables.  In the above examples, even if
1826our ``User`` and ``Address`` entities have overlapping column names, we can
1827select from both entities at once without having to specify any particular
1828labeling::
1829
1830  # 1.4 / 2.0 code
1831
1832  subq = select(User, Address).\
1833      join(User.addresses).subquery()
1834
1835  ua = aliased(User, subq)
1836  aa = aliased(Address, subq)
1837
1838  stmt = select(ua, aa).order_by(aa.email_address)
1839  result = session.execute(stmt)
1840
1841The above query will disambiguate the ``.id`` column of ``User`` and
1842``Address``, where ``Address.id`` is rendered and tracked as ``id_1``::
1843
1844  SELECT anon_1.id AS anon_1_id, anon_1.id_1 AS anon_1_id_1,
1845         anon_1.user_id AS anon_1_user_id,
1846         anon_1.email_address AS anon_1_email_address
1847  FROM (
1848    SELECT "user".id AS id, address.id AS id_1,
1849    address.user_id AS user_id, address.email_address AS email_address
1850    FROM "user" JOIN address ON "user".id = address.user_id
1851  ) AS anon_1 ORDER BY anon_1.email_address
1852
1853
1854:ticket:`5221`
1855
1856Selecting entities from alternative selectables; Query.select_entity_from()
1857---------------------------------------------------------------------------
1858
1859**Synopsis**
1860
1861The :meth:`_orm.Query.select_entity_from` method will be removed in 2.0::
1862
1863    subquery = session.query(User).filter(User.id == 5).subquery()
1864
1865    user = session.query(User).select_entity_from(subquery).first()
1866
1867**Migration to 2.0**
1868
1869As is the case described at :ref:`migration_20_query_from_self`, the
1870:func:`_orm.aliased` object provides a single place that operations like
1871"select entity from a subquery" may be achieved.  Using :term:`1.x style`::
1872
1873    from sqlalchemy.orm import aliased
1874
1875    subquery = session.query(User).filter(User.name.like("%somename%")).subquery()
1876
1877    ua = aliased(User, subquery)
1878
1879    user = session.query(ua).order_by(ua.id).first()
1880
1881Using :term:`2.0 style`::
1882
1883    from sqlalchemy.orm import aliased
1884
1885    subquery = select(User).where(User.name.like("%somename%")).subquery()
1886
1887    ua = aliased(User, subquery)
1888
1889    # note that LIMIT 1 is not automatically supplied, if needed
1890    user = session.execute(select(ua).order_by(ua.id).limit(1)).scalars().first()
1891
1892**Discussion**
1893
1894The points here are basically the same as those discussed at
1895:ref:`migration_20_query_from_self`.   The :meth:`_orm.Query.select_from_entity`
1896method was another way to instruct the query to load rows for a particular
1897ORM mapped entity from an alternate selectable, which involved having the
1898ORM apply automatic aliasing to that entity wherever it was used in the
1899query later on, such as in the WHERE clause or ORDER BY.   This intensely
1900complex feature is seldom used in this way, where as was the case with
1901:meth:`_orm.Query.from_self`, it's much easier to follow what's going on
1902when using an explicit :func:`_orm.aliased` object, both from a user point
1903of view as well as how the internals of the SQLAlchemy ORM must handle it.
1904
1905
1906.. _joinedload_not_uniqued:
1907
1908ORM Rows not uniquified by default
1909----------------------------------
1910
1911**Synopsis**
1912
1913ORM rows returned by ``session.execute(stmt)`` are no longer automatically
1914"uniqued".    This will normally be a welcome change, except in the case
1915where the "joined eager loading" loader strategy is used with collections::
1916
1917    # In the legacy API, many rows each have the same User primary key, but
1918    # only one User per primary key is returned
1919    users = session.query(User).options(joinedload(User.addresses))
1920
1921    # In the new API, uniquing is available but not implicitly
1922    # enabled
1923    result = session.execute(
1924        select(User).options(joinedload(User.addresses))
1925    )
1926
1927    # this actually will raise an error to let the user know that
1928    # uniquing should be applied
1929    rows = result.all()
1930
1931**Migrating to 2.0**
1932
1933When using a joined load of a collection, it's required that the
1934:meth:`_engine.Result.unique` method is called.  The ORM will actually set
1935a default row handler that will raise an error if this is not done, to
1936ensure that a joined eager load collection does not return duplicate rows
1937while still maintaining explicitness::
1938
1939    # 1.4 / 2.0 code
1940
1941    stmt = select(User).options(joinedload(User.addresses))
1942
1943    # statement will raise if unique() is not used, due to joinedload()
1944    # of a collection.  in all other cases, unique() is not needed.
1945    # By stating unique() explicitly, confusion over discrepancies between
1946    # number of objects/ rows returned vs. "SELECT COUNT(*)" is resolved
1947    rows = session.execute(stmt).unique().all()
1948
1949**Discussion**
1950
1951The situation here is a little bit unusual, in that SQLAlchemy is requiring
1952that a method be invoked that it is in fact entirely capable of doing
1953automatically.   The reason for requiring that the method be called is to
1954ensure the developer is "opting in" to the use of the
1955:meth:`_engine.Result.unique` method, such that they will not be confused when
1956a straight count of rows does not conflict with the count of
1957records in the actual result set, which has been a long running source of
1958user confusion and bug reports for many years.    That the uniquifying is
1959not happening in any other case by default will improve performance and
1960also improve clarity in those cases where automatic uniquing was causing
1961confusing results.
1962
1963To the degree that having to call :meth:`_engine.Result.unique` when joined
1964eager load collections are used is inconvenient, in modern SQLAlchemy
1965the :func:`_orm.selectinload` strategy presents a collection-oriented
1966eager loader that is superior in most respects to :func:`_orm.joinedload`
1967and should be preferred.
1968
1969
1970Autocommit mode removed from Session; autobegin support added
1971-------------------------------------------------------------
1972
1973**Synopsis**
1974
1975The :class:`_orm.Session` will no longer support "autocommit" mode, that
1976is, this pattern::
1977
1978    from sqlalchemy.orm import Session
1979
1980    sess = Session(engine, autocommit=True)
1981
1982    # no transaction begun, but emits SQL, won't be supported
1983    obj = sess.query(Class).first()
1984
1985
1986    # session flushes in a transaction that it begins and
1987    # commits, won't be supported
1988    sess.flush()
1989
1990
1991**Migration to 2.0**
1992
1993The main reason a :class:`_orm.Session` is used in "autocommit" mode
1994is so that the :meth:`_orm.Session.begin` method is available, so that framework
1995integrations and event hooks can control when this event happens.  In 1.4,
1996the :class:`_orm.Session` now features :ref:`autobegin behavior <change_5074>`
1997which resolves this issue; the :meth:`_orm.Session.begin` method may now
1998be called::
1999
2000
2001    from sqlalchemy.orm import Session
2002
2003    sess = Session(engine)
2004
2005    sess.begin()  # begin explicitly; if not called, will autobegin
2006                  # when database access is needed
2007
2008    sess.add(obj)
2009
2010    sess.commit()
2011
2012**Discussion**
2013
2014The "autocommit" mode is another holdover from the first versions
2015of SQLAlchemy.  The flag has stayed around mostly in support of allowing
2016explicit use of :meth:`_orm.Session.begin`, which is now solved by 1.4,
2017as well as to allow the use of "subtransactions", which are also removed in
20182.0.
2019
2020Session "subtransaction" behavior removed
2021------------------------------------------
2022
2023See the section :ref:`session_subtransactions` for background on this
2024change.
2025
2026
20272.0 Migration - ORM Extension and Recipe Changes
2028================================================
2029
2030Dogpile cache recipe and Horizontal Sharding uses new Session API
2031------------------------------------------------------------------
2032
2033As the :class:`_orm.Query` object becomes legacy, these two recipes
2034which previously relied upon subclassing of the :class:`_orm.Query`
2035object now make use of the :meth:`_orm.SessionEvents.do_orm_execute`
2036hook.    See the section :ref:`do_orm_execute_re_executing` for
2037an example.
2038
2039
2040
2041Baked Query Extension Superseded by built-in caching
2042-----------------------------------------------------
2043
2044The baked query extension is superseded by the built in caching system and
2045is no longer used by the ORM internals.
2046
2047See :ref:`sql_caching` for full background on the new caching system.
2048
2049
2050
2051Asyncio Support
2052=====================
2053
2054SQLAlchemy 1.4 includes asyncio support for both Core and ORM.
2055The new API exclusively makes use of the "future" patterns noted above.
2056See :ref:`change_3414` for background.
2057