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.

138 lines
4.4KB

  1. # mysql/pyodbc.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. r"""
  8. .. dialect:: mysql+pyodbc
  9. :name: PyODBC
  10. :dbapi: pyodbc
  11. :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
  12. :url: http://pypi.python.org/pypi/pyodbc/
  13. .. note::
  14. The PyODBC for MySQL dialect is **not tested as part of
  15. SQLAlchemy's continuous integration**.
  16. The recommended MySQL dialects are mysqlclient and PyMySQL.
  17. However, if you want to use the mysql+pyodbc dialect and require
  18. full support for ``utf8mb4`` characters (including supplementary
  19. characters like emoji) be sure to use a current release of
  20. MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
  21. version of the driver in your DSN or connection string.
  22. Pass through exact pyodbc connection string::
  23. import urllib
  24. connection_string = (
  25. 'DRIVER=MySQL ODBC 8.0 ANSI Driver;'
  26. 'SERVER=localhost;'
  27. 'PORT=3307;'
  28. 'DATABASE=mydb;'
  29. 'UID=root;'
  30. 'PWD=(whatever);'
  31. 'charset=utf8mb4;'
  32. )
  33. params = urllib.parse.quote_plus(connection_string)
  34. connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
  35. """ # noqa
  36. import re
  37. import sys
  38. from .base import MySQLDialect
  39. from .base import MySQLExecutionContext
  40. from .types import TIME
  41. from ... import util
  42. from ...connectors.pyodbc import PyODBCConnector
  43. from ...sql.sqltypes import Time
  44. class _pyodbcTIME(TIME):
  45. def result_processor(self, dialect, coltype):
  46. def process(value):
  47. # pyodbc returns a datetime.time object; no need to convert
  48. return value
  49. return process
  50. class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
  51. def get_lastrowid(self):
  52. cursor = self.create_cursor()
  53. cursor.execute("SELECT LAST_INSERT_ID()")
  54. lastrowid = cursor.fetchone()[0]
  55. cursor.close()
  56. return lastrowid
  57. class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
  58. supports_statement_cache = True
  59. colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
  60. supports_unicode_statements = True
  61. execution_ctx_cls = MySQLExecutionContext_pyodbc
  62. pyodbc_driver_name = "MySQL"
  63. def _detect_charset(self, connection):
  64. """Sniff out the character set in use for connection results."""
  65. # Prefer 'character_set_results' for the current connection over the
  66. # value in the driver. SET NAMES or individual variable SETs will
  67. # change the charset without updating the driver's view of the world.
  68. #
  69. # If it's decided that issuing that sort of SQL leaves you SOL, then
  70. # this can prefer the driver value.
  71. rs = connection.exec_driver_sql(
  72. "SHOW VARIABLES LIKE 'character_set%%'"
  73. )
  74. opts = {row[0]: row[1] for row in self._compat_fetchall(rs)}
  75. for key in ("character_set_connection", "character_set"):
  76. if opts.get(key, None):
  77. return opts[key]
  78. util.warn(
  79. "Could not detect the connection character set. "
  80. "Assuming latin1."
  81. )
  82. return "latin1"
  83. def _extract_error_code(self, exception):
  84. m = re.compile(r"\((\d+)\)").search(str(exception.args))
  85. c = m.group(1)
  86. if c:
  87. return int(c)
  88. else:
  89. return None
  90. def on_connect(self):
  91. super_ = super(MySQLDialect_pyodbc, self).on_connect()
  92. def on_connect(conn):
  93. if super_ is not None:
  94. super_(conn)
  95. # declare Unicode encoding for pyodbc as per
  96. # https://github.com/mkleehammer/pyodbc/wiki/Unicode
  97. pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
  98. pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
  99. if sys.version_info.major > 2:
  100. conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
  101. conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
  102. conn.setencoding(encoding="utf-8")
  103. else:
  104. conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
  105. conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
  106. conn.setencoding(str, encoding="utf-8")
  107. conn.setencoding(unicode, encoding="utf-8") # noqa: F821
  108. return on_connect
  109. dialect = MySQLDialect_pyodbc