SQLAlchemy 2.0.49__py3-none-any.whl → 2.0.51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +1 -1
- sqlalchemy/connectors/aioodbc.py +0 -1
- sqlalchemy/dialects/_typing.py +0 -1
- sqlalchemy/dialects/mssql/__init__.py +0 -1
- sqlalchemy/dialects/mssql/base.py +8 -18
- sqlalchemy/dialects/mssql/information_schema.py +0 -1
- sqlalchemy/dialects/mssql/pymssql.py +1 -0
- sqlalchemy/dialects/mssql/pyodbc.py +0 -1
- sqlalchemy/dialects/mysql/aiomysql.py +26 -2
- sqlalchemy/dialects/mysql/asyncmy.py +25 -3
- sqlalchemy/dialects/mysql/base.py +6 -4
- sqlalchemy/dialects/mysql/cymysql.py +1 -0
- sqlalchemy/dialects/mysql/dml.py +0 -1
- sqlalchemy/dialects/mysql/mariadbconnector.py +1 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +3 -2
- sqlalchemy/dialects/mysql/mysqldb.py +1 -0
- sqlalchemy/dialects/mysql/pymysql.py +30 -9
- sqlalchemy/dialects/mysql/pyodbc.py +1 -0
- sqlalchemy/dialects/mysql/reflection.py +2 -2
- sqlalchemy/dialects/oracle/cx_oracle.py +1 -0
- sqlalchemy/dialects/oracle/oracledb.py +1 -0
- sqlalchemy/dialects/oracle/vector.py +2 -4
- sqlalchemy/dialects/postgresql/__init__.py +0 -1
- sqlalchemy/dialects/postgresql/_psycopg_common.py +36 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +4 -1
- sqlalchemy/dialects/postgresql/base.py +53 -3
- sqlalchemy/dialects/postgresql/dml.py +0 -1
- sqlalchemy/dialects/postgresql/ext.py +6 -0
- sqlalchemy/dialects/postgresql/hstore.py +2 -3
- sqlalchemy/dialects/postgresql/operators.py +0 -1
- sqlalchemy/dialects/postgresql/pg8000.py +3 -5
- sqlalchemy/dialects/postgresql/provision.py +3 -2
- sqlalchemy/dialects/postgresql/psycopg.py +22 -38
- sqlalchemy/dialects/postgresql/psycopg2.py +3 -29
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +1 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +1 -0
- sqlalchemy/dialects/sqlite/base.py +8 -11
- sqlalchemy/dialects/sqlite/pysqlcipher.py +10 -7
- sqlalchemy/dialects/sqlite/pysqlite.py +1 -0
- sqlalchemy/engine/_py_processors.py +0 -1
- sqlalchemy/engine/base.py +3 -1
- sqlalchemy/engine/create.py +1 -1
- sqlalchemy/engine/cursor.py +6 -2
- sqlalchemy/engine/interfaces.py +1 -1
- sqlalchemy/engine/mock.py +0 -1
- sqlalchemy/engine/processors.py +1 -0
- sqlalchemy/engine/reflection.py +1 -0
- sqlalchemy/engine/result.py +27 -0
- sqlalchemy/event/api.py +1 -1
- sqlalchemy/event/attr.py +1 -0
- sqlalchemy/event/base.py +1 -0
- sqlalchemy/event/legacy.py +1 -0
- sqlalchemy/event/registry.py +1 -0
- sqlalchemy/exc.py +1 -0
- sqlalchemy/ext/__init__.py +0 -1
- sqlalchemy/ext/associationproxy.py +1 -0
- sqlalchemy/ext/asyncio/engine.py +1 -1
- sqlalchemy/ext/asyncio/session.py +0 -1
- sqlalchemy/ext/automap.py +1 -0
- sqlalchemy/ext/baked.py +0 -1
- sqlalchemy/ext/compiler.py +1 -0
- sqlalchemy/ext/declarative/extensions.py +1 -0
- sqlalchemy/ext/horizontal_shard.py +1 -0
- sqlalchemy/ext/instrumentation.py +1 -1
- sqlalchemy/ext/mypy/plugin.py +1 -0
- sqlalchemy/ext/orderinglist.py +1 -0
- sqlalchemy/ext/serializer.py +0 -1
- sqlalchemy/future/__init__.py +1 -0
- sqlalchemy/inspection.py +1 -0
- sqlalchemy/log.py +2 -2
- sqlalchemy/orm/collections.py +1 -0
- sqlalchemy/orm/context.py +1 -1
- sqlalchemy/orm/decl_api.py +14 -11
- sqlalchemy/orm/dependency.py +1 -1
- sqlalchemy/orm/descriptor_props.py +1 -1
- sqlalchemy/orm/dynamic.py +0 -1
- sqlalchemy/orm/evaluator.py +0 -1
- sqlalchemy/orm/events.py +1 -0
- sqlalchemy/orm/instrumentation.py +0 -1
- sqlalchemy/orm/loading.py +2 -2
- sqlalchemy/orm/mapper.py +1 -0
- sqlalchemy/orm/persistence.py +1 -0
- sqlalchemy/orm/properties.py +3 -4
- sqlalchemy/orm/query.py +1 -1
- sqlalchemy/orm/relationships.py +8 -1
- sqlalchemy/orm/scoping.py +2 -3
- sqlalchemy/orm/session.py +29 -7
- sqlalchemy/orm/strategies.py +1 -1
- sqlalchemy/orm/unitofwork.py +2 -3
- sqlalchemy/orm/util.py +4 -0
- sqlalchemy/pool/impl.py +1 -0
- sqlalchemy/sql/_orm_types.py +0 -1
- sqlalchemy/sql/_py_util.py +1 -1
- sqlalchemy/sql/base.py +0 -1
- sqlalchemy/sql/compiler.py +3 -1
- sqlalchemy/sql/crud.py +2 -1
- sqlalchemy/sql/ddl.py +1 -0
- sqlalchemy/sql/dml.py +1 -0
- sqlalchemy/sql/elements.py +7 -4
- sqlalchemy/sql/expression.py +0 -1
- sqlalchemy/sql/lambdas.py +2 -3
- sqlalchemy/sql/schema.py +3 -4
- sqlalchemy/sql/selectable.py +13 -28
- sqlalchemy/sql/sqltypes.py +1 -1
- sqlalchemy/sql/traversals.py +0 -1
- sqlalchemy/sql/util.py +2 -1
- sqlalchemy/testing/engines.py +0 -1
- sqlalchemy/testing/fixtures/base.py +10 -5
- sqlalchemy/testing/fixtures/mypy.py +4 -8
- sqlalchemy/testing/plugin/bootstrap.py +0 -1
- sqlalchemy/testing/plugin/pytestplugin.py +2 -5
- sqlalchemy/testing/profiling.py +0 -1
- sqlalchemy/testing/provision.py +0 -1
- sqlalchemy/testing/suite/test_reflection.py +0 -1
- sqlalchemy/testing/util.py +0 -1
- sqlalchemy/types.py +0 -1
- sqlalchemy/util/_collections.py +1 -1
- sqlalchemy/util/compat.py +1 -2
- sqlalchemy/util/langhelpers.py +17 -14
- sqlalchemy/util/preloaded.py +1 -0
- sqlalchemy/util/queue.py +1 -1
- sqlalchemy/util/tool_support.py +1 -0
- {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/METADATA +1 -1
- {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/RECORD +127 -127
- {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/WHEEL +0 -0
- {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
sqlalchemy/connectors/aioodbc.py
CHANGED
sqlalchemy/dialects/_typing.py
CHANGED
|
@@ -18,7 +18,6 @@ from ..sql.schema import Column
|
|
|
18
18
|
from ..sql.schema import ColumnCollectionConstraint
|
|
19
19
|
from ..sql.schema import Index
|
|
20
20
|
|
|
21
|
-
|
|
22
21
|
_OnConflictConstraintT = Union[str, ColumnCollectionConstraint, Index, None]
|
|
23
22
|
_OnConflictIndexElementsT = Optional[
|
|
24
23
|
Iterable[Union[Column[Any], str, roles.DDLConstraintColumnRole]]
|
|
@@ -3220,8 +3220,7 @@ class MSDialect(default.DefaultDialect):
|
|
|
3220
3220
|
|
|
3221
3221
|
view_name = f"sys.{row[0]}"
|
|
3222
3222
|
|
|
3223
|
-
cursor.execute(
|
|
3224
|
-
"""
|
|
3223
|
+
cursor.execute("""
|
|
3225
3224
|
SELECT CASE transaction_isolation_level
|
|
3226
3225
|
WHEN 0 THEN NULL
|
|
3227
3226
|
WHEN 1 THEN 'READ UNCOMMITTED'
|
|
@@ -3232,10 +3231,7 @@ class MSDialect(default.DefaultDialect):
|
|
|
3232
3231
|
AS TRANSACTION_ISOLATION_LEVEL
|
|
3233
3232
|
FROM {}
|
|
3234
3233
|
where session_id = @@SPID
|
|
3235
|
-
""".format(
|
|
3236
|
-
view_name
|
|
3237
|
-
)
|
|
3238
|
-
)
|
|
3234
|
+
""".format(view_name))
|
|
3239
3235
|
except self.dbapi.Error as err:
|
|
3240
3236
|
raise NotImplementedError(
|
|
3241
3237
|
"Can't fetch isolation level; encountered error {} when "
|
|
@@ -3432,8 +3428,7 @@ class MSDialect(default.DefaultDialect):
|
|
|
3432
3428
|
else "NULL as filter_definition"
|
|
3433
3429
|
)
|
|
3434
3430
|
rp = connection.execution_options(future_result=True).execute(
|
|
3435
|
-
sql.text(
|
|
3436
|
-
f"""
|
|
3431
|
+
sql.text(f"""
|
|
3437
3432
|
select
|
|
3438
3433
|
ind.index_id,
|
|
3439
3434
|
ind.is_unique,
|
|
@@ -3453,8 +3448,7 @@ where
|
|
|
3453
3448
|
and ind.type != 0
|
|
3454
3449
|
order by
|
|
3455
3450
|
ind.name
|
|
3456
|
-
"""
|
|
3457
|
-
)
|
|
3451
|
+
""")
|
|
3458
3452
|
.bindparams(
|
|
3459
3453
|
sql.bindparam("tabname", tablename, ischema.CoerceUnicode()),
|
|
3460
3454
|
sql.bindparam("schname", owner, ischema.CoerceUnicode()),
|
|
@@ -3482,8 +3476,7 @@ order by
|
|
|
3482
3476
|
do["mssql_where"] = row["filter_definition"]
|
|
3483
3477
|
|
|
3484
3478
|
rp = connection.execution_options(future_result=True).execute(
|
|
3485
|
-
sql.text(
|
|
3486
|
-
"""
|
|
3479
|
+
sql.text("""
|
|
3487
3480
|
select
|
|
3488
3481
|
ind_col.index_id,
|
|
3489
3482
|
col.name,
|
|
@@ -3503,8 +3496,7 @@ where
|
|
|
3503
3496
|
order by
|
|
3504
3497
|
ind_col.index_id,
|
|
3505
3498
|
ind_col.key_ordinal
|
|
3506
|
-
"""
|
|
3507
|
-
)
|
|
3499
|
+
""")
|
|
3508
3500
|
.bindparams(
|
|
3509
3501
|
sql.bindparam("tabname", tablename, ischema.CoerceUnicode()),
|
|
3510
3502
|
sql.bindparam("schname", owner, ischema.CoerceUnicode()),
|
|
@@ -3930,8 +3922,7 @@ order by
|
|
|
3930
3922
|
):
|
|
3931
3923
|
# Foreign key constraints
|
|
3932
3924
|
s = (
|
|
3933
|
-
text(
|
|
3934
|
-
"""\
|
|
3925
|
+
text("""\
|
|
3935
3926
|
WITH fk_info AS (
|
|
3936
3927
|
SELECT
|
|
3937
3928
|
ischema_ref_con.constraint_schema,
|
|
@@ -4031,8 +4022,7 @@ index_info AS (
|
|
|
4031
4022
|
|
|
4032
4023
|
ORDER BY fk_info.constraint_schema, fk_info.constraint_name,
|
|
4033
4024
|
fk_info.ordinal_position
|
|
4034
|
-
"""
|
|
4035
|
-
)
|
|
4025
|
+
""")
|
|
4036
4026
|
.bindparams(
|
|
4037
4027
|
sql.bindparam("tablename", tablename, ischema.CoerceUnicode()),
|
|
4038
4028
|
sql.bindparam("owner", owner, ischema.CoerceUnicode()),
|
|
@@ -28,6 +28,7 @@ This dialect should normally be used only with the
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
""" # noqa
|
|
31
|
+
|
|
31
32
|
from __future__ import annotations
|
|
32
33
|
|
|
33
34
|
from types import ModuleType
|
|
@@ -38,6 +39,7 @@ from typing import Tuple
|
|
|
38
39
|
from typing import TYPE_CHECKING
|
|
39
40
|
from typing import Union
|
|
40
41
|
|
|
42
|
+
from .pymysql import _connection_ping_reconnects_true
|
|
41
43
|
from .pymysql import MySQLDialect_pymysql
|
|
42
44
|
from ... import pool
|
|
43
45
|
from ... import util
|
|
@@ -46,6 +48,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
|
|
46
48
|
from ...connectors.asyncio import AsyncAdapt_dbapi_module
|
|
47
49
|
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
|
|
48
50
|
from ...connectors.asyncio import AsyncAdapt_terminate
|
|
51
|
+
from ...util import langhelpers
|
|
49
52
|
from ...util.concurrency import await_fallback
|
|
50
53
|
from ...util.concurrency import await_only
|
|
51
54
|
|
|
@@ -91,9 +94,12 @@ class AsyncAdapt_aiomysql_connection(
|
|
|
91
94
|
_cursor_cls = AsyncAdapt_aiomysql_cursor
|
|
92
95
|
_ss_cursor_cls = AsyncAdapt_aiomysql_ss_cursor
|
|
93
96
|
|
|
94
|
-
def ping(self, reconnect: bool) -> None:
|
|
97
|
+
def ping(self, reconnect: bool = False) -> None:
|
|
95
98
|
assert not reconnect
|
|
96
|
-
self.
|
|
99
|
+
if self.dbapi._send_false_to_ping:
|
|
100
|
+
self.await_(self._connection.ping(reconnect=False))
|
|
101
|
+
else:
|
|
102
|
+
self.await_(self._connection.ping())
|
|
97
103
|
|
|
98
104
|
def character_set_name(self) -> Optional[str]:
|
|
99
105
|
return self._connection.character_set_name() # type: ignore[no-any-return] # noqa: E501
|
|
@@ -170,6 +176,24 @@ class AsyncAdapt_aiomysql_dbapi(AsyncAdapt_dbapi_module):
|
|
|
170
176
|
await_only(creator_fn(*arg, **kw)),
|
|
171
177
|
)
|
|
172
178
|
|
|
179
|
+
@langhelpers.memoized_property
|
|
180
|
+
def _send_false_to_ping(self) -> bool:
|
|
181
|
+
"""determine if aiomysql has deprecated, changed the default of,
|
|
182
|
+
or removed the 'reconnect' argument of connection.ping().
|
|
183
|
+
|
|
184
|
+
See #13306 and #10492
|
|
185
|
+
|
|
186
|
+
""" # noqa: E501
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
Connection = __import__(
|
|
190
|
+
"aiomysql.connection"
|
|
191
|
+
).connection.Connection
|
|
192
|
+
except (ImportError, AttributeError):
|
|
193
|
+
return True
|
|
194
|
+
else:
|
|
195
|
+
return _connection_ping_reconnects_true(Connection)
|
|
196
|
+
|
|
173
197
|
def _init_cursors_subclasses(
|
|
174
198
|
self,
|
|
175
199
|
) -> Tuple[AsyncIODBAPICursor, AsyncIODBAPICursor]:
|
|
@@ -26,6 +26,7 @@ This dialect should normally be used only with the
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
""" # noqa
|
|
29
|
+
|
|
29
30
|
from __future__ import annotations
|
|
30
31
|
|
|
31
32
|
from types import ModuleType
|
|
@@ -35,6 +36,7 @@ from typing import Optional
|
|
|
35
36
|
from typing import TYPE_CHECKING
|
|
36
37
|
from typing import Union
|
|
37
38
|
|
|
39
|
+
from .pymysql import _connection_ping_reconnects_true
|
|
38
40
|
from .pymysql import MySQLDialect_pymysql
|
|
39
41
|
from ... import pool
|
|
40
42
|
from ... import util
|
|
@@ -43,6 +45,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
|
|
43
45
|
from ...connectors.asyncio import AsyncAdapt_dbapi_module
|
|
44
46
|
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
|
|
45
47
|
from ...connectors.asyncio import AsyncAdapt_terminate
|
|
48
|
+
from ...util import langhelpers
|
|
46
49
|
from ...util.concurrency import await_fallback
|
|
47
50
|
from ...util.concurrency import await_only
|
|
48
51
|
|
|
@@ -86,18 +89,21 @@ class AsyncAdapt_asyncmy_connection(
|
|
|
86
89
|
if isinstance(error, AttributeError):
|
|
87
90
|
raise self.dbapi.InternalError(
|
|
88
91
|
"network operation failed due to asyncmy attribute error"
|
|
89
|
-
)
|
|
92
|
+
) from error
|
|
90
93
|
|
|
91
94
|
raise error
|
|
92
95
|
|
|
93
|
-
def ping(self, reconnect: bool) -> None:
|
|
96
|
+
def ping(self, reconnect: bool = False) -> None:
|
|
94
97
|
assert not reconnect
|
|
95
98
|
return self.await_(self._do_ping())
|
|
96
99
|
|
|
97
100
|
async def _do_ping(self) -> None:
|
|
98
101
|
try:
|
|
99
102
|
async with self._execute_mutex:
|
|
100
|
-
|
|
103
|
+
if self.dbapi._send_false_to_ping:
|
|
104
|
+
await self._connection.ping(reconnect=False)
|
|
105
|
+
else:
|
|
106
|
+
await self._connection.ping()
|
|
101
107
|
except Exception as error:
|
|
102
108
|
self._handle_exception(error)
|
|
103
109
|
|
|
@@ -171,6 +177,22 @@ class AsyncAdapt_asyncmy_dbapi(AsyncAdapt_dbapi_module):
|
|
|
171
177
|
await_only(creator_fn(*arg, **kw)),
|
|
172
178
|
)
|
|
173
179
|
|
|
180
|
+
@langhelpers.memoized_property
|
|
181
|
+
def _send_false_to_ping(self) -> bool:
|
|
182
|
+
"""determine if asyncmy has deprecated, changed the default of,
|
|
183
|
+
or removed the 'reconnect' argument of connection.ping().
|
|
184
|
+
|
|
185
|
+
See #13306 and #10492
|
|
186
|
+
|
|
187
|
+
""" # noqa: E501
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
Connection = __import__("asyncmy.connection").connection.Connection
|
|
191
|
+
except (ImportError, AttributeError):
|
|
192
|
+
return True
|
|
193
|
+
else:
|
|
194
|
+
return _connection_ping_reconnects_true(Connection)
|
|
195
|
+
|
|
174
196
|
|
|
175
197
|
class MySQLDialect_asyncmy(MySQLDialect_pymysql):
|
|
176
198
|
driver = "asyncmy"
|
|
@@ -1063,6 +1063,7 @@ output:
|
|
|
1063
1063
|
)
|
|
1064
1064
|
|
|
1065
1065
|
""" # noqa
|
|
1066
|
+
|
|
1066
1067
|
from __future__ import annotations
|
|
1067
1068
|
|
|
1068
1069
|
from collections import defaultdict
|
|
@@ -2764,6 +2765,7 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
2764
2765
|
# i.e. first connect
|
|
2765
2766
|
_backslash_escapes = True
|
|
2766
2767
|
_server_ansiquotes = False
|
|
2768
|
+
_casing = 0
|
|
2767
2769
|
|
|
2768
2770
|
server_version_info: Tuple[int, ...]
|
|
2769
2771
|
identifier_preparer: MySQLIdentifierPreparer
|
|
@@ -3186,9 +3188,9 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
3186
3188
|
self._is_mysql and self.server_version_info >= (8, 0, 1)
|
|
3187
3189
|
)
|
|
3188
3190
|
|
|
3189
|
-
self._needs_correct_for_88718_96365 = (
|
|
3190
|
-
|
|
3191
|
-
)
|
|
3191
|
+
self._needs_correct_for_88718_96365 = self.server_version_info >= (
|
|
3192
|
+
8,
|
|
3193
|
+
) and (self.server_version_info < (8, 0, 14) or self._casing == 2)
|
|
3192
3194
|
|
|
3193
3195
|
self.delete_returning = (
|
|
3194
3196
|
self.is_mariadb and self.server_version_info >= (10, 0, 5)
|
|
@@ -3253,7 +3255,7 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
3253
3255
|
def _is_mariadb_102(self) -> bool:
|
|
3254
3256
|
return (
|
|
3255
3257
|
self.is_mariadb
|
|
3256
|
-
and self._mariadb_normalized_version_info # type:ignore[operator]
|
|
3258
|
+
and self._mariadb_normalized_version_info # type: ignore[operator]
|
|
3257
3259
|
> (
|
|
3258
3260
|
10,
|
|
3259
3261
|
2,
|
sqlalchemy/dialects/mysql/dml.py
CHANGED
|
@@ -45,6 +45,7 @@ charset/collation will allow connectivity.
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
""" # noqa
|
|
48
|
+
|
|
48
49
|
from __future__ import annotations
|
|
49
50
|
|
|
50
51
|
import re
|
|
@@ -110,8 +111,8 @@ class IdentifierPreparerCommon_mysqlconnector:
|
|
|
110
111
|
|
|
111
112
|
def _escape_identifier(self, value: str) -> str:
|
|
112
113
|
value = value.replace(
|
|
113
|
-
self.escape_quote, # type:ignore[attr-defined]
|
|
114
|
-
self.escape_to_quote, # type:ignore[attr-defined]
|
|
114
|
+
self.escape_quote, # type: ignore[attr-defined]
|
|
115
|
+
self.escape_to_quote, # type: ignore[attr-defined]
|
|
115
116
|
)
|
|
116
117
|
return value
|
|
117
118
|
|
|
@@ -47,11 +47,13 @@ and targets 100% compatibility. Most behavioral notes for MySQL-python apply
|
|
|
47
47
|
to the pymysql driver as well.
|
|
48
48
|
|
|
49
49
|
""" # noqa
|
|
50
|
+
|
|
50
51
|
from __future__ import annotations
|
|
51
52
|
|
|
52
53
|
from typing import Any
|
|
53
54
|
from typing import Dict
|
|
54
55
|
from typing import Optional
|
|
56
|
+
from typing import Type
|
|
55
57
|
from typing import TYPE_CHECKING
|
|
56
58
|
from typing import Union
|
|
57
59
|
|
|
@@ -69,6 +71,31 @@ if TYPE_CHECKING:
|
|
|
69
71
|
from ...engine.url import URL
|
|
70
72
|
|
|
71
73
|
|
|
74
|
+
def _connection_ping_reconnects_true(connection_cls: Type[Any]) -> bool:
|
|
75
|
+
"""Given a Connection class like pymysql.Connection, aiomysql.Connection,
|
|
76
|
+
asyncmy.Connection, inspect the ping() method and determine if it
|
|
77
|
+
has a "reconnect" parameter that either defaults to True, or is positional.
|
|
78
|
+
|
|
79
|
+
a return value of True here means that when we call ``connection.ping()``,
|
|
80
|
+
we **must** pass `reconnect=False`. a return value of False means that
|
|
81
|
+
we should call ``connection.ping()`` with **no arguments**.
|
|
82
|
+
|
|
83
|
+
This routine originates from issue #10492 for pymysql, however arg
|
|
84
|
+
signature mismatches in aiomysql/asyncmy tracked by issue #13306
|
|
85
|
+
necessitated a more open ended function.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
insp = langhelpers.get_callable_argspec(connection_cls.ping)
|
|
89
|
+
try:
|
|
90
|
+
reconnect_arg = insp.args[1]
|
|
91
|
+
except IndexError:
|
|
92
|
+
return False
|
|
93
|
+
else:
|
|
94
|
+
return reconnect_arg == "reconnect" and (
|
|
95
|
+
not insp.defaults or insp.defaults[0] is not False
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
72
99
|
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
|
73
100
|
driver = "pymysql"
|
|
74
101
|
supports_statement_cache = True
|
|
@@ -97,6 +124,8 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
|
|
97
124
|
https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
|
|
98
125
|
for background.
|
|
99
126
|
|
|
127
|
+
Revised as part of #13306
|
|
128
|
+
|
|
100
129
|
""" # noqa: E501
|
|
101
130
|
|
|
102
131
|
try:
|
|
@@ -106,15 +135,7 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
|
|
106
135
|
except (ImportError, AttributeError):
|
|
107
136
|
return True
|
|
108
137
|
else:
|
|
109
|
-
|
|
110
|
-
try:
|
|
111
|
-
reconnect_arg = insp.args[1]
|
|
112
|
-
except IndexError:
|
|
113
|
-
return False
|
|
114
|
-
else:
|
|
115
|
-
return reconnect_arg == "reconnect" and (
|
|
116
|
-
not insp.defaults or insp.defaults[0] is not False
|
|
117
|
-
)
|
|
138
|
+
return _connection_ping_reconnects_true(Connection)
|
|
118
139
|
|
|
119
140
|
def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
|
|
120
141
|
if self._send_false_to_ping:
|
|
@@ -371,7 +371,7 @@ class MySQLTableDefinitionParser:
|
|
|
371
371
|
|
|
372
372
|
buffer = []
|
|
373
373
|
for row in columns:
|
|
374
|
-
|
|
374
|
+
name, col_type, nullable, default, extra = (
|
|
375
375
|
row[i] for i in (0, 1, 2, 4, 5)
|
|
376
376
|
)
|
|
377
377
|
|
|
@@ -704,7 +704,7 @@ def cleanup_text(raw_text: str) -> str:
|
|
|
704
704
|
if "\\" in raw_text:
|
|
705
705
|
raw_text = re.sub(
|
|
706
706
|
_control_char_regexp,
|
|
707
|
-
lambda s: _control_char_map[s[0]], # type: ignore[index]
|
|
707
|
+
lambda s: _control_char_map[s[0]], # type: ignore[unused-ignore,index] # noqa: E501
|
|
708
708
|
raw_text,
|
|
709
709
|
)
|
|
710
710
|
return raw_text.replace("''", "'")
|
|
@@ -313,12 +313,10 @@ class VECTOR(types.TypeEngine):
|
|
|
313
313
|
)
|
|
314
314
|
|
|
315
315
|
else:
|
|
316
|
-
raise TypeError(
|
|
317
|
-
"""
|
|
316
|
+
raise TypeError("""
|
|
318
317
|
Invalid input for VECTOR: expected a list, an array.array,
|
|
319
318
|
or a SparseVector object.
|
|
320
|
-
"""
|
|
321
|
-
)
|
|
319
|
+
""")
|
|
322
320
|
|
|
323
321
|
return process
|
|
324
322
|
|
|
@@ -187,3 +187,39 @@ class _PGDialect_common_psycopg(PGDialect):
|
|
|
187
187
|
dbapi_connection.autocommit = before_autocommit
|
|
188
188
|
|
|
189
189
|
return True
|
|
190
|
+
|
|
191
|
+
def do_begin_twophase(self, connection, xid):
|
|
192
|
+
connection.connection.tpc_begin(xid)
|
|
193
|
+
|
|
194
|
+
def do_prepare_twophase(self, connection, xid):
|
|
195
|
+
connection.connection.tpc_prepare()
|
|
196
|
+
|
|
197
|
+
def _do_twophase(self, dbapi_conn, operation, xid, recover=False):
|
|
198
|
+
if recover:
|
|
199
|
+
if not self._twophase_idle_check(dbapi_conn):
|
|
200
|
+
dbapi_conn.rollback()
|
|
201
|
+
operation(xid)
|
|
202
|
+
else:
|
|
203
|
+
operation()
|
|
204
|
+
|
|
205
|
+
def _twophase_idle_check(self, dbapi_conn):
|
|
206
|
+
raise NotImplementedError
|
|
207
|
+
|
|
208
|
+
def do_rollback_twophase(
|
|
209
|
+
self, connection, xid, is_prepared=True, recover=False
|
|
210
|
+
):
|
|
211
|
+
dbapi_conn = connection.connection.dbapi_connection
|
|
212
|
+
self._do_twophase(
|
|
213
|
+
dbapi_conn, dbapi_conn.tpc_rollback, xid, recover=recover
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def do_commit_twophase(
|
|
217
|
+
self, connection, xid, is_prepared=True, recover=False
|
|
218
|
+
):
|
|
219
|
+
dbapi_conn = connection.connection.dbapi_connection
|
|
220
|
+
self._do_twophase(
|
|
221
|
+
dbapi_conn, dbapi_conn.tpc_commit, xid, recover=recover
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def do_recover_twophase(self, connection):
|
|
225
|
+
return [str(row) for row in connection.connection.tpc_recover()]
|
|
@@ -990,6 +990,9 @@ class AsyncAdapt_asyncpg_dbapi:
|
|
|
990
990
|
class InternalServerError(InternalError):
|
|
991
991
|
pass
|
|
992
992
|
|
|
993
|
+
class InternalClientError(InternalError):
|
|
994
|
+
pass
|
|
995
|
+
|
|
993
996
|
class InvalidCachedStatementError(NotSupportedError):
|
|
994
997
|
def __init__(self, message):
|
|
995
998
|
super().__init__(
|
|
@@ -1014,6 +1017,7 @@ class AsyncAdapt_asyncpg_dbapi:
|
|
|
1014
1017
|
asyncpg.exceptions.InterfaceError: self.InterfaceError,
|
|
1015
1018
|
asyncpg.exceptions.InvalidCachedStatementError: self.InvalidCachedStatementError, # noqa: E501
|
|
1016
1019
|
asyncpg.exceptions.InternalServerError: self.InternalServerError,
|
|
1020
|
+
asyncpg.exceptions.InternalClientError: self.InternalClientError,
|
|
1017
1021
|
}
|
|
1018
1022
|
|
|
1019
1023
|
def Binary(self, value):
|
|
@@ -1212,7 +1216,6 @@ class PGDialect_asyncpg(PGDialect):
|
|
|
1212
1216
|
"""
|
|
1213
1217
|
|
|
1214
1218
|
asyncpg_connection = conn._connection
|
|
1215
|
-
deserializer = self._json_deserializer or _py_json.loads
|
|
1216
1219
|
|
|
1217
1220
|
def _jsonb_encoder(str_value):
|
|
1218
1221
|
# \x01 is the prefix for jsonb used by PostgreSQL.
|
|
@@ -1198,6 +1198,21 @@ constraints, enabling table inheritance hierarchies in PostgreSQL.
|
|
|
1198
1198
|
|
|
1199
1199
|
Table("some_table", metadata, ..., postgresql_inherits=("t1", "t2", ...))
|
|
1200
1200
|
|
|
1201
|
+
For schema-qualified parent table names, use :class:`.quoted_name` with
|
|
1202
|
+
``quote=False`` to prevent the dotted name from being quoted as a single
|
|
1203
|
+
identifier::
|
|
1204
|
+
|
|
1205
|
+
from sqlalchemy.sql import quoted_name
|
|
1206
|
+
|
|
1207
|
+
Table(
|
|
1208
|
+
"some_table",
|
|
1209
|
+
metadata,
|
|
1210
|
+
...,
|
|
1211
|
+
postgresql_inherits=quoted_name(
|
|
1212
|
+
"my_schema.some_supertable", quote=False
|
|
1213
|
+
),
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1201
1216
|
``ON COMMIT``
|
|
1202
1217
|
^^^^^^^^^^^^^
|
|
1203
1218
|
|
|
@@ -1390,6 +1405,29 @@ that are subject to the action::
|
|
|
1390
1405
|
.. versionadded:: 2.0.40
|
|
1391
1406
|
|
|
1392
1407
|
|
|
1408
|
+
``NULLS NOT DISTINCT``
|
|
1409
|
+
^^^^^^^^^^^^^^^^^^^^^^
|
|
1410
|
+
|
|
1411
|
+
By default, two ``null`` values are not considered equal for unique constraints
|
|
1412
|
+
and indexes. Therefore, seemingly duplicate rows may be stored if one of the
|
|
1413
|
+
values in the constraint is ``null``. This default behavior is implementation
|
|
1414
|
+
defined, so other SQL dialects may behave differently than PostgreSQL.
|
|
1415
|
+
|
|
1416
|
+
The ``NULLS NOT DISTINCT`` clause can be used to change this behavior, treating
|
|
1417
|
+
null values as equal and preventing unintended duplicate rows. The opposite
|
|
1418
|
+
``NULLS DISTINCT`` clause can also be used to make PostgreSQL's default behavior
|
|
1419
|
+
explict.
|
|
1420
|
+
|
|
1421
|
+
The ``postgresql_nulls_not_distinct`` parameter can be set to ``True`` to
|
|
1422
|
+
add the ``NULLS NOT DISTINCT`` clause, or ``False`` to add ``NULLS DISTINCT``.
|
|
1423
|
+
Not setting it, or passing ``None``, will not add a clause and keep the default
|
|
1424
|
+
behavior.
|
|
1425
|
+
|
|
1426
|
+
This feature requires PostgreSQL 15 or later.
|
|
1427
|
+
|
|
1428
|
+
.. versionadded:: 2.0.16
|
|
1429
|
+
|
|
1430
|
+
|
|
1393
1431
|
.. _postgresql_table_valued_overview:
|
|
1394
1432
|
|
|
1395
1433
|
Table values, Table and Column valued functions, Row and Tuple objects
|
|
@@ -3478,7 +3516,11 @@ class PGDialect(default.DefaultDialect):
|
|
|
3478
3516
|
self.do_begin(connection.connection)
|
|
3479
3517
|
|
|
3480
3518
|
def do_prepare_twophase(self, connection, xid):
|
|
3481
|
-
connection.
|
|
3519
|
+
connection.execute(
|
|
3520
|
+
sql.text("PREPARE TRANSACTION :xid").bindparams(
|
|
3521
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
3522
|
+
)
|
|
3523
|
+
)
|
|
3482
3524
|
|
|
3483
3525
|
def do_rollback_twophase(
|
|
3484
3526
|
self, connection, xid, is_prepared=True, recover=False
|
|
@@ -3490,7 +3532,11 @@ class PGDialect(default.DefaultDialect):
|
|
|
3490
3532
|
# Must find out a way how to make the dbapi not
|
|
3491
3533
|
# open a transaction.
|
|
3492
3534
|
connection.exec_driver_sql("ROLLBACK")
|
|
3493
|
-
connection.
|
|
3535
|
+
connection.execute(
|
|
3536
|
+
sql.text("ROLLBACK PREPARED :xid").bindparams(
|
|
3537
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
3538
|
+
)
|
|
3539
|
+
)
|
|
3494
3540
|
connection.exec_driver_sql("BEGIN")
|
|
3495
3541
|
self.do_rollback(connection.connection)
|
|
3496
3542
|
else:
|
|
@@ -3502,7 +3548,11 @@ class PGDialect(default.DefaultDialect):
|
|
|
3502
3548
|
if is_prepared:
|
|
3503
3549
|
if recover:
|
|
3504
3550
|
connection.exec_driver_sql("ROLLBACK")
|
|
3505
|
-
connection.
|
|
3551
|
+
connection.execute(
|
|
3552
|
+
sql.text("COMMIT PREPARED :xid").bindparams(
|
|
3553
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
3554
|
+
)
|
|
3555
|
+
)
|
|
3506
3556
|
connection.exec_driver_sql("BEGIN")
|
|
3507
3557
|
self.do_rollback(connection.connection)
|
|
3508
3558
|
else:
|