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.
		
		
		
		
		
			
		
			
				
					131 lines
				
				4.0 KiB
			
		
		
			
		
	
	
					131 lines
				
				4.0 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								# mysql/pyodbc.py
							 | 
						||
| 
								 | 
							
								# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
							 | 
						||
| 
								 | 
							
								# <see AUTHORS file>
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This module is part of SQLAlchemy and is released under
							 | 
						||
| 
								 | 
							
								# the MIT License: https://www.opensource.org/licenses/mit-license.php
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								r"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. dialect:: mysql+pyodbc
							 | 
						||
| 
								 | 
							
								    :name: PyODBC
							 | 
						||
| 
								 | 
							
								    :dbapi: pyodbc
							 | 
						||
| 
								 | 
							
								    :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
							 | 
						||
| 
								 | 
							
								    :url: https://pypi.org/project/pyodbc/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. note::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The PyODBC for MySQL dialect is **not tested as part of
							 | 
						||
| 
								 | 
							
								    SQLAlchemy's continuous integration**.
							 | 
						||
| 
								 | 
							
								    The recommended MySQL dialects are mysqlclient and PyMySQL.
							 | 
						||
| 
								 | 
							
								    However, if you want to use the mysql+pyodbc dialect and require
							 | 
						||
| 
								 | 
							
								    full support for ``utf8mb4`` characters (including supplementary
							 | 
						||
| 
								 | 
							
								    characters like emoji) be sure to use a current release of
							 | 
						||
| 
								 | 
							
								    MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
							 | 
						||
| 
								 | 
							
								    version of the driver in your DSN or connection string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Pass through exact pyodbc connection string::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    import urllib
							 | 
						||
| 
								 | 
							
								    connection_string = (
							 | 
						||
| 
								 | 
							
								        'DRIVER=MySQL ODBC 8.0 ANSI Driver;'
							 | 
						||
| 
								 | 
							
								        'SERVER=localhost;'
							 | 
						||
| 
								 | 
							
								        'PORT=3307;'
							 | 
						||
| 
								 | 
							
								        'DATABASE=mydb;'
							 | 
						||
| 
								 | 
							
								        'UID=root;'
							 | 
						||
| 
								 | 
							
								        'PWD=(whatever);'
							 | 
						||
| 
								 | 
							
								        'charset=utf8mb4;'
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    params = urllib.parse.quote_plus(connection_string)
							 | 
						||
| 
								 | 
							
								    connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								"""  # noqa
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .base import MySQLDialect
							 | 
						||
| 
								 | 
							
								from .base import MySQLExecutionContext
							 | 
						||
| 
								 | 
							
								from .types import TIME
							 | 
						||
| 
								 | 
							
								from ... import exc
							 | 
						||
| 
								 | 
							
								from ... import util
							 | 
						||
| 
								 | 
							
								from ...connectors.pyodbc import PyODBCConnector
							 | 
						||
| 
								 | 
							
								from ...sql.sqltypes import Time
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _pyodbcTIME(TIME):
							 | 
						||
| 
								 | 
							
								    def result_processor(self, dialect, coltype):
							 | 
						||
| 
								 | 
							
								        def process(value):
							 | 
						||
| 
								 | 
							
								            # pyodbc returns a datetime.time object; no need to convert
							 | 
						||
| 
								 | 
							
								            return value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return process
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
							 | 
						||
| 
								 | 
							
								    def get_lastrowid(self):
							 | 
						||
| 
								 | 
							
								        cursor = self.create_cursor()
							 | 
						||
| 
								 | 
							
								        cursor.execute("SELECT LAST_INSERT_ID()")
							 | 
						||
| 
								 | 
							
								        lastrowid = cursor.fetchone()[0]
							 | 
						||
| 
								 | 
							
								        cursor.close()
							 | 
						||
| 
								 | 
							
								        return lastrowid
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
							 | 
						||
| 
								 | 
							
								    supports_statement_cache = True
							 | 
						||
| 
								 | 
							
								    colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
							 | 
						||
| 
								 | 
							
								    supports_unicode_statements = True
							 | 
						||
| 
								 | 
							
								    execution_ctx_cls = MySQLExecutionContext_pyodbc
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    pyodbc_driver_name = "MySQL"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _detect_charset(self, connection):
							 | 
						||
| 
								 | 
							
								        """Sniff out the character set in use for connection results."""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Prefer 'character_set_results' for the current connection over the
							 | 
						||
| 
								 | 
							
								        # value in the driver.  SET NAMES or individual variable SETs will
							 | 
						||
| 
								 | 
							
								        # change the charset without updating the driver's view of the world.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # If it's decided that issuing that sort of SQL leaves you SOL, then
							 | 
						||
| 
								 | 
							
								        # this can prefer the driver value.
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            value = self._fetch_setting(connection, "character_set_client")
							 | 
						||
| 
								 | 
							
								            if value:
							 | 
						||
| 
								 | 
							
								                return value
							 | 
						||
| 
								 | 
							
								        except exc.DBAPIError:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        util.warn(
							 | 
						||
| 
								 | 
							
								            "Could not detect the connection character set.  "
							 | 
						||
| 
								 | 
							
								            "Assuming latin1."
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        return "latin1"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _extract_error_code(self, exception):
							 | 
						||
| 
								 | 
							
								        m = re.compile(r"\((\d+)\)").search(str(exception.args))
							 | 
						||
| 
								 | 
							
								        c = m.group(1)
							 | 
						||
| 
								 | 
							
								        if c:
							 | 
						||
| 
								 | 
							
								            return int(c)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def on_connect(self):
							 | 
						||
| 
								 | 
							
								        super_ = super(MySQLDialect_pyodbc, self).on_connect()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def on_connect(conn):
							 | 
						||
| 
								 | 
							
								            if super_ is not None:
							 | 
						||
| 
								 | 
							
								                super_(conn)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # declare Unicode encoding for pyodbc as per
							 | 
						||
| 
								 | 
							
								            #   https://github.com/mkleehammer/pyodbc/wiki/Unicode
							 | 
						||
| 
								 | 
							
								            pyodbc_SQL_CHAR = 1  # pyodbc.SQL_CHAR
							 | 
						||
| 
								 | 
							
								            pyodbc_SQL_WCHAR = -8  # pyodbc.SQL_WCHAR
							 | 
						||
| 
								 | 
							
								            conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
							 | 
						||
| 
								 | 
							
								            conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
							 | 
						||
| 
								 | 
							
								            conn.setencoding(encoding="utf-8")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return on_connect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								dialect = MySQLDialect_pyodbc
							 |