diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-12-03 21:27:04 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-12-03 21:27:04 +0000 |
| commit | fff9409a339d7a8d33667f5f717a3ae7ed334842 (patch) | |
| tree | 3c00330b4636376eeae1b170da62f9e7fd156629 /lib/sqlalchemy | |
| parent | 0410eae36b36dc8ea7e747c4b81c7ec9de5f2da4 (diff) | |
| download | sqlalchemy-fff9409a339d7a8d33667f5f717a3ae7ed334842.tar.gz | |
- Query.with_polymorphic() now accepts a third
argument "discriminator" which will replace
the value of mapper.polymorphic_on for that
query. Mappers themselves no longer require
polymorphic_on to be set, even if the mapper
has a polymorphic_identity. When not set,
the mapper will load non-polymorphically
by default. Together, these two features allow
a non-polymorphic concrete inheritance setup
to use polymorphic loading on a per-query basis,
since concrete setups are prone to many
issues when used polymorphically in all cases.
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 15 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 33 |
2 files changed, 28 insertions, 20 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 48c2f9e27..9c62cadd9 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -165,7 +165,7 @@ class Mapper(object): self.local_table = self.local_table.alias() if self.with_polymorphic and isinstance(self.with_polymorphic[1], expression._SelectBaseMixin): - self.with_polymorphic[1] = self.with_polymorphic[1].alias() + self.with_polymorphic = (self.with_polymorphic[0], self.with_polymorphic[1].alias()) # our 'polymorphic identity', a string name that when located in a result set row # indicates this Mapper should be used to construct the object instance for that row. @@ -270,20 +270,11 @@ class Mapper(object): if mapper.polymorphic_on: self.polymorphic_on = self.mapped_table.corresponding_column(mapper.polymorphic_on) break - else: - # TODO: this exception not covered - raise sa_exc.ArgumentError("Mapper '%s' specifies a polymorphic_identity of '%s', " - "but no mapper in it's hierarchy specifies " - "the 'polymorphic_on' column argument" % (self, self.polymorphic_identity)) else: self._all_tables = set() self.base_mapper = self self.mapped_table = self.local_table if self.polymorphic_identity: - if self.polymorphic_on is None: - raise sa_exc.ArgumentError("Mapper '%s' specifies a polymorphic_identity of '%s', but " - "no mapper in it's hierarchy specifies the " - "'polymorphic_on' column argument" % (self, self.polymorphic_identity)) self.polymorphic_map[self.polymorphic_identity] = self self._identity_class = self.class_ @@ -1489,7 +1480,7 @@ class Mapper(object): # result set conversion - def _instance_processor(self, context, path, adapter, polymorphic_from=None, extension=None, only_load_props=None, refresh_state=None): + def _instance_processor(self, context, path, adapter, polymorphic_from=None, extension=None, only_load_props=None, refresh_state=None, polymorphic_discriminator=None): """Produce a mapper level row processor callable which processes rows into mapped instances.""" pk_cols = self.primary_key @@ -1497,7 +1488,7 @@ class Mapper(object): if polymorphic_from or refresh_state: polymorphic_on = None else: - polymorphic_on = self.polymorphic_on + polymorphic_on = polymorphic_discriminator or self.polymorphic_on polymorphic_instances = util.PopulateDict(self._configure_subclass_mapper(context, path, adapter)) version_id_col = self.version_id_col diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 4bff81d67..88357f34c 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -358,7 +358,7 @@ class Query(object): self._current_path = path @_generative(__no_clauseelement_condition) - def with_polymorphic(self, cls_or_mappers, selectable=None): + def with_polymorphic(self, cls_or_mappers, selectable=None, discriminator=None): """Load columns for descendant mappers of this Query's mapper. Using this method will ensure that each descendant mapper's @@ -367,12 +367,12 @@ class Query(object): instances will also have those columns already loaded so that no "post fetch" of those columns will be required. - ``cls_or_mappers`` is a single class or mapper, or list of class/mappers, + :param cls_or_mappers: - a single class or mapper, or list of class/mappers, which inherit from this Query's mapper. Alternatively, it may also be the string ``'*'``, in which case all descending mappers will be added to the FROM clause. - ``selectable`` is a table or select() statement that will + :param selectable: - a table or select() statement that will be used in place of the generated FROM clause. This argument is required if any of the desired mappers use concrete table inheritance, since SQLAlchemy currently cannot generate UNIONs @@ -382,9 +382,15 @@ class Query(object): will result in their table being appended directly to the FROM clause which will usually lead to incorrect results. + :param discriminator: - a column to be used as the "discriminator" + column for the given selectable. If not given, the polymorphic_on + attribute of the mapper will be used, if any. This is useful + for mappers that don't have polymorphic loading behavior by default, + such as concrete table mappers. + """ entity = self._generate_mapper_zero() - entity.set_with_polymorphic(self, cls_or_mappers, selectable=selectable) + entity.set_with_polymorphic(self, cls_or_mappers, selectable=selectable, discriminator=discriminator) @_generative() def yield_per(self, count): @@ -1654,6 +1660,7 @@ class _MapperEntity(_QueryEntity): self.adapter = adapter self.selectable = from_obj self._with_polymorphic = with_polymorphic + self._polymorphic_discriminator = None self.is_aliased_class = is_aliased_class if is_aliased_class: self.path_entity = self.entity = self.entity_zero = entity @@ -1661,13 +1668,14 @@ class _MapperEntity(_QueryEntity): self.path_entity = mapper.base_mapper self.entity = self.entity_zero = mapper - def set_with_polymorphic(self, query, cls_or_mappers, selectable): + def set_with_polymorphic(self, query, cls_or_mappers, selectable, discriminator): if cls_or_mappers is None: query._reset_polymorphic_adapter(self.mapper) return mappers, from_obj = self.mapper._with_polymorphic_args(cls_or_mappers, selectable) self._with_polymorphic = mappers + self._polymorphic_discriminator = discriminator # TODO: do the wrapped thing here too so that with_polymorphic() can be # applied to aliases @@ -1718,10 +1726,12 @@ class _MapperEntity(_QueryEntity): if self.primary_entity: _instance = self.mapper._instance_processor(context, (self.path_entity,), adapter, - extension=self.extension, only_load_props=query._only_load_props, refresh_state=context.refresh_state + extension=self.extension, only_load_props=query._only_load_props, refresh_state=context.refresh_state, + polymorphic_discriminator=self._polymorphic_discriminator ) else: - _instance = self.mapper._instance_processor(context, (self.path_entity,), adapter) + _instance = self.mapper._instance_processor(context, (self.path_entity,), adapter, + polymorphic_discriminator=self._polymorphic_discriminator) if custom_rows: def main(context, row, result): @@ -1759,7 +1769,14 @@ class _MapperEntity(_QueryEntity): only_load_props=query._only_load_props, column_collection=context.primary_columns ) - + + if self._polymorphic_discriminator: + if adapter: + pd = adapter.columns[self._polymorphic_discriminator] + else: + pd = self._polymorphic_discriminator + context.primary_columns.append(pd) + def __str__(self): return str(self.mapper) |
