mariadbconnector.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # mysql/mariadbconnector.py
  2. # Copyright (C) 2005-2022 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. """
  8. .. dialect:: mysql+mariadbconnector
  9. :name: MariaDB Connector/Python
  10. :dbapi: mariadb
  11. :connectstring: mariadb+mariadbconnector://<user>:<password>@<host>[:<port>]/<dbname>
  12. :url: https://pypi.org/project/mariadb/
  13. Driver Status
  14. -------------
  15. MariaDB Connector/Python enables Python programs to access MariaDB and MySQL
  16. databases using an API which is compliant with the Python DB API 2.0 (PEP-249).
  17. It is written in C and uses MariaDB Connector/C client library for client server
  18. communication.
  19. Note that the default driver for a ``mariadb://`` connection URI continues to
  20. be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.
  21. .. mariadb: https://github.com/mariadb-corporation/mariadb-connector-python
  22. """ # noqa
  23. import re
  24. from .base import MySQLCompiler
  25. from .base import MySQLDialect
  26. from .base import MySQLExecutionContext
  27. from ... import sql
  28. from ... import util
  29. mariadb_cpy_minimum_version = (1, 0, 1)
  30. class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
  31. def create_server_side_cursor(self):
  32. return self._dbapi_connection.cursor(buffered=False)
  33. def create_default_cursor(self):
  34. return self._dbapi_connection.cursor(buffered=True)
  35. class MySQLCompiler_mariadbconnector(MySQLCompiler):
  36. pass
  37. class MySQLDialect_mariadbconnector(MySQLDialect):
  38. driver = "mariadbconnector"
  39. supports_statement_cache = True
  40. # set this to True at the module level to prevent the driver from running
  41. # against a backend that server detects as MySQL. currently this appears to
  42. # be unnecessary as MariaDB client libraries have always worked against
  43. # MySQL databases. However, if this changes at some point, this can be
  44. # adjusted, but PLEASE ADD A TEST in test/dialect/mysql/test_dialect.py if
  45. # this change is made at some point to ensure the correct exception
  46. # is raised at the correct point when running the driver against
  47. # a MySQL backend.
  48. # is_mariadb = True
  49. supports_unicode_statements = True
  50. encoding = "utf8mb4"
  51. convert_unicode = True
  52. supports_sane_rowcount = True
  53. supports_sane_multi_rowcount = True
  54. supports_native_decimal = True
  55. default_paramstyle = "qmark"
  56. execution_ctx_cls = MySQLExecutionContext_mariadbconnector
  57. statement_compiler = MySQLCompiler_mariadbconnector
  58. supports_server_side_cursors = True
  59. @util.memoized_property
  60. def _dbapi_version(self):
  61. if self.dbapi and hasattr(self.dbapi, "__version__"):
  62. return tuple(
  63. [
  64. int(x)
  65. for x in re.findall(
  66. r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
  67. )
  68. ]
  69. )
  70. else:
  71. return (99, 99, 99)
  72. def __init__(self, **kwargs):
  73. super(MySQLDialect_mariadbconnector, self).__init__(**kwargs)
  74. self.paramstyle = "qmark"
  75. if self.dbapi is not None:
  76. if self._dbapi_version < mariadb_cpy_minimum_version:
  77. raise NotImplementedError(
  78. "The minimum required version for MariaDB "
  79. "Connector/Python is %s"
  80. % ".".join(str(x) for x in mariadb_cpy_minimum_version)
  81. )
  82. @classmethod
  83. def dbapi(cls):
  84. return __import__("mariadb")
  85. def is_disconnect(self, e, connection, cursor):
  86. if super(MySQLDialect_mariadbconnector, self).is_disconnect(
  87. e, connection, cursor
  88. ):
  89. return True
  90. elif isinstance(e, self.dbapi.Error):
  91. str_e = str(e).lower()
  92. return "not connected" in str_e or "isn't valid" in str_e
  93. else:
  94. return False
  95. def create_connect_args(self, url):
  96. opts = url.translate_connect_args()
  97. int_params = [
  98. "connect_timeout",
  99. "read_timeout",
  100. "write_timeout",
  101. "client_flag",
  102. "port",
  103. "pool_size",
  104. ]
  105. bool_params = [
  106. "local_infile",
  107. "ssl_verify_cert",
  108. "ssl",
  109. "pool_reset_connection",
  110. ]
  111. for key in int_params:
  112. util.coerce_kw_type(opts, key, int)
  113. for key in bool_params:
  114. util.coerce_kw_type(opts, key, bool)
  115. # FOUND_ROWS must be set in CLIENT_FLAGS to enable
  116. # supports_sane_rowcount.
  117. client_flag = opts.get("client_flag", 0)
  118. if self.dbapi is not None:
  119. try:
  120. CLIENT_FLAGS = __import__(
  121. self.dbapi.__name__ + ".constants.CLIENT"
  122. ).constants.CLIENT
  123. client_flag |= CLIENT_FLAGS.FOUND_ROWS
  124. except (AttributeError, ImportError):
  125. self.supports_sane_rowcount = False
  126. opts["client_flag"] = client_flag
  127. return [[], opts]
  128. def _extract_error_code(self, exception):
  129. try:
  130. rc = exception.errno
  131. except:
  132. rc = -1
  133. return rc
  134. def _detect_charset(self, connection):
  135. return "utf8mb4"
  136. _isolation_lookup = set(
  137. [
  138. "SERIALIZABLE",
  139. "READ UNCOMMITTED",
  140. "READ COMMITTED",
  141. "REPEATABLE READ",
  142. "AUTOCOMMIT",
  143. ]
  144. )
  145. def _set_isolation_level(self, connection, level):
  146. if level == "AUTOCOMMIT":
  147. connection.autocommit = True
  148. else:
  149. connection.autocommit = False
  150. super(MySQLDialect_mariadbconnector, self)._set_isolation_level(
  151. connection, level
  152. )
  153. def do_begin_twophase(self, connection, xid):
  154. connection.execute(
  155. sql.text("XA BEGIN :xid").bindparams(
  156. sql.bindparam("xid", xid, literal_execute=True)
  157. )
  158. )
  159. def do_prepare_twophase(self, connection, xid):
  160. connection.execute(
  161. sql.text("XA END :xid").bindparams(
  162. sql.bindparam("xid", xid, literal_execute=True)
  163. )
  164. )
  165. connection.execute(
  166. sql.text("XA PREPARE :xid").bindparams(
  167. sql.bindparam("xid", xid, literal_execute=True)
  168. )
  169. )
  170. def do_rollback_twophase(
  171. self, connection, xid, is_prepared=True, recover=False
  172. ):
  173. if not is_prepared:
  174. connection.execute(
  175. sql.text("XA END :xid").bindparams(
  176. sql.bindparam("xid", xid, literal_execute=True)
  177. )
  178. )
  179. connection.execute(
  180. sql.text("XA ROLLBACK :xid").bindparams(
  181. sql.bindparam("xid", xid, literal_execute=True)
  182. )
  183. )
  184. def do_commit_twophase(
  185. self, connection, xid, is_prepared=True, recover=False
  186. ):
  187. if not is_prepared:
  188. self.do_prepare_twophase(connection, xid)
  189. connection.execute(
  190. sql.text("XA COMMIT :xid").bindparams(
  191. sql.bindparam("xid", xid, literal_execute=True)
  192. )
  193. )
  194. dialect = MySQLDialect_mariadbconnector