]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [feature] The "extend_existing" flag on Table
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 4 Dec 2011 19:16:42 +0000 (14:16 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 4 Dec 2011 19:16:42 +0000 (14:16 -0500)
now allows for the reflection process to take
effect for a Table object that's already been
defined; when autoload=True and extend_existing=True
are both set, the full set of columns will be
reflected from the Table which will then
*overwrite* those columns already present,
rather than no activity occurring.  Columns that
are present directly in the autoload run
will be used as always, however.
[ticket:1410]

CHANGES
lib/sqlalchemy/schema.py
test/engine/test_reflection.py

diff --git a/CHANGES b/CHANGES
index b9bdfb15214e105323de69cd890bbd8f8bd82b56..6d4870ab7025ac46c46deb2927e1fba3668aad6b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -164,6 +164,18 @@ CHANGES
       on dialect, but only works on Postgresql so far.
     Courtesy Manlio Perillo, [ticket:1679]
 
+  - [feature] The "extend_existing" flag on Table
+    now allows for the reflection process to take
+    effect for a Table object that's already been 
+    defined; when autoload=True and extend_existing=True
+    are both set, the full set of columns will be
+    reflected from the Table which will then 
+    *overwrite* those columns already present,
+    rather than no activity occurring.  Columns that
+    are present directly in the autoload run
+    will be used as always, however.
+    [ticket:1410]
+
   - [bug] Fixed bug whereby TypeDecorator would
     return a stale value for _type_affinity, when
     using a TypeDecorator that "switches" types,
index 093a456e22df2b9ee31a336b7a7556fb4cdb789a..b5b057625bc42b7ff6a458b4debafa9ee22f8466 100644 (file)
@@ -138,13 +138,34 @@ class Table(SchemaItem, expression.TableClause):
         or Connection instance to be used for the table reflection. If
         ``None``, the underlying MetaData's bound connectable will be used.
 
-    :param extend_existing: When ``True``, indicates that if this Table is already
+    :param extend_existing: When ``True``, indicates that if this :class:`.Table` is already
         present in the given :class:`.MetaData`, apply further arguments within
         the constructor to the existing :class:`.Table`.  
         
-        If extend_existing or keep_existing are not set, an error is
+        If ``extend_existing`` or ``keep_existing`` are not set, an error is
         raised if additional table modifiers are specified when 
         the given :class:`.Table` is already present in the :class:`.MetaData`.
+        
+        As of version 0.7.4, ``extend_existing`` will work in conjunction
+        with ``autoload=True`` to run a new reflection operation against
+        the database; new :class:`.Column` objects will be produced
+        from database metadata to replace those existing with the same
+        name, and additional :class:`.Column` objects not present
+        in the :class:`.Table` will be added.
+        As is always the case with ``autoload=True``, :class:`.Column`
+        objects can be specified in the same :class:`.Table` constructor,
+        which will take precedence.  I.e.::
+        
+            Table("mytable", metadata,
+                        Column('y', Integer),
+                        extend_existing=True,
+                        autoload=True,
+                        autoload_with=engine
+                    )
+        
+        The above will overwrite all columns within ``mytable`` which are present
+        in the database, except for ``y`` which will be used as is
+        from the above definition.
 
     :param implicit_returning: True by default - indicates that 
         RETURNING can be used by default to fetch newly inserted primary key 
@@ -329,27 +350,30 @@ class Table(SchemaItem, expression.TableClause):
         # we do it after the table is in the singleton dictionary to support
         # circular foreign keys
         if autoload:
-            if autoload_with:
-                autoload_with.run_callable(
-                    autoload_with.dialect.reflecttable,
-                    self, include_columns
-                )
-            else:
-                bind = _bind_or_error(metadata, 
-                        msg="No engine is bound to this Table's MetaData. "
-                        "Pass an engine to the Table via "
-                        "autoload_with=<someengine>, "
-                        "or associate the MetaData with an engine via "
-                        "metadata.bind=<someengine>")
-                bind.run_callable(
-                        bind.dialect.reflecttable,
-                        self, include_columns
-                    )
+            self._autoload(metadata, autoload_with, include_columns)
 
         # initialize all the column, etc. objects.  done after reflection to
         # allow user-overrides
         self._init_items(*args)
 
+    def _autoload(self, metadata, autoload_with, include_columns):
+        if autoload_with:
+            autoload_with.run_callable(
+                autoload_with.dialect.reflecttable,
+                self, include_columns
+            )
+        else:
+            bind = _bind_or_error(metadata, 
+                    msg="No engine is bound to this Table's MetaData. "
+                    "Pass an engine to the Table via "
+                    "autoload_with=<someengine>, "
+                    "or associate the MetaData with an engine via "
+                    "metadata.bind=<someengine>")
+            bind.run_callable(
+                    bind.dialect.reflecttable,
+                    self, include_columns
+                )
+
     @property
     def _sorted_constraints(self):
         """Return the set of constraints as a list, sorted by creation order."""
@@ -378,6 +402,9 @@ class Table(SchemaItem, expression.TableClause):
         if 'info' in kwargs:
             self.info = kwargs.pop('info')
 
+        if autoload:
+            self._autoload(self.metadata, autoload_with, include_columns)
+
         self._extra_kwargs(**kwargs)
         self._init_items(*args)
 
index 4beeab14d7c545e18831d273b16073f5eb66c14b..4e80e71e4fc54b4222e8a2698f3d5eeee8f373f9 100644 (file)
@@ -3,7 +3,7 @@ import StringIO, unicodedata
 from sqlalchemy import types as sql_types
 from sqlalchemy import schema, events, event
 from sqlalchemy.engine.reflection import Inspector
-from sqlalchemy import MetaData, Integer
+from sqlalchemy import MetaData, Integer, String
 from test.lib.schema import Table, Column
 import sqlalchemy as sa
 from test.lib import ComparesTables, \
@@ -118,6 +118,45 @@ class ReflectionTest(fixtures.TestBase, ComparesTables):
         for c in ('a', 'c', 'd'):
             assert c not in foo.c
 
+    @testing.provide_metadata
+    def test_extend_existing(self):
+        meta = self.metadata
+
+        t1 = Table('t', meta,
+            Column('x', Integer),
+            Column('y', Integer),
+            Column('z', Integer, server_default="5"),
+        )
+        meta.create_all()
+
+        m2 = MetaData()
+        old_z = Column('z', String)
+        old_y = Column('y', String)
+        old_q = Column('q', Integer)
+        t2 = Table('t', m2, old_z, old_q)
+        t2 = Table('t', m2, old_y,
+                        extend_existing=True, 
+                        autoload=True, 
+                        autoload_with=testing.db)
+        eq_(
+            set(t2.columns.keys()), 
+            set(['x', 'y', 'z', 'q'])
+        )
+        assert t2.c.z is not old_z
+        assert t2.c.y is old_y
+        assert t2.c.z.type._type_affinity is Integer
+        assert t2.c.q is old_q
+
+        m3 = MetaData()
+        t3 = Table('t', m3, Column('z', Integer))
+        t3 = Table('t', m3, extend_existing=False, 
+                        autoload=True, 
+                        autoload_with=testing.db)
+        eq_(
+            set(t3.columns.keys()), 
+            set(['z'])
+        )
+
     @testing.emits_warning(r".*omitted columns")
     @testing.provide_metadata
     def test_include_columns_indexes(self):