Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
mysqldb.py304 linesDownload Raw Back to mysql
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
18Driver Status
19-------------
20
21The mysqlclient DBAPI is a maintained fork of the
22`MySQL-Python <https://sourceforge.net/projects/mysql-python>`_ DBAPI
23that is no longer maintained.  `mysqlclient`_ supports Python 2 and Python 3
24and is very stable.
25
26.. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python
27
28.. _mysqldb_unicode:
29
30Unicode
31-------
32
33Please see :ref:`mysql_unicode` for current recommendations on unicode
34handling.
35
36.. _mysqldb_ssl:
37
38SSL Connections
39----------------
40
41The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the
42key "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
56For convenience, the following keys may also be specified inline within the URL
57where 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
73Using MySQLdb with Google Cloud SQL
74-----------------------------------
75
76Google Cloud SQL now recommends use of the MySQLdb dialect.  Connect
77using a URL like the following::
78
79    mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
80
81Server Side Cursors
82-------------------
83
84The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
85
86"""
87
88import re
89
90from .base import MySQLCompiler
91from .base import MySQLDialect
92from .base import MySQLExecutionContext
93from .base import MySQLIdentifierPreparer
94from .base import TEXT
95from ... import sql
96from ... import util
97
98
99class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
100    pass
101
102
103class MySQLCompiler_mysqldb(MySQLCompiler):
104    pass
105
106
107class 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", int)
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
303dialect = MySQLDialect_mysqldb
304 
codekingpro/portable-devtools · Team Ai