From d9af0892dc2b822446d0a0168ece606d8975e175 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Wed, 15 Feb 2017 10:15:39 -0800 Subject: [PATCH] Load all eager relationships on 'before_commit' event This event handler will ensure that all eagerly loaded relationships are loaded on a newly created sqla object. This will allow us to continue our pattern of calling make_whatever_dict outside of the transaction context manager after the switch to the new engine facade managers. Co-Authored-By: Mike Bayer Partially-Implements: blueprint enginefacade-switch Change-Id: I144fed595a740c29fe58ec75fe8dd0a1f1ca4523 --- neutron/db/api.py | 31 +++++++++++++++++++ .../tests/unit/db/test_db_base_plugin_v2.py | 9 ++++++ 2 files changed, 40 insertions(+) diff --git a/neutron/db/api.py b/neutron/db/api.py index 5168dd0957f..00ae61bca9d 100644 --- a/neutron/db/api.py +++ b/neutron/db/api.py @@ -31,6 +31,7 @@ import six import sqlalchemy from sqlalchemy import event # noqa from sqlalchemy import exc as sql_exc +from sqlalchemy import orm from sqlalchemy.orm import exc from neutron._i18n import _LE @@ -261,3 +262,33 @@ def sqla_remove_all(): # already removed pass del _REGISTERED_SQLA_EVENTS[:] + + +@event.listens_for(orm.session.Session, "before_commit") +def load_one_to_manys(session): + # TODO(kevinbenton): we should be able to remove this after we + # have eliminated all places where related objects are constructed + # using a key rather than a relationship. + for new_object in session.new: + state = sqlalchemy.inspect(new_object) + + # set up relationship loading so that we can call lazy + # loaders on the object even though the ".key" is not set up yet + # (normally happens by in after_flush_postexec, but we're trying + # to do this more succinctly). in this context this is only + # setting a simple flag on the object's state. + session.enable_relationship_loading(new_object) + + # look for eager relationships and do normal load. + # For relationships where the related object is also + # in the session these lazy loads will pull from the + # identity map and not emit SELECT. Otherwise, we are still + # local in the transaction so a normal SELECT load will work fine. + for relationship_attr in state.mapper.relationships: + if relationship_attr.lazy not in ('joined', 'subquery'): + # we only want to automatically load relationships that would + # automatically load during a lookup operation + continue + if relationship_attr.key not in state.dict: + getattr(new_object, relationship_attr.key) + assert relationship_attr.key in state.dict diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index 9ba965cd3b3..fb261d313f6 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -6061,6 +6061,15 @@ class TestSubnetPoolsV2(NeutronDbPluginV2TestCase): class DbModelMixin(object): """DB model tests.""" + def test_make_network_dict_outside_engine_facade_manager(self): + ctx = context.get_admin_context() + with db_api.context_manager.writer.using(ctx): + network = models_v2.Network(name="net_net", status="OK", + admin_state_up=True) + ctx.session.add(network) + pl = db_base_plugin_common.DbBasePluginCommon() + self.assertFalse(pl._make_network_dict(network, context=ctx)['shared']) + def test_repr(self): """testing the string representation of 'model' classes.""" network = models_v2.Network(name="net_net", status="OK",