Mike Bayer  committed c9db569

- added "contains_alias()" option for result set mapping to an alias of the mapped table

  • Participants
  • Parent commits ccb9339

Comments (0)

Files changed (5)

   - added "alias" argument to contains_eager().  use it to specify the string name
     or Alias instance of an alias used in the query for the eagerly loaded child items.
     easier to use than "decorator"
+  - added "contains_alias()" option for result set mapping to an alias of the mapped table
   - mapper options like eagerload(), lazyload(), deferred(), will work for "synonym()"
   relationships [ticket:485]
   - fixed bug where cascade operations incorrectly included deleted collection

File doc/build/content/adv_datamapping.txt

     adalias.user_id AS adalias_user_id, adalias.email_address AS adalias_email_address, (...other columns...)
     FROM users LEFT OUTER JOIN email_addresses AS adalias ON users.user_id = adalias.user_id
+In the case that the main table itself is also aliased, the `contains_alias()` option can be used (new in version 0.3.5):
+    {python}
+    # define an aliased UNION called 'ulist'
+    statement =>7)).alias('ulist')
+    # add on an eager load of "addresses"
+    statement = statement.outerjoin(addresses).select(use_labels=True)
+    # create query, indicating "ulist" is an alias for the main table, "addresses" property should
+    # be eager loaded
+    query = create_session().query(User).options(contains_alias('ulist'), contains_eager('addresses'))
+    # results
+    r = query.instances(statement.execute())
 ### Mapper Options {@name=mapperoptions}
 Options which can be sent to the `mapper()` function.  For arguments to `relation()`, see [advdatamapping_properties_relationoptions](rel:advdatamapping_properties_relationoptions).

File lib/sqlalchemy/orm/

 __all__ = ['relation', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'extension', 
         'mapper', 'clear_mappers', 'compile_mappers', 'clear_mapper', 'class_mapper', 'object_mapper', 'MapperExtension', 'Query', 
-        'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_eager', 'EXT_PASS', 'object_session'
+        'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', 'object_session'
 def relation(*args, **kwargs):
     used with query.options()."""
     return strategies.EagerLazyOption(name, lazy=None)
+def contains_alias(alias):
+    """return a MapperOption that will indicate to the query that the main table
+    has been aliased.
+    "alias" is the string name or Alias object representing the alias.
+    """
+    class AliasedRow(MapperExtension):
+        def __init__(self, alias):
+            self.alias = alias
+            if isinstance(self.alias, basestring):
+                self.selectable = None
+            else:
+                self.selectable = alias
+        def get_selectable(self, mapper):
+            if self.selectable is None:
+                self.selectable = mapper.mapped_table.alias(self.alias)
+            return self.selectable
+        def translate_row(self, mapper, context, row):
+            newrow = sautil.DictDecorator(row)
+            selectable = self.get_selectable(mapper)
+            for c in mapper.mapped_table.c:
+                c2 = selectable.corresponding_column(c, keys_ok=True, raiseerr=False)
+                if c2 and row.has_key(c2):
+                    newrow[c] = row[c2]
+            return newrow
+    return ExtensionOption(AliasedRow(alias))
 def contains_eager(key, alias=None, decorator=None):
     """return a MapperOption that will indicate to the query that the given 
     attribute will be eagerly loaded.

File lib/sqlalchemy/orm/

         either case, executes all the property loaders on the instance to also process extra
         information in the row."""
+        ret = context.extension.translate_row(self, context, row)
+        if ret is not EXT_PASS:
+            row = ret
         if not skip_polymorphic and self.polymorphic_on is not None:
             discriminator = row[self.polymorphic_on]
             if discriminator is not None:
         the return value of this method is used as the result of if the
         value is anything other than EXT_PASS."""
         return EXT_PASS
+    def translate_row(self, mapper, context, row):
+        """perform pre-processing on the given result row and return a new row instance.
+        this is called as the very first step in the _instance() method."""
+        return EXT_PASS
     def create_instance(self, mapper, selectcontext, row, class_):
         """receieve a row when a new object instance is about to be created from that row.  
         the method can choose to create the instance itself, or it can return 
         return self._do('select_by', *args, **kwargs)
     def select(self, *args, **kwargs):
         return self._do('select', *args, **kwargs)
+    def translate_row(self, *args, **kwargs):
+        return self._do('translate_row', *args, **kwargs)
     def create_instance(self, *args, **kwargs):
         return self._do('create_instance', *args, **kwargs)
     def append_result(self, *args, **kwargs):

File test/orm/

             {'user_id' : 9, 'addresses' : (Address, [])}
+    def testcustomfromalias(self):
+        mapper(User, users, properties={
+            'addresses':relation(Address, lazy=True)
+        })
+        mapper(Address, addresses)
+        query =>7)).alias('ulist').outerjoin(addresses).select(use_labels=True)
+        q = create_session().query(User)
+        def go():
+            l = q.options(contains_alias('ulist'), contains_eager('addresses')).instances(query.execute())
+            self.assert_result(l, User, *user_address_result)
+        self.assert_sql_count(testbase.db, go, 1)
     def testcustomeagerquery(self):
         mapper(User, users, properties={
             # setting lazy=True - the contains_eager() option below