mysqldb.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # mysql/mysqldb.py
  2. # Copyright (C) 2005-2017 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+mysqldb
  9. :name: MySQL-Python
  10. :dbapi: mysqldb
  11. :connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
  12. :url: http://sourceforge.net/projects/mysql-python
  13. .. _mysqldb_unicode:
  14. Unicode
  15. -------
  16. Please see :ref:`mysql_unicode` for current recommendations on unicode
  17. handling.
  18. Py3K Support
  19. ------------
  20. Currently, MySQLdb only runs on Python 2 and development has been stopped.
  21. `mysqlclient`_ is fork of MySQLdb and provides Python 3 support as well
  22. as some bugfixes.
  23. .. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python
  24. Using MySQLdb with Google Cloud SQL
  25. -----------------------------------
  26. Google Cloud SQL now recommends use of the MySQLdb dialect. Connect
  27. using a URL like the following::
  28. mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
  29. Server Side Cursors
  30. -------------------
  31. The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
  32. """
  33. from .base import (MySQLDialect, MySQLExecutionContext,
  34. MySQLCompiler, MySQLIdentifierPreparer)
  35. from .base import TEXT
  36. from ... import sql
  37. from ... import util
  38. import re
  39. class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
  40. @property
  41. def rowcount(self):
  42. if hasattr(self, '_rowcount'):
  43. return self._rowcount
  44. else:
  45. return self.cursor.rowcount
  46. class MySQLCompiler_mysqldb(MySQLCompiler):
  47. def visit_mod_binary(self, binary, operator, **kw):
  48. return self.process(binary.left, **kw) + " %% " + \
  49. self.process(binary.right, **kw)
  50. def post_process_text(self, text):
  51. return text.replace('%', '%%')
  52. class MySQLIdentifierPreparer_mysqldb(MySQLIdentifierPreparer):
  53. def _escape_identifier(self, value):
  54. value = value.replace(self.escape_quote, self.escape_to_quote)
  55. return value.replace("%", "%%")
  56. class MySQLDialect_mysqldb(MySQLDialect):
  57. driver = 'mysqldb'
  58. supports_unicode_statements = True
  59. supports_sane_rowcount = True
  60. supports_sane_multi_rowcount = True
  61. supports_native_decimal = True
  62. default_paramstyle = 'format'
  63. execution_ctx_cls = MySQLExecutionContext_mysqldb
  64. statement_compiler = MySQLCompiler_mysqldb
  65. preparer = MySQLIdentifierPreparer_mysqldb
  66. def __init__(self, server_side_cursors=False, **kwargs):
  67. super(MySQLDialect_mysqldb, self).__init__(**kwargs)
  68. self.server_side_cursors = server_side_cursors
  69. @util.langhelpers.memoized_property
  70. def supports_server_side_cursors(self):
  71. try:
  72. cursors = __import__('MySQLdb.cursors').cursors
  73. self._sscursor = cursors.SSCursor
  74. return True
  75. except (ImportError, AttributeError):
  76. return False
  77. @classmethod
  78. def dbapi(cls):
  79. return __import__('MySQLdb')
  80. def do_executemany(self, cursor, statement, parameters, context=None):
  81. rowcount = cursor.executemany(statement, parameters)
  82. if context is not None:
  83. context._rowcount = rowcount
  84. def _check_unicode_returns(self, connection):
  85. # work around issue fixed in
  86. # https://github.com/farcepest/MySQLdb1/commit/cd44524fef63bd3fcb71947392326e9742d520e8
  87. # specific issue w/ the utf8_bin collation and unicode returns
  88. has_utf8_bin = self.server_version_info > (5, ) and \
  89. connection.scalar(
  90. "show collation where %s = 'utf8' and %s = 'utf8_bin'"
  91. % (
  92. self.identifier_preparer.quote("Charset"),
  93. self.identifier_preparer.quote("Collation")
  94. ))
  95. if has_utf8_bin:
  96. additional_tests = [
  97. sql.collate(sql.cast(
  98. sql.literal_column(
  99. "'test collated returns'"),
  100. TEXT(charset='utf8')), "utf8_bin")
  101. ]
  102. else:
  103. additional_tests = []
  104. return super(MySQLDialect_mysqldb, self)._check_unicode_returns(
  105. connection, additional_tests)
  106. def create_connect_args(self, url):
  107. opts = url.translate_connect_args(database='db', username='user',
  108. password='passwd')
  109. opts.update(url.query)
  110. util.coerce_kw_type(opts, 'compress', bool)
  111. util.coerce_kw_type(opts, 'connect_timeout', int)
  112. util.coerce_kw_type(opts, 'read_timeout', int)
  113. util.coerce_kw_type(opts, 'client_flag', int)
  114. util.coerce_kw_type(opts, 'local_infile', int)
  115. # Note: using either of the below will cause all strings to be
  116. # returned as Unicode, both in raw SQL operations and with column
  117. # types like String and MSString.
  118. util.coerce_kw_type(opts, 'use_unicode', bool)
  119. util.coerce_kw_type(opts, 'charset', str)
  120. # Rich values 'cursorclass' and 'conv' are not supported via
  121. # query string.
  122. ssl = {}
  123. keys = ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']
  124. for key in keys:
  125. if key in opts:
  126. ssl[key[4:]] = opts[key]
  127. util.coerce_kw_type(ssl, key[4:], str)
  128. del opts[key]
  129. if ssl:
  130. opts['ssl'] = ssl
  131. # FOUND_ROWS must be set in CLIENT_FLAGS to enable
  132. # supports_sane_rowcount.
  133. client_flag = opts.get('client_flag', 0)
  134. if self.dbapi is not None:
  135. try:
  136. CLIENT_FLAGS = __import__(
  137. self.dbapi.__name__ + '.constants.CLIENT'
  138. ).constants.CLIENT
  139. client_flag |= CLIENT_FLAGS.FOUND_ROWS
  140. except (AttributeError, ImportError):
  141. self.supports_sane_rowcount = False
  142. opts['client_flag'] = client_flag
  143. return [[], opts]
  144. def _get_server_version_info(self, connection):
  145. dbapi_con = connection.connection
  146. version = []
  147. r = re.compile(r'[.\-]')
  148. for n in r.split(dbapi_con.get_server_info()):
  149. try:
  150. version.append(int(n))
  151. except ValueError:
  152. version.append(n)
  153. return tuple(version)
  154. def _extract_error_code(self, exception):
  155. return exception.args[0]
  156. def _detect_charset(self, connection):
  157. """Sniff out the character set in use for connection results."""
  158. try:
  159. # note: the SQL here would be
  160. # "SHOW VARIABLES LIKE 'character_set%%'"
  161. cset_name = connection.connection.character_set_name
  162. except AttributeError:
  163. util.warn(
  164. "No 'character_set_name' can be detected with "
  165. "this MySQL-Python version; "
  166. "please upgrade to a recent version of MySQL-Python. "
  167. "Assuming latin1.")
  168. return 'latin1'
  169. else:
  170. return cset_name()
  171. _isolation_lookup = set(['SERIALIZABLE', 'READ UNCOMMITTED',
  172. 'READ COMMITTED', 'REPEATABLE READ',
  173. 'AUTOCOMMIT'])
  174. def _set_isolation_level(self, connection, level):
  175. if level == 'AUTOCOMMIT':
  176. connection.autocommit(True)
  177. else:
  178. connection.autocommit(False)
  179. super(MySQLDialect_mysqldb, self)._set_isolation_level(connection,
  180. level)
  181. dialect = MySQLDialect_mysqldb