1
0
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:
MoonTestUse1
2025-02-09 01:11:49 +06:00
parent ce52f8a23a
commit 0aa3ef8fc2
5827 changed files with 14316 additions and 1906434 deletions

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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",
)

View File

@@ -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

View File

@@ -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__(

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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:

View File

@@ -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)

View File

@@ -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.

View File

@@ -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:

View File

@@ -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
--------------------------

View File

@@ -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

View File

@@ -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 +"

View File

@@ -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",

View File

@@ -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_)