203 lines
7.1 KiB
Python
203 lines
7.1 KiB
Python
|
"""Support for the MySQL database via the MySQL-python adapter.
|
||
|
|
||
|
MySQL-Python is available at:
|
||
|
|
||
|
http://sourceforge.net/projects/mysql-python
|
||
|
|
||
|
At least version 1.2.1 or 1.2.2 should be used.
|
||
|
|
||
|
Connecting
|
||
|
-----------
|
||
|
|
||
|
Connect string format::
|
||
|
|
||
|
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||
|
|
||
|
Character Sets
|
||
|
--------------
|
||
|
|
||
|
Many MySQL server installations default to a ``latin1`` encoding for client
|
||
|
connections. All data sent through the connection will be converted into
|
||
|
``latin1``, even if you have ``utf8`` or another character set on your tables
|
||
|
and columns. With versions 4.1 and higher, you can change the connection
|
||
|
character set either through server configuration or by including the
|
||
|
``charset`` parameter in the URL used for ``create_engine``. The ``charset``
|
||
|
option is passed through to MySQL-Python and has the side-effect of also
|
||
|
enabling ``use_unicode`` in the driver by default. For regular encoded
|
||
|
strings, also pass ``use_unicode=0`` in the connection arguments::
|
||
|
|
||
|
# set client encoding to utf8; all strings come back as unicode
|
||
|
create_engine('mysql+mysqldb:///mydb?charset=utf8')
|
||
|
|
||
|
# set client encoding to utf8; all strings come back as utf8 str
|
||
|
create_engine('mysql+mysqldb:///mydb?charset=utf8&use_unicode=0')
|
||
|
|
||
|
Known Issues
|
||
|
-------------
|
||
|
|
||
|
MySQL-python at least as of version 1.2.2 has a serious memory leak related
|
||
|
to unicode conversion, a feature which is disabled via ``use_unicode=0``.
|
||
|
The recommended connection form with SQLAlchemy is::
|
||
|
|
||
|
engine = create_engine('mysql://scott:tiger@localhost/test?charset=utf8&use_unicode=0', pool_recycle=3600)
|
||
|
|
||
|
|
||
|
"""
|
||
|
|
||
|
import re
|
||
|
|
||
|
from sqlalchemy.dialects.mysql.base import (MySQLDialect, MySQLExecutionContext,
|
||
|
MySQLCompiler, MySQLIdentifierPreparer)
|
||
|
from sqlalchemy.engine import base as engine_base, default
|
||
|
from sqlalchemy.sql import operators as sql_operators
|
||
|
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
|
||
|
from sqlalchemy import processors
|
||
|
|
||
|
class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
|
||
|
|
||
|
@property
|
||
|
def rowcount(self):
|
||
|
if hasattr(self, '_rowcount'):
|
||
|
return self._rowcount
|
||
|
else:
|
||
|
return self.cursor.rowcount
|
||
|
|
||
|
|
||
|
class MySQLCompiler_mysqldb(MySQLCompiler):
|
||
|
def visit_mod(self, binary, **kw):
|
||
|
return self.process(binary.left) + " %% " + self.process(binary.right)
|
||
|
|
||
|
def post_process_text(self, text):
|
||
|
return text.replace('%', '%%')
|
||
|
|
||
|
|
||
|
class MySQLIdentifierPreparer_mysqldb(MySQLIdentifierPreparer):
|
||
|
|
||
|
def _escape_identifier(self, value):
|
||
|
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||
|
return value.replace("%", "%%")
|
||
|
|
||
|
class MySQLDialect_mysqldb(MySQLDialect):
|
||
|
driver = 'mysqldb'
|
||
|
supports_unicode_statements = False
|
||
|
supports_sane_rowcount = True
|
||
|
supports_sane_multi_rowcount = True
|
||
|
|
||
|
supports_native_decimal = True
|
||
|
|
||
|
default_paramstyle = 'format'
|
||
|
execution_ctx_cls = MySQLExecutionContext_mysqldb
|
||
|
statement_compiler = MySQLCompiler_mysqldb
|
||
|
preparer = MySQLIdentifierPreparer_mysqldb
|
||
|
|
||
|
colspecs = util.update_copy(
|
||
|
MySQLDialect.colspecs,
|
||
|
{
|
||
|
}
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def dbapi(cls):
|
||
|
return __import__('MySQLdb')
|
||
|
|
||
|
def do_executemany(self, cursor, statement, parameters, context=None):
|
||
|
rowcount = cursor.executemany(statement, parameters)
|
||
|
if context is not None:
|
||
|
context._rowcount = rowcount
|
||
|
|
||
|
def create_connect_args(self, url):
|
||
|
opts = url.translate_connect_args(database='db', username='user',
|
||
|
password='passwd')
|
||
|
opts.update(url.query)
|
||
|
|
||
|
util.coerce_kw_type(opts, 'compress', bool)
|
||
|
util.coerce_kw_type(opts, 'connect_timeout', int)
|
||
|
util.coerce_kw_type(opts, 'client_flag', int)
|
||
|
util.coerce_kw_type(opts, 'local_infile', int)
|
||
|
# Note: using either of the below will cause all strings to be returned
|
||
|
# as Unicode, both in raw SQL operations and with column types like
|
||
|
# String and MSString.
|
||
|
util.coerce_kw_type(opts, 'use_unicode', bool)
|
||
|
util.coerce_kw_type(opts, 'charset', str)
|
||
|
|
||
|
# Rich values 'cursorclass' and 'conv' are not supported via
|
||
|
# query string.
|
||
|
|
||
|
ssl = {}
|
||
|
for key in ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']:
|
||
|
if key in opts:
|
||
|
ssl[key[4:]] = opts[key]
|
||
|
util.coerce_kw_type(ssl, key[4:], str)
|
||
|
del opts[key]
|
||
|
if ssl:
|
||
|
opts['ssl'] = ssl
|
||
|
|
||
|
# FOUND_ROWS must be set in CLIENT_FLAGS to enable
|
||
|
# supports_sane_rowcount.
|
||
|
client_flag = opts.get('client_flag', 0)
|
||
|
if self.dbapi is not None:
|
||
|
try:
|
||
|
from MySQLdb.constants import CLIENT as CLIENT_FLAGS
|
||
|
client_flag |= CLIENT_FLAGS.FOUND_ROWS
|
||
|
except:
|
||
|
pass
|
||
|
opts['client_flag'] = client_flag
|
||
|
return [[], opts]
|
||
|
|
||
|
def _get_server_version_info(self, connection):
|
||
|
dbapi_con = connection.connection
|
||
|
version = []
|
||
|
r = re.compile('[.\-]')
|
||
|
for n in r.split(dbapi_con.get_server_info()):
|
||
|
try:
|
||
|
version.append(int(n))
|
||
|
except ValueError:
|
||
|
version.append(n)
|
||
|
return tuple(version)
|
||
|
|
||
|
def _extract_error_code(self, exception):
|
||
|
try:
|
||
|
return exception.orig.args[0]
|
||
|
except AttributeError:
|
||
|
return None
|
||
|
|
||
|
def _detect_charset(self, connection):
|
||
|
"""Sniff out the character set in use for connection results."""
|
||
|
|
||
|
# Note: MySQL-python 1.2.1c7 seems to ignore changes made
|
||
|
# on a connection via set_character_set()
|
||
|
if self.server_version_info < (4, 1, 0):
|
||
|
try:
|
||
|
return connection.connection.character_set_name()
|
||
|
except AttributeError:
|
||
|
# < 1.2.1 final MySQL-python drivers have no charset support.
|
||
|
# a query is needed.
|
||
|
pass
|
||
|
|
||
|
# 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.
|
||
|
rs = connection.execute("SHOW VARIABLES LIKE 'character_set%%'")
|
||
|
opts = dict([(row[0], row[1]) for row in self._compat_fetchall(rs)])
|
||
|
|
||
|
if 'character_set_results' in opts:
|
||
|
return opts['character_set_results']
|
||
|
try:
|
||
|
return connection.connection.character_set_name()
|
||
|
except AttributeError:
|
||
|
# Still no charset on < 1.2.1 final...
|
||
|
if 'character_set' in opts:
|
||
|
return opts['character_set']
|
||
|
else:
|
||
|
util.warn(
|
||
|
"Could not detect the connection character set with this "
|
||
|
"combination of MySQL server and MySQL-python. "
|
||
|
"MySQL-python >= 1.2.2 is recommended. Assuming latin1.")
|
||
|
return 'latin1'
|
||
|
|
||
|
|
||
|
dialect = MySQLDialect_mysqldb
|