mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-12 11:49:59 -04:00
implement native uuid for mariadb >= 10.7
Modified the MariaDB dialect so that when using the :class:`_sqltypes.Uuid`
datatype with MariaDB >= 10.7, leaving the
:paramref:`_sqltypes.Uuid.native_uuid` parameter at its default of True,
the native ``UUID`` datatype will be rendered in DDL and used for database
communication, rather than ``CHAR(32)`` (the non-native UUID type) as was
the case previously. This is a behavioral change since 2.0, where the
generic :class:`_sqltypes.Uuid` datatype delivered ``CHAR(32)`` for all
MySQL and MariaDB variants. Support for all major DBAPIs is implemented
including support for less common "insertmanyvalues" scenarios where UUID
values are generated in different ways for primary keys. Thanks much to
Volodymyr Kochetkov for delivering the PR.
To support this fully without hacks, the mariadb dialect now supports
driver-specific mariadb dialects as well, where we add one here for the
mysqlconnector DBAPI that doesn't accept Python UUID objects, whereas
all the other ones do.
Fixes: #10339
Closes: #10849
Co-authored-by: Mike Bayer <mike_mp@zzzcomputing.com>
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/10849
Pull-request-sha: 8490b08713
Change-Id: Ib920871102b9b64f2cba9697f5cb72b6263e4ed8
This commit is contained in:
committed by
Mike Bayer
parent
1c58fe53b6
commit
49ce245998
+16
@@ -0,0 +1,16 @@
|
||||
.. change::
|
||||
:tags: usecase, mariadb
|
||||
:tickets: 10339
|
||||
|
||||
Modified the MariaDB dialect so that when using the :class:`_sqltypes.Uuid`
|
||||
datatype with MariaDB >= 10.7, leaving the
|
||||
:paramref:`_sqltypes.Uuid.native_uuid` parameter at its default of True,
|
||||
the native ``UUID`` datatype will be rendered in DDL and used for database
|
||||
communication, rather than ``CHAR(32)`` (the non-native UUID type) as was
|
||||
the case previously. This is a behavioral change since 2.0, where the
|
||||
generic :class:`_sqltypes.Uuid` datatype delivered ``CHAR(32)`` for all
|
||||
MySQL and MariaDB variants. Support for all major DBAPIs is implemented
|
||||
including support for less common "insertmanyvalues" scenarios where UUID
|
||||
values are generated in different ways for primary keys. Thanks much to
|
||||
Volodymyr Kochetkov for delivering the PR.
|
||||
|
||||
@@ -7,26 +7,100 @@
|
||||
# mypy: ignore-errors
|
||||
from .base import MariaDBIdentifierPreparer
|
||||
from .base import MySQLDialect
|
||||
from ... import util
|
||||
from ...sql.sqltypes import UUID
|
||||
from ...sql.sqltypes import Uuid
|
||||
|
||||
|
||||
class _MariaDBUUID(UUID):
|
||||
def __init__(self, as_uuid: bool = True, native_uuid: bool = True):
|
||||
self.as_uuid = as_uuid
|
||||
|
||||
# the _MariaDBUUID internal type is only invoked for a Uuid() with
|
||||
# native_uuid=True. for non-native uuid type, the plain Uuid
|
||||
# returns itself due to the workings of the Emulated superclass.
|
||||
assert native_uuid
|
||||
|
||||
# for internal type, force string conversion for result_processor() as
|
||||
# current drivers are returning a string, not a Python UUID object
|
||||
self.native_uuid = False
|
||||
|
||||
@property
|
||||
def native(self):
|
||||
# override to return True, this is a native type, just turning
|
||||
# off native_uuid for internal data handling
|
||||
return True
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
if not dialect.supports_native_uuid or not dialect._allows_uuid_binds:
|
||||
return super().bind_processor(dialect)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _sentinel_value_resolver(self, dialect):
|
||||
"""Return a callable that will receive the uuid object or string
|
||||
as it is normally passed to the DB in the parameter set, after
|
||||
bind_processor() is called. Convert this value to match
|
||||
what it would be as coming back from MariaDB RETURNING. this seems
|
||||
to be *after* SQLAlchemy's datatype has converted, so these
|
||||
will be UUID objects if as_uuid=True and dashed strings if
|
||||
as_uuid=False
|
||||
|
||||
"""
|
||||
|
||||
if not dialect._allows_uuid_binds:
|
||||
|
||||
def process(value):
|
||||
return (
|
||||
f"{value[0:8]}-{value[8:12]}-"
|
||||
f"{value[12:16]}-{value[16:20]}-{value[20:]}"
|
||||
)
|
||||
|
||||
return process
|
||||
elif self.as_uuid:
|
||||
return str
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class MariaDBDialect(MySQLDialect):
|
||||
is_mariadb = True
|
||||
supports_statement_cache = True
|
||||
supports_native_uuid = True
|
||||
|
||||
_allows_uuid_binds = True
|
||||
|
||||
name = "mariadb"
|
||||
preparer = MariaDBIdentifierPreparer
|
||||
|
||||
colspecs = util.update_copy(MySQLDialect.colspecs, {Uuid: _MariaDBUUID})
|
||||
|
||||
def initialize(self, connection):
|
||||
super().initialize(connection)
|
||||
|
||||
self.supports_native_uuid = (
|
||||
self.server_version_info is not None
|
||||
and self.server_version_info >= (10, 7)
|
||||
)
|
||||
|
||||
|
||||
def loader(driver):
|
||||
driver_mod = __import__(
|
||||
dialect_mod = __import__(
|
||||
"sqlalchemy.dialects.mysql.%s" % driver
|
||||
).dialects.mysql
|
||||
driver_cls = getattr(driver_mod, driver).dialect
|
||||
|
||||
return type(
|
||||
"MariaDBDialect_%s" % driver,
|
||||
(
|
||||
MariaDBDialect,
|
||||
driver_cls,
|
||||
),
|
||||
{"supports_statement_cache": True},
|
||||
)
|
||||
driver_mod = getattr(dialect_mod, driver)
|
||||
if hasattr(driver_mod, "mariadb_dialect"):
|
||||
driver_cls = driver_mod.mariadb_dialect
|
||||
return driver_cls
|
||||
else:
|
||||
driver_cls = driver_mod.dialect
|
||||
|
||||
return type(
|
||||
"MariaDBDialect_%s" % driver,
|
||||
(
|
||||
MariaDBDialect,
|
||||
driver_cls,
|
||||
),
|
||||
{"supports_statement_cache": True},
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ from uuid import UUID as _python_UUID
|
||||
from .base import MySQLCompiler
|
||||
from .base import MySQLDialect
|
||||
from .base import MySQLExecutionContext
|
||||
from .mariadb import MariaDBDialect
|
||||
from ... import sql
|
||||
from ... import util
|
||||
from ...sql import sqltypes
|
||||
@@ -279,4 +280,12 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
|
||||
)
|
||||
|
||||
|
||||
class MariaDBDialect_mariadbconnector(
|
||||
MariaDBDialect, MySQLDialect_mariadbconnector
|
||||
):
|
||||
supports_statement_cache = True
|
||||
_allows_uuid_binds = False
|
||||
|
||||
|
||||
dialect = MySQLDialect_mariadbconnector
|
||||
mariadb_dialect = MariaDBDialect_mariadbconnector
|
||||
|
||||
@@ -29,6 +29,7 @@ from .base import BIT
|
||||
from .base import MySQLCompiler
|
||||
from .base import MySQLDialect
|
||||
from .base import MySQLIdentifierPreparer
|
||||
from .mariadb import MariaDBDialect
|
||||
from ... import util
|
||||
|
||||
|
||||
@@ -176,4 +177,12 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
super()._set_isolation_level(connection, level)
|
||||
|
||||
|
||||
class MariaDBDialect_mysqlconnector(
|
||||
MariaDBDialect, MySQLDialect_mysqlconnector
|
||||
):
|
||||
supports_statement_cache = True
|
||||
_allows_uuid_binds = False
|
||||
|
||||
|
||||
dialect = MySQLDialect_mysqlconnector
|
||||
mariadb_dialect = MariaDBDialect_mysqlconnector
|
||||
|
||||
@@ -21,6 +21,7 @@ from sqlalchemy import TypeDecorator
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy import UnicodeText
|
||||
from sqlalchemy.dialects.mysql import base as mysql
|
||||
from sqlalchemy.dialects.mysql.mariadb import MariaDBDialect
|
||||
from sqlalchemy.testing import assert_raises
|
||||
from sqlalchemy.testing import assert_raises_message
|
||||
from sqlalchemy.testing import AssertsCompiledSQL
|
||||
@@ -474,6 +475,48 @@ class TypeCompileTest(fixtures.TestBase, AssertsCompiledSQL):
|
||||
self.assert_compile(type_, sql_text)
|
||||
|
||||
|
||||
class MariaDBUUIDTest(fixtures.TestBase, AssertsCompiledSQL):
|
||||
__only_on__ = "mysql", "mariadb"
|
||||
__backend__ = True
|
||||
|
||||
def test_requirements(self):
|
||||
if testing.against("mariadb>=10.7"):
|
||||
assert testing.requires.uuid_data_type.enabled
|
||||
else:
|
||||
assert not testing.requires.uuid_data_type.enabled
|
||||
|
||||
def test_compile_generic(self):
|
||||
if testing.against("mariadb>=10.7"):
|
||||
self.assert_compile(sqltypes.Uuid(), "UUID")
|
||||
else:
|
||||
self.assert_compile(sqltypes.Uuid(), "CHAR(32)")
|
||||
|
||||
def test_compile_upper(self):
|
||||
self.assert_compile(sqltypes.UUID(), "UUID")
|
||||
|
||||
@testing.combinations(
|
||||
(sqltypes.Uuid(), (10, 6, 5), "CHAR(32)"),
|
||||
(sqltypes.Uuid(native_uuid=False), (10, 6, 5), "CHAR(32)"),
|
||||
(sqltypes.Uuid(), (10, 7, 0), "UUID"),
|
||||
(sqltypes.Uuid(native_uuid=False), (10, 7, 0), "CHAR(32)"),
|
||||
(sqltypes.UUID(), (10, 6, 5), "UUID"),
|
||||
(sqltypes.UUID(), (10, 7, 0), "UUID"),
|
||||
)
|
||||
def test_mariadb_uuid_combinations(self, type_, version, res):
|
||||
dialect = MariaDBDialect()
|
||||
dialect.server_version_info = version
|
||||
dialect.supports_native_uuid = version >= (10, 7)
|
||||
self.assert_compile(type_, res, dialect=dialect)
|
||||
|
||||
@testing.combinations(
|
||||
(sqltypes.Uuid(),),
|
||||
(sqltypes.Uuid(native_uuid=False),),
|
||||
)
|
||||
def test_mysql_uuid_combinations(self, type_):
|
||||
dialect = mysql.MySQLDialect()
|
||||
self.assert_compile(type_, "CHAR(32)", dialect=dialect)
|
||||
|
||||
|
||||
class TypeRoundTripTest(fixtures.TestBase, AssertsExecutionResults):
|
||||
__dialect__ = mysql.dialect()
|
||||
__only_on__ = "mysql", "mariadb"
|
||||
|
||||
@@ -1527,8 +1527,7 @@ class DefaultRequirements(SuiteRequirements):
|
||||
|
||||
@property
|
||||
def async_dialect(self):
|
||||
"""dialect makes use of await_() to invoke operations on
|
||||
the DBAPI."""
|
||||
"""dialect makes use of await_() to invoke operations on the DBAPI."""
|
||||
|
||||
return self.asyncio + only_on(
|
||||
LambdaPredicate(
|
||||
|
||||
Reference in New Issue
Block a user