From: Mike Bayer Date: Sat, 2 Mar 2013 22:47:58 +0000 (-0500) Subject: - :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all` will X-Git-Tag: rel_0_8_0~13^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ddfe3b390fac8e5c7ef633ad5dd67463e94443b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all` will now accommodate an empty list as an instruction to not create/drop any items, rather than ignoring the collection. [ticket:2664]. This is a behavioral change and extra notes to the changelog and migration document have been added. - create a new test suite for exercising codepaths in engine/ddl.py --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index 4d388b2b08..37b0236268 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -6,6 +6,25 @@ .. changelog:: :version: 0.8.0 + .. note:: + + Be sure to *re-read* :doc:`migration_08` for this release. + There are some new behavioral changes as of 0.8.0 + not present in 0.8.0b2, including: + + * :ref:`legacy_is_orphan_addition` + + * :ref:`metadata_create_drop_tables` + + .. change:: + :tags: bug, schema + :tickets: 2664 + + :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all` will + now accommodate an empty list as an instruction to not create/drop + any items, rather than ignoring the collection. + + .. change:: :tags: bug, tests :tickets: 2669 diff --git a/doc/build/changelog/migration_08.rst b/doc/build/changelog/migration_08.rst index 06a9402f6a..bb6d85e58f 100644 --- a/doc/build/changelog/migration_08.rst +++ b/doc/build/changelog/migration_08.rst @@ -1145,6 +1145,23 @@ entity, ``query.correlate(someentity)``. :ticket:`2179` +.. _metadata_create_drop_tables: + +create_all() and drop_all() will now honor an empty list as such +---------------------------------------------------------------- + +The methods :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all` +will now accept a list of :class:`.Table` objects that is empty, +and will not emit any CREATE or DROP statements. Previously, +an empty list was interepreted the same as passing ``None`` +for a collection, and CREATE/DROP would be emitted for all +items unconditionally. + +This is a bug fix but some applications may have been relying upon +the previous behavior. + +:ticket:`2664` + Repaired the Event Targeting of :class:`.InstrumentationEvents` ---------------------------------------------------------------- diff --git a/lib/sqlalchemy/engine/ddl.py b/lib/sqlalchemy/engine/ddl.py index 6035e2901b..c61a9d59cb 100644 --- a/lib/sqlalchemy/engine/ddl.py +++ b/lib/sqlalchemy/engine/ddl.py @@ -21,7 +21,7 @@ class SchemaGenerator(DDLBase): tables=None, **kwargs): super(SchemaGenerator, self).__init__(connection, **kwargs) self.checkfirst = checkfirst - self.tables = tables and set(tables) or None + self.tables = tables self.preparer = dialect.identifier_preparer self.dialect = dialect self.memo = {} @@ -49,7 +49,7 @@ class SchemaGenerator(DDLBase): ) def visit_metadata(self, metadata): - if self.tables: + if self.tables is not None: tables = self.tables else: tables = metadata.tables.values() @@ -117,7 +117,7 @@ class SchemaDropper(DDLBase): self.memo = {} def visit_metadata(self, metadata): - if self.tables: + if self.tables is not None: tables = self.tables else: tables = metadata.tables.values() diff --git a/test/engine/test_ddlemit.py b/test/engine/test_ddlemit.py new file mode 100644 index 0000000000..3dbd5756ad --- /dev/null +++ b/test/engine/test_ddlemit.py @@ -0,0 +1,184 @@ +from sqlalchemy.testing import fixtures +from sqlalchemy.engine.ddl import SchemaGenerator, SchemaDropper +from sqlalchemy.engine import default +from sqlalchemy import MetaData, Table, Column, Integer, Sequence +from sqlalchemy import schema + +class EmitDDLTest(fixtures.TestBase): + def _mock_connection(self, item_exists): + _canary = [] + + class MockDialect(default.DefaultDialect): + supports_sequences = True + + def has_table(self, connection, name, schema): + return item_exists(name) + + def has_sequence(self, connection, name, schema): + return item_exists(name) + + class MockConnection(object): + dialect = MockDialect() + canary = _canary + + def execute(self, item): + _canary.append(item) + + return MockConnection() + + def _mock_create_fixture(self, checkfirst, tables, + item_exists=lambda item: False): + connection = self._mock_connection(item_exists) + + return SchemaGenerator(connection.dialect, connection, + checkfirst=checkfirst, + tables=tables) + + def _mock_drop_fixture(self, checkfirst, tables, + item_exists=lambda item: True): + connection = self._mock_connection(item_exists) + + return SchemaDropper(connection.dialect, connection, + checkfirst=checkfirst, + tables=tables) + + def _table_fixture(self): + m = MetaData() + + return (m, ) + tuple( + Table('t%d' % i, m, Column('x', Integer)) + for i in xrange(1, 6) + ) + + def _table_seq_fixture(self): + m = MetaData() + + s1 = Sequence('s1') + s2 = Sequence('s2') + t1 = Table('t1', m, Column("x", Integer, s1, primary_key=True)) + t2 = Table('t2', m, Column("x", Integer, s2, primary_key=True)) + + return m, t1, t2, s1, s2 + + + def test_create_seq_checkfirst(self): + m, t1, t2, s1, s2 = self._table_seq_fixture() + generator = self._mock_create_fixture(True, [t1, t2], + item_exists=lambda t: t not in ("t1", "s1") + ) + + self._assert_create([t1, s1], generator, m) + + + def test_drop_seq_checkfirst(self): + m, t1, t2, s1, s2 = self._table_seq_fixture() + generator = self._mock_drop_fixture(True, [t1, t2], + item_exists=lambda t: t in ("t1", "s1") + ) + + self._assert_drop([t1, s1], generator, m) + + def test_create_collection_checkfirst(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_create_fixture(True, [t2, t3, t4], + item_exists=lambda t: t not in ("t2", "t4") + ) + + self._assert_create_tables([t2, t4], generator, m) + + def test_drop_collection_checkfirst(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_drop_fixture(True, [t2, t3, t4], + item_exists=lambda t: t in ("t2", "t4") + ) + + self._assert_drop_tables([t2, t4], generator, m) + + def test_create_collection_nocheck(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_create_fixture(False, [t2, t3, t4], + item_exists=lambda t: t not in ("t2", "t4") + ) + + self._assert_create_tables([t2, t3, t4], generator, m) + + def test_create_empty_collection(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_create_fixture(True, [], + item_exists=lambda t: t not in ("t2", "t4") + ) + + self._assert_create_tables([], generator, m) + + def test_drop_empty_collection(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_drop_fixture(True, [], + item_exists=lambda t: t in ("t2", "t4") + ) + + self._assert_drop_tables([], generator, m) + + def test_drop_collection_nocheck(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_drop_fixture(False, [t2, t3, t4], + item_exists=lambda t: t in ("t2", "t4") + ) + + self._assert_drop_tables([t2, t3, t4], generator, m) + + def test_create_metadata_checkfirst(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_create_fixture(True, None, + item_exists=lambda t: t not in ("t2", "t4") + ) + + self._assert_create_tables([t2, t4], generator, m) + + def test_drop_metadata_checkfirst(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_drop_fixture(True, None, + item_exists=lambda t: t in ("t2", "t4") + ) + + self._assert_drop_tables([t2, t4], generator, m) + + def test_create_metadata_nocheck(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_create_fixture(False, None, + item_exists=lambda t: t not in ("t2", "t4") + ) + + self._assert_create_tables([t1, t2, t3, t4, t5], generator, m) + + def test_drop_metadata_nocheck(self): + m, t1, t2, t3, t4, t5 = self._table_fixture() + generator = self._mock_drop_fixture(False, None, + item_exists=lambda t: t in ("t2", "t4") + ) + + self._assert_drop_tables([t1, t2, t3, t4, t5], generator, m) + + def _assert_create_tables(self, elements, generator, argument): + self._assert_ddl(schema.CreateTable, elements, generator, argument) + + def _assert_drop_tables(self, elements, generator, argument): + self._assert_ddl(schema.DropTable, elements, generator, argument) + + def _assert_create(self, elements, generator, argument): + self._assert_ddl( + (schema.CreateTable, schema.CreateSequence), + elements, generator, argument) + + def _assert_drop(self, elements, generator, argument): + self._assert_ddl( + (schema.DropTable, schema.DropSequence), + elements, generator, argument) + + def _assert_ddl(self, ddl_cls, elements, generator, argument): + generator.traverse_single(argument) + for c in generator.connection.canary: + assert isinstance(c, ddl_cls) + assert c.element in elements, "element %r was not expected"\ + % c.element + elements.remove(c.element) + assert not elements, "elements remain in list: %r" % elements diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index b702e31e71..db2eaa4fa7 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -1628,3 +1628,4 @@ class CatchAllEventsTest(fixtures.TestBase): 'UniqueConstraint->Table', 'UniqueConstraint->t2' ] ) +