SQLAlchemy 2.0.47__cp313-cp313t-win32.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 +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# dialects/mysql/cymysql.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
r"""
|
|
9
|
+
|
|
10
|
+
.. dialect:: mysql+cymysql
|
|
11
|
+
:name: CyMySQL
|
|
12
|
+
:dbapi: cymysql
|
|
13
|
+
:connectstring: mysql+cymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
|
14
|
+
:url: https://github.com/nakagami/CyMySQL
|
|
15
|
+
|
|
16
|
+
.. note::
|
|
17
|
+
|
|
18
|
+
The CyMySQL dialect is **not tested as part of SQLAlchemy's continuous
|
|
19
|
+
integration** and may have unresolved issues. The recommended MySQL
|
|
20
|
+
dialects are mysqlclient and PyMySQL.
|
|
21
|
+
|
|
22
|
+
""" # noqa
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
from typing import Iterable
|
|
27
|
+
from typing import Optional
|
|
28
|
+
from typing import TYPE_CHECKING
|
|
29
|
+
from typing import Union
|
|
30
|
+
|
|
31
|
+
from .base import MySQLDialect
|
|
32
|
+
from .mysqldb import MySQLDialect_mysqldb
|
|
33
|
+
from .types import BIT
|
|
34
|
+
from ... import util
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from ...engine.base import Connection
|
|
38
|
+
from ...engine.interfaces import DBAPIConnection
|
|
39
|
+
from ...engine.interfaces import DBAPICursor
|
|
40
|
+
from ...engine.interfaces import DBAPIModule
|
|
41
|
+
from ...engine.interfaces import Dialect
|
|
42
|
+
from ...engine.interfaces import PoolProxiedConnection
|
|
43
|
+
from ...sql.type_api import _ResultProcessorType
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _cymysqlBIT(BIT):
|
|
47
|
+
def result_processor(
|
|
48
|
+
self, dialect: Dialect, coltype: object
|
|
49
|
+
) -> Optional[_ResultProcessorType[Any]]:
|
|
50
|
+
"""Convert MySQL's 64 bit, variable length binary string to a long."""
|
|
51
|
+
|
|
52
|
+
def process(value: Optional[Iterable[int]]) -> Optional[int]:
|
|
53
|
+
if value is not None:
|
|
54
|
+
v = 0
|
|
55
|
+
for i in iter(value):
|
|
56
|
+
v = v << 8 | i
|
|
57
|
+
return v
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
return process
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class MySQLDialect_cymysql(MySQLDialect_mysqldb):
|
|
64
|
+
driver = "cymysql"
|
|
65
|
+
supports_statement_cache = True
|
|
66
|
+
|
|
67
|
+
description_encoding = None
|
|
68
|
+
supports_sane_rowcount = True
|
|
69
|
+
supports_sane_multi_rowcount = False
|
|
70
|
+
supports_unicode_statements = True
|
|
71
|
+
|
|
72
|
+
colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _cymysqlBIT})
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def import_dbapi(cls) -> DBAPIModule:
|
|
76
|
+
return __import__("cymysql")
|
|
77
|
+
|
|
78
|
+
def _detect_charset(self, connection: Connection) -> str:
|
|
79
|
+
return connection.connection.charset # type: ignore[no-any-return]
|
|
80
|
+
|
|
81
|
+
def _extract_error_code(self, exception: DBAPIModule.Error) -> int:
|
|
82
|
+
return exception.errno # type: ignore[no-any-return]
|
|
83
|
+
|
|
84
|
+
def is_disconnect(
|
|
85
|
+
self,
|
|
86
|
+
e: DBAPIModule.Error,
|
|
87
|
+
connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
|
|
88
|
+
cursor: Optional[DBAPICursor],
|
|
89
|
+
) -> bool:
|
|
90
|
+
if isinstance(e, self.loaded_dbapi.OperationalError):
|
|
91
|
+
return self._extract_error_code(e) in (
|
|
92
|
+
2006,
|
|
93
|
+
2013,
|
|
94
|
+
2014,
|
|
95
|
+
2045,
|
|
96
|
+
2055,
|
|
97
|
+
)
|
|
98
|
+
elif isinstance(e, self.loaded_dbapi.InterfaceError):
|
|
99
|
+
# if underlying connection is closed,
|
|
100
|
+
# this is the error you get
|
|
101
|
+
return True
|
|
102
|
+
else:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
dialect = MySQLDialect_cymysql
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# dialects/mysql/dml.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import Dict
|
|
11
|
+
from typing import List
|
|
12
|
+
from typing import Mapping
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
from ... import exc
|
|
18
|
+
from ... import util
|
|
19
|
+
from ...sql._typing import _DMLTableArgument
|
|
20
|
+
from ...sql.base import _exclusive_against
|
|
21
|
+
from ...sql.base import _generative
|
|
22
|
+
from ...sql.base import ColumnCollection
|
|
23
|
+
from ...sql.base import ReadOnlyColumnCollection
|
|
24
|
+
from ...sql.dml import Insert as StandardInsert
|
|
25
|
+
from ...sql.elements import ClauseElement
|
|
26
|
+
from ...sql.elements import KeyedColumnElement
|
|
27
|
+
from ...sql.expression import alias
|
|
28
|
+
from ...sql.selectable import NamedFromClause
|
|
29
|
+
from ...util.typing import Self
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = ("Insert", "insert")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def insert(table: _DMLTableArgument) -> Insert:
|
|
36
|
+
"""Construct a MySQL/MariaDB-specific variant :class:`_mysql.Insert`
|
|
37
|
+
construct.
|
|
38
|
+
|
|
39
|
+
.. container:: inherited_member
|
|
40
|
+
|
|
41
|
+
The :func:`sqlalchemy.dialects.mysql.insert` function creates
|
|
42
|
+
a :class:`sqlalchemy.dialects.mysql.Insert`. This class is based
|
|
43
|
+
on the dialect-agnostic :class:`_sql.Insert` construct which may
|
|
44
|
+
be constructed using the :func:`_sql.insert` function in
|
|
45
|
+
SQLAlchemy Core.
|
|
46
|
+
|
|
47
|
+
The :class:`_mysql.Insert` construct includes additional methods
|
|
48
|
+
:meth:`_mysql.Insert.on_duplicate_key_update`.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return Insert(table)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Insert(StandardInsert):
|
|
55
|
+
"""MySQL-specific implementation of INSERT.
|
|
56
|
+
|
|
57
|
+
Adds methods for MySQL-specific syntaxes such as ON DUPLICATE KEY UPDATE.
|
|
58
|
+
|
|
59
|
+
The :class:`~.mysql.Insert` object is created using the
|
|
60
|
+
:func:`sqlalchemy.dialects.mysql.insert` function.
|
|
61
|
+
|
|
62
|
+
.. versionadded:: 1.2
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
stringify_dialect = "mysql"
|
|
67
|
+
inherit_cache = False
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def inserted(
|
|
71
|
+
self,
|
|
72
|
+
) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
73
|
+
"""Provide the "inserted" namespace for an ON DUPLICATE KEY UPDATE
|
|
74
|
+
statement
|
|
75
|
+
|
|
76
|
+
MySQL's ON DUPLICATE KEY UPDATE clause allows reference to the row
|
|
77
|
+
that would be inserted, via a special function called ``VALUES()``.
|
|
78
|
+
This attribute provides all columns in this row to be referenceable
|
|
79
|
+
such that they will render within a ``VALUES()`` function inside the
|
|
80
|
+
ON DUPLICATE KEY UPDATE clause. The attribute is named ``.inserted``
|
|
81
|
+
so as not to conflict with the existing
|
|
82
|
+
:meth:`_expression.Insert.values` method.
|
|
83
|
+
|
|
84
|
+
.. tip:: The :attr:`_mysql.Insert.inserted` attribute is an instance
|
|
85
|
+
of :class:`_expression.ColumnCollection`, which provides an
|
|
86
|
+
interface the same as that of the :attr:`_schema.Table.c`
|
|
87
|
+
collection described at :ref:`metadata_tables_and_columns`.
|
|
88
|
+
With this collection, ordinary names are accessible like attributes
|
|
89
|
+
(e.g. ``stmt.inserted.some_column``), but special names and
|
|
90
|
+
dictionary method names should be accessed using indexed access,
|
|
91
|
+
such as ``stmt.inserted["column name"]`` or
|
|
92
|
+
``stmt.inserted["values"]``. See the docstring for
|
|
93
|
+
:class:`_expression.ColumnCollection` for further examples.
|
|
94
|
+
|
|
95
|
+
.. seealso::
|
|
96
|
+
|
|
97
|
+
:ref:`mysql_insert_on_duplicate_key_update` - example of how
|
|
98
|
+
to use :attr:`_expression.Insert.inserted`
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
return self.inserted_alias.columns
|
|
102
|
+
|
|
103
|
+
@util.memoized_property
|
|
104
|
+
def inserted_alias(self) -> NamedFromClause:
|
|
105
|
+
return alias(self.table, name="inserted")
|
|
106
|
+
|
|
107
|
+
@_generative
|
|
108
|
+
@_exclusive_against(
|
|
109
|
+
"_post_values_clause",
|
|
110
|
+
msgs={
|
|
111
|
+
"_post_values_clause": "This Insert construct already "
|
|
112
|
+
"has an ON DUPLICATE KEY clause present"
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
def on_duplicate_key_update(self, *args: _UpdateArg, **kw: Any) -> Self:
|
|
116
|
+
r"""
|
|
117
|
+
Specifies the ON DUPLICATE KEY UPDATE clause.
|
|
118
|
+
|
|
119
|
+
:param \**kw: Column keys linked to UPDATE values. The
|
|
120
|
+
values may be any SQL expression or supported literal Python
|
|
121
|
+
values.
|
|
122
|
+
|
|
123
|
+
.. warning:: This dictionary does **not** take into account
|
|
124
|
+
Python-specified default UPDATE values or generation functions,
|
|
125
|
+
e.g. those specified using :paramref:`_schema.Column.onupdate`.
|
|
126
|
+
These values will not be exercised for an ON DUPLICATE KEY UPDATE
|
|
127
|
+
style of UPDATE, unless values are manually specified here.
|
|
128
|
+
|
|
129
|
+
:param \*args: As an alternative to passing key/value parameters,
|
|
130
|
+
a dictionary or list of 2-tuples can be passed as a single positional
|
|
131
|
+
argument.
|
|
132
|
+
|
|
133
|
+
Passing a single dictionary is equivalent to the keyword argument
|
|
134
|
+
form::
|
|
135
|
+
|
|
136
|
+
insert().on_duplicate_key_update({"name": "some name"})
|
|
137
|
+
|
|
138
|
+
Passing a list of 2-tuples indicates that the parameter assignments
|
|
139
|
+
in the UPDATE clause should be ordered as sent, in a manner similar
|
|
140
|
+
to that described for the :class:`_expression.Update`
|
|
141
|
+
construct overall
|
|
142
|
+
in :ref:`tutorial_parameter_ordered_updates`::
|
|
143
|
+
|
|
144
|
+
insert().on_duplicate_key_update(
|
|
145
|
+
[
|
|
146
|
+
("name", "some name"),
|
|
147
|
+
("value", "some value"),
|
|
148
|
+
]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
.. versionchanged:: 1.3 parameters can be specified as a dictionary
|
|
152
|
+
or list of 2-tuples; the latter form provides for parameter
|
|
153
|
+
ordering.
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
.. versionadded:: 1.2
|
|
157
|
+
|
|
158
|
+
.. seealso::
|
|
159
|
+
|
|
160
|
+
:ref:`mysql_insert_on_duplicate_key_update`
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
if args and kw:
|
|
164
|
+
raise exc.ArgumentError(
|
|
165
|
+
"Can't pass kwargs and positional arguments simultaneously"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if args:
|
|
169
|
+
if len(args) > 1:
|
|
170
|
+
raise exc.ArgumentError(
|
|
171
|
+
"Only a single dictionary or list of tuples "
|
|
172
|
+
"is accepted positionally."
|
|
173
|
+
)
|
|
174
|
+
values = args[0]
|
|
175
|
+
else:
|
|
176
|
+
values = kw
|
|
177
|
+
|
|
178
|
+
self._post_values_clause = OnDuplicateClause(
|
|
179
|
+
self.inserted_alias, values
|
|
180
|
+
)
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class OnDuplicateClause(ClauseElement):
|
|
185
|
+
__visit_name__ = "on_duplicate_key_update"
|
|
186
|
+
|
|
187
|
+
_parameter_ordering: Optional[List[str]] = None
|
|
188
|
+
|
|
189
|
+
update: Dict[str, Any]
|
|
190
|
+
stringify_dialect = "mysql"
|
|
191
|
+
|
|
192
|
+
def __init__(
|
|
193
|
+
self, inserted_alias: NamedFromClause, update: _UpdateArg
|
|
194
|
+
) -> None:
|
|
195
|
+
self.inserted_alias = inserted_alias
|
|
196
|
+
|
|
197
|
+
# auto-detect that parameters should be ordered. This is copied from
|
|
198
|
+
# Update._proces_colparams(), however we don't look for a special flag
|
|
199
|
+
# in this case since we are not disambiguating from other use cases as
|
|
200
|
+
# we are in Update.values().
|
|
201
|
+
if isinstance(update, list) and (
|
|
202
|
+
update and isinstance(update[0], tuple)
|
|
203
|
+
):
|
|
204
|
+
self._parameter_ordering = [key for key, value in update]
|
|
205
|
+
update = dict(update)
|
|
206
|
+
|
|
207
|
+
if isinstance(update, dict):
|
|
208
|
+
if not update:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
"update parameter dictionary must not be empty"
|
|
211
|
+
)
|
|
212
|
+
elif isinstance(update, ColumnCollection):
|
|
213
|
+
update = dict(update)
|
|
214
|
+
else:
|
|
215
|
+
raise ValueError(
|
|
216
|
+
"update parameter must be a non-empty dictionary "
|
|
217
|
+
"or a ColumnCollection such as the `.c.` collection "
|
|
218
|
+
"of a Table object"
|
|
219
|
+
)
|
|
220
|
+
self.update = update
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
_UpdateArg = Union[
|
|
224
|
+
Mapping[Any, Any], List[Tuple[str, Any]], ColumnCollection[Any, Any]
|
|
225
|
+
]
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# dialects/mysql/enumerated.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import enum
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import Dict
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from typing import Set
|
|
16
|
+
from typing import Type
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
from typing import Union
|
|
19
|
+
|
|
20
|
+
from .types import _StringType
|
|
21
|
+
from ... import exc
|
|
22
|
+
from ... import sql
|
|
23
|
+
from ... import util
|
|
24
|
+
from ...sql import sqltypes
|
|
25
|
+
from ...sql import type_api
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from ...engine.interfaces import Dialect
|
|
29
|
+
from ...sql.elements import ColumnElement
|
|
30
|
+
from ...sql.type_api import _BindProcessorType
|
|
31
|
+
from ...sql.type_api import _ResultProcessorType
|
|
32
|
+
from ...sql.type_api import TypeEngine
|
|
33
|
+
from ...sql.type_api import TypeEngineMixin
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ENUM(type_api.NativeForEmulated, sqltypes.Enum, _StringType):
|
|
37
|
+
"""MySQL ENUM type."""
|
|
38
|
+
|
|
39
|
+
__visit_name__ = "ENUM"
|
|
40
|
+
|
|
41
|
+
native_enum = True
|
|
42
|
+
|
|
43
|
+
def __init__(self, *enums: Union[str, Type[enum.Enum]], **kw: Any) -> None:
|
|
44
|
+
"""Construct an ENUM.
|
|
45
|
+
|
|
46
|
+
E.g.::
|
|
47
|
+
|
|
48
|
+
Column("myenum", ENUM("foo", "bar", "baz"))
|
|
49
|
+
|
|
50
|
+
:param enums: The range of valid values for this ENUM. Values in
|
|
51
|
+
enums are not quoted, they will be escaped and surrounded by single
|
|
52
|
+
quotes when generating the schema. This object may also be a
|
|
53
|
+
PEP-435-compliant enumerated type.
|
|
54
|
+
|
|
55
|
+
.. versionadded: 1.1 added support for PEP-435-compliant enumerated
|
|
56
|
+
types.
|
|
57
|
+
|
|
58
|
+
:param strict: This flag has no effect.
|
|
59
|
+
|
|
60
|
+
.. versionchanged:: The MySQL ENUM type as well as the base Enum
|
|
61
|
+
type now validates all Python data values.
|
|
62
|
+
|
|
63
|
+
:param charset: Optional, a column-level character set for this string
|
|
64
|
+
value. Takes precedence to 'ascii' or 'unicode' short-hand.
|
|
65
|
+
|
|
66
|
+
:param collation: Optional, a column-level collation for this string
|
|
67
|
+
value. Takes precedence to 'binary' short-hand.
|
|
68
|
+
|
|
69
|
+
:param ascii: Defaults to False: short-hand for the ``latin1``
|
|
70
|
+
character set, generates ASCII in schema.
|
|
71
|
+
|
|
72
|
+
:param unicode: Defaults to False: short-hand for the ``ucs2``
|
|
73
|
+
character set, generates UNICODE in schema.
|
|
74
|
+
|
|
75
|
+
:param binary: Defaults to False: short-hand, pick the binary
|
|
76
|
+
collation type that matches the column's character set. Generates
|
|
77
|
+
BINARY in schema. This does not affect the type of data stored,
|
|
78
|
+
only the collation of character data.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
kw.pop("strict", None)
|
|
82
|
+
self._enum_init(enums, kw) # type: ignore[arg-type]
|
|
83
|
+
_StringType.__init__(self, length=self.length, **kw)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def adapt_emulated_to_native(
|
|
87
|
+
cls,
|
|
88
|
+
impl: Union[TypeEngine[Any], TypeEngineMixin],
|
|
89
|
+
**kw: Any,
|
|
90
|
+
) -> ENUM:
|
|
91
|
+
"""Produce a MySQL native :class:`.mysql.ENUM` from plain
|
|
92
|
+
:class:`.Enum`.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
if TYPE_CHECKING:
|
|
96
|
+
assert isinstance(impl, ENUM)
|
|
97
|
+
kw.setdefault("validate_strings", impl.validate_strings)
|
|
98
|
+
kw.setdefault("values_callable", impl.values_callable)
|
|
99
|
+
kw.setdefault("omit_aliases", impl._omit_aliases)
|
|
100
|
+
return cls(**kw)
|
|
101
|
+
|
|
102
|
+
def _object_value_for_elem(self, elem: str) -> Union[str, enum.Enum]:
|
|
103
|
+
# mysql sends back a blank string for any value that
|
|
104
|
+
# was persisted that was not in the enums; that is, it does no
|
|
105
|
+
# validation on the incoming data, it "truncates" it to be
|
|
106
|
+
# the blank string. Return it straight.
|
|
107
|
+
if elem == "":
|
|
108
|
+
return elem
|
|
109
|
+
else:
|
|
110
|
+
return super()._object_value_for_elem(elem)
|
|
111
|
+
|
|
112
|
+
def __repr__(self) -> str:
|
|
113
|
+
return util.generic_repr(
|
|
114
|
+
self, to_inspect=[ENUM, _StringType, sqltypes.Enum]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# TODO: SET is a string as far as configuration but does not act like
|
|
119
|
+
# a string at the python level. We either need to make a py-type agnostic
|
|
120
|
+
# version of String as a base to be used for this, make this some kind of
|
|
121
|
+
# TypeDecorator, or just vendor it out as its own type.
|
|
122
|
+
class SET(_StringType):
|
|
123
|
+
"""MySQL SET type."""
|
|
124
|
+
|
|
125
|
+
__visit_name__ = "SET"
|
|
126
|
+
|
|
127
|
+
def __init__(self, *values: str, **kw: Any):
|
|
128
|
+
"""Construct a SET.
|
|
129
|
+
|
|
130
|
+
E.g.::
|
|
131
|
+
|
|
132
|
+
Column("myset", SET("foo", "bar", "baz"))
|
|
133
|
+
|
|
134
|
+
The list of potential values is required in the case that this
|
|
135
|
+
set will be used to generate DDL for a table, or if the
|
|
136
|
+
:paramref:`.SET.retrieve_as_bitwise` flag is set to True.
|
|
137
|
+
|
|
138
|
+
:param values: The range of valid values for this SET. The values
|
|
139
|
+
are not quoted, they will be escaped and surrounded by single
|
|
140
|
+
quotes when generating the schema.
|
|
141
|
+
|
|
142
|
+
:param convert_unicode: Same flag as that of
|
|
143
|
+
:paramref:`.String.convert_unicode`.
|
|
144
|
+
|
|
145
|
+
:param collation: same as that of :paramref:`.String.collation`
|
|
146
|
+
|
|
147
|
+
:param charset: same as that of :paramref:`.VARCHAR.charset`.
|
|
148
|
+
|
|
149
|
+
:param ascii: same as that of :paramref:`.VARCHAR.ascii`.
|
|
150
|
+
|
|
151
|
+
:param unicode: same as that of :paramref:`.VARCHAR.unicode`.
|
|
152
|
+
|
|
153
|
+
:param binary: same as that of :paramref:`.VARCHAR.binary`.
|
|
154
|
+
|
|
155
|
+
:param retrieve_as_bitwise: if True, the data for the set type will be
|
|
156
|
+
persisted and selected using an integer value, where a set is coerced
|
|
157
|
+
into a bitwise mask for persistence. MySQL allows this mode which
|
|
158
|
+
has the advantage of being able to store values unambiguously,
|
|
159
|
+
such as the blank string ``''``. The datatype will appear
|
|
160
|
+
as the expression ``col + 0`` in a SELECT statement, so that the
|
|
161
|
+
value is coerced into an integer value in result sets.
|
|
162
|
+
This flag is required if one wishes
|
|
163
|
+
to persist a set that can store the blank string ``''`` as a value.
|
|
164
|
+
|
|
165
|
+
.. warning::
|
|
166
|
+
|
|
167
|
+
When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is
|
|
168
|
+
essential that the list of set values is expressed in the
|
|
169
|
+
**exact same order** as exists on the MySQL database.
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
self.retrieve_as_bitwise = kw.pop("retrieve_as_bitwise", False)
|
|
173
|
+
self.values = tuple(values)
|
|
174
|
+
if not self.retrieve_as_bitwise and "" in values:
|
|
175
|
+
raise exc.ArgumentError(
|
|
176
|
+
"Can't use the blank value '' in a SET without "
|
|
177
|
+
"setting retrieve_as_bitwise=True"
|
|
178
|
+
)
|
|
179
|
+
if self.retrieve_as_bitwise:
|
|
180
|
+
self._inversed_bitmap: Dict[str, int] = {
|
|
181
|
+
value: 2**idx for idx, value in enumerate(self.values)
|
|
182
|
+
}
|
|
183
|
+
self._bitmap: Dict[int, str] = {
|
|
184
|
+
2**idx: value for idx, value in enumerate(self.values)
|
|
185
|
+
}
|
|
186
|
+
length = max([len(v) for v in values] + [0])
|
|
187
|
+
kw.setdefault("length", length)
|
|
188
|
+
super().__init__(**kw)
|
|
189
|
+
|
|
190
|
+
def column_expression(
|
|
191
|
+
self, colexpr: ColumnElement[Any]
|
|
192
|
+
) -> ColumnElement[Any]:
|
|
193
|
+
if self.retrieve_as_bitwise:
|
|
194
|
+
return sql.type_coerce(
|
|
195
|
+
sql.type_coerce(colexpr, sqltypes.Integer) + 0, self
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
return colexpr
|
|
199
|
+
|
|
200
|
+
def result_processor(
|
|
201
|
+
self, dialect: Dialect, coltype: Any
|
|
202
|
+
) -> Optional[_ResultProcessorType[Any]]:
|
|
203
|
+
if self.retrieve_as_bitwise:
|
|
204
|
+
|
|
205
|
+
def process(value: Union[str, int, None]) -> Optional[Set[str]]:
|
|
206
|
+
if value is not None:
|
|
207
|
+
value = int(value)
|
|
208
|
+
|
|
209
|
+
return set(util.map_bits(self._bitmap.__getitem__, value))
|
|
210
|
+
else:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
super_convert = super().result_processor(dialect, coltype)
|
|
215
|
+
|
|
216
|
+
def process(value: Union[str, Set[str], None]) -> Optional[Set[str]]: # type: ignore[misc] # noqa: E501
|
|
217
|
+
if isinstance(value, str):
|
|
218
|
+
# MySQLdb returns a string, let's parse
|
|
219
|
+
if super_convert:
|
|
220
|
+
value = super_convert(value)
|
|
221
|
+
assert value is not None
|
|
222
|
+
if TYPE_CHECKING:
|
|
223
|
+
assert isinstance(value, str)
|
|
224
|
+
return set(re.findall(r"[^,]+", value))
|
|
225
|
+
else:
|
|
226
|
+
# mysql-connector-python does a naive
|
|
227
|
+
# split(",") which throws in an empty string
|
|
228
|
+
if value is not None:
|
|
229
|
+
value.discard("")
|
|
230
|
+
return value
|
|
231
|
+
|
|
232
|
+
return process
|
|
233
|
+
|
|
234
|
+
def bind_processor(
|
|
235
|
+
self, dialect: Dialect
|
|
236
|
+
) -> _BindProcessorType[Union[str, int]]:
|
|
237
|
+
super_convert = super().bind_processor(dialect)
|
|
238
|
+
if self.retrieve_as_bitwise:
|
|
239
|
+
|
|
240
|
+
def process(
|
|
241
|
+
value: Union[str, int, set[str], None],
|
|
242
|
+
) -> Union[str, int, None]:
|
|
243
|
+
if value is None:
|
|
244
|
+
return None
|
|
245
|
+
elif isinstance(value, (int, str)):
|
|
246
|
+
if super_convert:
|
|
247
|
+
return super_convert(value) # type: ignore[arg-type, no-any-return] # noqa: E501
|
|
248
|
+
else:
|
|
249
|
+
return value
|
|
250
|
+
else:
|
|
251
|
+
int_value = 0
|
|
252
|
+
for v in value:
|
|
253
|
+
int_value |= self._inversed_bitmap[v]
|
|
254
|
+
return int_value
|
|
255
|
+
|
|
256
|
+
else:
|
|
257
|
+
|
|
258
|
+
def process(
|
|
259
|
+
value: Union[str, int, set[str], None],
|
|
260
|
+
) -> Union[str, int, None]:
|
|
261
|
+
# accept strings and int (actually bitflag) values directly
|
|
262
|
+
if value is not None and not isinstance(value, (int, str)):
|
|
263
|
+
value = ",".join(value)
|
|
264
|
+
if super_convert:
|
|
265
|
+
return super_convert(value) # type: ignore
|
|
266
|
+
else:
|
|
267
|
+
return value
|
|
268
|
+
|
|
269
|
+
return process
|
|
270
|
+
|
|
271
|
+
def adapt(self, cls: type, **kw: Any) -> Any:
|
|
272
|
+
kw["retrieve_as_bitwise"] = self.retrieve_as_bitwise
|
|
273
|
+
return util.constructor_copy(self, cls, *self.values, **kw)
|
|
274
|
+
|
|
275
|
+
def __repr__(self) -> str:
|
|
276
|
+
return util.generic_repr(
|
|
277
|
+
self,
|
|
278
|
+
to_inspect=[SET, _StringType],
|
|
279
|
+
additional_kw=[
|
|
280
|
+
("retrieve_as_bitwise", False),
|
|
281
|
+
],
|
|
282
|
+
)
|