pyodbc.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # mysql/pyodbc.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. r"""
  8. .. dialect:: mysql+pyodbc
  9. :name: PyODBC
  10. :dbapi: pyodbc
  11. :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
  12. :url: https://pypi.org/project/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. from .base import MySQLDialect
  38. from .base import MySQLExecutionContext
  39. from .types import TIME
  40. from ... import exc
  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. try:
  72. value = connection.exec_driver_sql(
  73. "select @@character_set_client"
  74. ).scalar()
  75. if value:
  76. return value
  77. except exc.DBAPIError:
  78. pass
  79. util.warn(
  80. "Could not detect the connection character set. "
  81. "Assuming latin1."
  82. )
  83. return "latin1"
  84. def _extract_error_code(self, exception):
  85. m = re.compile(r"\((\d+)\)").search(str(exception.args))
  86. c = m.group(1)
  87. if c:
  88. return int(c)
  89. else:
  90. return None
  91. def on_connect(self):
  92. super_ = super(MySQLDialect_pyodbc, self).on_connect()
  93. def on_connect(conn):
  94. if super_ is not None:
  95. super_(conn)
  96. # declare Unicode encoding for pyodbc as per
  97. # https://github.com/mkleehammer/pyodbc/wiki/Unicode
  98. pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
  99. pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
  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. return on_connect
  104. dialect = MySQLDialect_pyodbc