Update SQLAlchemy
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# mysql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 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
|
||||
@@ -9,29 +9,11 @@
|
||||
Supported Versions and Features
|
||||
-------------------------------
|
||||
|
||||
SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0,
|
||||
with capabilities increasing with more modern servers.
|
||||
|
||||
Versions 4.1 and higher support the basic SQL functionality that SQLAlchemy
|
||||
uses in the ORM and SQL expressions. These versions pass the applicable tests
|
||||
in the suite 100%. No heroic measures are taken to work around major missing
|
||||
SQL features- if your server version does not support sub-selects, for
|
||||
SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
|
||||
However, no heroic measures are taken to work around major missing
|
||||
SQL features - if your server version does not support sub-selects, for
|
||||
example, they won't work in SQLAlchemy either.
|
||||
|
||||
Most available DBAPI drivers are supported; see below.
|
||||
|
||||
===================================== ===============
|
||||
Feature Minimum Version
|
||||
===================================== ===============
|
||||
sqlalchemy.orm 4.1.1
|
||||
Table Reflection 3.23.x
|
||||
DDL Generation 4.1.1
|
||||
utf8/Full Unicode Connections 4.1.1
|
||||
Transactions 3.23.15
|
||||
Two-Phase Transactions 5.0.3
|
||||
Nested Transactions 5.0.3
|
||||
===================================== ===============
|
||||
|
||||
See the official MySQL documentation for detailed information about features
|
||||
supported in any given server release.
|
||||
|
||||
@@ -44,18 +26,21 @@ Connection Timeouts
|
||||
-------------------
|
||||
|
||||
MySQL features an automatic connection close behavior, for connections that have
|
||||
been idle for eight hours or more. To circumvent having this issue, use the
|
||||
been idle for eight hours or more. To circumvent having this issue, use the
|
||||
``pool_recycle`` option which controls the maximum age of any connection::
|
||||
|
||||
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
|
||||
|
||||
.. _mysql_storage_engines:
|
||||
|
||||
Storage Engines
|
||||
---------------
|
||||
|
||||
Most MySQL server installations have a default table type of ``MyISAM``, a
|
||||
non-transactional table type. During a transaction, non-transactional storage
|
||||
engines do not participate and continue to store table changes in autocommit
|
||||
mode. For fully atomic transactions, all participating tables must use a
|
||||
mode. For fully atomic transactions as well as support for foreign key
|
||||
constraints, all participating tables must use a
|
||||
transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
|
||||
|
||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
||||
@@ -68,6 +53,10 @@ creation option can be specified in this syntax::
|
||||
mysql_charset='utf8'
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
`The InnoDB Storage Engine <http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html>`_ - on the MySQL website.
|
||||
|
||||
Case Sensitivity and Table Reflection
|
||||
-------------------------------------
|
||||
|
||||
@@ -87,19 +76,19 @@ to be used.
|
||||
Transaction Isolation Level
|
||||
---------------------------
|
||||
|
||||
:func:`.create_engine` accepts an ``isolation_level``
|
||||
parameter which results in the command ``SET SESSION
|
||||
TRANSACTION ISOLATION LEVEL <level>`` being invoked for
|
||||
:func:`.create_engine` accepts an ``isolation_level``
|
||||
parameter which results in the command ``SET SESSION
|
||||
TRANSACTION ISOLATION LEVEL <level>`` being invoked for
|
||||
every new connection. Valid values for this parameter are
|
||||
``READ COMMITTED``, ``READ UNCOMMITTED``,
|
||||
``READ COMMITTED``, ``READ UNCOMMITTED``,
|
||||
``REPEATABLE READ``, and ``SERIALIZABLE``::
|
||||
|
||||
engine = create_engine(
|
||||
"mysql://scott:tiger@localhost/test",
|
||||
"mysql://scott:tiger@localhost/test",
|
||||
isolation_level="READ UNCOMMITTED"
|
||||
)
|
||||
|
||||
(new in 0.7.6)
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
Keys
|
||||
----
|
||||
@@ -185,6 +174,24 @@ available.
|
||||
|
||||
update(..., mysql_limit=10)
|
||||
|
||||
rowcount Support
|
||||
----------------
|
||||
|
||||
SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
|
||||
usual definition of "number of rows matched by an UPDATE or DELETE" statement.
|
||||
This is in contradiction to the default setting on most MySQL DBAPI drivers,
|
||||
which is "number of rows actually modified/deleted". For this reason, the
|
||||
SQLAlchemy MySQL dialects always set the ``constants.CLIENT.FOUND_ROWS`` flag,
|
||||
or whatever is equivalent for the DBAPI in use, on connect, unless the flag value
|
||||
is overridden using DBAPI-specific options
|
||||
(such as ``client_flag`` for the MySQL-Python driver, ``found_rows`` for the
|
||||
OurSQL driver).
|
||||
|
||||
See also:
|
||||
|
||||
:attr:`.ResultProxy.rowcount`
|
||||
|
||||
|
||||
CAST Support
|
||||
------------
|
||||
|
||||
@@ -242,7 +249,7 @@ Index Types
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Some MySQL storage engines permit you to specify an index type when creating
|
||||
an index or primary key constraint. SQLAlchemy provides this feature via the
|
||||
an index or primary key constraint. SQLAlchemy provides this feature via the
|
||||
``mysql_using`` parameter on :class:`.Index`::
|
||||
|
||||
Index('my_index', my_table.c.data, mysql_using='hash')
|
||||
@@ -252,7 +259,7 @@ As well as the ``mysql_using`` parameter on :class:`.PrimaryKeyConstraint`::
|
||||
PrimaryKeyConstraint("data", mysql_using='hash')
|
||||
|
||||
The value passed to the keyword argument will be simply passed through to the
|
||||
underlying CREATE INDEX or PRIMARY KEY clause, so it *must* be a valid index
|
||||
underlying CREATE INDEX or PRIMARY KEY clause, so it *must* be a valid index
|
||||
type for your MySQL storage engine.
|
||||
|
||||
More information can be found at:
|
||||
@@ -344,9 +351,9 @@ class _FloatType(_NumericType, sqltypes.Float):
|
||||
(precision is None and scale is not None) or
|
||||
(precision is not None and scale is None)
|
||||
):
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
|
||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
||||
self.scale = scale
|
||||
@@ -1273,11 +1280,11 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
# No cast until 4, no decimals until 5.
|
||||
if not self.dialect._supports_cast:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause.self_group())
|
||||
|
||||
type_ = self.process(cast.typeclause)
|
||||
if type_ is None:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause.self_group())
|
||||
|
||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||
|
||||
@@ -1289,13 +1296,13 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
"""Add special MySQL keywords in place of DISTINCT.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
.. 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() + " "
|
||||
@@ -1343,16 +1350,16 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
if limit is None:
|
||||
# hardwire the upper limit. Currently
|
||||
# needed by OurSQL with Python 3
|
||||
# (https://bugs.launchpad.net/oursql/+bug/686232),
|
||||
# (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)),
|
||||
self.process(sql.literal(offset)),
|
||||
"18446744073709551615")
|
||||
else:
|
||||
return ' \n LIMIT %s, %s' % (
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(limit)))
|
||||
else:
|
||||
# No offset provided, so just use the limit
|
||||
@@ -1366,10 +1373,10 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
return None
|
||||
|
||||
def update_tables_clause(self, update_stmt, from_table, extra_froms, **kw):
|
||||
return ', '.join(t._compiler_dispatch(self, asfrom=True, **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,
|
||||
def update_from_clause(self, update_stmt, from_table,
|
||||
extra_froms, from_hints, **kw):
|
||||
return None
|
||||
|
||||
@@ -1395,8 +1402,12 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
auto_inc_column is not list(table.primary_key)[0]:
|
||||
if constraint_string:
|
||||
constraint_string += ", \n\t"
|
||||
constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \
|
||||
self.preparer.format_column(auto_inc_column))
|
||||
constraint_string += "KEY %s (%s)" % (
|
||||
self.preparer.quote(
|
||||
"idx_autoinc_%s" % auto_inc_column.name, None
|
||||
),
|
||||
self.preparer.format_column(auto_inc_column)
|
||||
)
|
||||
|
||||
return constraint_string
|
||||
|
||||
@@ -1431,7 +1442,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
opts = dict(
|
||||
(
|
||||
k[len(self.dialect.name)+1:].upper(),
|
||||
k[len(self.dialect.name)+1:].upper(),
|
||||
v
|
||||
)
|
||||
for k, v in table.kwargs.items()
|
||||
@@ -1447,7 +1458,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
|
||||
|
||||
if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
|
||||
'DEFAULT_CHARSET',
|
||||
'DEFAULT_COLLATE'):
|
||||
opt = opt.replace('_', ' ')
|
||||
@@ -1467,7 +1478,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
table = preparer.format_table(index.table)
|
||||
columns = [preparer.quote(c.name, c.quote) for c in index.columns]
|
||||
name = preparer.quote(
|
||||
self._index_identifier(index.name),
|
||||
self._index_identifier(index.name),
|
||||
index.quote)
|
||||
|
||||
text = "CREATE "
|
||||
@@ -1576,24 +1587,24 @@ 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)" %
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s, %(scale)s)" %
|
||||
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)" %
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s, %(scale)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s, %(scale)s)" %
|
||||
{'precision': type_.precision, 'scale' : type_.scale})
|
||||
|
||||
def visit_DOUBLE(self, type_):
|
||||
@@ -1616,7 +1627,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if self._mysql_type(type_) and \
|
||||
type_.scale is not None and \
|
||||
type_.precision is not None:
|
||||
return self._extend_numeric(type_,
|
||||
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,))
|
||||
@@ -1625,24 +1636,24 @@ 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)" %
|
||||
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)" %
|
||||
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)" %
|
||||
return self._extend_numeric(type_,
|
||||
"MEDIUMINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "MEDIUMINT")
|
||||
@@ -1655,8 +1666,8 @@ 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)" %
|
||||
return self._extend_numeric(type_,
|
||||
"SMALLINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width}
|
||||
)
|
||||
else:
|
||||
@@ -1706,7 +1717,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
|
||||
else:
|
||||
raise exc.CompileError(
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
@@ -1722,7 +1733,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_string(type_, {'national':True}, "VARCHAR(%(length)s)" % {'length': type_.length})
|
||||
else:
|
||||
raise exc.CompileError(
|
||||
"NVARCHAR requires a length on dialect %s" %
|
||||
"NVARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_NCHAR(self, type_):
|
||||
@@ -1783,8 +1794,8 @@ class MySQLIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
quote = '"'
|
||||
|
||||
super(MySQLIdentifierPreparer, self).__init__(
|
||||
dialect,
|
||||
initial_quote=quote,
|
||||
dialect,
|
||||
initial_quote=quote,
|
||||
escape_quote=quote)
|
||||
|
||||
def _quote_free_identifiers(self, *ids):
|
||||
@@ -1817,7 +1828,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
preparer = MySQLIdentifierPreparer
|
||||
|
||||
# default SQL compilation settings -
|
||||
# these are modified upon initialize(),
|
||||
# these are modified upon initialize(),
|
||||
# i.e. first connect
|
||||
_backslash_escapes = True
|
||||
_server_ansiquotes = False
|
||||
@@ -1834,7 +1845,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
else:
|
||||
return None
|
||||
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ'])
|
||||
|
||||
def set_isolation_level(self, connection, level):
|
||||
@@ -1842,7 +1853,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
if level not in self._isolation_lookup:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
@@ -1914,7 +1925,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
return self._extract_error_code(e) in \
|
||||
(2006, 2013, 2014, 2045, 2055)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
# if underlying connection is closed,
|
||||
# if underlying connection is closed,
|
||||
# this is the error you get
|
||||
return "(0, '')" in str(e)
|
||||
else:
|
||||
@@ -2017,7 +2028,6 @@ class MySQLDialect(default.DefaultDialect):
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
charset = self._connection_charset
|
||||
if self.server_version_info < (5, 0, 2):
|
||||
raise NotImplementedError
|
||||
if schema is None:
|
||||
@@ -2028,7 +2038,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
rp = connection.execute("SHOW FULL TABLES FROM %s" %
|
||||
self.identifier_preparer.quote_identifier(schema))
|
||||
return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
|
||||
if row[1] == 'VIEW']
|
||||
if row[1] in ('VIEW', 'SYSTEM VIEW')]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||
@@ -2129,9 +2139,9 @@ class MySQLDialect(default.DefaultDialect):
|
||||
|
||||
def _parsed_state_or_create(self, connection, table_name, schema=None, **kw):
|
||||
return self._setup_parser(
|
||||
connection,
|
||||
table_name,
|
||||
schema,
|
||||
connection,
|
||||
table_name,
|
||||
schema,
|
||||
info_cache=kw.get('info_cache', None)
|
||||
)
|
||||
|
||||
@@ -2139,7 +2149,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
def _tabledef_parser(self):
|
||||
"""return the MySQLTableDefinitionParser, generate if needed.
|
||||
|
||||
The deferred creation ensures that the dialect has
|
||||
The deferred creation ensures that the dialect has
|
||||
retrieved server version information first.
|
||||
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user