From 449389a45f358300ba95f7d03c7b94b64703e31a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 9 Feb 2022 21:59:43 -0500 Subject: [PATCH] doc fixes * clarify merge behavior for non-present attributes, references #7687 * fix AsyncSession in async_scoped_session documentation, name the scoped session AsyncScopedSession, fixes: #7671 * Use non-deprecated execute() style in sqltypes JSON examples, fixes: #7633 * Add note regarding mitigation for https://github.com/MagicStack/asyncpg/issues/727, fixes #7245 Fixes: #7671 Fixes: #7633 Fixes: #7245 Change-Id: Ic40b4378ca321367a912864f4eddfdd9714fe217 --- doc/build/orm/extensions/asyncio.rst | 10 +++---- doc/build/orm/session_state_management.rst | 13 ++++---- lib/sqlalchemy/dialects/postgresql/asyncpg.py | 19 ++++++++++++ lib/sqlalchemy/sql/sqltypes.py | 30 +++++++++++-------- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/doc/build/orm/extensions/asyncio.rst b/doc/build/orm/extensions/asyncio.rst index 4aff3130b2..5d552b419b 100644 --- a/doc/build/orm/extensions/asyncio.rst +++ b/doc/build/orm/extensions/asyncio.rst @@ -712,10 +712,10 @@ constructor:: from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.asyncio import AsyncSession - async_session_factory = sessionmaker(some_async_engine, class_=_AsyncSession) - AsyncSession = async_scoped_session(async_session_factory, scopefunc=current_task) + async_session_factory = sessionmaker(some_async_engine, class_=AsyncSession) + AsyncScopedSession = async_scoped_session(async_session_factory, scopefunc=current_task) - some_async_session = AsyncSession() + some_async_session = AsyncScopedSession() :class:`_asyncio.async_scoped_session` also includes **proxy behavior** similar to that of :class:`.scoped_session`, which means it can be @@ -728,10 +728,10 @@ the usual ``await`` keywords are necessary, including for the some_async_session.add(some_object) # use the AsyncSession via the context-local proxy - await AsyncSession.commit() + await AsyncScopedSession.commit() # "remove" the current proxied AsyncSession for the local context - await AsyncSession.remove() + await AsyncScopedSession.remove() .. versionadded:: 1.4.19 diff --git a/doc/build/orm/session_state_management.rst b/doc/build/orm/session_state_management.rst index 64efffd761..47b4fbe7fd 100644 --- a/doc/build/orm/session_state_management.rst +++ b/doc/build/orm/session_state_management.rst @@ -204,11 +204,14 @@ When given an instance, it follows these steps: key if not located locally. * If the given instance has no primary key, or if no instance can be found with the primary key given, a new instance is created. -* The state of the given instance is then copied onto the located/newly - created instance. For attributes which are present on the source - instance, the value is transferred to the target instance. For mapped - attributes which aren't present on the source, the attribute is - expired on the target instance, discarding its existing value. +* The state of the given instance is then copied onto the located/newly created + instance. For attribute values which are present on the source instance, the + value is transferred to the target instance. For attribute values that aren't + present on the source instance, the corresponding attribute on the target + instance is :term:`expired` from memory, which discards any locally + present value from the target instance for that attribute, but no + direct modification is made to the database-persisted value for that + attribute. If the ``load=True`` flag is left at its default, this copy process emits events and will load the target object's diff --git a/lib/sqlalchemy/dialects/postgresql/asyncpg.py b/lib/sqlalchemy/dialects/postgresql/asyncpg.py index 7dbcfa357c..4c3c47ba6f 100644 --- a/lib/sqlalchemy/dialects/postgresql/asyncpg.py +++ b/lib/sqlalchemy/dialects/postgresql/asyncpg.py @@ -96,6 +96,25 @@ To disable the prepared statement cache, use a value of zero:: stale, nor can it retry the statement as the PostgreSQL transaction is invalidated when these errors occur. +Disabling the PostgreSQL JIT to improve ENUM datatype handling +--------------------------------------------------------------- + +Asyncpg has an `issue `_ when +using PostgreSQL ENUM datatypes, where upon the creation of new database +connections, an expensive query may be emitted in order to retrieve metadata +regarding custom types which has been shown to negatively affect performance. +To mitigate this issue, the PostgreSQL "jit" setting may be disabled from the +client using this setting passed to :func:`_asyncio.create_async_engine`:: + + engine = create_async_engine( + "postgresql+asyncpg://user:password@localhost/tmp", + connect_args={"server_settings": {"jit": "off"}}, + ) + +.. seealso:: + + https://github.com/MagicStack/asyncpg/issues/727 + """ # noqa import collections diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 575f402c3f..0ec771cb43 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -1904,7 +1904,7 @@ class JSON(Indexable, TypeEngine[Any]): with engine.connect() as conn: conn.execute( data_table.insert(), - data = {"key1": "value1", "key2": "value2"} + {"data": {"key1": "value1", "key2": "value2"}} ) **JSON-Specific Expression Operators** @@ -2000,20 +2000,22 @@ class JSON(Indexable, TypeEngine[Any]): **Support for JSON null vs. SQL NULL** - When working with NULL values, the :class:`_types.JSON` - type recommends the + When working with NULL values, the :class:`_types.JSON` type recommends the use of two specific constants in order to differentiate between a column - that evaluates to SQL NULL, e.g. no value, vs. the JSON-encoded string - of ``"null"``. To insert or select against a value that is SQL NULL, - use the constant :func:`.null`:: + that evaluates to SQL NULL, e.g. no value, vs. the JSON-encoded string of + ``"null"``. To insert or select against a value that is SQL NULL, use the + constant :func:`.null`. This symbol may be passed as a parameter value + specifically when using the :class:`_types.JSON` datatype, which contains + special logic that interprets this symbol to mean that the column value + should be SQL NULL as opposed to JSON ``"null"``:: from sqlalchemy import null - conn.execute(table.insert(), json_value=null()) + conn.execute(table.insert(), {"json_value": null()}) To insert or select against a value that is JSON ``"null"``, use the constant :attr:`_types.JSON.NULL`:: - conn.execute(table.insert(), json_value=JSON.NULL) + conn.execute(table.insert(), {"json_value": JSON.NULL}) The :class:`_types.JSON` type supports a flag :paramref:`_types.JSON.none_as_null` which when set to True will result @@ -2114,12 +2116,14 @@ class JSON(Indexable, TypeEngine[Any]): """Construct a :class:`_types.JSON` type. :param none_as_null=False: if True, persist the value ``None`` as a - SQL NULL value, not the JSON encoding of ``null``. Note that - when this flag is False, the :func:`.null` construct can still - be used to persist a NULL value:: + SQL NULL value, not the JSON encoding of ``null``. Note that when this + flag is False, the :func:`.null` construct can still be used to + persist a NULL value, which may be passed directly as a parameter + value that is specially interpreted by the :class:`_types.JSON` type + as SQL NULL:: from sqlalchemy import null - conn.execute(table.insert(), data=null()) + conn.execute(table.insert(), {"data": null()}) .. note:: @@ -2461,7 +2465,7 @@ class ARRAY( connection.execute( mytable.insert(), - data=[1,2,3] + {"data": [1,2,3]} ) The :class:`_types.ARRAY` type can be constructed given a fixed number -- 2.47.2