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