From 7e9f273835ac68df894568ba4292bfbc74ce187b Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 18 Dec 2019 21:50:24 -0500 Subject: Don't apply aliasing + adaption for simple relationship joins Identified a performance issue in the system by which a join is constructed based on a mapped relationship. The clause adaption system would be used for the majority of join expressions including in the common case where no adaptation is needed. The conditions under which this adaptation occur have been refined so that average non-aliased joins along a simple relationship without a "secondary" table use about 70% less function calls. Change-Id: Ifbe04214576e5a9fac86ca80c1dc7145c27cd50a --- lib/sqlalchemy/orm/relationships.py | 20 +++++++++++++++++--- lib/sqlalchemy/orm/util.py | 1 + lib/sqlalchemy/testing/profiling.py | 7 +++++-- 3 files changed, 23 insertions(+), 5 deletions(-) (limited to 'lib/sqlalchemy') diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index fc3e78ea1..7ee12ca6c 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -1044,6 +1044,7 @@ class RelationshipProperty(StrategizedProperty): source_selectable=adapt_from, source_polymorphic=True, of_type_mapper=of_type_mapper, + alias_secondary=True, ) if sj is not None: return pj & sj @@ -2215,12 +2216,18 @@ class RelationshipProperty(StrategizedProperty): dest_polymorphic=False, dest_selectable=None, of_type_mapper=None, + alias_secondary=False, ): + + aliased = False + + if alias_secondary and self.secondary is not None: + aliased = True + if source_selectable is None: if source_polymorphic and self.parent.with_polymorphic: source_selectable = self.parent._with_polymorphic_selectable - aliased = False if dest_selectable is None: dest_selectable = self.entity.selectable if dest_polymorphic and self.mapper.with_polymorphic: @@ -2229,13 +2236,20 @@ class RelationshipProperty(StrategizedProperty): if self._is_self_referential and source_selectable is None: dest_selectable = dest_selectable._anonymous_fromclause() aliased = True - else: + elif dest_selectable is not self.mapper._with_polymorphic_selectable: aliased = True dest_mapper = of_type_mapper or self.mapper single_crit = dest_mapper._single_table_criterion - aliased = aliased or (source_selectable is not None) + aliased = aliased or ( + source_selectable is not None + and ( + source_selectable + is not self.parent._with_polymorphic_selectable + or source_selectable._is_subquery + ) + ) ( primaryjoin, diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index b96387c08..2f78fa535 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1035,6 +1035,7 @@ class _ORMJoin(expression.Join): source_polymorphic=True, dest_polymorphic=True, of_type_mapper=right_info.mapper, + alias_secondary=True, ) if sj is not None: diff --git a/lib/sqlalchemy/testing/profiling.py b/lib/sqlalchemy/testing/profiling.py index bfc4997e1..b837d9b5b 100644 --- a/lib/sqlalchemy/testing/profiling.py +++ b/lib/sqlalchemy/testing/profiling.py @@ -221,7 +221,7 @@ class ProfileStatsFile(object): profile_f.close() -def function_call_count(variance=0.05): +def function_call_count(variance=0.05, times=1): """Assert a target for a test case's function call count. The main purpose of this assertion is to detect changes in @@ -234,8 +234,11 @@ def function_call_count(variance=0.05): def decorate(fn): def wrap(*args, **kw): + timerange = range(times) with count_functions(variance=variance): - return fn(*args, **kw) + for time in timerange: + rv = fn(*args, **kw) + return rv return update_wrapper(wrap, fn) -- cgit v1.2.1