SQLAlchemy 2.0.36__cp313-cp313-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
- SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
- SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
- SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
- SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
- sqlalchemy/__init__.py +294 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +174 -0
- sqlalchemy/connectors/asyncio.py +213 -0
- sqlalchemy/connectors/pyodbc.py +249 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +91 -0
- sqlalchemy/dialects/__init__.py +61 -0
- sqlalchemy/dialects/_typing.py +25 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +64 -0
- sqlalchemy/dialects/mssql/base.py +4010 -0
- sqlalchemy/dialects/mssql/information_schema.py +254 -0
- sqlalchemy/dialects/mssql/json.py +133 -0
- sqlalchemy/dialects/mssql/provision.py +162 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +745 -0
- sqlalchemy/dialects/mysql/__init__.py +101 -0
- sqlalchemy/dialects/mysql/aiomysql.py +333 -0
- sqlalchemy/dialects/mysql/asyncmy.py +337 -0
- sqlalchemy/dialects/mysql/base.py +3494 -0
- sqlalchemy/dialects/mysql/cymysql.py +84 -0
- sqlalchemy/dialects/mysql/dml.py +219 -0
- sqlalchemy/dialects/mysql/enumerated.py +244 -0
- sqlalchemy/dialects/mysql/expression.py +141 -0
- sqlalchemy/dialects/mysql/json.py +81 -0
- sqlalchemy/dialects/mysql/mariadb.py +32 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
- sqlalchemy/dialects/mysql/mysqldb.py +303 -0
- sqlalchemy/dialects/mysql/provision.py +110 -0
- sqlalchemy/dialects/mysql/pymysql.py +137 -0
- sqlalchemy/dialects/mysql/pyodbc.py +138 -0
- sqlalchemy/dialects/mysql/reflection.py +677 -0
- sqlalchemy/dialects/mysql/reserved_words.py +571 -0
- sqlalchemy/dialects/mysql/types.py +774 -0
- sqlalchemy/dialects/oracle/__init__.py +67 -0
- sqlalchemy/dialects/oracle/base.py +3271 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +431 -0
- sqlalchemy/dialects/oracle/provision.py +220 -0
- sqlalchemy/dialects/oracle/types.py +287 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
- sqlalchemy/dialects/postgresql/array.py +425 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
- sqlalchemy/dialects/postgresql/base.py +5008 -0
- sqlalchemy/dialects/postgresql/dml.py +310 -0
- sqlalchemy/dialects/postgresql/ext.py +496 -0
- sqlalchemy/dialects/postgresql/hstore.py +397 -0
- sqlalchemy/dialects/postgresql/json.py +333 -0
- sqlalchemy/dialects/postgresql/named_types.py +509 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +662 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
- sqlalchemy/dialects/postgresql/provision.py +175 -0
- sqlalchemy/dialects/postgresql/psycopg.py +772 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1029 -0
- sqlalchemy/dialects/postgresql/types.py +303 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
- sqlalchemy/dialects/sqlite/base.py +2805 -0
- sqlalchemy/dialects/sqlite/dml.py +240 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +198 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3375 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +875 -0
- sqlalchemy/engine/cursor.py +2181 -0
- sqlalchemy/engine/default.py +2365 -0
- sqlalchemy/engine/events.py +951 -0
- sqlalchemy/engine/interfaces.py +3403 -0
- sqlalchemy/engine/mock.py +131 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2098 -0
- sqlalchemy/engine/result.py +2382 -0
- sqlalchemy/engine/row.py +401 -0
- sqlalchemy/engine/strategies.py +19 -0
- sqlalchemy/engine/url.py +910 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +25 -0
- sqlalchemy/event/api.py +225 -0
- sqlalchemy/event/attr.py +655 -0
- sqlalchemy/event/base.py +470 -0
- sqlalchemy/event/legacy.py +246 -0
- sqlalchemy/event/registry.py +386 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +830 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2013 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +279 -0
- sqlalchemy/ext/asyncio/engine.py +1466 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +961 -0
- sqlalchemy/ext/asyncio/scoping.py +1614 -0
- sqlalchemy/ext/asyncio/session.py +1936 -0
- sqlalchemy/ext/automap.py +1691 -0
- sqlalchemy/ext/baked.py +574 -0
- sqlalchemy/ext/compiler.py +570 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +548 -0
- sqlalchemy/ext/horizontal_shard.py +481 -0
- sqlalchemy/ext/hybrid.py +1514 -0
- sqlalchemy/ext/indexable.py +341 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1073 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +320 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +416 -0
- sqlalchemy/ext/serializer.py +181 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +170 -0
- sqlalchemy/orm/_orm_constructors.py +2571 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2835 -0
- sqlalchemy/orm/base.py +973 -0
- sqlalchemy/orm/bulk_persistence.py +2123 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1620 -0
- sqlalchemy/orm/context.py +3268 -0
- sqlalchemy/orm/decl_api.py +1883 -0
- sqlalchemy/orm/decl_base.py +2190 -0
- sqlalchemy/orm/dependency.py +1304 -0
- sqlalchemy/orm/descriptor_props.py +1076 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3261 -0
- sqlalchemy/orm/exc.py +228 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1474 -0
- sqlalchemy/orm/loading.py +1682 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4432 -0
- sqlalchemy/orm/path_registry.py +811 -0
- sqlalchemy/orm/persistence.py +1782 -0
- sqlalchemy/orm/properties.py +886 -0
- sqlalchemy/orm/query.py +3396 -0
- sqlalchemy/orm/relationships.py +3500 -0
- sqlalchemy/orm/scoping.py +2165 -0
- sqlalchemy/orm/session.py +5301 -0
- sqlalchemy/orm/state.py +1143 -0
- sqlalchemy/orm/state_changes.py +198 -0
- sqlalchemy/orm/strategies.py +3473 -0
- sqlalchemy/orm/strategy_options.py +2569 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2424 -0
- sqlalchemy/orm/writeonly.py +678 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1515 -0
- sqlalchemy/pool/events.py +370 -0
- sqlalchemy/pool/impl.py +581 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +70 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +140 -0
- sqlalchemy/sql/_elements_constructors.py +1850 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +635 -0
- sqlalchemy/sql/_typing.py +460 -0
- sqlalchemy/sql/annotation.py +585 -0
- sqlalchemy/sql/base.py +2185 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1405 -0
- sqlalchemy/sql/compiler.py +7818 -0
- sqlalchemy/sql/crud.py +1669 -0
- sqlalchemy/sql/ddl.py +1378 -0
- sqlalchemy/sql/default_comparator.py +552 -0
- sqlalchemy/sql/dml.py +1817 -0
- sqlalchemy/sql/elements.py +5499 -0
- sqlalchemy/sql/events.py +455 -0
- sqlalchemy/sql/expression.py +162 -0
- sqlalchemy/sql/functions.py +2055 -0
- sqlalchemy/sql/lambdas.py +1449 -0
- sqlalchemy/sql/naming.py +212 -0
- sqlalchemy/sql/operators.py +2579 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6158 -0
- sqlalchemy/sql/selectable.py +7004 -0
- sqlalchemy/sql/sqltypes.py +3827 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2339 -0
- sqlalchemy/sql/util.py +1486 -0
- sqlalchemy/sql/visitors.py +1165 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +989 -0
- sqlalchemy/testing/assertsql.py +516 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +427 -0
- sqlalchemy/testing/engines.py +472 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +435 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +366 -0
- sqlalchemy/testing/fixtures/mypy.py +312 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +503 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +779 -0
- sqlalchemy/testing/plugin/pytestplugin.py +868 -0
- sqlalchemy/testing/profiling.py +324 -0
- sqlalchemy/testing/provision.py +496 -0
- sqlalchemy/testing/requirements.py +1818 -0
- sqlalchemy/testing/schema.py +224 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +211 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +740 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3225 -0
- sqlalchemy/testing/suite/test_results.py +502 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +1999 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2141 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +537 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +76 -0
- sqlalchemy/util/__init__.py +160 -0
- sqlalchemy/util/_collections.py +715 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +301 -0
- sqlalchemy/util/concurrency.py +108 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2218 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +629 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# dialects/mysql/mysqldb.py
|
|
2
|
+
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
.. dialect:: mysql+mysqldb
|
|
13
|
+
:name: mysqlclient (maintained fork of MySQL-Python)
|
|
14
|
+
:dbapi: mysqldb
|
|
15
|
+
:connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
|
16
|
+
:url: https://pypi.org/project/mysqlclient/
|
|
17
|
+
|
|
18
|
+
Driver Status
|
|
19
|
+
-------------
|
|
20
|
+
|
|
21
|
+
The mysqlclient DBAPI is a maintained fork of the
|
|
22
|
+
`MySQL-Python <https://sourceforge.net/projects/mysql-python>`_ DBAPI
|
|
23
|
+
that is no longer maintained. `mysqlclient`_ supports Python 2 and Python 3
|
|
24
|
+
and is very stable.
|
|
25
|
+
|
|
26
|
+
.. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python
|
|
27
|
+
|
|
28
|
+
.. _mysqldb_unicode:
|
|
29
|
+
|
|
30
|
+
Unicode
|
|
31
|
+
-------
|
|
32
|
+
|
|
33
|
+
Please see :ref:`mysql_unicode` for current recommendations on unicode
|
|
34
|
+
handling.
|
|
35
|
+
|
|
36
|
+
.. _mysqldb_ssl:
|
|
37
|
+
|
|
38
|
+
SSL Connections
|
|
39
|
+
----------------
|
|
40
|
+
|
|
41
|
+
The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the
|
|
42
|
+
key "ssl", which may be specified using the
|
|
43
|
+
:paramref:`_sa.create_engine.connect_args` dictionary::
|
|
44
|
+
|
|
45
|
+
engine = create_engine(
|
|
46
|
+
"mysql+mysqldb://scott:tiger@192.168.0.134/test",
|
|
47
|
+
connect_args={
|
|
48
|
+
"ssl": {
|
|
49
|
+
"ca": "/home/gord/client-ssl/ca.pem",
|
|
50
|
+
"cert": "/home/gord/client-ssl/client-cert.pem",
|
|
51
|
+
"key": "/home/gord/client-ssl/client-key.pem"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
For convenience, the following keys may also be specified inline within the URL
|
|
57
|
+
where they will be interpreted into the "ssl" dictionary automatically:
|
|
58
|
+
"ssl_ca", "ssl_cert", "ssl_key", "ssl_capath", "ssl_cipher",
|
|
59
|
+
"ssl_check_hostname". An example is as follows::
|
|
60
|
+
|
|
61
|
+
connection_uri = (
|
|
62
|
+
"mysql+mysqldb://scott:tiger@192.168.0.134/test"
|
|
63
|
+
"?ssl_ca=/home/gord/client-ssl/ca.pem"
|
|
64
|
+
"&ssl_cert=/home/gord/client-ssl/client-cert.pem"
|
|
65
|
+
"&ssl_key=/home/gord/client-ssl/client-key.pem"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
.. seealso::
|
|
69
|
+
|
|
70
|
+
:ref:`pymysql_ssl` in the PyMySQL dialect
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Using MySQLdb with Google Cloud SQL
|
|
74
|
+
-----------------------------------
|
|
75
|
+
|
|
76
|
+
Google Cloud SQL now recommends use of the MySQLdb dialect. Connect
|
|
77
|
+
using a URL like the following::
|
|
78
|
+
|
|
79
|
+
mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
|
|
80
|
+
|
|
81
|
+
Server Side Cursors
|
|
82
|
+
-------------------
|
|
83
|
+
|
|
84
|
+
The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
import re
|
|
89
|
+
|
|
90
|
+
from .base import MySQLCompiler
|
|
91
|
+
from .base import MySQLDialect
|
|
92
|
+
from .base import MySQLExecutionContext
|
|
93
|
+
from .base import MySQLIdentifierPreparer
|
|
94
|
+
from .base import TEXT
|
|
95
|
+
from ... import sql
|
|
96
|
+
from ... import util
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class MySQLCompiler_mysqldb(MySQLCompiler):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class MySQLDialect_mysqldb(MySQLDialect):
|
|
108
|
+
driver = "mysqldb"
|
|
109
|
+
supports_statement_cache = True
|
|
110
|
+
supports_unicode_statements = True
|
|
111
|
+
supports_sane_rowcount = True
|
|
112
|
+
supports_sane_multi_rowcount = True
|
|
113
|
+
|
|
114
|
+
supports_native_decimal = True
|
|
115
|
+
|
|
116
|
+
default_paramstyle = "format"
|
|
117
|
+
execution_ctx_cls = MySQLExecutionContext_mysqldb
|
|
118
|
+
statement_compiler = MySQLCompiler_mysqldb
|
|
119
|
+
preparer = MySQLIdentifierPreparer
|
|
120
|
+
|
|
121
|
+
def __init__(self, **kwargs):
|
|
122
|
+
super().__init__(**kwargs)
|
|
123
|
+
self._mysql_dbapi_version = (
|
|
124
|
+
self._parse_dbapi_version(self.dbapi.__version__)
|
|
125
|
+
if self.dbapi is not None and hasattr(self.dbapi, "__version__")
|
|
126
|
+
else (0, 0, 0)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def _parse_dbapi_version(self, version):
|
|
130
|
+
m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version)
|
|
131
|
+
if m:
|
|
132
|
+
return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
|
|
133
|
+
else:
|
|
134
|
+
return (0, 0, 0)
|
|
135
|
+
|
|
136
|
+
@util.langhelpers.memoized_property
|
|
137
|
+
def supports_server_side_cursors(self):
|
|
138
|
+
try:
|
|
139
|
+
cursors = __import__("MySQLdb.cursors").cursors
|
|
140
|
+
self._sscursor = cursors.SSCursor
|
|
141
|
+
return True
|
|
142
|
+
except (ImportError, AttributeError):
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def import_dbapi(cls):
|
|
147
|
+
return __import__("MySQLdb")
|
|
148
|
+
|
|
149
|
+
def on_connect(self):
|
|
150
|
+
super_ = super().on_connect()
|
|
151
|
+
|
|
152
|
+
def on_connect(conn):
|
|
153
|
+
if super_ is not None:
|
|
154
|
+
super_(conn)
|
|
155
|
+
|
|
156
|
+
charset_name = conn.character_set_name()
|
|
157
|
+
|
|
158
|
+
if charset_name is not None:
|
|
159
|
+
cursor = conn.cursor()
|
|
160
|
+
cursor.execute("SET NAMES %s" % charset_name)
|
|
161
|
+
cursor.close()
|
|
162
|
+
|
|
163
|
+
return on_connect
|
|
164
|
+
|
|
165
|
+
def do_ping(self, dbapi_connection):
|
|
166
|
+
dbapi_connection.ping()
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
def do_executemany(self, cursor, statement, parameters, context=None):
|
|
170
|
+
rowcount = cursor.executemany(statement, parameters)
|
|
171
|
+
if context is not None:
|
|
172
|
+
context._rowcount = rowcount
|
|
173
|
+
|
|
174
|
+
def _check_unicode_returns(self, connection):
|
|
175
|
+
# work around issue fixed in
|
|
176
|
+
# https://github.com/farcepest/MySQLdb1/commit/cd44524fef63bd3fcb71947392326e9742d520e8
|
|
177
|
+
# specific issue w/ the utf8mb4_bin collation and unicode returns
|
|
178
|
+
|
|
179
|
+
collation = connection.exec_driver_sql(
|
|
180
|
+
"show collation where %s = 'utf8mb4' and %s = 'utf8mb4_bin'"
|
|
181
|
+
% (
|
|
182
|
+
self.identifier_preparer.quote("Charset"),
|
|
183
|
+
self.identifier_preparer.quote("Collation"),
|
|
184
|
+
)
|
|
185
|
+
).scalar()
|
|
186
|
+
has_utf8mb4_bin = self.server_version_info > (5,) and collation
|
|
187
|
+
if has_utf8mb4_bin:
|
|
188
|
+
additional_tests = [
|
|
189
|
+
sql.collate(
|
|
190
|
+
sql.cast(
|
|
191
|
+
sql.literal_column("'test collated returns'"),
|
|
192
|
+
TEXT(charset="utf8mb4"),
|
|
193
|
+
),
|
|
194
|
+
"utf8mb4_bin",
|
|
195
|
+
)
|
|
196
|
+
]
|
|
197
|
+
else:
|
|
198
|
+
additional_tests = []
|
|
199
|
+
return super()._check_unicode_returns(connection, additional_tests)
|
|
200
|
+
|
|
201
|
+
def create_connect_args(self, url, _translate_args=None):
|
|
202
|
+
if _translate_args is None:
|
|
203
|
+
_translate_args = dict(
|
|
204
|
+
database="db", username="user", password="passwd"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
opts = url.translate_connect_args(**_translate_args)
|
|
208
|
+
opts.update(url.query)
|
|
209
|
+
|
|
210
|
+
util.coerce_kw_type(opts, "compress", bool)
|
|
211
|
+
util.coerce_kw_type(opts, "connect_timeout", int)
|
|
212
|
+
util.coerce_kw_type(opts, "read_timeout", int)
|
|
213
|
+
util.coerce_kw_type(opts, "write_timeout", int)
|
|
214
|
+
util.coerce_kw_type(opts, "client_flag", int)
|
|
215
|
+
util.coerce_kw_type(opts, "local_infile", bool)
|
|
216
|
+
# Note: using either of the below will cause all strings to be
|
|
217
|
+
# returned as Unicode, both in raw SQL operations and with column
|
|
218
|
+
# types like String and MSString.
|
|
219
|
+
util.coerce_kw_type(opts, "use_unicode", bool)
|
|
220
|
+
util.coerce_kw_type(opts, "charset", str)
|
|
221
|
+
|
|
222
|
+
# Rich values 'cursorclass' and 'conv' are not supported via
|
|
223
|
+
# query string.
|
|
224
|
+
|
|
225
|
+
ssl = {}
|
|
226
|
+
keys = [
|
|
227
|
+
("ssl_ca", str),
|
|
228
|
+
("ssl_key", str),
|
|
229
|
+
("ssl_cert", str),
|
|
230
|
+
("ssl_capath", str),
|
|
231
|
+
("ssl_cipher", str),
|
|
232
|
+
("ssl_check_hostname", bool),
|
|
233
|
+
]
|
|
234
|
+
for key, kw_type in keys:
|
|
235
|
+
if key in opts:
|
|
236
|
+
ssl[key[4:]] = opts[key]
|
|
237
|
+
util.coerce_kw_type(ssl, key[4:], kw_type)
|
|
238
|
+
del opts[key]
|
|
239
|
+
if ssl:
|
|
240
|
+
opts["ssl"] = ssl
|
|
241
|
+
|
|
242
|
+
# FOUND_ROWS must be set in CLIENT_FLAGS to enable
|
|
243
|
+
# supports_sane_rowcount.
|
|
244
|
+
client_flag = opts.get("client_flag", 0)
|
|
245
|
+
|
|
246
|
+
client_flag_found_rows = self._found_rows_client_flag()
|
|
247
|
+
if client_flag_found_rows is not None:
|
|
248
|
+
client_flag |= client_flag_found_rows
|
|
249
|
+
opts["client_flag"] = client_flag
|
|
250
|
+
return [[], opts]
|
|
251
|
+
|
|
252
|
+
def _found_rows_client_flag(self):
|
|
253
|
+
if self.dbapi is not None:
|
|
254
|
+
try:
|
|
255
|
+
CLIENT_FLAGS = __import__(
|
|
256
|
+
self.dbapi.__name__ + ".constants.CLIENT"
|
|
257
|
+
).constants.CLIENT
|
|
258
|
+
except (AttributeError, ImportError):
|
|
259
|
+
return None
|
|
260
|
+
else:
|
|
261
|
+
return CLIENT_FLAGS.FOUND_ROWS
|
|
262
|
+
else:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def _extract_error_code(self, exception):
|
|
266
|
+
return exception.args[0]
|
|
267
|
+
|
|
268
|
+
def _detect_charset(self, connection):
|
|
269
|
+
"""Sniff out the character set in use for connection results."""
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
# note: the SQL here would be
|
|
273
|
+
# "SHOW VARIABLES LIKE 'character_set%%'"
|
|
274
|
+
cset_name = connection.connection.character_set_name
|
|
275
|
+
except AttributeError:
|
|
276
|
+
util.warn(
|
|
277
|
+
"No 'character_set_name' can be detected with "
|
|
278
|
+
"this MySQL-Python version; "
|
|
279
|
+
"please upgrade to a recent version of MySQL-Python. "
|
|
280
|
+
"Assuming latin1."
|
|
281
|
+
)
|
|
282
|
+
return "latin1"
|
|
283
|
+
else:
|
|
284
|
+
return cset_name()
|
|
285
|
+
|
|
286
|
+
def get_isolation_level_values(self, dbapi_connection):
|
|
287
|
+
return (
|
|
288
|
+
"SERIALIZABLE",
|
|
289
|
+
"READ UNCOMMITTED",
|
|
290
|
+
"READ COMMITTED",
|
|
291
|
+
"REPEATABLE READ",
|
|
292
|
+
"AUTOCOMMIT",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def set_isolation_level(self, dbapi_connection, level):
|
|
296
|
+
if level == "AUTOCOMMIT":
|
|
297
|
+
dbapi_connection.autocommit(True)
|
|
298
|
+
else:
|
|
299
|
+
dbapi_connection.autocommit(False)
|
|
300
|
+
super().set_isolation_level(dbapi_connection, level)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
dialect = MySQLDialect_mysqldb
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# dialects/mysql/provision.py
|
|
2
|
+
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
from ... import exc
|
|
10
|
+
from ...testing.provision import configure_follower
|
|
11
|
+
from ...testing.provision import create_db
|
|
12
|
+
from ...testing.provision import drop_db
|
|
13
|
+
from ...testing.provision import generate_driver_url
|
|
14
|
+
from ...testing.provision import temp_table_keyword_args
|
|
15
|
+
from ...testing.provision import upsert
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@generate_driver_url.for_db("mysql", "mariadb")
|
|
19
|
+
def generate_driver_url(url, driver, query_str):
|
|
20
|
+
backend = url.get_backend_name()
|
|
21
|
+
|
|
22
|
+
# NOTE: at the moment, tests are running mariadbconnector
|
|
23
|
+
# against both mariadb and mysql backends. if we want this to be
|
|
24
|
+
# limited, do the decision making here to reject a "mysql+mariadbconnector"
|
|
25
|
+
# URL. Optionally also re-enable the module level
|
|
26
|
+
# MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
|
|
27
|
+
# a unit and/or functional test.
|
|
28
|
+
|
|
29
|
+
# all the Jenkins tests have been running mysqlclient Python library
|
|
30
|
+
# built against mariadb client drivers for years against all MySQL /
|
|
31
|
+
# MariaDB versions going back to MySQL 5.6, currently they can talk
|
|
32
|
+
# to MySQL databases without problems.
|
|
33
|
+
|
|
34
|
+
if backend == "mysql":
|
|
35
|
+
dialect_cls = url.get_dialect()
|
|
36
|
+
if dialect_cls._is_mariadb_from_url(url):
|
|
37
|
+
backend = "mariadb"
|
|
38
|
+
|
|
39
|
+
new_url = url.set(
|
|
40
|
+
drivername="%s+%s" % (backend, driver)
|
|
41
|
+
).update_query_string(query_str)
|
|
42
|
+
|
|
43
|
+
if driver == "mariadbconnector":
|
|
44
|
+
new_url = new_url.difference_update_query(["charset"])
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
new_url.get_dialect()
|
|
48
|
+
except exc.NoSuchModuleError:
|
|
49
|
+
return None
|
|
50
|
+
else:
|
|
51
|
+
return new_url
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@create_db.for_db("mysql", "mariadb")
|
|
55
|
+
def _mysql_create_db(cfg, eng, ident):
|
|
56
|
+
with eng.begin() as conn:
|
|
57
|
+
try:
|
|
58
|
+
_mysql_drop_db(cfg, conn, ident)
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
with eng.begin() as conn:
|
|
63
|
+
conn.exec_driver_sql(
|
|
64
|
+
"CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
|
|
65
|
+
)
|
|
66
|
+
conn.exec_driver_sql(
|
|
67
|
+
"CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
|
|
68
|
+
)
|
|
69
|
+
conn.exec_driver_sql(
|
|
70
|
+
"CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@configure_follower.for_db("mysql", "mariadb")
|
|
75
|
+
def _mysql_configure_follower(config, ident):
|
|
76
|
+
config.test_schema = "%s_test_schema" % ident
|
|
77
|
+
config.test_schema_2 = "%s_test_schema_2" % ident
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@drop_db.for_db("mysql", "mariadb")
|
|
81
|
+
def _mysql_drop_db(cfg, eng, ident):
|
|
82
|
+
with eng.begin() as conn:
|
|
83
|
+
conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
|
|
84
|
+
conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
|
|
85
|
+
conn.exec_driver_sql("DROP DATABASE %s" % ident)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@temp_table_keyword_args.for_db("mysql", "mariadb")
|
|
89
|
+
def _mysql_temp_table_keyword_args(cfg, eng):
|
|
90
|
+
return {"prefixes": ["TEMPORARY"]}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@upsert.for_db("mariadb")
|
|
94
|
+
def _upsert(
|
|
95
|
+
cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False
|
|
96
|
+
):
|
|
97
|
+
from sqlalchemy.dialects.mysql import insert
|
|
98
|
+
|
|
99
|
+
stmt = insert(table)
|
|
100
|
+
|
|
101
|
+
if set_lambda:
|
|
102
|
+
stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
|
|
103
|
+
else:
|
|
104
|
+
pk1 = table.primary_key.c[0]
|
|
105
|
+
stmt = stmt.on_duplicate_key_update({pk1.key: pk1})
|
|
106
|
+
|
|
107
|
+
stmt = stmt.returning(
|
|
108
|
+
*returning, sort_by_parameter_order=sort_by_parameter_order
|
|
109
|
+
)
|
|
110
|
+
return stmt
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# dialects/mysql/pymysql.py
|
|
2
|
+
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
r"""
|
|
11
|
+
|
|
12
|
+
.. dialect:: mysql+pymysql
|
|
13
|
+
:name: PyMySQL
|
|
14
|
+
:dbapi: pymysql
|
|
15
|
+
:connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
|
16
|
+
:url: https://pymysql.readthedocs.io/
|
|
17
|
+
|
|
18
|
+
Unicode
|
|
19
|
+
-------
|
|
20
|
+
|
|
21
|
+
Please see :ref:`mysql_unicode` for current recommendations on unicode
|
|
22
|
+
handling.
|
|
23
|
+
|
|
24
|
+
.. _pymysql_ssl:
|
|
25
|
+
|
|
26
|
+
SSL Connections
|
|
27
|
+
------------------
|
|
28
|
+
|
|
29
|
+
The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb,
|
|
30
|
+
described at :ref:`mysqldb_ssl`. See that section for additional examples.
|
|
31
|
+
|
|
32
|
+
If the server uses an automatically-generated certificate that is self-signed
|
|
33
|
+
or does not match the host name (as seen from the client), it may also be
|
|
34
|
+
necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::
|
|
35
|
+
|
|
36
|
+
connection_uri = (
|
|
37
|
+
"mysql+pymysql://scott:tiger@192.168.0.134/test"
|
|
38
|
+
"?ssl_ca=/home/gord/client-ssl/ca.pem"
|
|
39
|
+
"&ssl_cert=/home/gord/client-ssl/client-cert.pem"
|
|
40
|
+
"&ssl_key=/home/gord/client-ssl/client-key.pem"
|
|
41
|
+
"&ssl_check_hostname=false"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
MySQL-Python Compatibility
|
|
46
|
+
--------------------------
|
|
47
|
+
|
|
48
|
+
The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
|
|
49
|
+
and targets 100% compatibility. Most behavioral notes for MySQL-python apply
|
|
50
|
+
to the pymysql driver as well.
|
|
51
|
+
|
|
52
|
+
""" # noqa
|
|
53
|
+
|
|
54
|
+
from .mysqldb import MySQLDialect_mysqldb
|
|
55
|
+
from ...util import langhelpers
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
|
59
|
+
driver = "pymysql"
|
|
60
|
+
supports_statement_cache = True
|
|
61
|
+
|
|
62
|
+
description_encoding = None
|
|
63
|
+
|
|
64
|
+
@langhelpers.memoized_property
|
|
65
|
+
def supports_server_side_cursors(self):
|
|
66
|
+
try:
|
|
67
|
+
cursors = __import__("pymysql.cursors").cursors
|
|
68
|
+
self._sscursor = cursors.SSCursor
|
|
69
|
+
return True
|
|
70
|
+
except (ImportError, AttributeError):
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def import_dbapi(cls):
|
|
75
|
+
return __import__("pymysql")
|
|
76
|
+
|
|
77
|
+
@langhelpers.memoized_property
|
|
78
|
+
def _send_false_to_ping(self):
|
|
79
|
+
"""determine if pymysql has deprecated, changed the default of,
|
|
80
|
+
or removed the 'reconnect' argument of connection.ping().
|
|
81
|
+
|
|
82
|
+
See #10492 and
|
|
83
|
+
https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
|
|
84
|
+
for background.
|
|
85
|
+
|
|
86
|
+
""" # noqa: E501
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
Connection = __import__(
|
|
90
|
+
"pymysql.connections"
|
|
91
|
+
).connections.Connection
|
|
92
|
+
except (ImportError, AttributeError):
|
|
93
|
+
return True
|
|
94
|
+
else:
|
|
95
|
+
insp = langhelpers.get_callable_argspec(Connection.ping)
|
|
96
|
+
try:
|
|
97
|
+
reconnect_arg = insp.args[1]
|
|
98
|
+
except IndexError:
|
|
99
|
+
return False
|
|
100
|
+
else:
|
|
101
|
+
return reconnect_arg == "reconnect" and (
|
|
102
|
+
not insp.defaults or insp.defaults[0] is not False
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def do_ping(self, dbapi_connection):
|
|
106
|
+
if self._send_false_to_ping:
|
|
107
|
+
dbapi_connection.ping(False)
|
|
108
|
+
else:
|
|
109
|
+
dbapi_connection.ping()
|
|
110
|
+
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
def create_connect_args(self, url, _translate_args=None):
|
|
114
|
+
if _translate_args is None:
|
|
115
|
+
_translate_args = dict(username="user")
|
|
116
|
+
return super().create_connect_args(
|
|
117
|
+
url, _translate_args=_translate_args
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def is_disconnect(self, e, connection, cursor):
|
|
121
|
+
if super().is_disconnect(e, connection, cursor):
|
|
122
|
+
return True
|
|
123
|
+
elif isinstance(e, self.dbapi.Error):
|
|
124
|
+
str_e = str(e).lower()
|
|
125
|
+
return (
|
|
126
|
+
"already closed" in str_e or "connection was killed" in str_e
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def _extract_error_code(self, exception):
|
|
132
|
+
if isinstance(exception.args[0], Exception):
|
|
133
|
+
exception = exception.args[0]
|
|
134
|
+
return exception.args[0]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
dialect = MySQLDialect_pymysql
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# dialects/mysql/pyodbc.py
|
|
2
|
+
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
r"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
.. dialect:: mysql+pyodbc
|
|
14
|
+
:name: PyODBC
|
|
15
|
+
:dbapi: pyodbc
|
|
16
|
+
:connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
|
|
17
|
+
:url: https://pypi.org/project/pyodbc/
|
|
18
|
+
|
|
19
|
+
.. note::
|
|
20
|
+
|
|
21
|
+
The PyODBC for MySQL dialect is **not tested as part of
|
|
22
|
+
SQLAlchemy's continuous integration**.
|
|
23
|
+
The recommended MySQL dialects are mysqlclient and PyMySQL.
|
|
24
|
+
However, if you want to use the mysql+pyodbc dialect and require
|
|
25
|
+
full support for ``utf8mb4`` characters (including supplementary
|
|
26
|
+
characters like emoji) be sure to use a current release of
|
|
27
|
+
MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
|
|
28
|
+
version of the driver in your DSN or connection string.
|
|
29
|
+
|
|
30
|
+
Pass through exact pyodbc connection string::
|
|
31
|
+
|
|
32
|
+
import urllib
|
|
33
|
+
connection_string = (
|
|
34
|
+
'DRIVER=MySQL ODBC 8.0 ANSI Driver;'
|
|
35
|
+
'SERVER=localhost;'
|
|
36
|
+
'PORT=3307;'
|
|
37
|
+
'DATABASE=mydb;'
|
|
38
|
+
'UID=root;'
|
|
39
|
+
'PWD=(whatever);'
|
|
40
|
+
'charset=utf8mb4;'
|
|
41
|
+
)
|
|
42
|
+
params = urllib.parse.quote_plus(connection_string)
|
|
43
|
+
connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
|
|
44
|
+
|
|
45
|
+
""" # noqa
|
|
46
|
+
|
|
47
|
+
import re
|
|
48
|
+
|
|
49
|
+
from .base import MySQLDialect
|
|
50
|
+
from .base import MySQLExecutionContext
|
|
51
|
+
from .types import TIME
|
|
52
|
+
from ... import exc
|
|
53
|
+
from ... import util
|
|
54
|
+
from ...connectors.pyodbc import PyODBCConnector
|
|
55
|
+
from ...sql.sqltypes import Time
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _pyodbcTIME(TIME):
|
|
59
|
+
def result_processor(self, dialect, coltype):
|
|
60
|
+
def process(value):
|
|
61
|
+
# pyodbc returns a datetime.time object; no need to convert
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
return process
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
|
|
68
|
+
def get_lastrowid(self):
|
|
69
|
+
cursor = self.create_cursor()
|
|
70
|
+
cursor.execute("SELECT LAST_INSERT_ID()")
|
|
71
|
+
lastrowid = cursor.fetchone()[0]
|
|
72
|
+
cursor.close()
|
|
73
|
+
return lastrowid
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
|
|
77
|
+
supports_statement_cache = True
|
|
78
|
+
colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
|
|
79
|
+
supports_unicode_statements = True
|
|
80
|
+
execution_ctx_cls = MySQLExecutionContext_pyodbc
|
|
81
|
+
|
|
82
|
+
pyodbc_driver_name = "MySQL"
|
|
83
|
+
|
|
84
|
+
def _detect_charset(self, connection):
|
|
85
|
+
"""Sniff out the character set in use for connection results."""
|
|
86
|
+
|
|
87
|
+
# Prefer 'character_set_results' for the current connection over the
|
|
88
|
+
# value in the driver. SET NAMES or individual variable SETs will
|
|
89
|
+
# change the charset without updating the driver's view of the world.
|
|
90
|
+
#
|
|
91
|
+
# If it's decided that issuing that sort of SQL leaves you SOL, then
|
|
92
|
+
# this can prefer the driver value.
|
|
93
|
+
|
|
94
|
+
# set this to None as _fetch_setting attempts to use it (None is OK)
|
|
95
|
+
self._connection_charset = None
|
|
96
|
+
try:
|
|
97
|
+
value = self._fetch_setting(connection, "character_set_client")
|
|
98
|
+
if value:
|
|
99
|
+
return value
|
|
100
|
+
except exc.DBAPIError:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
util.warn(
|
|
104
|
+
"Could not detect the connection character set. "
|
|
105
|
+
"Assuming latin1."
|
|
106
|
+
)
|
|
107
|
+
return "latin1"
|
|
108
|
+
|
|
109
|
+
def _get_server_version_info(self, connection):
|
|
110
|
+
return MySQLDialect._get_server_version_info(self, connection)
|
|
111
|
+
|
|
112
|
+
def _extract_error_code(self, exception):
|
|
113
|
+
m = re.compile(r"\((\d+)\)").search(str(exception.args))
|
|
114
|
+
c = m.group(1)
|
|
115
|
+
if c:
|
|
116
|
+
return int(c)
|
|
117
|
+
else:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def on_connect(self):
|
|
121
|
+
super_ = super().on_connect()
|
|
122
|
+
|
|
123
|
+
def on_connect(conn):
|
|
124
|
+
if super_ is not None:
|
|
125
|
+
super_(conn)
|
|
126
|
+
|
|
127
|
+
# declare Unicode encoding for pyodbc as per
|
|
128
|
+
# https://github.com/mkleehammer/pyodbc/wiki/Unicode
|
|
129
|
+
pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
|
|
130
|
+
pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
|
|
131
|
+
conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
|
|
132
|
+
conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
|
|
133
|
+
conn.setencoding(encoding="utf-8")
|
|
134
|
+
|
|
135
|
+
return on_connect
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
dialect = MySQLDialect_pyodbc
|