This commit is contained in:
2025-05-22 20:25:38 +02:00
parent 09f6750c2b
commit ce03fbf12f
529 changed files with 3353 additions and 3312 deletions

View File

@@ -37,6 +37,8 @@ from ...util.concurrency import await_only
class AsyncAdapt_aiomysql_cursor:
# TODO: base on connectors/asyncio.py
# see #10415
server_side = False
__slots__ = (
"_adapt_connection",
@@ -139,6 +141,8 @@ class AsyncAdapt_aiomysql_cursor:
class AsyncAdapt_aiomysql_ss_cursor(AsyncAdapt_aiomysql_cursor):
# TODO: base on connectors/asyncio.py
# see #10415
__slots__ = ()
server_side = True
@@ -167,6 +171,8 @@ class AsyncAdapt_aiomysql_ss_cursor(AsyncAdapt_aiomysql_cursor):
class AsyncAdapt_aiomysql_connection(AdaptedConnection):
# TODO: base on connectors/asyncio.py
# see #10415
await_ = staticmethod(await_only)
__slots__ = ("dbapi", "_execute_mutex")
@@ -202,6 +208,8 @@ class AsyncAdapt_aiomysql_connection(AdaptedConnection):
class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection):
# TODO: base on connectors/asyncio.py
# see #10415
__slots__ = ()
await_ = staticmethod(await_fallback)

View File

@@ -37,6 +37,8 @@ from ...util.concurrency import await_only
class AsyncAdapt_asyncmy_cursor:
# TODO: base on connectors/asyncio.py
# see #10415
server_side = False
__slots__ = (
"_adapt_connection",
@@ -141,6 +143,8 @@ class AsyncAdapt_asyncmy_cursor:
class AsyncAdapt_asyncmy_ss_cursor(AsyncAdapt_asyncmy_cursor):
# TODO: base on connectors/asyncio.py
# see #10415
__slots__ = ()
server_side = True
@@ -171,6 +175,8 @@ class AsyncAdapt_asyncmy_ss_cursor(AsyncAdapt_asyncmy_cursor):
class AsyncAdapt_asyncmy_connection(AdaptedConnection):
# TODO: base on connectors/asyncio.py
# see #10415
await_ = staticmethod(await_only)
__slots__ = ("dbapi", "_execute_mutex")

View File

@@ -999,14 +999,14 @@ output::
)
""" # noqa
from __future__ import annotations
from array import array as _array
from collections import defaultdict
from itertools import compress
import re
from typing import cast
from sqlalchemy import literal_column
from sqlalchemy.sql import visitors
from . import reflection as _reflection
from .enumerated import ENUM
from .enumerated import SET
@@ -1047,10 +1047,12 @@ from .types import TINYTEXT
from .types import VARCHAR
from .types import YEAR
from ... import exc
from ... import literal_column
from ... import log
from ... import schema as sa_schema
from ... import sql
from ... import util
from ...engine import cursor as _cursor
from ...engine import default
from ...engine import reflection
from ...engine.reflection import ReflectionDefaults
@@ -1062,7 +1064,10 @@ from ...sql import operators
from ...sql import roles
from ...sql import sqltypes
from ...sql import util as sql_util
from ...sql import visitors
from ...sql.compiler import InsertmanyvaluesSentinelOpts
from ...sql.compiler import SQLCompiler
from ...sql.schema import SchemaConst
from ...types import BINARY
from ...types import BLOB
from ...types import BOOLEAN
@@ -1071,6 +1076,7 @@ from ...types import UUID
from ...types import VARBINARY
from ...util import topological
SET_RE = re.compile(
r"\s*SET\s+(?:(?:GLOBAL|SESSION)\s+)?\w", re.I | re.UNICODE
)
@@ -1164,6 +1170,32 @@ ischema_names = {
class MySQLExecutionContext(default.DefaultExecutionContext):
def post_exec(self):
if (
self.isdelete
and cast(SQLCompiler, self.compiled).effective_returning
and not self.cursor.description
):
# All MySQL/mariadb drivers appear to not include
# cursor.description for DELETE..RETURNING with no rows if the
# WHERE criteria is a straight "false" condition such as our EMPTY
# IN condition. manufacture an empty result in this case (issue
# #10505)
#
# taken from cx_Oracle implementation
self.cursor_fetch_strategy = (
_cursor.FullyBufferedCursorFetchStrategy(
self.cursor,
[
(entry.keyname, None)
for entry in cast(
SQLCompiler, self.compiled
)._result_columns
],
[],
)
)
def create_server_side_cursor(self):
if self.dialect.supports_server_side_cursors:
return self._dbapi_connection.cursor(self.dialect._sscursor)
@@ -1208,6 +1240,12 @@ class MySQLCompiler(compiler.SQLCompiler):
)
return f"{clause} WITH ROLLUP"
def visit_aggregate_strings_func(self, fn, **kw):
expr, delimeter = (
elem._compiler_dispatch(self, **kw) for elem in fn.clauses
)
return f"group_concat({expr} SEPARATOR {delimeter})"
def visit_sequence(self, seq, **kw):
return "nextval(%s)" % self.preparer.format_sequence(seq)
@@ -1760,7 +1798,12 @@ class MySQLCompiler(compiler.SQLCompiler):
class MySQLDDLCompiler(compiler.DDLCompiler):
def get_column_specification(self, column, **kw):
"""Builds column DDL."""
if (
self.dialect.is_mariadb is True
and column.computed is not None
and column._user_defined_nullable is SchemaConst.NULL_UNSPECIFIED
):
column.nullable = True
colspec = [
self.preparer.format_column(column),
self.dialect.type_compiler_instance.process(

View File

@@ -30,16 +30,46 @@ be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.
""" # noqa
import re
from uuid import UUID as _python_UUID
from .base import MySQLCompiler
from .base import MySQLDialect
from .base import MySQLExecutionContext
from ... import sql
from ... import util
from ...sql import sqltypes
mariadb_cpy_minimum_version = (1, 0, 1)
class _MariaDBUUID(sqltypes.UUID[sqltypes._UUID_RETURN]):
# work around JIRA issue
# https://jira.mariadb.org/browse/CONPY-270. When that issue is fixed,
# this type can be removed.
def result_processor(self, dialect, coltype):
if self.as_uuid:
def process(value):
if value is not None:
if hasattr(value, "decode"):
value = value.decode("ascii")
value = _python_UUID(value)
return value
return process
else:
def process(value):
if value is not None:
if hasattr(value, "decode"):
value = value.decode("ascii")
value = str(_python_UUID(value))
return value
return process
class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
_lastrowid = None
@@ -50,9 +80,20 @@ class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
return self._dbapi_connection.cursor(buffered=True)
def post_exec(self):
super().post_exec()
self._rowcount = self.cursor.rowcount
if self.isinsert and self.compiled.postfetch_lastrowid:
self._lastrowid = self.cursor.lastrowid
@property
def rowcount(self):
if self._rowcount is not None:
return self._rowcount
else:
return self.cursor.rowcount
def get_lastrowid(self):
return self._lastrowid
@@ -87,6 +128,10 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
supports_server_side_cursors = True
colspecs = util.update_copy(
MySQLDialect.colspecs, {sqltypes.Uuid: _MariaDBUUID}
)
@util.memoized_property
def _dbapi_version(self):
if self.dbapi and hasattr(self.dbapi, "__version__"):

View File

@@ -168,7 +168,7 @@ class MySQLDialect_mysqldb(MySQLDialect):
return on_connect
def do_ping(self, dbapi_connection):
dbapi_connection.ping(False)
dbapi_connection.ping()
return True
def do_executemany(self, cursor, statement, parameters, context=None):

View File

@@ -74,6 +74,40 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
def import_dbapi(cls):
return __import__("pymysql")
@langhelpers.memoized_property
def _send_false_to_ping(self):
"""determine if pymysql has deprecated, changed the default of,
or removed the 'reconnect' argument of connection.ping().
See #10492 and
https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
for background.
""" # noqa: E501
try:
Connection = __import__("pymysql.connections").Connection
except (ImportError, AttributeError):
return True
else:
insp = langhelpers.get_callable_argspec(Connection.ping)
try:
reconnect_arg = insp.args[1]
except IndexError:
return False
else:
return reconnect_arg == "reconnect" and (
not insp.defaults or insp.defaults[0] is not False
)
def do_ping(self, dbapi_connection):
if self._send_false_to_ping:
dbapi_connection.ping(False)
else:
dbapi_connection.ping()
return True
def create_connect_args(self, url, _translate_args=None):
if _translate_args is None:
_translate_args = dict(username="user")

View File

@@ -509,7 +509,7 @@ class MySQLTableDefinitionParser:
r"\((?P<local>[^\)]+?)\) REFERENCES +"
r"(?P<table>%(iq)s[^%(fq)s]+%(fq)s"
r"(?:\.%(iq)s[^%(fq)s]+%(fq)s)?) +"
r"\((?P<foreign>[^\)]+?)\)"
r"\((?P<foreign>(?:%(iq)s[^%(fq)s]+%(fq)s(?: *, *)?)+)\)"
r"(?: +(?P<match>MATCH \w+))?"
r"(?: +ON DELETE (?P<ondelete>%(on)s))?"
r"(?: +ON UPDATE (?P<onupdate>%(on)s))?" % kw