summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql
diff options
context:
space:
mode:
authorcyfraeviolae <cyfraeviolae>2024-04-03 03:17:55 -0400
committercyfraeviolae <cyfraeviolae>2024-04-03 03:17:55 -0400
commit12cf076118570eebbff08c6b3090e0d4798447a1 (patch)
tree3ba25e17e3c3a5e82316558ba3864b955919ff72 /venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql
parentc45662ff3923b34614ddcc8feb9195541166dcc5 (diff)
no venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql')
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py101
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pycbin2654 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pycbin18123 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pycbin18787 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pycbin145328 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pycbin3348 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pycbin9104 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pycbin11258 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pycbin5392 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pycbin3982 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pycbin1171 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pycbin12859 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pycbin9700 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pycbin12714 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pycbin4848 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pycbin5649 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pycbin5859 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pycbin27141 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pycbin4446 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pycbin33731 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py332
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py337
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py3447
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py84
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py219
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py244
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py141
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py81
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py32
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py275
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py179
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py303
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py107
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py137
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py138
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py677
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py571
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py774
38 files changed, 0 insertions, 8179 deletions
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py
deleted file mode 100644
index 60bac87..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# dialects/mysql/__init__.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-from . import aiomysql # noqa
-from . import asyncmy # noqa
-from . import base # noqa
-from . import cymysql # noqa
-from . import mariadbconnector # noqa
-from . import mysqlconnector # noqa
-from . import mysqldb # noqa
-from . import pymysql # noqa
-from . import pyodbc # noqa
-from .base import BIGINT
-from .base import BINARY
-from .base import BIT
-from .base import BLOB
-from .base import BOOLEAN
-from .base import CHAR
-from .base import DATE
-from .base import DATETIME
-from .base import DECIMAL
-from .base import DOUBLE
-from .base import ENUM
-from .base import FLOAT
-from .base import INTEGER
-from .base import JSON
-from .base import LONGBLOB
-from .base import LONGTEXT
-from .base import MEDIUMBLOB
-from .base import MEDIUMINT
-from .base import MEDIUMTEXT
-from .base import NCHAR
-from .base import NUMERIC
-from .base import NVARCHAR
-from .base import REAL
-from .base import SET
-from .base import SMALLINT
-from .base import TEXT
-from .base import TIME
-from .base import TIMESTAMP
-from .base import TINYBLOB
-from .base import TINYINT
-from .base import TINYTEXT
-from .base import VARBINARY
-from .base import VARCHAR
-from .base import YEAR
-from .dml import Insert
-from .dml import insert
-from .expression import match
-from ...util import compat
-
-# default dialect
-base.dialect = dialect = mysqldb.dialect
-
-__all__ = (
- "BIGINT",
- "BINARY",
- "BIT",
- "BLOB",
- "BOOLEAN",
- "CHAR",
- "DATE",
- "DATETIME",
- "DECIMAL",
- "DOUBLE",
- "ENUM",
- "FLOAT",
- "INTEGER",
- "INTEGER",
- "JSON",
- "LONGBLOB",
- "LONGTEXT",
- "MEDIUMBLOB",
- "MEDIUMINT",
- "MEDIUMTEXT",
- "NCHAR",
- "NVARCHAR",
- "NUMERIC",
- "SET",
- "SMALLINT",
- "REAL",
- "TEXT",
- "TIME",
- "TIMESTAMP",
- "TINYBLOB",
- "TINYINT",
- "TINYTEXT",
- "VARBINARY",
- "VARCHAR",
- "YEAR",
- "dialect",
- "insert",
- "Insert",
- "match",
-)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc
deleted file mode 100644
index 2a39bd6..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc
deleted file mode 100644
index cd8e408..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc
deleted file mode 100644
index 1c1eb90..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc
deleted file mode 100644
index e26259b..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc
deleted file mode 100644
index e08de25..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc
deleted file mode 100644
index 87e30ca..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc
deleted file mode 100644
index 2df5629..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc
deleted file mode 100644
index c3fd7e9..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc
deleted file mode 100644
index 417677b..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc
deleted file mode 100644
index 79ef157..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc
deleted file mode 100644
index a01ff5f..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc
deleted file mode 100644
index d7a6a76..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc
deleted file mode 100644
index d572a66..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc
deleted file mode 100644
index 3865a06..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc
deleted file mode 100644
index bc40e52..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc
deleted file mode 100644
index 07a2fcc..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc
deleted file mode 100644
index 0b1bc4c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc
deleted file mode 100644
index 1ef118c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc
deleted file mode 100644
index 6d20ed0..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py
deleted file mode 100644
index 405fa82..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# dialects/mysql/aiomysql.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors <see AUTHORS
-# file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-r"""
-.. dialect:: mysql+aiomysql
- :name: aiomysql
- :dbapi: aiomysql
- :connectstring: mysql+aiomysql://user:password@host:port/dbname[?key=value&key=value...]
- :url: https://github.com/aio-libs/aiomysql
-
-The aiomysql dialect is SQLAlchemy's second Python asyncio dialect.
-
-Using a special asyncio mediation layer, the aiomysql dialect is usable
-as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
-extension package.
-
-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")
-
-
-""" # noqa
-from .pymysql import MySQLDialect_pymysql
-from ... import pool
-from ... import util
-from ...engine import AdaptedConnection
-from ...util.concurrency import asyncio
-from ...util.concurrency import await_fallback
-from ...util.concurrency import await_only
-
-
-class AsyncAdapt_aiomysql_cursor:
- # TODO: base on connectors/asyncio.py
- # see #10415
- server_side = False
- __slots__ = (
- "_adapt_connection",
- "_connection",
- "await_",
- "_cursor",
- "_rows",
- )
-
- def __init__(self, adapt_connection):
- self._adapt_connection = adapt_connection
- self._connection = adapt_connection._connection
- self.await_ = adapt_connection.await_
-
- cursor = self._connection.cursor(adapt_connection.dbapi.Cursor)
-
- # see https://github.com/aio-libs/aiomysql/issues/543
- self._cursor = self.await_(cursor.__aenter__())
- self._rows = []
-
- @property
- def description(self):
- return self._cursor.description
-
- @property
- def rowcount(self):
- return self._cursor.rowcount
-
- @property
- def arraysize(self):
- return self._cursor.arraysize
-
- @arraysize.setter
- def arraysize(self, value):
- self._cursor.arraysize = value
-
- @property
- def lastrowid(self):
- return self._cursor.lastrowid
-
- def close(self):
- # note we aren't actually closing the cursor here,
- # we are just letting GC do it. to allow this to be async
- # we would need the Result to change how it does "Safe close cursor".
- # MySQL "cursors" don't actually have state to be "closed" besides
- # 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[:] = []
-
- def execute(self, operation, parameters=None):
- return self.await_(self._execute_async(operation, parameters))
-
- def executemany(self, operation, seq_of_parameters):
- return self.await_(
- self._executemany_async(operation, seq_of_parameters)
- )
-
- async def _execute_async(self, operation, parameters):
- async with self._adapt_connection._execute_mutex:
- result = await self._cursor.execute(operation, parameters)
-
- if not self.server_side:
- # aiomysql has a "fake" async result, so we have to pull it out
- # 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())
- return result
-
- async def _executemany_async(self, operation, seq_of_parameters):
- async with self._adapt_connection._execute_mutex:
- return await self._cursor.executemany(operation, seq_of_parameters)
-
- def setinputsizes(self, *inputsizes):
- pass
-
- def __iter__(self):
- while self._rows:
- yield self._rows.pop(0)
-
- def fetchone(self):
- if self._rows:
- return self._rows.pop(0)
- else:
- return None
-
- def fetchmany(self, size=None):
- if size is None:
- size = self.arraysize
-
- retval = self._rows[0:size]
- self._rows[:] = self._rows[size:]
- return retval
-
- def fetchall(self):
- retval = self._rows[:]
- self._rows[:] = []
- return retval
-
-
-class AsyncAdapt_aiomysql_ss_cursor(AsyncAdapt_aiomysql_cursor):
- # TODO: base on connectors/asyncio.py
- # see #10415
- __slots__ = ()
- server_side = True
-
- def __init__(self, adapt_connection):
- self._adapt_connection = adapt_connection
- self._connection = adapt_connection._connection
- self.await_ = adapt_connection.await_
-
- cursor = self._connection.cursor(adapt_connection.dbapi.SSCursor)
-
- self._cursor = self.await_(cursor.__aenter__())
-
- def close(self):
- if self._cursor is not None:
- self.await_(self._cursor.close())
- self._cursor = None
-
- def fetchone(self):
- return self.await_(self._cursor.fetchone())
-
- def fetchmany(self, size=None):
- return self.await_(self._cursor.fetchmany(size=size))
-
- def fetchall(self):
- return self.await_(self._cursor.fetchall())
-
-
-class AsyncAdapt_aiomysql_connection(AdaptedConnection):
- # TODO: base on connectors/asyncio.py
- # see #10415
- await_ = staticmethod(await_only)
- __slots__ = ("dbapi", "_execute_mutex")
-
- def __init__(self, dbapi, connection):
- self.dbapi = dbapi
- self._connection = connection
- self._execute_mutex = asyncio.Lock()
-
- def ping(self, reconnect):
- return self.await_(self._connection.ping(reconnect))
-
- def character_set_name(self):
- return self._connection.character_set_name()
-
- def autocommit(self, value):
- self.await_(self._connection.autocommit(value))
-
- def cursor(self, server_side=False):
- if server_side:
- return AsyncAdapt_aiomysql_ss_cursor(self)
- else:
- return AsyncAdapt_aiomysql_cursor(self)
-
- def rollback(self):
- self.await_(self._connection.rollback())
-
- def commit(self):
- self.await_(self._connection.commit())
-
- def terminate(self):
- # it's not awaitable.
- self._connection.close()
-
- def close(self) -> None:
- self.await_(self._connection.ensure_closed())
-
-
-class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection):
- # TODO: base on connectors/asyncio.py
- # see #10415
- __slots__ = ()
-
- await_ = staticmethod(await_fallback)
-
-
-class AsyncAdapt_aiomysql_dbapi:
- def __init__(self, aiomysql, pymysql):
- self.aiomysql = aiomysql
- self.pymysql = pymysql
- self.paramstyle = "format"
- self._init_dbapi_attributes()
- self.Cursor, self.SSCursor = self._init_cursors_subclasses()
-
- def _init_dbapi_attributes(self):
- for name in (
- "Warning",
- "Error",
- "InterfaceError",
- "DataError",
- "DatabaseError",
- "OperationalError",
- "InterfaceError",
- "IntegrityError",
- "ProgrammingError",
- "InternalError",
- "NotSupportedError",
- ):
- setattr(self, name, getattr(self.aiomysql, name))
-
- for name in (
- "NUMBER",
- "STRING",
- "DATETIME",
- "BINARY",
- "TIMESTAMP",
- "Binary",
- ):
- setattr(self, name, getattr(self.pymysql, name))
-
- def connect(self, *arg, **kw):
- async_fallback = kw.pop("async_fallback", False)
- creator_fn = kw.pop("async_creator_fn", self.aiomysql.connect)
-
- if util.asbool(async_fallback):
- return AsyncAdaptFallback_aiomysql_connection(
- self,
- await_fallback(creator_fn(*arg, **kw)),
- )
- else:
- return AsyncAdapt_aiomysql_connection(
- self,
- await_only(creator_fn(*arg, **kw)),
- )
-
- def _init_cursors_subclasses(self):
- # suppress unconditional warning emitted by aiomysql
- class Cursor(self.aiomysql.Cursor):
- async def _show_warnings(self, conn):
- pass
-
- class SSCursor(self.aiomysql.SSCursor):
- async def _show_warnings(self, conn):
- pass
-
- return Cursor, SSCursor
-
-
-class MySQLDialect_aiomysql(MySQLDialect_pymysql):
- driver = "aiomysql"
- supports_statement_cache = True
-
- supports_server_side_cursors = True
- _sscursor = AsyncAdapt_aiomysql_ss_cursor
-
- is_async = True
- has_terminate = True
-
- @classmethod
- def import_dbapi(cls):
- return AsyncAdapt_aiomysql_dbapi(
- __import__("aiomysql"), __import__("pymysql")
- )
-
- @classmethod
- def get_pool_class(cls, url):
- async_fallback = url.query.get("async_fallback", False)
-
- if util.asbool(async_fallback):
- return pool.FallbackAsyncAdaptedQueuePool
- else:
- return pool.AsyncAdaptedQueuePool
-
- def do_terminate(self, dbapi_connection) -> None:
- dbapi_connection.terminate()
-
- def create_connect_args(self, url):
- return super().create_connect_args(
- url, _translate_args=dict(username="user", database="db")
- )
-
- def is_disconnect(self, e, connection, cursor):
- if super().is_disconnect(e, connection, cursor):
- return True
- else:
- str_e = str(e).lower()
- return "not connected" in str_e
-
- def _found_rows_client_flag(self):
- from pymysql.constants import CLIENT
-
- return CLIENT.FOUND_ROWS
-
- def get_driver_connection(self, connection):
- return connection._connection
-
-
-dialect = MySQLDialect_aiomysql
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py
deleted file mode 100644
index 7360044..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py
+++ /dev/null
@@ -1,337 +0,0 @@
-# dialects/mysql/asyncmy.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors <see AUTHORS
-# file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-r"""
-.. dialect:: mysql+asyncmy
- :name: asyncmy
- :dbapi: asyncmy
- :connectstring: mysql+asyncmy://user:password@host:port/dbname[?key=value&key=value...]
- :url: https://github.com/long2ice/asyncmy
-
-Using a special asyncio mediation layer, the asyncmy dialect is usable
-as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
-extension package.
-
-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")
-
-
-""" # noqa
-from contextlib import asynccontextmanager
-
-from .pymysql import MySQLDialect_pymysql
-from ... import pool
-from ... import util
-from ...engine import AdaptedConnection
-from ...util.concurrency import asyncio
-from ...util.concurrency import await_fallback
-from ...util.concurrency import await_only
-
-
-class AsyncAdapt_asyncmy_cursor:
- # TODO: base on connectors/asyncio.py
- # see #10415
- server_side = False
- __slots__ = (
- "_adapt_connection",
- "_connection",
- "await_",
- "_cursor",
- "_rows",
- )
-
- def __init__(self, adapt_connection):
- self._adapt_connection = adapt_connection
- self._connection = adapt_connection._connection
- self.await_ = adapt_connection.await_
-
- cursor = self._connection.cursor()
-
- self._cursor = self.await_(cursor.__aenter__())
- self._rows = []
-
- @property
- def description(self):
- return self._cursor.description
-
- @property
- def rowcount(self):
- return self._cursor.rowcount
-
- @property
- def arraysize(self):
- return self._cursor.arraysize
-
- @arraysize.setter
- def arraysize(self, value):
- self._cursor.arraysize = value
-
- @property
- def lastrowid(self):
- return self._cursor.lastrowid
-
- def close(self):
- # note we aren't actually closing the cursor here,
- # we are just letting GC do it. to allow this to be async
- # we would need the Result to change how it does "Safe close cursor".
- # MySQL "cursors" don't actually have state to be "closed" besides
- # 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[:] = []
-
- def execute(self, operation, parameters=None):
- return self.await_(self._execute_async(operation, parameters))
-
- def executemany(self, operation, seq_of_parameters):
- return self.await_(
- self._executemany_async(operation, seq_of_parameters)
- )
-
- async def _execute_async(self, operation, parameters):
- async with self._adapt_connection._mutex_and_adapt_errors():
- if parameters is None:
- result = await self._cursor.execute(operation)
- else:
- result = await self._cursor.execute(operation, parameters)
-
- if not self.server_side:
- # asyncmy has a "fake" async result, so we have to pull it out
- # 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())
- return result
-
- async def _executemany_async(self, operation, seq_of_parameters):
- async with self._adapt_connection._mutex_and_adapt_errors():
- return await self._cursor.executemany(operation, seq_of_parameters)
-
- def setinputsizes(self, *inputsizes):
- pass
-
- def __iter__(self):
- while self._rows:
- yield self._rows.pop(0)
-
- def fetchone(self):
- if self._rows:
- return self._rows.pop(0)
- else:
- return None
-
- def fetchmany(self, size=None):
- if size is None:
- size = self.arraysize
-
- retval = self._rows[0:size]
- self._rows[:] = self._rows[size:]
- return retval
-
- def fetchall(self):
- retval = self._rows[:]
- self._rows[:] = []
- return retval
-
-
-class AsyncAdapt_asyncmy_ss_cursor(AsyncAdapt_asyncmy_cursor):
- # TODO: base on connectors/asyncio.py
- # see #10415
- __slots__ = ()
- server_side = True
-
- def __init__(self, adapt_connection):
- self._adapt_connection = adapt_connection
- self._connection = adapt_connection._connection
- self.await_ = adapt_connection.await_
-
- cursor = self._connection.cursor(
- adapt_connection.dbapi.asyncmy.cursors.SSCursor
- )
-
- self._cursor = self.await_(cursor.__aenter__())
-
- def close(self):
- if self._cursor is not None:
- self.await_(self._cursor.close())
- self._cursor = None
-
- def fetchone(self):
- return self.await_(self._cursor.fetchone())
-
- def fetchmany(self, size=None):
- return self.await_(self._cursor.fetchmany(size=size))
-
- def fetchall(self):
- return self.await_(self._cursor.fetchall())
-
-
-class AsyncAdapt_asyncmy_connection(AdaptedConnection):
- # TODO: base on connectors/asyncio.py
- # see #10415
- await_ = staticmethod(await_only)
- __slots__ = ("dbapi", "_execute_mutex")
-
- def __init__(self, dbapi, connection):
- self.dbapi = dbapi
- self._connection = connection
- self._execute_mutex = asyncio.Lock()
-
- @asynccontextmanager
- async def _mutex_and_adapt_errors(self):
- async with self._execute_mutex:
- try:
- yield
- except AttributeError:
- raise self.dbapi.InternalError(
- "network operation failed due to asyncmy attribute error"
- )
-
- def ping(self, reconnect):
- assert not reconnect
- return self.await_(self._do_ping())
-
- async def _do_ping(self):
- async with self._mutex_and_adapt_errors():
- return await self._connection.ping(False)
-
- def character_set_name(self):
- return self._connection.character_set_name()
-
- def autocommit(self, value):
- self.await_(self._connection.autocommit(value))
-
- def cursor(self, server_side=False):
- if server_side:
- return AsyncAdapt_asyncmy_ss_cursor(self)
- else:
- return AsyncAdapt_asyncmy_cursor(self)
-
- def rollback(self):
- self.await_(self._connection.rollback())
-
- def commit(self):
- self.await_(self._connection.commit())
-
- def terminate(self):
- # it's not awaitable.
- self._connection.close()
-
- def close(self) -> None:
- self.await_(self._connection.ensure_closed())
-
-
-class AsyncAdaptFallback_asyncmy_connection(AsyncAdapt_asyncmy_connection):
- __slots__ = ()
-
- await_ = staticmethod(await_fallback)
-
-
-def _Binary(x):
- """Return x as a binary type."""
- return bytes(x)
-
-
-class AsyncAdapt_asyncmy_dbapi:
- def __init__(self, asyncmy):
- self.asyncmy = asyncmy
- self.paramstyle = "format"
- self._init_dbapi_attributes()
-
- def _init_dbapi_attributes(self):
- for name in (
- "Warning",
- "Error",
- "InterfaceError",
- "DataError",
- "DatabaseError",
- "OperationalError",
- "InterfaceError",
- "IntegrityError",
- "ProgrammingError",
- "InternalError",
- "NotSupportedError",
- ):
- setattr(self, name, getattr(self.asyncmy.errors, name))
-
- STRING = util.symbol("STRING")
- NUMBER = util.symbol("NUMBER")
- BINARY = util.symbol("BINARY")
- DATETIME = util.symbol("DATETIME")
- TIMESTAMP = util.symbol("TIMESTAMP")
- Binary = staticmethod(_Binary)
-
- def connect(self, *arg, **kw):
- async_fallback = kw.pop("async_fallback", False)
- creator_fn = kw.pop("async_creator_fn", self.asyncmy.connect)
-
- if util.asbool(async_fallback):
- return AsyncAdaptFallback_asyncmy_connection(
- self,
- await_fallback(creator_fn(*arg, **kw)),
- )
- else:
- return AsyncAdapt_asyncmy_connection(
- self,
- await_only(creator_fn(*arg, **kw)),
- )
-
-
-class MySQLDialect_asyncmy(MySQLDialect_pymysql):
- driver = "asyncmy"
- supports_statement_cache = True
-
- supports_server_side_cursors = True
- _sscursor = AsyncAdapt_asyncmy_ss_cursor
-
- is_async = True
- has_terminate = True
-
- @classmethod
- def import_dbapi(cls):
- return AsyncAdapt_asyncmy_dbapi(__import__("asyncmy"))
-
- @classmethod
- def get_pool_class(cls, url):
- async_fallback = url.query.get("async_fallback", False)
-
- if util.asbool(async_fallback):
- return pool.FallbackAsyncAdaptedQueuePool
- else:
- return pool.AsyncAdaptedQueuePool
-
- def do_terminate(self, dbapi_connection) -> None:
- dbapi_connection.terminate()
-
- def create_connect_args(self, url):
- return super().create_connect_args(
- url, _translate_args=dict(username="user", database="db")
- )
-
- def is_disconnect(self, e, connection, cursor):
- if super().is_disconnect(e, connection, cursor):
- return True
- else:
- str_e = str(e).lower()
- return (
- "not connected" in str_e or "network operation failed" in str_e
- )
-
- def _found_rows_client_flag(self):
- from asyncmy.constants import CLIENT
-
- return CLIENT.FOUND_ROWS
-
- def get_driver_connection(self, connection):
- return connection._connection
-
-
-dialect = MySQLDialect_asyncmy
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py
deleted file mode 100644
index dacbb7a..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py
+++ /dev/null
@@ -1,3447 +0,0 @@
-# dialects/mysql/base.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-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+
-
-Supported Versions and Features
--------------------------------
-
-SQLAlchemy supports MySQL starting with version 5.0.2 through modern releases,
-as well as all modern versions of MariaDB. See the official MySQL
-documentation for detailed information about features supported in any given
-server release.
-
-.. versionchanged:: 1.4 minimum MySQL version supported is now 5.0.2.
-
-MariaDB Support
-~~~~~~~~~~~~~~~
-
-The MariaDB variant of MySQL retains fundamental compatibility with MySQL's
-protocols however the development of these two products continues to diverge.
-Within the realm of SQLAlchemy, the two databases have a small number of
-syntactical and behavioral differences that SQLAlchemy accommodates automatically.
-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")
-
-Upon first connect, the SQLAlchemy dialect employs a
-server version detection scheme that determines if the
-backing database reports as MariaDB. Based on this flag, the dialect
-can make different choices in those of areas where its behavior
-must be different.
-
-.. _mysql_mariadb_only_mode:
-
-MariaDB-Only Mode
-~~~~~~~~~~~~~~~~~
-
-The dialect also supports an **optional** "MariaDB-only" mode of connection, which may be
-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")
-
-The above engine, upon first connect, will raise an error if the server version
-detection detects that the backing database is not MariaDB.
-
-When using an engine with ``"mariadb"`` as the dialect name, **all mysql-specific options
-that include the name "mysql" in them are now named with "mariadb"**. This means
-options like ``mysql_engine`` should be named ``mariadb_engine``, etc. Both
-"mysql" and "mariadb" options can be used simultaneously for applications that
-use URLs with both "mysql" and "mariadb" dialects::
-
- my_table = Table(
- "mytable",
- metadata,
- Column("id", Integer, primary_key=True),
- Column("textdata", String(50)),
- mariadb_engine="InnoDB",
- mysql_engine="InnoDB",
- )
-
- Index(
- "textdata_ix",
- my_table.c.textdata,
- mysql_prefix="FULLTEXT",
- mariadb_prefix="FULLTEXT",
- )
-
-Similar behavior will occur when the above structures are reflected, i.e. the
-"mariadb" prefix will be present in the option names when the database URL
-is based on the "mariadb" name.
-
-.. versionadded:: 1.4 Added "mariadb" dialect name supporting "MariaDB-only mode"
- for the MySQL dialect.
-
-.. _mysql_connection_timeouts:
-
-Connection Timeouts and Disconnects
------------------------------------
-
-MySQL / MariaDB feature an automatic connection close behavior, for connections that
-have been idle for a fixed period of time, defaulting to eight hours.
-To circumvent having this issue, use
-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)
-
-For more comprehensive disconnect detection of pooled connections, including
-accommodation of server restarts and network issues, a pre-ping approach may
-be employed. See :ref:`pool_disconnects` for current approaches.
-
-.. seealso::
-
- :ref:`pool_disconnects` - Background on several techniques for dealing
- with timed out connections as well as database restarts.
-
-.. _mysql_storage_engines:
-
-CREATE TABLE arguments including Storage Engines
-------------------------------------------------
-
-Both MySQL's and MariaDB's CREATE TABLE syntax includes a wide array of special options,
-including ``ENGINE``, ``CHARSET``, ``MAX_ROWS``, ``ROW_FORMAT``,
-``INSERT_METHOD``, and many more.
-To accommodate the rendering of these arguments, specify the form
-``mysql_argument_name="value"``. For example, to specify a table with
-``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"
- )
-
-When supporting :ref:`mysql_mariadb_only_mode` mode, similar keys against
-the "mariadb" prefix must be included as well. The values can of course
-vary independently so that different settings on MySQL vs. MariaDB may
-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"
-
- )
-
-The MySQL / MariaDB dialects will normally transfer any keyword specified as
-``mysql_keyword_name`` to be rendered as ``KEYWORD_NAME`` in the
-``CREATE TABLE`` statement. A handful of these names will render with a space
-instead of an underscore; to support this, the MySQL dialect has awareness of
-these particular names, which include ``DATA DIRECTORY``
-(e.g. ``mysql_data_directory``), ``CHARACTER SET`` (e.g.
-``mysql_character_set``) and ``INDEX DIRECTORY`` (e.g.
-``mysql_index_directory``).
-
-The most common argument is ``mysql_engine``, which refers to the storage
-engine for the table. Historically, MySQL server installations would default
-to ``MyISAM`` for this value, although newer versions may be defaulting
-to ``InnoDB``. The ``InnoDB`` engine is typically preferred for its support
-of transactions and foreign keys.
-
-A :class:`_schema.Table`
-that is created in a MySQL / MariaDB database with a storage engine
-of ``MyISAM`` will be essentially non-transactional, meaning any
-INSERT/UPDATE/DELETE statement referring to this table will be invoked as
-autocommit. It also will have no support for foreign key constraints; while
-the ``CREATE TABLE`` statement accepts foreign key options, when using the
-``MyISAM`` storage engine these arguments are discarded. Reflecting such a
-table will also produce no foreign key constraint information.
-
-For fully atomic transactions as well as support for foreign key
-constraints, all participating ``CREATE TABLE`` statements must specify a
-transactional engine, which in the vast majority of cases is ``InnoDB``.
-
-
-Case Sensitivity and Table Reflection
--------------------------------------
-
-Both MySQL and MariaDB have inconsistent support for case-sensitive identifier
-names, basing support on specific details of the underlying
-operating system. However, it has been observed that no matter
-what case sensitivity behavior is present, the names of tables in
-foreign key declarations are *always* received from the database
-as all-lower case, making it impossible to accurately reflect a
-schema where inter-related tables use mixed-case identifier names.
-
-Therefore it is strongly advised that table names be declared as
-all lower case both within SQLAlchemy as well as on the MySQL / MariaDB
-database itself, especially if database reflection features are
-to be used.
-
-.. _mysql_isolation_level:
-
-Transaction Isolation Level
----------------------------
-
-All MySQL / MariaDB dialects support setting of transaction isolation level both via a
-dialect-specific parameter :paramref:`_sa.create_engine.isolation_level`
-accepted
-by :func:`_sa.create_engine`, as well as the
-:paramref:`.Connection.execution_options.isolation_level` argument as passed to
-:meth:`_engine.Connection.execution_options`.
-This feature works by issuing the
-command ``SET SESSION TRANSACTION ISOLATION LEVEL <level>`` for each new
-connection. For the special AUTOCOMMIT isolation level, DBAPI-specific
-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"
- )
-
-To set using per-connection execution options::
-
- connection = engine.connect()
- connection = connection.execution_options(
- isolation_level="READ COMMITTED"
- )
-
-Valid values for ``isolation_level`` include:
-
-* ``READ COMMITTED``
-* ``READ UNCOMMITTED``
-* ``REPEATABLE READ``
-* ``SERIALIZABLE``
-* ``AUTOCOMMIT``
-
-The special ``AUTOCOMMIT`` value makes use of the various "autocommit"
-attributes provided by specific DBAPIs, and is currently supported by
-MySQLdb, MySQL-Client, MySQL-Connector Python, and PyMySQL. Using it,
-the database connection will return true for the value of
-``SELECT @@autocommit;``.
-
-There are also more options for isolation level configurations, such as
-"sub-engine" objects linked to a main :class:`_engine.Engine` which each apply
-different isolation level settings. See the discussion at
-:ref:`dbapi_autocommit` for background.
-
-.. seealso::
-
- :ref:`dbapi_autocommit`
-
-AUTO_INCREMENT Behavior
------------------------
-
-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.create()
- CREATE TABLE mytable (
- id INTEGER NOT NULL AUTO_INCREMENT,
- PRIMARY KEY (id)
- )
-
-You can disable this behavior by passing ``False`` to the
-:paramref:`_schema.Column.autoincrement` argument of :class:`_schema.Column`.
-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)
- )
-
-.. _mysql_ss_cursors:
-
-Server Side Cursors
--------------------
-
-Server-side cursor support is available for the mysqlclient, PyMySQL,
-mariadbconnector dialects and may also be available in others. This makes use
-of either the "buffered=True/False" flag if available or by using a class such
-as ``MySQLdb.cursors.SSCursor`` or ``pymysql.cursors.SSCursor`` internally.
-
-
-Server side cursors are enabled on a per-statement basis by using the
-:paramref:`.Connection.execution_options.stream_results` connection execution
-option::
-
- with engine.connect() as conn:
- 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
-used with this option.
-
-.. deprecated:: 1.4 The dialect-level server_side_cursors flag is deprecated
- and will be removed in a future release. Please use the
- :paramref:`_engine.Connection.stream_results` execution option for
- unbuffered cursor support.
-
-.. seealso::
-
- :ref:`engine_stream_results`
-
-.. _mysql_unicode:
-
-Unicode
--------
-
-Charset Selection
-~~~~~~~~~~~~~~~~~
-
-Most MySQL / MariaDB DBAPIs offer the option to set the client character set for
-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")
-
-This charset is the **client character set** for the connection. Some
-MySQL DBAPIs will default this to a value such as ``latin1``, and some
-will make use of the ``default-character-set`` setting in the ``my.cnf``
-file as well. Documentation for the DBAPI in use should be consulted
-for specific behavior.
-
-The encoding used for Unicode has traditionally been ``'utf8'``. However, for
-MySQL versions 5.5.3 and MariaDB 5.5 on forward, a new MySQL-specific encoding
-``'utf8mb4'`` has been introduced, and as of MySQL 8.0 a warning is emitted by
-the server if plain ``utf8`` is specified within any server-side directives,
-replaced with ``utf8mb3``. The rationale for this new encoding is due to the
-fact that MySQL's legacy utf-8 encoding only supports codepoints up to three
-bytes instead of four. Therefore, when communicating with a MySQL or MariaDB
-database that includes codepoints more than three bytes in size, this new
-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")
-
-All modern DBAPIs should support the ``utf8mb4`` charset.
-
-In order to use ``utf8mb4`` encoding for a schema that was created with legacy
-``utf8``, changes to the MySQL/MariaDB schema and/or server configuration may be
-required.
-
-.. seealso::
-
- `The utf8mb4 Character Set \
- <https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html>`_ - \
- in the MySQL documentation
-
-.. _mysql_binary_introducer:
-
-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::
-
- default.py:509: Warning: (1300, "Invalid utf8mb4 character string:
- 'F9876A'")
- cursor.execute(statement, parameters)
-
-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::
-
- INSERT INTO table (data) VALUES (_binary %s)
-
-These character set introducers are provided by the DBAPI driver, assuming the
-use of mysqlclient or PyMySQL (both of which are recommended). Add the query
-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")
-
- # PyMySQL
- engine = create_engine(
- "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.
-
-SQLAlchemy itself cannot render this ``_binary`` prefix reliably, as it does
-not work with the NULL value, which is valid to be sent as a bound parameter.
-As the MySQL driver renders parameters directly into the SQL string, it's the
-most efficient place for this additional keyword to be passed.
-
-.. seealso::
-
- `Character set introducers <https://dev.mysql.com/doc/refman/5.7/en/charset-introducer.html>`_ - on the MySQL website
-
-
-ANSI Quoting Style
-------------------
-
-MySQL / MariaDB feature two varieties of identifier "quoting style", one using
-backticks and the other using quotes, e.g. ```some_identifier``` vs.
-``"some_identifier"``. All MySQL dialects detect which version
-is in use by checking the value of :ref:`sql_mode<mysql_sql_mode>` when a connection is first
-established with a particular :class:`_engine.Engine`.
-This quoting style comes
-into play when rendering table and column names as well as when reflecting
-existing database structures. The detection is entirely automatic and
-no special configuration is needed to use either quoting style.
-
-
-.. _mysql_sql_mode:
-
-Changing the sql_mode
----------------------
-
-MySQL supports operating in multiple
-`Server SQL Modes <https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html>`_ for
-both Servers and Clients. To change the ``sql_mode`` for a given application, a
-developer can leverage SQLAlchemy's Events system.
-
-In the following example, the event system is used to set the ``sql_mode`` on
-the ``first_connect`` and ``connect`` events::
-
- from sqlalchemy import create_engine, event
-
- 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)
- def connect(dbapi_connection, connection_record):
- 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"
-statement on the connection at the moment a particular DBAPI connection is
-first created for a given Pool, before the connection is made available to the
-connection pool. Additionally, because the function was registered with
-``insert=True``, it will be prepended to the internal list of registered
-functions.
-
-
-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]'))
-
-And of course any valid SQL statement can be executed as a string as well.
-
-Some limited direct support for MySQL / MariaDB extensions to SQL is currently
-available.
-
-* INSERT..ON DUPLICATE KEY UPDATE: See
- :ref:`mysql_insert_on_duplicate_key_update`
-
-* SELECT pragma, use :meth:`_expression.Select.prefix_with` and
- :meth:`_query.Query.prefix_with`::
-
- select(...).prefix_with(['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
-
-* UPDATE with LIMIT::
-
- update(..., mysql_limit=10, mariadb_limit=10)
-
-* optimizer hints, use :meth:`_expression.Select.prefix_with` and
- :meth:`_query.Query.prefix_with`::
-
- select(...).prefix_with("/*+ NO_RANGE_OPTIMIZATION(t4 PRIMARY) */")
-
-* index hints, use :meth:`_expression.Select.with_hint` and
- :meth:`_query.Query.with_hint`::
-
- select(...).with_hint(some_table, "USE INDEX xyz")
-
-* MATCH operator support::
-
- from sqlalchemy.dialects.mysql import match
- select(...).where(match(col1, col2, against="some expr").in_boolean_mode())
-
- .. seealso::
-
- :class:`_mysql.match`
-
-INSERT/DELETE...RETURNING
--------------------------
-
-The MariaDB dialect supports 10.5+'s ``INSERT..RETURNING`` and
-``DELETE..RETURNING`` (10.0+) syntaxes. ``INSERT..RETURNING`` may be used
-automatically in some cases in order to fetch newly generated identifiers in
-place of the traditional approach of using ``cursor.lastrowid``, however
-``cursor.lastrowid`` is currently still preferred for simple single-statement
-cases for its better performance.
-
-To specify an explicit ``RETURNING`` clause, use the
-:meth:`._UpdateBase.returning` method on a per-statement basis::
-
- # INSERT..RETURNING
- result = connection.execute(
- 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)
- )
- print(result.all())
-
-.. versionadded:: 2.0 Added support for MariaDB RETURNING
-
-.. _mysql_insert_on_duplicate_key_update:
-
-INSERT...ON DUPLICATE KEY UPDATE (Upsert)
-------------------------------------------
-
-MySQL / MariaDB allow "upserts" (update or insert)
-of rows into a table via the ``ON DUPLICATE KEY UPDATE`` clause of the
-``INSERT`` statement. A candidate row will only be inserted if that row does
-not match an existing primary or unique key in the table; otherwise, an UPDATE
-will be performed. The statement allows for separate specification of the
-values to INSERT versus the values for UPDATE.
-
-SQLAlchemy provides ``ON DUPLICATE KEY UPDATE`` support via the MySQL-specific
-:func:`.mysql.insert()` function, which provides
-the generative method :meth:`~.mysql.Insert.on_duplicate_key_update`:
-
-.. sourcecode:: pycon+sql
-
- >>> from sqlalchemy.dialects.mysql import insert
-
- >>> insert_stmt = insert(my_table).values(
- ... id='some_existing_id',
- ... data='inserted value')
-
- >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
- ... data=insert_stmt.inserted.data,
- ... status='U'
- ... )
- >>> print(on_duplicate_key_stmt)
- {printsql}INSERT INTO my_table (id, data) VALUES (%s, %s)
- ON DUPLICATE KEY UPDATE data = VALUES(data), status = %s
-
-
-Unlike PostgreSQL's "ON CONFLICT" phrase, the "ON DUPLICATE KEY UPDATE"
-phrase will always match on any primary key or unique key, and will always
-perform an UPDATE if there's a match; there are no options for it to raise
-an error or to skip performing an UPDATE.
-
-``ON DUPLICATE KEY UPDATE`` is used to perform an update of the already
-existing row, using any combination of new values as well as values
-from the proposed insertion. These values are normally specified using
-keyword arguments passed to the
-:meth:`_mysql.Insert.on_duplicate_key_update`
-given column key values (usually the name of the column, unless it
-specifies :paramref:`_schema.Column.key`
-) as keys and literal or SQL expressions
-as values:
-
-.. sourcecode:: pycon+sql
-
- >>> insert_stmt = insert(my_table).values(
- ... id='some_existing_id',
- ... data='inserted value')
-
- >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
- ... data="some data",
- ... updated_at=func.current_timestamp(),
- ... )
-
- >>> print(on_duplicate_key_stmt)
- {printsql}INSERT INTO my_table (id, data) VALUES (%s, %s)
- ON DUPLICATE KEY UPDATE data = %s, updated_at = CURRENT_TIMESTAMP
-
-In a manner similar to that of :meth:`.UpdateBase.values`, other parameter
-forms are accepted, including a single dictionary:
-
-.. sourcecode:: pycon+sql
-
- >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
- ... {"data": "some data", "updated_at": func.current_timestamp()},
- ... )
-
-as well as a list of 2-tuples, which will automatically provide
-a parameter-ordered UPDATE statement in a manner similar to that described
-at :ref:`tutorial_parameter_ordered_updates`. Unlike the :class:`_expression.Update`
-object,
-no special flag is needed to specify the intent since the argument form is
-this context is unambiguous:
-
-.. sourcecode:: pycon+sql
-
- >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
- ... [
- ... ("data", "some data"),
- ... ("updated_at", func.current_timestamp()),
- ... ]
- ... )
-
- >>> print(on_duplicate_key_stmt)
- {printsql}INSERT INTO my_table (id, data) VALUES (%s, %s)
- ON DUPLICATE KEY UPDATE data = %s, updated_at = CURRENT_TIMESTAMP
-
-.. versionchanged:: 1.3 support for parameter-ordered UPDATE clause within
- MySQL ON DUPLICATE KEY UPDATE
-
-.. warning::
-
- The :meth:`_mysql.Insert.on_duplicate_key_update`
- method does **not** take into
- account Python-side default UPDATE values or generation functions, e.g.
- e.g. those specified using :paramref:`_schema.Column.onupdate`.
- These values will not be exercised for an ON DUPLICATE KEY style of UPDATE,
- unless they are manually specified explicitly in the parameters.
-
-
-
-In order to refer to the proposed insertion row, the special alias
-:attr:`_mysql.Insert.inserted` is available as an attribute on
-the :class:`_mysql.Insert` object; this object is a
-:class:`_expression.ColumnCollection` which contains all columns of the target
-table:
-
-.. sourcecode:: pycon+sql
-
- >>> stmt = insert(my_table).values(
- ... id='some_id',
- ... data='inserted value',
- ... author='jlh')
-
- >>> do_update_stmt = stmt.on_duplicate_key_update(
- ... data="updated value",
- ... author=stmt.inserted.author
- ... )
-
- >>> print(do_update_stmt)
- {printsql}INSERT INTO my_table (id, data, author) VALUES (%s, %s, %s)
- ON DUPLICATE KEY UPDATE data = %s, author = VALUES(author)
-
-When rendered, the "inserted" namespace will produce the expression
-``VALUES(<columnname>)``.
-
-.. versionadded:: 1.2 Added support for MySQL ON DUPLICATE KEY UPDATE clause
-
-
-
-rowcount Support
-----------------
-
-SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
-usual definition of "number of rows matched by an UPDATE or DELETE" statement.
-This is in contradiction to the default setting on most MySQL DBAPI drivers,
-which is "number of rows actually modified/deleted". For this reason, the
-SQLAlchemy MySQL dialects always add the ``constants.CLIENT.FOUND_ROWS``
-flag, or whatever is equivalent for the target dialect, upon connection.
-This setting is currently hardcoded.
-
-.. seealso::
-
- :attr:`_engine.CursorResult.rowcount`
-
-
-.. _mysql_indexes:
-
-MySQL / MariaDB- Specific Index Options
------------------------------------------
-
-MySQL and MariaDB-specific extensions to the :class:`.Index` construct are available.
-
-Index Length
-~~~~~~~~~~~~~
-
-MySQL and MariaDB both provide an option to create index entries with a certain length, where
-"length" refers to the number of characters or bytes in each value which will
-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('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})
-
-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
-either an integer (and, thus, specify the same prefix length value for all
-columns of the index) or a dict in which keys are column names and values are
-prefix length values for corresponding columns. MySQL and MariaDB only allow a
-length for a column of an index if it is for a CHAR, VARCHAR, TEXT, BINARY,
-VARBINARY and BLOB.
-
-Index Prefixes
-~~~~~~~~~~~~~~
-
-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')
-
-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
-storage engine.
-
-.. seealso::
-
- `CREATE INDEX <https://dev.mysql.com/doc/refman/5.0/en/create-index.html>`_ - MySQL documentation
-
-Index Types
-~~~~~~~~~~~~~
-
-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')
-
-As well as the ``mysql_using`` parameter on :class:`.PrimaryKeyConstraint`::
-
- 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
-type for your MySQL storage engine.
-
-More information can be found at:
-
-https://dev.mysql.com/doc/refman/5.0/en/create-index.html
-
-https://dev.mysql.com/doc/refman/5.0/en/create-table.html
-
-Index Parsers
-~~~~~~~~~~~~~
-
-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",
- )
-
-.. versionadded:: 1.3
-
-
-.. _mysql_foreign_keys:
-
-MySQL / MariaDB Foreign Keys
------------------------------
-
-MySQL and MariaDB's behavior regarding foreign keys has some important caveats.
-
-Foreign Key Arguments to Avoid
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Neither MySQL nor MariaDB support the foreign key arguments "DEFERRABLE", "INITIALLY",
-or "MATCH". Using the ``deferrable`` or ``initially`` keyword argument with
-:class:`_schema.ForeignKeyConstraint` or :class:`_schema.ForeignKey`
-will have the effect of
-these keywords being rendered in a DDL expression, which will then raise an
-error on MySQL or MariaDB. In order to use these keywords on a foreign key while having
-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
- return compiler.visit_foreign_key_constraint(element, **kw)
-
-The "MATCH" keyword is in fact more insidious, and is explicitly disallowed
-by SQLAlchemy in conjunction with the MySQL or MariaDB backends. This argument is
-silently ignored by MySQL / MariaDB, but in addition has the effect of ON UPDATE and ON
-DELETE options also being ignored by the backend. Therefore MATCH should
-never be used with the MySQL / MariaDB backends; as is the case with DEFERRABLE and
-INITIALLY, custom compilation rules can be used to correct a
-ForeignKeyConstraint at DDL definition time.
-
-Reflection of Foreign Key Constraints
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Not all MySQL / MariaDB storage engines support foreign keys. When using the
-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
- )
-
-.. seealso::
-
- :ref:`mysql_storage_engines`
-
-.. _mysql_unique_constraints:
-
-MySQL / MariaDB Unique Constraints and Reflection
-----------------------------------------------------
-
-SQLAlchemy supports both the :class:`.Index` construct with the
-flag ``unique=True``, indicating a UNIQUE index, as well as the
-:class:`.UniqueConstraint` construct, representing a UNIQUE constraint.
-Both objects/syntaxes are supported by MySQL / MariaDB when emitting DDL to create
-these constraints. However, MySQL / MariaDB does not have a unique constraint
-construct that is separate from a unique index; that is, the "UNIQUE"
-constraint on MySQL / MariaDB is equivalent to creating a "UNIQUE INDEX".
-
-When reflecting these constructs, the
-:meth:`_reflection.Inspector.get_indexes`
-and the :meth:`_reflection.Inspector.get_unique_constraints`
-methods will **both**
-return an entry for a UNIQUE index in MySQL / MariaDB. However, when performing
-full table reflection using ``Table(..., autoload_with=engine)``,
-the :class:`.UniqueConstraint` construct is
-**not** part of the fully reflected :class:`_schema.Table` construct under any
-circumstances; this construct is always represented by a :class:`.Index`
-with the ``unique=True`` setting present in the :attr:`_schema.Table.indexes`
-collection.
-
-
-TIMESTAMP / DATETIME issues
----------------------------
-
-.. _mysql_timestamp_onupdate:
-
-Rendering ON UPDATE CURRENT TIMESTAMP for MySQL / MariaDB's explicit_defaults_for_timestamp
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-MySQL / MariaDB have historically expanded the DDL for the :class:`_types.TIMESTAMP`
-datatype into the phrase "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE
-CURRENT_TIMESTAMP", which includes non-standard SQL that automatically updates
-the column with the current timestamp when an UPDATE occurs, eliminating the
-usual need to use a trigger in such a case where server-side update changes are
-desired.
-
-MySQL 5.6 introduced a new flag `explicit_defaults_for_timestamp
-<https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html
-#sysvar_explicit_defaults_for_timestamp>`_ which disables the above behavior,
-and in MySQL 8 this flag defaults to true, meaning in order to get a MySQL
-"on update timestamp" without changing this flag, the above DDL must be
-rendered explicitly. Additionally, the same DDL is valid for use of the
-``DATETIME`` datatype as well.
-
-SQLAlchemy's MySQL dialect does not yet have an option to generate
-MySQL's "ON UPDATE CURRENT_TIMESTAMP" clause, noting that this is not a general
-purpose "ON UPDATE" as there is no such syntax in standard SQL. SQLAlchemy's
-:paramref:`_schema.Column.server_onupdate` parameter is currently not related
-to this special MySQL behavior.
-
-To generate this DDL, make use of the :paramref:`_schema.Column.server_default`
-parameter and pass a textual clause that also includes the ON UPDATE clause::
-
- from sqlalchemy import Table, MetaData, Column, Integer, String, TIMESTAMP
- from sqlalchemy import text
-
- metadata = MetaData()
-
- mytable = Table(
- "mytable",
- metadata,
- Column('id', Integer, primary_key=True),
- Column('data', String(50)),
- Column(
- 'last_updated',
- TIMESTAMP,
- server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
- )
- )
-
-The same instructions apply to use of the :class:`_types.DateTime` and
-:class:`_types.DATETIME` datatypes::
-
- from sqlalchemy import DateTime
-
- mytable = Table(
- "mytable",
- metadata,
- Column('id', Integer, primary_key=True),
- Column('data', String(50)),
- Column(
- 'last_updated',
- DateTime,
- 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'
-
- 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()
- )
-
-
-.. _mysql_timestamp_null:
-
-TIMESTAMP Columns and NULL
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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::
-
- mysql> CREATE TABLE ts_test (
- -> a INTEGER,
- -> b INTEGER NOT NULL,
- -> c TIMESTAMP,
- -> d TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- -> e TIMESTAMP NULL);
- Query OK, 0 rows affected (0.03 sec)
-
- mysql> SHOW CREATE TABLE ts_test;
- +---------+-----------------------------------------------------
- | Table | Create Table
- +---------+-----------------------------------------------------
- | ts_test | CREATE TABLE `ts_test` (
- `a` int(11) DEFAULT NULL,
- `b` int(11) NOT NULL,
- `c` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- `d` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `e` timestamp NULL DEFAULT NULL
- ) ENGINE=MyISAM DEFAULT CHARSET=latin1
-
-Above, we see that an INTEGER column defaults to NULL, unless it is specified
-with NOT NULL. But when the column is of type TIMESTAMP, an implicit
-default of CURRENT_TIMESTAMP is generated which also coerces the column
-to be a NOT NULL, even though we did not specify it as such.
-
-This behavior of MySQL can be changed on the MySQL side using the
-`explicit_defaults_for_timestamp
-<https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html
-#sysvar_explicit_defaults_for_timestamp>`_ configuration flag introduced in
-MySQL 5.6. With this server setting enabled, TIMESTAMP columns behave like
-any other datatype on the MySQL side with regards to defaults and nullability.
-
-However, to accommodate the vast majority of MySQL databases that do not
-specify this new flag, SQLAlchemy emits the "NULL" specifier explicitly with
-any TIMESTAMP column that does not specify ``nullable=False``. In order to
-accommodate newer databases that specify ``explicit_defaults_for_timestamp``,
-SQLAlchemy also emits NOT NULL for TIMESTAMP columns that do specify
-``nullable=False``. The following example illustrates::
-
- from sqlalchemy import MetaData, Integer, Table, Column, text
- 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)
- )
-
-
- from sqlalchemy import create_engine
- e = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo=True)
- m.create_all(e)
-
-output::
-
- CREATE TABLE ts_test (
- a INTEGER,
- b INTEGER NOT NULL,
- c TIMESTAMP NULL,
- d TIMESTAMP NOT NULL
- )
-
-""" # noqa
-from __future__ import annotations
-
-from array import array as _array
-from collections import defaultdict
-from itertools import compress
-import re
-from typing import cast
-
-from . import reflection as _reflection
-from .enumerated import ENUM
-from .enumerated import SET
-from .json import JSON
-from .json import JSONIndexType
-from .json import JSONPathType
-from .reserved_words import RESERVED_WORDS_MARIADB
-from .reserved_words import RESERVED_WORDS_MYSQL
-from .types import _FloatType
-from .types import _IntegerType
-from .types import _MatchType
-from .types import _NumericType
-from .types import _StringType
-from .types import BIGINT
-from .types import BIT
-from .types import CHAR
-from .types import DATETIME
-from .types import DECIMAL
-from .types import DOUBLE
-from .types import FLOAT
-from .types import INTEGER
-from .types import LONGBLOB
-from .types import LONGTEXT
-from .types import MEDIUMBLOB
-from .types import MEDIUMINT
-from .types import MEDIUMTEXT
-from .types import NCHAR
-from .types import NUMERIC
-from .types import NVARCHAR
-from .types import REAL
-from .types import SMALLINT
-from .types import TEXT
-from .types import TIME
-from .types import TIMESTAMP
-from .types import TINYBLOB
-from .types import TINYINT
-from .types import TINYTEXT
-from .types import VARCHAR
-from .types import YEAR
-from ... import exc
-from ... import literal_column
-from ... import log
-from ... import schema as sa_schema
-from ... import sql
-from ... import util
-from ...engine import cursor as _cursor
-from ...engine import default
-from ...engine import reflection
-from ...engine.reflection import ReflectionDefaults
-from ...sql import coercions
-from ...sql import compiler
-from ...sql import elements
-from ...sql import functions
-from ...sql import operators
-from ...sql import roles
-from ...sql import sqltypes
-from ...sql import util as sql_util
-from ...sql import visitors
-from ...sql.compiler import InsertmanyvaluesSentinelOpts
-from ...sql.compiler import SQLCompiler
-from ...sql.schema import SchemaConst
-from ...types import BINARY
-from ...types import BLOB
-from ...types import BOOLEAN
-from ...types import DATE
-from ...types import UUID
-from ...types import VARBINARY
-from ...util import topological
-
-
-SET_RE = re.compile(
- r"\s*SET\s+(?:(?:GLOBAL|SESSION)\s+)?\w", re.I | re.UNICODE
-)
-
-# old names
-MSTime = TIME
-MSSet = SET
-MSEnum = ENUM
-MSLongBlob = LONGBLOB
-MSMediumBlob = MEDIUMBLOB
-MSTinyBlob = TINYBLOB
-MSBlob = BLOB
-MSBinary = BINARY
-MSVarBinary = VARBINARY
-MSNChar = NCHAR
-MSNVarChar = NVARCHAR
-MSChar = CHAR
-MSString = VARCHAR
-MSLongText = LONGTEXT
-MSMediumText = MEDIUMTEXT
-MSTinyText = TINYTEXT
-MSText = TEXT
-MSYear = YEAR
-MSTimeStamp = TIMESTAMP
-MSBit = BIT
-MSSmallInteger = SMALLINT
-MSTinyInteger = TINYINT
-MSMediumInteger = MEDIUMINT
-MSBigInteger = BIGINT
-MSNumeric = NUMERIC
-MSDecimal = DECIMAL
-MSDouble = DOUBLE
-MSReal = REAL
-MSFloat = FLOAT
-MSInteger = INTEGER
-
-colspecs = {
- _IntegerType: _IntegerType,
- _NumericType: _NumericType,
- _FloatType: _FloatType,
- sqltypes.Numeric: NUMERIC,
- sqltypes.Float: FLOAT,
- sqltypes.Double: DOUBLE,
- sqltypes.Time: TIME,
- sqltypes.Enum: ENUM,
- sqltypes.MatchType: _MatchType,
- sqltypes.JSON: JSON,
- sqltypes.JSON.JSONIndexType: JSONIndexType,
- sqltypes.JSON.JSONPathType: JSONPathType,
-}
-
-# Everything 3.23 through 5.1 excepting OpenGIS types.
-ischema_names = {
- "bigint": BIGINT,
- "binary": BINARY,
- "bit": BIT,
- "blob": BLOB,
- "boolean": BOOLEAN,
- "char": CHAR,
- "date": DATE,
- "datetime": DATETIME,
- "decimal": DECIMAL,
- "double": DOUBLE,
- "enum": ENUM,
- "fixed": DECIMAL,
- "float": FLOAT,
- "int": INTEGER,
- "integer": INTEGER,
- "json": JSON,
- "longblob": LONGBLOB,
- "longtext": LONGTEXT,
- "mediumblob": MEDIUMBLOB,
- "mediumint": MEDIUMINT,
- "mediumtext": MEDIUMTEXT,
- "nchar": NCHAR,
- "nvarchar": NVARCHAR,
- "numeric": NUMERIC,
- "set": SET,
- "smallint": SMALLINT,
- "text": TEXT,
- "time": TIME,
- "timestamp": TIMESTAMP,
- "tinyblob": TINYBLOB,
- "tinyint": TINYINT,
- "tinytext": TINYTEXT,
- "uuid": UUID,
- "varbinary": VARBINARY,
- "varchar": VARCHAR,
- "year": YEAR,
-}
-
-
-class MySQLExecutionContext(default.DefaultExecutionContext):
- def post_exec(self):
- if (
- self.isdelete
- and cast(SQLCompiler, self.compiled).effective_returning
- and not self.cursor.description
- ):
- # All MySQL/mariadb drivers appear to not include
- # cursor.description for DELETE..RETURNING with no rows if the
- # WHERE criteria is a straight "false" condition such as our EMPTY
- # IN condition. manufacture an empty result in this case (issue
- # #10505)
- #
- # taken from cx_Oracle implementation
- self.cursor_fetch_strategy = (
- _cursor.FullyBufferedCursorFetchStrategy(
- self.cursor,
- [
- (entry.keyname, None)
- for entry in cast(
- SQLCompiler, self.compiled
- )._result_columns
- ],
- [],
- )
- )
-
- def create_server_side_cursor(self):
- if self.dialect.supports_server_side_cursors:
- return self._dbapi_connection.cursor(self.dialect._sscursor)
- else:
- raise NotImplementedError()
-
- def fire_sequence(self, seq, type_):
- return self._execute_scalar(
- (
- "select nextval(%s)"
- % self.identifier_preparer.format_sequence(seq)
- ),
- type_,
- )
-
-
-class MySQLCompiler(compiler.SQLCompiler):
- render_table_with_column_in_update_from = True
- """Overridden from base SQLCompiler value"""
-
- extract_map = compiler.SQLCompiler.extract_map.copy()
- extract_map.update({"milliseconds": "millisecond"})
-
- def default_from(self):
- """Called when a ``SELECT`` statement has no froms,
- and no ``FROM`` clause is to be appended.
-
- """
- if self.stack:
- stmt = self.stack[-1]["selectable"]
- if stmt._where_criteria:
- return " FROM DUAL"
-
- return ""
-
- def visit_random_func(self, fn, **kw):
- return "rand%s" % self.function_argspec(fn)
-
- def visit_rollup_func(self, fn, **kw):
- clause = ", ".join(
- elem._compiler_dispatch(self, **kw) for elem in fn.clauses
- )
- return f"{clause} WITH ROLLUP"
-
- def visit_aggregate_strings_func(self, fn, **kw):
- expr, delimeter = (
- elem._compiler_dispatch(self, **kw) for elem in fn.clauses
- )
- return f"group_concat({expr} SEPARATOR {delimeter})"
-
- def visit_sequence(self, seq, **kw):
- return "nextval(%s)" % self.preparer.format_sequence(seq)
-
- def visit_sysdate_func(self, fn, **kw):
- return "SYSDATE()"
-
- def _render_json_extract_from_binary(self, binary, operator, **kw):
- # note we are intentionally calling upon the process() calls in the
- # order in which they appear in the SQL String as this is used
- # by positional parameter rendering
-
- if binary.type._type_affinity is sqltypes.JSON:
- return "JSON_EXTRACT(%s, %s)" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
-
- # for non-JSON, MySQL doesn't handle JSON null at all so it has to
- # be explicit
- case_expression = "CASE JSON_EXTRACT(%s, %s) WHEN 'null' THEN NULL" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
-
- if binary.type._type_affinity is sqltypes.Integer:
- type_expression = (
- "ELSE CAST(JSON_EXTRACT(%s, %s) AS SIGNED INTEGER)"
- % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
- )
- elif binary.type._type_affinity is sqltypes.Numeric:
- if (
- binary.type.scale is not None
- and binary.type.precision is not None
- ):
- # using DECIMAL here because MySQL does not recognize NUMERIC
- type_expression = (
- "ELSE CAST(JSON_EXTRACT(%s, %s) AS DECIMAL(%s, %s))"
- % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- binary.type.precision,
- binary.type.scale,
- )
- )
- else:
- # FLOAT / REAL not added in MySQL til 8.0.17
- type_expression = (
- "ELSE JSON_EXTRACT(%s, %s)+0.0000000000000000000000"
- % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
- )
- elif binary.type._type_affinity is sqltypes.Boolean:
- # the NULL handling is particularly weird with boolean, so
- # explicitly return true/false constants
- type_expression = "WHEN true THEN true ELSE false"
- elif binary.type._type_affinity is sqltypes.String:
- # (gord): this fails with a JSON value that's a four byte unicode
- # string. SQLite has the same problem at the moment
- # (zzzeek): I'm not really sure. let's take a look at a test case
- # that hits each backend and maybe make a requires rule for it?
- type_expression = "ELSE JSON_UNQUOTE(JSON_EXTRACT(%s, %s))" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
- else:
- # other affinity....this is not expected right now
- type_expression = "ELSE JSON_EXTRACT(%s, %s)" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
-
- return case_expression + " " + type_expression + " END"
-
- def visit_json_getitem_op_binary(self, binary, operator, **kw):
- return self._render_json_extract_from_binary(binary, operator, **kw)
-
- def visit_json_path_getitem_op_binary(self, binary, operator, **kw):
- return self._render_json_extract_from_binary(binary, operator, **kw)
-
- def visit_on_duplicate_key_update(self, on_duplicate, **kw):
- statement = self.current_executable
-
- if on_duplicate._parameter_ordering:
- parameter_ordering = [
- coercions.expect(roles.DMLColumnRole, key)
- for key in on_duplicate._parameter_ordering
- ]
- ordered_keys = set(parameter_ordering)
- cols = [
- statement.table.c[key]
- for key in parameter_ordering
- if key in statement.table.c
- ] + [c for c in statement.table.c if c.key not in ordered_keys]
- else:
- cols = statement.table.c
-
- clauses = []
-
- requires_mysql8_alias = (
- self.dialect._requires_alias_for_on_duplicate_key
- )
-
- if requires_mysql8_alias:
- if statement.table.name.lower() == "new":
- _on_dup_alias_name = "new_1"
- 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]
-
- if coercions._is_literal(val):
- val = elements.BindParameter(None, val, type_=column.type)
- value_text = self.process(val.self_group(), use_schema=False)
- else:
-
- def replace(obj):
- if (
- isinstance(obj, elements.BindParameter)
- and obj.type._isnull
- ):
- obj = obj._clone()
- obj.type = column.type
- return obj
- elif (
- isinstance(obj, elements.ColumnClause)
- and obj.table is on_duplicate.inserted_alias
- ):
- if requires_mysql8_alias:
- column_literal_clause = (
- f"{_on_dup_alias_name}."
- f"{self.preparer.quote(obj.name)}"
- )
- else:
- column_literal_clause = (
- f"VALUES({self.preparer.quote(obj.name)})"
- )
- return literal_column(column_literal_clause)
- else:
- # element is not replaced
- return None
-
- val = visitors.replacement_traverse(val, {}, replace)
- value_text = self.process(val.self_group(), use_schema=False)
-
- 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}
- if non_matching:
- util.warn(
- "Additional column names not matching "
- "any column keys in table '%s': %s"
- % (
- self.statement.table.name,
- (", ".join("'%s'" % c for c in non_matching)),
- )
- )
-
- if requires_mysql8_alias:
- return (
- f"AS {_on_dup_alias_name} "
- f"ON DUPLICATE KEY UPDATE {', '.join(clauses)}"
- )
- else:
- return f"ON DUPLICATE KEY UPDATE {', '.join(clauses)}"
-
- def visit_concat_op_expression_clauselist(
- self, clauselist, operator, **kw
- ):
- return "concat(%s)" % (
- ", ".join(self.process(elem, **kw) for elem in clauselist.clauses)
- )
-
- def visit_concat_op_binary(self, binary, operator, **kw):
- return "concat(%s, %s)" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
-
- _match_valid_flag_combinations = frozenset(
- (
- # (boolean_mode, natural_language, query_expansion)
- (False, False, False),
- (True, False, False),
- (False, True, False),
- (False, False, True),
- (False, True, True),
- )
- )
-
- _match_flag_expressions = (
- "IN BOOLEAN MODE",
- "IN NATURAL LANGUAGE MODE",
- "WITH QUERY EXPANSION",
- )
-
- def visit_mysql_match(self, element, **kw):
- return self.visit_match_op_binary(element, element.operator, **kw)
-
- def visit_match_op_binary(self, binary, operator, **kw):
- """
- Note that `mysql_boolean_mode` is enabled by default because of
- backward compatibility
- """
-
- modifiers = binary.modifiers
-
- boolean_mode = modifiers.get("mysql_boolean_mode", True)
- natural_language = modifiers.get("mysql_natural_language", False)
- query_expansion = modifiers.get("mysql_query_expansion", False)
-
- flag_combination = (boolean_mode, natural_language, query_expansion)
-
- if flag_combination not in self._match_valid_flag_combinations:
- flags = (
- "in_boolean_mode=%s" % boolean_mode,
- "in_natural_language_mode=%s" % natural_language,
- "with_query_expansion=%s" % query_expansion,
- )
-
- flags = ", ".join(flags)
-
- raise exc.CompileError("Invalid MySQL match flags: %s" % flags)
-
- match_clause = binary.left
- match_clause = self.process(match_clause, **kw)
- against_clause = self.process(binary.right, **kw)
-
- if any(flag_combination):
- flag_expressions = compress(
- self._match_flag_expressions,
- flag_combination,
- )
-
- against_clause = [against_clause]
- against_clause.extend(flag_expressions)
-
- against_clause = " ".join(against_clause)
-
- return "MATCH (%s) AGAINST (%s)" % (match_clause, against_clause)
-
- def get_from_hint_text(self, table, text):
- return text
-
- def visit_typeclause(self, typeclause, type_=None, **kw):
- if type_ is None:
- type_ = typeclause.type.dialect_impl(self.dialect)
- if isinstance(type_, sqltypes.TypeDecorator):
- return self.visit_typeclause(typeclause, type_.impl, **kw)
- elif isinstance(type_, sqltypes.Integer):
- if getattr(type_, "unsigned", False):
- return "UNSIGNED INTEGER"
- else:
- return "SIGNED INTEGER"
- elif isinstance(type_, sqltypes.TIMESTAMP):
- return "DATETIME"
- elif isinstance(
- type_,
- (
- sqltypes.DECIMAL,
- sqltypes.DateTime,
- sqltypes.Date,
- sqltypes.Time,
- ),
- ):
- return self.dialect.type_compiler_instance.process(type_)
- elif isinstance(type_, sqltypes.String) and not isinstance(
- type_, (ENUM, SET)
- ):
- adapted = CHAR._adapt_string_for_cast(type_)
- return self.dialect.type_compiler_instance.process(adapted)
- elif isinstance(type_, sqltypes._Binary):
- return "BINARY"
- elif isinstance(type_, sqltypes.JSON):
- return "JSON"
- elif isinstance(type_, sqltypes.NUMERIC):
- return self.dialect.type_compiler_instance.process(type_).replace(
- "NUMERIC", "DECIMAL"
- )
- elif (
- isinstance(type_, sqltypes.Float)
- and self.dialect._support_float_cast
- ):
- return self.dialect.type_compiler_instance.process(type_)
- else:
- return None
-
- def visit_cast(self, cast, **kw):
- type_ = self.process(cast.typeclause)
- if type_ is None:
- util.warn(
- "Datatype %s does not support CAST on MySQL/MariaDb; "
- "the CAST will be skipped."
- % self.dialect.type_compiler_instance.process(
- cast.typeclause.type
- )
- )
- return self.process(cast.clause.self_group(), **kw)
-
- return "CAST(%s AS %s)" % (self.process(cast.clause, **kw), type_)
-
- def render_literal_value(self, value, type_):
- value = super().render_literal_value(value, type_)
- if self.dialect._backslash_escapes:
- value = value.replace("\\", "\\\\")
- return value
-
- # override native_boolean=False behavior here, as
- # MySQL still supports native boolean
- def visit_true(self, element, **kw):
- return "true"
-
- def visit_false(self, element, **kw):
- return "false"
-
- def get_select_precolumns(self, select, **kw):
- """Add special MySQL keywords in place of DISTINCT.
-
- .. deprecated:: 1.4 This usage is deprecated.
- :meth:`_expression.Select.prefix_with` should be used for special
- keywords at the start of a SELECT.
-
- """
- if isinstance(select._distinct, str):
- util.warn_deprecated(
- "Sending string values for 'distinct' is deprecated in the "
- "MySQL dialect and will be removed in a future release. "
- "Please use :meth:`.Select.prefix_with` for special keywords "
- "at the start of a SELECT statement",
- version="1.4",
- )
- return select._distinct.upper() + " "
-
- return super().get_select_precolumns(select, **kw)
-
- def visit_join(self, join, asfrom=False, from_linter=None, **kwargs):
- if from_linter:
- from_linter.edges.add((join.left, join.right))
-
- if join.full:
- join_type = " FULL OUTER JOIN "
- elif join.isouter:
- join_type = " LEFT OUTER JOIN "
- else:
- join_type = " INNER JOIN "
-
- return "".join(
- (
- self.process(
- join.left, asfrom=True, from_linter=from_linter, **kwargs
- ),
- join_type,
- self.process(
- join.right, asfrom=True, from_linter=from_linter, **kwargs
- ),
- " ON ",
- self.process(join.onclause, from_linter=from_linter, **kwargs),
- )
- )
-
- def for_update_clause(self, select, **kw):
- if select._for_update_arg.read:
- tmp = " LOCK IN SHARE MODE"
- else:
- tmp = " FOR UPDATE"
-
- if select._for_update_arg.of and self.dialect.supports_for_update_of:
- tables = util.OrderedSet()
- for c in select._for_update_arg.of:
- tables.update(sql_util.surface_selectables_only(c))
-
- tmp += " OF " + ", ".join(
- self.process(table, ashint=True, use_schema=False, **kw)
- for table in tables
- )
-
- if select._for_update_arg.nowait:
- tmp += " NOWAIT"
-
- if select._for_update_arg.skip_locked:
- tmp += " SKIP LOCKED"
-
- return tmp
-
- def limit_clause(self, select, **kw):
- # MySQL supports:
- # LIMIT <limit>
- # LIMIT <offset>, <limit>
- # and in server versions > 3.3:
- # LIMIT <limit> OFFSET <offset>
- # The latter is more readable for offsets but we're stuck with the
- # former until we can refine dialects by server revision.
-
- limit_clause, offset_clause = (
- select._limit_clause,
- select._offset_clause,
- )
-
- if limit_clause is None and offset_clause is None:
- return ""
- elif offset_clause is not None:
- # As suggested by the MySQL docs, need to apply an
- # artificial limit if one wasn't provided
- # https://dev.mysql.com/doc/refman/5.0/en/select.html
- if limit_clause is None:
- # TODO: remove ??
- # hardwire the upper limit. Currently
- # needed consistent with the usage of the upper
- # bound as part of MySQL's "syntax" for OFFSET with
- # no LIMIT.
- return " \n LIMIT %s, %s" % (
- self.process(offset_clause, **kw),
- "18446744073709551615",
- )
- else:
- return " \n LIMIT %s, %s" % (
- self.process(offset_clause, **kw),
- self.process(limit_clause, **kw),
- )
- else:
- # No offset provided, so just use the limit
- return " \n LIMIT %s" % (self.process(limit_clause, **kw),)
-
- def update_limit_clause(self, update_stmt):
- limit = update_stmt.kwargs.get("%s_limit" % self.dialect.name, None)
- if limit:
- return "LIMIT %s" % limit
- else:
- return None
-
- def update_tables_clause(self, update_stmt, from_table, extra_froms, **kw):
- kw["asfrom"] = True
- return ", ".join(
- t._compiler_dispatch(self, **kw)
- for t in [from_table] + list(extra_froms)
- )
-
- def update_from_clause(
- self, update_stmt, from_table, extra_froms, from_hints, **kw
- ):
- return None
-
- def delete_table_clause(self, delete_stmt, from_table, extra_froms, **kw):
- """If we have extra froms make sure we render any alias as hint."""
- ashint = False
- if extra_froms:
- ashint = True
- return from_table._compiler_dispatch(
- self, asfrom=True, iscrud=True, ashint=ashint, **kw
- )
-
- def delete_extra_from_clause(
- self, delete_stmt, from_table, extra_froms, from_hints, **kw
- ):
- """Render the DELETE .. USING clause specific to MySQL."""
- kw["asfrom"] = True
- return "USING " + ", ".join(
- t._compiler_dispatch(self, fromhints=from_hints, **kw)
- for t in [from_table] + extra_froms
- )
-
- def visit_empty_set_expr(self, element_types, **kw):
- return (
- "SELECT %(outer)s FROM (SELECT %(inner)s) "
- "as _empty_set WHERE 1!=1"
- % {
- "inner": ", ".join(
- "1 AS _in_%s" % idx
- for idx, type_ in enumerate(element_types)
- ),
- "outer": ", ".join(
- "_in_%s" % idx for idx, type_ in enumerate(element_types)
- ),
- }
- )
-
- def visit_is_distinct_from_binary(self, binary, operator, **kw):
- return "NOT (%s <=> %s)" % (
- self.process(binary.left),
- self.process(binary.right),
- )
-
- def visit_is_not_distinct_from_binary(self, binary, operator, **kw):
- return "%s <=> %s" % (
- self.process(binary.left),
- self.process(binary.right),
- )
-
- def _mariadb_regexp_flags(self, flags, pattern, **kw):
- return "CONCAT('(?', %s, ')', %s)" % (
- self.render_literal_value(flags, sqltypes.STRINGTYPE),
- self.process(pattern, **kw),
- )
-
- def _regexp_match(self, op_string, binary, operator, **kw):
- flags = binary.modifiers["flags"]
- if flags is None:
- return self._generate_generic_binary(binary, op_string, **kw)
- elif self.dialect.is_mariadb:
- return "%s%s%s" % (
- self.process(binary.left, **kw),
- op_string,
- self._mariadb_regexp_flags(flags, binary.right),
- )
- else:
- text = "REGEXP_LIKE(%s, %s, %s)" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- self.render_literal_value(flags, sqltypes.STRINGTYPE),
- )
- if op_string == " NOT REGEXP ":
- return "NOT %s" % text
- else:
- return text
-
- def visit_regexp_match_op_binary(self, binary, operator, **kw):
- return self._regexp_match(" REGEXP ", binary, operator, **kw)
-
- def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
- return self._regexp_match(" NOT REGEXP ", binary, operator, **kw)
-
- def visit_regexp_replace_op_binary(self, binary, operator, **kw):
- flags = binary.modifiers["flags"]
- if flags is None:
- return "REGEXP_REPLACE(%s, %s)" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- )
- elif self.dialect.is_mariadb:
- return "REGEXP_REPLACE(%s, %s, %s)" % (
- self.process(binary.left, **kw),
- self._mariadb_regexp_flags(flags, binary.right.clauses[0]),
- self.process(binary.right.clauses[1], **kw),
- )
- else:
- return "REGEXP_REPLACE(%s, %s, %s)" % (
- self.process(binary.left, **kw),
- self.process(binary.right, **kw),
- self.render_literal_value(flags, sqltypes.STRINGTYPE),
- )
-
-
-class MySQLDDLCompiler(compiler.DDLCompiler):
- def get_column_specification(self, column, **kw):
- """Builds column DDL."""
- if (
- self.dialect.is_mariadb is True
- and column.computed is not None
- and column._user_defined_nullable is SchemaConst.NULL_UNSPECIFIED
- ):
- column.nullable = True
- colspec = [
- self.preparer.format_column(column),
- self.dialect.type_compiler_instance.process(
- column.type, type_expression=column
- ),
- ]
-
- if column.computed is not None:
- colspec.append(self.process(column.computed))
-
- is_timestamp = isinstance(
- column.type._unwrapped_dialect_impl(self.dialect),
- sqltypes.TIMESTAMP,
- )
-
- if not column.nullable:
- colspec.append("NOT NULL")
-
- # see: https://docs.sqlalchemy.org/en/latest/dialects/mysql.html#mysql_timestamp_null # noqa
- elif column.nullable and is_timestamp:
- colspec.append("NULL")
-
- comment = column.comment
- if comment is not None:
- literal = self.sql_compiler.render_literal_value(
- comment, sqltypes.String()
- )
- colspec.append("COMMENT " + literal)
-
- if (
- column.table is not None
- and column is column.table._autoincrement_column
- and (
- column.server_default is None
- or isinstance(column.server_default, sa_schema.Identity)
- )
- and not (
- self.dialect.supports_sequences
- and isinstance(column.default, sa_schema.Sequence)
- and not column.default.optional
- )
- ):
- colspec.append("AUTO_INCREMENT")
- else:
- default = self.get_column_default_string(column)
- if default is not None:
- colspec.append("DEFAULT " + default)
- return " ".join(colspec)
-
- def post_create_table(self, table):
- """Build table-level CREATE options like ENGINE and COLLATE."""
-
- table_opts = []
-
- opts = {
- k[len(self.dialect.name) + 1 :].upper(): v
- for k, v in table.kwargs.items()
- if k.startswith("%s_" % self.dialect.name)
- }
-
- if table.comment is not None:
- opts["COMMENT"] = table.comment
-
- partition_options = [
- "PARTITION_BY",
- "PARTITIONS",
- "SUBPARTITIONS",
- "SUBPARTITION_BY",
- ]
-
- nonpart_options = set(opts).difference(partition_options)
- part_options = set(opts).intersection(partition_options)
-
- for opt in topological.sort(
- [
- ("DEFAULT_CHARSET", "COLLATE"),
- ("DEFAULT_CHARACTER_SET", "COLLATE"),
- ("CHARSET", "COLLATE"),
- ("CHARACTER_SET", "COLLATE"),
- ],
- nonpart_options,
- ):
- arg = opts[opt]
- if opt in _reflection._options_of_type_string:
- arg = self.sql_compiler.render_literal_value(
- arg, sqltypes.String()
- )
-
- if opt in (
- "DATA_DIRECTORY",
- "INDEX_DIRECTORY",
- "DEFAULT_CHARACTER_SET",
- "CHARACTER_SET",
- "DEFAULT_CHARSET",
- "DEFAULT_COLLATE",
- ):
- opt = opt.replace("_", " ")
-
- joiner = "="
- if opt in (
- "TABLESPACE",
- "DEFAULT CHARACTER SET",
- "CHARACTER SET",
- "COLLATE",
- ):
- joiner = " "
-
- table_opts.append(joiner.join((opt, arg)))
-
- for opt in topological.sort(
- [
- ("PARTITION_BY", "PARTITIONS"),
- ("PARTITION_BY", "SUBPARTITION_BY"),
- ("PARTITION_BY", "SUBPARTITIONS"),
- ("PARTITIONS", "SUBPARTITIONS"),
- ("PARTITIONS", "SUBPARTITION_BY"),
- ("SUBPARTITION_BY", "SUBPARTITIONS"),
- ],
- part_options,
- ):
- arg = opts[opt]
- if opt in _reflection._options_of_type_string:
- arg = self.sql_compiler.render_literal_value(
- arg, sqltypes.String()
- )
-
- opt = opt.replace("_", " ")
- joiner = " "
-
- table_opts.append(joiner.join((opt, arg)))
-
- return " ".join(table_opts)
-
- def visit_create_index(self, create, **kw):
- index = create.element
- self._verify_index_table(index)
- preparer = self.preparer
- table = preparer.format_table(index.table)
-
- columns = [
- self.sql_compiler.process(
- (
- elements.Grouping(expr)
- if (
- isinstance(expr, elements.BinaryExpression)
- or (
- isinstance(expr, elements.UnaryExpression)
- and expr.modifier
- not in (operators.desc_op, operators.asc_op)
- )
- or isinstance(expr, functions.FunctionElement)
- )
- else expr
- ),
- include_table=False,
- literal_binds=True,
- )
- for expr in index.expressions
- ]
-
- name = self._prepared_index_name(index)
-
- text = "CREATE "
- if index.unique:
- text += "UNIQUE "
-
- index_prefix = index.kwargs.get("%s_prefix" % self.dialect.name, None)
- if index_prefix:
- text += index_prefix + " "
-
- text += "INDEX "
- if create.if_not_exists:
- text += "IF NOT EXISTS "
- text += "%s ON %s " % (name, table)
-
- length = index.dialect_options[self.dialect.name]["length"]
- if length is not None:
- if isinstance(length, dict):
- # length value can be a (column_name --> integer value)
- # mapping specifying the prefix length for each column of the
- # index
- columns = ", ".join(
- (
- "%s(%d)" % (expr, length[col.name])
- if col.name in length
- else (
- "%s(%d)" % (expr, length[expr])
- if expr in length
- else "%s" % expr
- )
- )
- for col, expr in zip(index.expressions, columns)
- )
- else:
- # or can be an integer value specifying the same
- # prefix length for all columns of the index
- columns = ", ".join(
- "%s(%d)" % (col, length) for col in columns
- )
- else:
- columns = ", ".join(columns)
- text += "(%s)" % columns
-
- parser = index.dialect_options["mysql"]["with_parser"]
- if parser is not None:
- text += " WITH PARSER %s" % (parser,)
-
- using = index.dialect_options["mysql"]["using"]
- if using is not None:
- text += " USING %s" % (preparer.quote(using))
-
- return text
-
- def visit_primary_key_constraint(self, constraint, **kw):
- text = super().visit_primary_key_constraint(constraint)
- using = constraint.dialect_options["mysql"]["using"]
- if using:
- text += " USING %s" % (self.preparer.quote(using))
- return text
-
- def visit_drop_index(self, drop, **kw):
- index = drop.element
- text = "\nDROP INDEX "
- if drop.if_exists:
- text += "IF EXISTS "
-
- return text + "%s ON %s" % (
- self._prepared_index_name(index, include_schema=False),
- self.preparer.format_table(index.table),
- )
-
- def visit_drop_constraint(self, drop, **kw):
- constraint = drop.element
- if isinstance(constraint, sa_schema.ForeignKeyConstraint):
- qual = "FOREIGN KEY "
- const = self.preparer.format_constraint(constraint)
- elif isinstance(constraint, sa_schema.PrimaryKeyConstraint):
- qual = "PRIMARY KEY "
- const = ""
- elif isinstance(constraint, sa_schema.UniqueConstraint):
- qual = "INDEX "
- const = self.preparer.format_constraint(constraint)
- elif isinstance(constraint, sa_schema.CheckConstraint):
- if self.dialect.is_mariadb:
- qual = "CONSTRAINT "
- else:
- qual = "CHECK "
- const = self.preparer.format_constraint(constraint)
- else:
- qual = ""
- const = self.preparer.format_constraint(constraint)
- return "ALTER TABLE %s DROP %s%s" % (
- self.preparer.format_table(constraint.table),
- qual,
- const,
- )
-
- def define_constraint_match(self, constraint):
- if constraint.match is not None:
- raise exc.CompileError(
- "MySQL ignores the 'MATCH' keyword while at the same time "
- "causes ON UPDATE/ON DELETE clauses to be ignored."
- )
- return ""
-
- def visit_set_table_comment(self, create, **kw):
- return "ALTER TABLE %s COMMENT %s" % (
- self.preparer.format_table(create.element),
- self.sql_compiler.render_literal_value(
- create.element.comment, sqltypes.String()
- ),
- )
-
- def visit_drop_table_comment(self, create, **kw):
- return "ALTER TABLE %s COMMENT ''" % (
- self.preparer.format_table(create.element)
- )
-
- def visit_set_column_comment(self, create, **kw):
- return "ALTER TABLE %s CHANGE %s %s" % (
- self.preparer.format_table(create.element.table),
- self.preparer.format_column(create.element),
- self.get_column_specification(create.element),
- )
-
-
-class MySQLTypeCompiler(compiler.GenericTypeCompiler):
- def _extend_numeric(self, type_, spec):
- "Extend a numeric-type declaration with MySQL specific extensions."
-
- if not self._mysql_type(type_):
- return spec
-
- if type_.unsigned:
- spec += " UNSIGNED"
- if type_.zerofill:
- spec += " ZEROFILL"
- return spec
-
- def _extend_string(self, type_, defaults, spec):
- """Extend a string-type declaration with standard SQL CHARACTER SET /
- COLLATE annotations and MySQL specific extensions.
-
- """
-
- def attr(name):
- return getattr(type_, name, defaults.get(name))
-
- if attr("charset"):
- charset = "CHARACTER SET %s" % attr("charset")
- elif attr("ascii"):
- charset = "ASCII"
- elif attr("unicode"):
- charset = "UNICODE"
- else:
- charset = None
-
- if attr("collation"):
- collation = "COLLATE %s" % type_.collation
- elif attr("binary"):
- collation = "BINARY"
- else:
- collation = None
-
- if attr("national"):
- # NATIONAL (aka NCHAR/NVARCHAR) trumps charsets.
- return " ".join(
- [c for c in ("NATIONAL", spec, collation) if c is not None]
- )
- return " ".join(
- [c for c in (spec, charset, collation) if c is not None]
- )
-
- def _mysql_type(self, type_):
- return isinstance(type_, (_StringType, _NumericType))
-
- def visit_NUMERIC(self, type_, **kw):
- if type_.precision is None:
- return self._extend_numeric(type_, "NUMERIC")
- elif type_.scale is None:
- return self._extend_numeric(
- type_,
- "NUMERIC(%(precision)s)" % {"precision": type_.precision},
- )
- else:
- return self._extend_numeric(
- type_,
- "NUMERIC(%(precision)s, %(scale)s)"
- % {"precision": type_.precision, "scale": type_.scale},
- )
-
- def visit_DECIMAL(self, type_, **kw):
- if type_.precision is None:
- return self._extend_numeric(type_, "DECIMAL")
- elif type_.scale is None:
- return self._extend_numeric(
- type_,
- "DECIMAL(%(precision)s)" % {"precision": type_.precision},
- )
- else:
- return self._extend_numeric(
- type_,
- "DECIMAL(%(precision)s, %(scale)s)"
- % {"precision": type_.precision, "scale": type_.scale},
- )
-
- def visit_DOUBLE(self, type_, **kw):
- if type_.precision is not None and type_.scale is not None:
- return self._extend_numeric(
- type_,
- "DOUBLE(%(precision)s, %(scale)s)"
- % {"precision": type_.precision, "scale": type_.scale},
- )
- else:
- return self._extend_numeric(type_, "DOUBLE")
-
- def visit_REAL(self, type_, **kw):
- if type_.precision is not None and type_.scale is not None:
- return self._extend_numeric(
- type_,
- "REAL(%(precision)s, %(scale)s)"
- % {"precision": type_.precision, "scale": type_.scale},
- )
- else:
- return self._extend_numeric(type_, "REAL")
-
- def visit_FLOAT(self, type_, **kw):
- if (
- self._mysql_type(type_)
- and type_.scale is not None
- and type_.precision is not None
- ):
- return self._extend_numeric(
- type_, "FLOAT(%s, %s)" % (type_.precision, type_.scale)
- )
- elif type_.precision is not None:
- return self._extend_numeric(
- type_, "FLOAT(%s)" % (type_.precision,)
- )
- else:
- return self._extend_numeric(type_, "FLOAT")
-
- def visit_INTEGER(self, type_, **kw):
- if self._mysql_type(type_) and type_.display_width is not None:
- return self._extend_numeric(
- type_,
- "INTEGER(%(display_width)s)"
- % {"display_width": type_.display_width},
- )
- else:
- return self._extend_numeric(type_, "INTEGER")
-
- def visit_BIGINT(self, type_, **kw):
- if self._mysql_type(type_) and type_.display_width is not None:
- return self._extend_numeric(
- type_,
- "BIGINT(%(display_width)s)"
- % {"display_width": type_.display_width},
- )
- else:
- return self._extend_numeric(type_, "BIGINT")
-
- def visit_MEDIUMINT(self, type_, **kw):
- if self._mysql_type(type_) and type_.display_width is not None:
- return self._extend_numeric(
- type_,
- "MEDIUMINT(%(display_width)s)"
- % {"display_width": type_.display_width},
- )
- else:
- return self._extend_numeric(type_, "MEDIUMINT")
-
- def visit_TINYINT(self, type_, **kw):
- if self._mysql_type(type_) and type_.display_width is not None:
- return self._extend_numeric(
- type_, "TINYINT(%s)" % type_.display_width
- )
- else:
- return self._extend_numeric(type_, "TINYINT")
-
- def visit_SMALLINT(self, type_, **kw):
- if self._mysql_type(type_) and type_.display_width is not None:
- return self._extend_numeric(
- type_,
- "SMALLINT(%(display_width)s)"
- % {"display_width": type_.display_width},
- )
- else:
- return self._extend_numeric(type_, "SMALLINT")
-
- def visit_BIT(self, type_, **kw):
- if type_.length is not None:
- return "BIT(%s)" % type_.length
- else:
- return "BIT"
-
- def visit_DATETIME(self, type_, **kw):
- if getattr(type_, "fsp", None):
- return "DATETIME(%d)" % type_.fsp
- else:
- return "DATETIME"
-
- def visit_DATE(self, type_, **kw):
- return "DATE"
-
- def visit_TIME(self, type_, **kw):
- if getattr(type_, "fsp", None):
- return "TIME(%d)" % type_.fsp
- else:
- return "TIME"
-
- def visit_TIMESTAMP(self, type_, **kw):
- if getattr(type_, "fsp", None):
- return "TIMESTAMP(%d)" % type_.fsp
- else:
- return "TIMESTAMP"
-
- def visit_YEAR(self, type_, **kw):
- if type_.display_width is None:
- return "YEAR"
- else:
- return "YEAR(%s)" % type_.display_width
-
- def visit_TEXT(self, type_, **kw):
- if type_.length is not None:
- return self._extend_string(type_, {}, "TEXT(%d)" % type_.length)
- else:
- return self._extend_string(type_, {}, "TEXT")
-
- def visit_TINYTEXT(self, type_, **kw):
- return self._extend_string(type_, {}, "TINYTEXT")
-
- def visit_MEDIUMTEXT(self, type_, **kw):
- return self._extend_string(type_, {}, "MEDIUMTEXT")
-
- def visit_LONGTEXT(self, type_, **kw):
- return self._extend_string(type_, {}, "LONGTEXT")
-
- def visit_VARCHAR(self, type_, **kw):
- if type_.length is not None:
- return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
- else:
- raise exc.CompileError(
- "VARCHAR requires a length on dialect %s" % self.dialect.name
- )
-
- def visit_CHAR(self, type_, **kw):
- if type_.length is not None:
- return self._extend_string(
- type_, {}, "CHAR(%(length)s)" % {"length": type_.length}
- )
- else:
- return self._extend_string(type_, {}, "CHAR")
-
- def visit_NVARCHAR(self, type_, **kw):
- # We'll actually generate the equiv. "NATIONAL VARCHAR" instead
- # of "NVARCHAR".
- if type_.length is not None:
- return self._extend_string(
- type_,
- {"national": True},
- "VARCHAR(%(length)s)" % {"length": type_.length},
- )
- else:
- raise exc.CompileError(
- "NVARCHAR requires a length on dialect %s" % self.dialect.name
- )
-
- def visit_NCHAR(self, type_, **kw):
- # We'll actually generate the equiv.
- # "NATIONAL CHAR" instead of "NCHAR".
- if type_.length is not None:
- return self._extend_string(
- type_,
- {"national": True},
- "CHAR(%(length)s)" % {"length": type_.length},
- )
- else:
- return self._extend_string(type_, {"national": True}, "CHAR")
-
- def visit_UUID(self, type_, **kw):
- return "UUID"
-
- def visit_VARBINARY(self, type_, **kw):
- return "VARBINARY(%d)" % type_.length
-
- def visit_JSON(self, type_, **kw):
- return "JSON"
-
- def visit_large_binary(self, type_, **kw):
- return self.visit_BLOB(type_)
-
- def visit_enum(self, type_, **kw):
- if not type_.native_enum:
- return super().visit_enum(type_)
- else:
- return self._visit_enumerated_values("ENUM", type_, type_.enums)
-
- def visit_BLOB(self, type_, **kw):
- if type_.length is not None:
- return "BLOB(%d)" % type_.length
- else:
- return "BLOB"
-
- def visit_TINYBLOB(self, type_, **kw):
- return "TINYBLOB"
-
- def visit_MEDIUMBLOB(self, type_, **kw):
- return "MEDIUMBLOB"
-
- def visit_LONGBLOB(self, type_, **kw):
- return "LONGBLOB"
-
- def _visit_enumerated_values(self, name, type_, enumerated_values):
- quoted_enums = []
- for e in enumerated_values:
- quoted_enums.append("'%s'" % e.replace("'", "''"))
- return self._extend_string(
- type_, {}, "%s(%s)" % (name, ",".join(quoted_enums))
- )
-
- def visit_ENUM(self, type_, **kw):
- return self._visit_enumerated_values("ENUM", type_, type_.enums)
-
- def visit_SET(self, type_, **kw):
- return self._visit_enumerated_values("SET", type_, type_.values)
-
- def visit_BOOLEAN(self, type_, **kw):
- return "BOOL"
-
-
-class MySQLIdentifierPreparer(compiler.IdentifierPreparer):
- reserved_words = RESERVED_WORDS_MYSQL
-
- def __init__(self, dialect, server_ansiquotes=False, **kw):
- if not server_ansiquotes:
- quote = "`"
- else:
- quote = '"'
-
- super().__init__(dialect, initial_quote=quote, escape_quote=quote)
-
- def _quote_free_identifiers(self, *ids):
- """Unilaterally identifier-quote any number of strings."""
-
- return tuple([self.quote_identifier(i) for i in ids if i is not None])
-
-
-class MariaDBIdentifierPreparer(MySQLIdentifierPreparer):
- reserved_words = RESERVED_WORDS_MARIADB
-
-
-@log.class_logger
-class MySQLDialect(default.DefaultDialect):
- """Details of the MySQL dialect.
- Not used directly in application code.
- """
-
- name = "mysql"
- supports_statement_cache = True
-
- supports_alter = True
-
- # MySQL has no true "boolean" type; we
- # allow for the "true" and "false" keywords, however
- supports_native_boolean = False
-
- # identifiers are 64, however aliases can be 255...
- max_identifier_length = 255
- max_index_name_length = 64
- max_constraint_name_length = 64
-
- div_is_floordiv = False
-
- supports_native_enum = True
-
- returns_native_bytes = True
-
- supports_sequences = False # default for MySQL ...
- # ... may be updated to True for MariaDB 10.3+ in initialize()
-
- sequences_optional = False
-
- supports_for_update_of = False # default for MySQL ...
- # ... may be updated to True for MySQL 8+ in initialize()
-
- _requires_alias_for_on_duplicate_key = False # Only available ...
- # ... in MySQL 8+
-
- # MySQL doesn't support "DEFAULT VALUES" but *does* support
- # "VALUES (DEFAULT)"
- supports_default_values = False
- supports_default_metavalue = True
-
- use_insertmanyvalues: bool = True
- insertmanyvalues_implicit_sentinel = (
- InsertmanyvaluesSentinelOpts.ANY_AUTOINCREMENT
- )
-
- supports_sane_rowcount = True
- supports_sane_multi_rowcount = False
- supports_multivalues_insert = True
- insert_null_pk_still_autoincrements = True
-
- supports_comments = True
- inline_comments = True
- default_paramstyle = "format"
- colspecs = colspecs
-
- cte_follows_insert = True
-
- statement_compiler = MySQLCompiler
- ddl_compiler = MySQLDDLCompiler
- type_compiler_cls = MySQLTypeCompiler
- ischema_names = ischema_names
- preparer = MySQLIdentifierPreparer
-
- is_mariadb = False
- _mariadb_normalized_version_info = None
-
- # default SQL compilation settings -
- # these are modified upon initialize(),
- # i.e. first connect
- _backslash_escapes = True
- _server_ansiquotes = False
-
- construct_arguments = [
- (sa_schema.Table, {"*": None}),
- (sql.Update, {"limit": None}),
- (sa_schema.PrimaryKeyConstraint, {"using": None}),
- (
- sa_schema.Index,
- {
- "using": None,
- "length": None,
- "prefix": None,
- "with_parser": None,
- },
- ),
- ]
-
- def __init__(
- self,
- json_serializer=None,
- json_deserializer=None,
- is_mariadb=None,
- **kwargs,
- ):
- kwargs.pop("use_ansiquotes", None) # legacy
- default.DefaultDialect.__init__(self, **kwargs)
- self._json_serializer = json_serializer
- self._json_deserializer = json_deserializer
- self._set_mariadb(is_mariadb, None)
-
- def get_isolation_level_values(self, dbapi_conn):
- return (
- "SERIALIZABLE",
- "READ UNCOMMITTED",
- "READ COMMITTED",
- "REPEATABLE READ",
- )
-
- def set_isolation_level(self, dbapi_connection, level):
- cursor = dbapi_connection.cursor()
- cursor.execute(f"SET SESSION TRANSACTION ISOLATION LEVEL {level}")
- cursor.execute("COMMIT")
- cursor.close()
-
- def get_isolation_level(self, dbapi_connection):
- cursor = dbapi_connection.cursor()
- if self._is_mysql and self.server_version_info >= (5, 7, 20):
- cursor.execute("SELECT @@transaction_isolation")
- else:
- cursor.execute("SELECT @@tx_isolation")
- row = cursor.fetchone()
- if row is None:
- util.warn(
- "Could not retrieve transaction isolation level for MySQL "
- "connection."
- )
- raise NotImplementedError()
- val = row[0]
- cursor.close()
- if isinstance(val, bytes):
- val = val.decode()
- return val.upper().replace("-", " ")
-
- @classmethod
- def _is_mariadb_from_url(cls, url):
- dbapi = cls.import_dbapi()
- dialect = cls(dbapi=dbapi)
-
- cargs, cparams = dialect.create_connect_args(url)
- conn = dialect.connect(*cargs, **cparams)
- try:
- cursor = conn.cursor()
- cursor.execute("SELECT VERSION() LIKE '%MariaDB%'")
- val = cursor.fetchone()[0]
- except:
- raise
- else:
- return bool(val)
- finally:
- conn.close()
-
- def _get_server_version_info(self, connection):
- # get database server version info explicitly over the wire
- # to avoid proxy servers like MaxScale getting in the
- # way with their own values, see #4205
- dbapi_con = connection.connection
- cursor = dbapi_con.cursor()
- cursor.execute("SELECT VERSION()")
- val = cursor.fetchone()[0]
- cursor.close()
- if isinstance(val, bytes):
- val = val.decode()
-
- return self._parse_server_version(val)
-
- def _parse_server_version(self, val):
- version = []
- is_mariadb = False
-
- r = re.compile(r"[.\-+]")
- tokens = r.split(val)
- for token in tokens:
- parsed_token = re.match(
- r"^(?:(\d+)(?:a|b|c)?|(MariaDB\w*))$", token
- )
- if not parsed_token:
- continue
- elif parsed_token.group(2):
- self._mariadb_normalized_version_info = tuple(version[-3:])
- is_mariadb = True
- else:
- digit = int(parsed_token.group(1))
- version.append(digit)
-
- server_version_info = tuple(version)
-
- self._set_mariadb(
- server_version_info and is_mariadb, server_version_info
- )
-
- if not is_mariadb:
- self._mariadb_normalized_version_info = server_version_info
-
- if server_version_info < (5, 0, 2):
- raise NotImplementedError(
- "the MySQL/MariaDB dialect supports server "
- "version info 5.0.2 and above."
- )
-
- # setting it here to help w the test suite
- self.server_version_info = server_version_info
- return server_version_info
-
- def _set_mariadb(self, is_mariadb, server_version_info):
- if is_mariadb is None:
- return
-
- if not is_mariadb and self.is_mariadb:
- raise exc.InvalidRequestError(
- "MySQL version %s is not a MariaDB variant."
- % (".".join(map(str, server_version_info)),)
- )
- if is_mariadb:
- self.preparer = MariaDBIdentifierPreparer
- # this would have been set by the default dialect already,
- # so set it again
- self.identifier_preparer = self.preparer(self)
-
- # this will be updated on first connect in initialize()
- # if using older mariadb version
- self.delete_returning = True
- self.insert_returning = True
-
- self.is_mariadb = is_mariadb
-
- def do_begin_twophase(self, connection, xid):
- connection.execute(sql.text("XA BEGIN :xid"), dict(xid=xid))
-
- def do_prepare_twophase(self, connection, xid):
- connection.execute(sql.text("XA END :xid"), dict(xid=xid))
- connection.execute(sql.text("XA PREPARE :xid"), dict(xid=xid))
-
- def do_rollback_twophase(
- self, connection, xid, is_prepared=True, recover=False
- ):
- if not is_prepared:
- connection.execute(sql.text("XA END :xid"), dict(xid=xid))
- connection.execute(sql.text("XA ROLLBACK :xid"), dict(xid=xid))
-
- def do_commit_twophase(
- self, connection, xid, is_prepared=True, recover=False
- ):
- if not is_prepared:
- self.do_prepare_twophase(connection, xid)
- connection.execute(sql.text("XA COMMIT :xid"), dict(xid=xid))
-
- def do_recover_twophase(self, connection):
- resultset = connection.exec_driver_sql("XA RECOVER")
- return [row["data"][0 : row["gtrid_length"]] for row in resultset]
-
- def is_disconnect(self, e, connection, cursor):
- if isinstance(
- e,
- (
- self.dbapi.OperationalError,
- self.dbapi.ProgrammingError,
- self.dbapi.InterfaceError,
- ),
- ) and self._extract_error_code(e) in (
- 1927,
- 2006,
- 2013,
- 2014,
- 2045,
- 2055,
- 4031,
- ):
- return True
- elif isinstance(
- e, (self.dbapi.InterfaceError, self.dbapi.InternalError)
- ):
- # if underlying connection is closed,
- # this is the error you get
- return "(0, '')" in str(e)
- else:
- return False
-
- def _compat_fetchall(self, rp, charset=None):
- """Proxy result rows to smooth over MySQL-Python driver
- inconsistencies."""
-
- return [_DecodingRow(row, charset) for row in rp.fetchall()]
-
- def _compat_fetchone(self, rp, charset=None):
- """Proxy a result row to smooth over MySQL-Python driver
- inconsistencies."""
-
- row = rp.fetchone()
- if row:
- return _DecodingRow(row, charset)
- else:
- return None
-
- def _compat_first(self, rp, charset=None):
- """Proxy a result row to smooth over MySQL-Python driver
- inconsistencies."""
-
- row = rp.first()
- if row:
- return _DecodingRow(row, charset)
- else:
- return None
-
- def _extract_error_code(self, exception):
- raise NotImplementedError()
-
- def _get_default_schema_name(self, connection):
- return connection.exec_driver_sql("SELECT DATABASE()").scalar()
-
- @reflection.cache
- def has_table(self, connection, table_name, schema=None, **kw):
- self._ensure_has_table_connection(connection)
-
- if schema is None:
- schema = self.default_schema_name
-
- assert schema is not None
-
- full_name = ".".join(
- self.identifier_preparer._quote_free_identifiers(
- schema, table_name
- )
- )
-
- # DESCRIBE *must* be used because there is no information schema
- # table that returns information on temp tables that is consistently
- # available on MariaDB / MySQL / engine-agnostic etc.
- # therefore we have no choice but to use DESCRIBE and an error catch
- # to detect "False". See issue #9058
-
- try:
- with connection.exec_driver_sql(
- f"DESCRIBE {full_name}",
- execution_options={"skip_user_error_events": True},
- ) as rs:
- return rs.fetchone() is not None
- except exc.DBAPIError as e:
- # https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html # noqa: E501
- # there are a lot of codes that *may* pop up here at some point
- # but we continue to be fairly conservative. We include:
- # 1146: Table '%s.%s' doesn't exist - what every MySQL has emitted
- # for decades
- #
- # mysql 8 suddenly started emitting:
- # 1049: Unknown database '%s' - for nonexistent schema
- #
- # also added:
- # 1051: Unknown table '%s' - not known to emit
- #
- # there's more "doesn't exist" kinds of messages but they are
- # less clear if mysql 8 would suddenly start using one of those
- if self._extract_error_code(e.orig) in (1146, 1049, 1051):
- return False
- raise
-
- @reflection.cache
- def has_sequence(self, connection, sequence_name, schema=None, **kw):
- if not self.supports_sequences:
- self._sequences_not_supported()
- if not schema:
- schema = self.default_schema_name
- # MariaDB implements sequences as a special type of table
- #
- cursor = connection.execute(
- sql.text(
- "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES "
- "WHERE TABLE_TYPE='SEQUENCE' and TABLE_NAME=:name AND "
- "TABLE_SCHEMA=:schema_name"
- ),
- dict(
- name=str(sequence_name),
- schema_name=str(schema),
- ),
- )
- return cursor.first() is not None
-
- def _sequences_not_supported(self):
- raise NotImplementedError(
- "Sequences are supported only by the "
- "MariaDB series 10.3 or greater"
- )
-
- @reflection.cache
- def get_sequence_names(self, connection, schema=None, **kw):
- if not self.supports_sequences:
- self._sequences_not_supported()
- if not schema:
- schema = self.default_schema_name
- # MariaDB implements sequences as a special type of table
- cursor = connection.execute(
- sql.text(
- "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES "
- "WHERE TABLE_TYPE='SEQUENCE' and TABLE_SCHEMA=:schema_name"
- ),
- dict(schema_name=schema),
- )
- return [
- row[0]
- for row in self._compat_fetchall(
- cursor, charset=self._connection_charset
- )
- ]
-
- def initialize(self, connection):
- # this is driver-based, does not need server version info
- # and is fairly critical for even basic SQL operations
- self._connection_charset = self._detect_charset(connection)
-
- # call super().initialize() because we need to have
- # server_version_info set up. in 1.4 under python 2 only this does the
- # "check unicode returns" thing, which is the one area that some
- # SQL gets compiled within initialize() currently
- default.DefaultDialect.initialize(self, connection)
-
- self._detect_sql_mode(connection)
- self._detect_ansiquotes(connection) # depends on sql mode
- self._detect_casing(connection)
- if self._server_ansiquotes:
- # if ansiquotes == True, build a new IdentifierPreparer
- # with the new setting
- self.identifier_preparer = self.preparer(
- self, server_ansiquotes=self._server_ansiquotes
- )
-
- self.supports_sequences = (
- self.is_mariadb and self.server_version_info >= (10, 3)
- )
-
- self.supports_for_update_of = (
- self._is_mysql and self.server_version_info >= (8,)
- )
-
- self._needs_correct_for_88718_96365 = (
- not self.is_mariadb and self.server_version_info >= (8,)
- )
-
- self.delete_returning = (
- self.is_mariadb and self.server_version_info >= (10, 0, 5)
- )
-
- self.insert_returning = (
- self.is_mariadb and self.server_version_info >= (10, 5)
- )
-
- self._requires_alias_for_on_duplicate_key = (
- self._is_mysql and self.server_version_info >= (8, 0, 20)
- )
-
- self._warn_for_known_db_issues()
-
- def _warn_for_known_db_issues(self):
- if self.is_mariadb:
- mdb_version = self._mariadb_normalized_version_info
- if mdb_version > (10, 2) and mdb_version < (10, 2, 9):
- util.warn(
- "MariaDB %r before 10.2.9 has known issues regarding "
- "CHECK constraints, which impact handling of NULL values "
- "with SQLAlchemy's boolean datatype (MDEV-13596). An "
- "additional issue prevents proper migrations of columns "
- "with CHECK constraints (MDEV-11114). Please upgrade to "
- "MariaDB 10.2.9 or greater, or use the MariaDB 10.1 "
- "series, to avoid these issues." % (mdb_version,)
- )
-
- @property
- def _support_float_cast(self):
- if not self.server_version_info:
- return False
- elif self.is_mariadb:
- # ref https://mariadb.com/kb/en/mariadb-1045-release-notes/
- return self.server_version_info >= (10, 4, 5)
- else:
- # 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 _is_mariadb(self):
- return self.is_mariadb
-
- @property
- def _is_mysql(self):
- return not self.is_mariadb
-
- @property
- def _is_mariadb_102(self):
- return self.is_mariadb and self._mariadb_normalized_version_info > (
- 10,
- 2,
- )
-
- @reflection.cache
- def get_schema_names(self, connection, **kw):
- rp = connection.exec_driver_sql("SHOW schemas")
- return [r[0] for r in rp]
-
- @reflection.cache
- def get_table_names(self, connection, schema=None, **kw):
- """Return a Unicode SHOW TABLES from a given schema."""
- if schema is not None:
- current_schema = schema
- else:
- current_schema = self.default_schema_name
-
- charset = self._connection_charset
-
- rp = connection.exec_driver_sql(
- "SHOW FULL TABLES FROM %s"
- % self.identifier_preparer.quote_identifier(current_schema)
- )
-
- return [
- row[0]
- for row in self._compat_fetchall(rp, charset=charset)
- if row[1] == "BASE TABLE"
- ]
-
- @reflection.cache
- def get_view_names(self, connection, schema=None, **kw):
- if schema is None:
- schema = self.default_schema_name
- charset = self._connection_charset
- rp = connection.exec_driver_sql(
- "SHOW FULL TABLES FROM %s"
- % self.identifier_preparer.quote_identifier(schema)
- )
- return [
- row[0]
- for row in self._compat_fetchall(rp, charset=charset)
- if row[1] in ("VIEW", "SYSTEM VIEW")
- ]
-
- @reflection.cache
- def get_table_options(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
- if parsed_state.table_options:
- return parsed_state.table_options
- else:
- return ReflectionDefaults.table_options()
-
- @reflection.cache
- def get_columns(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
- if parsed_state.columns:
- return parsed_state.columns
- else:
- return ReflectionDefaults.columns()
-
- @reflection.cache
- def get_pk_constraint(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
- for key in parsed_state.keys:
- if key["type"] == "PRIMARY":
- # There can be only one.
- cols = [s[0] for s in key["columns"]]
- return {"constrained_columns": cols, "name": None}
- return ReflectionDefaults.pk_constraint()
-
- @reflection.cache
- def get_foreign_keys(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
- default_schema = None
-
- fkeys = []
-
- for spec in parsed_state.fk_constraints:
- ref_name = spec["table"][-1]
- ref_schema = len(spec["table"]) > 1 and spec["table"][-2] or schema
-
- if not ref_schema:
- if default_schema is None:
- default_schema = connection.dialect.default_schema_name
- if schema == default_schema:
- ref_schema = schema
-
- loc_names = spec["local"]
- ref_names = spec["foreign"]
-
- con_kw = {}
- for opt in ("onupdate", "ondelete"):
- if spec.get(opt, False) not in ("NO ACTION", None):
- con_kw[opt] = spec[opt]
-
- fkey_d = {
- "name": spec["name"],
- "constrained_columns": loc_names,
- "referred_schema": ref_schema,
- "referred_table": ref_name,
- "referred_columns": ref_names,
- "options": con_kw,
- }
- fkeys.append(fkey_d)
-
- if self._needs_correct_for_88718_96365:
- self._correct_for_mysql_bugs_88718_96365(fkeys, connection)
-
- return fkeys if fkeys else ReflectionDefaults.foreign_keys()
-
- def _correct_for_mysql_bugs_88718_96365(self, fkeys, connection):
- # Foreign key is always in lower case (MySQL 8.0)
- # https://bugs.mysql.com/bug.php?id=88718
- # issue #4344 for SQLAlchemy
-
- # table name also for MySQL 8.0
- # https://bugs.mysql.com/bug.php?id=96365
- # issue #4751 for SQLAlchemy
-
- # for lower_case_table_names=2, information_schema.columns
- # preserves the original table/schema casing, but SHOW CREATE
- # TABLE does not. this problem is not in lower_case_table_names=1,
- # but use case-insensitive matching for these two modes in any case.
-
- if self._casing in (1, 2):
-
- def lower(s):
- return s.lower()
-
- else:
- # if on case sensitive, there can be two tables referenced
- # with the same name different casing, so we need to use
- # case-sensitive matching.
- def lower(s):
- 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),
- )
-
- # 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
- # case.
- # in casing=2, table name and schema name come back from the
- # information_schema.columns view in the case
- # that was used in CREATE DATABASE and CREATE TABLE, but
- # SHOW CREATE TABLE converts them to *lower case*, therefore
- # not matching. So for this case, case-insensitive lookup
- # is necessary
- d = defaultdict(dict)
- for schema, tname, cname in correct_for_wrong_fk_case:
- d[(lower(schema), lower(tname))]["SCHEMANAME"] = schema
- d[(lower(schema), lower(tname))]["TABLENAME"] = tname
- d[(lower(schema), lower(tname))][cname.lower()] = cname
-
- for fkey in fkeys:
- rec = d[
- (
- lower(fkey["referred_schema"] or default_schema_name),
- lower(fkey["referred_table"]),
- )
- ]
-
- fkey["referred_table"] = rec["TABLENAME"]
- if fkey["referred_schema"] is not None:
- fkey["referred_schema"] = rec["SCHEMANAME"]
-
- fkey["referred_columns"] = [
- rec[col.lower()] for col in fkey["referred_columns"]
- ]
-
- @reflection.cache
- def get_check_constraints(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
-
- cks = [
- {"name": spec["name"], "sqltext": spec["sqltext"]}
- for spec in parsed_state.ck_constraints
- ]
- cks.sort(key=lambda d: d["name"] or "~") # sort None as last
- return cks if cks else ReflectionDefaults.check_constraints()
-
- @reflection.cache
- def get_table_comment(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
- comment = parsed_state.table_options.get(f"{self.name}_comment", None)
- if comment is not None:
- return {"text": comment}
- else:
- return ReflectionDefaults.table_comment()
-
- @reflection.cache
- def get_indexes(self, connection, table_name, schema=None, **kw):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
-
- indexes = []
-
- for spec in parsed_state.keys:
- dialect_options = {}
- unique = False
- flavor = spec["type"]
- if flavor == "PRIMARY":
- continue
- if flavor == "UNIQUE":
- unique = True
- elif flavor in ("FULLTEXT", "SPATIAL"):
- dialect_options["%s_prefix" % self.name] = flavor
- elif flavor is None:
- pass
- else:
- self.logger.info(
- "Converting unknown KEY type %s to a plain KEY", flavor
- )
- pass
-
- if spec["parser"]:
- dialect_options["%s_with_parser" % (self.name)] = spec[
- "parser"
- ]
-
- index_d = {}
-
- index_d["name"] = spec["name"]
- index_d["column_names"] = [s[0] for s in spec["columns"]]
- mysql_length = {
- s[0]: s[1] for s in spec["columns"] if s[1] is not None
- }
- if mysql_length:
- dialect_options["%s_length" % self.name] = mysql_length
-
- index_d["unique"] = unique
- if flavor:
- index_d["type"] = flavor
-
- if dialect_options:
- index_d["dialect_options"] = dialect_options
-
- indexes.append(index_d)
- indexes.sort(key=lambda d: d["name"] or "~") # sort None as last
- return indexes if indexes else ReflectionDefaults.indexes()
-
- @reflection.cache
- def get_unique_constraints(
- self, connection, table_name, schema=None, **kw
- ):
- parsed_state = self._parsed_state_or_create(
- connection, table_name, schema, **kw
- )
-
- ucs = [
- {
- "name": key["name"],
- "column_names": [col[0] for col in key["columns"]],
- "duplicates_index": key["name"],
- }
- for key in parsed_state.keys
- if key["type"] == "UNIQUE"
- ]
- ucs.sort(key=lambda d: d["name"] or "~") # sort None as last
- if ucs:
- return ucs
- else:
- return ReflectionDefaults.unique_constraints()
-
- @reflection.cache
- def get_view_definition(self, connection, view_name, schema=None, **kw):
- charset = self._connection_charset
- full_name = ".".join(
- self.identifier_preparer._quote_free_identifiers(schema, view_name)
- )
- sql = self._show_create_table(
- connection, None, charset, full_name=full_name
- )
- if sql.upper().startswith("CREATE TABLE"):
- # it's a table, not a view
- raise exc.NoSuchTableError(full_name)
- return sql
-
- def _parsed_state_or_create(
- self, connection, table_name, schema=None, **kw
- ):
- return self._setup_parser(
- connection,
- table_name,
- schema,
- info_cache=kw.get("info_cache", None),
- )
-
- @util.memoized_property
- def _tabledef_parser(self):
- """return the MySQLTableDefinitionParser, generate if needed.
-
- The deferred creation ensures that the dialect has
- retrieved server version information first.
-
- """
- preparer = self.identifier_preparer
- return _reflection.MySQLTableDefinitionParser(self, preparer)
-
- @reflection.cache
- def _setup_parser(self, connection, table_name, schema=None, **kw):
- charset = self._connection_charset
- parser = self._tabledef_parser
- full_name = ".".join(
- self.identifier_preparer._quote_free_identifiers(
- schema, table_name
- )
- )
- sql = self._show_create_table(
- connection, None, charset, full_name=full_name
- )
- if parser._check_view(sql):
- # Adapt views to something table-like.
- columns = self._describe_table(
- connection, None, charset, full_name=full_name
- )
- sql = parser._describe_to_create(table_name, columns)
- return parser.parse(sql, charset)
-
- def _fetch_setting(self, connection, setting_name):
- charset = self._connection_charset
-
- if self.server_version_info and self.server_version_info < (5, 6):
- sql = "SHOW VARIABLES LIKE '%s'" % setting_name
- fetch_col = 1
- else:
- sql = "SELECT @@%s" % setting_name
- fetch_col = 0
-
- show_var = connection.exec_driver_sql(sql)
- row = self._compat_first(show_var, charset=charset)
- if not row:
- return None
- else:
- return row[fetch_col]
-
- def _detect_charset(self, connection):
- raise NotImplementedError()
-
- def _detect_casing(self, connection):
- """Sniff out identifier case sensitivity.
-
- Cached per-connection. This value can not change without a server
- restart.
-
- """
- # https://dev.mysql.com/doc/refman/en/identifier-case-sensitivity.html
-
- setting = self._fetch_setting(connection, "lower_case_table_names")
- if setting is None:
- cs = 0
- else:
- # 4.0.15 returns OFF or ON according to [ticket:489]
- # 3.23 doesn't, 4.0.27 doesn't..
- if setting == "OFF":
- cs = 0
- elif setting == "ON":
- cs = 1
- else:
- cs = int(setting)
- self._casing = cs
- return cs
-
- def _detect_collations(self, connection):
- """Pull the active COLLATIONS list from the server.
-
- Cached per-connection.
- """
-
- collations = {}
- charset = self._connection_charset
- rs = connection.exec_driver_sql("SHOW COLLATION")
- for row in self._compat_fetchall(rs, charset):
- collations[row[0]] = row[1]
- return collations
-
- def _detect_sql_mode(self, connection):
- setting = self._fetch_setting(connection, "sql_mode")
-
- if setting is None:
- util.warn(
- "Could not retrieve SQL_MODE; please ensure the "
- "MySQL user has permissions to SHOW VARIABLES"
- )
- self._sql_mode = ""
- else:
- self._sql_mode = setting or ""
-
- def _detect_ansiquotes(self, connection):
- """Detect and adjust for the ANSI_QUOTES sql mode."""
-
- mode = self._sql_mode
- if not mode:
- mode = ""
- elif mode.isdigit():
- mode_no = int(mode)
- mode = (mode_no | 4 == mode_no) and "ANSI_QUOTES" or ""
-
- self._server_ansiquotes = "ANSI_QUOTES" in mode
-
- # as of MySQL 5.0.1
- self._backslash_escapes = "NO_BACKSLASH_ESCAPES" not in mode
-
- def _show_create_table(
- self, connection, table, charset=None, full_name=None
- ):
- """Run SHOW CREATE TABLE for a ``Table``."""
-
- if full_name is None:
- full_name = self.identifier_preparer.format_table(table)
- st = "SHOW CREATE TABLE %s" % full_name
-
- rp = None
- try:
- rp = connection.execution_options(
- skip_user_error_events=True
- ).exec_driver_sql(st)
- except exc.DBAPIError as e:
- if self._extract_error_code(e.orig) == 1146:
- raise exc.NoSuchTableError(full_name) from e
- else:
- raise
- row = self._compat_first(rp, charset=charset)
- if not row:
- raise exc.NoSuchTableError(full_name)
- return row[1].strip()
-
- def _describe_table(self, connection, table, charset=None, full_name=None):
- """Run DESCRIBE for a ``Table`` and return processed rows."""
-
- if full_name is None:
- full_name = self.identifier_preparer.format_table(table)
- st = "DESCRIBE %s" % full_name
-
- rp, rows = None, None
- try:
- try:
- rp = connection.execution_options(
- skip_user_error_events=True
- ).exec_driver_sql(st)
- except exc.DBAPIError as e:
- code = self._extract_error_code(e.orig)
- if code == 1146:
- raise exc.NoSuchTableError(full_name) from e
-
- elif code == 1356:
- raise exc.UnreflectableTableError(
- "Table or view named %s could not be "
- "reflected: %s" % (full_name, e)
- ) from e
-
- else:
- raise
- rows = self._compat_fetchall(rp, charset=charset)
- finally:
- if rp:
- rp.close()
- return rows
-
-
-class _DecodingRow:
- """Return unicode-decoded values based on type inspection.
-
- Smooth over data type issues (esp. with alpha driver versions) and
- normalize strings as Unicode regardless of user-configured driver
- encoding settings.
-
- """
-
- # Some MySQL-python versions can return some columns as
- # sets.Set(['value']) (seriously) but thankfully that doesn't
- # seem to come up in DDL queries.
-
- _encoding_compat = {
- "koi8r": "koi8_r",
- "koi8u": "koi8_u",
- "utf16": "utf-16-be", # MySQL's uft16 is always bigendian
- "utf8mb4": "utf8", # real utf8
- "utf8mb3": "utf8", # real utf8; saw this happen on CI but I cannot
- # reproduce, possibly mariadb10.6 related
- "eucjpms": "ujis",
- }
-
- def __init__(self, rowproxy, charset):
- self.rowproxy = rowproxy
- self.charset = self._encoding_compat.get(charset, charset)
-
- def __getitem__(self, index):
- item = self.rowproxy[index]
- if isinstance(item, _array):
- item = item.tostring()
-
- if self.charset and isinstance(item, bytes):
- return item.decode(self.charset)
- else:
- return item
-
- def __getattr__(self, attr):
- item = getattr(self.rowproxy, attr)
- if isinstance(item, _array):
- item = item.tostring()
- if self.charset and isinstance(item, bytes):
- return item.decode(self.charset)
- else:
- return item
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py
deleted file mode 100644
index f199aa4..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# dialects/mysql/cymysql.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-r"""
-
-.. dialect:: mysql+cymysql
- :name: CyMySQL
- :dbapi: cymysql
- :connectstring: mysql+cymysql://<username>:<password>@<host>/<dbname>[?<options>]
- :url: https://github.com/nakagami/CyMySQL
-
-.. note::
-
- The CyMySQL dialect is **not tested as part of SQLAlchemy's continuous
- integration** and may have unresolved issues. The recommended MySQL
- dialects are mysqlclient and PyMySQL.
-
-""" # noqa
-
-from .base import BIT
-from .base import MySQLDialect
-from .mysqldb import MySQLDialect_mysqldb
-from ... import util
-
-
-class _cymysqlBIT(BIT):
- def result_processor(self, dialect, coltype):
- """Convert MySQL's 64 bit, variable length binary string to a long."""
-
- def process(value):
- if value is not None:
- v = 0
- for i in iter(value):
- v = v << 8 | i
- return v
- return value
-
- return process
-
-
-class MySQLDialect_cymysql(MySQLDialect_mysqldb):
- driver = "cymysql"
- supports_statement_cache = True
-
- description_encoding = None
- supports_sane_rowcount = True
- supports_sane_multi_rowcount = False
- supports_unicode_statements = True
-
- colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _cymysqlBIT})
-
- @classmethod
- def import_dbapi(cls):
- return __import__("cymysql")
-
- def _detect_charset(self, connection):
- return connection.connection.charset
-
- def _extract_error_code(self, exception):
- return exception.errno
-
- def is_disconnect(self, e, connection, cursor):
- if isinstance(e, self.dbapi.OperationalError):
- return self._extract_error_code(e) in (
- 2006,
- 2013,
- 2014,
- 2045,
- 2055,
- )
- elif isinstance(e, self.dbapi.InterfaceError):
- # if underlying connection is closed,
- # this is the error you get
- return True
- else:
- return False
-
-
-dialect = MySQLDialect_cymysql
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py
deleted file mode 100644
index e4005c2..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py
+++ /dev/null
@@ -1,219 +0,0 @@
-# dialects/mysql/dml.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-from __future__ import annotations
-
-from typing import Any
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Tuple
-from typing import Union
-
-from ... import exc
-from ... import util
-from ...sql._typing import _DMLTableArgument
-from ...sql.base import _exclusive_against
-from ...sql.base import _generative
-from ...sql.base import ColumnCollection
-from ...sql.base import ReadOnlyColumnCollection
-from ...sql.dml import Insert as StandardInsert
-from ...sql.elements import ClauseElement
-from ...sql.elements import KeyedColumnElement
-from ...sql.expression import alias
-from ...sql.selectable import NamedFromClause
-from ...util.typing import Self
-
-
-__all__ = ("Insert", "insert")
-
-
-def insert(table: _DMLTableArgument) -> Insert:
- """Construct a MySQL/MariaDB-specific variant :class:`_mysql.Insert`
- construct.
-
- .. container:: inherited_member
-
- The :func:`sqlalchemy.dialects.mysql.insert` function creates
- a :class:`sqlalchemy.dialects.mysql.Insert`. This class is based
- on the dialect-agnostic :class:`_sql.Insert` construct which may
- be constructed using the :func:`_sql.insert` function in
- SQLAlchemy Core.
-
- The :class:`_mysql.Insert` construct includes additional methods
- :meth:`_mysql.Insert.on_duplicate_key_update`.
-
- """
- return Insert(table)
-
-
-class Insert(StandardInsert):
- """MySQL-specific implementation of INSERT.
-
- Adds methods for MySQL-specific syntaxes such as ON DUPLICATE KEY UPDATE.
-
- The :class:`~.mysql.Insert` object is created using the
- :func:`sqlalchemy.dialects.mysql.insert` function.
-
- .. versionadded:: 1.2
-
- """
-
- stringify_dialect = "mysql"
- inherit_cache = False
-
- @property
- def inserted(
- self,
- ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
- """Provide the "inserted" namespace for an ON DUPLICATE KEY UPDATE
- statement
-
- MySQL's ON DUPLICATE KEY UPDATE clause allows reference to the row
- that would be inserted, via a special function called ``VALUES()``.
- This attribute provides all columns in this row to be referenceable
- such that they will render within a ``VALUES()`` function inside the
- ON DUPLICATE KEY UPDATE clause. The attribute is named ``.inserted``
- so as not to conflict with the existing
- :meth:`_expression.Insert.values` method.
-
- .. tip:: The :attr:`_mysql.Insert.inserted` attribute is an instance
- of :class:`_expression.ColumnCollection`, which provides an
- interface the same as that of the :attr:`_schema.Table.c`
- collection described at :ref:`metadata_tables_and_columns`.
- With this collection, ordinary names are accessible like attributes
- (e.g. ``stmt.inserted.some_column``), but special names and
- dictionary method names should be accessed using indexed access,
- such as ``stmt.inserted["column name"]`` or
- ``stmt.inserted["values"]``. See the docstring for
- :class:`_expression.ColumnCollection` for further examples.
-
- .. seealso::
-
- :ref:`mysql_insert_on_duplicate_key_update` - example of how
- to use :attr:`_expression.Insert.inserted`
-
- """
- return self.inserted_alias.columns
-
- @util.memoized_property
- def inserted_alias(self) -> NamedFromClause:
- return alias(self.table, name="inserted")
-
- @_generative
- @_exclusive_against(
- "_post_values_clause",
- msgs={
- "_post_values_clause": "This Insert construct already "
- "has an ON DUPLICATE KEY clause present"
- },
- )
- def on_duplicate_key_update(self, *args: _UpdateArg, **kw: Any) -> Self:
- r"""
- Specifies the ON DUPLICATE KEY UPDATE clause.
-
- :param \**kw: Column keys linked to UPDATE values. The
- values may be any SQL expression or supported literal Python
- values.
-
- .. warning:: This dictionary does **not** take into account
- Python-specified default UPDATE values or generation functions,
- e.g. those specified using :paramref:`_schema.Column.onupdate`.
- These values will not be exercised for an ON DUPLICATE KEY UPDATE
- style of UPDATE, unless values are manually specified here.
-
- :param \*args: As an alternative to passing key/value parameters,
- a dictionary or list of 2-tuples can be passed as a single positional
- argument.
-
- Passing a single dictionary is equivalent to the keyword argument
- form::
-
- insert().on_duplicate_key_update({"name": "some name"})
-
- Passing a list of 2-tuples indicates that the parameter assignments
- in the UPDATE clause should be ordered as sent, in a manner similar
- to that described for the :class:`_expression.Update`
- construct overall
- in :ref:`tutorial_parameter_ordered_updates`::
-
- insert().on_duplicate_key_update(
- [("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
- ordering.
-
-
- .. versionadded:: 1.2
-
- .. seealso::
-
- :ref:`mysql_insert_on_duplicate_key_update`
-
- """
- if args and kw:
- raise exc.ArgumentError(
- "Can't pass kwargs and positional arguments simultaneously"
- )
-
- if args:
- if len(args) > 1:
- raise exc.ArgumentError(
- "Only a single dictionary or list of tuples "
- "is accepted positionally."
- )
- values = args[0]
- else:
- values = kw
-
- self._post_values_clause = OnDuplicateClause(
- self.inserted_alias, values
- )
- return self
-
-
-class OnDuplicateClause(ClauseElement):
- __visit_name__ = "on_duplicate_key_update"
-
- _parameter_ordering: Optional[List[str]] = None
-
- stringify_dialect = "mysql"
-
- def __init__(
- self, inserted_alias: NamedFromClause, update: _UpdateArg
- ) -> None:
- self.inserted_alias = inserted_alias
-
- # auto-detect that parameters should be ordered. This is copied from
- # Update._proces_colparams(), however we don't look for a special flag
- # in this case since we are not disambiguating from other use cases as
- # we are in Update.values().
- if isinstance(update, list) and (
- update and isinstance(update[0], tuple)
- ):
- self._parameter_ordering = [key for key, value in update]
- update = dict(update)
-
- if isinstance(update, dict):
- if not update:
- raise ValueError(
- "update parameter dictionary must not be empty"
- )
- elif isinstance(update, ColumnCollection):
- update = dict(update)
- else:
- raise ValueError(
- "update parameter must be a non-empty dictionary "
- "or a ColumnCollection such as the `.c.` collection "
- "of a Table object"
- )
- self.update = update
-
-
-_UpdateArg = Union[
- Mapping[Any, Any], List[Tuple[str, Any]], ColumnCollection[Any, Any]
-]
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py
deleted file mode 100644
index 96499d7..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py
+++ /dev/null
@@ -1,244 +0,0 @@
-# dialects/mysql/enumerated.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-import re
-
-from .types import _StringType
-from ... import exc
-from ... import sql
-from ... import util
-from ...sql import sqltypes
-
-
-class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum, _StringType):
- """MySQL ENUM type."""
-
- __visit_name__ = "ENUM"
-
- native_enum = True
-
- def __init__(self, *enums, **kw):
- """Construct an ENUM.
-
- E.g.::
-
- 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
- quotes when generating the schema. This object may also be a
- PEP-435-compliant enumerated type.
-
- .. versionadded: 1.1 added support for PEP-435-compliant enumerated
- types.
-
- :param strict: This flag has no effect.
-
- .. versionchanged:: The MySQL ENUM type as well as the base Enum
- type now validates all Python data values.
-
- :param charset: Optional, a column-level character set for this string
- value. Takes precedence to 'ascii' or 'unicode' short-hand.
-
- :param collation: Optional, a column-level collation for this string
- value. Takes precedence to 'binary' short-hand.
-
- :param ascii: Defaults to False: short-hand for the ``latin1``
- character set, generates ASCII in schema.
-
- :param unicode: Defaults to False: short-hand for the ``ucs2``
- character set, generates UNICODE in schema.
-
- :param binary: Defaults to False: short-hand, pick the binary
- collation type that matches the column's character set. Generates
- BINARY in schema. This does not affect the type of data stored,
- only the collation of character data.
-
- """
- kw.pop("strict", None)
- self._enum_init(enums, kw)
- _StringType.__init__(self, length=self.length, **kw)
-
- @classmethod
- def adapt_emulated_to_native(cls, impl, **kw):
- """Produce a MySQL native :class:`.mysql.ENUM` from plain
- :class:`.Enum`.
-
- """
- kw.setdefault("validate_strings", impl.validate_strings)
- kw.setdefault("values_callable", impl.values_callable)
- kw.setdefault("omit_aliases", impl._omit_aliases)
- return cls(**kw)
-
- def _object_value_for_elem(self, elem):
- # mysql sends back a blank string for any value that
- # was persisted that was not in the enums; that is, it does no
- # validation on the incoming data, it "truncates" it to be
- # the blank string. Return it straight.
- if elem == "":
- return elem
- else:
- return super()._object_value_for_elem(elem)
-
- def __repr__(self):
- return util.generic_repr(
- self, to_inspect=[ENUM, _StringType, sqltypes.Enum]
- )
-
-
-class SET(_StringType):
- """MySQL SET type."""
-
- __visit_name__ = "SET"
-
- def __init__(self, *values, **kw):
- """Construct a SET.
-
- E.g.::
-
- 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
- :paramref:`.SET.retrieve_as_bitwise` flag is set to True.
-
- :param values: The range of valid values for this SET. The values
- are not quoted, they will be escaped and surrounded by single
- quotes when generating the schema.
-
- :param convert_unicode: Same flag as that of
- :paramref:`.String.convert_unicode`.
-
- :param collation: same as that of :paramref:`.String.collation`
-
- :param charset: same as that of :paramref:`.VARCHAR.charset`.
-
- :param ascii: same as that of :paramref:`.VARCHAR.ascii`.
-
- :param unicode: same as that of :paramref:`.VARCHAR.unicode`.
-
- :param binary: same as that of :paramref:`.VARCHAR.binary`.
-
- :param retrieve_as_bitwise: if True, the data for the set type will be
- persisted and selected using an integer value, where a set is coerced
- into a bitwise mask for persistence. MySQL allows this mode which
- has the advantage of being able to store values unambiguously,
- such as the blank string ``''``. The datatype will appear
- as the expression ``col + 0`` in a SELECT statement, so that the
- value is coerced into an integer value in result sets.
- This flag is required if one wishes
- to persist a set that can store the blank string ``''`` as a value.
-
- .. warning::
-
- When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is
- essential that the list of set values is expressed in the
- **exact same order** as exists on the MySQL database.
-
- """
- self.retrieve_as_bitwise = kw.pop("retrieve_as_bitwise", False)
- self.values = tuple(values)
- if not self.retrieve_as_bitwise and "" in values:
- raise exc.ArgumentError(
- "Can't use the blank value '' in a SET without "
- "setting retrieve_as_bitwise=True"
- )
- if self.retrieve_as_bitwise:
- self._bitmap = {
- value: 2**idx for idx, value in enumerate(self.values)
- }
- self._bitmap.update(
- (2**idx, value) for idx, value in enumerate(self.values)
- )
- length = max([len(v) for v in values] + [0])
- kw.setdefault("length", length)
- super().__init__(**kw)
-
- def column_expression(self, colexpr):
- if self.retrieve_as_bitwise:
- return sql.type_coerce(
- sql.type_coerce(colexpr, sqltypes.Integer) + 0, self
- )
- else:
- return colexpr
-
- def result_processor(self, dialect, coltype):
- if self.retrieve_as_bitwise:
-
- def process(value):
- if value is not None:
- value = int(value)
-
- return set(util.map_bits(self._bitmap.__getitem__, value))
- else:
- return None
-
- else:
- super_convert = super().result_processor(dialect, coltype)
-
- def process(value):
- if isinstance(value, str):
- # MySQLdb returns a string, let's parse
- if super_convert:
- value = super_convert(value)
- return set(re.findall(r"[^,]+", value))
- else:
- # mysql-connector-python does a naive
- # split(",") which throws in an empty string
- if value is not None:
- value.discard("")
- return value
-
- return process
-
- def bind_processor(self, dialect):
- super_convert = super().bind_processor(dialect)
- if self.retrieve_as_bitwise:
-
- def process(value):
- if value is None:
- return None
- elif isinstance(value, (int, str)):
- if super_convert:
- return super_convert(value)
- else:
- return value
- else:
- int_value = 0
- for v in value:
- int_value |= self._bitmap[v]
- return int_value
-
- else:
-
- def process(value):
- # accept strings and int (actually bitflag) values directly
- if value is not None and not isinstance(value, (int, str)):
- value = ",".join(value)
-
- if super_convert:
- return super_convert(value)
- else:
- return value
-
- return process
-
- def adapt(self, impltype, **kw):
- kw["retrieve_as_bitwise"] = self.retrieve_as_bitwise
- return util.constructor_copy(self, impltype, *self.values, **kw)
-
- def __repr__(self):
- return util.generic_repr(
- self,
- to_inspect=[SET, _StringType],
- additional_kw=[
- ("retrieve_as_bitwise", False),
- ],
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py
deleted file mode 100644
index b81b58a..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# dialects/mysql/expression.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-from ... import exc
-from ... import util
-from ...sql import coercions
-from ...sql import elements
-from ...sql import operators
-from ...sql import roles
-from ...sql.base import _generative
-from ...sql.base import Generative
-from ...util.typing import Self
-
-
-class match(Generative, elements.BinaryExpression):
- """Produce a ``MATCH (X, Y) AGAINST ('TEXT')`` clause.
-
- E.g.::
-
- from sqlalchemy import desc
- from sqlalchemy.dialects.mysql import match
-
- match_expr = match(
- users_table.c.firstname,
- users_table.c.lastname,
- against="Firstname Lastname",
- )
-
- stmt = (
- select(users_table)
- .where(match_expr.in_boolean_mode())
- .order_by(desc(match_expr))
- )
-
- Would produce SQL resembling::
-
- SELECT id, firstname, lastname
- FROM user
- WHERE MATCH(firstname, lastname) AGAINST (:param_1 IN BOOLEAN MODE)
- ORDER BY MATCH(firstname, lastname) AGAINST (:param_2) DESC
-
- The :func:`_mysql.match` function is a standalone version of the
- :meth:`_sql.ColumnElement.match` method available on all
- SQL expressions, as when :meth:`_expression.ColumnElement.match` is
- used, but allows to pass multiple columns
-
- :param cols: column expressions to match against
-
- :param against: expression to be compared towards
-
- :param in_boolean_mode: boolean, set "boolean mode" to true
-
- :param in_natural_language_mode: boolean , set "natural language" to true
-
- :param with_query_expansion: boolean, set "query expansion" to true
-
- .. versionadded:: 1.4.19
-
- .. seealso::
-
- :meth:`_expression.ColumnElement.match`
-
- """
-
- __visit_name__ = "mysql_match"
-
- inherit_cache = True
-
- def __init__(self, *cols, **kw):
- if not cols:
- raise exc.ArgumentError("columns are required")
-
- against = kw.pop("against", None)
-
- if against is None:
- raise exc.ArgumentError("against is required")
- against = coercions.expect(
- roles.ExpressionElementRole,
- against,
- )
-
- left = elements.BooleanClauseList._construct_raw(
- operators.comma_op,
- clauses=cols,
- )
- left.group = False
-
- flags = util.immutabledict(
- {
- "mysql_boolean_mode": kw.pop("in_boolean_mode", False),
- "mysql_natural_language": kw.pop(
- "in_natural_language_mode", False
- ),
- "mysql_query_expansion": kw.pop("with_query_expansion", False),
- }
- )
-
- if kw:
- raise exc.ArgumentError("unknown arguments: %s" % (", ".join(kw)))
-
- super().__init__(left, against, operators.match_op, modifiers=flags)
-
- @_generative
- def in_boolean_mode(self) -> Self:
- """Apply the "IN BOOLEAN MODE" modifier to the MATCH expression.
-
- :return: a new :class:`_mysql.match` instance with modifications
- applied.
- """
-
- self.modifiers = self.modifiers.union({"mysql_boolean_mode": True})
- return self
-
- @_generative
- def in_natural_language_mode(self) -> Self:
- """Apply the "IN NATURAL LANGUAGE MODE" modifier to the MATCH
- expression.
-
- :return: a new :class:`_mysql.match` instance with modifications
- applied.
- """
-
- self.modifiers = self.modifiers.union({"mysql_natural_language": True})
- return self
-
- @_generative
- def with_query_expansion(self) -> Self:
- """Apply the "WITH QUERY EXPANSION" modifier to the MATCH expression.
-
- :return: a new :class:`_mysql.match` instance with modifications
- applied.
- """
-
- self.modifiers = self.modifiers.union({"mysql_query_expansion": True})
- return self
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py
deleted file mode 100644
index ebe4a34..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# dialects/mysql/json.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-from ... import types as sqltypes
-
-
-class JSON(sqltypes.JSON):
- """MySQL JSON type.
-
- MySQL supports JSON as of version 5.7.
- MariaDB supports JSON (as an alias for LONGTEXT) as of version 10.2.
-
- :class:`_mysql.JSON` is used automatically whenever the base
- :class:`_types.JSON` datatype is used against a MySQL or MariaDB backend.
-
- .. seealso::
-
- :class:`_types.JSON` - main documentation for the generic
- cross-platform JSON datatype.
-
- The :class:`.mysql.JSON` type supports persistence of JSON values
- as well as the core index operations provided by :class:`_types.JSON`
- datatype, by adapting the operations to render the ``JSON_EXTRACT``
- function at the database level.
-
- """
-
- pass
-
-
-class _FormatTypeMixin:
- def _format_value(self, value):
- raise NotImplementedError()
-
- def bind_processor(self, dialect):
- super_proc = self.string_bind_processor(dialect)
-
- def process(value):
- value = self._format_value(value)
- if super_proc:
- value = super_proc(value)
- return value
-
- return process
-
- def literal_processor(self, dialect):
- super_proc = self.string_literal_processor(dialect)
-
- def process(value):
- value = self._format_value(value)
- if super_proc:
- value = super_proc(value)
- return value
-
- return process
-
-
-class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
- def _format_value(self, value):
- if isinstance(value, int):
- value = "$[%s]" % value
- else:
- value = '$."%s"' % value
- return value
-
-
-class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
- def _format_value(self, value):
- return "$%s" % (
- "".join(
- [
- "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
- for elem in value
- ]
- )
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py
deleted file mode 100644
index 10a05f9..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# dialects/mysql/mariadb.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-from .base import MariaDBIdentifierPreparer
-from .base import MySQLDialect
-
-
-class MariaDBDialect(MySQLDialect):
- is_mariadb = True
- supports_statement_cache = True
- name = "mariadb"
- preparer = MariaDBIdentifierPreparer
-
-
-def loader(driver):
- driver_mod = __import__(
- "sqlalchemy.dialects.mysql.%s" % driver
- ).dialects.mysql
- driver_cls = getattr(driver_mod, driver).dialect
-
- return type(
- "MariaDBDialect_%s" % driver,
- (
- MariaDBDialect,
- driver_cls,
- ),
- {"supports_statement_cache": True},
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py
deleted file mode 100644
index 9bb3fa4..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py
+++ /dev/null
@@ -1,275 +0,0 @@
-# dialects/mysql/mariadbconnector.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-"""
-
-.. dialect:: mysql+mariadbconnector
- :name: MariaDB Connector/Python
- :dbapi: mariadb
- :connectstring: mariadb+mariadbconnector://<user>:<password>@<host>[:<port>]/<dbname>
- :url: https://pypi.org/project/mariadb/
-
-Driver Status
--------------
-
-MariaDB Connector/Python enables Python programs to access MariaDB and MySQL
-databases using an API which is compliant with the Python DB API 2.0 (PEP-249).
-It is written in C and uses MariaDB Connector/C client library for client server
-communication.
-
-Note that the default driver for a ``mariadb://`` connection URI continues to
-be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.
-
-.. mariadb: https://github.com/mariadb-corporation/mariadb-connector-python
-
-""" # noqa
-import re
-from uuid import UUID as _python_UUID
-
-from .base import MySQLCompiler
-from .base import MySQLDialect
-from .base import MySQLExecutionContext
-from ... import sql
-from ... import util
-from ...sql import sqltypes
-
-
-mariadb_cpy_minimum_version = (1, 0, 1)
-
-
-class _MariaDBUUID(sqltypes.UUID[sqltypes._UUID_RETURN]):
- # work around JIRA issue
- # https://jira.mariadb.org/browse/CONPY-270. When that issue is fixed,
- # this type can be removed.
- def result_processor(self, dialect, coltype):
- if self.as_uuid:
-
- def process(value):
- if value is not None:
- if hasattr(value, "decode"):
- value = value.decode("ascii")
- value = _python_UUID(value)
- return value
-
- return process
- else:
-
- def process(value):
- if value is not None:
- if hasattr(value, "decode"):
- value = value.decode("ascii")
- value = str(_python_UUID(value))
- return value
-
- return process
-
-
-class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
- _lastrowid = None
-
- def create_server_side_cursor(self):
- return self._dbapi_connection.cursor(buffered=False)
-
- def create_default_cursor(self):
- return self._dbapi_connection.cursor(buffered=True)
-
- def post_exec(self):
- super().post_exec()
-
- self._rowcount = self.cursor.rowcount
-
- if self.isinsert and self.compiled.postfetch_lastrowid:
- self._lastrowid = self.cursor.lastrowid
-
- def get_lastrowid(self):
- return self._lastrowid
-
-
-class MySQLCompiler_mariadbconnector(MySQLCompiler):
- pass
-
-
-class MySQLDialect_mariadbconnector(MySQLDialect):
- driver = "mariadbconnector"
- supports_statement_cache = True
-
- # set this to True at the module level to prevent the driver from running
- # against a backend that server detects as MySQL. currently this appears to
- # be unnecessary as MariaDB client libraries have always worked against
- # MySQL databases. However, if this changes at some point, this can be
- # adjusted, but PLEASE ADD A TEST in test/dialect/mysql/test_dialect.py if
- # this change is made at some point to ensure the correct exception
- # is raised at the correct point when running the driver against
- # a MySQL backend.
- # is_mariadb = True
-
- supports_unicode_statements = True
- encoding = "utf8mb4"
- convert_unicode = True
- supports_sane_rowcount = True
- supports_sane_multi_rowcount = True
- supports_native_decimal = True
- default_paramstyle = "qmark"
- execution_ctx_cls = MySQLExecutionContext_mariadbconnector
- statement_compiler = MySQLCompiler_mariadbconnector
-
- supports_server_side_cursors = True
-
- colspecs = util.update_copy(
- MySQLDialect.colspecs, {sqltypes.Uuid: _MariaDBUUID}
- )
-
- @util.memoized_property
- def _dbapi_version(self):
- if self.dbapi and hasattr(self.dbapi, "__version__"):
- return tuple(
- [
- int(x)
- for x in re.findall(
- r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
- )
- ]
- )
- else:
- return (99, 99, 99)
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.paramstyle = "qmark"
- if self.dbapi is not None:
- if self._dbapi_version < mariadb_cpy_minimum_version:
- raise NotImplementedError(
- "The minimum required version for MariaDB "
- "Connector/Python is %s"
- % ".".join(str(x) for x in mariadb_cpy_minimum_version)
- )
-
- @classmethod
- def import_dbapi(cls):
- return __import__("mariadb")
-
- def is_disconnect(self, e, connection, cursor):
- if super().is_disconnect(e, connection, cursor):
- return True
- elif isinstance(e, self.dbapi.Error):
- str_e = str(e).lower()
- return "not connected" in str_e or "isn't valid" in str_e
- else:
- return False
-
- def create_connect_args(self, url):
- opts = url.translate_connect_args()
-
- int_params = [
- "connect_timeout",
- "read_timeout",
- "write_timeout",
- "client_flag",
- "port",
- "pool_size",
- ]
- bool_params = [
- "local_infile",
- "ssl_verify_cert",
- "ssl",
- "pool_reset_connection",
- ]
-
- for key in int_params:
- util.coerce_kw_type(opts, key, int)
- for key in bool_params:
- util.coerce_kw_type(opts, key, bool)
-
- # FOUND_ROWS must be set in CLIENT_FLAGS to enable
- # supports_sane_rowcount.
- client_flag = opts.get("client_flag", 0)
- if self.dbapi is not None:
- try:
- CLIENT_FLAGS = __import__(
- self.dbapi.__name__ + ".constants.CLIENT"
- ).constants.CLIENT
- client_flag |= CLIENT_FLAGS.FOUND_ROWS
- except (AttributeError, ImportError):
- self.supports_sane_rowcount = False
- opts["client_flag"] = client_flag
- return [[], opts]
-
- def _extract_error_code(self, exception):
- try:
- rc = exception.errno
- except:
- rc = -1
- return rc
-
- def _detect_charset(self, connection):
- return "utf8mb4"
-
- def get_isolation_level_values(self, dbapi_connection):
- return (
- "SERIALIZABLE",
- "READ UNCOMMITTED",
- "READ COMMITTED",
- "REPEATABLE READ",
- "AUTOCOMMIT",
- )
-
- def set_isolation_level(self, connection, level):
- if level == "AUTOCOMMIT":
- connection.autocommit = True
- else:
- connection.autocommit = False
- super().set_isolation_level(connection, level)
-
- def do_begin_twophase(self, connection, xid):
- connection.execute(
- sql.text("XA BEGIN :xid").bindparams(
- sql.bindparam("xid", xid, literal_execute=True)
- )
- )
-
- def do_prepare_twophase(self, connection, xid):
- connection.execute(
- sql.text("XA END :xid").bindparams(
- sql.bindparam("xid", xid, literal_execute=True)
- )
- )
- connection.execute(
- sql.text("XA PREPARE :xid").bindparams(
- sql.bindparam("xid", xid, literal_execute=True)
- )
- )
-
- def do_rollback_twophase(
- self, connection, xid, is_prepared=True, recover=False
- ):
- if not is_prepared:
- connection.execute(
- sql.text("XA END :xid").bindparams(
- sql.bindparam("xid", xid, literal_execute=True)
- )
- )
- connection.execute(
- sql.text("XA ROLLBACK :xid").bindparams(
- sql.bindparam("xid", xid, literal_execute=True)
- )
- )
-
- def do_commit_twophase(
- self, connection, xid, is_prepared=True, recover=False
- ):
- if not is_prepared:
- self.do_prepare_twophase(connection, xid)
- connection.execute(
- sql.text("XA COMMIT :xid").bindparams(
- sql.bindparam("xid", xid, literal_execute=True)
- )
- )
-
-
-dialect = MySQLDialect_mariadbconnector
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py
deleted file mode 100644
index b152339..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# dialects/mysql/mysqlconnector.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-r"""
-.. dialect:: mysql+mysqlconnector
- :name: MySQL Connector/Python
- :dbapi: myconnpy
- :connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
- :url: https://pypi.org/project/mysql-connector-python/
-
-.. note::
-
- The MySQL Connector/Python DBAPI has had many issues since its release,
- some of which may remain unresolved, and the mysqlconnector dialect is
- **not tested as part of SQLAlchemy's continuous integration**.
- The recommended MySQL dialects are mysqlclient and PyMySQL.
-
-""" # noqa
-
-import re
-
-from .base import BIT
-from .base import MySQLCompiler
-from .base import MySQLDialect
-from .base import MySQLIdentifierPreparer
-from ... import util
-
-
-class MySQLCompiler_mysqlconnector(MySQLCompiler):
- def visit_mod_binary(self, binary, operator, **kw):
- return (
- self.process(binary.left, **kw)
- + " % "
- + self.process(binary.right, **kw)
- )
-
-
-class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
- @property
- def _double_percents(self):
- return False
-
- @_double_percents.setter
- def _double_percents(self, value):
- pass
-
- def _escape_identifier(self, value):
- value = value.replace(self.escape_quote, self.escape_to_quote)
- return value
-
-
-class _myconnpyBIT(BIT):
- def result_processor(self, dialect, coltype):
- """MySQL-connector already converts mysql bits, so."""
-
- return None
-
-
-class MySQLDialect_mysqlconnector(MySQLDialect):
- driver = "mysqlconnector"
- supports_statement_cache = True
-
- supports_sane_rowcount = True
- supports_sane_multi_rowcount = True
-
- supports_native_decimal = True
-
- default_paramstyle = "format"
- statement_compiler = MySQLCompiler_mysqlconnector
-
- preparer = MySQLIdentifierPreparer_mysqlconnector
-
- colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
-
- @classmethod
- def import_dbapi(cls):
- from mysql import connector
-
- return connector
-
- def do_ping(self, dbapi_connection):
- dbapi_connection.ping(False)
- return True
-
- def create_connect_args(self, url):
- opts = url.translate_connect_args(username="user")
-
- opts.update(url.query)
-
- 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, "compress", bool)
- util.coerce_kw_type(opts, "connection_timeout", int)
- util.coerce_kw_type(opts, "connect_timeout", int)
- util.coerce_kw_type(opts, "consume_results", bool)
- util.coerce_kw_type(opts, "force_ipv6", bool)
- util.coerce_kw_type(opts, "get_warnings", bool)
- util.coerce_kw_type(opts, "pool_reset_session", bool)
- util.coerce_kw_type(opts, "pool_size", int)
- util.coerce_kw_type(opts, "raise_on_warnings", bool)
- util.coerce_kw_type(opts, "raw", bool)
- util.coerce_kw_type(opts, "ssl_verify_cert", bool)
- util.coerce_kw_type(opts, "use_pure", bool)
- util.coerce_kw_type(opts, "use_unicode", bool)
-
- # unfortunately, MySQL/connector python refuses to release a
- # cursor without reading fully, so non-buffered isn't an option
- opts.setdefault("buffered", True)
-
- # FOUND_ROWS must be set in ClientFlag to enable
- # supports_sane_rowcount.
- if self.dbapi is not None:
- try:
- from mysql.connector.constants import ClientFlag
-
- client_flags = opts.get(
- "client_flags", ClientFlag.get_default()
- )
- client_flags |= ClientFlag.FOUND_ROWS
- opts["client_flags"] = client_flags
- except Exception:
- pass
- return [[], opts]
-
- @util.memoized_property
- def _mysqlconnector_version_info(self):
- if self.dbapi and hasattr(self.dbapi, "__version__"):
- m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
- if m:
- return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
-
- def _detect_charset(self, connection):
- return connection.connection.charset
-
- def _extract_error_code(self, exception):
- return exception.errno
-
- def is_disconnect(self, e, connection, cursor):
- errnos = (2006, 2013, 2014, 2045, 2055, 2048)
- exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError)
- if isinstance(e, exceptions):
- return (
- e.errno in errnos
- or "MySQL Connection not available." in str(e)
- or "Connection to MySQL is not available" in str(e)
- )
- else:
- return False
-
- def _compat_fetchall(self, rp, charset=None):
- return rp.fetchall()
-
- def _compat_fetchone(self, rp, charset=None):
- return rp.fetchone()
-
- _isolation_lookup = {
- "SERIALIZABLE",
- "READ UNCOMMITTED",
- "READ COMMITTED",
- "REPEATABLE READ",
- "AUTOCOMMIT",
- }
-
- def _set_isolation_level(self, connection, level):
- if level == "AUTOCOMMIT":
- connection.autocommit = True
- else:
- connection.autocommit = False
- super()._set_isolation_level(connection, level)
-
-
-dialect = MySQLDialect_mysqlconnector
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py
deleted file mode 100644
index 0c632b6..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# dialects/mysql/mysqldb.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-"""
-
-.. dialect:: mysql+mysqldb
- :name: mysqlclient (maintained fork of MySQL-Python)
- :dbapi: mysqldb
- :connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
- :url: https://pypi.org/project/mysqlclient/
-
-Driver Status
--------------
-
-The mysqlclient DBAPI is a maintained fork of the
-`MySQL-Python <https://sourceforge.net/projects/mysql-python>`_ DBAPI
-that is no longer maintained. `mysqlclient`_ supports Python 2 and Python 3
-and is very stable.
-
-.. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python
-
-.. _mysqldb_unicode:
-
-Unicode
--------
-
-Please see :ref:`mysql_unicode` for current recommendations on unicode
-handling.
-
-.. _mysqldb_ssl:
-
-SSL Connections
-----------------
-
-The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the
-key "ssl", which may be specified using the
-:paramref:`_sa.create_engine.connect_args` dictionary::
-
- engine = create_engine(
- "mysql+mysqldb://scott:tiger@192.168.0.134/test",
- connect_args={
- "ssl": {
- "ca": "/home/gord/client-ssl/ca.pem",
- "cert": "/home/gord/client-ssl/client-cert.pem",
- "key": "/home/gord/client-ssl/client-key.pem"
- }
- }
- )
-
-For convenience, the following keys may also be specified inline within the URL
-where they will be interpreted into the "ssl" dictionary automatically:
-"ssl_ca", "ssl_cert", "ssl_key", "ssl_capath", "ssl_cipher",
-"ssl_check_hostname". An example is as follows::
-
- connection_uri = (
- "mysql+mysqldb://scott:tiger@192.168.0.134/test"
- "?ssl_ca=/home/gord/client-ssl/ca.pem"
- "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
- "&ssl_key=/home/gord/client-ssl/client-key.pem"
- )
-
-.. seealso::
-
- :ref:`pymysql_ssl` in the PyMySQL dialect
-
-
-Using MySQLdb with Google Cloud SQL
------------------------------------
-
-Google Cloud SQL now recommends use of the MySQLdb dialect. Connect
-using a URL like the following::
-
- mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
-
-Server Side Cursors
--------------------
-
-The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
-
-"""
-
-import re
-
-from .base import MySQLCompiler
-from .base import MySQLDialect
-from .base import MySQLExecutionContext
-from .base import MySQLIdentifierPreparer
-from .base import TEXT
-from ... import sql
-from ... import util
-
-
-class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
- pass
-
-
-class MySQLCompiler_mysqldb(MySQLCompiler):
- pass
-
-
-class MySQLDialect_mysqldb(MySQLDialect):
- driver = "mysqldb"
- supports_statement_cache = True
- supports_unicode_statements = True
- supports_sane_rowcount = True
- supports_sane_multi_rowcount = True
-
- supports_native_decimal = True
-
- default_paramstyle = "format"
- execution_ctx_cls = MySQLExecutionContext_mysqldb
- statement_compiler = MySQLCompiler_mysqldb
- preparer = MySQLIdentifierPreparer
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self._mysql_dbapi_version = (
- self._parse_dbapi_version(self.dbapi.__version__)
- if self.dbapi is not None and hasattr(self.dbapi, "__version__")
- else (0, 0, 0)
- )
-
- def _parse_dbapi_version(self, version):
- m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version)
- if m:
- return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
- else:
- return (0, 0, 0)
-
- @util.langhelpers.memoized_property
- def supports_server_side_cursors(self):
- try:
- cursors = __import__("MySQLdb.cursors").cursors
- self._sscursor = cursors.SSCursor
- return True
- except (ImportError, AttributeError):
- return False
-
- @classmethod
- def import_dbapi(cls):
- return __import__("MySQLdb")
-
- def on_connect(self):
- super_ = super().on_connect()
-
- def on_connect(conn):
- if super_ is not None:
- super_(conn)
-
- charset_name = conn.character_set_name()
-
- if charset_name is not None:
- cursor = conn.cursor()
- cursor.execute("SET NAMES %s" % charset_name)
- cursor.close()
-
- return on_connect
-
- def do_ping(self, dbapi_connection):
- dbapi_connection.ping()
- return True
-
- def do_executemany(self, cursor, statement, parameters, context=None):
- rowcount = cursor.executemany(statement, parameters)
- if context is not None:
- context._rowcount = rowcount
-
- def _check_unicode_returns(self, connection):
- # work around issue fixed in
- # https://github.com/farcepest/MySQLdb1/commit/cd44524fef63bd3fcb71947392326e9742d520e8
- # specific issue w/ the utf8mb4_bin collation and unicode returns
-
- collation = connection.exec_driver_sql(
- "show collation where %s = 'utf8mb4' and %s = 'utf8mb4_bin'"
- % (
- self.identifier_preparer.quote("Charset"),
- self.identifier_preparer.quote("Collation"),
- )
- ).scalar()
- has_utf8mb4_bin = self.server_version_info > (5,) and collation
- if has_utf8mb4_bin:
- additional_tests = [
- sql.collate(
- sql.cast(
- sql.literal_column("'test collated returns'"),
- TEXT(charset="utf8mb4"),
- ),
- "utf8mb4_bin",
- )
- ]
- else:
- additional_tests = []
- return super()._check_unicode_returns(connection, additional_tests)
-
- def create_connect_args(self, url, _translate_args=None):
- if _translate_args is None:
- _translate_args = dict(
- database="db", username="user", password="passwd"
- )
-
- opts = url.translate_connect_args(**_translate_args)
- opts.update(url.query)
-
- util.coerce_kw_type(opts, "compress", bool)
- util.coerce_kw_type(opts, "connect_timeout", int)
- 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)
- # 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.
- util.coerce_kw_type(opts, "use_unicode", bool)
- util.coerce_kw_type(opts, "charset", str)
-
- # Rich values 'cursorclass' and 'conv' are not supported via
- # query string.
-
- ssl = {}
- keys = [
- ("ssl_ca", str),
- ("ssl_key", str),
- ("ssl_cert", str),
- ("ssl_capath", str),
- ("ssl_cipher", str),
- ("ssl_check_hostname", bool),
- ]
- for key, kw_type in keys:
- if key in opts:
- ssl[key[4:]] = opts[key]
- util.coerce_kw_type(ssl, key[4:], kw_type)
- del opts[key]
- if ssl:
- opts["ssl"] = ssl
-
- # FOUND_ROWS must be set in CLIENT_FLAGS to enable
- # supports_sane_rowcount.
- client_flag = opts.get("client_flag", 0)
-
- client_flag_found_rows = self._found_rows_client_flag()
- if client_flag_found_rows is not None:
- client_flag |= client_flag_found_rows
- opts["client_flag"] = client_flag
- return [[], opts]
-
- def _found_rows_client_flag(self):
- if self.dbapi is not None:
- try:
- CLIENT_FLAGS = __import__(
- self.dbapi.__name__ + ".constants.CLIENT"
- ).constants.CLIENT
- except (AttributeError, ImportError):
- return None
- else:
- return CLIENT_FLAGS.FOUND_ROWS
- else:
- return None
-
- def _extract_error_code(self, exception):
- return exception.args[0]
-
- def _detect_charset(self, connection):
- """Sniff out the character set in use for connection results."""
-
- try:
- # note: the SQL here would be
- # "SHOW VARIABLES LIKE 'character_set%%'"
- cset_name = connection.connection.character_set_name
- except AttributeError:
- util.warn(
- "No 'character_set_name' can be detected with "
- "this MySQL-Python version; "
- "please upgrade to a recent version of MySQL-Python. "
- "Assuming latin1."
- )
- return "latin1"
- else:
- return cset_name()
-
- def get_isolation_level_values(self, dbapi_connection):
- return (
- "SERIALIZABLE",
- "READ UNCOMMITTED",
- "READ COMMITTED",
- "REPEATABLE READ",
- "AUTOCOMMIT",
- )
-
- def set_isolation_level(self, dbapi_connection, level):
- if level == "AUTOCOMMIT":
- dbapi_connection.autocommit(True)
- else:
- dbapi_connection.autocommit(False)
- super().set_isolation_level(dbapi_connection, level)
-
-
-dialect = MySQLDialect_mysqldb
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py
deleted file mode 100644
index 3f05bce..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# dialects/mysql/provision.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-from ... import exc
-from ...testing.provision import configure_follower
-from ...testing.provision import create_db
-from ...testing.provision import drop_db
-from ...testing.provision import generate_driver_url
-from ...testing.provision import temp_table_keyword_args
-from ...testing.provision import upsert
-
-
-@generate_driver_url.for_db("mysql", "mariadb")
-def generate_driver_url(url, driver, query_str):
- backend = url.get_backend_name()
-
- # NOTE: at the moment, tests are running mariadbconnector
- # against both mariadb and mysql backends. if we want this to be
- # limited, do the decision making here to reject a "mysql+mariadbconnector"
- # URL. Optionally also re-enable the module level
- # MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
- # a unit and/or functional test.
-
- # all the Jenkins tests have been running mysqlclient Python library
- # built against mariadb client drivers for years against all MySQL /
- # MariaDB versions going back to MySQL 5.6, currently they can talk
- # to MySQL databases without problems.
-
- if backend == "mysql":
- dialect_cls = url.get_dialect()
- if dialect_cls._is_mariadb_from_url(url):
- backend = "mariadb"
-
- new_url = url.set(
- drivername="%s+%s" % (backend, driver)
- ).update_query_string(query_str)
-
- try:
- new_url.get_dialect()
- except exc.NoSuchModuleError:
- return None
- else:
- return new_url
-
-
-@create_db.for_db("mysql", "mariadb")
-def _mysql_create_db(cfg, eng, ident):
- with eng.begin() as conn:
- try:
- _mysql_drop_db(cfg, conn, ident)
- except Exception:
- pass
-
- with eng.begin() as conn:
- conn.exec_driver_sql(
- "CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
- )
- conn.exec_driver_sql(
- "CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
- )
- conn.exec_driver_sql(
- "CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
- )
-
-
-@configure_follower.for_db("mysql", "mariadb")
-def _mysql_configure_follower(config, ident):
- config.test_schema = "%s_test_schema" % ident
- config.test_schema_2 = "%s_test_schema_2" % ident
-
-
-@drop_db.for_db("mysql", "mariadb")
-def _mysql_drop_db(cfg, eng, ident):
- with eng.begin() as conn:
- conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
- conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
- conn.exec_driver_sql("DROP DATABASE %s" % ident)
-
-
-@temp_table_keyword_args.for_db("mysql", "mariadb")
-def _mysql_temp_table_keyword_args(cfg, eng):
- return {"prefixes": ["TEMPORARY"]}
-
-
-@upsert.for_db("mariadb")
-def _upsert(
- cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False
-):
- from sqlalchemy.dialects.mysql import insert
-
- stmt = insert(table)
-
- if set_lambda:
- stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
- else:
- pk1 = table.primary_key.c[0]
- stmt = stmt.on_duplicate_key_update({pk1.key: pk1})
-
- stmt = stmt.returning(
- *returning, sort_by_parameter_order=sort_by_parameter_order
- )
- return stmt
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py
deleted file mode 100644
index 830e441..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# dialects/mysql/pymysql.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-r"""
-
-.. dialect:: mysql+pymysql
- :name: PyMySQL
- :dbapi: pymysql
- :connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
- :url: https://pymysql.readthedocs.io/
-
-Unicode
--------
-
-Please see :ref:`mysql_unicode` for current recommendations on unicode
-handling.
-
-.. _pymysql_ssl:
-
-SSL Connections
-------------------
-
-The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb,
-described at :ref:`mysqldb_ssl`. See that section for additional examples.
-
-If the server uses an automatically-generated certificate that is self-signed
-or does not match the host name (as seen from the client), it may also be
-necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::
-
- connection_uri = (
- "mysql+pymysql://scott:tiger@192.168.0.134/test"
- "?ssl_ca=/home/gord/client-ssl/ca.pem"
- "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
- "&ssl_key=/home/gord/client-ssl/client-key.pem"
- "&ssl_check_hostname=false"
- )
-
-
-MySQL-Python Compatibility
---------------------------
-
-The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
-and targets 100% compatibility. Most behavioral notes for MySQL-python apply
-to the pymysql driver as well.
-
-""" # noqa
-
-from .mysqldb import MySQLDialect_mysqldb
-from ...util import langhelpers
-
-
-class MySQLDialect_pymysql(MySQLDialect_mysqldb):
- driver = "pymysql"
- supports_statement_cache = True
-
- description_encoding = None
-
- @langhelpers.memoized_property
- def supports_server_side_cursors(self):
- try:
- cursors = __import__("pymysql.cursors").cursors
- self._sscursor = cursors.SSCursor
- return True
- except (ImportError, AttributeError):
- return False
-
- @classmethod
- def import_dbapi(cls):
- return __import__("pymysql")
-
- @langhelpers.memoized_property
- def _send_false_to_ping(self):
- """determine if pymysql has deprecated, changed the default of,
- or removed the 'reconnect' argument of connection.ping().
-
- See #10492 and
- https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
- for background.
-
- """ # noqa: E501
-
- try:
- Connection = __import__(
- "pymysql.connections"
- ).connections.Connection
- except (ImportError, AttributeError):
- return True
- else:
- insp = langhelpers.get_callable_argspec(Connection.ping)
- try:
- reconnect_arg = insp.args[1]
- except IndexError:
- return False
- else:
- return reconnect_arg == "reconnect" and (
- not insp.defaults or insp.defaults[0] is not False
- )
-
- def do_ping(self, dbapi_connection):
- if self._send_false_to_ping:
- dbapi_connection.ping(False)
- else:
- dbapi_connection.ping()
-
- return True
-
- def create_connect_args(self, url, _translate_args=None):
- if _translate_args is None:
- _translate_args = dict(username="user")
- return super().create_connect_args(
- url, _translate_args=_translate_args
- )
-
- def is_disconnect(self, e, connection, cursor):
- if super().is_disconnect(e, connection, cursor):
- return True
- elif isinstance(e, self.dbapi.Error):
- str_e = str(e).lower()
- return (
- "already closed" in str_e or "connection was killed" in str_e
- )
- else:
- return False
-
- def _extract_error_code(self, exception):
- if isinstance(exception.args[0], Exception):
- exception = exception.args[0]
- return exception.args[0]
-
-
-dialect = MySQLDialect_pymysql
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py
deleted file mode 100644
index 428c8df..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# dialects/mysql/pyodbc.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-r"""
-
-
-.. dialect:: mysql+pyodbc
- :name: PyODBC
- :dbapi: pyodbc
- :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
- :url: https://pypi.org/project/pyodbc/
-
-.. note::
-
- The PyODBC for MySQL dialect is **not tested as part of
- SQLAlchemy's continuous integration**.
- The recommended MySQL dialects are mysqlclient and PyMySQL.
- However, if you want to use the mysql+pyodbc dialect and require
- full support for ``utf8mb4`` characters (including supplementary
- characters like emoji) be sure to use a current release of
- MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
- version of the driver in your DSN or connection string.
-
-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;'
- )
- params = urllib.parse.quote_plus(connection_string)
- connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
-
-""" # noqa
-
-import re
-
-from .base import MySQLDialect
-from .base import MySQLExecutionContext
-from .types import TIME
-from ... import exc
-from ... import util
-from ...connectors.pyodbc import PyODBCConnector
-from ...sql.sqltypes import Time
-
-
-class _pyodbcTIME(TIME):
- def result_processor(self, dialect, coltype):
- def process(value):
- # pyodbc returns a datetime.time object; no need to convert
- return value
-
- return process
-
-
-class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
- def get_lastrowid(self):
- cursor = self.create_cursor()
- cursor.execute("SELECT LAST_INSERT_ID()")
- lastrowid = cursor.fetchone()[0]
- cursor.close()
- return lastrowid
-
-
-class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
- supports_statement_cache = True
- colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
- supports_unicode_statements = True
- execution_ctx_cls = MySQLExecutionContext_pyodbc
-
- pyodbc_driver_name = "MySQL"
-
- def _detect_charset(self, connection):
- """Sniff out the character set in use for connection results."""
-
- # Prefer 'character_set_results' for the current connection over the
- # value in the driver. SET NAMES or individual variable SETs will
- # change the charset without updating the driver's view of the world.
- #
- # If it's decided that issuing that sort of SQL leaves you SOL, then
- # this can prefer the driver value.
-
- # set this to None as _fetch_setting attempts to use it (None is OK)
- self._connection_charset = None
- try:
- value = self._fetch_setting(connection, "character_set_client")
- if value:
- return value
- except exc.DBAPIError:
- pass
-
- util.warn(
- "Could not detect the connection character set. "
- "Assuming latin1."
- )
- return "latin1"
-
- def _get_server_version_info(self, connection):
- return MySQLDialect._get_server_version_info(self, connection)
-
- def _extract_error_code(self, exception):
- m = re.compile(r"\((\d+)\)").search(str(exception.args))
- c = m.group(1)
- if c:
- return int(c)
- else:
- return None
-
- def on_connect(self):
- super_ = super().on_connect()
-
- def on_connect(conn):
- if super_ is not None:
- super_(conn)
-
- # declare Unicode encoding for pyodbc as per
- # https://github.com/mkleehammer/pyodbc/wiki/Unicode
- pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
- pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
- conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
- conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
- conn.setencoding(encoding="utf-8")
-
- return on_connect
-
-
-dialect = MySQLDialect_pyodbc
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py
deleted file mode 100644
index c764e8c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py
+++ /dev/null
@@ -1,677 +0,0 @@
-# dialects/mysql/reflection.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-import re
-
-from .enumerated import ENUM
-from .enumerated import SET
-from .types import DATETIME
-from .types import TIME
-from .types import TIMESTAMP
-from ... import log
-from ... import types as sqltypes
-from ... import util
-
-
-class ReflectedState:
- """Stores raw information about a SHOW CREATE TABLE statement."""
-
- def __init__(self):
- self.columns = []
- self.table_options = {}
- self.table_name = None
- self.keys = []
- self.fk_constraints = []
- self.ck_constraints = []
-
-
-@log.class_logger
-class MySQLTableDefinitionParser:
- """Parses the results of a SHOW CREATE TABLE statement."""
-
- def __init__(self, dialect, preparer):
- self.dialect = dialect
- self.preparer = preparer
- self._prep_regexes()
-
- def parse(self, show_create, charset):
- state = ReflectedState()
- state.charset = charset
- for line in re.split(r"\r?\n", show_create):
- if line.startswith(" " + self.preparer.initial_quote):
- self._parse_column(line, state)
- # a regular table options line
- elif line.startswith(") "):
- self._parse_table_options(line, state)
- # an ANSI-mode table options line
- elif line == ")":
- pass
- elif line.startswith("CREATE "):
- self._parse_table_name(line, state)
- elif "PARTITION" in line:
- self._parse_partition_options(line, state)
- # Not present in real reflection, but may be if
- # loading from a file.
- elif not line:
- pass
- else:
- type_, spec = self._parse_constraints(line)
- if type_ is None:
- util.warn("Unknown schema content: %r" % line)
- elif type_ == "key":
- state.keys.append(spec)
- elif type_ == "fk_constraint":
- state.fk_constraints.append(spec)
- elif type_ == "ck_constraint":
- state.ck_constraints.append(spec)
- else:
- pass
- return state
-
- def _check_view(self, sql: str) -> bool:
- return bool(self._re_is_view.match(sql))
-
- def _parse_constraints(self, line):
- """Parse a KEY or CONSTRAINT line.
-
- :param line: A line of SHOW CREATE TABLE output
- """
-
- # KEY
- m = self._re_key.match(line)
- if m:
- spec = m.groupdict()
- # convert columns into name, length pairs
- # NOTE: we may want to consider SHOW INDEX as the
- # format of indexes in MySQL becomes more complex
- spec["columns"] = self._parse_keyexprs(spec["columns"])
- if spec["version_sql"]:
- m2 = self._re_key_version_sql.match(spec["version_sql"])
- if m2 and m2.groupdict()["parser"]:
- spec["parser"] = m2.groupdict()["parser"]
- if spec["parser"]:
- spec["parser"] = self.preparer.unformat_identifiers(
- spec["parser"]
- )[0]
- return "key", spec
-
- # FOREIGN KEY CONSTRAINT
- m = self._re_fk_constraint.match(line)
- if m:
- spec = m.groupdict()
- spec["table"] = self.preparer.unformat_identifiers(spec["table"])
- spec["local"] = [c[0] for c in self._parse_keyexprs(spec["local"])]
- spec["foreign"] = [
- c[0] for c in self._parse_keyexprs(spec["foreign"])
- ]
- return "fk_constraint", spec
-
- # CHECK constraint
- m = self._re_ck_constraint.match(line)
- if m:
- spec = m.groupdict()
- return "ck_constraint", spec
-
- # PARTITION and SUBPARTITION
- m = self._re_partition.match(line)
- if m:
- # Punt!
- return "partition", line
-
- # No match.
- return (None, line)
-
- def _parse_table_name(self, line, state):
- """Extract the table name.
-
- :param line: The first line of SHOW CREATE TABLE
- """
-
- regex, cleanup = self._pr_name
- m = regex.match(line)
- if m:
- state.table_name = cleanup(m.group("name"))
-
- def _parse_table_options(self, line, state):
- """Build a dictionary of all reflected table-level options.
-
- :param line: The final line of SHOW CREATE TABLE output.
- """
-
- options = {}
-
- if line and line != ")":
- rest_of_line = line
- for regex, cleanup in self._pr_options:
- m = regex.search(rest_of_line)
- if not m:
- continue
- directive, value = m.group("directive"), m.group("val")
- if cleanup:
- value = cleanup(value)
- options[directive.lower()] = value
- rest_of_line = regex.sub("", rest_of_line)
-
- for nope in ("auto_increment", "data directory", "index directory"):
- options.pop(nope, None)
-
- for opt, val in options.items():
- state.table_options["%s_%s" % (self.dialect.name, opt)] = val
-
- def _parse_partition_options(self, line, state):
- options = {}
- new_line = line[:]
-
- while new_line.startswith("(") or new_line.startswith(" "):
- new_line = new_line[1:]
-
- for regex, cleanup in self._pr_options:
- m = regex.search(new_line)
- if not m or "PARTITION" not in regex.pattern:
- continue
-
- directive = m.group("directive")
- directive = directive.lower()
- is_subpartition = directive == "subpartition"
-
- if directive == "partition" or is_subpartition:
- new_line = new_line.replace(") */", "")
- new_line = new_line.replace(",", "")
- if is_subpartition and new_line.endswith(")"):
- new_line = new_line[:-1]
- if self.dialect.name == "mariadb" and new_line.endswith(")"):
- if (
- "MAXVALUE" in new_line
- or "MINVALUE" in new_line
- or "ENGINE" in new_line
- ):
- # final line of MariaDB partition endswith ")"
- new_line = new_line[:-1]
-
- defs = "%s_%s_definitions" % (self.dialect.name, directive)
- options[defs] = new_line
-
- else:
- directive = directive.replace(" ", "_")
- value = m.group("val")
- if cleanup:
- value = cleanup(value)
- options[directive] = value
- break
-
- for opt, val in options.items():
- part_def = "%s_partition_definitions" % (self.dialect.name)
- subpart_def = "%s_subpartition_definitions" % (self.dialect.name)
- if opt == part_def or opt == subpart_def:
- # builds a string of definitions
- if opt not in state.table_options:
- state.table_options[opt] = val
- else:
- state.table_options[opt] = "%s, %s" % (
- state.table_options[opt],
- val,
- )
- else:
- state.table_options["%s_%s" % (self.dialect.name, opt)] = val
-
- def _parse_column(self, line, state):
- """Extract column details.
-
- Falls back to a 'minimal support' variant if full parse fails.
-
- :param line: Any column-bearing line from SHOW CREATE TABLE
- """
-
- spec = None
- m = self._re_column.match(line)
- if m:
- spec = m.groupdict()
- spec["full"] = True
- else:
- m = self._re_column_loose.match(line)
- if m:
- spec = m.groupdict()
- spec["full"] = False
- if not spec:
- util.warn("Unknown column definition %r" % line)
- return
- if not spec["full"]:
- util.warn("Incomplete reflection of column definition %r" % line)
-
- name, type_, args = spec["name"], spec["coltype"], spec["arg"]
-
- try:
- col_type = self.dialect.ischema_names[type_]
- except KeyError:
- util.warn(
- "Did not recognize type '%s' of column '%s'" % (type_, name)
- )
- col_type = sqltypes.NullType
-
- # Column type positional arguments eg. varchar(32)
- if args is None or args == "":
- type_args = []
- elif args[0] == "'" and args[-1] == "'":
- type_args = self._re_csv_str.findall(args)
- else:
- type_args = [int(v) for v in self._re_csv_int.findall(args)]
-
- # Column type keyword options
- type_kw = {}
-
- if issubclass(col_type, (DATETIME, TIME, TIMESTAMP)):
- if type_args:
- type_kw["fsp"] = type_args.pop(0)
-
- for kw in ("unsigned", "zerofill"):
- if spec.get(kw, False):
- type_kw[kw] = True
- for kw in ("charset", "collate"):
- if spec.get(kw, False):
- type_kw[kw] = spec[kw]
- if issubclass(col_type, (ENUM, SET)):
- type_args = _strip_values(type_args)
-
- if issubclass(col_type, SET) and "" in type_args:
- type_kw["retrieve_as_bitwise"] = True
-
- type_instance = col_type(*type_args, **type_kw)
-
- col_kw = {}
-
- # NOT NULL
- col_kw["nullable"] = True
- # this can be "NULL" in the case of TIMESTAMP
- if spec.get("notnull", False) == "NOT NULL":
- col_kw["nullable"] = False
- # For generated columns, the nullability is marked in a different place
- if spec.get("notnull_generated", False) == "NOT NULL":
- col_kw["nullable"] = False
-
- # AUTO_INCREMENT
- if spec.get("autoincr", False):
- col_kw["autoincrement"] = True
- elif issubclass(col_type, sqltypes.Integer):
- col_kw["autoincrement"] = False
-
- # DEFAULT
- default = spec.get("default", None)
-
- if default == "NULL":
- # eliminates the need to deal with this later.
- default = None
-
- comment = spec.get("comment", None)
-
- if comment is not None:
- comment = cleanup_text(comment)
-
- sqltext = spec.get("generated")
- if sqltext is not None:
- computed = dict(sqltext=sqltext)
- persisted = spec.get("persistence")
- if persisted is not None:
- computed["persisted"] = persisted == "STORED"
- col_kw["computed"] = computed
-
- col_d = dict(
- name=name, type=type_instance, default=default, comment=comment
- )
- col_d.update(col_kw)
- state.columns.append(col_d)
-
- def _describe_to_create(self, table_name, columns):
- """Re-format DESCRIBE output as a SHOW CREATE TABLE string.
-
- DESCRIBE is a much simpler reflection and is sufficient for
- reflecting views for runtime use. This method formats DDL
- for columns only- keys are omitted.
-
- :param columns: A sequence of DESCRIBE or SHOW COLUMNS 6-tuples.
- SHOW FULL COLUMNS FROM rows must be rearranged for use with
- this function.
- """
-
- buffer = []
- for row in columns:
- (name, col_type, nullable, default, extra) = (
- row[i] for i in (0, 1, 2, 4, 5)
- )
-
- line = [" "]
- line.append(self.preparer.quote_identifier(name))
- line.append(col_type)
- if not nullable:
- line.append("NOT NULL")
- if default:
- if "auto_increment" in default:
- pass
- elif col_type.startswith("timestamp") and default.startswith(
- "C"
- ):
- line.append("DEFAULT")
- line.append(default)
- elif default == "NULL":
- line.append("DEFAULT")
- line.append(default)
- else:
- line.append("DEFAULT")
- line.append("'%s'" % default.replace("'", "''"))
- if extra:
- line.append(extra)
-
- buffer.append(" ".join(line))
-
- return "".join(
- [
- (
- "CREATE TABLE %s (\n"
- % self.preparer.quote_identifier(table_name)
- ),
- ",\n".join(buffer),
- "\n) ",
- ]
- )
-
- def _parse_keyexprs(self, identifiers):
- """Unpack '"col"(2),"col" ASC'-ish strings into components."""
-
- return [
- (colname, int(length) if length else None, modifiers)
- for colname, length, modifiers in self._re_keyexprs.findall(
- identifiers
- )
- ]
-
- def _prep_regexes(self):
- """Pre-compile regular expressions."""
-
- self._re_columns = []
- self._pr_options = []
-
- _final = self.preparer.final_quote
-
- quotes = dict(
- zip(
- ("iq", "fq", "esc_fq"),
- [
- re.escape(s)
- for s in (
- self.preparer.initial_quote,
- _final,
- self.preparer._escape_identifier(_final),
- )
- ],
- )
- )
-
- self._pr_name = _pr_compile(
- r"^CREATE (?:\w+ +)?TABLE +"
- r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +\($" % quotes,
- self.preparer._unescape_identifier,
- )
-
- self._re_is_view = _re_compile(r"^CREATE(?! TABLE)(\s.*)?\sVIEW")
-
- # `col`,`col2`(32),`col3`(15) DESC
- #
- self._re_keyexprs = _re_compile(
- r"(?:"
- r"(?:%(iq)s((?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)"
- r"(?:\((\d+)\))?(?: +(ASC|DESC))?(?=\,|$))+" % quotes
- )
-
- # 'foo' or 'foo','bar' or 'fo,o','ba''a''r'
- self._re_csv_str = _re_compile(r"\x27(?:\x27\x27|[^\x27])*\x27")
-
- # 123 or 123,456
- self._re_csv_int = _re_compile(r"\d+")
-
- # `colname` <type> [type opts]
- # (NOT NULL | NULL)
- # DEFAULT ('value' | CURRENT_TIMESTAMP...)
- # COMMENT 'comment'
- # COLUMN_FORMAT (FIXED|DYNAMIC|DEFAULT)
- # STORAGE (DISK|MEMORY)
- self._re_column = _re_compile(
- r" "
- r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
- r"(?P<coltype>\w+)"
- r"(?:\((?P<arg>(?:\d+|\d+,\d+|"
- r"(?:'(?:''|[^'])*',?)+))\))?"
- r"(?: +(?P<unsigned>UNSIGNED))?"
- r"(?: +(?P<zerofill>ZEROFILL))?"
- r"(?: +CHARACTER SET +(?P<charset>[\w_]+))?"
- r"(?: +COLLATE +(?P<collate>[\w_]+))?"
- r"(?: +(?P<notnull>(?:NOT )?NULL))?"
- r"(?: +DEFAULT +(?P<default>"
- r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+"
- r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
- r"))?"
- r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
- r".*\))? ?(?P<persistence>VIRTUAL|STORED)?"
- r"(?: +(?P<notnull_generated>(?:NOT )?NULL))?"
- r")?"
- r"(?: +(?P<autoincr>AUTO_INCREMENT))?"
- r"(?: +COMMENT +'(?P<comment>(?:''|[^'])*)')?"
- r"(?: +COLUMN_FORMAT +(?P<colfmt>\w+))?"
- r"(?: +STORAGE +(?P<storage>\w+))?"
- r"(?: +(?P<extra>.*))?"
- r",?$" % quotes
- )
-
- # Fallback, try to parse as little as possible
- self._re_column_loose = _re_compile(
- r" "
- r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
- r"(?P<coltype>\w+)"
- r"(?:\((?P<arg>(?:\d+|\d+,\d+|\x27(?:\x27\x27|[^\x27])+\x27))\))?"
- r".*?(?P<notnull>(?:NOT )NULL)?" % quotes
- )
-
- # (PRIMARY|UNIQUE|FULLTEXT|SPATIAL) INDEX `name` (USING (BTREE|HASH))?
- # (`col` (ASC|DESC)?, `col` (ASC|DESC)?)
- # KEY_BLOCK_SIZE size | WITH PARSER name /*!50100 WITH PARSER name */
- self._re_key = _re_compile(
- r" "
- r"(?:(?P<type>\S+) )?KEY"
- r"(?: +%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?"
- r"(?: +USING +(?P<using_pre>\S+))?"
- r" +\((?P<columns>.+?)\)"
- r"(?: +USING +(?P<using_post>\S+))?"
- r"(?: +KEY_BLOCK_SIZE *[ =]? *(?P<keyblock>\S+))?"
- r"(?: +WITH PARSER +(?P<parser>\S+))?"
- r"(?: +COMMENT +(?P<comment>(\x27\x27|\x27([^\x27])*?\x27)+))?"
- r"(?: +/\*(?P<version_sql>.+)\*/ *)?"
- r",?$" % quotes
- )
-
- # https://forums.mysql.com/read.php?20,567102,567111#msg-567111
- # It means if the MySQL version >= \d+, execute what's in the comment
- self._re_key_version_sql = _re_compile(
- r"\!\d+ " r"(?: *WITH PARSER +(?P<parser>\S+) *)?"
- )
-
- # CONSTRAINT `name` FOREIGN KEY (`local_col`)
- # REFERENCES `remote` (`remote_col`)
- # MATCH FULL | MATCH PARTIAL | MATCH SIMPLE
- # ON DELETE CASCADE ON UPDATE RESTRICT
- #
- # unique constraints come back as KEYs
- kw = quotes.copy()
- kw["on"] = "RESTRICT|CASCADE|SET NULL|NO ACTION"
- self._re_fk_constraint = _re_compile(
- r" "
- r"CONSTRAINT +"
- r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
- r"FOREIGN KEY +"
- r"\((?P<local>[^\)]+?)\) REFERENCES +"
- r"(?P<table>%(iq)s[^%(fq)s]+%(fq)s"
- r"(?:\.%(iq)s[^%(fq)s]+%(fq)s)?) +"
- r"\((?P<foreign>(?:%(iq)s[^%(fq)s]+%(fq)s(?: *, *)?)+)\)"
- r"(?: +(?P<match>MATCH \w+))?"
- r"(?: +ON DELETE (?P<ondelete>%(on)s))?"
- r"(?: +ON UPDATE (?P<onupdate>%(on)s))?" % kw
- )
-
- # CONSTRAINT `CONSTRAINT_1` CHECK (`x` > 5)'
- # testing on MariaDB 10.2 shows that the CHECK constraint
- # is returned on a line by itself, so to match without worrying
- # about parenthesis in the expression we go to the end of the line
- self._re_ck_constraint = _re_compile(
- r" "
- r"CONSTRAINT +"
- r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +"
- r"CHECK +"
- r"\((?P<sqltext>.+)\),?" % kw
- )
-
- # PARTITION
- #
- # punt!
- self._re_partition = _re_compile(r"(?:.*)(?:SUB)?PARTITION(?:.*)")
-
- # Table-level options (COLLATE, ENGINE, etc.)
- # Do the string options first, since they have quoted
- # strings we need to get rid of.
- for option in _options_of_type_string:
- self._add_option_string(option)
-
- for option in (
- "ENGINE",
- "TYPE",
- "AUTO_INCREMENT",
- "AVG_ROW_LENGTH",
- "CHARACTER SET",
- "DEFAULT CHARSET",
- "CHECKSUM",
- "COLLATE",
- "DELAY_KEY_WRITE",
- "INSERT_METHOD",
- "MAX_ROWS",
- "MIN_ROWS",
- "PACK_KEYS",
- "ROW_FORMAT",
- "KEY_BLOCK_SIZE",
- "STATS_SAMPLE_PAGES",
- ):
- self._add_option_word(option)
-
- for option in (
- "PARTITION BY",
- "SUBPARTITION BY",
- "PARTITIONS",
- "SUBPARTITIONS",
- "PARTITION",
- "SUBPARTITION",
- ):
- self._add_partition_option_word(option)
-
- self._add_option_regex("UNION", r"\([^\)]+\)")
- self._add_option_regex("TABLESPACE", r".*? STORAGE DISK")
- self._add_option_regex(
- "RAID_TYPE",
- r"\w+\s+RAID_CHUNKS\s*\=\s*\w+RAID_CHUNKSIZE\s*=\s*\w+",
- )
-
- _optional_equals = r"(?:\s*(?:=\s*)|\s+)"
-
- def _add_option_string(self, directive):
- regex = r"(?P<directive>%s)%s" r"'(?P<val>(?:[^']|'')*?)'(?!')" % (
- re.escape(directive),
- self._optional_equals,
- )
- self._pr_options.append(_pr_compile(regex, cleanup_text))
-
- def _add_option_word(self, directive):
- regex = r"(?P<directive>%s)%s" r"(?P<val>\w+)" % (
- re.escape(directive),
- self._optional_equals,
- )
- self._pr_options.append(_pr_compile(regex))
-
- def _add_partition_option_word(self, directive):
- if directive == "PARTITION BY" or directive == "SUBPARTITION BY":
- regex = r"(?<!\S)(?P<directive>%s)%s" r"(?P<val>\w+.*)" % (
- re.escape(directive),
- self._optional_equals,
- )
- elif directive == "SUBPARTITIONS" or directive == "PARTITIONS":
- regex = r"(?<!\S)(?P<directive>%s)%s" r"(?P<val>\d+)" % (
- re.escape(directive),
- self._optional_equals,
- )
- else:
- regex = r"(?<!\S)(?P<directive>%s)(?!\S)" % (re.escape(directive),)
- self._pr_options.append(_pr_compile(regex))
-
- def _add_option_regex(self, directive, regex):
- regex = r"(?P<directive>%s)%s" r"(?P<val>%s)" % (
- re.escape(directive),
- self._optional_equals,
- regex,
- )
- self._pr_options.append(_pr_compile(regex))
-
-
-_options_of_type_string = (
- "COMMENT",
- "DATA DIRECTORY",
- "INDEX DIRECTORY",
- "PASSWORD",
- "CONNECTION",
-)
-
-
-def _pr_compile(regex, cleanup=None):
- """Prepare a 2-tuple of compiled regex and callable."""
-
- return (_re_compile(regex), cleanup)
-
-
-def _re_compile(regex):
- """Compile a string to regex, I and UNICODE."""
-
- return re.compile(regex, re.I | re.UNICODE)
-
-
-def _strip_values(values):
- "Strip reflected values quotes"
- strip_values = []
- for a in values:
- if a[0:1] == '"' or a[0:1] == "'":
- # strip enclosing quotes and unquote interior
- a = a[1:-1].replace(a[0] * 2, a[0])
- strip_values.append(a)
- return strip_values
-
-
-def cleanup_text(raw_text: str) -> str:
- if "\\" in raw_text:
- raw_text = re.sub(
- _control_char_regexp, lambda s: _control_char_map[s[0]], raw_text
- )
- return raw_text.replace("''", "'")
-
-
-_control_char_map = {
- "\\\\": "\\",
- "\\0": "\0",
- "\\a": "\a",
- "\\b": "\b",
- "\\t": "\t",
- "\\n": "\n",
- "\\v": "\v",
- "\\f": "\f",
- "\\r": "\r",
- # '\\e':'\e',
-}
-_control_char_regexp = re.compile(
- "|".join(re.escape(k) for k in _control_char_map)
-)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py
deleted file mode 100644
index 04764c1..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# dialects/mysql/reserved_words.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-# generated using:
-# https://gist.github.com/kkirsche/4f31f2153ed7a3248be1ec44ca6ddbc9
-#
-# https://mariadb.com/kb/en/reserved-words/
-# includes: Reserved Words, Oracle Mode (separate set unioned)
-# excludes: Exceptions, Function Names
-# mypy: ignore-errors
-
-RESERVED_WORDS_MARIADB = {
- "accessible",
- "add",
- "all",
- "alter",
- "analyze",
- "and",
- "as",
- "asc",
- "asensitive",
- "before",
- "between",
- "bigint",
- "binary",
- "blob",
- "both",
- "by",
- "call",
- "cascade",
- "case",
- "change",
- "char",
- "character",
- "check",
- "collate",
- "column",
- "condition",
- "constraint",
- "continue",
- "convert",
- "create",
- "cross",
- "current_date",
- "current_role",
- "current_time",
- "current_timestamp",
- "current_user",
- "cursor",
- "database",
- "databases",
- "day_hour",
- "day_microsecond",
- "day_minute",
- "day_second",
- "dec",
- "decimal",
- "declare",
- "default",
- "delayed",
- "delete",
- "desc",
- "describe",
- "deterministic",
- "distinct",
- "distinctrow",
- "div",
- "do_domain_ids",
- "double",
- "drop",
- "dual",
- "each",
- "else",
- "elseif",
- "enclosed",
- "escaped",
- "except",
- "exists",
- "exit",
- "explain",
- "false",
- "fetch",
- "float",
- "float4",
- "float8",
- "for",
- "force",
- "foreign",
- "from",
- "fulltext",
- "general",
- "grant",
- "group",
- "having",
- "high_priority",
- "hour_microsecond",
- "hour_minute",
- "hour_second",
- "if",
- "ignore",
- "ignore_domain_ids",
- "ignore_server_ids",
- "in",
- "index",
- "infile",
- "inner",
- "inout",
- "insensitive",
- "insert",
- "int",
- "int1",
- "int2",
- "int3",
- "int4",
- "int8",
- "integer",
- "intersect",
- "interval",
- "into",
- "is",
- "iterate",
- "join",
- "key",
- "keys",
- "kill",
- "leading",
- "leave",
- "left",
- "like",
- "limit",
- "linear",
- "lines",
- "load",
- "localtime",
- "localtimestamp",
- "lock",
- "long",
- "longblob",
- "longtext",
- "loop",
- "low_priority",
- "master_heartbeat_period",
- "master_ssl_verify_server_cert",
- "match",
- "maxvalue",
- "mediumblob",
- "mediumint",
- "mediumtext",
- "middleint",
- "minute_microsecond",
- "minute_second",
- "mod",
- "modifies",
- "natural",
- "no_write_to_binlog",
- "not",
- "null",
- "numeric",
- "offset",
- "on",
- "optimize",
- "option",
- "optionally",
- "or",
- "order",
- "out",
- "outer",
- "outfile",
- "over",
- "page_checksum",
- "parse_vcol_expr",
- "partition",
- "position",
- "precision",
- "primary",
- "procedure",
- "purge",
- "range",
- "read",
- "read_write",
- "reads",
- "real",
- "recursive",
- "ref_system_id",
- "references",
- "regexp",
- "release",
- "rename",
- "repeat",
- "replace",
- "require",
- "resignal",
- "restrict",
- "return",
- "returning",
- "revoke",
- "right",
- "rlike",
- "rows",
- "row_number",
- "schema",
- "schemas",
- "second_microsecond",
- "select",
- "sensitive",
- "separator",
- "set",
- "show",
- "signal",
- "slow",
- "smallint",
- "spatial",
- "specific",
- "sql",
- "sql_big_result",
- "sql_calc_found_rows",
- "sql_small_result",
- "sqlexception",
- "sqlstate",
- "sqlwarning",
- "ssl",
- "starting",
- "stats_auto_recalc",
- "stats_persistent",
- "stats_sample_pages",
- "straight_join",
- "table",
- "terminated",
- "then",
- "tinyblob",
- "tinyint",
- "tinytext",
- "to",
- "trailing",
- "trigger",
- "true",
- "undo",
- "union",
- "unique",
- "unlock",
- "unsigned",
- "update",
- "usage",
- "use",
- "using",
- "utc_date",
- "utc_time",
- "utc_timestamp",
- "values",
- "varbinary",
- "varchar",
- "varcharacter",
- "varying",
- "when",
- "where",
- "while",
- "window",
- "with",
- "write",
- "xor",
- "year_month",
- "zerofill",
-}.union(
- {
- "body",
- "elsif",
- "goto",
- "history",
- "others",
- "package",
- "period",
- "raise",
- "rowtype",
- "system",
- "system_time",
- "versioning",
- "without",
- }
-)
-
-# 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
-# includes: MySQL x.0 Keywords and Reserved Words
-# excludes: MySQL x.0 New Keywords and Reserved Words,
-# MySQL x.0 Removed Keywords and Reserved Words
-RESERVED_WORDS_MYSQL = {
- "accessible",
- "add",
- "admin",
- "all",
- "alter",
- "analyze",
- "and",
- "array",
- "as",
- "asc",
- "asensitive",
- "before",
- "between",
- "bigint",
- "binary",
- "blob",
- "both",
- "by",
- "call",
- "cascade",
- "case",
- "change",
- "char",
- "character",
- "check",
- "collate",
- "column",
- "condition",
- "constraint",
- "continue",
- "convert",
- "create",
- "cross",
- "cube",
- "cume_dist",
- "current_date",
- "current_time",
- "current_timestamp",
- "current_user",
- "cursor",
- "database",
- "databases",
- "day_hour",
- "day_microsecond",
- "day_minute",
- "day_second",
- "dec",
- "decimal",
- "declare",
- "default",
- "delayed",
- "delete",
- "dense_rank",
- "desc",
- "describe",
- "deterministic",
- "distinct",
- "distinctrow",
- "div",
- "double",
- "drop",
- "dual",
- "each",
- "else",
- "elseif",
- "empty",
- "enclosed",
- "escaped",
- "except",
- "exists",
- "exit",
- "explain",
- "false",
- "fetch",
- "first_value",
- "float",
- "float4",
- "float8",
- "for",
- "force",
- "foreign",
- "from",
- "fulltext",
- "function",
- "general",
- "generated",
- "get",
- "get_master_public_key",
- "grant",
- "group",
- "grouping",
- "groups",
- "having",
- "high_priority",
- "hour_microsecond",
- "hour_minute",
- "hour_second",
- "if",
- "ignore",
- "ignore_server_ids",
- "in",
- "index",
- "infile",
- "inner",
- "inout",
- "insensitive",
- "insert",
- "int",
- "int1",
- "int2",
- "int3",
- "int4",
- "int8",
- "integer",
- "intersect",
- "interval",
- "into",
- "io_after_gtids",
- "io_before_gtids",
- "is",
- "iterate",
- "join",
- "json_table",
- "key",
- "keys",
- "kill",
- "lag",
- "last_value",
- "lateral",
- "lead",
- "leading",
- "leave",
- "left",
- "like",
- "limit",
- "linear",
- "lines",
- "load",
- "localtime",
- "localtimestamp",
- "lock",
- "long",
- "longblob",
- "longtext",
- "loop",
- "low_priority",
- "master_bind",
- "master_heartbeat_period",
- "master_ssl_verify_server_cert",
- "match",
- "maxvalue",
- "mediumblob",
- "mediumint",
- "mediumtext",
- "member",
- "middleint",
- "minute_microsecond",
- "minute_second",
- "mod",
- "modifies",
- "natural",
- "no_write_to_binlog",
- "not",
- "nth_value",
- "ntile",
- "null",
- "numeric",
- "of",
- "on",
- "optimize",
- "optimizer_costs",
- "option",
- "optionally",
- "or",
- "order",
- "out",
- "outer",
- "outfile",
- "over",
- "parse_gcol_expr",
- "parallel",
- "partition",
- "percent_rank",
- "persist",
- "persist_only",
- "precision",
- "primary",
- "procedure",
- "purge",
- "qualify",
- "range",
- "rank",
- "read",
- "read_write",
- "reads",
- "real",
- "recursive",
- "references",
- "regexp",
- "release",
- "rename",
- "repeat",
- "replace",
- "require",
- "resignal",
- "restrict",
- "return",
- "revoke",
- "right",
- "rlike",
- "role",
- "row",
- "row_number",
- "rows",
- "schema",
- "schemas",
- "second_microsecond",
- "select",
- "sensitive",
- "separator",
- "set",
- "show",
- "signal",
- "slow",
- "smallint",
- "spatial",
- "specific",
- "sql",
- "sql_after_gtids",
- "sql_before_gtids",
- "sql_big_result",
- "sql_calc_found_rows",
- "sql_small_result",
- "sqlexception",
- "sqlstate",
- "sqlwarning",
- "ssl",
- "starting",
- "stored",
- "straight_join",
- "system",
- "table",
- "terminated",
- "then",
- "tinyblob",
- "tinyint",
- "tinytext",
- "to",
- "trailing",
- "trigger",
- "true",
- "undo",
- "union",
- "unique",
- "unlock",
- "unsigned",
- "update",
- "usage",
- "use",
- "using",
- "utc_date",
- "utc_time",
- "utc_timestamp",
- "values",
- "varbinary",
- "varchar",
- "varcharacter",
- "varying",
- "virtual",
- "when",
- "where",
- "while",
- "window",
- "with",
- "write",
- "xor",
- "year_month",
- "zerofill",
-}
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py
deleted file mode 100644
index 734f6ae..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py
+++ /dev/null
@@ -1,774 +0,0 @@
-# dialects/mysql/types.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: ignore-errors
-
-
-import datetime
-
-from ... import exc
-from ... import util
-from ...sql import sqltypes
-
-
-class _NumericType:
- """Base for MySQL numeric types.
-
- This is the base both for NUMERIC as well as INTEGER, hence
- it's a mixin.
-
- """
-
- def __init__(self, unsigned=False, zerofill=False, **kw):
- self.unsigned = unsigned
- self.zerofill = zerofill
- super().__init__(**kw)
-
- def __repr__(self):
- return util.generic_repr(
- self, to_inspect=[_NumericType, sqltypes.Numeric]
- )
-
-
-class _FloatType(_NumericType, sqltypes.Float):
- def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
- if isinstance(self, (REAL, DOUBLE)) and (
- (precision is None and scale is not None)
- or (precision is not None and scale is None)
- ):
- raise exc.ArgumentError(
- "You must specify both precision and scale or omit "
- "both altogether."
- )
- super().__init__(precision=precision, asdecimal=asdecimal, **kw)
- self.scale = scale
-
- def __repr__(self):
- return util.generic_repr(
- self, to_inspect=[_FloatType, _NumericType, sqltypes.Float]
- )
-
-
-class _IntegerType(_NumericType, sqltypes.Integer):
- def __init__(self, display_width=None, **kw):
- self.display_width = display_width
- super().__init__(**kw)
-
- def __repr__(self):
- return util.generic_repr(
- self, to_inspect=[_IntegerType, _NumericType, sqltypes.Integer]
- )
-
-
-class _StringType(sqltypes.String):
- """Base for MySQL string types."""
-
- def __init__(
- self,
- charset=None,
- collation=None,
- ascii=False, # noqa
- binary=False,
- unicode=False,
- national=False,
- **kw,
- ):
- self.charset = charset
-
- # allow collate= or collation=
- kw.setdefault("collation", kw.pop("collate", collation))
-
- self.ascii = ascii
- self.unicode = unicode
- self.binary = binary
- self.national = national
- super().__init__(**kw)
-
- def __repr__(self):
- return util.generic_repr(
- self, to_inspect=[_StringType, sqltypes.String]
- )
-
-
-class _MatchType(sqltypes.Float, sqltypes.MatchType):
- def __init__(self, **kw):
- # TODO: float arguments?
- sqltypes.Float.__init__(self)
- sqltypes.MatchType.__init__(self)
-
-
-class NUMERIC(_NumericType, sqltypes.NUMERIC):
- """MySQL NUMERIC type."""
-
- __visit_name__ = "NUMERIC"
-
- def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
- """Construct a NUMERIC.
-
- :param precision: Total digits in this number. If scale and precision
- are both None, values are stored to limits allowed by the server.
-
- :param scale: The number of digits after the decimal point.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(
- precision=precision, scale=scale, asdecimal=asdecimal, **kw
- )
-
-
-class DECIMAL(_NumericType, sqltypes.DECIMAL):
- """MySQL DECIMAL type."""
-
- __visit_name__ = "DECIMAL"
-
- def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
- """Construct a DECIMAL.
-
- :param precision: Total digits in this number. If scale and precision
- are both None, values are stored to limits allowed by the server.
-
- :param scale: The number of digits after the decimal point.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(
- precision=precision, scale=scale, asdecimal=asdecimal, **kw
- )
-
-
-class DOUBLE(_FloatType, sqltypes.DOUBLE):
- """MySQL DOUBLE type."""
-
- __visit_name__ = "DOUBLE"
-
- def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
- """Construct a DOUBLE.
-
- .. note::
-
- The :class:`.DOUBLE` type by default converts from float
- to Decimal, using a truncation that defaults to 10 digits.
- Specify either ``scale=n`` or ``decimal_return_scale=n`` in order
- to change this scale, or ``asdecimal=False`` to return values
- directly as Python floating points.
-
- :param precision: Total digits in this number. If scale and precision
- are both None, values are stored to limits allowed by the server.
-
- :param scale: The number of digits after the decimal point.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(
- precision=precision, scale=scale, asdecimal=asdecimal, **kw
- )
-
-
-class REAL(_FloatType, sqltypes.REAL):
- """MySQL REAL type."""
-
- __visit_name__ = "REAL"
-
- def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
- """Construct a REAL.
-
- .. note::
-
- The :class:`.REAL` type by default converts from float
- to Decimal, using a truncation that defaults to 10 digits.
- Specify either ``scale=n`` or ``decimal_return_scale=n`` in order
- to change this scale, or ``asdecimal=False`` to return values
- directly as Python floating points.
-
- :param precision: Total digits in this number. If scale and precision
- are both None, values are stored to limits allowed by the server.
-
- :param scale: The number of digits after the decimal point.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(
- precision=precision, scale=scale, asdecimal=asdecimal, **kw
- )
-
-
-class FLOAT(_FloatType, sqltypes.FLOAT):
- """MySQL FLOAT type."""
-
- __visit_name__ = "FLOAT"
-
- def __init__(self, precision=None, scale=None, asdecimal=False, **kw):
- """Construct a FLOAT.
-
- :param precision: Total digits in this number. If scale and precision
- are both None, values are stored to limits allowed by the server.
-
- :param scale: The number of digits after the decimal point.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(
- precision=precision, scale=scale, asdecimal=asdecimal, **kw
- )
-
- def bind_processor(self, dialect):
- return None
-
-
-class INTEGER(_IntegerType, sqltypes.INTEGER):
- """MySQL INTEGER type."""
-
- __visit_name__ = "INTEGER"
-
- def __init__(self, display_width=None, **kw):
- """Construct an INTEGER.
-
- :param display_width: Optional, maximum display width for this number.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(display_width=display_width, **kw)
-
-
-class BIGINT(_IntegerType, sqltypes.BIGINT):
- """MySQL BIGINTEGER type."""
-
- __visit_name__ = "BIGINT"
-
- def __init__(self, display_width=None, **kw):
- """Construct a BIGINTEGER.
-
- :param display_width: Optional, maximum display width for this number.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(display_width=display_width, **kw)
-
-
-class MEDIUMINT(_IntegerType):
- """MySQL MEDIUMINTEGER type."""
-
- __visit_name__ = "MEDIUMINT"
-
- def __init__(self, display_width=None, **kw):
- """Construct a MEDIUMINTEGER
-
- :param display_width: Optional, maximum display width for this number.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(display_width=display_width, **kw)
-
-
-class TINYINT(_IntegerType):
- """MySQL TINYINT type."""
-
- __visit_name__ = "TINYINT"
-
- def __init__(self, display_width=None, **kw):
- """Construct a TINYINT.
-
- :param display_width: Optional, maximum display width for this number.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(display_width=display_width, **kw)
-
-
-class SMALLINT(_IntegerType, sqltypes.SMALLINT):
- """MySQL SMALLINTEGER type."""
-
- __visit_name__ = "SMALLINT"
-
- def __init__(self, display_width=None, **kw):
- """Construct a SMALLINTEGER.
-
- :param display_width: Optional, maximum display width for this number.
-
- :param unsigned: a boolean, optional.
-
- :param zerofill: Optional. If true, values will be stored as strings
- left-padded with zeros. Note that this does not effect the values
- returned by the underlying database API, which continue to be
- numeric.
-
- """
- super().__init__(display_width=display_width, **kw)
-
-
-class BIT(sqltypes.TypeEngine):
- """MySQL BIT type.
-
- This type is for MySQL 5.0.3 or greater for MyISAM, and 5.0.5 or greater
- for MyISAM, MEMORY, InnoDB and BDB. For older versions, use a
- MSTinyInteger() type.
-
- """
-
- __visit_name__ = "BIT"
-
- def __init__(self, length=None):
- """Construct a BIT.
-
- :param length: Optional, number of bits.
-
- """
- self.length = length
-
- def result_processor(self, dialect, coltype):
- """Convert a MySQL's 64 bit, variable length binary string to a long.
-
- TODO: this is MySQL-db, pyodbc specific. OurSQL and mysqlconnector
- already do this, so this logic should be moved to those dialects.
-
- """
-
- def process(value):
- if value is not None:
- v = 0
- for i in value:
- if not isinstance(i, int):
- i = ord(i) # convert byte to int on Python 2
- v = v << 8 | i
- return v
- return value
-
- return process
-
-
-class TIME(sqltypes.TIME):
- """MySQL TIME type."""
-
- __visit_name__ = "TIME"
-
- def __init__(self, timezone=False, fsp=None):
- """Construct a MySQL TIME type.
-
- :param timezone: not used by the MySQL dialect.
- :param fsp: fractional seconds precision value.
- MySQL 5.6 supports storage of fractional seconds;
- this parameter will be used when emitting DDL
- for the TIME type.
-
- .. note::
-
- DBAPI driver support for fractional seconds may
- be limited; current support includes
- MySQL Connector/Python.
-
- """
- super().__init__(timezone=timezone)
- self.fsp = fsp
-
- def result_processor(self, dialect, coltype):
- time = datetime.time
-
- def process(value):
- # convert from a timedelta value
- if value is not None:
- microseconds = value.microseconds
- seconds = value.seconds
- minutes = seconds // 60
- return time(
- minutes // 60,
- minutes % 60,
- seconds - minutes * 60,
- microsecond=microseconds,
- )
- else:
- return None
-
- return process
-
-
-class TIMESTAMP(sqltypes.TIMESTAMP):
- """MySQL TIMESTAMP type."""
-
- __visit_name__ = "TIMESTAMP"
-
- def __init__(self, timezone=False, fsp=None):
- """Construct a MySQL TIMESTAMP type.
-
- :param timezone: not used by the MySQL dialect.
- :param fsp: fractional seconds precision value.
- MySQL 5.6.4 supports storage of fractional seconds;
- this parameter will be used when emitting DDL
- for the TIMESTAMP type.
-
- .. note::
-
- DBAPI driver support for fractional seconds may
- be limited; current support includes
- MySQL Connector/Python.
-
- """
- super().__init__(timezone=timezone)
- self.fsp = fsp
-
-
-class DATETIME(sqltypes.DATETIME):
- """MySQL DATETIME type."""
-
- __visit_name__ = "DATETIME"
-
- def __init__(self, timezone=False, fsp=None):
- """Construct a MySQL DATETIME type.
-
- :param timezone: not used by the MySQL dialect.
- :param fsp: fractional seconds precision value.
- MySQL 5.6.4 supports storage of fractional seconds;
- this parameter will be used when emitting DDL
- for the DATETIME type.
-
- .. note::
-
- DBAPI driver support for fractional seconds may
- be limited; current support includes
- MySQL Connector/Python.
-
- """
- super().__init__(timezone=timezone)
- self.fsp = fsp
-
-
-class YEAR(sqltypes.TypeEngine):
- """MySQL YEAR type, for single byte storage of years 1901-2155."""
-
- __visit_name__ = "YEAR"
-
- def __init__(self, display_width=None):
- self.display_width = display_width
-
-
-class TEXT(_StringType, sqltypes.TEXT):
- """MySQL TEXT type, for character storage encoded up to 2^16 bytes."""
-
- __visit_name__ = "TEXT"
-
- def __init__(self, length=None, **kw):
- """Construct a TEXT.
-
- :param length: Optional, if provided the server may optimize storage
- by substituting the smallest TEXT type sufficient to store
- ``length`` bytes of characters.
-
- :param charset: Optional, a column-level character set for this string
- value. Takes precedence to 'ascii' or 'unicode' short-hand.
-
- :param collation: Optional, a column-level collation for this string
- value. Takes precedence to 'binary' short-hand.
-
- :param ascii: Defaults to False: short-hand for the ``latin1``
- character set, generates ASCII in schema.
-
- :param unicode: Defaults to False: short-hand for the ``ucs2``
- character set, generates UNICODE in schema.
-
- :param national: Optional. If true, use the server's configured
- national character set.
-
- :param binary: Defaults to False: short-hand, pick the binary
- collation type that matches the column's character set. Generates
- BINARY in schema. This does not affect the type of data stored,
- only the collation of character data.
-
- """
- super().__init__(length=length, **kw)
-
-
-class TINYTEXT(_StringType):
- """MySQL TINYTEXT type, for character storage encoded up to 2^8 bytes."""
-
- __visit_name__ = "TINYTEXT"
-
- def __init__(self, **kwargs):
- """Construct a TINYTEXT.
-
- :param charset: Optional, a column-level character set for this string
- value. Takes precedence to 'ascii' or 'unicode' short-hand.
-
- :param collation: Optional, a column-level collation for this string
- value. Takes precedence to 'binary' short-hand.
-
- :param ascii: Defaults to False: short-hand for the ``latin1``
- character set, generates ASCII in schema.
-
- :param unicode: Defaults to False: short-hand for the ``ucs2``
- character set, generates UNICODE in schema.
-
- :param national: Optional. If true, use the server's configured
- national character set.
-
- :param binary: Defaults to False: short-hand, pick the binary
- collation type that matches the column's character set. Generates
- BINARY in schema. This does not affect the type of data stored,
- only the collation of character data.
-
- """
- super().__init__(**kwargs)
-
-
-class MEDIUMTEXT(_StringType):
- """MySQL MEDIUMTEXT type, for character storage encoded up
- to 2^24 bytes."""
-
- __visit_name__ = "MEDIUMTEXT"
-
- def __init__(self, **kwargs):
- """Construct a MEDIUMTEXT.
-
- :param charset: Optional, a column-level character set for this string
- value. Takes precedence to 'ascii' or 'unicode' short-hand.
-
- :param collation: Optional, a column-level collation for this string
- value. Takes precedence to 'binary' short-hand.
-
- :param ascii: Defaults to False: short-hand for the ``latin1``
- character set, generates ASCII in schema.
-
- :param unicode: Defaults to False: short-hand for the ``ucs2``
- character set, generates UNICODE in schema.
-
- :param national: Optional. If true, use the server's configured
- national character set.
-
- :param binary: Defaults to False: short-hand, pick the binary
- collation type that matches the column's character set. Generates
- BINARY in schema. This does not affect the type of data stored,
- only the collation of character data.
-
- """
- super().__init__(**kwargs)
-
-
-class LONGTEXT(_StringType):
- """MySQL LONGTEXT type, for character storage encoded up to 2^32 bytes."""
-
- __visit_name__ = "LONGTEXT"
-
- def __init__(self, **kwargs):
- """Construct a LONGTEXT.
-
- :param charset: Optional, a column-level character set for this string
- value. Takes precedence to 'ascii' or 'unicode' short-hand.
-
- :param collation: Optional, a column-level collation for this string
- value. Takes precedence to 'binary' short-hand.
-
- :param ascii: Defaults to False: short-hand for the ``latin1``
- character set, generates ASCII in schema.
-
- :param unicode: Defaults to False: short-hand for the ``ucs2``
- character set, generates UNICODE in schema.
-
- :param national: Optional. If true, use the server's configured
- national character set.
-
- :param binary: Defaults to False: short-hand, pick the binary
- collation type that matches the column's character set. Generates
- BINARY in schema. This does not affect the type of data stored,
- only the collation of character data.
-
- """
- super().__init__(**kwargs)
-
-
-class VARCHAR(_StringType, sqltypes.VARCHAR):
- """MySQL VARCHAR type, for variable-length character data."""
-
- __visit_name__ = "VARCHAR"
-
- def __init__(self, length=None, **kwargs):
- """Construct a VARCHAR.
-
- :param charset: Optional, a column-level character set for this string
- value. Takes precedence to 'ascii' or 'unicode' short-hand.
-
- :param collation: Optional, a column-level collation for this string
- value. Takes precedence to 'binary' short-hand.
-
- :param ascii: Defaults to False: short-hand for the ``latin1``
- character set, generates ASCII in schema.
-
- :param unicode: Defaults to False: short-hand for the ``ucs2``
- character set, generates UNICODE in schema.
-
- :param national: Optional. If true, use the server's configured
- national character set.
-
- :param binary: Defaults to False: short-hand, pick the binary
- collation type that matches the column's character set. Generates
- BINARY in schema. This does not affect the type of data stored,
- only the collation of character data.
-
- """
- super().__init__(length=length, **kwargs)
-
-
-class CHAR(_StringType, sqltypes.CHAR):
- """MySQL CHAR type, for fixed-length character data."""
-
- __visit_name__ = "CHAR"
-
- def __init__(self, length=None, **kwargs):
- """Construct a CHAR.
-
- :param length: Maximum data length, in characters.
-
- :param binary: Optional, use the default binary collation for the
- national character set. This does not affect the type of data
- stored, use a BINARY type for binary data.
-
- :param collation: Optional, request a particular collation. Must be
- compatible with the national character set.
-
- """
- super().__init__(length=length, **kwargs)
-
- @classmethod
- 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_)
- if isinstance(type_, sqltypes.CHAR):
- return type_
- elif isinstance(type_, _StringType):
- return CHAR(
- length=type_.length,
- charset=type_.charset,
- collation=type_.collation,
- ascii=type_.ascii,
- binary=type_.binary,
- unicode=type_.unicode,
- national=False, # not supported in CAST
- )
- else:
- return CHAR(length=type_.length)
-
-
-class NVARCHAR(_StringType, sqltypes.NVARCHAR):
- """MySQL NVARCHAR type.
-
- For variable-length character data in the server's configured national
- character set.
- """
-
- __visit_name__ = "NVARCHAR"
-
- def __init__(self, length=None, **kwargs):
- """Construct an NVARCHAR.
-
- :param length: Maximum data length, in characters.
-
- :param binary: Optional, use the default binary collation for the
- national character set. This does not affect the type of data
- stored, use a BINARY type for binary data.
-
- :param collation: Optional, request a particular collation. Must be
- compatible with the national character set.
-
- """
- kwargs["national"] = True
- super().__init__(length=length, **kwargs)
-
-
-class NCHAR(_StringType, sqltypes.NCHAR):
- """MySQL NCHAR type.
-
- For fixed-length character data in the server's configured national
- character set.
- """
-
- __visit_name__ = "NCHAR"
-
- def __init__(self, length=None, **kwargs):
- """Construct an NCHAR.
-
- :param length: Maximum data length, in characters.
-
- :param binary: Optional, use the default binary collation for the
- national character set. This does not affect the type of data
- stored, use a BINARY type for binary data.
-
- :param collation: Optional, request a particular collation. Must be
- compatible with the national character set.
-
- """
- kwargs["national"] = True
- super().__init__(length=length, **kwargs)
-
-
-class TINYBLOB(sqltypes._Binary):
- """MySQL TINYBLOB type, for binary data up to 2^8 bytes."""
-
- __visit_name__ = "TINYBLOB"
-
-
-class MEDIUMBLOB(sqltypes._Binary):
- """MySQL MEDIUMBLOB type, for binary data up to 2^24 bytes."""
-
- __visit_name__ = "MEDIUMBLOB"
-
-
-class LONGBLOB(sqltypes._Binary):
- """MySQL LONGBLOB type, for binary data up to 2^32 bytes."""
-
- __visit_name__ = "LONGBLOB"