1# mysql/oursql.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: https://www.opensource.org/licenses/mit-license.php 7 8""" 9 10.. dialect:: mysql+oursql 11 :name: OurSQL 12 :dbapi: oursql 13 :connectstring: mysql+oursql://<user>:<password>@<host>[:<port>]/<dbname> 14 :url: https://packages.python.org/oursql/ 15 16.. note:: 17 18 The OurSQL MySQL dialect is legacy and is no longer supported upstream, 19 and is **not tested as part of SQLAlchemy's continuous integration**. 20 The recommended MySQL dialects are mysqlclient and PyMySQL. 21 22.. deprecated:: 1.4 The OurSQL DBAPI is deprecated and will be removed 23 in a future version. Please use one of the supported DBAPIs to 24 connect to mysql. 25 26Unicode 27------- 28 29Please see :ref:`mysql_unicode` for current recommendations on unicode 30handling. 31 32 33""" 34 35 36from .base import BIT 37from .base import MySQLDialect 38from .base import MySQLExecutionContext 39from ... import types as sqltypes 40from ... import util 41 42 43class _oursqlBIT(BIT): 44 def result_processor(self, dialect, coltype): 45 """oursql already converts mysql bits, so.""" 46 47 return None 48 49 50class MySQLExecutionContext_oursql(MySQLExecutionContext): 51 @property 52 def plain_query(self): 53 return self.execution_options.get("_oursql_plain_query", False) 54 55 56class MySQLDialect_oursql(MySQLDialect): 57 driver = "oursql" 58 supports_statement_cache = True 59 60 if util.py2k: 61 supports_unicode_binds = True 62 supports_unicode_statements = True 63 64 supports_native_decimal = True 65 66 supports_sane_rowcount = True 67 supports_sane_multi_rowcount = True 68 execution_ctx_cls = MySQLExecutionContext_oursql 69 70 colspecs = util.update_copy( 71 MySQLDialect.colspecs, {sqltypes.Time: sqltypes.Time, BIT: _oursqlBIT} 72 ) 73 74 @classmethod 75 def dbapi(cls): 76 util.warn_deprecated( 77 "The OurSQL DBAPI is deprecated and will be removed " 78 "in a future version. Please use one of the supported DBAPIs to " 79 "connect to mysql.", 80 version="1.4", 81 ) 82 return __import__("oursql") 83 84 def do_execute(self, cursor, statement, parameters, context=None): 85 """Provide an implementation of 86 *cursor.execute(statement, parameters)*.""" 87 88 if context and context.plain_query: 89 cursor.execute(statement, plain_query=True) 90 else: 91 cursor.execute(statement, parameters) 92 93 def do_begin(self, connection): 94 connection.cursor().execute("BEGIN", plain_query=True) 95 96 def _xa_query(self, connection, query, xid): 97 if util.py2k: 98 arg = connection.connection._escape_string(xid) 99 else: 100 charset = self._connection_charset 101 arg = connection.connection._escape_string( 102 xid.encode(charset) 103 ).decode(charset) 104 arg = "'%s'" % arg 105 connection.execution_options(_oursql_plain_query=True).exec_driver_sql( 106 query % arg 107 ) 108 109 # Because mysql is bad, these methods have to be 110 # reimplemented to use _PlainQuery. Basically, some queries 111 # refuse to return any data if they're run through 112 # the parameterized query API, or refuse to be parameterized 113 # in the first place. 114 def do_begin_twophase(self, connection, xid): 115 self._xa_query(connection, "XA BEGIN %s", xid) 116 117 def do_prepare_twophase(self, connection, xid): 118 self._xa_query(connection, "XA END %s", xid) 119 self._xa_query(connection, "XA PREPARE %s", xid) 120 121 def do_rollback_twophase( 122 self, connection, xid, is_prepared=True, recover=False 123 ): 124 if not is_prepared: 125 self._xa_query(connection, "XA END %s", xid) 126 self._xa_query(connection, "XA ROLLBACK %s", xid) 127 128 def do_commit_twophase( 129 self, connection, xid, is_prepared=True, recover=False 130 ): 131 if not is_prepared: 132 self.do_prepare_twophase(connection, xid) 133 self._xa_query(connection, "XA COMMIT %s", xid) 134 135 # Q: why didn't we need all these "plain_query" overrides earlier ? 136 # am i on a newer/older version of OurSQL ? 137 def has_table(self, connection, table_name, schema=None): 138 return MySQLDialect.has_table( 139 self, 140 connection.connect().execution_options(_oursql_plain_query=True), 141 table_name, 142 schema, 143 ) 144 145 def get_table_options(self, connection, table_name, schema=None, **kw): 146 return MySQLDialect.get_table_options( 147 self, 148 connection.connect().execution_options(_oursql_plain_query=True), 149 table_name, 150 schema=schema, 151 **kw 152 ) 153 154 def get_columns(self, connection, table_name, schema=None, **kw): 155 return MySQLDialect.get_columns( 156 self, 157 connection.connect().execution_options(_oursql_plain_query=True), 158 table_name, 159 schema=schema, 160 **kw 161 ) 162 163 def get_view_names(self, connection, schema=None, **kw): 164 return MySQLDialect.get_view_names( 165 self, 166 connection.connect().execution_options(_oursql_plain_query=True), 167 schema=schema, 168 **kw 169 ) 170 171 def get_table_names(self, connection, schema=None, **kw): 172 return MySQLDialect.get_table_names( 173 self, 174 connection.connect().execution_options(_oursql_plain_query=True), 175 schema, 176 ) 177 178 def get_schema_names(self, connection, **kw): 179 return MySQLDialect.get_schema_names( 180 self, 181 connection.connect().execution_options(_oursql_plain_query=True), 182 **kw 183 ) 184 185 def initialize(self, connection): 186 return MySQLDialect.initialize( 187 self, connection.execution_options(_oursql_plain_query=True) 188 ) 189 190 def _show_create_table( 191 self, connection, table, charset=None, full_name=None 192 ): 193 return MySQLDialect._show_create_table( 194 self, 195 connection.connect(close_with_result=True).execution_options( 196 _oursql_plain_query=True 197 ), 198 table, 199 charset, 200 full_name, 201 ) 202 203 def is_disconnect(self, e, connection, cursor): 204 if isinstance(e, self.dbapi.ProgrammingError): 205 return ( 206 e.errno is None 207 and "cursor" not in e.args[1] 208 and e.args[1].endswith("closed") 209 ) 210 else: 211 return e.errno in (2006, 2013, 2014, 2045, 2055) 212 213 def create_connect_args(self, url): 214 opts = url.translate_connect_args( 215 database="db", username="user", password="passwd" 216 ) 217 opts.update(url.query) 218 219 util.coerce_kw_type(opts, "port", int) 220 util.coerce_kw_type(opts, "compress", bool) 221 util.coerce_kw_type(opts, "autoping", bool) 222 util.coerce_kw_type(opts, "raise_on_warnings", bool) 223 224 util.coerce_kw_type(opts, "default_charset", bool) 225 if opts.pop("default_charset", False): 226 opts["charset"] = None 227 else: 228 util.coerce_kw_type(opts, "charset", str) 229 opts["use_unicode"] = opts.get("use_unicode", True) 230 util.coerce_kw_type(opts, "use_unicode", bool) 231 232 # FOUND_ROWS must be set in CLIENT_FLAGS to enable 233 # supports_sane_rowcount. 234 opts.setdefault("found_rows", True) 235 236 ssl = {} 237 for key in [ 238 "ssl_ca", 239 "ssl_key", 240 "ssl_cert", 241 "ssl_capath", 242 "ssl_cipher", 243 ]: 244 if key in opts: 245 ssl[key[4:]] = opts[key] 246 util.coerce_kw_type(ssl, key[4:], str) 247 del opts[key] 248 if ssl: 249 opts["ssl"] = ssl 250 251 return [[], opts] 252 253 def _extract_error_code(self, exception): 254 return exception.errno 255 256 def _detect_charset(self, connection): 257 """Sniff out the character set in use for connection results.""" 258 259 return connection.connection.charset 260 261 def _compat_fetchall(self, rp, charset=None): 262 """oursql isn't super-broken like MySQLdb, yaaay.""" 263 return rp.fetchall() 264 265 def _compat_fetchone(self, rp, charset=None): 266 """oursql isn't super-broken like MySQLdb, yaaay.""" 267 return rp.fetchone() 268 269 def _compat_first(self, rp, charset=None): 270 return rp.first() 271 272 273dialect = MySQLDialect_oursql 274