From: Mike Bayer Date: Mon, 29 Jun 2015 17:47:27 +0000 (-0400) Subject: - Fixed 1.0 regression where a "deferred" attribute would not populate X-Git-Tag: rel_1_0_7~22 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4d6f4ed184b94e60d5d39eff7fae678d64e9aeaa;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Fixed 1.0 regression where a "deferred" attribute would not populate correctly if it were loaded within the "optimized inheritance load", which is a special SELECT emitted in the case of joined table inheritance used to populate expired or unloaded attributes against a joined table without loading the base table. This is related to the fact that SQLA 1.0 no longer guesses about loading deferred columns and must be directed explicitly. fixes #3468 --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index e857445018..9f0f0dff3e 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -18,6 +18,18 @@ .. changelog:: :version: 1.0.7 + .. change:: + :tags: bug, orm + :tickets: 3468 + + Fixed 1.0 regression where a "deferred" attribute would not populate + correctly if it were loaded within the "optimized inheritance load", + which is a special SELECT emitted in the case of joined table + inheritance used to populate expired or unloaded attributes against + a joined table without loading the base table. This is related to + the fact that SQLA 1.0 no longer guesses about loading deferred + columns and must be directed explicitly. + .. change:: :tags: bug, orm :tickets: 3466 diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index 50afaf6012..b81e98a580 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -17,6 +17,8 @@ from __future__ import absolute_import from .. import util from . import attributes, exc as orm_exc from ..sql import util as sql_util +from . import strategy_options + from .util import _none_set, state_str from .base import _SET_DEFERRED_EXPIRED, _DEFER_FOR_STATE from .. import exc as sa_exc @@ -612,10 +614,17 @@ def load_scalar_attributes(mapper, state, attribute_names): result = False if mapper.inherits and not mapper.concrete: + # because we are using Core to produce a select() that we + # pass to the Query, we aren't calling setup() for mapped + # attributes; in 1.0 this means deferred attrs won't get loaded + # by default statement = mapper._optimized_get_statement(state, attribute_names) if statement is not None: result = load_on_ident( - session.query(mapper).from_statement(statement), + session.query(mapper). + options( + strategy_options.Load(mapper).undefer("*") + ).from_statement(statement), None, only_load_props=attribute_names, refresh_state=state diff --git a/test/orm/inheritance/test_basic.py b/test/orm/inheritance/test_basic.py index d8b2a44af0..911d4bc5c8 100644 --- a/test/orm/inheritance/test_basic.py +++ b/test/orm/inheritance/test_basic.py @@ -1148,6 +1148,62 @@ class FlushTest(fixtures.MappedTest): sess.flush() assert user_roles.count().scalar() == 1 + +class OptimizedGetOnDeferredTest(fixtures.MappedTest): + """test that the 'optimized get' path accommodates deferred columns.""" + + @classmethod + def define_tables(cls, metadata): + Table( + "a", metadata, + Column('id', Integer, primary_key=True, + test_needs_autoincrement=True) + ) + Table( + "b", metadata, + Column('id', Integer, ForeignKey('a.id'), primary_key=True), + Column('data', String(10)) + ) + + @classmethod + def setup_classes(cls): + class A(cls.Basic): + pass + + class B(A): + pass + + @classmethod + def setup_mappers(cls): + A, B = cls.classes("A", "B") + a, b = cls.tables("a", "b") + + mapper(A, a) + mapper(B, b, inherits=A, properties={ + 'data': deferred(b.c.data), + 'expr': column_property(b.c.data + 'q', deferred=True) + }) + + def test_column_property(self): + A, B = self.classes("A", "B") + sess = Session() + b1 = B(data='x') + sess.add(b1) + sess.flush() + + eq_(b1.expr, 'xq') + + def test_expired_column(self): + A, B = self.classes("A", "B") + sess = Session() + b1 = B(data='x') + sess.add(b1) + sess.flush() + sess.expire(b1, ['data']) + + eq_(b1.data, 'x') + + class JoinedNoFKSortingTest(fixtures.MappedTest): @classmethod def define_tables(cls, metadata):