]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [bug] ORM will perform extra effort to determine
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 5 Jul 2012 14:19:59 +0000 (10:19 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 5 Jul 2012 14:19:59 +0000 (10:19 -0400)
that an FK dependency between two tables is
not significant during flush if the tables
are related via joined inheritance and the FK
dependency is not part of the inherit_condition,
saves the user a use_alter directive.
[ticket:2527]

CHANGES
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/sql/util.py
test/orm/inheritance/test_basic.py

diff --git a/CHANGES b/CHANGES
index 0f0159894f70576700e4b00ed8aac4583c73c0a3..19e4ad512320e43604cfd97e6b3263fb6f1b410e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -58,6 +58,14 @@ underneath "0.7.xx".
     approach can be upgraded to this new
     approach. [ticket:1401]
 
+  - [bug] ORM will perform extra effort to determine
+    that an FK dependency between two tables is
+    not significant during flush if the tables
+    are related via joined inheritance and the FK
+    dependency is not part of the inherit_condition,
+    saves the user a use_alter directive.
+    [ticket:2527]
+
   - [feature] New standalone function with_polymorphic()
     provides the functionality of query.with_polymorphic()
     in a standalone form.   It can be applied to any
index 761e1af569c83c1c33dde2ca62e65e222a6ce468..dceccf70f837612b0f12a81a95e6286f6276f39a 100644 (file)
@@ -206,16 +206,16 @@ class Mapper(object):
     local_table = None
     """The :class:`.Selectable` which this :class:`.Mapper` manages.
 
-    Typically is an instance of :class:`.Table` or :class:`.Alias`. 
-    May also be ``None``. 
+    Typically is an instance of :class:`.Table` or :class:`.Alias`.
+    May also be ``None``.
 
     The "local" table is the
-    selectable that the :class:`.Mapper` is directly responsible for 
+    selectable that the :class:`.Mapper` is directly responsible for
     managing from an attribute access and flush perspective.   For
     non-inheriting mappers, the local table is the same as the
     "mapped" table.   For joined-table inheritance mappers, local_table
     will be the particular sub-table of the overall "join" which
-    this :class:`.Mapper` represents.  If this mapper is a 
+    this :class:`.Mapper` represents.  If this mapper is a
     single-table inheriting mapper, local_table will be ``None``.
 
     See also :attr:`~.Mapper.mapped_table`.
@@ -225,11 +225,11 @@ class Mapper(object):
     mapped_table = None
     """The :class:`.Selectable` to which this :class:`.Mapper` is mapped.
 
-    Typically an instance of :class:`.Table`, :class:`.Join`, or 
+    Typically an instance of :class:`.Table`, :class:`.Join`, or
     :class:`.Alias`.
 
-    The "mapped" table is the selectable that 
-    the mapper selects from during queries.   For non-inheriting 
+    The "mapped" table is the selectable that
+    the mapper selects from during queries.   For non-inheriting
     mappers, the mapped table is the same as the "local" table.
     For joined-table inheritance mappers, mapped_table references the
     full :class:`.Join` representing full rows for this particular
@@ -241,7 +241,7 @@ class Mapper(object):
     """
 
     inherits = None
-    """References the :class:`.Mapper` which this :class:`.Mapper` 
+    """References the :class:`.Mapper` which this :class:`.Mapper`
     inherits from, if any.
 
     This is a *read only* attribute determined during mapper construction.
@@ -260,7 +260,7 @@ class Mapper(object):
     """
 
     concrete = None
-    """Represent ``True`` if this :class:`.Mapper` is a concrete 
+    """Represent ``True`` if this :class:`.Mapper` is a concrete
     inheritance mapper.
 
     This is a *read only* attribute determined during mapper construction.
@@ -283,7 +283,7 @@ class Mapper(object):
 
     primary_key = None
     """An iterable containing the collection of :class:`.Column` objects
-    which comprise the 'primary key' of the mapped table, from the 
+    which comprise the 'primary key' of the mapped table, from the
     perspective of this :class:`.Mapper`.
 
     This list is against the selectable in :attr:`~.Mapper.mapped_table`.  In the
@@ -293,7 +293,7 @@ class Mapper(object):
     referenced by the :class:`.Join`.
 
     The list is also not necessarily the same as the primary key column
-    collection associated with the underlying tables; the :class:`.Mapper` 
+    collection associated with the underlying tables; the :class:`.Mapper`
     features a ``primary_key`` argument that can override what the
     :class:`.Mapper` considers as primary key columns.
 
@@ -320,7 +320,7 @@ class Mapper(object):
     """
 
     single = None
-    """Represent ``True`` if this :class:`.Mapper` is a single table 
+    """Represent ``True`` if this :class:`.Mapper` is a single table
     inheritance mapper.
 
     :attr:`~.Mapper.local_table` will be ``None`` if this flag is set.
@@ -331,8 +331,8 @@ class Mapper(object):
     """
 
     non_primary = None
-    """Represent ``True`` if this :class:`.Mapper` is a "non-primary" 
-    mapper, e.g. a mapper that is used only to selet rows but not for 
+    """Represent ``True`` if this :class:`.Mapper` is a "non-primary"
+    mapper, e.g. a mapper that is used only to selet rows but not for
     persistence management.
 
     This is a *read only* attribute determined during mapper construction.
@@ -356,10 +356,10 @@ class Mapper(object):
     """A mapping of "polymorphic identity" identifiers mapped to :class:`.Mapper`
     instances, within an inheritance scenario.
 
-    The identifiers can be of any type which is comparable to the 
+    The identifiers can be of any type which is comparable to the
     type of column represented by :attr:`~.Mapper.polymorphic_on`.
 
-    An inheritance chain of mappers will all reference the same 
+    An inheritance chain of mappers will all reference the same
     polymorphic map object.  The object is used to correlate incoming
     result rows to target mappers.
 
@@ -394,10 +394,10 @@ class Mapper(object):
     """
 
     columns = None
-    """A collection of :class:`.Column` or other scalar expression 
+    """A collection of :class:`.Column` or other scalar expression
     objects maintained by this :class:`.Mapper`.
 
-    The collection behaves the same as that of the ``c`` attribute on 
+    The collection behaves the same as that of the ``c`` attribute on
     any :class:`.Table` object, except that only those columns included in
     this mapping are present, and are keyed based on the attribute name
     defined in the mapping, not necessarily the ``key`` attribute of the
@@ -411,11 +411,11 @@ class Mapper(object):
 
     validators = None
     """An immutable dictionary of attributes which have been decorated
-    using the :func:`~.orm.validates` decorator.    
-    
+    using the :func:`~.orm.validates` decorator.
+
     The dictionary contains string attribute names as keys
     mapped to the actual validation method.
-    
+
     """
 
     c = None
@@ -439,13 +439,13 @@ class Mapper(object):
                 self.inherits = class_mapper(self.inherits, compile=False)
             if not issubclass(self.class_, self.inherits.class_):
                 raise sa_exc.ArgumentError(
-                        "Class '%s' does not inherit from '%s'" % 
+                        "Class '%s' does not inherit from '%s'" %
                         (self.class_.__name__, self.inherits.class_.__name__))
             if self.non_primary != self.inherits.non_primary:
                 np = not self.non_primary and "primary" or "non-primary"
                 raise sa_exc.ArgumentError(
                         "Inheritance of %s mapper for class '%s' is "
-                        "only allowed from a %s mapper" % 
+                        "only allowed from a %s mapper" %
                         (np, self.class_.__name__, np))
             # inherit_condition is optional.
             if self.local_table is None:
@@ -468,7 +468,7 @@ class Mapper(object):
                                                     self.inherits.local_table,
                                                     self.local_table)
                     self.mapped_table = sql.join(
-                                                self.inherits.mapped_table, 
+                                                self.inherits.mapped_table,
                                                 self.local_table,
                                                 self.inherit_condition)
 
@@ -495,7 +495,7 @@ class Mapper(object):
                     "the inherited versioning column. "
                     "version_id_col should only be specified on "
                     "the base-most mapper that includes versioning." %
-                    (self.version_id_col.description, 
+                    (self.version_id_col.description,
                     self.inherits.version_id_col.description)
                 )
 
@@ -524,7 +524,7 @@ class Mapper(object):
 
         if self.mapped_table is None:
             raise sa_exc.ArgumentError(
-                    "Mapper '%s' does not have a mapped_table specified." 
+                    "Mapper '%s' does not have a mapped_table specified."
                     % self)
 
     def _set_with_polymorphic(self, with_polymorphic):
@@ -586,7 +586,7 @@ class Mapper(object):
         if self.inherits:
             self.dispatch._update(self.inherits.dispatch)
             super_extensions = set(
-                        chain(*[m._deprecated_extensions 
+                        chain(*[m._deprecated_extensions
                         for m in self.inherits.iterate_to_root()]))
         else:
             super_extensions = set()
@@ -598,7 +598,7 @@ class Mapper(object):
     def _configure_listeners(self):
         if self.inherits:
             super_extensions = set(
-                        chain(*[m._deprecated_extensions 
+                        chain(*[m._deprecated_extensions
                         for m in self.inherits.iterate_to_root()]))
         else:
             super_extensions = set()
@@ -645,8 +645,8 @@ class Mapper(object):
                     "remove *all* current mappers from all classes." %
                     self.class_)
             #else:
-                # a ClassManager may already exist as 
-                # ClassManager.instrument_attribute() creates 
+                # a ClassManager may already exist as
+                # ClassManager.instrument_attribute() creates
                 # new managers for each subclass if they don't yet exist.
 
         _mapper_registry[self] = True
@@ -660,8 +660,8 @@ class Mapper(object):
 
         manager.mapper = self
         manager.deferred_scalar_loader = self._load_scalar_attributes
-        # The remaining members can be added by any mapper, 
+
+        # The remaining members can be added by any mapper,
         # e_name None or not.
         if manager.info.get(_INSTRUMENTOR, False):
             return
@@ -745,10 +745,10 @@ class Mapper(object):
         self._readonly_props = set(
             self._columntoproperty[col]
             for col in self._columntoproperty
-            if not hasattr(col, 'table') or 
+            if not hasattr(col, 'table') or
             col.table not in self._cols_by_table)
 
-        # if explicit PK argument sent, add those columns to the 
+        # if explicit PK argument sent, add those columns to the
         # primary key mappings
         if self._primary_key_argument:
             for k in self._primary_key_argument:
@@ -761,23 +761,23 @@ class Mapper(object):
                     len(self._pks_by_table[self.mapped_table]) == 0:
                 raise sa_exc.ArgumentError(
                         "Mapper %s could not assemble any primary "
-                        "key columns for mapped table '%s'" % 
+                        "key columns for mapped table '%s'" %
                         (self, self.mapped_table.description))
         elif self.local_table not in self._pks_by_table and \
             isinstance(self.local_table, schema.Table):
             util.warn("Could not assemble any primary "
                         "keys for locally mapped table '%s' - "
-                        "no rows will be persisted in this Table." 
+                        "no rows will be persisted in this Table."
                         % self.local_table.description)
 
         if self.inherits and \
                 not self.concrete and \
                 not self._primary_key_argument:
-            # if inheriting, the "primary key" for this mapper is 
+            # if inheriting, the "primary key" for this mapper is
             # that of the inheriting (unless concrete or explicit)
             self.primary_key = self.inherits.primary_key
         else:
-            # determine primary key from argument or mapped_table pks - 
+            # determine primary key from argument or mapped_table pks -
             # reduce to the minimal set of columns
             if self._primary_key_argument:
                 primary_key = sql_util.reduce_columns(
@@ -792,7 +792,7 @@ class Mapper(object):
             if len(primary_key) == 0:
                 raise sa_exc.ArgumentError(
                     "Mapper %s could not assemble any primary "
-                    "key columns for mapped table '%s'" % 
+                    "key columns for mapped table '%s'" %
                     (self, self.mapped_table.description))
 
             self.primary_key = tuple(primary_key)
@@ -844,19 +844,19 @@ class Mapper(object):
                 if column in mapper._columntoproperty:
                     column_key = mapper._columntoproperty[column].key
 
-            self._configure_property(column_key, 
-                                    column, 
-                                    init=False, 
+            self._configure_property(column_key,
+                                    column,
+                                    init=False,
                                     setparent=True)
 
     def _configure_polymorphic_setter(self, init=False):
-        """Configure an attribute on the mapper representing the 
-        'polymorphic_on' column, if applicable, and not 
+        """Configure an attribute on the mapper representing the
+        'polymorphic_on' column, if applicable, and not
         already generated by _configure_properties (which is typical).
 
         Also create a setter function which will assign this
         attribute to the value of the 'polymorphic_identity'
-        upon instance construction, also if applicable.  This 
+        upon instance construction, also if applicable.  This
         routine will run when an instance is created.
 
         """
@@ -905,15 +905,15 @@ class Mapper(object):
             else:
                 # polymorphic_on is a Column or SQL expression and doesn't
                 # appear to be mapped.
-                # this means it can be 1. only present in the with_polymorphic 
+                # this means it can be 1. only present in the with_polymorphic
                 # selectable or 2. a totally standalone SQL expression which we'd
                 # hope is compatible with this mapper's mapped_table
                 col = self.mapped_table.corresponding_column(self.polymorphic_on)
                 if col is None:
-                    # polymorphic_on doesn't derive from any column/expression 
+                    # polymorphic_on doesn't derive from any column/expression
                     # isn't present in the mapped table.
-                    # we will make a "hidden" ColumnProperty for it. 
-                    # Just check that if it's directly a schema.Column and we 
+                    # we will make a "hidden" ColumnProperty for it.
+                    # Just check that if it's directly a schema.Column and we
                     # have with_polymorphic, it's likely a user error if the
                     # schema.Column isn't represented somehow in either mapped_table or
                     # with_polymorphic.   Otherwise as of 0.7.4 we just go with it
@@ -931,11 +931,11 @@ class Mapper(object):
                             "loads will not function properly"
                                  % col.description)
                 else:
-                    # column/expression that polymorphic_on derives from 
+                    # column/expression that polymorphic_on derives from
                     # is present in our mapped table
                     # and is probably mapped, but polymorphic_on itself
-                    # is not.  This happens when 
-                    # the polymorphic_on is only directly present in the 
+                    # is not.  This happens when
+                    # the polymorphic_on is only directly present in the
                     # with_polymorphic selectable, as when use polymorphic_union.
                     # we'll make a separate ColumnProperty for it.
                     instrument = True
@@ -951,7 +951,7 @@ class Mapper(object):
                     key = col.key
 
                 self._configure_property(
-                                key, 
+                                key,
                                 properties.ColumnProperty(col, _instrument=instrument),
                                 init=init, setparent=True)
                 polymorphic_key = key
@@ -997,15 +997,15 @@ class Mapper(object):
             self._configure_property(key, prop, init=False, setparent=False)
         elif key not in self._props:
             self._configure_property(
-                            key, 
-                            properties.ConcreteInheritedProperty(), 
+                            key,
+                            properties.ConcreteInheritedProperty(),
                             init=init, setparent=True)
 
     def _configure_property(self, key, prop, init=True, setparent=True):
         self._log("_configure_property(%s, %s)", key, prop.__class__.__name__)
 
         if not isinstance(prop, MapperProperty):
-            # we were passed a Column or a list of Columns; 
+            # we were passed a Column or a list of Columns;
             # generate a properties.ColumnProperty
             columns = util.to_list(prop)
             column = columns[0]
@@ -1025,7 +1025,7 @@ class Mapper(object):
                             "explicitly."
                              % (prop.columns[-1], column, key))
 
-                # existing properties.ColumnProperty from an inheriting 
+                # existing properties.ColumnProperty from an inheriting
                 # mapper. make a copy and append our column to it
                 prop = prop.copy()
                 prop.columns.insert(0, column)
@@ -1064,14 +1064,14 @@ class Mapper(object):
                     "(including its availability as a foreign key), "
                     "use the 'include_properties' or 'exclude_properties' "
                     "mapper arguments to control specifically which table "
-                    "columns get mapped." % 
+                    "columns get mapped." %
                     (key, self, column.key, prop))
 
         if isinstance(prop, properties.ColumnProperty):
             col = self.mapped_table.corresponding_column(prop.columns[0])
 
-            # if the column is not present in the mapped table, 
-            # test if a column has been added after the fact to the 
+            # if the column is not present in the mapped table,
+            # test if a column has been added after the fact to the
             # parent table (or their parent, etc.) [ticket:1570]
             if col is None and self.inherits:
                 path = [self]
@@ -1085,20 +1085,20 @@ class Mapper(object):
                         break
                     path.append(m)
 
-            # subquery expression, column not present in the mapped 
+            # subquery expression, column not present in the mapped
             # selectable.
             if col is None:
                 col = prop.columns[0]
 
-                # column is coming in after _readonly_props was 
+                # column is coming in after _readonly_props was
                 # initialized; check for 'readonly'
                 if hasattr(self, '_readonly_props') and \
-                    (not hasattr(col, 'table') or 
+                    (not hasattr(col, 'table') or
                     col.table not in self._cols_by_table):
                         self._readonly_props.add(prop)
 
             else:
-                # if column is coming in after _cols_by_table was 
+                # if column is coming in after _cols_by_table was
                 # initialized, ensure the col is in the right set
                 if hasattr(self, '_cols_by_table') and \
                                     col.table in self._cols_by_table and \
@@ -1198,10 +1198,10 @@ class Mapper(object):
     def _log_desc(self):
         return "(" + self.class_.__name__ + \
             "|" + \
-            (self.local_table is not None and 
-                self.local_table.description or 
+            (self.local_table is not None and
+                self.local_table.description or
                 str(self.local_table)) +\
-            (self.non_primary and 
+            (self.non_primary and
             "|non-primary" or "") + ")"
 
     def _log(self, msg, *args):
@@ -1222,7 +1222,7 @@ class Mapper(object):
     def __str__(self):
         return "Mapper|%s|%s%s" % (
             self.class_.__name__,
-            self.local_table is not None and 
+            self.local_table is not None and
             self.local_table.description or None,
             self.non_primary and "|non-primary" or ""
         )
@@ -1281,7 +1281,7 @@ class Mapper(object):
             for m in mappers:
                 if not m.isa(self):
                     raise sa_exc.InvalidRequestError(
-                                "%r does not inherit from %r"  % 
+                                "%r does not inherit from %r"  %
                                 (m, self))
         else:
             mappers = []
@@ -1347,7 +1347,7 @@ class Mapper(object):
                             self._mappers_from_spec(spec, selectable),
                             False)
 
-    def _with_polymorphic_args(self, spec=None, selectable=False, 
+    def _with_polymorphic_args(self, spec=None, selectable=False,
                                 innerjoin=False):
         if self.with_polymorphic:
             if not spec:
@@ -1360,7 +1360,7 @@ class Mapper(object):
         if selectable is not None:
             return mappers, selectable
         else:
-            return mappers, self._selectable_from_mappers(mappers, 
+            return mappers, self._selectable_from_mappers(mappers,
                                 innerjoin)
 
     @_memoized_configured_property
@@ -1387,7 +1387,7 @@ class Mapper(object):
                         mappers])
             ):
                 if getattr(c, '_is_polymorphic_discriminator', False) and \
-                        (self.polymorphic_on is None or 
+                        (self.polymorphic_on is None or
                         c.columns[0] is not self.polymorphic_on):
                         continue
                 yield c
@@ -1475,7 +1475,7 @@ class Mapper(object):
         return result
 
     def _is_userland_descriptor(self, obj):
-        if isinstance(obj, (MapperProperty, 
+        if isinstance(obj, (MapperProperty,
                             attributes.QueryableAttribute)):
             return False
         elif not hasattr(obj, '__get__'):
@@ -1528,7 +1528,7 @@ class Mapper(object):
         return False
 
     def common_parent(self, other):
-        """Return true if the given mapper shares a 
+        """Return true if the given mapper shares a
         common inherited parent as this mapper."""
 
         return self.base_mapper is other.base_mapper
@@ -1657,7 +1657,7 @@ class Mapper(object):
             for col in self.primary_key
         ]
 
-    def _get_state_attr_by_column(self, state, dict_, column, 
+    def _get_state_attr_by_column(self, state, dict_, column,
                                     passive=attributes.PASSIVE_OFF):
         prop = self._columntoproperty[column]
         return state.manager[prop.key].impl.get(state, dict_, passive=passive)
@@ -1671,7 +1671,7 @@ class Mapper(object):
         dict_ = attributes.instance_dict(obj)
         return self._get_committed_state_attr_by_column(state, dict_, column)
 
-    def _get_committed_state_attr_by_column(self, state, dict_, 
+    def _get_committed_state_attr_by_column(self, state, dict_,
                         column, passive=attributes.PASSIVE_OFF):
 
         prop = self._columntoproperty[column]
@@ -1698,8 +1698,8 @@ class Mapper(object):
             if statement is not None:
                 result = loading.load_on_ident(
                             session.query(self).from_statement(statement),
-                                None, 
-                                only_load_props=attribute_names, 
+                                None,
+                                only_load_props=attribute_names,
                                 refresh_state=state
                             )
 
@@ -1723,17 +1723,17 @@ class Mapper(object):
                     _none_set.issuperset(identity_key):
                 util.warn("Instance %s to be refreshed doesn't "
                             "contain a full primary key - can't be refreshed "
-                            "(and shouldn't be expired, either)." 
+                            "(and shouldn't be expired, either)."
                             % state_str(state))
                 return
 
             result = loading.load_on_ident(
                         session.query(self),
-                                    identity_key, 
-                                    refresh_state=state, 
+                                    identity_key,
+                                    refresh_state=state,
                                     only_load_props=attribute_names)
 
-        # if instance is pending, a refresh operation 
+        # if instance is pending, a refresh operation
         # may not complete (even if PK attributes are assigned)
         if has_key and result is None:
             raise orm_exc.ObjectDeletedError(state)
@@ -1742,16 +1742,16 @@ class Mapper(object):
         """assemble a WHERE clause which retrieves a given state by primary
         key, using a minimized set of tables.
 
-        Applies to a joined-table inheritance mapper where the 
+        Applies to a joined-table inheritance mapper where the
         requested attribute names are only present on joined tables,
-        not the base table.  The WHERE clause attempts to include 
+        not the base table.  The WHERE clause attempts to include
         only those tables to minimize joins.
 
         """
         props = self._props
 
         tables = set(chain(
-                        *[sql_util.find_tables(c, check_columns=True) 
+                        *[sql_util.find_tables(c, check_columns=True)
                         for key in attribute_names
                         for c in props[key].columns]
                     ))
@@ -1770,8 +1770,8 @@ class Mapper(object):
 
             if leftcol.table not in tables:
                 leftval = self._get_committed_state_attr_by_column(
-                                    state, state.dict, 
-                                    leftcol, 
+                                    state, state.dict,
+                                    leftcol,
                                     passive=attributes.PASSIVE_NO_INITIALIZE)
                 if leftval is attributes.PASSIVE_NO_RESULT or leftval is None:
                     raise ColumnsNotAvailable()
@@ -1779,8 +1779,8 @@ class Mapper(object):
                                             type_=binary.right.type)
             elif rightcol.table not in tables:
                 rightval = self._get_committed_state_attr_by_column(
-                                    state, state.dict, 
-                                    rightcol, 
+                                    state, state.dict,
+                                    rightcol,
                                     passive=attributes.PASSIVE_NO_INITIALIZE)
                 if rightval is attributes.PASSIVE_NO_RESULT or rightval is None:
                     raise ColumnsNotAvailable()
@@ -1796,8 +1796,8 @@ class Mapper(object):
                     start = True
                 if start and not mapper.single:
                     allconds.append(visitors.cloned_traverse(
-                                                mapper.inherit_condition, 
-                                                {}, 
+                                                mapper.inherit_condition,
+                                                {},
                                                 {'binary':visit_binary}
                                         )
                                     )
@@ -1830,7 +1830,7 @@ class Mapper(object):
         visited_states = set()
         prp, mpp = object(), object()
 
-        visitables = deque([(deque(self._props.values()), prp, 
+        visitables = deque([(deque(self._props.values()), prp,
                                 state, state.dict)])
 
         while visitables:
@@ -1843,7 +1843,7 @@ class Mapper(object):
                 prop = iterator.popleft()
                 if type_ not in prop.cascade:
                     continue
-                queue = deque(prop.cascade_iterator(type_, parent_state, 
+                queue = deque(prop.cascade_iterator(type_, parent_state,
                             parent_dict, visited_states, halt_on))
                 if queue:
                     visitables.append((queue,mpp, None, None))
@@ -1852,8 +1852,8 @@ class Mapper(object):
                                 corresponding_dict = iterator.popleft()
                 yield instance, instance_mapper, \
                         corresponding_state, corresponding_dict
-                visitables.append((deque(instance_mapper._props.values()), 
-                                        prp, corresponding_state, 
+                visitables.append((deque(instance_mapper._props.values()),
+                                        prp, corresponding_state,
                                         corresponding_dict))
 
     @_memoized_configured_property
@@ -1865,9 +1865,31 @@ class Mapper(object):
         table_to_mapper = {}
         for mapper in self.base_mapper.self_and_descendants:
             for t in mapper.tables:
-                table_to_mapper[t] = mapper
+                table_to_mapper.setdefault(t, mapper)
+
+        def skip(fk):
+            # attempt to skip dependencies that are not
+            # significant to the inheritance chain
+            # for two tables that are related by inheritance.
+            # while that dependency may be important, it's techinically
+            # not what we mean to sort on here.
+            parent = table_to_mapper.get(fk.parent.table)
+            dep = table_to_mapper.get(fk.column.table)
+            if parent is not None and \
+                dep is not None and \
+                dep is not parent and \
+                dep.inherit_condition is not None:
+                cols = set(sql_util.find_columns(dep.inherit_condition))
+                if parent.inherit_condition is not None:
+                    cols = cols.union(sql_util.find_columns(
+                                        parent.inherit_condition))
+                    return fk.parent not in cols and fk.column not in cols
+                else:
+                    return fk.parent not in cols
+            return False
 
-        sorted_ = sql_util.sort_tables(table_to_mapper.iterkeys())
+        sorted_ = sql_util.sort_tables(table_to_mapper.iterkeys(),
+                                    skip_fn=skip)
         ret = util.OrderedDict()
         for t in sorted_:
             ret[t] = table_to_mapper[t]
@@ -1910,7 +1932,7 @@ class Mapper(object):
 
     @util.memoized_property
     def _table_to_equated(self):
-        """memoized map of tables to collections of columns to be 
+        """memoized map of tables to collections of columns to be
         synchronized upwards to the base mapper."""
 
         result = util.defaultdict(list)
@@ -1931,14 +1953,14 @@ def configure_mappers():
     """Initialize the inter-mapper relationships of all mappers that
     have been constructed thus far.
 
-    This function can be called any number of times, but in 
+    This function can be called any number of times, but in
     most cases is handled internally.
 
     """
 
     global _new_mappers
     if not _new_mappers:
-        return 
+        return
 
     _call_configured = None
     _COMPILE_MUTEX.acquire()
@@ -1954,8 +1976,8 @@ def configure_mappers():
                 return
 
             # initialize properties on all mappers
-            # note that _mapper_registry is unordered, which 
-            # may randomly conceal/reveal issues related to 
+            # note that _mapper_registry is unordered, which
+            # may randomly conceal/reveal issues related to
             # the order of mapper compilation
             for mapper in list(_mapper_registry):
                 if getattr(mapper, '_configure_failed', False):
@@ -2022,7 +2044,7 @@ def validates(*names, **kw):
     condition which is not supported.
 
     :param \*names: list of attribute names to be validated.
-    :param include_removes: if True, "remove" events will be 
+    :param include_removes: if True, "remove" events will be
      sent as well - the validation function must accept an additional
      argument "is_remove" which will be a boolean.
 
@@ -2043,7 +2065,7 @@ def _event_on_load(state, ctx):
 
 def _event_on_first_init(manager, cls):
     """Initial mapper compilation trigger.
-    
+
     instrumentation calls this one when InstanceState
     is first generated, and is needed for legacy mutable
     attributes to work.
@@ -2056,11 +2078,11 @@ def _event_on_first_init(manager, cls):
 
 def _event_on_init(state, args, kwargs):
     """Run init_instance hooks.
-    
+
     This also includes mapper compilation, normally not needed
     here but helps with some piecemeal configuration
     scenarios (such as in the ORM tutorial).
-    
+
     """
 
     instrumenting_mapper = state.manager.info.get(_INSTRUMENTOR)
index 83a75a77f54406d9c9f036b8b524c12242ea1d15..b0818b8910e1f5c76ddec68403819e8cb0d84d2d 100644 (file)
@@ -12,7 +12,7 @@ from collections import deque
 
 """Utility functions that build upon SQL and Schema constructs."""
 
-def sort_tables(tables):
+def sort_tables(tables, skip_fn=None):
     """sort a collection of Table objects in order of their foreign-key dependency."""
 
     tables = list(tables)
@@ -20,6 +20,8 @@ def sort_tables(tables):
     def visit_foreign_key(fkey):
         if fkey.use_alter:
             return
+        elif skip_fn and skip_fn(fkey):
+            return
         parent_table = fkey.column.table
         if parent_table in tables:
             child_table = fkey.parent.table
@@ -27,8 +29,8 @@ def sort_tables(tables):
                 tuples.append((parent_table, child_table))
 
     for table in tables:
-        visitors.traverse(table, 
-                            {'schema_visitor':True}, 
+        visitors.traverse(table,
+                            {'schema_visitor':True},
                             {'foreign_key':visit_foreign_key})
 
         tuples.extend(
@@ -38,9 +40,9 @@ def sort_tables(tables):
     return list(topological.sort(tuples, tables))
 
 def find_join_source(clauses, join_to):
-    """Given a list of FROM clauses and a selectable, 
-    return the first index and element from the list of 
-    clauses which can be joined against the selectable.  returns 
+    """Given a list of FROM clauses and a selectable,
+    return the first index and element from the list of
+    clauses which can be joined against the selectable.  returns
     None, None if no match is found.
 
     e.g.::
@@ -66,25 +68,25 @@ def find_join_source(clauses, join_to):
 def visit_binary_product(fn, expr):
     """Produce a traversal of the given expression, delivering
     column comparisons to the given function.
-    
+
     The function is of the form::
-    
+
         def my_fn(binary, left, right)
-    
-    For each binary expression located which has a 
+
+    For each binary expression located which has a
     comparison operator, the product of "left" and
     "right" will be delivered to that function,
     in terms of that binary.
-    
+
     Hence an expression like::
-    
+
         and_(
             (a + b) == q + func.sum(e + f),
             j == r
         )
-    
+
     would have the traversal::
-    
+
         a <eq> q
         a <eq> e
         a <eq> f
@@ -96,7 +98,7 @@ def visit_binary_product(fn, expr):
     That is, every combination of "left" and
     "right" that doesn't further contain
     a binary comparison is passed as pairs.
-    
+
     """
     stack = []
     def visit(element):
@@ -121,8 +123,8 @@ def visit_binary_product(fn, expr):
                     yield e
     list(visit(expr))
 
-def find_tables(clause, check_columns=False, 
-                include_aliases=False, include_joins=False, 
+def find_tables(clause, check_columns=False,
+                include_aliases=False, include_joins=False,
                 include_selects=False, include_crud=False):
     """locate Table objects within the given expression."""
 
@@ -171,7 +173,7 @@ def unwrap_order_by(clause):
             (
                 not isinstance(t, expression._UnaryExpression) or \
                 not operators.is_ordering_modifier(t.modifier)
-            ): 
+            ):
             cols.add(t)
         else:
             for c in t.get_children():
@@ -226,7 +228,7 @@ def _quote_ddl_expr(element):
 class _repr_params(object):
     """A string view of bound parameters, truncating
     display to the given number of 'multi' parameter sets.
-    
+
     """
     def __init__(self, params, batches):
         self.params = params
@@ -246,7 +248,7 @@ class _repr_params(object):
 
 
 def expression_as_ddl(clause):
-    """Given a SQL expression, convert for usage in DDL, such as 
+    """Given a SQL expression, convert for usage in DDL, such as
      CREATE INDEX and CHECK CONSTRAINT.
 
      Converts bind params into quoted literals, column identifiers
@@ -285,7 +287,7 @@ def adapt_criterion_to_null(crit, nulls):
     return visitors.cloned_traverse(crit, {}, {'binary':visit_binary})
 
 
-def join_condition(a, b, ignore_nonexistent_tables=False, 
+def join_condition(a, b, ignore_nonexistent_tables=False,
                             a_subset=None,
                             consider_as_foreign_keys=None):
     """create a join condition between two tables or selectables.
@@ -321,7 +323,7 @@ def join_condition(a, b, ignore_nonexistent_tables=False,
         if left is None:
             continue
         for fk in sorted(
-                    b.foreign_keys, 
+                    b.foreign_keys,
                     key=lambda fk:fk.parent._creation_order):
             if consider_as_foreign_keys is not None and \
                 fk.parent not in consider_as_foreign_keys:
@@ -339,7 +341,7 @@ def join_condition(a, b, ignore_nonexistent_tables=False,
                 constraints.add(fk.constraint)
         if left is not b:
             for fk in sorted(
-                        left.foreign_keys, 
+                        left.foreign_keys,
                         key=lambda fk:fk.parent._creation_order):
                 if consider_as_foreign_keys is not None and \
                     fk.parent not in consider_as_foreign_keys:
@@ -385,12 +387,12 @@ def join_condition(a, b, ignore_nonexistent_tables=False,
 class Annotated(object):
     """clones a ClauseElement and applies an 'annotations' dictionary.
 
-    Unlike regular clones, this clone also mimics __hash__() and 
+    Unlike regular clones, this clone also mimics __hash__() and
     __cmp__() of the original element so that it takes its place
     in hashed collections.
 
     A reference to the original element is maintained, for the important
-    reason of keeping its hash value current.  When GC'ed, the 
+    reason of keeping its hash value current.  When GC'ed, the
     hash value may be reused, causing conflicts.
 
     """
@@ -406,13 +408,13 @@ class Annotated(object):
             try:
                 cls = annotated_classes[element.__class__]
             except KeyError:
-                cls = annotated_classes[element.__class__] = type.__new__(type, 
-                        "Annotated%s" % element.__class__.__name__, 
+                cls = annotated_classes[element.__class__] = type.__new__(type,
+                        "Annotated%s" % element.__class__.__name__,
                         (Annotated, element.__class__), {})
             return object.__new__(cls)
 
     def __init__(self, element, values):
-        # force FromClause to generate their internal 
+        # force FromClause to generate their internal
         # collections into __dict__
         if isinstance(element, expression.FromClause):
             element.c
@@ -481,7 +483,7 @@ for cls in expression.__dict__.values() + [schema.Column, schema.Table]:
         exec "annotated_classes[cls] = Annotated%s" % (cls.__name__)
 
 def _deep_annotate(element, annotations, exclude=None):
-    """Deep copy the given ClauseElement, annotating each element 
+    """Deep copy the given ClauseElement, annotating each element
     with the given annotations dictionary.
 
     Elements within the exclude collection will be cloned but not annotated.
@@ -528,17 +530,17 @@ def _deep_deannotate(element, values=None):
         element = clone(element)
     return element
 
-def _shallow_annotate(element, annotations): 
-    """Annotate the given ClauseElement and copy its internals so that 
-    internal objects refer to the new annotated object. 
+def _shallow_annotate(element, annotations):
+    """Annotate the given ClauseElement and copy its internals so that
+    internal objects refer to the new annotated object.
 
-    Basically used to apply a "dont traverse" annotation to a  
-    selectable, without digging throughout the whole 
-    structure wasting time. 
-    """ 
-    element = element._annotate(annotations) 
-    element._copy_internals() 
-    return element 
+    Basically used to apply a "dont traverse" annotation to a
+    selectable, without digging throughout the whole
+    structure wasting time.
+    """
+    element = element._annotate(annotations)
+    element._copy_internals()
+    return element
 
 def splice_joins(left, right, stop_on=None):
     if left is None:
@@ -626,7 +628,7 @@ def reduce_columns(columns, *clauses, **kw):
 
     return expression.ColumnSet(columns.difference(omit))
 
-def criterion_as_pairs(expression, consider_as_foreign_keys=None, 
+def criterion_as_pairs(expression, consider_as_foreign_keys=None,
                         consider_as_referenced_keys=None, any_operator=False):
     """traverse an expression and locate binary criterion pairs."""
 
@@ -648,20 +650,20 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None,
 
         if consider_as_foreign_keys:
             if binary.left in consider_as_foreign_keys and \
-                        (col_is(binary.right, binary.left) or 
+                        (col_is(binary.right, binary.left) or
                         binary.right not in consider_as_foreign_keys):
                 pairs.append((binary.right, binary.left))
             elif binary.right in consider_as_foreign_keys and \
-                        (col_is(binary.left, binary.right) or 
+                        (col_is(binary.left, binary.right) or
                         binary.left not in consider_as_foreign_keys):
                 pairs.append((binary.left, binary.right))
         elif consider_as_referenced_keys:
             if binary.left in consider_as_referenced_keys and \
-                        (col_is(binary.right, binary.left) or 
+                        (col_is(binary.right, binary.left) or
                         binary.right not in consider_as_referenced_keys):
                 pairs.append((binary.left, binary.right))
             elif binary.right in consider_as_referenced_keys and \
-                        (col_is(binary.left, binary.right) or 
+                        (col_is(binary.left, binary.right) or
                         binary.left not in consider_as_referenced_keys):
                 pairs.append((binary.right, binary.left))
         else:
@@ -678,17 +680,17 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None,
 def folded_equivalents(join, equivs=None):
     """Return a list of uniquely named columns.
 
-    The column list of the given Join will be narrowed 
+    The column list of the given Join will be narrowed
     down to a list of all equivalently-named,
     equated columns folded into one column, where 'equated' means they are
     equated to each other in the ON clause of this join.
 
     This function is used by Join.select(fold_equivalents=True).
 
-    Deprecated.   This function is used for a certain kind of 
+    Deprecated.   This function is used for a certain kind of
     "polymorphic_union" which is designed to achieve joined
     table inheritance where the base table has no "discriminator"
-    column; [ticket:1131] will provide a better way to 
+    column; [ticket:1131] will provide a better way to
     achieve this.
 
     """
@@ -773,9 +775,9 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
       s.c.col1 == table2.c.col1
 
     """
-    def __init__(self, selectable, equivalents=None, 
-                        include=None, exclude=None, 
-                        include_fn=None, exclude_fn=None, 
+    def __init__(self, selectable, equivalents=None,
+                        include=None, exclude=None,
+                        include_fn=None, exclude_fn=None,
                         adapt_on_names=False):
         self.__traverse_options__ = {'stop_on':[selectable]}
         self.selectable = selectable
@@ -794,12 +796,12 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
 
     def _corresponding_column(self, col, require_embedded, _seen=util.EMPTY_SET):
         newcol = self.selectable.corresponding_column(
-                                    col, 
+                                    col,
                                     require_embedded=require_embedded)
         if newcol is None and col in self.equivalents and col not in _seen:
             for equiv in self.equivalents[col]:
-                newcol = self._corresponding_column(equiv, 
-                                require_embedded=require_embedded, 
+                newcol = self._corresponding_column(equiv,
+                                require_embedded=require_embedded,
                                 _seen=_seen.union([col]))
                 if newcol is not None:
                     return newcol
@@ -823,14 +825,14 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
 class ColumnAdapter(ClauseAdapter):
     """Extends ClauseAdapter with extra utility functions.
 
-    Provides the ability to "wrap" this ClauseAdapter 
+    Provides the ability to "wrap" this ClauseAdapter
     around another, a columns dictionary which returns
-    adapted elements given an original, and an 
+    adapted elements given an original, and an
     adapted_row() factory.
 
     """
-    def __init__(self, selectable, equivalents=None, 
-                        chain_to=None, include=None, 
+    def __init__(self, selectable, equivalents=None,
+                        chain_to=None, include=None,
                         exclude=None, adapt_required=False):
         ClauseAdapter.__init__(self, selectable, equivalents, include, exclude)
         if chain_to:
@@ -866,7 +868,7 @@ class ColumnAdapter(ClauseAdapter):
                 c = c.label(None)
 
         # adapt_required indicates that if we got the same column
-        # back which we put in (i.e. it passed through), 
+        # back which we put in (i.e. it passed through),
         # it's not correct.  this is used by eagerloading which
         # knows that all columns and expressions need to be adapted
         # to a result row, and a "passthrough" is definitely targeting
index 271dd49597edc90156b0210f8f4a565fd4894f4d..0e647a66e3249249e2da65e0ad756b40efdaa26e 100644 (file)
@@ -10,6 +10,7 @@ from test.lib import testing, engines
 from test.lib import fixtures
 from test.orm import _fixtures
 from test.lib.schema import Table, Column
+from sqlalchemy.ext.declarative import declarative_base
 
 class O2MTest(fixtures.MappedTest):
     """deals with inheritance and one-to-many relationships"""
@@ -75,15 +76,15 @@ class O2MTest(fixtures.MappedTest):
 class PolymorphicOnNotLocalTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
-        t1 = Table('t1', metadata, 
+        t1 = Table('t1', metadata,
                 Column('id', Integer, primary_key=True,
-                            test_needs_autoincrement=True), 
+                            test_needs_autoincrement=True),
                 Column('x', String(10)),
                 Column('q', String(10)))
-        t2 = Table('t2', metadata, 
+        t2 = Table('t2', metadata,
                 Column('id', Integer, primary_key=True,
-                            test_needs_autoincrement=True), 
-                Column('y', String(10)), 
+                            test_needs_autoincrement=True),
+                Column('y', String(10)),
                 Column('xid', ForeignKey('t1.id')))
 
     @classmethod
@@ -181,7 +182,7 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
             "discriminator":column_property(expr)
         }, polymorphic_identity="parent",
             polymorphic_on=expr)
-        mapper(Child, t2, inherits=Parent, 
+        mapper(Child, t2, inherits=Parent,
                 polymorphic_identity="child")
 
         self._roundtrip(parent_ident='p', child_ident='c')
@@ -213,7 +214,7 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
         self._roundtrip(parent_ident='p', child_ident='c')
 
     def test_polymorphic_on_expr_implicit_map_no_label_single(self):
-        """test that single_table_criterion is propagated 
+        """test that single_table_criterion is propagated
         with a standalone expr"""
         t2, t1 = self.tables.t2, self.tables.t1
         Parent, Child = self.classes.Parent, self.classes.Child
@@ -228,7 +229,7 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
         self._roundtrip(parent_ident='p', child_ident='c')
 
     def test_polymorphic_on_expr_implicit_map_w_label_single(self):
-        """test that single_table_criterion is propagated 
+        """test that single_table_criterion is propagated
         with a standalone expr"""
         t2, t1 = self.tables.t2, self.tables.t1
         Parent, Child = self.classes.Parent, self.classes.Child
@@ -254,7 +255,7 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
             "discriminator":cprop
         }, polymorphic_identity="parent",
             polymorphic_on=cprop)
-        mapper(Child, t2, inherits=Parent, 
+        mapper(Child, t2, inherits=Parent,
                 polymorphic_identity="child")
 
         self._roundtrip(parent_ident='p', child_ident='c')
@@ -271,7 +272,7 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
             "discriminator":cprop
         }, polymorphic_identity="parent",
             polymorphic_on="discriminator")
-        mapper(Child, t2, inherits=Parent, 
+        mapper(Child, t2, inherits=Parent,
                 polymorphic_identity="child")
 
         self._roundtrip(parent_ident='p', child_ident='c')
@@ -323,13 +324,49 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
             [Child]
         )
 
+class SortOnlyOnImportantFKsTest(fixtures.MappedTest):
+    @classmethod
+    def define_tables(cls, metadata):
+        Table('a', metadata,
+                Column('id', Integer, primary_key=True),
+                Column('b_id', Integer,
+                        ForeignKey('b.id', use_alter=True, name='b'))
+            )
+        Table('b', metadata,
+            Column('id', Integer, ForeignKey('a.id'), primary_key=True)
+            )
+
+    @classmethod
+    def setup_classes(cls):
+        Base = declarative_base()
+
+        class A(Base):
+            __tablename__ = "a"
+
+            id = Column(Integer, primary_key=True)
+            b_id = Column(Integer, ForeignKey('b.id'))
+
+        class B(A):
+            __tablename__ = "b"
+
+            id = Column(Integer, ForeignKey('a.id'), primary_key=True)
+
+            __mapper_args__ = {'inherit_condition': id == A.id}
+
+        cls.classes.A = A
+        cls.classes.B = B
+
+    def test_flush(self):
+        s = Session(testing.db)
+        s.add(self.classes.B())
+        s.flush()
 
 class FalseDiscriminatorTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         global t1
-        t1 = Table('t1', metadata, 
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True), 
+        t1 = Table('t1', metadata,
+            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
             Column('type', Boolean, nullable=False))
 
     def test_false_on_sub(self):
@@ -405,12 +442,12 @@ class PolymorphicAttributeManagementTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table('table_a', metadata,
-            Column('id', Integer, primary_key=True, 
+            Column('id', Integer, primary_key=True,
                                 test_needs_autoincrement=True),
             Column('class_name', String(50))
         )
         Table('table_b', metadata,
-           Column('id', Integer, ForeignKey('table_a.id'), 
+           Column('id', Integer, ForeignKey('table_a.id'),
                                 primary_key=True),
            Column('class_name', String(50))
         )
@@ -432,13 +469,13 @@ class PolymorphicAttributeManagementTest(fixtures.MappedTest):
         class C(B):
             pass
 
-        mapper(A, table_a, 
-                        polymorphic_on=table_a.c.class_name, 
+        mapper(A, table_a,
+                        polymorphic_on=table_a.c.class_name,
                         polymorphic_identity='a')
-        mapper(B, table_b, inherits=A, 
-                        polymorphic_on=table_b.c.class_name, 
+        mapper(B, table_b, inherits=A,
+                        polymorphic_on=table_b.c.class_name,
                         polymorphic_identity='b')
-        mapper(C, table_c, inherits=B, 
+        mapper(C, table_c, inherits=B,
                         polymorphic_identity='c')
 
     def test_poly_configured_immediate(self):
@@ -489,25 +526,25 @@ class CascadeTest(fixtures.MappedTest):
     def define_tables(cls, metadata):
         global t1, t2, t3, t4
         t1= Table('t1', metadata,
-            Column('id', Integer, primary_key=True, 
+            Column('id', Integer, primary_key=True,
                                     test_needs_autoincrement=True),
             Column('data', String(30))
             )
 
         t2 = Table('t2', metadata,
-            Column('id', Integer, primary_key=True, 
+            Column('id', Integer, primary_key=True,
                                     test_needs_autoincrement=True),
             Column('t1id', Integer, ForeignKey('t1.id')),
             Column('type', String(30)),
             Column('data', String(30))
         )
         t3 = Table('t3', metadata,
-            Column('id', Integer, ForeignKey('t2.id'), 
+            Column('id', Integer, ForeignKey('t2.id'),
                                     primary_key=True),
             Column('moredata', String(30)))
 
         t4 = Table('t4', metadata,
-            Column('id', Integer, primary_key=True, 
+            Column('id', Integer, primary_key=True,
                                     test_needs_autoincrement=True),
             Column('t3id', Integer, ForeignKey('t3.id')),
             Column('data', String(30)))
@@ -757,7 +794,7 @@ class EagerLazyTest(fixtures.MappedTest):
         self.assert_(len(q.first().eager) == 1)
 
 class EagerTargetingTest(fixtures.MappedTest):
-    """test a scenario where joined table inheritance might be 
+    """test a scenario where joined table inheritance might be
     confused as an eagerly loaded joined table."""
 
     @classmethod
@@ -782,7 +819,7 @@ class EagerTargetingTest(fixtures.MappedTest):
         class B(A):
             pass
 
-        mapper(A, a_table, polymorphic_on=a_table.c.type, polymorphic_identity='A', 
+        mapper(A, a_table, polymorphic_on=a_table.c.type, polymorphic_identity='A',
                 properties={
                     'children': relationship(A, order_by=a_table.c.name)
             })
@@ -945,9 +982,9 @@ class VersioningTest(fixtures.MappedTest):
         class Stuff(Base):
             pass
         mapper(Stuff, stuff)
-        mapper(Base, base, 
-                    polymorphic_on=base.c.discriminator, 
-                    version_id_col=base.c.version_id, 
+        mapper(Base, base,
+                    polymorphic_on=base.c.discriminator,
+                    version_id_col=base.c.version_id,
                     polymorphic_identity=1, properties={
             'stuff':relationship(Stuff)
         })
@@ -971,7 +1008,7 @@ class VersioningTest(fixtures.MappedTest):
         sess.flush()
 
         assert_raises(orm_exc.StaleDataError,
-                        sess2.query(Base).with_lockmode('read').get, 
+                        sess2.query(Base).with_lockmode('read').get,
                         s1.id)
 
         if not testing.db.dialect.supports_sane_rowcount:
@@ -994,8 +1031,8 @@ class VersioningTest(fixtures.MappedTest):
         class Sub(Base):
             pass
 
-        mapper(Base, base, 
-                    polymorphic_on=base.c.discriminator, 
+        mapper(Base, base,
+                    polymorphic_on=base.c.discriminator,
                     version_id_col=base.c.version_id, polymorphic_identity=1)
         mapper(Sub, subtable, inherits=Base, polymorphic_identity=2)
 
@@ -1073,16 +1110,16 @@ class DistinctPKTest(fixtures.MappedTest):
     def test_explicit_props(self):
         person_mapper = mapper(Person, person_table)
         mapper(Employee, employee_table, inherits=person_mapper,
-                        properties={'pid':person_table.c.id, 
+                        properties={'pid':person_table.c.id,
                                     'eid':employee_table.c.id})
         self._do_test(False)
 
     def test_explicit_composite_pk(self):
         person_mapper = mapper(Person, person_table)
-        mapper(Employee, employee_table, 
-                    inherits=person_mapper, 
+        mapper(Employee, employee_table,
+                    inherits=person_mapper,
                     primary_key=[person_table.c.id, employee_table.c.id])
-        assert_raises_message(sa_exc.SAWarning, 
+        assert_raises_message(sa_exc.SAWarning,
                                     r"On mapper Mapper\|Employee\|employees, "
                                     "primary key column 'persons.id' is being "
                                     "combined with distinct primary key column 'employees.id' "
@@ -1188,7 +1225,7 @@ class OverrideColKeyTest(fixtures.MappedTest):
     def define_tables(cls, metadata):
         global base, subtable
 
-        base = Table('base', metadata, 
+        base = Table('base', metadata,
             Column('base_id', Integer, primary_key=True, test_needs_autoincrement=True),
             Column('data', String(255)),
             Column('sqlite_fixer', String(10))
@@ -1239,7 +1276,7 @@ class OverrideColKeyTest(fixtures.MappedTest):
             class_mapper(Sub).get_property('id').columns,
             [base.c.base_id, subtable.c.base_id]
         )
+
         s1 = Sub()
         s1.id = 10
         sess = create_session()
@@ -1412,7 +1449,7 @@ class OptimizedLoadTest(fixtures.MappedTest):
             Column('type', String(50)),
             Column('counter', Integer, server_default="1")
         )
-        Table('sub', metadata, 
+        Table('sub', metadata,
             Column('id', Integer, ForeignKey('base.id'), primary_key=True),
             Column('sub', String(50)),
             Column('counter', Integer, server_default="1"),
@@ -1429,7 +1466,7 @@ class OptimizedLoadTest(fixtures.MappedTest):
         )
 
     def test_optimized_passes(self):
-        """"test that the 'optimized load' routine doesn't crash when 
+        """"test that the 'optimized load' routine doesn't crash when
         a column in the join condition is not available."""
 
         base, sub = self.tables.base, self.tables.sub
@@ -1444,7 +1481,7 @@ class OptimizedLoadTest(fixtures.MappedTest):
 
         # redefine Sub's "id" to favor the "id" col in the subtable.
         # "id" is also part of the primary join condition
-        mapper(Sub, sub, inherits=Base, 
+        mapper(Sub, sub, inherits=Base,
                         polymorphic_identity='sub',
                         properties={'id':[sub.c.id, base.c.id]})
         sess = sessionmaker()()
@@ -1453,8 +1490,8 @@ class OptimizedLoadTest(fixtures.MappedTest):
         sess.commit()
         sess.expunge_all()
 
-        # load s1 via Base.  s1.id won't populate since it's relative to 
-        # the "sub" table.  The optimized load kicks in and tries to 
+        # load s1 via Base.  s1.id won't populate since it's relative to
+        # the "sub" table.  The optimized load kicks in and tries to
         # generate on the primary join, but cannot since "id" is itself unloaded.
         # the optimized load needs to return "None" so regular full-row loading proceeds
         s1 = sess.query(Base).first()
@@ -1499,7 +1536,7 @@ class OptimizedLoadTest(fixtures.MappedTest):
         sess.expunge_all()
         # query a bunch of rows to ensure there's no cartesian
         # product against "base" occurring, it is in fact
-        # detecting that "base" needs to be in the join 
+        # detecting that "base" needs to be in the join
         # criterion
         eq_(
             sess.query(Base).order_by(Base.id).all(),
@@ -1585,24 +1622,24 @@ class OptimizedLoadTest(fixtures.MappedTest):
             pass
         class Sub(Base):
             pass
-        mapper(Base, base, polymorphic_on=base.c.type, 
+        mapper(Base, base, polymorphic_on=base.c.type,
                             polymorphic_identity='base')
         m = mapper(Sub, sub, inherits=Base, polymorphic_identity='sub')
 
         s1 = Sub()
-        assert m._optimized_get_statement(attributes.instance_state(s1), 
+        assert m._optimized_get_statement(attributes.instance_state(s1),
                                 ['counter2']) is None
 
         # loads s1.id as None
         eq_(s1.id, None)
 
         # this now will come up with a value of None for id - should reject
-        assert m._optimized_get_statement(attributes.instance_state(s1), 
+        assert m._optimized_get_statement(attributes.instance_state(s1),
                                 ['counter2']) is None
 
         s1.id = 1
         attributes.instance_state(s1).commit_all(s1.__dict__, None)
-        assert m._optimized_get_statement(attributes.instance_state(s1), 
+        assert m._optimized_get_statement(attributes.instance_state(s1),
                                 ['counter2']) is not None
 
     def test_load_expired_on_pending_twolevel(self):
@@ -1617,7 +1654,7 @@ class OptimizedLoadTest(fixtures.MappedTest):
         class SubSub(Sub):
             pass
 
-        mapper(Base, base, polymorphic_on=base.c.type, 
+        mapper(Base, base, polymorphic_on=base.c.type,
                     polymorphic_identity='base')
         mapper(Sub, sub, inherits=Base, polymorphic_identity='sub')
         mapper(SubSub, subsub, inherits=Sub, polymorphic_identity='subsub')
@@ -1720,7 +1757,7 @@ class InhCondTest(fixtures.TestBase):
 
         mapper(Base, base_table)
         # succeeds, despite "owner" table not configured yet
-        m2 = mapper(Derived, derived_table, 
+        m2 = mapper(Derived, derived_table,
                     inherits=Base)
         assert m2.inherit_condition.compare(
                     base_table.c.id==derived_table.c.id
@@ -1732,7 +1769,7 @@ class InhCondTest(fixtures.TestBase):
             Column("id", Integer, primary_key=True)
         )
         derived_table = Table("derived", m,
-            Column("id", Integer, ForeignKey('base.id'), 
+            Column("id", Integer, ForeignKey('base.id'),
                 primary_key=True),
             Column('order_id', Integer, ForeignKey('order.foo'))
         )
@@ -1782,7 +1819,7 @@ class InhCondTest(fixtures.TestBase):
             Column("id", Integer, primary_key=True)
         )
         derived_table = Table("derived", m2,
-            Column("id", Integer, ForeignKey('base.id'), 
+            Column("id", Integer, ForeignKey('base.id'),
                 primary_key=True),
         )
 
@@ -1813,7 +1850,7 @@ class InhCondTest(fixtures.TestBase):
             Column("id", Integer, primary_key=True)
         )
         derived_table = Table("derived", m,
-            Column("id", Integer, ForeignKey('base.q'), 
+            Column("id", Integer, ForeignKey('base.q'),
                 primary_key=True),
         )
 
@@ -1838,12 +1875,12 @@ class PKDiscriminatorTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         parents = Table('parents', metadata,
-                           Column('id', Integer, primary_key=True, 
+                           Column('id', Integer, primary_key=True,
                                     test_needs_autoincrement=True),
                            Column('name', String(60)))
 
         children = Table('children', metadata,
-                        Column('id', Integer, ForeignKey('parents.id'), 
+                        Column('id', Integer, ForeignKey('parents.id'),
                                     primary_key=True),
                         Column('type', Integer,primary_key=True),
                         Column('name', String(60)))
@@ -1892,7 +1929,7 @@ class NoPolyIdentInMiddleTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table('base', metadata,
-            Column('id', Integer, primary_key=True, 
+            Column('id', Integer, primary_key=True,
                             test_needs_autoincrement=True),
             Column('type', String(50), nullable=False),
         )
@@ -1969,7 +2006,7 @@ class DeleteOrphanTest(fixtures.MappedTest):
     """Test the fairly obvious, that an error is raised
     when attempting to insert an orphan.
 
-    Previous SQLA versions would check this constraint 
+    Previous SQLA versions would check this constraint
     in memory which is the original rationale for this test.
 
     """
@@ -2014,14 +2051,14 @@ class PolymorphicUnionTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = 'default'
 
     def _fixture(self):
-        t1 = table('t1', column('c1', Integer), 
-                        column('c2', Integer), 
+        t1 = table('t1', column('c1', Integer),
+                        column('c2', Integer),
                         column('c3', Integer))
-        t2 = table('t2', column('c1', Integer), column('c2', Integer), 
-                                column('c3', Integer), 
+        t2 = table('t2', column('c1', Integer), column('c2', Integer),
+                                column('c3', Integer),
                                 column('c4', Integer))
-        t3 = table('t3', column('c1', Integer), 
-                                column('c3', Integer), 
+        t3 = table('t3', column('c1', Integer),
+                                column('c3', Integer),
                                 column('c5', Integer))
         return t1, t2, t3
 
@@ -2072,12 +2109,12 @@ class NameConflictTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         content = Table('content', metadata,
-            Column('id', Integer, primary_key=True, 
+            Column('id', Integer, primary_key=True,
                     test_needs_autoincrement=True),
             Column('type', String(30))
         )
         foo = Table('foo', metadata,
-            Column('id', Integer, ForeignKey('content.id'), 
+            Column('id', Integer, ForeignKey('content.id'),
                         primary_key=True),
             Column('content_type', String(30))
         )
@@ -2087,9 +2124,9 @@ class NameConflictTest(fixtures.MappedTest):
             pass
         class Foo(Content):
             pass
-        mapper(Content, self.tables.content, 
+        mapper(Content, self.tables.content,
                     polymorphic_on=self.tables.content.c.type)
-        mapper(Foo, self.tables.foo, inherits=Content, 
+        mapper(Foo, self.tables.foo, inherits=Content,
                     polymorphic_identity='foo')
         sess = create_session()
         f = Foo()