Packages update
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# mysql/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
|
||||
pyodbc, zxjdbc, mysqlconnector
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -68,6 +68,22 @@ creation option can be specified in this syntax::
|
||||
mysql_charset='utf8'
|
||||
)
|
||||
|
||||
Case Sensitivity and Table Reflection
|
||||
-------------------------------------
|
||||
|
||||
MySQL has inconsistent support for case-sensitive identifier
|
||||
names, basing support on specific details of the underlying
|
||||
operating system. However, it has been observed that no matter
|
||||
what case sensitivity behavior is present, the names of tables in
|
||||
foreign key declarations are *always* received from the database
|
||||
as all-lower case, making it impossible to accurately reflect a
|
||||
schema where inter-related tables use mixed-case identifier names.
|
||||
|
||||
Therefore it is strongly advised that table names be declared as
|
||||
all lower case both within SQLAlchemy as well as on the MySQL
|
||||
database itself, especially if database reflection features are
|
||||
to be used.
|
||||
|
||||
Keys
|
||||
----
|
||||
|
||||
@@ -81,7 +97,7 @@ foreign keys. For these tables, you may supply a
|
||||
autoload=True
|
||||
)
|
||||
|
||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT``` on
|
||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
|
||||
an integer primary key column::
|
||||
|
||||
>>> t = Table('mytable', metadata,
|
||||
@@ -152,14 +168,61 @@ available.
|
||||
|
||||
update(..., mysql_limit=10)
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
CAST Support
|
||||
------------
|
||||
|
||||
If you have problems that seem server related, first check that you are
|
||||
using the most recent stable MySQL-Python package available. The Database
|
||||
Notes page on the wiki at http://www.sqlalchemy.org is a good resource for
|
||||
timely information affecting MySQL in SQLAlchemy.
|
||||
MySQL documents the CAST operator as available in version 4.0.2. When using the
|
||||
SQLAlchemy :func:`.cast` function, SQLAlchemy
|
||||
will not render the CAST token on MySQL before this version, based on server version
|
||||
detection, instead rendering the internal expression directly.
|
||||
|
||||
CAST may still not be desirable on an early MySQL version post-4.0.2, as it didn't
|
||||
add all datatype support until 4.1.1. If your application falls into this
|
||||
narrow area, the behavior of CAST can be controlled using the :ref:`sqlalchemy.ext.compiler_toplevel`
|
||||
system, as per the recipe below::
|
||||
|
||||
from sqlalchemy.sql.expression import _Cast
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
@compiles(_Cast, 'mysql')
|
||||
def _check_mysql_version(element, compiler, **kw):
|
||||
if compiler.dialect.server_version_info < (4, 1, 0):
|
||||
return compiler.process(element.clause, **kw)
|
||||
else:
|
||||
return compiler.visit_cast(element, **kw)
|
||||
|
||||
The above function, which only needs to be declared once
|
||||
within an application, overrides the compilation of the
|
||||
:func:`.cast` construct to check for version 4.1.0 before
|
||||
fully rendering CAST; else the internal element of the
|
||||
construct is rendered directly.
|
||||
|
||||
|
||||
.. _mysql_indexes:
|
||||
|
||||
MySQL Specific Index Options
|
||||
----------------------------
|
||||
|
||||
MySQL-specific extensions to the :class:`.Index` construct are available.
|
||||
|
||||
Index Length
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
MySQL provides an option to create index entries with a certain length, where
|
||||
"length" refers to the number of characters or bytes in each value which will
|
||||
become part of the index. SQLAlchemy provides this feature via the
|
||||
``mysql_length`` parameter::
|
||||
|
||||
Index('my_index', my_table.c.data, mysql_length=10)
|
||||
|
||||
Prefix lengths are given in characters for nonbinary string types and in bytes
|
||||
for binary string types. The value passed to the keyword argument will be
|
||||
simply passed through to the underlying CREATE INDEX command, so it *must* be
|
||||
an integer. MySQL only allows a length for an index if it is for a CHAR,
|
||||
VARCHAR, TEXT, BINARY, VARBINARY and BLOB.
|
||||
|
||||
More information can be found at:
|
||||
http://dev.mysql.com/doc/refman/5.0/en/create-index.html
|
||||
"""
|
||||
|
||||
import datetime, inspect, re, sys
|
||||
@@ -174,7 +237,7 @@ from array import array as _array
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy import types as sqltypes
|
||||
|
||||
from sqlalchemy.util import topological
|
||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||
BLOB, BINARY, VARBINARY
|
||||
|
||||
@@ -231,9 +294,9 @@ SET_RE = re.compile(
|
||||
class _NumericType(object):
|
||||
"""Base for MySQL numeric types."""
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.unsigned = kw.pop('unsigned', False)
|
||||
self.zerofill = kw.pop('zerofill', False)
|
||||
def __init__(self, unsigned=False, zerofill=False, **kw):
|
||||
self.unsigned = unsigned
|
||||
self.zerofill = zerofill
|
||||
super(_NumericType, self).__init__(**kw)
|
||||
|
||||
class _FloatType(_NumericType, sqltypes.Float):
|
||||
@@ -362,7 +425,7 @@ class DOUBLE(_FloatType):
|
||||
super(DOUBLE, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
class REAL(_FloatType):
|
||||
class REAL(_FloatType, sqltypes.REAL):
|
||||
"""MySQL REAL type."""
|
||||
|
||||
__visit_name__ = 'REAL'
|
||||
@@ -747,7 +810,7 @@ class CHAR(_StringType, sqltypes.CHAR):
|
||||
|
||||
__visit_name__ = 'CHAR'
|
||||
|
||||
def __init__(self, length, **kwargs):
|
||||
def __init__(self, length=None, **kwargs):
|
||||
"""Construct a CHAR.
|
||||
|
||||
:param length: Maximum data length, in characters.
|
||||
@@ -942,6 +1005,10 @@ class ENUM(sqltypes.Enum, _StringType):
|
||||
return value
|
||||
return process
|
||||
|
||||
def adapt(self, impltype, **kw):
|
||||
kw['strict'] = self.strict
|
||||
return sqltypes.Enum.adapt(self, impltype, **kw)
|
||||
|
||||
class SET(_StringType):
|
||||
"""MySQL SET type."""
|
||||
|
||||
@@ -988,8 +1055,8 @@ class SET(_StringType):
|
||||
strip_values.append(a)
|
||||
|
||||
self.values = strip_values
|
||||
length = max([len(v) for v in strip_values] + [0])
|
||||
super(SET, self).__init__(length=length, **kw)
|
||||
kw.setdefault('length', max([len(v) for v in strip_values] + [0]))
|
||||
super(SET, self).__init__(**kw)
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
@@ -1113,6 +1180,9 @@ class MySQLExecutionContext(default.DefaultExecutionContext):
|
||||
|
||||
class MySQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
render_table_with_column_in_update_from = True
|
||||
"""Overridden from base SQLCompiler value"""
|
||||
|
||||
extract_map = compiler.SQLCompiler.extract_map.copy()
|
||||
extract_map.update ({
|
||||
'milliseconds': 'millisecond',
|
||||
@@ -1157,7 +1227,7 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
return 'CHAR'
|
||||
elif isinstance(type_, sqltypes._Binary):
|
||||
return 'BINARY'
|
||||
elif isinstance(type_, NUMERIC):
|
||||
elif isinstance(type_, sqltypes.NUMERIC):
|
||||
return self.dialect.type_compiler.process(type_).replace('NUMERIC', 'DECIMAL')
|
||||
else:
|
||||
return None
|
||||
@@ -1180,6 +1250,15 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
return value
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
"""Add special MySQL keywords in place of DISTINCT.
|
||||
|
||||
.. note::
|
||||
|
||||
this usage is deprecated. :meth:`.Select.prefix_with`
|
||||
should be used for special keywords at the start
|
||||
of a SELECT.
|
||||
|
||||
"""
|
||||
if isinstance(select._distinct, basestring):
|
||||
return select._distinct.upper() + " "
|
||||
elif select._distinct:
|
||||
@@ -1222,32 +1301,39 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
elif offset is not None:
|
||||
# As suggested by the MySQL docs, need to apply an
|
||||
# artificial limit if one wasn't provided
|
||||
# http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
if limit is None:
|
||||
limit = 18446744073709551615
|
||||
return ' \n LIMIT %s, %s' % (offset, limit)
|
||||
# hardwire the upper limit. Currently
|
||||
# needed by OurSQL with Python 3
|
||||
# (https://bugs.launchpad.net/oursql/+bug/686232),
|
||||
# but also is consistent with the usage of the upper
|
||||
# bound as part of MySQL's "syntax" for OFFSET with
|
||||
# no LIMIT
|
||||
return ' \n LIMIT %s, %s' % (
|
||||
self.process(sql.literal(offset)),
|
||||
"18446744073709551615")
|
||||
else:
|
||||
return ' \n LIMIT %s, %s' % (
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(limit)))
|
||||
else:
|
||||
# No offset provided, so just use the limit
|
||||
return ' \n LIMIT %s' % (limit,)
|
||||
return ' \n LIMIT %s' % (self.process(sql.literal(limit)),)
|
||||
|
||||
def visit_update(self, update_stmt):
|
||||
self.stack.append({'from': set([update_stmt.table])})
|
||||
|
||||
self.isupdate = True
|
||||
colparams = self._get_colparams(update_stmt)
|
||||
|
||||
text = "UPDATE " + self.preparer.format_table(update_stmt.table) + \
|
||||
" SET " + ', '.join(["%s=%s" % (self.preparer.format_column(c[0]), c[1]) for c in colparams])
|
||||
|
||||
if update_stmt._whereclause is not None:
|
||||
text += " WHERE " + self.process(update_stmt._whereclause)
|
||||
|
||||
limit = update_stmt.kwargs.get('mysql_limit', None)
|
||||
def update_limit_clause(self, update_stmt):
|
||||
limit = update_stmt.kwargs.get('%s_limit' % self.dialect.name, None)
|
||||
if limit:
|
||||
text += " LIMIT %s" % limit
|
||||
return "LIMIT %s" % limit
|
||||
else:
|
||||
return None
|
||||
|
||||
self.stack.pop(-1)
|
||||
def update_tables_clause(self, update_stmt, from_table, extra_froms, **kw):
|
||||
return ', '.join(t._compiler_dispatch(self, asfrom=True, **kw)
|
||||
for t in [from_table] + list(extra_froms))
|
||||
|
||||
def update_from_clause(self, update_stmt, from_table, extra_froms, **kw):
|
||||
return None
|
||||
|
||||
return text
|
||||
|
||||
# ug. "InnoDB needs indexes on foreign keys and referenced keys [...].
|
||||
# Starting with MySQL 4.1.2, these indexes are created automatically.
|
||||
@@ -1259,8 +1345,9 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
"""Get table constraints."""
|
||||
constraint_string = super(MySQLDDLCompiler, self).create_table_constraints(table)
|
||||
|
||||
is_innodb = table.kwargs.has_key('mysql_engine') and \
|
||||
table.kwargs['mysql_engine'].lower() == 'innodb'
|
||||
engine_key = '%s_engine' % self.dialect.name
|
||||
is_innodb = table.kwargs.has_key(engine_key) and \
|
||||
table.kwargs[engine_key].lower() == 'innodb'
|
||||
|
||||
auto_inc_column = table._autoincrement_column
|
||||
|
||||
@@ -1293,16 +1380,8 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
elif column.nullable and is_timestamp and default is None:
|
||||
colspec.append('NULL')
|
||||
|
||||
if column.primary_key and column.autoincrement:
|
||||
try:
|
||||
first = [c for c in column.table.primary_key.columns
|
||||
if (c.autoincrement and
|
||||
isinstance(c.type, sqltypes.Integer) and
|
||||
not c.foreign_keys)].pop(0)
|
||||
if column is first:
|
||||
colspec.append('AUTO_INCREMENT')
|
||||
except IndexError:
|
||||
pass
|
||||
if column is column.table._autoincrement_column and column.server_default is None:
|
||||
colspec.append('AUTO_INCREMENT')
|
||||
|
||||
return ' '.join(colspec)
|
||||
|
||||
@@ -1310,27 +1389,62 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
"""Build table-level CREATE options like ENGINE and COLLATE."""
|
||||
|
||||
table_opts = []
|
||||
for k in table.kwargs:
|
||||
if k.startswith('mysql_'):
|
||||
opt = k[6:].upper()
|
||||
|
||||
arg = table.kwargs[k]
|
||||
if opt in _options_of_type_string:
|
||||
arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
|
||||
opts = dict(
|
||||
(
|
||||
k[len(self.dialect.name)+1:].upper(),
|
||||
v
|
||||
)
|
||||
for k, v in table.kwargs.items()
|
||||
if k.startswith('%s_' % self.dialect.name)
|
||||
)
|
||||
|
||||
if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET', 'DEFAULT_CHARSET',
|
||||
'DEFAULT_COLLATE'):
|
||||
opt = opt.replace('_', ' ')
|
||||
for opt in topological.sort([
|
||||
('DEFAULT_CHARSET', 'COLLATE'),
|
||||
('DEFAULT_CHARACTER_SET', 'COLLATE')
|
||||
], opts):
|
||||
arg = opts[opt]
|
||||
if opt in _options_of_type_string:
|
||||
arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
|
||||
|
||||
joiner = '='
|
||||
if opt in ('TABLESPACE', 'DEFAULT CHARACTER SET',
|
||||
'CHARACTER SET', 'COLLATE'):
|
||||
joiner = ' '
|
||||
if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
|
||||
'DEFAULT_CHARSET',
|
||||
'DEFAULT_COLLATE'):
|
||||
opt = opt.replace('_', ' ')
|
||||
|
||||
table_opts.append(joiner.join((opt, arg)))
|
||||
joiner = '='
|
||||
if opt in ('TABLESPACE', 'DEFAULT CHARACTER SET',
|
||||
'CHARACTER SET', 'COLLATE'):
|
||||
joiner = ' '
|
||||
|
||||
table_opts.append(joiner.join((opt, arg)))
|
||||
return ' '.join(table_opts)
|
||||
|
||||
def visit_create_index(self, create):
|
||||
index = create.element
|
||||
preparer = self.preparer
|
||||
text = "CREATE "
|
||||
if index.unique:
|
||||
text += "UNIQUE "
|
||||
text += "INDEX %s ON %s " \
|
||||
% (preparer.quote(self._index_identifier(index.name),
|
||||
index.quote),preparer.format_table(index.table))
|
||||
if 'mysql_length' in index.kwargs:
|
||||
length = index.kwargs['mysql_length']
|
||||
else:
|
||||
length = None
|
||||
if length is not None:
|
||||
text+= "(%s(%d))" \
|
||||
% (', '.join(preparer.quote(c.name, c.quote)
|
||||
for c in index.columns), length)
|
||||
else:
|
||||
text+= "(%s)" \
|
||||
% (', '.join(preparer.quote(c.name, c.quote)
|
||||
for c in index.columns))
|
||||
return text
|
||||
|
||||
|
||||
def visit_drop_index(self, drop):
|
||||
index = drop.element
|
||||
|
||||
@@ -1408,17 +1522,25 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if type_.precision is None:
|
||||
return self._extend_numeric(type_, "NUMERIC")
|
||||
elif type_.scale is None:
|
||||
return self._extend_numeric(type_, "NUMERIC(%(precision)s)" % {'precision': type_.precision})
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_, "NUMERIC(%(precision)s, %(scale)s)" % {'precision': type_.precision, 'scale' : type_.scale})
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s, %(scale)s)" %
|
||||
{'precision': type_.precision, 'scale' : type_.scale})
|
||||
|
||||
def visit_DECIMAL(self, type_):
|
||||
if type_.precision is None:
|
||||
return self._extend_numeric(type_, "DECIMAL")
|
||||
elif type_.scale is None:
|
||||
return self._extend_numeric(type_, "DECIMAL(%(precision)s)" % {'precision': type_.precision})
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_, "DECIMAL(%(precision)s, %(scale)s)" % {'precision': type_.precision, 'scale' : type_.scale})
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s, %(scale)s)" %
|
||||
{'precision': type_.precision, 'scale' : type_.scale})
|
||||
|
||||
def visit_DOUBLE(self, type_):
|
||||
if type_.precision is not None and type_.scale is not None:
|
||||
@@ -1437,8 +1559,11 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_numeric(type_, 'REAL')
|
||||
|
||||
def visit_FLOAT(self, type_):
|
||||
if self._mysql_type(type_) and type_.scale is not None and type_.precision is not None:
|
||||
return self._extend_numeric(type_, "FLOAT(%s, %s)" % (type_.precision, type_.scale))
|
||||
if self._mysql_type(type_) and \
|
||||
type_.scale is not None and \
|
||||
type_.precision is not None:
|
||||
return self._extend_numeric(type_,
|
||||
"FLOAT(%s, %s)" % (type_.precision, type_.scale))
|
||||
elif type_.precision is not None:
|
||||
return self._extend_numeric(type_, "FLOAT(%s)" % (type_.precision,))
|
||||
else:
|
||||
@@ -1446,19 +1571,25 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_INTEGER(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_, "INTEGER(%(display_width)s)" % {'display_width': type_.display_width})
|
||||
return self._extend_numeric(type_,
|
||||
"INTEGER(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "INTEGER")
|
||||
|
||||
def visit_BIGINT(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_, "BIGINT(%(display_width)s)" % {'display_width': type_.display_width})
|
||||
return self._extend_numeric(type_,
|
||||
"BIGINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "BIGINT")
|
||||
|
||||
def visit_MEDIUMINT(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_, "MEDIUMINT(%(display_width)s)" % {'display_width': type_.display_width})
|
||||
return self._extend_numeric(type_,
|
||||
"MEDIUMINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "MEDIUMINT")
|
||||
|
||||
@@ -1470,7 +1601,10 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_SMALLINT(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_, "SMALLINT(%(display_width)s)" % {'display_width': type_.display_width})
|
||||
return self._extend_numeric(type_,
|
||||
"SMALLINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width}
|
||||
)
|
||||
else:
|
||||
return self._extend_numeric(type_, "SMALLINT")
|
||||
|
||||
@@ -1517,7 +1651,9 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if type_.length:
|
||||
return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
|
||||
else:
|
||||
raise exc.InvalidRequestError("VARCHAR requires a length when rendered on MySQL")
|
||||
raise exc.CompileError(
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
if type_.length:
|
||||
@@ -1531,7 +1667,9 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if type_.length:
|
||||
return self._extend_string(type_, {'national':True}, "VARCHAR(%(length)s)" % {'length': type_.length})
|
||||
else:
|
||||
raise exc.InvalidRequestError("NVARCHAR requires a length when rendered on MySQL")
|
||||
raise exc.CompileError(
|
||||
"NVARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_NCHAR(self, type_):
|
||||
# We'll actually generate the equiv. "NATIONAL CHAR" instead of "NCHAR".
|
||||
@@ -1685,7 +1823,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
resultset = connection.execute("XA RECOVER")
|
||||
return [row['data'][0:row['gtrid_length']] for row in resultset]
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
return self._extract_error_code(e) in \
|
||||
(2006, 2013, 2014, 2045, 2055)
|
||||
@@ -1741,7 +1879,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
have = rs.rowcount > 0
|
||||
rs.close()
|
||||
return have
|
||||
except exc.SQLError, e:
|
||||
except exc.DBAPIError, e:
|
||||
if self._extract_error_code(e.orig) == 1146:
|
||||
return False
|
||||
raise
|
||||
@@ -1941,17 +2079,6 @@ class MySQLDialect(default.DefaultDialect):
|
||||
sql = parser._describe_to_create(table_name, columns)
|
||||
return parser.parse(sql, charset)
|
||||
|
||||
def _adjust_casing(self, table, charset=None):
|
||||
"""Adjust Table name to the server case sensitivity, if needed."""
|
||||
|
||||
casing = self._server_casing
|
||||
|
||||
# For winxx database hosts. TODO: is this really needed?
|
||||
if casing == 1 and table.name != table.name.lower():
|
||||
table.name = table.name.lower()
|
||||
lc_alias = sa_schema._get_table_key(table.name, table.schema)
|
||||
table.metadata.tables[lc_alias] = table
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -2029,7 +2156,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
rp = None
|
||||
try:
|
||||
rp = connection.execute(st)
|
||||
except exc.SQLError, e:
|
||||
except exc.DBAPIError, e:
|
||||
if self._extract_error_code(e.orig) == 1146:
|
||||
raise exc.NoSuchTableError(full_name)
|
||||
else:
|
||||
@@ -2053,7 +2180,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
try:
|
||||
try:
|
||||
rp = connection.execute(st)
|
||||
except exc.SQLError, e:
|
||||
except exc.DBAPIError, e:
|
||||
if self._extract_error_code(e.orig) == 1146:
|
||||
raise exc.NoSuchTableError(full_name)
|
||||
else:
|
||||
@@ -2185,7 +2312,7 @@ class MySQLTableDefinitionParser(object):
|
||||
options.pop(nope, None)
|
||||
|
||||
for opt, val in options.items():
|
||||
state.table_options['mysql_%s' % opt] = val
|
||||
state.table_options['%s_%s' % (self.dialect.name, opt)] = val
|
||||
|
||||
def _parse_column(self, line, state):
|
||||
"""Extract column details.
|
||||
@@ -2432,9 +2559,7 @@ class MySQLTableDefinitionParser(object):
|
||||
# PARTITION
|
||||
#
|
||||
# punt!
|
||||
self._re_partition = _re_compile(
|
||||
r' '
|
||||
r'(?:SUB)?PARTITION')
|
||||
self._re_partition = _re_compile(r'(?:.*)(?:SUB)?PARTITION(?:.*)')
|
||||
|
||||
# Table-level options (COLLATE, ENGINE, etc.)
|
||||
# Do the string options first, since they have quoted strings we need to get rid of.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -118,7 +118,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
errnos = (2006, 2013, 2014, 2045, 2055, 2048)
|
||||
exceptions = (self.dbapi.OperationalError,self.dbapi.InterfaceError)
|
||||
if isinstance(e, exceptions):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -19,187 +19,64 @@ Connect string format::
|
||||
|
||||
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Character Sets
|
||||
--------------
|
||||
Unicode
|
||||
-------
|
||||
|
||||
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
|
||||
MySQLdb will accommodate Python ``unicode`` objects if the
|
||||
``use_unicode=1`` parameter, or the ``charset`` parameter,
|
||||
is passed as a connection argument.
|
||||
|
||||
Without this setting, many MySQL server installations default to
|
||||
a ``latin1`` encoding for client connections, which has the effect
|
||||
of all data being converted into ``latin1``, even if you have ``utf8``
|
||||
or another character set configured 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::
|
||||
``charset`` parameter. The ``charset``
|
||||
parameter as received by MySQL-Python also has the side-effect of
|
||||
enabling ``use_unicode=1``::
|
||||
|
||||
# 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 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')
|
||||
Manually configuring ``use_unicode=0`` will cause MySQL-python to
|
||||
return encoded strings::
|
||||
|
||||
# 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
|
||||
MySQL-python 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)
|
||||
|
||||
It is strongly advised to use the latest version of MySQL-Python.
|
||||
|
||||
"""
|
||||
|
||||
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
|
||||
from sqlalchemy.connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
|
||||
class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
|
||||
|
||||
@property
|
||||
def rowcount(self):
|
||||
if hasattr(self, '_rowcount'):
|
||||
return self._rowcount
|
||||
else:
|
||||
return self.cursor.rowcount
|
||||
class MySQLExecutionContext_mysqldb(MySQLDBExecutionContext, MySQLExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
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 MySQLCompiler_mysqldb(MySQLDBCompiler, MySQLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLIdentifierPreparer_mysqldb(MySQLIdentifierPreparer):
|
||||
class MySQLIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer, MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
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'
|
||||
class MySQLDialect_mysqldb(MySQLDBConnector, MySQLDialect):
|
||||
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):
|
||||
return exception.args[0]
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/oursql.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -17,8 +17,8 @@ Connect string format::
|
||||
|
||||
mysql+oursql://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Character Sets
|
||||
--------------
|
||||
Unicode
|
||||
-------
|
||||
|
||||
oursql defaults to using ``utf8`` as the connection charset, but other
|
||||
encodings may be used instead. Like the MySQL-Python driver, unicode support
|
||||
@@ -64,8 +64,6 @@ class MySQLExecutionContext_oursql(MySQLExecutionContext):
|
||||
|
||||
class MySQLDialect_oursql(MySQLDialect):
|
||||
driver = 'oursql'
|
||||
# Py3K
|
||||
# description_encoding = None
|
||||
# Py2K
|
||||
supports_unicode_binds = True
|
||||
supports_unicode_statements = True
|
||||
@@ -107,6 +105,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# Py3K
|
||||
# charset = self._connection_charset
|
||||
# arg = connection.connection._escape_string(xid.encode(charset)).decode(charset)
|
||||
arg = "'%s'" % arg
|
||||
connection.execution_options(_oursql_plain_query=True).execute(query % arg)
|
||||
|
||||
# Because mysql is bad, these methods have to be
|
||||
@@ -115,23 +114,23 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# the parameterized query API, or refuse to be parameterized
|
||||
# in the first place.
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
self._xa_query(connection, 'XA BEGIN "%s"', xid)
|
||||
self._xa_query(connection, 'XA BEGIN %s', xid)
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
self._xa_query(connection, 'XA END "%s"', xid)
|
||||
self._xa_query(connection, 'XA PREPARE "%s"', xid)
|
||||
self._xa_query(connection, 'XA END %s', xid)
|
||||
self._xa_query(connection, 'XA PREPARE %s', xid)
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self._xa_query(connection, 'XA END "%s"', xid)
|
||||
self._xa_query(connection, 'XA ROLLBACK "%s"', xid)
|
||||
self._xa_query(connection, 'XA END %s', xid)
|
||||
self._xa_query(connection, 'XA ROLLBACK %s', xid)
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self.do_prepare_twophase(connection, xid)
|
||||
self._xa_query(connection, 'XA COMMIT "%s"', xid)
|
||||
self._xa_query(connection, 'XA COMMIT %s', xid)
|
||||
|
||||
# Q: why didn't we need all these "plain_query" overrides earlier ?
|
||||
# am i on a newer/older version of OurSQL ?
|
||||
@@ -195,7 +194,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table, charset, full_name)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.ProgrammingError):
|
||||
return e.errno is None and 'cursor' not in e.args[1] and e.args[1].endswith('closed')
|
||||
else:
|
||||
@@ -222,6 +221,16 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# supports_sane_rowcount.
|
||||
opts.setdefault('found_rows', True)
|
||||
|
||||
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
|
||||
|
||||
return [[], opts]
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# mysql/pymysql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via the pymysql adapter.
|
||||
|
||||
pymysql is available at:
|
||||
|
||||
http://code.google.com/p/pymysql/
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string::
|
||||
|
||||
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
|
||||
MySQL-Python Compatibility
|
||||
--------------------------
|
||||
|
||||
The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
|
||||
and targets 100% compatibility. Most behavioral notes for MySQL-python apply to
|
||||
the pymysql driver as well.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
|
||||
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
||||
driver = 'pymysql'
|
||||
|
||||
description_encoding = None
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pymysql')
|
||||
|
||||
dialect = MySQLDialect_pymysql
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/zxjdbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Reference in New Issue
Block a user