mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-29 20:14:55 -04:00
Ensure mapper.polymorphic_on is polymorphic_prop.columns[0]
Fixed bug where joined eager loading would fail for a polymorphically- loaded mapper, where the polymorphic_on was set to an un-mapped expression such as a CASE expression. Change-Id: Iffe68196aaac592165c89684f09f4c06cd78ce54 Fixes: #3800
This commit is contained in:
Vendored
+9
@@ -18,6 +18,15 @@
|
||||
.. changelog::
|
||||
:version: 1.0.16
|
||||
|
||||
.. change::
|
||||
:tags: bug, orm
|
||||
:tickets: 3800
|
||||
:versions: 1.1.0
|
||||
|
||||
Fixed bug where joined eager loading would fail for a polymorphically-
|
||||
loaded mapper, where the polymorphic_on was set to an un-mapped
|
||||
expression such as a CASE expression.
|
||||
|
||||
.. change::
|
||||
:tags: bug, orm
|
||||
:tickets: 3798
|
||||
|
||||
@@ -1401,9 +1401,6 @@ class Mapper(InspectionAttr):
|
||||
# polymorphic_on is a column that is already mapped
|
||||
# to a ColumnProperty
|
||||
prop = self._columntoproperty[self.polymorphic_on]
|
||||
polymorphic_key = prop.key
|
||||
self.polymorphic_on = prop.columns[0]
|
||||
polymorphic_key = prop.key
|
||||
elif isinstance(self.polymorphic_on, MapperProperty):
|
||||
# polymorphic_on is directly a MapperProperty,
|
||||
# ensure it's a ColumnProperty
|
||||
@@ -1414,8 +1411,6 @@ class Mapper(InspectionAttr):
|
||||
"property or SQL expression "
|
||||
"can be passed for polymorphic_on")
|
||||
prop = self.polymorphic_on
|
||||
self.polymorphic_on = prop.columns[0]
|
||||
polymorphic_key = prop.key
|
||||
elif not expression._is_column(self.polymorphic_on):
|
||||
# polymorphic_on is not a Column and not a ColumnProperty;
|
||||
# not supported right now.
|
||||
@@ -1477,12 +1472,14 @@ class Mapper(InspectionAttr):
|
||||
col.label("_sa_polymorphic_on")
|
||||
key = col.key
|
||||
|
||||
self._configure_property(
|
||||
key,
|
||||
properties.ColumnProperty(col,
|
||||
_instrument=instrument),
|
||||
init=init, setparent=True)
|
||||
polymorphic_key = key
|
||||
prop = properties.ColumnProperty(col, _instrument=instrument)
|
||||
self._configure_property(key, prop, init=init, setparent=True)
|
||||
|
||||
# the actual polymorphic_on should be the first public-facing
|
||||
# column in the property
|
||||
self.polymorphic_on = prop.columns[0]
|
||||
polymorphic_key = prop.key
|
||||
|
||||
else:
|
||||
# no polymorphic_on was set.
|
||||
# check inheriting mappers for one.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import warnings
|
||||
from sqlalchemy.testing import eq_, assert_raises, assert_raises_message
|
||||
from sqlalchemy.testing import eq_, is_, assert_raises, assert_raises_message
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy import exc as sa_exc, util, event
|
||||
from sqlalchemy.orm import *
|
||||
@@ -77,6 +77,65 @@ class O2MTest(fixtures.MappedTest):
|
||||
eq_(l[0].parent_foo.data, 'foo #1')
|
||||
eq_(l[1].parent_foo.data, 'foo #1')
|
||||
|
||||
|
||||
class PolyExpressionEagerLoad(fixtures.DeclarativeMappedTest):
|
||||
run_setup_mappers = 'once'
|
||||
__dialect__ = 'default'
|
||||
|
||||
@classmethod
|
||||
def setup_classes(cls):
|
||||
Base = cls.DeclarativeBasic
|
||||
|
||||
class A(fixtures.ComparableEntity, Base):
|
||||
__tablename__ = 'a'
|
||||
|
||||
id = Column(Integer, primary_key=True,
|
||||
test_needs_autoincrement=True)
|
||||
discriminator = Column(String(50), nullable=False)
|
||||
child_id = Column(Integer, ForeignKey('a.id'))
|
||||
child = relationship('A')
|
||||
|
||||
p_a = case([
|
||||
(discriminator == "a", "a"),
|
||||
], else_="b")
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': 'a',
|
||||
"polymorphic_on": p_a,
|
||||
}
|
||||
|
||||
class B(A):
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': 'b'
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def insert_data(cls):
|
||||
A = cls.classes.A
|
||||
|
||||
session = Session(testing.db)
|
||||
session.add_all([
|
||||
A(id=1, discriminator='a'),
|
||||
A(id=2, discriminator='b', child_id=1),
|
||||
A(id=3, discriminator='c', child_id=1),
|
||||
])
|
||||
session.commit()
|
||||
|
||||
def test_joinedload(self):
|
||||
A = self.classes.A
|
||||
B = self.classes.B
|
||||
|
||||
session = Session(testing.db)
|
||||
result = session.query(A).filter_by(child_id=None).\
|
||||
options(joinedload('child')).one()
|
||||
|
||||
|
||||
eq_(
|
||||
result,
|
||||
A(id=1, discriminator='a', child=[B(id=2), B(id=3)]),
|
||||
)
|
||||
|
||||
|
||||
class PolymorphicResolutionMultiLevel(fixtures.DeclarativeMappedTest,
|
||||
testing.AssertsCompiledSQL):
|
||||
run_setup_mappers = 'once'
|
||||
@@ -396,6 +455,22 @@ class PolymorphicOnNotLocalTest(fixtures.MappedTest):
|
||||
def _roundtrip(self, set_event=True, parent_ident='parent', child_ident='child'):
|
||||
Parent, Child = self.classes.Parent, self.classes.Child
|
||||
|
||||
# locate the "polymorphic_on" ColumnProperty. This isn't
|
||||
# "officially" stored at the moment so do some heuristics to find it.
|
||||
parent_mapper = inspect(Parent)
|
||||
for prop in parent_mapper.column_attrs:
|
||||
if not prop.instrument:
|
||||
break
|
||||
else:
|
||||
prop = parent_mapper._columntoproperty[
|
||||
parent_mapper.polymorphic_on]
|
||||
|
||||
# then make sure the column we will query on matches.
|
||||
is_(
|
||||
parent_mapper.polymorphic_on,
|
||||
prop.columns[0]
|
||||
)
|
||||
|
||||
if set_event:
|
||||
@event.listens_for(Parent, "init", propagate=True)
|
||||
def set_identity(instance, *arg, **kw):
|
||||
|
||||
Reference in New Issue
Block a user