mysqlconnector.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # dialects/mysql/mysqlconnector.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. r"""
  9. .. dialect:: mysql+mysqlconnector
  10. :name: MySQL Connector/Python
  11. :dbapi: myconnpy
  12. :connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
  13. :url: https://pypi.org/project/mysql-connector-python/
  14. .. note::
  15. The MySQL Connector/Python DBAPI has had many issues since its release,
  16. some of which may remain unresolved, and the mysqlconnector dialect is
  17. **not tested as part of SQLAlchemy's continuous integration**.
  18. The recommended MySQL dialects are mysqlclient and PyMySQL.
  19. """ # noqa
  20. import re
  21. from .base import BIT
  22. from .base import MySQLCompiler
  23. from .base import MySQLDialect
  24. from .base import MySQLIdentifierPreparer
  25. from ... import util
  26. class MySQLCompiler_mysqlconnector(MySQLCompiler):
  27. def visit_mod_binary(self, binary, operator, **kw):
  28. return (
  29. self.process(binary.left, **kw)
  30. + " % "
  31. + self.process(binary.right, **kw)
  32. )
  33. class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
  34. @property
  35. def _double_percents(self):
  36. return False
  37. @_double_percents.setter
  38. def _double_percents(self, value):
  39. pass
  40. def _escape_identifier(self, value):
  41. value = value.replace(self.escape_quote, self.escape_to_quote)
  42. return value
  43. class _myconnpyBIT(BIT):
  44. def result_processor(self, dialect, coltype):
  45. """MySQL-connector already converts mysql bits, so."""
  46. return None
  47. class MySQLDialect_mysqlconnector(MySQLDialect):
  48. driver = "mysqlconnector"
  49. supports_statement_cache = True
  50. supports_sane_rowcount = True
  51. supports_sane_multi_rowcount = True
  52. supports_native_decimal = True
  53. default_paramstyle = "format"
  54. statement_compiler = MySQLCompiler_mysqlconnector
  55. preparer = MySQLIdentifierPreparer_mysqlconnector
  56. colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
  57. @classmethod
  58. def import_dbapi(cls):
  59. from mysql import connector
  60. return connector
  61. def do_ping(self, dbapi_connection):
  62. dbapi_connection.ping(False)
  63. return True
  64. def create_connect_args(self, url):
  65. opts = url.translate_connect_args(username="user")
  66. opts.update(url.query)
  67. util.coerce_kw_type(opts, "allow_local_infile", bool)
  68. util.coerce_kw_type(opts, "autocommit", bool)
  69. util.coerce_kw_type(opts, "buffered", bool)
  70. util.coerce_kw_type(opts, "compress", bool)
  71. util.coerce_kw_type(opts, "connection_timeout", int)
  72. util.coerce_kw_type(opts, "connect_timeout", int)
  73. util.coerce_kw_type(opts, "consume_results", bool)
  74. util.coerce_kw_type(opts, "force_ipv6", bool)
  75. util.coerce_kw_type(opts, "get_warnings", bool)
  76. util.coerce_kw_type(opts, "pool_reset_session", bool)
  77. util.coerce_kw_type(opts, "pool_size", int)
  78. util.coerce_kw_type(opts, "raise_on_warnings", bool)
  79. util.coerce_kw_type(opts, "raw", bool)
  80. util.coerce_kw_type(opts, "ssl_verify_cert", bool)
  81. util.coerce_kw_type(opts, "use_pure", bool)
  82. util.coerce_kw_type(opts, "use_unicode", bool)
  83. # unfortunately, MySQL/connector python refuses to release a
  84. # cursor without reading fully, so non-buffered isn't an option
  85. opts.setdefault("buffered", True)
  86. # FOUND_ROWS must be set in ClientFlag to enable
  87. # supports_sane_rowcount.
  88. if self.dbapi is not None:
  89. try:
  90. from mysql.connector.constants import ClientFlag
  91. client_flags = opts.get(
  92. "client_flags", ClientFlag.get_default()
  93. )
  94. client_flags |= ClientFlag.FOUND_ROWS
  95. opts["client_flags"] = client_flags
  96. except Exception:
  97. pass
  98. return [[], opts]
  99. @util.memoized_property
  100. def _mysqlconnector_version_info(self):
  101. if self.dbapi and hasattr(self.dbapi, "__version__"):
  102. m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
  103. if m:
  104. return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
  105. def _detect_charset(self, connection):
  106. return connection.connection.charset
  107. def _extract_error_code(self, exception):
  108. return exception.errno
  109. def is_disconnect(self, e, connection, cursor):
  110. errnos = (2006, 2013, 2014, 2045, 2055, 2048)
  111. exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError)
  112. if isinstance(e, exceptions):
  113. return (
  114. e.errno in errnos
  115. or "MySQL Connection not available." in str(e)
  116. or "Connection to MySQL is not available" in str(e)
  117. )
  118. else:
  119. return False
  120. def _compat_fetchall(self, rp, charset=None):
  121. return rp.fetchall()
  122. def _compat_fetchone(self, rp, charset=None):
  123. return rp.fetchone()
  124. _isolation_lookup = {
  125. "SERIALIZABLE",
  126. "READ UNCOMMITTED",
  127. "READ COMMITTED",
  128. "REPEATABLE READ",
  129. "AUTOCOMMIT",
  130. }
  131. def _set_isolation_level(self, connection, level):
  132. if level == "AUTOCOMMIT":
  133. connection.autocommit = True
  134. else:
  135. connection.autocommit = False
  136. super()._set_isolation_level(connection, level)
  137. dialect = MySQLDialect_mysqlconnector