]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all` will
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 2 Mar 2013 22:47:58 +0000 (17:47 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 2 Mar 2013 22:47:58 +0000 (17:47 -0500)
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

doc/build/changelog/changelog_08.rst
doc/build/changelog/migration_08.rst
lib/sqlalchemy/engine/ddl.py
test/engine/test_ddlemit.py [new file with mode: 0644]
test/sql/test_metadata.py

index 4d388b2b08d51113afd7baa6270f5b9c0e051b14..37b0236268c9a400582aa66d15afd7f1cc796702 100644 (file)
@@ -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
index 06a9402f6a8380903ad2d3e15f301f4395b715d3..bb6d85e58f69565f5e440fafbf6b6989b4737d05 100644 (file)
@@ -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`
 ----------------------------------------------------------------
 
index 6035e2901b592f3d840fc019bcd8a47d1dd06375..c61a9d59cb5510fe7dced204cbfee2fbe30fc6a9 100644 (file)
@@ -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 (file)
index 0000000..3dbd575
--- /dev/null
@@ -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
index b702e31e71d43d4c012d1614ae1a6e5555ee93d7..db2eaa4fa75be6a937adabad2f1905e2036fb73e 100644 (file)
@@ -1628,3 +1628,4 @@ class CatchAllEventsTest(fixtures.TestBase):
                 'UniqueConstraint->Table', 'UniqueConstraint->t2'
             ]
         )
+