diff options
author | Jason Kirtland <jek@discorporate.us> | 2007-08-03 02:38:00 +0000 |
---|---|---|
committer | Jason Kirtland <jek@discorporate.us> | 2007-08-03 02:38:00 +0000 |
commit | b8588ef4f76c9d0104bfc53b7af1f99386be4d4c (patch) | |
tree | 1d887aa7c677d45ac2d9ddd143f47f119cbf5af3 /lib/sqlalchemy/databases/mysql.py | |
parent | 90b2a57056f06dc14652517e331cb2609479dbc8 (diff) | |
download | sqlalchemy-b8588ef4f76c9d0104bfc53b7af1f99386be4d4c.tar.gz |
- Dialects can be queried for the server version (sqlite and mysql only with this commit)
- Mark everything in a test suite as failed when setUpAll fails.
- Added test coverage for Unicode table names in metadata.reflect()
- @testing.exclude() filters out tests by server version
- Applied exclude to the test suite, MySQL 4.1 passes again (no XA or SAVEPOINT)
- Removed MySQL charset-setting pool hook- charset=utf8&use_unicode=0 works just as well. (Am I nuts? I'd swear this didn't work before.)
- Finally migrated some old MySQL-tests into the dialect test module
- Corrected 'commit' and 'rollback' logic (and comment) for ancient MySQL versions lacking transactions entirely
- Deprecated the MySQL get_version_info in favor of server_version_info
- Added a big hunk-o-doc for MySQL.
Diffstat (limited to 'lib/sqlalchemy/databases/mysql.py')
-rw-r--r-- | lib/sqlalchemy/databases/mysql.py | 209 |
1 files changed, 150 insertions, 59 deletions
diff --git a/lib/sqlalchemy/databases/mysql.py b/lib/sqlalchemy/databases/mysql.py index 81a17cbe7..d4a2ac59f 100644 --- a/lib/sqlalchemy/databases/mysql.py +++ b/lib/sqlalchemy/databases/mysql.py @@ -4,11 +4,87 @@ # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -import re, datetime, inspect, warnings, weakref, operator +"""Support for the MySQL database. + +SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0, +with capablities 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 +example, they won't work in SQLAlchemy either. + +Currently, the only DB-API driver supported is `MySQL-Python` (also referred to +as `MySQLdb`). Either 1.2.1 or 1.2.2 are recommended. The alpha, beta and +gamma releases of 1.2.1 and 1.2.2 should be avoided. Support for Jython and +IronPython is planned. + +===================================== =============== +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. + +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 passing +the ``charset`` parameter to ``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. + +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 transactional engine such as `InnoDB`, `Falcon`, `SolidDB`, +`PBXT`, etc. Storage engines can be elected when creating tables in +SQLAlchemy by supplying a ``mysql_engine='whatever'`` to the ``Table`` +constructor. Any MySQL table creation option can be specified in this syntax. + +Not all MySQL storage engines support foreign keys. For `MyISAM` and similar +engines, the information loaded by table reflection will not include foreign +keys. For these tables, you may supply ``ForeignKeyConstraints`` at reflection +time:: + + Table('mytable', metadata, autoload=True, + ForeignKeyConstraint(['other_id'], ['othertable.other_id'])) + +For normal SQLAlchemy usage, loading this module is unnescesary. It will be +loaded on-demand when a MySQL connection is needed. If you would like to use +one of the MySQL-specific or enhanced column types when creating tables with +your ``Table`` definitions, then you will need to import them from this module:: + + from sqlalchemy.databases import mysql + + Table('mytable', metadata, + Column('id', Integer, primary_key=True), + Column('ittybittyblob', mysql.MSTinyBlob), + Column('biggy', mysql.MSBigInteger(unsigned=True))) + +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://sqlalchemy.org is a good resource for timely +information affecting MySQL in SQLAlchemy. +""" + +import re, datetime, inspect, warnings, weakref, operator, sys from array import array as _array from sqlalchemy import sql, schema, ansisql -from sqlalchemy.engine import default +from sqlalchemy.engine import base as engine_base, default import sqlalchemy.types as sqltypes import sqlalchemy.exceptions as exceptions import sqlalchemy.util as util @@ -786,7 +862,7 @@ class MSBinary(_BinaryType): pad value. length - Maximum data length, in bytes. If not length is specified, this + Maximum data length, in bytes. If length is not specified, this will generate a BLOB. This usage is deprecated. """ @@ -1112,12 +1188,34 @@ class MySQLDialect(ansisql.ANSIDialect): def do_execute(self, cursor, statement, parameters, **kwargs): cursor.execute(statement, parameters) + def do_commit(self, connection): + """Execute a COMMIT.""" + + # COMMIT/ROLLBACK were introduced in 3.23.15. + # Yes, we have at least one user who has to talk to these old versions! + # + # Ignore commit/rollback if support isn't present, otherwise even basic + # operations via autocommit fail. + try: + connection.commit() + except: + if self._server_version_info(connection) < (3, 23, 15): + args = sys.exc_info()[1].args + if args and args[0] == 1064: + return + raise + def do_rollback(self, connection): - # MySQL without InnoDB doesnt support rollback() + """Execute a ROLLBACK.""" + try: connection.rollback() except: - pass + if self._server_version_info(connection) < (3, 23, 15): + args = sys.exc_info()[1].args + if args and args[0] == 1064: + return + raise def do_begin_twophase(self, connection, xid): connection.execute("XA BEGIN %s", xid) @@ -1142,6 +1240,9 @@ class MySQLDialect(ansisql.ANSIDialect): resultset = connection.execute("XA RECOVER") return [row['data'][0:row['gtrid_length']] for row in resultset] + def do_ping(self, connection): + connection.ping() + def is_disconnect(self, e): return isinstance(e, self.dbapi.OperationalError) and \ e.args[0] in (2006, 2013, 2014, 2045, 2055) @@ -1183,13 +1284,28 @@ class MySQLDialect(ansisql.ANSIDialect): return False raise - def get_version_info(self, connectable): - """A tuple of the database server version.""" - - if hasattr(connectable, 'connect'): - dbapi_con = connectable.connect().connection - else: - dbapi_con = connectable + def server_version_info(self, connection): + """A tuple of the database server version. + + Formats the remote server version as a tuple of version values, + e.g. ``(5, 0, 44)``. If there are strings in the version number + they will be in the tuple too, so don't count on these all being + ``int`` values. + + This is a fast check that does not require a round trip. It is also + cached per-Connection. + """ + + try: + return connection.properties['_mysql_server_version_info'] + except KeyError: + version = connection.properties['_mysql_server_version_info'] = \ + self._server_version_info(connection.connection.connection) + return version + + def _server_version_info(self, dbapi_con): + """Convert a MySQL-python server_info string into a tuple.""" + version = [] for n in dbapi_con.get_server_info().split('.'): try: @@ -1198,6 +1314,19 @@ class MySQLDialect(ansisql.ANSIDialect): version.append(n) return tuple(version) + # @deprecated + def get_version_info(self, connectable): + """A tuple of the database server version. + + Deprecated, use ``server_version_info()``. + """ + + if isinstance(connectable, engine_base.Engine): + connectable = connectable.contextual_connect() + + return self.server_version_info(connectable) + get_version_info = util.deprecated(get_version_info) + def reflecttable(self, connection, table, include_columns): """Load column definitions from the server.""" @@ -1308,7 +1437,7 @@ class MySQLDialect(ansisql.ANSIDialect): # Note: MySQL-python 1.2.1c7 seems to ignore changes made # on a connection via set_character_set() - if self.get_version_info(connection) < (4, 1, 0): + if self.server_version_info(connection) < (4, 1, 0): try: return connection.connection.character_set_name() except AttributeError: @@ -1373,7 +1502,7 @@ class MySQLDialect(ansisql.ANSIDialect): return connection.properties['collations'] except KeyError: collations = {} - if self.get_version_info(connection) < (4, 1, 0): + if self.server_version_info(connection) < (4, 1, 0): pass else: rs = connection.execute('SHOW COLLATION') @@ -1458,6 +1587,11 @@ class MySQLCompiler(ansisql.ANSICompiler): return text +# ug. "InnoDB needs indexes on foreign keys and referenced keys [...]. +# Starting with MySQL 4.1.2, these indexes are created automatically. +# In older versions, the indexes must be created explicitly or the +# creation of foreign key constraints fails." + class MySQLSchemaGenerator(ansisql.ANSISchemaGenerator): def get_column_specification(self, column, override_pk=False, first_pk=False): @@ -1514,6 +1648,8 @@ class MySQLSchemaDropper(ansisql.ANSISchemaDropper): class MySQLIdentifierPreparer(ansisql.ANSIIdentifierPreparer): + """MySQL-specific schema identifier configuration.""" + def __init__(self, dialect): super(MySQLIdentifierPreparer, self).__init__(dialect, initial_quote='`') @@ -1534,50 +1670,5 @@ class MySQLIdentifierPreparer(ansisql.ANSIIdentifierPreparer): # just leave everything as-is. return value -class MySQLCharsetOnConnect(object): - """Use an alternate connection character set automatically.""" - - def __init__(self, charset, collation=None): - """Creates a pool listener that decorates new database connections. - - Sets the connection character set on MySQL connections. Strings - sent to and from the server will use this encoding, and if a collation - is provided it will be used as the default. - - There is also a MySQL-python 'charset' keyword for connections, - however that keyword has the side-effect of turning all strings into - Unicode. - - This class is a ``Pool`` listener. To use, pass an insstance to the - ``listeners`` argument to create_engine or Pool constructor, or - manually add it to a pool with ``add_listener()``. - charset: - The character set to use - - collation: - Optional, use a non-default collation for the given charset - """ - - self.charset = charset - self.collation = collation - - def connect(self, dbapi_con, con_record): - cr = dbapi_con.cursor() - try: - if self.collation is None: - if hasattr(dbapi_con, 'set_character_set'): - dbapi_con.set_character_set(self.charset) - else: - cr.execute("SET NAMES %s" % self.charset) - else: - if hasattr(dbapi_con, 'set_character_set'): - dbapi_con.set_character_set(self.charset) - cr.execute("SET NAMES %s COLLATE %s" % (self.charset, - self.collation)) - # let SQL errors (1064 if SET NAMES is not supported) raise - finally: - cr.close() - - dialect = MySQLDialect |