Skip to content

Commit 903b188

Browse files
committed
Small callcount reductions and refinement for cached queries
baked wasn't using the new one()/first()/one_or_none() methods, fixed that. loading._instance_processor() can skip setting up the quick populators every time because it can cache the getters. Callcounts have gone below what 1.3 does for the test_baked_query performance suite, however runtime for continued inexplicable reasons has not :(. still suspecting the result tuples but this seems so hard to believe. Change-Id: Ifbca04834d27350e0fa82cb8512e66112abc8729
1 parent 6930dfc commit 903b188

3 files changed

Lines changed: 90 additions & 77 deletions

File tree

lib/sqlalchemy/ext/baked.py

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def __str__(self):
383383
return str(self._as_query())
384384

385385
def __iter__(self):
386-
return iter(self._iter())
386+
return self._iter().__iter__()
387387

388388
def _iter(self):
389389
bq = self.bq
@@ -463,36 +463,23 @@ def first(self):
463463
Equivalent to :meth:`_query.Query.first`.
464464
465465
"""
466+
466467
bq = self.bq.with_criteria(lambda q: q.slice(0, 1))
467-
ret = list(
468+
return (
468469
bq.for_session(self.session)
469470
.params(self._params)
470471
._using_post_criteria(self._post_criteria)
472+
._iter()
473+
.first()
471474
)
472-
if len(ret) > 0:
473-
return ret[0]
474-
else:
475-
return None
476475

477476
def one(self):
478477
"""Return exactly one result or raise an exception.
479478
480479
Equivalent to :meth:`_query.Query.one`.
481480
482481
"""
483-
try:
484-
ret = self.one_or_none()
485-
except orm_exc.MultipleResultsFound as err:
486-
util.raise_(
487-
orm_exc.MultipleResultsFound(
488-
"Multiple rows were found for one()"
489-
),
490-
replace_context=err,
491-
)
492-
else:
493-
if ret is None:
494-
raise orm_exc.NoResultFound("No row was found for one()")
495-
return ret
482+
return self._iter().one()
496483

497484
def one_or_none(self):
498485
"""Return one or zero results, or raise an exception for multiple
@@ -503,17 +490,7 @@ def one_or_none(self):
503490
.. versionadded:: 1.0.9
504491
505492
"""
506-
ret = list(self)
507-
508-
l = len(ret)
509-
if l == 1:
510-
return ret[0]
511-
elif l == 0:
512-
return None
513-
else:
514-
raise orm_exc.MultipleResultsFound(
515-
"Multiple rows were found for one_or_none()"
516-
)
493+
return self._iter().one_or_none()
517494

518495
def all(self):
519496
"""Return all rows.

lib/sqlalchemy/orm/loading.py

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
"""
1515
from __future__ import absolute_import
1616

17-
import collections
18-
1917
from . import attributes
2018
from . import exc as orm_exc
2119
from . import path_registry
@@ -588,48 +586,86 @@ def _instance_processor(
588586

589587
identity_class = mapper._identity_class
590588

591-
populators = collections.defaultdict(list)
589+
compile_state = context.compile_state
590+
591+
populators = {}
592592

593593
props = mapper._prop_set
594594
if only_load_props is not None:
595595
props = props.intersection(mapper._props[k] for k in only_load_props)
596596

597-
quick_populators = path.get(
598-
context.attributes, "memoized_setups", _none_set
599-
)
597+
getters = path.get(compile_state.attributes, "getters", None)
598+
if getters is None:
599+
# directives given to us by the ColumnLoader.setup_query()
600+
# methods. Turn these directives into getters against the
601+
# actual result set.
602+
quick_populators = path.get(
603+
context.attributes, "memoized_setups", _none_set
604+
)
605+
cached_populators = {
606+
"new": [],
607+
"expire": [],
608+
"quick": [],
609+
"todo": [],
610+
"delayed": [],
611+
"existing": [],
612+
"eager": [],
613+
}
600614

601-
for prop in props:
602-
if prop in quick_populators:
603-
# this is an inlined path just for column-based attributes.
604-
col = quick_populators[prop]
605-
if col is _DEFER_FOR_STATE:
606-
populators["new"].append(
607-
(prop.key, prop._deferred_column_loader)
608-
)
609-
elif col is _SET_DEFERRED_EXPIRED:
610-
# note that in this path, we are no longer
611-
# searching in the result to see if the column might
612-
# be present in some unexpected way.
613-
populators["expire"].append((prop.key, False))
614-
elif col is _RAISE_FOR_STATE:
615-
populators["new"].append((prop.key, prop._raise_column_loader))
616-
else:
617-
getter = None
618-
if not getter:
619-
getter = result._getter(col, False)
620-
if getter:
621-
populators["quick"].append((prop.key, getter))
622-
else:
623-
# fall back to the ColumnProperty itself, which
624-
# will iterate through all of its columns
625-
# to see if one fits
626-
prop.create_row_processor(
627-
context, path, mapper, result, adapter, populators
615+
pk_cols = mapper.primary_key
616+
617+
if adapter:
618+
pk_cols = [adapter.columns[c] for c in pk_cols]
619+
getters = {
620+
"populators": cached_populators,
621+
"primary_key_getter": result._tuple_getter(pk_cols),
622+
}
623+
624+
for prop in props:
625+
if prop in quick_populators:
626+
# this is an inlined path just for column-based attributes.
627+
col = quick_populators[prop]
628+
if col is _DEFER_FOR_STATE:
629+
cached_populators["new"].append(
630+
(prop.key, prop._deferred_column_loader)
628631
)
629-
else:
630-
prop.create_row_processor(
631-
context, path, mapper, result, adapter, populators
632-
)
632+
elif col is _SET_DEFERRED_EXPIRED:
633+
# note that in this path, we are no longer
634+
# searching in the result to see if the column might
635+
# be present in some unexpected way.
636+
cached_populators["expire"].append((prop.key, False))
637+
elif col is _RAISE_FOR_STATE:
638+
cached_populators["new"].append(
639+
(prop.key, prop._raise_column_loader)
640+
)
641+
else:
642+
getter = None
643+
if not getter:
644+
getter = result._getter(col, False)
645+
if getter:
646+
cached_populators["quick"].append((prop.key, getter))
647+
else:
648+
# fall back to the ColumnProperty itself, which
649+
# will iterate through all of its columns
650+
# to see if one fits
651+
prop.create_row_processor(
652+
context,
653+
path,
654+
mapper,
655+
result,
656+
adapter,
657+
cached_populators,
658+
)
659+
else:
660+
cached_populators["todo"].append(prop)
661+
path.set(compile_state.attributes, "getters", getters)
662+
663+
cached_populators = getters["populators"]
664+
populators = {k: list(v) for k, v in cached_populators.items()}
665+
for prop in cached_populators["todo"]:
666+
prop.create_row_processor(
667+
context, path, mapper, result, adapter, populators
668+
)
633669

634670
propagated_loader_options = context.propagated_loader_options
635671
load_path = (
@@ -707,11 +743,7 @@ def _instance_processor(
707743
else:
708744
refresh_identity_key = None
709745

710-
pk_cols = mapper.primary_key
711-
712-
if adapter:
713-
pk_cols = [adapter.columns[c] for c in pk_cols]
714-
tuple_getter = result._tuple_getter(pk_cols)
746+
primary_key_getter = getters["primary_key_getter"]
715747

716748
if mapper.allow_partial_pks:
717749
is_not_primary_key = _none_set.issuperset
@@ -732,7 +764,11 @@ def _instance(row):
732764
else:
733765
# look at the row, see if that identity is in the
734766
# session, or we have to create a new one
735-
identitykey = (identity_class, tuple_getter(row), identity_token)
767+
identitykey = (
768+
identity_class,
769+
primary_key_getter(row),
770+
identity_token,
771+
)
736772

737773
instance = session_identity_map.get(identitykey)
738774

@@ -875,7 +911,7 @@ def _instance(row):
875911
def ensure_no_pk(row):
876912
identitykey = (
877913
identity_class,
878-
tuple_getter(row),
914+
primary_key_getter(row),
879915
identity_token,
880916
)
881917
if not is_not_primary_key(identitykey[1]):

test/ext/test_baked.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def test_one_or_none_multiple_result(self):
180180

181181
assert_raises_message(
182182
orm_exc.MultipleResultsFound,
183-
"Multiple rows were found for one_or_none()",
183+
"Multiple rows were found when one or none was required",
184184
bq(Session()).one_or_none,
185185
)
186186

@@ -192,7 +192,7 @@ def test_one_no_result(self):
192192

193193
assert_raises_message(
194194
orm_exc.NoResultFound,
195-
"No row was found for one()",
195+
"No row was found when one was required",
196196
bq(Session()).one,
197197
)
198198

@@ -213,7 +213,7 @@ def test_one_multiple_result(self):
213213

214214
assert_raises_message(
215215
orm_exc.MultipleResultsFound,
216-
"Multiple rows were found for one()",
216+
"Multiple rows were found when exactly one was required",
217217
bq(Session()).one,
218218
)
219219

0 commit comments

Comments
 (0)