]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Unit tests pass 100% on MySQL installed
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Jun 2011 23:52:26 +0000 (19:52 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Jun 2011 23:52:26 +0000 (19:52 -0400)
on windows, after aggressive exclusion of a wide variety
of tests.   Not clear to what degree the failures are related to
version 5.5 vs. the usage of windows, in particular the ON UPDATE CASCADE
immediately crashes the server.   The features being tested here are all
edge cases not likely to be used in typical MySQL environments.
- Removed the "adjust casing" step that would
fail when reflecting a table on MySQL
on windows with a mixed case name.  After some
experimenting with a windows MySQL server, it's
been determined that this step wasn't really
helping the situation much; MySQL does not return
FK names with proper casing on non-windows
platforms either, and removing the step at
least allows the reflection to act more like
it does on other OSes.   A warning here
has been considered but its difficult to
determine under what conditions such a warning
can be raised, so punted on that for now -
added some docs instead. [ticket:2181]

- supports_sane_rowcount will be set to False
if using MySQLdb and the DBAPI doesn't provide
the constants.CLIENT module.

CHANGES
README.unittests
lib/sqlalchemy/connectors/mysqldb.py
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/engine/reflection.py
test/engine/test_reflection.py
test/engine/test_transaction.py
test/lib/requires.py
test/lib/testing.py
test/orm/test_naturalpks.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index 4c8a86db12e528d9366d897651b51e70b85fcf3f..36b4fe275cee2aed780419cccea16e2b7231b72b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -25,6 +25,29 @@ CHANGES
     and are redundant:  reflecttable(), create(), 
     drop(), text(), engine.func
 
+- mysql
+  - Unit tests pass 100% on MySQL installed
+    on windows.
+
+  - Removed the "adjust casing" step that would
+    fail when reflecting a table on MySQL
+    on windows with a mixed case name.  After some
+    experimenting with a windows MySQL server, it's
+    been determined that this step wasn't really 
+    helping the situation much; MySQL does not return
+    FK names with proper casing on non-windows 
+    platforms either, and removing the step at
+    least allows the reflection to act more like
+    it does on other OSes.   A warning here
+    has been considered but its difficult to 
+    determine under what conditions such a warning
+    can be raised, so punted on that for now - 
+    added some docs instead. [ticket:2181]
+
+  - supports_sane_rowcount will be set to False
+    if using MySQLdb and the DBAPI doesn't provide 
+    the constants.CLIENT module.
+
 0.7.0
 =======
 - This section documents those changes from 0.7b4
index 0ae6e155e5af48559a458887db97b0e4a72be1bf..d06452d359c789b8f5a7045db44696a997b6c73b 100644 (file)
@@ -102,6 +102,9 @@ expect them to be present will fail.
 
 Additional steps specific to individual databases are as follows:
 
+    MYSQL: Default storage engine should be "MyISAM".   Tests that require
+    "InnoDB" as the engine will specify this explicitly.
+
     ORACLE: a user named "test_schema" is created.
 
     The primary database user needs to be able to create and drop tables,
index 189c412a00e9718f06e0ff79dad96e922a004fe7..b5a9f05a8d2b8bd48a9202eccbcbf0120f07fc7b 100644 (file)
@@ -93,7 +93,7 @@ class MySQLDBConnector(Connector):
                                     ).constants.CLIENT
                 client_flag |= CLIENT_FLAGS.FOUND_ROWS
             except (AttributeError, ImportError):
-                pass
+                self.supports_sane_rowcount = False
             opts['client_flag'] = client_flag
         return [[], opts]
 
index 33dc8a73e4890e0833159c2829d7afc64e1f091e..bb23c12afa3e2547e64998c757eee81810531036 100644 (file)
@@ -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
 ----
 
@@ -1974,17 +1990,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()
 
@@ -2114,7 +2119,7 @@ class MySQLTableDefinitionParser(object):
         self.dialect = dialect
         self.preparer = preparer
         self._prep_regexes()
-    
+
     def parse(self, show_create, charset):
         state = ReflectedState()
         state.charset = charset
index ca436032535bcd484706aba94cb8f93d40bde5b8..e87c1ce0fd3791742169f78b4914c60fad6ef01a 100644 (file)
@@ -346,12 +346,6 @@ class Inspector(object):
         """
         dialect = self.bind.dialect
 
-        # MySQL dialect does this.  Applicable with other dialects?
-        if hasattr(dialect, '_connection_charset') \
-                                        and hasattr(dialect, '_adjust_casing'):
-            charset = dialect._connection_charset
-            dialect._adjust_casing(table)
-
         # table attributes we might need.
         reflection_options = dict(
             (k, table.kwargs.get(k)) for k in dialect.reflection_options if k in table.kwargs)
index 5a684eaa50a43a591ef67c611fb5eab08b6cc531..febd83c1b12842fd5b95d72bede54ff2b300d70f 100644 (file)
@@ -394,6 +394,7 @@ class ReflectionTest(fixtures.TestBase, ComparesTables):
         finally:
             meta.drop_all()
 
+    @testing.provide_metadata
     def test_nonreflected_fk_raises(self):
         """test that a NoReferencedColumnError is raised when reflecting
         a table with an FK to another table which has not included the target
@@ -401,7 +402,7 @@ class ReflectionTest(fixtures.TestBase, ComparesTables):
 
         """
 
-        meta = MetaData(testing.db)
+        meta = self.metadata
         a1 = Table('a', meta,
             Column('x', sa.Integer, primary_key=True),
             Column('z', sa.Integer),
@@ -412,15 +413,11 @@ class ReflectionTest(fixtures.TestBase, ComparesTables):
             test_needs_fk=True
         )
         meta.create_all()
-        try:
-            m2 = MetaData(testing.db)
-            a2 = Table('a', m2, include_columns=['z'], autoload=True)
-            b2 = Table('b', m2, autoload=True)
-
-            assert_raises(sa.exc.NoReferencedColumnError, a2.join, b2)
-        finally:
-            meta.drop_all()
+        m2 = MetaData(testing.db)
+        a2 = Table('a', m2, include_columns=['z'], autoload=True)
+        b2 = Table('b', m2, autoload=True)
 
+        assert_raises(sa.exc.NoReferencedColumnError, a2.join, b2)
 
     @testing.exclude('mysql', '<', (4, 1, 1), 'innodb funkiness')
     def test_override_existing_fk(self):
@@ -585,7 +582,10 @@ class ReflectionTest(fixtures.TestBase, ComparesTables):
 
 
     @testing.crashes('oracle', 'FIXME: unknown, confirm not fails_on')
-    @testing.fails_on('+informixdb', 'FIXME: should be supported via the DELIMITED env var but that breaks everything else for now')
+    @testing.fails_on('+informixdb', 
+                        "FIXME: should be supported via the "
+                        "DELIMITED env var but that breaks "
+                        "everything else for now")
     def test_reserved(self):
 
         # check a table that uses an SQL reserved name doesn't cause an
@@ -1136,6 +1136,48 @@ class ReverseCasingReflectTest(fixtures.TestBase, AssertsCompiledSQL):
                             'weird_casing."Col2", weird_casing."col3" '
                             'FROM weird_casing')
 
+class CaseSensitiveTest(fixtures.TablesTest):
+    """Nail down case sensitive behaviors, mostly on MySQL."""
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table('SomeTable', metadata, 
+            Column('x', Integer, primary_key=True),
+            test_needs_fk=True
+        )
+        Table('SomeOtherTable', metadata, 
+            Column('x', Integer, primary_key=True),
+            Column('y', Integer, sa.ForeignKey("SomeTable.x")),
+            test_needs_fk=True
+        )
+
+    @testing.fails_if(testing.requires._has_mysql_on_windows)
+    def test_table_names(self):
+        x = testing.db.run_callable(
+            testing.db.dialect.get_table_names
+        )
+        assert set(["SomeTable", "SomeOtherTable"]).issubset(x)
+
+    def test_reflect_exact_name(self):
+        m = MetaData()
+        t1 = Table("SomeTable", m, autoload=True, autoload_with=testing.db)
+        eq_(t1.name, "SomeTable")
+        assert t1.c.x is not None
+
+    @testing.fails_on('mysql', 'FKs come back as lower case no matter what')
+    def test_reflect_via_fk(self):
+        m = MetaData()
+        t2 = Table("SomeOtherTable", m, autoload=True, autoload_with=testing.db)
+        eq_(t2.name, "SomeOtherTable")
+        assert "SomeTable" in m.tables
+
+    @testing.fails_on_everything_except('sqlite', 'mysql')
+    def test_reflect_case_insensitive(self):
+        m = MetaData()
+        t2 = Table("sOmEtAbLe", m, autoload=True, autoload_with=testing.db)
+        eq_(t2.name, "sOmEtAbLe")
+
+
 class ComponentReflectionTest(fixtures.TestBase):
 
     @testing.requires.schemas
index e4046f4d361b5d0b505fefd2a4c1c693c3d6eb81..1184befda9507ab68d797fec8318f86a970e720d 100644 (file)
@@ -276,6 +276,7 @@ class TransactionTest(fixtures.TestBase):
     # PG emergency shutdown:
     # select * from pg_prepared_xacts
     # ROLLBACK PREPARED '<xid>'
+    @testing.requires.skip_mysql_on_windows
     @testing.requires.two_phase_transactions
     @testing.requires.savepoints
     def test_mixed_two_phase_transaction(self):
index f79c53c44c3e22ce960457a4a9db366c3d761a8d..42ac82988b456662b9e2841bed8cca8743ab341a 100644 (file)
@@ -129,11 +129,11 @@ def savepoints(fn):
     return _chain_decorators_on(
         fn,
         emits_warning_on('mssql', 'Savepoint support in mssql is experimental and may lead to data loss.'),
-        no_support('access', 'not supported by database'),
-        no_support('sqlite', 'not supported by database'),
-        no_support('sybase', 'FIXME: guessing, needs confirmation'),
-        exclude('mysql', '<', (5, 0, 3), 'not supported by database'),
-        exclude('informix', '<', (11, 55, 'xC3'), 'not supported by database'),
+        no_support('access', 'savepoints not supported'),
+        no_support('sqlite', 'savepoints not supported'),
+        no_support('sybase', 'savepoints not supported'),
+        exclude('mysql', '<', (5, 0, 3), 'savepoints not supported'),
+        exclude('informix', '<', (11, 55, 'xC3'), 'savepoints not supported'),
         )
 
 def denormalized_names(fn):
@@ -369,6 +369,10 @@ def _has_sqlite():
     except ImportError:
         return False
 
+def _has_mysql_on_windows():
+    return testing.against('mysql+mysqldb') and \
+            testing.db.dialect._server_casing == 1
+
 def sqlite(fn):
     return _chain_decorators_on(
         fn,
@@ -387,3 +391,13 @@ def ad_hoc_engines(fn):
         fn,
         skip_if(lambda: config.options.low_connections)
     )
+
+def skip_mysql_on_windows(fn):
+    """Catchall for a large variety of MySQL on Windows failures"""
+
+    return _chain_decorators_on(
+        fn,
+        skip_if(_has_mysql_on_windows,
+            "Not supported on MySQL + Windows"
+        )
+    )
\ No newline at end of file
index f5babc19c313300c3a83ed54c414c3d03ead70c4..90e8b7746d91dd636a5675868ad622da79dcfce2 100644 (file)
@@ -267,6 +267,7 @@ def _is_excluded(db, op, spec):
       _is_excluded('bigdb', '==', (9,0,9))
       _is_excluded('yikesdb', 'in', ((0, 3, 'alpha2'), (0, 3, 'alpha3')))
     """
+
     vendor_spec = db_spec(db)
 
     if not vendor_spec(config.db):
index 34b392cd14dc9c38165f56a4cbd2044079ab4ff1..f0f3ebb211847b02a534848e804c822522c14141 100644 (file)
@@ -15,6 +15,9 @@ from test.lib import fixtures
 from test.orm import _fixtures
 
 class NaturalPKTest(fixtures.MappedTest):
+    # MySQL 5.5 on Windows crashes (the entire server, not the client)
+    # if you screw around with ON UPDATE CASCADE type of stuff.
+    __requires__ = 'skip_mysql_on_windows',
 
     @classmethod
     def define_tables(cls, metadata):
@@ -616,6 +619,8 @@ class SelfReferentialTest(fixtures.MappedTest):
 
 
 class NonPKCascadeTest(fixtures.MappedTest):
+    __requires__ = 'skip_mysql_on_windows',
+
     @classmethod
     def define_tables(cls, metadata):
         if testing.against('oracle'):
@@ -983,6 +988,8 @@ class JoinedInheritanceTest(fixtures.MappedTest):
     # mssql doesn't allow ON UPDATE on self-referential keys
     __unsupported_on__ = ('mssql',) 
 
+    __requires__ = 'skip_mysql_on_windows',
+
     @classmethod
     def define_tables(cls, metadata):
         if testing.against('oracle'):
index 6ccb67f9198f836b5af65da1d268b4c4a1547b58..4fbafe6848cdd751ba0659018f6d1459fed80222 100644 (file)
@@ -761,8 +761,9 @@ class EnumTest(fixtures.TestBase):
         eq_(e1.adapt(ENUM).name, 'foo')
         eq_(e1.adapt(ENUM).schema, 'bar')
 
-    @testing.fails_on('mysql+mysqldb', "MySQL seems to issue a 'data truncated' warning.")
-    @testing.fails_on('mysql+pymysql', "MySQL seems to issue a 'data truncated' warning.")
+    @testing.crashes('mysql', 
+                    'Inconsistent behavior across various OS/drivers'
+                )
     def test_constraint(self):
         assert_raises(exc.DBAPIError, 
             enum_table.insert().execute,