mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Проверка 09.02.2025
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/__init__.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -53,7 +53,8 @@ from .base import YEAR
|
||||
from .dml import Insert
|
||||
from .dml import insert
|
||||
from .expression import match
|
||||
from ...util import compat
|
||||
from .mariadb import INET4
|
||||
from .mariadb import INET6
|
||||
|
||||
# default dialect
|
||||
base.dialect = dialect = mysqldb.dialect
|
||||
@@ -71,6 +72,8 @@ __all__ = (
|
||||
"DOUBLE",
|
||||
"ENUM",
|
||||
"FLOAT",
|
||||
"INET4",
|
||||
"INET6",
|
||||
"INTEGER",
|
||||
"INTEGER",
|
||||
"JSON",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/aiomysql.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors <see AUTHORS
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors <see AUTHORS
|
||||
# file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -23,10 +23,14 @@ This dialect should normally be used only with the
|
||||
:func:`_asyncio.create_async_engine` engine creation function::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
engine = create_async_engine("mysql+aiomysql://user:pass@hostname/dbname?charset=utf8mb4")
|
||||
|
||||
engine = create_async_engine(
|
||||
"mysql+aiomysql://user:pass@hostname/dbname?charset=utf8mb4"
|
||||
)
|
||||
|
||||
""" # noqa
|
||||
from collections import deque
|
||||
|
||||
from .pymysql import MySQLDialect_pymysql
|
||||
from ... import pool
|
||||
from ... import util
|
||||
@@ -57,7 +61,7 @@ class AsyncAdapt_aiomysql_cursor:
|
||||
|
||||
# see https://github.com/aio-libs/aiomysql/issues/543
|
||||
self._cursor = self.await_(cursor.__aenter__())
|
||||
self._rows = []
|
||||
self._rows = deque()
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
@@ -87,7 +91,7 @@ class AsyncAdapt_aiomysql_cursor:
|
||||
# exhausting rows, which we already have done for sync cursor.
|
||||
# another option would be to emulate aiosqlite dialect and assign
|
||||
# cursor only if we are doing server side cursor operation.
|
||||
self._rows[:] = []
|
||||
self._rows.clear()
|
||||
|
||||
def execute(self, operation, parameters=None):
|
||||
return self.await_(self._execute_async(operation, parameters))
|
||||
@@ -106,7 +110,7 @@ class AsyncAdapt_aiomysql_cursor:
|
||||
# of that here since our default result is not async.
|
||||
# we could just as easily grab "_rows" here and be done with it
|
||||
# but this is safer.
|
||||
self._rows = list(await self._cursor.fetchall())
|
||||
self._rows = deque(await self._cursor.fetchall())
|
||||
return result
|
||||
|
||||
async def _executemany_async(self, operation, seq_of_parameters):
|
||||
@@ -118,11 +122,11 @@ class AsyncAdapt_aiomysql_cursor:
|
||||
|
||||
def __iter__(self):
|
||||
while self._rows:
|
||||
yield self._rows.pop(0)
|
||||
yield self._rows.popleft()
|
||||
|
||||
def fetchone(self):
|
||||
if self._rows:
|
||||
return self._rows.pop(0)
|
||||
return self._rows.popleft()
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -130,13 +134,12 @@ class AsyncAdapt_aiomysql_cursor:
|
||||
if size is None:
|
||||
size = self.arraysize
|
||||
|
||||
retval = self._rows[0:size]
|
||||
self._rows[:] = self._rows[size:]
|
||||
return retval
|
||||
rr = self._rows
|
||||
return [rr.popleft() for _ in range(min(size, len(rr)))]
|
||||
|
||||
def fetchall(self):
|
||||
retval = self._rows[:]
|
||||
self._rows[:] = []
|
||||
retval = list(self._rows)
|
||||
self._rows.clear()
|
||||
return retval
|
||||
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/asyncmy.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors <see AUTHORS
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors <see AUTHORS
|
||||
# file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -21,10 +21,13 @@ This dialect should normally be used only with the
|
||||
:func:`_asyncio.create_async_engine` engine creation function::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
engine = create_async_engine("mysql+asyncmy://user:pass@hostname/dbname?charset=utf8mb4")
|
||||
|
||||
engine = create_async_engine(
|
||||
"mysql+asyncmy://user:pass@hostname/dbname?charset=utf8mb4"
|
||||
)
|
||||
|
||||
""" # noqa
|
||||
from collections import deque
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from .pymysql import MySQLDialect_pymysql
|
||||
@@ -56,7 +59,7 @@ class AsyncAdapt_asyncmy_cursor:
|
||||
cursor = self._connection.cursor()
|
||||
|
||||
self._cursor = self.await_(cursor.__aenter__())
|
||||
self._rows = []
|
||||
self._rows = deque()
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
@@ -86,7 +89,7 @@ class AsyncAdapt_asyncmy_cursor:
|
||||
# exhausting rows, which we already have done for sync cursor.
|
||||
# another option would be to emulate aiosqlite dialect and assign
|
||||
# cursor only if we are doing server side cursor operation.
|
||||
self._rows[:] = []
|
||||
self._rows.clear()
|
||||
|
||||
def execute(self, operation, parameters=None):
|
||||
return self.await_(self._execute_async(operation, parameters))
|
||||
@@ -108,7 +111,7 @@ class AsyncAdapt_asyncmy_cursor:
|
||||
# of that here since our default result is not async.
|
||||
# we could just as easily grab "_rows" here and be done with it
|
||||
# but this is safer.
|
||||
self._rows = list(await self._cursor.fetchall())
|
||||
self._rows = deque(await self._cursor.fetchall())
|
||||
return result
|
||||
|
||||
async def _executemany_async(self, operation, seq_of_parameters):
|
||||
@@ -120,11 +123,11 @@ class AsyncAdapt_asyncmy_cursor:
|
||||
|
||||
def __iter__(self):
|
||||
while self._rows:
|
||||
yield self._rows.pop(0)
|
||||
yield self._rows.popleft()
|
||||
|
||||
def fetchone(self):
|
||||
if self._rows:
|
||||
return self._rows.pop(0)
|
||||
return self._rows.popleft()
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -132,13 +135,12 @@ class AsyncAdapt_asyncmy_cursor:
|
||||
if size is None:
|
||||
size = self.arraysize
|
||||
|
||||
retval = self._rows[0:size]
|
||||
self._rows[:] = self._rows[size:]
|
||||
return retval
|
||||
rr = self._rows
|
||||
return [rr.popleft() for _ in range(min(size, len(rr)))]
|
||||
|
||||
def fetchall(self):
|
||||
retval = self._rows[:]
|
||||
self._rows[:] = []
|
||||
retval = list(self._rows)
|
||||
self._rows.clear()
|
||||
return retval
|
||||
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/base.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -11,7 +11,6 @@ r"""
|
||||
|
||||
.. dialect:: mysql
|
||||
:name: MySQL / MariaDB
|
||||
:full_support: 5.6, 5.7, 8.0 / 10.8, 10.9
|
||||
:normal_support: 5.6+ / 10+
|
||||
:best_effort: 5.0.2+ / 5.0.2+
|
||||
|
||||
@@ -35,7 +34,9 @@ syntactical and behavioral differences that SQLAlchemy accommodates automaticall
|
||||
To connect to a MariaDB database, no changes to the database URL are required::
|
||||
|
||||
|
||||
engine = create_engine("mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4")
|
||||
engine = create_engine(
|
||||
"mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4"
|
||||
)
|
||||
|
||||
Upon first connect, the SQLAlchemy dialect employs a
|
||||
server version detection scheme that determines if the
|
||||
@@ -53,7 +54,9 @@ useful for the case where an application makes use of MariaDB-specific features
|
||||
and is not compatible with a MySQL database. To use this mode of operation,
|
||||
replace the "mysql" token in the above URL with "mariadb"::
|
||||
|
||||
engine = create_engine("mariadb+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4")
|
||||
engine = create_engine(
|
||||
"mariadb+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4"
|
||||
)
|
||||
|
||||
The above engine, upon first connect, will raise an error if the server version
|
||||
detection detects that the backing database is not MariaDB.
|
||||
@@ -99,7 +102,7 @@ the :paramref:`_sa.create_engine.pool_recycle` option which ensures that
|
||||
a connection will be discarded and replaced with a new one if it has been
|
||||
present in the pool for a fixed number of seconds::
|
||||
|
||||
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
|
||||
engine = create_engine("mysql+mysqldb://...", pool_recycle=3600)
|
||||
|
||||
For more comprehensive disconnect detection of pooled connections, including
|
||||
accommodation of server restarts and network issues, a pre-ping approach may
|
||||
@@ -123,12 +126,14 @@ To accommodate the rendering of these arguments, specify the form
|
||||
``ENGINE`` of ``InnoDB``, ``CHARSET`` of ``utf8mb4``, and ``KEY_BLOCK_SIZE``
|
||||
of ``1024``::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('data', String(32)),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_key_block_size="1024"
|
||||
)
|
||||
Table(
|
||||
"mytable",
|
||||
metadata,
|
||||
Column("data", String(32)),
|
||||
mysql_engine="InnoDB",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_key_block_size="1024",
|
||||
)
|
||||
|
||||
When supporting :ref:`mysql_mariadb_only_mode` mode, similar keys against
|
||||
the "mariadb" prefix must be included as well. The values can of course
|
||||
@@ -137,19 +142,17 @@ be maintained::
|
||||
|
||||
# support both "mysql" and "mariadb-only" engine URLs
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('data', String(32)),
|
||||
|
||||
mysql_engine='InnoDB',
|
||||
mariadb_engine='InnoDB',
|
||||
|
||||
mysql_charset='utf8mb4',
|
||||
mariadb_charset='utf8',
|
||||
|
||||
mysql_key_block_size="1024"
|
||||
mariadb_key_block_size="1024"
|
||||
|
||||
)
|
||||
Table(
|
||||
"mytable",
|
||||
metadata,
|
||||
Column("data", String(32)),
|
||||
mysql_engine="InnoDB",
|
||||
mariadb_engine="InnoDB",
|
||||
mysql_charset="utf8mb4",
|
||||
mariadb_charset="utf8",
|
||||
mysql_key_block_size="1024",
|
||||
mariadb_key_block_size="1024",
|
||||
)
|
||||
|
||||
The MySQL / MariaDB dialects will normally transfer any keyword specified as
|
||||
``mysql_keyword_name`` to be rendered as ``KEYWORD_NAME`` in the
|
||||
@@ -215,16 +218,14 @@ techniques are used.
|
||||
To set isolation level using :func:`_sa.create_engine`::
|
||||
|
||||
engine = create_engine(
|
||||
"mysql+mysqldb://scott:tiger@localhost/test",
|
||||
isolation_level="READ UNCOMMITTED"
|
||||
)
|
||||
"mysql+mysqldb://scott:tiger@localhost/test",
|
||||
isolation_level="READ UNCOMMITTED",
|
||||
)
|
||||
|
||||
To set using per-connection execution options::
|
||||
|
||||
connection = engine.connect()
|
||||
connection = connection.execution_options(
|
||||
isolation_level="READ COMMITTED"
|
||||
)
|
||||
connection = connection.execution_options(isolation_level="READ COMMITTED")
|
||||
|
||||
Valid values for ``isolation_level`` include:
|
||||
|
||||
@@ -256,8 +257,8 @@ When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
|
||||
the first :class:`.Integer` primary key column which is not marked as a
|
||||
foreign key::
|
||||
|
||||
>>> t = Table('mytable', metadata,
|
||||
... Column('mytable_id', Integer, primary_key=True)
|
||||
>>> t = Table(
|
||||
... "mytable", metadata, Column("mytable_id", Integer, primary_key=True)
|
||||
... )
|
||||
>>> t.create()
|
||||
CREATE TABLE mytable (
|
||||
@@ -271,10 +272,12 @@ This flag
|
||||
can also be used to enable auto-increment on a secondary column in a
|
||||
multi-column key for some storage engines::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('gid', Integer, primary_key=True, autoincrement=False),
|
||||
Column('id', Integer, primary_key=True)
|
||||
)
|
||||
Table(
|
||||
"mytable",
|
||||
metadata,
|
||||
Column("gid", Integer, primary_key=True, autoincrement=False),
|
||||
Column("id", Integer, primary_key=True),
|
||||
)
|
||||
|
||||
.. _mysql_ss_cursors:
|
||||
|
||||
@@ -292,7 +295,9 @@ Server side cursors are enabled on a per-statement basis by using the
|
||||
option::
|
||||
|
||||
with engine.connect() as conn:
|
||||
result = conn.execution_options(stream_results=True).execute(text("select * from table"))
|
||||
result = conn.execution_options(stream_results=True).execute(
|
||||
text("select * from table")
|
||||
)
|
||||
|
||||
Note that some kinds of SQL statements may not be supported with
|
||||
server side cursors; generally, only SQL statements that return rows should be
|
||||
@@ -320,7 +325,8 @@ a connection. This is typically delivered using the ``charset`` parameter
|
||||
in the URL, such as::
|
||||
|
||||
e = create_engine(
|
||||
"mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4")
|
||||
"mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4"
|
||||
)
|
||||
|
||||
This charset is the **client character set** for the connection. Some
|
||||
MySQL DBAPIs will default this to a value such as ``latin1``, and some
|
||||
@@ -340,7 +346,8 @@ charset is preferred, if supported by both the database as well as the client
|
||||
DBAPI, as in::
|
||||
|
||||
e = create_engine(
|
||||
"mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4")
|
||||
"mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4"
|
||||
)
|
||||
|
||||
All modern DBAPIs should support the ``utf8mb4`` charset.
|
||||
|
||||
@@ -362,7 +369,9 @@ Dealing with Binary Data Warnings and Unicode
|
||||
MySQL versions 5.6, 5.7 and later (not MariaDB at the time of this writing) now
|
||||
emit a warning when attempting to pass binary data to the database, while a
|
||||
character set encoding is also in place, when the binary data itself is not
|
||||
valid for that encoding::
|
||||
valid for that encoding:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
default.py:509: Warning: (1300, "Invalid utf8mb4 character string:
|
||||
'F9876A'")
|
||||
@@ -372,7 +381,9 @@ This warning is due to the fact that the MySQL client library is attempting to
|
||||
interpret the binary string as a unicode object even if a datatype such
|
||||
as :class:`.LargeBinary` is in use. To resolve this, the SQL statement requires
|
||||
a binary "character set introducer" be present before any non-NULL value
|
||||
that renders like this::
|
||||
that renders like this:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
INSERT INTO table (data) VALUES (_binary %s)
|
||||
|
||||
@@ -382,12 +393,13 @@ string parameter ``binary_prefix=true`` to the URL to repair this warning::
|
||||
|
||||
# mysqlclient
|
||||
engine = create_engine(
|
||||
"mysql+mysqldb://scott:tiger@localhost/test?charset=utf8mb4&binary_prefix=true")
|
||||
"mysql+mysqldb://scott:tiger@localhost/test?charset=utf8mb4&binary_prefix=true"
|
||||
)
|
||||
|
||||
# PyMySQL
|
||||
engine = create_engine(
|
||||
"mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4&binary_prefix=true")
|
||||
|
||||
"mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4&binary_prefix=true"
|
||||
)
|
||||
|
||||
The ``binary_prefix`` flag may or may not be supported by other MySQL drivers.
|
||||
|
||||
@@ -430,7 +442,10 @@ the ``first_connect`` and ``connect`` events::
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
|
||||
eng = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo='debug')
|
||||
eng = create_engine(
|
||||
"mysql+mysqldb://scott:tiger@localhost/test", echo="debug"
|
||||
)
|
||||
|
||||
|
||||
# `insert=True` will ensure this is the very first listener to run
|
||||
@event.listens_for(eng, "connect", insert=True)
|
||||
@@ -438,6 +453,7 @@ the ``first_connect`` and ``connect`` events::
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("SET sql_mode = 'STRICT_ALL_TABLES'")
|
||||
|
||||
|
||||
conn = eng.connect()
|
||||
|
||||
In the example illustrated above, the "connect" event will invoke the "SET"
|
||||
@@ -454,8 +470,8 @@ MySQL / MariaDB SQL Extensions
|
||||
Many of the MySQL / MariaDB SQL extensions are handled through SQLAlchemy's generic
|
||||
function and operator support::
|
||||
|
||||
table.select(table.c.password==func.md5('plaintext'))
|
||||
table.select(table.c.username.op('regexp')('^[a-d]'))
|
||||
table.select(table.c.password == func.md5("plaintext"))
|
||||
table.select(table.c.username.op("regexp")("^[a-d]"))
|
||||
|
||||
And of course any valid SQL statement can be executed as a string as well.
|
||||
|
||||
@@ -468,11 +484,18 @@ available.
|
||||
* SELECT pragma, use :meth:`_expression.Select.prefix_with` and
|
||||
:meth:`_query.Query.prefix_with`::
|
||||
|
||||
select(...).prefix_with(['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
|
||||
select(...).prefix_with(["HIGH_PRIORITY", "SQL_SMALL_RESULT"])
|
||||
|
||||
* UPDATE with LIMIT::
|
||||
|
||||
update(..., mysql_limit=10, mariadb_limit=10)
|
||||
update(...).with_dialect_options(mysql_limit=10, mariadb_limit=10)
|
||||
|
||||
* DELETE
|
||||
with LIMIT::
|
||||
|
||||
delete(...).with_dialect_options(mysql_limit=10, mariadb_limit=10)
|
||||
|
||||
.. versionadded:: 2.0.37 Added delete with limit
|
||||
|
||||
* optimizer hints, use :meth:`_expression.Select.prefix_with` and
|
||||
:meth:`_query.Query.prefix_with`::
|
||||
@@ -484,14 +507,16 @@ available.
|
||||
|
||||
select(...).with_hint(some_table, "USE INDEX xyz")
|
||||
|
||||
* MATCH operator support::
|
||||
* MATCH
|
||||
operator support::
|
||||
|
||||
from sqlalchemy.dialects.mysql import match
|
||||
select(...).where(match(col1, col2, against="some expr").in_boolean_mode())
|
||||
from sqlalchemy.dialects.mysql import match
|
||||
|
||||
.. seealso::
|
||||
select(...).where(match(col1, col2, against="some expr").in_boolean_mode())
|
||||
|
||||
:class:`_mysql.match`
|
||||
.. seealso::
|
||||
|
||||
:class:`_mysql.match`
|
||||
|
||||
INSERT/DELETE...RETURNING
|
||||
-------------------------
|
||||
@@ -508,17 +533,15 @@ To specify an explicit ``RETURNING`` clause, use the
|
||||
|
||||
# INSERT..RETURNING
|
||||
result = connection.execute(
|
||||
table.insert().
|
||||
values(name='foo').
|
||||
returning(table.c.col1, table.c.col2)
|
||||
table.insert().values(name="foo").returning(table.c.col1, table.c.col2)
|
||||
)
|
||||
print(result.all())
|
||||
|
||||
# DELETE..RETURNING
|
||||
result = connection.execute(
|
||||
table.delete().
|
||||
where(table.c.name=='foo').
|
||||
returning(table.c.col1, table.c.col2)
|
||||
table.delete()
|
||||
.where(table.c.name == "foo")
|
||||
.returning(table.c.col1, table.c.col2)
|
||||
)
|
||||
print(result.all())
|
||||
|
||||
@@ -545,12 +568,11 @@ the generative method :meth:`~.mysql.Insert.on_duplicate_key_update`:
|
||||
>>> from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
>>> insert_stmt = insert(my_table).values(
|
||||
... id='some_existing_id',
|
||||
... data='inserted value')
|
||||
... id="some_existing_id", data="inserted value"
|
||||
... )
|
||||
|
||||
>>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
|
||||
... data=insert_stmt.inserted.data,
|
||||
... status='U'
|
||||
... data=insert_stmt.inserted.data, status="U"
|
||||
... )
|
||||
>>> print(on_duplicate_key_stmt)
|
||||
{printsql}INSERT INTO my_table (id, data) VALUES (%s, %s)
|
||||
@@ -575,8 +597,8 @@ as values:
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> insert_stmt = insert(my_table).values(
|
||||
... id='some_existing_id',
|
||||
... data='inserted value')
|
||||
... id="some_existing_id", data="inserted value"
|
||||
... )
|
||||
|
||||
>>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
|
||||
... data="some data",
|
||||
@@ -639,13 +661,11 @@ table:
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> stmt = insert(my_table).values(
|
||||
... id='some_id',
|
||||
... data='inserted value',
|
||||
... author='jlh')
|
||||
... id="some_id", data="inserted value", author="jlh"
|
||||
... )
|
||||
|
||||
>>> do_update_stmt = stmt.on_duplicate_key_update(
|
||||
... data="updated value",
|
||||
... author=stmt.inserted.author
|
||||
... data="updated value", author=stmt.inserted.author
|
||||
... )
|
||||
|
||||
>>> print(do_update_stmt)
|
||||
@@ -690,13 +710,13 @@ MySQL and MariaDB both provide an option to create index entries with a certain
|
||||
become part of the index. SQLAlchemy provides this feature via the
|
||||
``mysql_length`` and/or ``mariadb_length`` parameters::
|
||||
|
||||
Index('my_index', my_table.c.data, mysql_length=10, mariadb_length=10)
|
||||
Index("my_index", my_table.c.data, mysql_length=10, mariadb_length=10)
|
||||
|
||||
Index('a_b_idx', my_table.c.a, my_table.c.b, mysql_length={'a': 4,
|
||||
'b': 9})
|
||||
Index("a_b_idx", my_table.c.a, my_table.c.b, mysql_length={"a": 4, "b": 9})
|
||||
|
||||
Index('a_b_idx', my_table.c.a, my_table.c.b, mariadb_length={'a': 4,
|
||||
'b': 9})
|
||||
Index(
|
||||
"a_b_idx", my_table.c.a, my_table.c.b, mariadb_length={"a": 4, "b": 9}
|
||||
)
|
||||
|
||||
Prefix lengths are given in characters for nonbinary string types and in bytes
|
||||
for binary string types. The value passed to the keyword argument *must* be
|
||||
@@ -713,7 +733,7 @@ MySQL storage engines permit you to specify an index prefix when creating
|
||||
an index. SQLAlchemy provides this feature via the
|
||||
``mysql_prefix`` parameter on :class:`.Index`::
|
||||
|
||||
Index('my_index', my_table.c.data, mysql_prefix='FULLTEXT')
|
||||
Index("my_index", my_table.c.data, mysql_prefix="FULLTEXT")
|
||||
|
||||
The value passed to the keyword argument will be simply passed through to the
|
||||
underlying CREATE INDEX, so it *must* be a valid index prefix for your MySQL
|
||||
@@ -730,11 +750,13 @@ Some MySQL storage engines permit you to specify an index type when creating
|
||||
an index or primary key constraint. SQLAlchemy provides this feature via the
|
||||
``mysql_using`` parameter on :class:`.Index`::
|
||||
|
||||
Index('my_index', my_table.c.data, mysql_using='hash', mariadb_using='hash')
|
||||
Index(
|
||||
"my_index", my_table.c.data, mysql_using="hash", mariadb_using="hash"
|
||||
)
|
||||
|
||||
As well as the ``mysql_using`` parameter on :class:`.PrimaryKeyConstraint`::
|
||||
|
||||
PrimaryKeyConstraint("data", mysql_using='hash', mariadb_using='hash')
|
||||
PrimaryKeyConstraint("data", mysql_using="hash", mariadb_using="hash")
|
||||
|
||||
The value passed to the keyword argument will be simply passed through to the
|
||||
underlying CREATE INDEX or PRIMARY KEY clause, so it *must* be a valid index
|
||||
@@ -753,9 +775,12 @@ CREATE FULLTEXT INDEX in MySQL also supports a "WITH PARSER" option. This
|
||||
is available using the keyword argument ``mysql_with_parser``::
|
||||
|
||||
Index(
|
||||
'my_index', my_table.c.data,
|
||||
mysql_prefix='FULLTEXT', mysql_with_parser="ngram",
|
||||
mariadb_prefix='FULLTEXT', mariadb_with_parser="ngram",
|
||||
"my_index",
|
||||
my_table.c.data,
|
||||
mysql_prefix="FULLTEXT",
|
||||
mysql_with_parser="ngram",
|
||||
mariadb_prefix="FULLTEXT",
|
||||
mariadb_with_parser="ngram",
|
||||
)
|
||||
|
||||
.. versionadded:: 1.3
|
||||
@@ -782,6 +807,7 @@ them ignored on a MySQL / MariaDB backend, use a custom compile rule::
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.schema import ForeignKeyConstraint
|
||||
|
||||
|
||||
@compiles(ForeignKeyConstraint, "mysql", "mariadb")
|
||||
def process(element, compiler, **kw):
|
||||
element.deferrable = element.initially = None
|
||||
@@ -803,10 +829,12 @@ very common ``MyISAM`` MySQL storage engine, the information loaded by table
|
||||
reflection will not include foreign keys. For these tables, you may supply a
|
||||
:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
|
||||
|
||||
Table('mytable', metadata,
|
||||
ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
|
||||
autoload_with=engine
|
||||
)
|
||||
Table(
|
||||
"mytable",
|
||||
metadata,
|
||||
ForeignKeyConstraint(["other_id"], ["othertable.other_id"]),
|
||||
autoload_with=engine,
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -878,13 +906,15 @@ parameter and pass a textual clause that also includes the ON UPDATE clause::
|
||||
mytable = Table(
|
||||
"mytable",
|
||||
metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', String(50)),
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("data", String(50)),
|
||||
Column(
|
||||
'last_updated',
|
||||
"last_updated",
|
||||
TIMESTAMP,
|
||||
server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
|
||||
)
|
||||
server_default=text(
|
||||
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
The same instructions apply to use of the :class:`_types.DateTime` and
|
||||
@@ -895,34 +925,37 @@ The same instructions apply to use of the :class:`_types.DateTime` and
|
||||
mytable = Table(
|
||||
"mytable",
|
||||
metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', String(50)),
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("data", String(50)),
|
||||
Column(
|
||||
'last_updated',
|
||||
"last_updated",
|
||||
DateTime,
|
||||
server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
|
||||
)
|
||||
server_default=text(
|
||||
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Even though the :paramref:`_schema.Column.server_onupdate` feature does not
|
||||
generate this DDL, it still may be desirable to signal to the ORM that this
|
||||
updated value should be fetched. This syntax looks like the following::
|
||||
|
||||
from sqlalchemy.schema import FetchedValue
|
||||
|
||||
|
||||
class MyClass(Base):
|
||||
__tablename__ = 'mytable'
|
||||
__tablename__ = "mytable"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
data = Column(String(50))
|
||||
last_updated = Column(
|
||||
TIMESTAMP,
|
||||
server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
server_onupdate=FetchedValue()
|
||||
server_default=text(
|
||||
"CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
|
||||
),
|
||||
server_onupdate=FetchedValue(),
|
||||
)
|
||||
|
||||
|
||||
.. _mysql_timestamp_null:
|
||||
|
||||
TIMESTAMP Columns and NULL
|
||||
@@ -932,7 +965,9 @@ MySQL historically enforces that a column which specifies the
|
||||
TIMESTAMP datatype implicitly includes a default value of
|
||||
CURRENT_TIMESTAMP, even though this is not stated, and additionally
|
||||
sets the column as NOT NULL, the opposite behavior vs. that of all
|
||||
other datatypes::
|
||||
other datatypes:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
mysql> CREATE TABLE ts_test (
|
||||
-> a INTEGER,
|
||||
@@ -977,19 +1012,24 @@ SQLAlchemy also emits NOT NULL for TIMESTAMP columns that do specify
|
||||
from sqlalchemy.dialects.mysql import TIMESTAMP
|
||||
|
||||
m = MetaData()
|
||||
t = Table('ts_test', m,
|
||||
Column('a', Integer),
|
||||
Column('b', Integer, nullable=False),
|
||||
Column('c', TIMESTAMP),
|
||||
Column('d', TIMESTAMP, nullable=False)
|
||||
)
|
||||
t = Table(
|
||||
"ts_test",
|
||||
m,
|
||||
Column("a", Integer),
|
||||
Column("b", Integer, nullable=False),
|
||||
Column("c", TIMESTAMP),
|
||||
Column("d", TIMESTAMP, nullable=False),
|
||||
)
|
||||
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
e = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo=True)
|
||||
m.create_all(e)
|
||||
|
||||
output::
|
||||
output:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
CREATE TABLE ts_test (
|
||||
a INTEGER,
|
||||
@@ -1349,7 +1389,7 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
clauses = []
|
||||
|
||||
requires_mysql8_alias = (
|
||||
requires_mysql8_alias = statement.select is None and (
|
||||
self.dialect._requires_alias_for_on_duplicate_key
|
||||
)
|
||||
|
||||
@@ -1359,10 +1399,17 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
else:
|
||||
_on_dup_alias_name = "new"
|
||||
|
||||
# traverses through all table columns to preserve table column order
|
||||
for column in (col for col in cols if col.key in on_duplicate.update):
|
||||
val = on_duplicate.update[column.key]
|
||||
on_duplicate_update = {
|
||||
coercions.expect_as_key(roles.DMLColumnRole, key): value
|
||||
for key, value in on_duplicate.update.items()
|
||||
}
|
||||
|
||||
# traverses through all table columns to preserve table column order
|
||||
for column in (col for col in cols if col.key in on_duplicate_update):
|
||||
val = on_duplicate_update[column.key]
|
||||
|
||||
# TODO: this coercion should be up front. we can't cache
|
||||
# SQL constructs with non-bound literals buried in them
|
||||
if coercions._is_literal(val):
|
||||
val = elements.BindParameter(None, val, type_=column.type)
|
||||
value_text = self.process(val.self_group(), use_schema=False)
|
||||
@@ -1400,7 +1447,7 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
name_text = self.preparer.quote(column.name)
|
||||
clauses.append("%s = %s" % (name_text, value_text))
|
||||
|
||||
non_matching = set(on_duplicate.update) - {c.key for c in cols}
|
||||
non_matching = set(on_duplicate_update) - {c.key for c in cols}
|
||||
if non_matching:
|
||||
util.warn(
|
||||
"Additional column names not matching "
|
||||
@@ -1678,8 +1725,15 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def update_limit_clause(self, update_stmt):
|
||||
limit = update_stmt.kwargs.get("%s_limit" % self.dialect.name, None)
|
||||
if limit:
|
||||
return "LIMIT %s" % limit
|
||||
if limit is not None:
|
||||
return f"LIMIT {int(limit)}"
|
||||
else:
|
||||
return None
|
||||
|
||||
def delete_limit_clause(self, delete_stmt):
|
||||
limit = delete_stmt.kwargs.get("%s_limit" % self.dialect.name, None)
|
||||
if limit is not None:
|
||||
return f"LIMIT {int(limit)}"
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1850,7 +1904,15 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
else:
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec.append("DEFAULT " + default)
|
||||
if (
|
||||
isinstance(
|
||||
column.server_default.arg, functions.FunctionElement
|
||||
)
|
||||
and self.dialect._support_default_function
|
||||
):
|
||||
colspec.append(f"DEFAULT ({default})")
|
||||
else:
|
||||
colspec.append("DEFAULT " + default)
|
||||
return " ".join(colspec)
|
||||
|
||||
def post_create_table(self, table):
|
||||
@@ -2380,6 +2442,8 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def _visit_enumerated_values(self, name, type_, enumerated_values):
|
||||
quoted_enums = []
|
||||
for e in enumerated_values:
|
||||
if self.dialect.identifier_preparer._double_percents:
|
||||
e = e.replace("%", "%%")
|
||||
quoted_enums.append("'%s'" % e.replace("'", "''"))
|
||||
return self._extend_string(
|
||||
type_, {}, "%s(%s)" % (name, ",".join(quoted_enums))
|
||||
@@ -2493,6 +2557,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
construct_arguments = [
|
||||
(sa_schema.Table, {"*": None}),
|
||||
(sql.Update, {"limit": None}),
|
||||
(sql.Delete, {"limit": None}),
|
||||
(sa_schema.PrimaryKeyConstraint, {"using": None}),
|
||||
(
|
||||
sa_schema.Index,
|
||||
@@ -2893,6 +2958,17 @@ class MySQLDialect(default.DefaultDialect):
|
||||
# ref https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-17.html#mysqld-8-0-17-feature # noqa
|
||||
return self.server_version_info >= (8, 0, 17)
|
||||
|
||||
@property
|
||||
def _support_default_function(self):
|
||||
if not self.server_version_info:
|
||||
return False
|
||||
elif self.is_mariadb:
|
||||
# ref https://mariadb.com/kb/en/mariadb-1021-release-notes/
|
||||
return self.server_version_info >= (10, 2, 1)
|
||||
else:
|
||||
# ref https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html # noqa
|
||||
return self.server_version_info >= (8, 0, 13)
|
||||
|
||||
@property
|
||||
def _is_mariadb(self):
|
||||
return self.is_mariadb
|
||||
@@ -3050,29 +3126,47 @@ class MySQLDialect(default.DefaultDialect):
|
||||
return s
|
||||
|
||||
default_schema_name = connection.dialect.default_schema_name
|
||||
col_tuples = [
|
||||
(
|
||||
lower(rec["referred_schema"] or default_schema_name),
|
||||
lower(rec["referred_table"]),
|
||||
col_name,
|
||||
)
|
||||
for rec in fkeys
|
||||
for col_name in rec["referred_columns"]
|
||||
]
|
||||
|
||||
if col_tuples:
|
||||
correct_for_wrong_fk_case = connection.execute(
|
||||
sql.text(
|
||||
"""
|
||||
select table_schema, table_name, column_name
|
||||
from information_schema.columns
|
||||
where (table_schema, table_name, lower(column_name)) in
|
||||
:table_data;
|
||||
"""
|
||||
).bindparams(sql.bindparam("table_data", expanding=True)),
|
||||
dict(table_data=col_tuples),
|
||||
# NOTE: using (table_schema, table_name, lower(column_name)) in (...)
|
||||
# is very slow since mysql does not seem able to properly use indexse.
|
||||
# Unpack the where condition instead.
|
||||
schema_by_table_by_column = defaultdict(lambda: defaultdict(list))
|
||||
for rec in fkeys:
|
||||
sch = lower(rec["referred_schema"] or default_schema_name)
|
||||
tbl = lower(rec["referred_table"])
|
||||
for col_name in rec["referred_columns"]:
|
||||
schema_by_table_by_column[sch][tbl].append(col_name)
|
||||
|
||||
if schema_by_table_by_column:
|
||||
|
||||
condition = sql.or_(
|
||||
*(
|
||||
sql.and_(
|
||||
_info_columns.c.table_schema == schema,
|
||||
sql.or_(
|
||||
*(
|
||||
sql.and_(
|
||||
_info_columns.c.table_name == table,
|
||||
sql.func.lower(
|
||||
_info_columns.c.column_name
|
||||
).in_(columns),
|
||||
)
|
||||
for table, columns in tables.items()
|
||||
)
|
||||
),
|
||||
)
|
||||
for schema, tables in schema_by_table_by_column.items()
|
||||
)
|
||||
)
|
||||
|
||||
select = sql.select(
|
||||
_info_columns.c.table_schema,
|
||||
_info_columns.c.table_name,
|
||||
_info_columns.c.column_name,
|
||||
).where(condition)
|
||||
|
||||
correct_for_wrong_fk_case = connection.execute(select)
|
||||
|
||||
# in casing=0, table name and schema name come back in their
|
||||
# exact case.
|
||||
# in casing=1, table name and schema name come back in lower
|
||||
@@ -3445,3 +3539,12 @@ class _DecodingRow:
|
||||
return item.decode(self.charset)
|
||||
else:
|
||||
return item
|
||||
|
||||
|
||||
_info_columns = sql.table(
|
||||
"columns",
|
||||
sql.column("table_schema", VARCHAR(64)),
|
||||
sql.column("table_name", VARCHAR(64)),
|
||||
sql.column("column_name", VARCHAR(64)),
|
||||
schema="information_schema",
|
||||
)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/cymysql.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/dml.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,6 +7,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
@@ -141,7 +142,11 @@ class Insert(StandardInsert):
|
||||
in :ref:`tutorial_parameter_ordered_updates`::
|
||||
|
||||
insert().on_duplicate_key_update(
|
||||
[("name", "some name"), ("value", "some value")])
|
||||
[
|
||||
("name", "some name"),
|
||||
("value", "some value"),
|
||||
]
|
||||
)
|
||||
|
||||
.. versionchanged:: 1.3 parameters can be specified as a dictionary
|
||||
or list of 2-tuples; the latter form provides for parameter
|
||||
@@ -181,6 +186,7 @@ class OnDuplicateClause(ClauseElement):
|
||||
|
||||
_parameter_ordering: Optional[List[str]] = None
|
||||
|
||||
update: Dict[str, Any]
|
||||
stringify_dialect = "mysql"
|
||||
|
||||
def __init__(
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/enumerated.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -28,7 +28,7 @@ class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum, _StringType):
|
||||
|
||||
E.g.::
|
||||
|
||||
Column('myenum', ENUM("foo", "bar", "baz"))
|
||||
Column("myenum", ENUM("foo", "bar", "baz"))
|
||||
|
||||
:param enums: The range of valid values for this ENUM. Values in
|
||||
enums are not quoted, they will be escaped and surrounded by single
|
||||
@@ -102,8 +102,7 @@ class SET(_StringType):
|
||||
|
||||
E.g.::
|
||||
|
||||
Column('myset', SET("foo", "bar", "baz"))
|
||||
|
||||
Column("myset", SET("foo", "bar", "baz"))
|
||||
|
||||
The list of potential values is required in the case that this
|
||||
set will be used to generate DDL for a table, or if the
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/expression.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -38,7 +38,9 @@ class match(Generative, elements.BinaryExpression):
|
||||
.order_by(desc(match_expr))
|
||||
)
|
||||
|
||||
Would produce SQL resembling::
|
||||
Would produce SQL resembling:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
SELECT id, firstname, lastname
|
||||
FROM user
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/json.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/mariadb.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,6 +7,34 @@
|
||||
# mypy: ignore-errors
|
||||
from .base import MariaDBIdentifierPreparer
|
||||
from .base import MySQLDialect
|
||||
from .base import MySQLTypeCompiler
|
||||
from ...sql import sqltypes
|
||||
|
||||
|
||||
class INET4(sqltypes.TypeEngine[str]):
|
||||
"""INET4 column type for MariaDB
|
||||
|
||||
.. versionadded:: 2.0.37
|
||||
"""
|
||||
|
||||
__visit_name__ = "INET4"
|
||||
|
||||
|
||||
class INET6(sqltypes.TypeEngine[str]):
|
||||
"""INET6 column type for MariaDB
|
||||
|
||||
.. versionadded:: 2.0.37
|
||||
"""
|
||||
|
||||
__visit_name__ = "INET6"
|
||||
|
||||
|
||||
class MariaDBTypeCompiler(MySQLTypeCompiler):
|
||||
def visit_INET4(self, type_, **kwargs) -> str:
|
||||
return "INET4"
|
||||
|
||||
def visit_INET6(self, type_, **kwargs) -> str:
|
||||
return "INET6"
|
||||
|
||||
|
||||
class MariaDBDialect(MySQLDialect):
|
||||
@@ -14,6 +42,7 @@ class MariaDBDialect(MySQLDialect):
|
||||
supports_statement_cache = True
|
||||
name = "mariadb"
|
||||
preparer = MariaDBIdentifierPreparer
|
||||
type_compiler_cls = MariaDBTypeCompiler
|
||||
|
||||
|
||||
def loader(driver):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/mariadbconnector.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -87,13 +87,6 @@ class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
|
||||
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
|
||||
|
||||
@@ -172,6 +165,7 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
opts.update(url.query)
|
||||
|
||||
int_params = [
|
||||
"connect_timeout",
|
||||
@@ -186,6 +180,7 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
|
||||
"ssl_verify_cert",
|
||||
"ssl",
|
||||
"pool_reset_connection",
|
||||
"compress",
|
||||
]
|
||||
|
||||
for key in int_params:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -96,6 +96,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
util.coerce_kw_type(opts, "allow_local_infile", bool)
|
||||
util.coerce_kw_type(opts, "autocommit", bool)
|
||||
util.coerce_kw_type(opts, "buffered", bool)
|
||||
util.coerce_kw_type(opts, "client_flag", int)
|
||||
util.coerce_kw_type(opts, "compress", bool)
|
||||
util.coerce_kw_type(opts, "connection_timeout", int)
|
||||
util.coerce_kw_type(opts, "connect_timeout", int)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -48,9 +48,9 @@ key "ssl", which may be specified using the
|
||||
"ssl": {
|
||||
"ca": "/home/gord/client-ssl/ca.pem",
|
||||
"cert": "/home/gord/client-ssl/client-cert.pem",
|
||||
"key": "/home/gord/client-ssl/client-key.pem"
|
||||
"key": "/home/gord/client-ssl/client-key.pem",
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
For convenience, the following keys may also be specified inline within the URL
|
||||
@@ -74,7 +74,9 @@ Using MySQLdb with Google Cloud SQL
|
||||
-----------------------------------
|
||||
|
||||
Google Cloud SQL now recommends use of the MySQLdb dialect. Connect
|
||||
using a URL like the following::
|
||||
using a URL like the following:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
|
||||
|
||||
@@ -97,12 +99,7 @@ from ... import util
|
||||
|
||||
|
||||
class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
|
||||
@property
|
||||
def rowcount(self):
|
||||
if hasattr(self, "_rowcount"):
|
||||
return self._rowcount
|
||||
else:
|
||||
return self.cursor.rowcount
|
||||
pass
|
||||
|
||||
|
||||
class MySQLCompiler_mysqldb(MySQLCompiler):
|
||||
@@ -217,7 +214,7 @@ class MySQLDialect_mysqldb(MySQLDialect):
|
||||
util.coerce_kw_type(opts, "read_timeout", int)
|
||||
util.coerce_kw_type(opts, "write_timeout", int)
|
||||
util.coerce_kw_type(opts, "client_flag", int)
|
||||
util.coerce_kw_type(opts, "local_infile", int)
|
||||
util.coerce_kw_type(opts, "local_infile", bool)
|
||||
# Note: using either of the below will cause all strings to be
|
||||
# returned as Unicode, both in raw SQL operations and with column
|
||||
# types like String and MSString.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/provision.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -40,6 +40,9 @@ def generate_driver_url(url, driver, query_str):
|
||||
drivername="%s+%s" % (backend, driver)
|
||||
).update_query_string(query_str)
|
||||
|
||||
if driver == "mariadbconnector":
|
||||
new_url = new_url.difference_update_query(["charset"])
|
||||
|
||||
try:
|
||||
new_url.get_dialect()
|
||||
except exc.NoSuchModuleError:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/pymysql.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -41,7 +41,6 @@ necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::
|
||||
"&ssl_check_hostname=false"
|
||||
)
|
||||
|
||||
|
||||
MySQL-Python Compatibility
|
||||
--------------------------
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -30,14 +30,15 @@ r"""
|
||||
Pass through exact pyodbc connection string::
|
||||
|
||||
import urllib
|
||||
|
||||
connection_string = (
|
||||
'DRIVER=MySQL ODBC 8.0 ANSI Driver;'
|
||||
'SERVER=localhost;'
|
||||
'PORT=3307;'
|
||||
'DATABASE=mydb;'
|
||||
'UID=root;'
|
||||
'PWD=(whatever);'
|
||||
'charset=utf8mb4;'
|
||||
"DRIVER=MySQL ODBC 8.0 ANSI Driver;"
|
||||
"SERVER=localhost;"
|
||||
"PORT=3307;"
|
||||
"DATABASE=mydb;"
|
||||
"UID=root;"
|
||||
"PWD=(whatever);"
|
||||
"charset=utf8mb4;"
|
||||
)
|
||||
params = urllib.parse.quote_plus(connection_string)
|
||||
connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/reflection.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -505,7 +505,7 @@ class MySQLTableDefinitionParser:
|
||||
#
|
||||
# unique constraints come back as KEYs
|
||||
kw = quotes.copy()
|
||||
kw["on"] = "RESTRICT|CASCADE|SET NULL|NO ACTION"
|
||||
kw["on"] = "RESTRICT|CASCADE|SET NULL|NO ACTION|SET DEFAULT"
|
||||
self._re_fk_constraint = _re_compile(
|
||||
r" "
|
||||
r"CONSTRAINT +"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/reserved_words.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -282,6 +282,7 @@ RESERVED_WORDS_MARIADB = {
|
||||
}
|
||||
)
|
||||
|
||||
# https://dev.mysql.com/doc/refman/8.3/en/keywords.html
|
||||
# https://dev.mysql.com/doc/refman/8.0/en/keywords.html
|
||||
# https://dev.mysql.com/doc/refman/5.7/en/keywords.html
|
||||
# https://dev.mysql.com/doc/refman/5.6/en/keywords.html
|
||||
@@ -403,6 +404,7 @@ RESERVED_WORDS_MYSQL = {
|
||||
"int4",
|
||||
"int8",
|
||||
"integer",
|
||||
"intersect",
|
||||
"interval",
|
||||
"into",
|
||||
"io_after_gtids",
|
||||
@@ -468,6 +470,7 @@ RESERVED_WORDS_MYSQL = {
|
||||
"outfile",
|
||||
"over",
|
||||
"parse_gcol_expr",
|
||||
"parallel",
|
||||
"partition",
|
||||
"percent_rank",
|
||||
"persist",
|
||||
@@ -476,6 +479,7 @@ RESERVED_WORDS_MYSQL = {
|
||||
"primary",
|
||||
"procedure",
|
||||
"purge",
|
||||
"qualify",
|
||||
"range",
|
||||
"rank",
|
||||
"read",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# dialects/mysql/types.py
|
||||
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -499,7 +499,7 @@ class YEAR(sqltypes.TypeEngine):
|
||||
|
||||
|
||||
class TEXT(_StringType, sqltypes.TEXT):
|
||||
"""MySQL TEXT type, for text up to 2^16 characters."""
|
||||
"""MySQL TEXT type, for character storage encoded up to 2^16 bytes."""
|
||||
|
||||
__visit_name__ = "TEXT"
|
||||
|
||||
@@ -508,7 +508,7 @@ class TEXT(_StringType, sqltypes.TEXT):
|
||||
|
||||
:param length: Optional, if provided the server may optimize storage
|
||||
by substituting the smallest TEXT type sufficient to store
|
||||
``length`` characters.
|
||||
``length`` bytes of characters.
|
||||
|
||||
:param charset: Optional, a column-level character set for this string
|
||||
value. Takes precedence to 'ascii' or 'unicode' short-hand.
|
||||
@@ -535,7 +535,7 @@ class TEXT(_StringType, sqltypes.TEXT):
|
||||
|
||||
|
||||
class TINYTEXT(_StringType):
|
||||
"""MySQL TINYTEXT type, for text up to 2^8 characters."""
|
||||
"""MySQL TINYTEXT type, for character storage encoded up to 2^8 bytes."""
|
||||
|
||||
__visit_name__ = "TINYTEXT"
|
||||
|
||||
@@ -567,7 +567,8 @@ class TINYTEXT(_StringType):
|
||||
|
||||
|
||||
class MEDIUMTEXT(_StringType):
|
||||
"""MySQL MEDIUMTEXT type, for text up to 2^24 characters."""
|
||||
"""MySQL MEDIUMTEXT type, for character storage encoded up
|
||||
to 2^24 bytes."""
|
||||
|
||||
__visit_name__ = "MEDIUMTEXT"
|
||||
|
||||
@@ -599,7 +600,7 @@ class MEDIUMTEXT(_StringType):
|
||||
|
||||
|
||||
class LONGTEXT(_StringType):
|
||||
"""MySQL LONGTEXT type, for text up to 2^32 characters."""
|
||||
"""MySQL LONGTEXT type, for character storage encoded up to 2^32 bytes."""
|
||||
|
||||
__visit_name__ = "LONGTEXT"
|
||||
|
||||
@@ -683,7 +684,7 @@ class CHAR(_StringType, sqltypes.CHAR):
|
||||
super().__init__(length=length, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _adapt_string_for_cast(self, type_):
|
||||
def _adapt_string_for_cast(cls, type_):
|
||||
# copy the given string type into a CHAR
|
||||
# for the purposes of rendering a CAST expression
|
||||
type_ = sqltypes.to_instance(type_)
|
||||
|
Reference in New Issue
Block a user