Packages update

This commit is contained in:
Ruud
2012-02-11 16:28:06 +01:00
parent 3bbf1126c3
commit 02e01fb2d6
217 changed files with 26395 additions and 21194 deletions
+219 -94
View File
@@ -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.