summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-12-03 21:27:04 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-12-03 21:27:04 +0000
commitfff9409a339d7a8d33667f5f717a3ae7ed334842 (patch)
tree3c00330b4636376eeae1b170da62f9e7fd156629 /lib/sqlalchemy
parent0410eae36b36dc8ea7e747c4b81c7ec9de5f2da4 (diff)
downloadsqlalchemy-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.py15
-rw-r--r--lib/sqlalchemy/orm/query.py33
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)