Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# mysql/mysqlconnector.py 

2# Copyright (C) 2005-2020 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 

8r""" 

9.. dialect:: mysql+mysqlconnector 

10 :name: MySQL Connector/Python 

11 :dbapi: myconnpy 

12 :connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname> 

13 :url: https://pypi.org/project/mysql-connector-python/ 

14 

15.. note:: 

16 

17 The MySQL Connector/Python DBAPI has had many issues since its release, 

18 some of which may remain unresolved, and the mysqlconnector dialect is 

19 **not tested as part of SQLAlchemy's continuous integration**. 

20 The recommended MySQL dialects are mysqlclient and PyMySQL. 

21 

22""" # noqa 

23 

24import re 

25 

26from .base import BIT 

27from .base import MySQLCompiler 

28from .base import MySQLDialect 

29from .base import MySQLExecutionContext 

30from .base import MySQLIdentifierPreparer 

31from ... import processors 

32from ... import util 

33 

34 

35class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext): 

36 def get_lastrowid(self): 

37 return self.cursor.lastrowid 

38 

39 

40class MySQLCompiler_mysqlconnector(MySQLCompiler): 

41 def visit_mod_binary(self, binary, operator, **kw): 

42 if self.dialect._mysqlconnector_double_percents: 

43 return ( 

44 self.process(binary.left, **kw) 

45 + " %% " 

46 + self.process(binary.right, **kw) 

47 ) 

48 else: 

49 return ( 

50 self.process(binary.left, **kw) 

51 + " % " 

52 + self.process(binary.right, **kw) 

53 ) 

54 

55 def post_process_text(self, text): 

56 if self.dialect._mysqlconnector_double_percents: 

57 return text.replace("%", "%%") 

58 else: 

59 return text 

60 

61 def escape_literal_column(self, text): 

62 if self.dialect._mysqlconnector_double_percents: 

63 return text.replace("%", "%%") 

64 else: 

65 return text 

66 

67 

68class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer): 

69 @property 

70 def _double_percents(self): 

71 return self.dialect._mysqlconnector_double_percents 

72 

73 @_double_percents.setter 

74 def _double_percents(self, value): 

75 pass 

76 

77 def _escape_identifier(self, value): 

78 value = value.replace(self.escape_quote, self.escape_to_quote) 

79 if self.dialect._mysqlconnector_double_percents: 

80 return value.replace("%", "%%") 

81 else: 

82 return value 

83 

84 

85class _myconnpyBIT(BIT): 

86 def result_processor(self, dialect, coltype): 

87 """MySQL-connector already converts mysql bits, so.""" 

88 

89 return None 

90 

91 

92class MySQLDialect_mysqlconnector(MySQLDialect): 

93 driver = "mysqlconnector" 

94 

95 supports_unicode_binds = True 

96 

97 supports_sane_rowcount = True 

98 supports_sane_multi_rowcount = True 

99 

100 supports_native_decimal = True 

101 

102 default_paramstyle = "format" 

103 execution_ctx_cls = MySQLExecutionContext_mysqlconnector 

104 statement_compiler = MySQLCompiler_mysqlconnector 

105 

106 preparer = MySQLIdentifierPreparer_mysqlconnector 

107 

108 colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT}) 

109 

110 def __init__(self, *arg, **kw): 

111 super(MySQLDialect_mysqlconnector, self).__init__(*arg, **kw) 

112 

113 # hack description encoding since mysqlconnector randomly 

114 # returns bytes or not 

115 self._description_decoder = ( 

116 processors.to_conditional_unicode_processor_factory 

117 )(self.description_encoding) 

118 

119 def _check_unicode_description(self, connection): 

120 # hack description encoding since mysqlconnector randomly 

121 # returns bytes or not 

122 return False 

123 

124 @property 

125 def description_encoding(self): 

126 # total guess 

127 return "latin-1" 

128 

129 @util.memoized_property 

130 def supports_unicode_statements(self): 

131 return util.py3k or self._mysqlconnector_version_info > (2, 0) 

132 

133 @classmethod 

134 def dbapi(cls): 

135 from mysql import connector 

136 

137 return connector 

138 

139 def do_ping(self, dbapi_connection): 

140 try: 

141 dbapi_connection.ping(False) 

142 except self.dbapi.Error as err: 

143 if self.is_disconnect(err, dbapi_connection, None): 

144 return False 

145 else: 

146 raise 

147 else: 

148 return True 

149 

150 def create_connect_args(self, url): 

151 opts = url.translate_connect_args(username="user") 

152 

153 opts.update(url.query) 

154 

155 util.coerce_kw_type(opts, "allow_local_infile", bool) 

156 util.coerce_kw_type(opts, "autocommit", bool) 

157 util.coerce_kw_type(opts, "buffered", bool) 

158 util.coerce_kw_type(opts, "compress", bool) 

159 util.coerce_kw_type(opts, "connection_timeout", int) 

160 util.coerce_kw_type(opts, "connect_timeout", int) 

161 util.coerce_kw_type(opts, "consume_results", bool) 

162 util.coerce_kw_type(opts, "force_ipv6", bool) 

163 util.coerce_kw_type(opts, "get_warnings", bool) 

164 util.coerce_kw_type(opts, "pool_reset_session", bool) 

165 util.coerce_kw_type(opts, "pool_size", int) 

166 util.coerce_kw_type(opts, "raise_on_warnings", bool) 

167 util.coerce_kw_type(opts, "raw", bool) 

168 util.coerce_kw_type(opts, "ssl_verify_cert", bool) 

169 util.coerce_kw_type(opts, "use_pure", bool) 

170 util.coerce_kw_type(opts, "use_unicode", bool) 

171 

172 # unfortunately, MySQL/connector python refuses to release a 

173 # cursor without reading fully, so non-buffered isn't an option 

174 opts.setdefault("buffered", True) 

175 

176 # FOUND_ROWS must be set in ClientFlag to enable 

177 # supports_sane_rowcount. 

178 if self.dbapi is not None: 

179 try: 

180 from mysql.connector.constants import ClientFlag 

181 

182 client_flags = opts.get( 

183 "client_flags", ClientFlag.get_default() 

184 ) 

185 client_flags |= ClientFlag.FOUND_ROWS 

186 opts["client_flags"] = client_flags 

187 except Exception: 

188 pass 

189 return [[], opts] 

190 

191 @util.memoized_property 

192 def _mysqlconnector_version_info(self): 

193 if self.dbapi and hasattr(self.dbapi, "__version__"): 

194 m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__) 

195 if m: 

196 return tuple(int(x) for x in m.group(1, 2, 3) if x is not None) 

197 

198 @util.memoized_property 

199 def _mysqlconnector_double_percents(self): 

200 return not util.py3k and self._mysqlconnector_version_info < (2, 0) 

201 

202 def _detect_charset(self, connection): 

203 return connection.connection.charset 

204 

205 def _extract_error_code(self, exception): 

206 return exception.errno 

207 

208 def is_disconnect(self, e, connection, cursor): 

209 errnos = (2006, 2013, 2014, 2045, 2055, 2048) 

210 exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError) 

211 if isinstance(e, exceptions): 

212 return ( 

213 e.errno in errnos 

214 or "MySQL Connection not available." in str(e) 

215 or "Connection to MySQL is not available" in str(e) 

216 ) 

217 else: 

218 return False 

219 

220 def _compat_fetchall(self, rp, charset=None): 

221 return rp.fetchall() 

222 

223 def _compat_fetchone(self, rp, charset=None): 

224 return rp.fetchone() 

225 

226 _isolation_lookup = set( 

227 [ 

228 "SERIALIZABLE", 

229 "READ UNCOMMITTED", 

230 "READ COMMITTED", 

231 "REPEATABLE READ", 

232 "AUTOCOMMIT", 

233 ] 

234 ) 

235 

236 def _set_isolation_level(self, connection, level): 

237 if level == "AUTOCOMMIT": 

238 connection.autocommit = True 

239 else: 

240 connection.autocommit = False 

241 super(MySQLDialect_mysqlconnector, self)._set_isolation_level( 

242 connection, level 

243 ) 

244 

245 

246dialect = MySQLDialect_mysqlconnector