1# mysql/mysqlconnector.py 2# Copyright (C) 2005-2018 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""" 9.. dialect:: mysql+mysqlconnector 10 :name: MySQL Connector/Python 11 :dbapi: myconnpy 12 :connectstring: mysql+mysqlconnector://<user>:<password>@\ 13<host>[:<port>]/<dbname> 14 :url: http://dev.mysql.com/downloads/connector/python/ 15 16 17Unicode 18------- 19 20Please see :ref:`mysql_unicode` for current recommendations on unicode 21handling. 22 23""" 24 25from .base import (MySQLDialect, MySQLExecutionContext, 26 MySQLCompiler, MySQLIdentifierPreparer, 27 BIT) 28 29from ... import util 30import re 31 32 33class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext): 34 35 def get_lastrowid(self): 36 return self.cursor.lastrowid 37 38 39class MySQLCompiler_mysqlconnector(MySQLCompiler): 40 def visit_mod_binary(self, binary, operator, **kw): 41 if self.dialect._mysqlconnector_double_percents: 42 return self.process(binary.left, **kw) + " %% " + \ 43 self.process(binary.right, **kw) 44 else: 45 return self.process(binary.left, **kw) + " % " + \ 46 self.process(binary.right, **kw) 47 48 def post_process_text(self, text): 49 if self.dialect._mysqlconnector_double_percents: 50 return text.replace('%', '%%') 51 else: 52 return text 53 54 def escape_literal_column(self, text): 55 if self.dialect._mysqlconnector_double_percents: 56 return text.replace('%', '%%') 57 else: 58 return text 59 60 61class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer): 62 63 def _escape_identifier(self, value): 64 value = value.replace(self.escape_quote, self.escape_to_quote) 65 if self.dialect._mysqlconnector_double_percents: 66 return value.replace("%", "%%") 67 else: 68 return value 69 70 71class _myconnpyBIT(BIT): 72 def result_processor(self, dialect, coltype): 73 """MySQL-connector already converts mysql bits, so.""" 74 75 return None 76 77 78class MySQLDialect_mysqlconnector(MySQLDialect): 79 driver = 'mysqlconnector' 80 81 supports_unicode_binds = True 82 83 supports_sane_rowcount = True 84 supports_sane_multi_rowcount = True 85 86 supports_native_decimal = True 87 88 default_paramstyle = 'format' 89 execution_ctx_cls = MySQLExecutionContext_mysqlconnector 90 statement_compiler = MySQLCompiler_mysqlconnector 91 92 preparer = MySQLIdentifierPreparer_mysqlconnector 93 94 colspecs = util.update_copy( 95 MySQLDialect.colspecs, 96 { 97 BIT: _myconnpyBIT, 98 } 99 ) 100 101 @util.memoized_property 102 def supports_unicode_statements(self): 103 return util.py3k or self._mysqlconnector_version_info > (2, 0) 104 105 @classmethod 106 def dbapi(cls): 107 from mysql import connector 108 return connector 109 110 def create_connect_args(self, url): 111 opts = url.translate_connect_args(username='user') 112 113 opts.update(url.query) 114 115 util.coerce_kw_type(opts, 'allow_local_infile', bool) 116 util.coerce_kw_type(opts, 'autocommit', bool) 117 util.coerce_kw_type(opts, 'buffered', bool) 118 util.coerce_kw_type(opts, 'compress', bool) 119 util.coerce_kw_type(opts, 'connection_timeout', int) 120 util.coerce_kw_type(opts, 'connect_timeout', int) 121 util.coerce_kw_type(opts, 'consume_results', bool) 122 util.coerce_kw_type(opts, 'force_ipv6', bool) 123 util.coerce_kw_type(opts, 'get_warnings', bool) 124 util.coerce_kw_type(opts, 'pool_reset_session', bool) 125 util.coerce_kw_type(opts, 'pool_size', int) 126 util.coerce_kw_type(opts, 'raise_on_warnings', bool) 127 util.coerce_kw_type(opts, 'raw', bool) 128 util.coerce_kw_type(opts, 'ssl_verify_cert', bool) 129 util.coerce_kw_type(opts, 'use_pure', bool) 130 util.coerce_kw_type(opts, 'use_unicode', bool) 131 132 # unfortunately, MySQL/connector python refuses to release a 133 # cursor without reading fully, so non-buffered isn't an option 134 opts.setdefault('buffered', True) 135 136 # FOUND_ROWS must be set in ClientFlag to enable 137 # supports_sane_rowcount. 138 if self.dbapi is not None: 139 try: 140 from mysql.connector.constants import ClientFlag 141 client_flags = opts.get( 142 'client_flags', ClientFlag.get_default()) 143 client_flags |= ClientFlag.FOUND_ROWS 144 opts['client_flags'] = client_flags 145 except Exception: 146 pass 147 return [[], opts] 148 149 @util.memoized_property 150 def _mysqlconnector_version_info(self): 151 if self.dbapi and hasattr(self.dbapi, '__version__'): 152 m = re.match(r'(\d+)\.(\d+)(?:\.(\d+))?', 153 self.dbapi.__version__) 154 if m: 155 return tuple( 156 int(x) 157 for x in m.group(1, 2, 3) 158 if x is not None) 159 160 @util.memoized_property 161 def _mysqlconnector_double_percents(self): 162 return not util.py3k and self._mysqlconnector_version_info < (2, 0) 163 164 def _detect_charset(self, connection): 165 return connection.connection.charset 166 167 def _extract_error_code(self, exception): 168 return exception.errno 169 170 def is_disconnect(self, e, connection, cursor): 171 errnos = (2006, 2013, 2014, 2045, 2055, 2048) 172 exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError) 173 if isinstance(e, exceptions): 174 return e.errno in errnos or \ 175 "MySQL Connection not available." in str(e) 176 else: 177 return False 178 179 def _compat_fetchall(self, rp, charset=None): 180 return rp.fetchall() 181 182 def _compat_fetchone(self, rp, charset=None): 183 return rp.fetchone() 184 185 _isolation_lookup = set(['SERIALIZABLE', 'READ UNCOMMITTED', 186 'READ COMMITTED', 'REPEATABLE READ', 187 'AUTOCOMMIT']) 188 189 def _set_isolation_level(self, connection, level): 190 if level == 'AUTOCOMMIT': 191 connection.autocommit = True 192 else: 193 connection.autocommit = False 194 super(MySQLDialect_mysqlconnector, self)._set_isolation_level( 195 connection, level) 196 197 198dialect = MySQLDialect_mysqlconnector 199