You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
7.3KB

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