diff options
-rw-r--r-- | doc/build/orm/extensions/baked.rst | 34 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/baked.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/horizontal_shard.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/context.py | 16 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/loading.py | 116 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 16 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 70 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 19 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 255 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/util.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/base.py | 40 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/dml.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/lambdas.py | 2 | ||||
-rw-r--r-- | test/aaa_profiling/test_orm.py | 11 | ||||
-rw-r--r-- | test/ext/test_baked.py | 655 | ||||
-rw-r--r-- | test/orm/inheritance/test_polymorphic_rel.py | 54 | ||||
-rw-r--r-- | test/orm/inheritance/test_relationship.py | 21 | ||||
-rw-r--r-- | test/orm/test_lazy_relations.py | 79 | ||||
-rw-r--r-- | test/profiles.txt | 180 |
23 files changed, 532 insertions, 1082 deletions
diff --git a/doc/build/orm/extensions/baked.rst b/doc/build/orm/extensions/baked.rst index 9ff343239..e8651dbaa 100644 --- a/doc/build/orm/extensions/baked.rst +++ b/doc/build/orm/extensions/baked.rst @@ -24,14 +24,8 @@ the caching of the SQL calls and result sets themselves is available in caching system that removes the need for the :class:`.BakedQuery` system. Caching is now transparently active for all Core and ORM queries with no action taken by the user, using the system described at :ref:`sql_caching`. - For background on using lambda-style construction for cacheable Core and ORM - SQL constructs, which is now an optional technique to provide additional - performance gains, see the section :ref:`engine_lambda_caching`. - -.. versionadded:: 1.0.0 - .. note:: The :mod:`sqlalchemy.ext.baked` extension is **not for beginners**. Using @@ -450,9 +444,7 @@ causing all baked queries to not use the cache when used against that Like all session flags, it is also accepted by factory objects like :class:`.sessionmaker` and methods like :meth:`.sessionmaker.configure`. -The immediate rationale for this flag is to reduce memory use in the case -that the query baking used by relationship loaders and other loaders -is not desirable. It also can be used in the case that an application +The immediate rationale for this flag is so that an application which is seeing issues potentially due to cache key conflicts from user-defined baked queries or other baked query issues can turn the behavior off, in order to identify or eliminate baked queries as the cause of an issue. @@ -462,26 +454,10 @@ order to identify or eliminate baked queries as the cause of an issue. Lazy Loading Integration ------------------------ -The baked query system is integrated into SQLAlchemy's lazy loader feature -as used by :func:`_orm.relationship`, and will cache queries for most lazy -load conditions. A small subset of -"lazy loads" may not be cached; these involve query options in conjunction with ad-hoc -:obj:`.aliased` structures that cannot produce a repeatable cache -key. - -.. versionchanged:: 1.2 "baked" queries are now the foundation of the - lazy-loader feature of :func:`_orm.relationship`. - -Opting out with the bake_queries flag -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The :func:`_orm.relationship` construct includes a flag -:paramref:`_orm.relationship.bake_queries` which when set to False will cause -that relationship to opt out of caching queries. Additionally, the -:paramref:`.Session.enable_baked_queries` setting can be used to disable -all "baked query" use. These flags can be useful to conserve memory, -when memory conservation is more important than performance for a particular -relationship or for the application overall. +.. versionchanged:: 1.4 As of SQLAlchemy 1.4, the "baked query" system is no + longer part of the relationship loading system. + The :ref:`native caching <sql_caching>` system is used instead. + API Documentation ----------------- diff --git a/lib/sqlalchemy/ext/baked.py b/lib/sqlalchemy/ext/baked.py index e642a83d5..97c825f02 100644 --- a/lib/sqlalchemy/ext/baked.py +++ b/lib/sqlalchemy/ext/baked.py @@ -200,6 +200,12 @@ class BakedQuery(object): if ck is None: self.spoil(full=True) else: + assert not ck[1], ( + "loader options with variable bound parameters " + "not supported with baked queries. Please " + "use new-style select() statements for cached " + "ORM queries." + ) key += ck[0] self.add_criteria( @@ -395,7 +401,7 @@ class Result(object): for fn in self._post_criteria: q = fn(q) - params = q.load_options._params + params = q._params execution_options = dict(q._execution_options) execution_options.update( { diff --git a/lib/sqlalchemy/ext/horizontal_shard.py b/lib/sqlalchemy/ext/horizontal_shard.py index 786d00597..53545826b 100644 --- a/lib/sqlalchemy/ext/horizontal_shard.py +++ b/lib/sqlalchemy/ext/horizontal_shard.py @@ -207,13 +207,10 @@ class ShardedSession(Session): def execute_and_instances(orm_context): - params = orm_context.parameters if orm_context.is_select: load_options = active_options = orm_context.load_options update_options = None - if params is None: - params = active_options._params else: load_options = None diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index 55a6b4cd2..96725e55b 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -46,6 +46,7 @@ class QueryContext(object): __slots__ = ( "compile_state", "query", + "params", "load_options", "bind_arguments", "execution_options", @@ -83,6 +84,7 @@ class QueryContext(object): self, compile_state, statement, + params, session, load_options, execution_options=None, @@ -96,6 +98,7 @@ class QueryContext(object): self.session = session self.loaders_require_buffering = False self.loaders_require_uniquing = False + self.params = params self.propagated_loader_options = { o for o in statement._with_options if o.propagate_to_loaders @@ -239,7 +242,13 @@ class ORMCompileState(CompileState): @classmethod def orm_setup_cursor_result( - cls, session, statement, execution_options, bind_arguments, result + cls, + session, + statement, + params, + execution_options, + bind_arguments, + result, ): execution_context = result.context compile_state = execution_context.compiled.compile_state @@ -256,6 +265,7 @@ class ORMCompileState(CompileState): querycontext = QueryContext( compile_state, statement, + params, session, load_options, execution_options, @@ -711,7 +721,9 @@ class ORMSelectCompileState(ORMCompileState, SelectState): and "entity_namespace" in element._annotations ): for elem in _select_iterables( - element._annotations["entity_namespace"].columns + element._annotations[ + "entity_namespace" + ]._all_column_expressions ): yield elem else: diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index e569c0603..4cf820ae3 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -38,6 +38,7 @@ from .. import util from ..sql import operators from ..sql import roles from ..sql import visitors +from ..sql.base import ExecutableOption from ..sql.traversals import HasCacheKey if util.TYPE_CHECKING: @@ -675,7 +676,7 @@ class StrategizedProperty(MapperProperty): ) -class ORMOption(HasCacheKey): +class ORMOption(ExecutableOption): """Base class for option objects that are passed to ORM queries. These options may be consumed by :meth:`.Query.options`, diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index 8d1ae2e69..601393156 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -187,6 +187,7 @@ def merge_frozen_result(session, statement, frozen_result, load=True): @util.preload_module("sqlalchemy.orm.context") def merge_result(query, iterator, load=True): """Merge a result into this :class:`.Query` object's Session.""" + querycontext = util.preloaded.orm_context session = query.session @@ -298,7 +299,8 @@ def load_on_ident( with_for_update=None, only_load_props=None, no_autoflush=False, - bind_arguments=util.immutabledict(), + bind_arguments=util.EMPTY_DICT, + execution_options=util.EMPTY_DICT, ): """Load the given identity key from the database.""" if key is not None: @@ -318,6 +320,7 @@ def load_on_ident( identity_token=identity_token, no_autoflush=no_autoflush, bind_arguments=bind_arguments, + execution_options=execution_options, ) @@ -331,7 +334,8 @@ def load_on_pk_identity( only_load_props=None, identity_token=None, no_autoflush=False, - bind_arguments=util.immutabledict(), + bind_arguments=util.EMPTY_DICT, + execution_options=util.EMPTY_DICT, ): """Load the given primary key identity from the database.""" @@ -339,21 +343,18 @@ def load_on_pk_identity( query = statement q = query._clone() + is_lambda = q._is_lambda_element + # TODO: fix these imports .... from .context import QueryContext, ORMCompileState if load_options is None: load_options = QueryContext.default_load_options - compile_options = ORMCompileState.default_compile_options.safe_merge( - q._compile_options - ) + compile_options = ORMCompileState.default_compile_options if primary_key_identity is not None: - # mapper = query._only_full_mapper_zero("load_on_pk_identity") - - # TODO: error checking? - mapper = query._raw_columns[0]._annotations["parententity"] + mapper = query._propagate_attrs["plugin_subject"] (_get_clause, _get_params) = mapper._get_clause @@ -379,10 +380,16 @@ def load_on_pk_identity( "release." ) - # TODO: can mapper._get_clause be pre-adapted? - q._where_criteria = ( - sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}), - ) + if is_lambda: + q = q.add_criteria( + lambda q: q.where( + sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}) + ), + ) + else: + q._where_criteria = ( + sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}), + ) params = dict( [ @@ -392,44 +399,71 @@ def load_on_pk_identity( ) ] ) + else: + params = None - load_options += {"_params": params} + if is_lambda: + if with_for_update is not None or refresh_state or only_load_props: + raise NotImplementedError( + "refresh operation not supported with lambda statement" + ) - if with_for_update is not None: - version_check = True - q._for_update_arg = with_for_update - elif query._for_update_arg is not None: - version_check = True - q._for_update_arg = query._for_update_arg - else: version_check = False - if refresh_state and refresh_state.load_options: - compile_options += {"_current_path": refresh_state.load_path.parent} - q = q.options(*refresh_state.load_options) - - # TODO: most of the compile_options that are not legacy only involve this - # function, so try to see if handling of them can mostly be local to here + _, load_options = _set_get_options( + compile_options, + load_options, + populate_existing=bool(refresh_state), + version_check=version_check, + only_load_props=only_load_props, + refresh_state=refresh_state, + identity_token=identity_token, + ) - q._compile_options, load_options = _set_get_options( - compile_options, - load_options, - populate_existing=bool(refresh_state), - version_check=version_check, - only_load_props=only_load_props, - refresh_state=refresh_state, - identity_token=identity_token, - ) - q._order_by = None + if no_autoflush: + load_options += {"_autoflush": False} + else: + if with_for_update is not None: + version_check = True + q._for_update_arg = with_for_update + elif query._for_update_arg is not None: + version_check = True + q._for_update_arg = query._for_update_arg + else: + version_check = False + + if refresh_state and refresh_state.load_options: + compile_options += { + "_current_path": refresh_state.load_path.parent + } + q = q.options(*refresh_state.load_options) + + # TODO: most of the compile_options that are not legacy only involve + # this function, so try to see if handling of them can mostly be local + # to here + + q._compile_options, load_options = _set_get_options( + compile_options, + load_options, + populate_existing=bool(refresh_state), + version_check=version_check, + only_load_props=only_load_props, + refresh_state=refresh_state, + identity_token=identity_token, + ) + q._order_by = None - if no_autoflush: - load_options += {"_autoflush": False} + if no_autoflush: + load_options += {"_autoflush": False} + execution_options = util.EMPTY_DICT.merge_with( + execution_options, {"_sa_orm_load_options": load_options} + ) result = ( session.execute( q, - params=load_options._params, - execution_options={"_sa_orm_load_options": load_options}, + params=params, + execution_options=execution_options, bind_arguments=bind_arguments, future=True, ) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 2b04f1cc7..6d22c6205 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -719,9 +719,8 @@ class Mapper( """ return self - _cache_key_traversal = [ - ("mapper", visitors.ExtendedInternalTraversal.dp_plain_obj), - ] + def _gen_cache_key(self, anon_map, bindparams): + return (self,) @property def entity(self): @@ -2315,6 +2314,17 @@ class Mapper( ) ) + @property + def _all_column_expressions(self): + poly_properties = self._polymorphic_properties + adapter = self._polymorphic_adapter + + return [ + adapter.columns[prop.columns[0]] if adapter else prop.columns[0] + for prop in poly_properties + if isinstance(prop, properties.ColumnProperty) + ] + def _columns_plus_keys(self, polymorphic_mappers=()): if polymorphic_mappers: poly_properties = self._iterate_polymorphic_properties( diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index a78af92b9..7c254c61b 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -1832,7 +1832,13 @@ class BulkUDCompileState(CompileState): @classmethod def orm_setup_cursor_result( - cls, session, statement, execution_options, bind_arguments, result + cls, + session, + statement, + params, + execution_options, + bind_arguments, + result, ): # this stage of the execution is called after the diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index b8226dfc0..d60c03bdc 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -125,6 +125,8 @@ class Query( load_options = QueryContext.default_load_options + _params = util.EMPTY_DICT + # local Query builder state, not needed for # compilation or execution _aliased_generation = None @@ -366,10 +368,8 @@ class Query( else: stmt = self._compile_state(for_statement=True).statement - if self.load_options._params: - # this is the search and replace thing. this is kind of nuts - # to be doing here. - stmt = stmt.params(self.load_options._params) + if self._params: + stmt = stmt.params(self._params) return stmt @@ -431,11 +431,7 @@ class Query( return stmt def subquery( - self, - name=None, - with_labels=False, - reduce_columns=False, - _legacy_core_statement=False, + self, name=None, with_labels=False, reduce_columns=False, ): """Return the full SELECT statement represented by this :class:`_query.Query`, embedded within an @@ -463,10 +459,7 @@ class Query( if with_labels: q = q.with_labels() - if _legacy_core_statement: - q = q._compile_state(for_statement=True).statement - else: - q = q.statement + q = q.statement if reduce_columns: q = q.reduce_columns() @@ -994,6 +987,13 @@ class Query( after rollback or commit handles object state automatically. This method is not intended for general use. + .. versionadded:: 1.4 + + The :meth:`.populate_existing` method is equivalent to passing the + ``populate_existing=True`` option to the + :meth:`_orm.Query.execution_options` method. + + """ self.load_options += {"_populate_existing": True} @@ -1298,16 +1298,11 @@ class Query( self.with_labels() .enable_eagerloads(False) .correlate(None) - .subquery(_legacy_core_statement=True) + .subquery() ._anonymous_fromclause() ) - parententity = self._raw_columns[0]._annotations.get("parententity") - if parententity: - ac = aliased(parententity.mapper, alias=fromclause) - q = self._from_selectable(ac) - else: - q = self._from_selectable(fromclause) + q = self._from_selectable(fromclause) if entities: q._set_entities(entities) @@ -1503,12 +1498,29 @@ class Query( def execution_options(self, **kwargs): """ Set non-SQL options which take effect during execution. - The options are the same as those accepted by - :meth:`_engine.Connection.execution_options`. + Options allowed here include all of those accepted by + :meth:`_engine.Connection.execution_options`, as well as a series + of ORM specific options: + + ``populate_existing=True`` - equivalent to using + :meth:`_orm.Query.populate_existing` + + ``autoflush=True|False`` - equivalent to using + :meth:`_orm.Query.autoflush` + + ``yield_per=<value>`` - equivalent to using + :meth:`_orm.Query.yield_per` Note that the ``stream_results`` execution option is enabled automatically if the :meth:`~sqlalchemy.orm.query.Query.yield_per()` - method is used. + method or execution option is used. + + The execution options may also be specified on a per execution basis + when using :term:`2.0 style` queries via the + :paramref:`_orm.Session.execution_options` parameter. + + .. versionadded:: 1.4 - added ORM options to + :meth:`_orm.Query.execution_options` .. seealso:: @@ -1579,8 +1591,7 @@ class Query( "params() takes zero or one positional argument, " "which is a dictionary." ) - params = self.load_options._params.union(kwargs) - self.load_options += {"_params": params} + self._params = self._params.union(kwargs) def where(self, *criterion): """A synonym for :meth:`.Query.filter`. @@ -2694,7 +2705,8 @@ class Query( def _iter(self): # new style execution. - params = self.load_options._params + params = self._params + statement = self._statement_20() result = self.session.execute( statement, @@ -2789,6 +2801,7 @@ class Query( context = QueryContext( compile_state, compile_state.statement, + self._params, self.session, self.load_options, ) @@ -2984,7 +2997,7 @@ class Query( delete_._where_criteria = self._where_criteria result = self.session.execute( delete_, - self.load_options._params, + self._params, execution_options={"synchronize_session": synchronize_session}, future=True, ) @@ -3060,7 +3073,7 @@ class Query( upd._where_criteria = self._where_criteria result = self.session.execute( upd, - self.load_options._params, + self._params, execution_options={"synchronize_session": synchronize_session}, future=True, ) @@ -3104,6 +3117,7 @@ class Query( context = QueryContext( compile_state, compile_state.statement, + self._params, self.session, self.load_options, ) diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 25aedd52d..339c57bdc 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -36,7 +36,6 @@ from ..inspection import inspect from ..sql import coercions from ..sql import dml from ..sql import roles -from ..sql import selectable from ..sql import visitors from ..sql.base import CompileState @@ -235,17 +234,22 @@ class ORMExecuteState(util.MemoizedSlots): @property def is_select(self): """return True if this is a SELECT operation.""" - return isinstance(self.statement, selectable.Select) + return self.statement.is_select + + @property + def is_insert(self): + """return True if this is an INSERT operation.""" + return self.statement.is_dml and self.statement.is_insert @property def is_update(self): """return True if this is an UPDATE operation.""" - return isinstance(self.statement, dml.Update) + return self.statement.is_dml and self.statement.is_update @property def is_delete(self): """return True if this is a DELETE operation.""" - return isinstance(self.statement, dml.Delete) + return self.statement.is_dml and self.statement.is_delete @property def _is_crud(self): @@ -1622,7 +1626,12 @@ class Session(_SessionClassMethods): if compile_state_cls: result = compile_state_cls.orm_setup_cursor_result( - self, statement, execution_options, bind_arguments, result + self, + statement, + params, + execution_options, + bind_arguments, + result, ) return result diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index db82f0b74..44f303fee 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -26,6 +26,7 @@ from .base import _RAISE_FOR_STATE from .base import _SET_DEFERRED_EXPIRED from .context import _column_descriptions from .context import ORMCompileState +from .context import QueryContext from .interfaces import LoaderStrategy from .interfaces import StrategizedProperty from .session import _state_session @@ -34,7 +35,6 @@ from .util import _none_set from .util import aliased from .. import event from .. import exc as sa_exc -from .. import future from .. import inspect from .. import log from .. import sql @@ -487,7 +487,7 @@ class DeferredColumnLoader(LoaderStrategy): if ( loading.load_on_ident( session, - future.select(localparent).apply_labels(), + sql.select(localparent).apply_labels(), state.key, only_load_props=group, refresh_state=state, @@ -620,7 +620,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): "_simple_lazy_clause", "_raise_always", "_raise_on_sql", - "_bakery", + "_query_cache", ) def __init__(self, parent, strategy_key): @@ -881,83 +881,68 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): for pk in self.mapper.primary_key ] - @util.preload_module("sqlalchemy.ext.baked") - def _memoized_attr__bakery(self): - return util.preloaded.ext_baked.bakery(size=50) + def _memoized_attr__query_cache(self): + return util.LRUCache(30) @util.preload_module("sqlalchemy.orm.strategy_options") def _emit_lazyload(self, session, state, primary_key_identity, passive): - # emit lazy load now using BakedQuery, to cut way down on the overhead - # of generating queries. - # there are two big things we are trying to guard against here: - # - # 1. two different lazy loads that need to have a different result, - # being cached on the same key. The results between two lazy loads - # can be different due to the options passed to the query, which - # take effect for descendant objects. Therefore we have to make - # sure paths and load options generate good cache keys, and if they - # don't, we don't cache. - # 2. a lazy load that gets cached on a key that includes some - # "throwaway" object, like a per-query AliasedClass, meaning - # the cache key will never be seen again and the cache itself - # will fill up. (the cache is an LRU cache, so while we won't - # run out of memory, it will perform terribly when it's full. A - # warning is emitted if this occurs.) We must prevent the - # generation of a cache key that is including a throwaway object - # in the key. - strategy_options = util.preloaded.orm_strategy_options - # note that "lazy='select'" and "lazy=True" make two separate - # lazy loaders. Currently the LRU cache is local to the LazyLoader, - # however add ourselves to the initial cache key just to future - # proof in case it moves - q = self._bakery(lambda session: session.query(self.entity), self) - - q.add_criteria( - lambda q: q._with_invoke_all_eagers(False), self.parent_property, + stmt = sql.lambda_stmt( + lambda: sql.select(self.entity) + .apply_labels() + ._set_compile_options(ORMCompileState.default_compile_options), + global_track_bound_values=False, + lambda_cache=self._query_cache, + track_on=(self,), ) if not self.parent_property.bake_queries: - q.spoil(full=True) + stmt = stmt.spoil() + + load_options = QueryContext.default_load_options + + load_options += { + "_invoke_all_eagers": False, + "_lazy_loaded_from": state, + } if self.parent_property.secondary is not None: - q.add_criteria( - lambda q: q.select_from( - self.mapper, self.parent_property.secondary - ) + stmt += lambda stmt: stmt.select_from( + self.mapper, self.parent_property.secondary ) pending = not state.key # don't autoflush on pending if pending or passive & attributes.NO_AUTOFLUSH: - q.add_criteria(lambda q: q.autoflush(False)) + stmt += lambda stmt: stmt.execution_options(autoflush=False) if state.load_options: - # here, if any of the options cannot return a cache key, - # the BakedQuery "spoils" and caching will not occur. a path - # that features Cls.attribute.of_type(some_alias) will cancel - # caching, for example, since "some_alias" is user-defined and - # is usually a throwaway object. + effective_path = state.load_path[self.parent_property] - q._add_lazyload_options(state.load_options, effective_path) + opts = list(state.load_options) + + stmt += lambda stmt: stmt.options(*opts) + stmt += lambda stmt: stmt._update_compile_options( + {"_current_path": effective_path} + ) if self.use_get: if self._raise_on_sql: self._invoke_raise_load(state, passive, "raise_on_sql") - return ( - q(session) - .with_post_criteria(lambda q: q._set_lazyload_from(state)) - ._load_on_pk_identity( - session, session.query(self.mapper), primary_key_identity - ) + return loading.load_on_pk_identity( + session, + stmt, + primary_key_identity, + load_options=load_options, + execution_options={"compiled_cache": self._query_cache}, ) if self._order_by: - q.add_criteria(lambda q: q.order_by(*self._order_by)) + stmt += lambda stmt: stmt.order_by(*self._order_by) def _lazyload_reverse(compile_context): for rev in self.parent_property._reverse_property: @@ -974,13 +959,18 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): ] ).lazyload(rev.key).process_compile_state(compile_context) - q.add_criteria( - lambda q: q._add_context_option( - _lazyload_reverse, self.parent_property - ) + stmt += lambda stmt: stmt._add_context_option( + _lazyload_reverse, self.parent_property ) lazy_clause, params = self._generate_lazy_clause(state, passive) + + execution_options = { + "_sa_orm_load_options": load_options, + } + if not self.parent_property.bake_queries: + execution_options["compiled_cache"] = None + if self.key in state.dict: return attributes.ATTR_WAS_SET @@ -994,21 +984,16 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): if self._raise_on_sql: self._invoke_raise_load(state, passive, "raise_on_sql") - q.add_criteria(lambda q: q.filter(lazy_clause)) - - # set parameters in the query such that we don't overwrite - # parameters that are already set within it - def set_default_params(q): - params.update(q.load_options._params) - q.load_options += {"_params": params} - return q + stmt = stmt.add_criteria( + lambda stmt: stmt.where(lazy_clause), enable_tracking=False + ) - result = ( - q(session) - .with_post_criteria(lambda q: q._set_lazyload_from(state)) - .with_post_criteria(set_default_params) - .all() + result = session.execute( + stmt, params, future=True, execution_options=execution_options ) + + result = result.unique().scalars().all() + if self.uselist: return result else: @@ -1409,6 +1394,7 @@ class SubqueryLoader(PostLoader): "session", "execution_options", "load_options", + "params", "subq", "_data", ) @@ -1419,6 +1405,7 @@ class SubqueryLoader(PostLoader): self.session = context.session self.execution_options = context.execution_options self.load_options = context.load_options + self.params = context.params or {} self.subq = subq self._data = None @@ -1443,7 +1430,7 @@ class SubqueryLoader(PostLoader): # to work with baked query, the parameters may have been # updated since this query was created, so take these into account - rows = list(q.params(self.load_options._params)) + rows = list(q.params(self.params)) for k, v in itertools.groupby(rows, lambda x: x[1:]): self._data[k].extend(vv[0] for vv in v) @@ -1519,19 +1506,16 @@ class SubqueryLoader(PostLoader): orig_query, "orm" ) - # this would create the full blown compile state, which we don't - # need - # orig_compile_state = compile_state_cls.create_for_statement( - # orig_query, None) - if orig_query._is_lambda_element: - util.warn( - 'subqueryloader for "%s" must invoke lambda callable at %r in ' - "order to produce a new query, decreasing the efficiency " - "of caching for this statement. Consider using " - "selectinload() for more effective full-lambda caching" - % (self, orig_query) - ) + if context.load_options._lazy_loaded_from is None: + util.warn( + 'subqueryloader for "%s" must invoke lambda callable ' + "at %r in " + "order to produce a new query, decreasing the efficiency " + "of caching for this statement. Consider using " + "selectinload() for more effective full-lambda caching" + % (self, orig_query) + ) orig_query = orig_query._resolved # this is the more "quick" version, however it's not clear how @@ -2112,7 +2096,7 @@ class JoinedLoader(AbstractRelationshipLoader): else: # all other cases are innerjoin=='nested' approach eagerjoin = self._splice_nested_inner_join( - path, towrap, clauses, onclause + path, towrap, clauses, onclause, ) compile_state.eager_joins[query_entity_key] = eagerjoin @@ -2153,7 +2137,7 @@ class JoinedLoader(AbstractRelationshipLoader): assert isinstance(join_obj, orm_util._ORMJoin) elif isinstance(join_obj, sql.selectable.FromGrouping): return self._splice_nested_inner_join( - path, join_obj.element, clauses, onclause, splicing + path, join_obj.element, clauses, onclause, splicing, ) elif not isinstance(join_obj, orm_util._ORMJoin): if path[-2] is splicing: @@ -2170,12 +2154,12 @@ class JoinedLoader(AbstractRelationshipLoader): return None target_join = self._splice_nested_inner_join( - path, join_obj.right, clauses, onclause, join_obj._right_memo + path, join_obj.right, clauses, onclause, join_obj._right_memo, ) if target_join is None: right_splice = False target_join = self._splice_nested_inner_join( - path, join_obj.left, clauses, onclause, join_obj._left_memo + path, join_obj.left, clauses, onclause, join_obj._left_memo, ) if target_join is None: # should only return None when recursively called, @@ -2401,7 +2385,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): "_parent_alias", "_query_info", "_fallback_query_info", - "_bakery", + "_query_cache", ) query_info = collections.namedtuple( @@ -2504,9 +2488,8 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): (("lazy", "select"),) ).init_class_attribute(mapper) - @util.preload_module("sqlalchemy.ext.baked") - def _memoized_attr__bakery(self): - return util.preloaded.ext_baked.bakery(size=50) + def _memoized_attr__query_cache(self): + return util.LRUCache(30) def create_row_processor( self, @@ -2564,9 +2547,8 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): with_poly_entity = path_w_prop.get( context.attributes, "path_with_polymorphic", None ) - if with_poly_entity is not None: - effective_entity = with_poly_entity + effective_entity = inspect(with_poly_entity) else: effective_entity = self.entity @@ -2645,32 +2627,37 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): # we need to adapt our "pk_cols" and "in_expr" to that # entity. in non-"omit join" mode, these are against the # parent entity and do not need adaption. - insp = inspect(effective_entity) - if insp.is_aliased_class: - pk_cols = [insp._adapt_element(col) for col in pk_cols] - in_expr = insp._adapt_element(in_expr) - pk_cols = [insp._adapt_element(col) for col in pk_cols] - - q = self._bakery( - lambda session: session.query( + if effective_entity.is_aliased_class: + pk_cols = [ + effective_entity._adapt_element(col) for col in pk_cols + ] + in_expr = effective_entity._adapt_element(in_expr) + + q = sql.lambda_stmt( + lambda: sql.select( orm_util.Bundle("pk", *pk_cols), effective_entity - ), - self, + ).apply_labels(), + lambda_cache=self._query_cache, + global_track_bound_values=False, + track_on=(self, effective_entity,) + tuple(pk_cols), ) + if not self.parent_property.bake_queries: + q = q.spoil() + if not query_info.load_with_join: # the Bundle we have in the "omit_join" case is against raw, non # annotated columns, so to ensure the Query knows its primary # entity, we add it explicitly. If we made the Bundle against # annotated columns, we hit a performance issue in this specific # case, which is detailed in issue #4347. - q.add_criteria(lambda q: q.select_from(effective_entity)) + q = q.add_criteria(lambda q: q.select_from(effective_entity)) else: # in the non-omit_join case, the Bundle is against the annotated/ # mapped column of the parent entity, but the #4347 issue does not # occur in this case. pa = self._parent_alias - q.add_criteria( + q = q.add_criteria( lambda q: q.select_from(pa).join( getattr(pa, self.parent_property.key).of_type( effective_entity @@ -2678,18 +2665,9 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): ) ) - if query_info.load_only_child: - q.add_criteria( - lambda q: q.filter( - in_expr.in_(sql.bindparam("primary_keys", expanding=True)) - ) - ) - else: - q.add_criteria( - lambda q: q.filter( - in_expr.in_(sql.bindparam("primary_keys", expanding=True)) - ) - ) + q = q.add_criteria( + lambda q: q.filter(in_expr.in_(sql.bindparam("primary_keys"))) + ) # a test which exercises what these comments talk about is # test_selectin_relations.py -> test_twolevel_selectin_w_polymorphic @@ -2715,31 +2693,39 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): # that query will be in terms of the effective entity we were just # handed. # - # But now the selectinload/ baked query we are running is *also* + # But now the selectinload query we are running is *also* # cached. What if it's cached and running from some previous iteration # of that AliasedInsp? Well in that case it will also use the previous - # iteration of the loader options. If the baked query expires and + # iteration of the loader options. If the query expires and # gets generated again, it will be handed the current effective_entity # and the current _with_options, again in terms of whatever # compile_state.select_statement happens to be right now, so the # query will still be internally consistent and loader callables # will be correctly invoked. - q._add_lazyload_options( - orig_query._with_options, path[self.parent_property] + effective_path = path[self.parent_property] + + options = orig_query._with_options + q = q.add_criteria( + lambda q: q.options(*options)._update_compile_options( + {"_current_path": effective_path} + ) ) if context.populate_existing: - q.add_criteria(lambda q: q.populate_existing()) + q = q.add_criteria( + lambda q: q.execution_options(populate_existing=True) + ) if self.parent_property.order_by: if not query_info.load_with_join: eager_order_by = self.parent_property.order_by - if insp.is_aliased_class: + if effective_entity.is_aliased_class: eager_order_by = [ - insp._adapt_element(elem) for elem in eager_order_by + effective_entity._adapt_element(elem) + for elem in eager_order_by ] - q.add_criteria(lambda q: q.order_by(*eager_order_by)) + q = q.add_criteria(lambda q: q.order_by(*eager_order_by)) else: def _setup_outermost_orderby(compile_context): @@ -2747,7 +2733,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): util.to_list(self.parent_property.order_by) ) - q.add_criteria( + q = q.add_criteria( lambda q: q._add_context_option( _setup_outermost_orderby, self.parent_property ) @@ -2770,11 +2756,16 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): our_keys = our_keys[self._chunksize :] data = { k: v - for k, v in q(context.session).params( - primary_keys=[ - key[0] if query_info.zero_idx else key for key in chunk - ] - ) + for k, v in context.session.execute( + q, + params={ + "primary_keys": [ + key[0] if query_info.zero_idx else key + for key in chunk + ] + }, + future=True, + ).unique() } for key in chunk: @@ -2817,7 +2808,9 @@ class SelectInLoader(PostLoader, util.MemoizedSlots): data = collections.defaultdict(list) for k, v in itertools.groupby( - q(context.session).params(primary_keys=primary_keys), + context.session.execute( + q, params={"primary_keys": primary_keys}, future=True + ).unique(), lambda x: x[0], ): data[k].extend(vv[1] for vv in v) diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index ffaa93404..b405153b9 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -352,7 +352,6 @@ class Load(Generative, LoaderOption): self, attr, strategy, propagate_to_loaders=True ): strategy = self._coerce_strat(strategy) - self.propagate_to_loaders = propagate_to_loaders cloned = self._clone_for_bind_strategy(attr, strategy, "relationship") self.path = cloned.path @@ -577,6 +576,7 @@ class _UnboundLoad(Load): if attr: path = path + (attr,) self.path = path + return path def __getstate__(self): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 68ffa2393..71ee29597 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -806,7 +806,7 @@ class AliasedInsp( return {} @util.memoized_property - def columns(self): + def _all_column_expressions(self): if self._is_with_polymorphic: cols_plus_keys = self.mapper._columns_plus_keys( [ent.mapper for ent in self._with_polymorphic_entities] diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index 36a8151d3..dc2804691 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -16,6 +16,7 @@ import re from . import roles from .traversals import HasCacheKey # noqa +from .traversals import HasCopyInternals # noqa from .traversals import MemoizedHasCacheKey # noqa from .visitors import ClauseVisitor from .visitors import ExtendedInternalTraversal @@ -699,6 +700,20 @@ class CacheableOptions(Options, HasCacheKey): return HasCacheKey._generate_cache_key_for_object(self) +class ExecutableOption(HasCopyInternals, HasCacheKey): + _annotations = util.EMPTY_DICT + + __visit_name__ = "executable_option" + + def _clone(self): + """Create a shallow copy of this ExecutableOption. + + """ + c = self.__class__.__new__(self.__class__) + c.__dict__ = dict(self.__dict__) + return c + + class Executable(Generative): """Mark a :class:`_expression.ClauseElement` as supporting execution. @@ -715,11 +730,18 @@ class Executable(Generative): _with_context_options = () _executable_traverse_internals = [ - ("_with_options", ExtendedInternalTraversal.dp_has_cache_key_list), + ("_with_options", InternalTraversal.dp_executable_options), ("_with_context_options", ExtendedInternalTraversal.dp_plain_obj), ("_propagate_attrs", ExtendedInternalTraversal.dp_propagate_attrs), ] + is_select = False + is_update = False + is_insert = False + is_text = False + is_delete = False + is_dml = False + @property def _effective_plugin_target(self): return self.__visit_name__ @@ -759,6 +781,22 @@ class Executable(Generative): ) @_generative + def _set_compile_options(self, compile_options): + """Assign the compile options to a new value. + + :param compile_options: appropriate CacheableOptions structure + + """ + + self._compile_options = compile_options + + @_generative + def _update_compile_options(self, options): + """update the _compile_options with new keys.""" + + self._compile_options += options + + @_generative def _add_context_option(self, callable_, cache_args): """Add a context option to this statement. diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index a8bd1de33..ac4055bdf 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -353,6 +353,7 @@ class FromLinter(collections.namedtuple("FromLinter", ["froms", "edges"])): message = template.format( froms=froms_str, start=self.froms[start_with] ) + util.warn(message) @@ -713,7 +714,10 @@ class SQLCompiler(Compiled): self.cache_key = cache_key if cache_key: - self._cache_key_bind_match = {b: b for b in cache_key[1]} + self._cache_key_bind_match = ckbm = { + b.key: b for b in cache_key[1] + } + ckbm.update({b: b for b in cache_key[1]}) # compile INSERT/UPDATE defaults/sequences to expect executemany # style execution, which may mean no pre-execute of defaults, @@ -2166,7 +2170,10 @@ class SQLCompiler(Compiled): # key was been generated. ckbm = self._cache_key_bind_match if ckbm: - ckbm.update({bp: bindparam for bp in bindparam._cloned_set}) + for bp in bindparam._cloned_set: + if bp.key in ckbm: + cb = ckbm[bp.key] + ckbm[cb] = bindparam if bindparam.isoutparam: self.has_out_parameters = True @@ -2186,6 +2193,7 @@ class SQLCompiler(Compiled): expanding=bindparam.expanding, **kwargs ) + if bindparam.expanding: ret = "(%s)" % ret return ret diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py index 81baa9f52..21476c1f9 100644 --- a/lib/sqlalchemy/sql/dml.py +++ b/lib/sqlalchemy/sql/dml.py @@ -194,6 +194,8 @@ class UpdateBase( _hints = util.immutabledict() named_with_column = False + is_dml = True + @classmethod def _constructor_20_deprecations(cls, fn_name, clsname, names): @@ -774,6 +776,8 @@ class Insert(ValuesBase): select = None include_insert_from_select_defaults = False + is_insert = True + _traverse_internals = ( [ ("table", InternalTraversal.dp_clauseelement), @@ -1002,6 +1006,8 @@ class Update(DMLWhereBase, ValuesBase): __visit_name__ = "update" + is_update = True + _traverse_internals = ( [ ("table", InternalTraversal.dp_clauseelement), @@ -1247,6 +1253,8 @@ class Delete(DMLWhereBase, UpdateBase): __visit_name__ = "delete" + is_delete = True + _traverse_internals = ( [ ("table", InternalTraversal.dp_clauseelement), diff --git a/lib/sqlalchemy/sql/lambdas.py b/lib/sqlalchemy/sql/lambdas.py index 327003902..7d52f97ee 100644 --- a/lib/sqlalchemy/sql/lambdas.py +++ b/lib/sqlalchemy/sql/lambdas.py @@ -1,5 +1,5 @@ # sql/lambdas.py -# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors # <see AUTHORS file> # # This module is part of SQLAlchemy and is released under diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index 850159323..fb1edd38f 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -620,10 +620,13 @@ class QueryTest(NoCache, fixtures.MappedTest): class SelectInEagerLoadTest(NoCache, fixtures.MappedTest): - """basic test for selectin() loading, which uses a baked query. + """basic test for selectin() loading, which uses a lambda query. - if the baked query starts spoiling due to some bug in cache keys, - this callcount blows up. + For the previous "baked query" version of this, statement caching + was still taking effect as the selectinloader used its own baked + query cache. in 1.4 we align the loader caches with the global + "cache_size" (tenatitively) so the callcount has gone up to accommodate + for 3x the compilations. """ @@ -888,7 +891,7 @@ class JoinedEagerLoadTest(NoCache, fixtures.MappedTest): r.context.compiled.compile_state = compile_state obj = ORMCompileState.orm_setup_cursor_result( - sess, compile_state.statement, exec_opts, {}, r, + sess, compile_state.statement, {}, exec_opts, {}, r, ) list(obj) sess.close() diff --git a/test/ext/test_baked.py b/test/ext/test_baked.py index 791b04b57..6279dcf55 100644 --- a/test/ext/test_baked.py +++ b/test/ext/test_baked.py @@ -5,15 +5,9 @@ from sqlalchemy import bindparam from sqlalchemy import event from sqlalchemy import exc as sa_exc from sqlalchemy import func -from sqlalchemy import literal_column from sqlalchemy import testing from sqlalchemy.ext import baked -from sqlalchemy.orm import aliased -from sqlalchemy.orm import backref -from sqlalchemy.orm import defaultload from sqlalchemy.orm import exc as orm_exc -from sqlalchemy.orm import lazyload -from sqlalchemy.orm import Load from sqlalchemy.orm import mapper from sqlalchemy.orm import relationship from sqlalchemy.orm import Session @@ -24,7 +18,6 @@ from sqlalchemy.testing import eq_ from sqlalchemy.testing import is_ from sqlalchemy.testing import is_not_ from sqlalchemy.testing import mock -from sqlalchemy.testing.assertsql import CompiledSQL from test.orm import _fixtures @@ -971,654 +964,6 @@ class ResultTest(BakedTest): self.assert_sql_count(testing.db, go, 2) -class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest): - run_setup_mappers = "each" - - @testing.fixture - def modify_query_fixture(self): - def set_event(bake_ok): - - event.listen( - Query, - "before_compile", - _modify_query, - retval=True, - bake_ok=bake_ok, - ) - return m1 - - m1 = mock.Mock() - - def _modify_query(query): - m1(query.column_descriptions[0]["entity"]) - query = query.enable_assertions(False).filter( - literal_column("1") == 1 - ) - return query - - yield set_event - event.remove(Query, "before_compile", _modify_query) - - def _o2m_fixture(self, lazy="select", **kw): - User = self.classes.User - Address = self.classes.Address - - mapper( - User, - self.tables.users, - properties={ - "addresses": relationship( - Address, - order_by=self.tables.addresses.c.id, - lazy=lazy, - **kw - ) - }, - ) - mapper(Address, self.tables.addresses) - return User, Address - - def _o2m_twolevel_fixture(self, lazy="select", **kw): - User = self.classes.User - Address = self.classes.Address - Dingaling = self.classes.Dingaling - - mapper( - User, - self.tables.users, - properties={ - "addresses": relationship( - Address, - order_by=self.tables.addresses.c.id, - lazy=lazy, - **kw - ) - }, - ) - mapper( - Address, - self.tables.addresses, - properties={"dingalings": relationship(Dingaling, lazy=lazy)}, - ) - mapper(Dingaling, self.tables.dingalings) - return User, Address, Dingaling - - def _o2m_threelevel_fixture(self, lazy="select", **kw): - Order = self.classes.Order - User = self.classes.User - Address = self.classes.Address - Dingaling = self.classes.Dingaling - - mapper( - Order, self.tables.orders, properties={"user": relationship(User)} - ) - - mapper( - User, - self.tables.users, - properties={ - "addresses": relationship( - Address, - order_by=self.tables.addresses.c.id, - lazy=lazy, - **kw - ) - }, - ) - mapper( - Address, - self.tables.addresses, - properties={"dingalings": relationship(Dingaling, lazy=lazy)}, - ) - mapper(Dingaling, self.tables.dingalings) - return Order, User, Address, Dingaling - - def _m2o_fixture(self): - User = self.classes.User - Address = self.classes.Address - - mapper(User, self.tables.users) - mapper( - Address, - self.tables.addresses, - properties={"user": relationship(User)}, - ) - return User, Address - - def test_same_lazyload_multiple_paths(self): - """this is an initial test that requires the presence of - the ResultMetaData._adapt_to_context method. - - Both queries emit a lazyload against User.addresses that then - includes a subqueryload against Address.dingalings. - - The subqueryload produces an adapted query that has anonymized - columns. for these to be locatable in the result, the columns in - the adapted query have to match up to the result map, which is not - the case if the statement is pulled from the cache. - - The query is done in two different ways so that one lazyload will not - have a state.load_path on User, and the other one will. this means - there will be two different baked queries that both produce the same - Core SELECT statement. Using logical cache keys in Core, rather than - based on identity, means two different BakedContext objects will use - the same Core context. For this reason, at result fetch time the - ResultMetaData._adapt_to_context step is required so that the ORM can - still locate the columns. - - This will all be done differently with direct ORM results, however this - same issue that ResultMetaData has to be adapted to the live statement - will exist indepndently of baked queries. More statement-level caching - tests need to be added which test with complex statements that include - anonymous columns. - - """ - Order, User, Address, Dingaling = self._o2m_threelevel_fixture() - - sess = Session() - from sqlalchemy.orm import joinedload - - o1 = ( - sess.query(Order) - .options( - joinedload(Order.user) - .lazyload(User.addresses) - .subqueryload(Address.dingalings) - ) - .filter(Order.id == 2) - .first() - ) - assert o1.user.addresses[0].dingalings - - sess.expire_all() - - u1 = ( - sess.query(User) - .options(lazyload(User.addresses).subqueryload(Address.dingalings)) - .filter(User.id == 9) - .first() - ) - assert u1.addresses[0].dingalings - - def test_no_cache_for_event(self, modify_query_fixture): - - m1 = modify_query_fixture(False) - - User, Address = self._o2m_fixture() - - sess = Session() - u1 = sess.query(User).filter(User.id == 7).first() - - u1.addresses - - eq_(m1.mock_calls, [mock.call(User), mock.call(Address)]) - - sess.expire(u1, ["addresses"]) - - u1.addresses - eq_( - m1.mock_calls, - [mock.call(User), mock.call(Address), mock.call(Address)], - ) - - def test_cache_ok_for_event(self, modify_query_fixture): - - m1 = modify_query_fixture(True) - - User, Address = self._o2m_fixture() - - sess = Session() - u1 = sess.query(User).filter(User.id == 7).first() - - u1.addresses - - eq_(m1.mock_calls, [mock.call(User), mock.call(Address)]) - - sess.expire(u1, ["addresses"]) - - u1.addresses - eq_(m1.mock_calls, [mock.call(User), mock.call(Address)]) - - def test_aliased_unbound_are_now_safe_to_cache(self): - User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") - - class SubDingaling(Dingaling): - pass - - mapper(SubDingaling, None, inherits=Dingaling) - - lru = Address.dingalings.property._lazy_strategy._bakery( - lambda q: None - )._bakery - l1 = len(lru) - for i in range(5): - sess = Session() - u1 = ( - sess.query(User) - .options( - defaultload(User.addresses).lazyload( - Address.dingalings.of_type(aliased(SubDingaling)) - ) - ) - .first() - ) - for ad in u1.addresses: - ad.dingalings - l2 = len(lru) - eq_(l1, 0) - eq_(l2, 2) - - def test_aliased_bound_are_now_safe_to_cache(self): - User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") - - class SubDingaling(Dingaling): - pass - - mapper(SubDingaling, None, inherits=Dingaling) - - lru = Address.dingalings.property._lazy_strategy._bakery( - lambda q: None - )._bakery - l1 = len(lru) - for i in range(5): - sess = Session() - u1 = ( - sess.query(User) - .options( - Load(User) - .defaultload(User.addresses) - .lazyload( - Address.dingalings.of_type(aliased(SubDingaling)) - ) - ) - .first() - ) - for ad in u1.addresses: - ad.dingalings - l2 = len(lru) - eq_(l1, 0) - eq_(l2, 2) - - def test_safe_unbound_option_allows_bake(self): - User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") - - lru = Address.dingalings.property._lazy_strategy._bakery( - lambda q: None - )._bakery - l1 = len(lru) - for i in range(5): - sess = Session() - u1 = ( - sess.query(User) - .options( - defaultload(User.addresses).lazyload(Address.dingalings) - ) - .first() - ) - for ad in u1.addresses: - ad.dingalings - l2 = len(lru) - eq_(l1, 0) - eq_(l2, 2) - - def test_safe_bound_option_allows_bake(self): - User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") - - lru = Address.dingalings.property._lazy_strategy._bakery( - lambda q: None - )._bakery - l1 = len(lru) - for i in range(5): - sess = Session() - u1 = ( - sess.query(User) - .options( - Load(User) - .defaultload(User.addresses) - .lazyload(Address.dingalings) - ) - .first() - ) - for ad in u1.addresses: - ad.dingalings - l2 = len(lru) - eq_(l1, 0) - eq_(l2, 2) - - def test_baked_lazy_loading_relationship_flag_true(self): - self._test_baked_lazy_loading_relationship_flag(True) - - def test_baked_lazy_loading_relationship_flag_false(self): - self._test_baked_lazy_loading_relationship_flag(False) - - def _test_baked_lazy_loading_relationship_flag(self, flag): - User, Address = self._o2m_fixture(bake_queries=flag) - from sqlalchemy import inspect - from sqlalchemy.orm.interfaces import UserDefinedOption - - address_mapper = inspect(Address) - sess = Session(testing.db) - - # there's no event in the compile process either at the ORM - # or core level and it is not easy to patch. the option object - # is the one thing that will get carried into the lazyload from the - # outside and invoked on a per-compile basis - - class MockOpt(UserDefinedOption): - _is_compile_state = True - propagate_to_loaders = True - _is_legacy_option = True - - def _gen_cache_key(self, *args): - return ("hi",) - - def _generate_path_cache_key(self, *args): - return ("hi",) - - def _generate_cache_key(self, *args): - return (("hi",), []) - - _mock = mock.Mock() - - def process_query(self, *args): - self._mock.process_query(*args) - - def process_query_conditionally(self, *args): - self._mock.process_query_conditionally(*args) - - def process_compile_state(self, *args): - self._mock.process_compile_state(*args) - - def orm_execute(self): - self._mock.orm_execute() - - @property - def mock_calls(self): - return self._mock.mock_calls - - mock_opt = MockOpt() - - u1 = sess.query(User).options(mock_opt).first() - - @event.listens_for(sess, "do_orm_execute") - def _my_compile_state(context): - if ( - context.statement._raw_columns[0]._annotations["parententity"] - is address_mapper - ): - mock_opt.orm_execute() - - u1.addresses - - sess.expire(u1) - u1.addresses - - if flag: - eq_( - mock_opt.mock_calls, - [ - mock.call.process_query(mock.ANY), - mock.call.process_compile_state(mock.ANY), # query.first() - mock.call.process_query_conditionally(mock.ANY), - mock.call.orm_execute(), # lazyload addresses - mock.call.process_compile_state(mock.ANY), # emit lazyload - mock.call.process_compile_state( - mock.ANY - ), # load scalar attributes for user - # lazyload addresses, no call to process_compile_state - mock.call.orm_execute(), - ], - ) - else: - eq_( - mock_opt.mock_calls, - [ - mock.call.process_query(mock.ANY), - mock.call.process_compile_state(mock.ANY), # query.first() - mock.call.process_query_conditionally(mock.ANY), - mock.call.orm_execute(), # lazyload addresses - mock.call.process_compile_state(mock.ANY), # emit_lazyload - mock.call.process_compile_state( - mock.ANY - ), # load_scalar_attributes for user - mock.call.process_query_conditionally(mock.ANY), - mock.call.orm_execute(), # lazyload addresses - mock.call.process_compile_state( - mock.ANY - ), # emit_lazyload, here the query was not cached - ], - ) - - def test_baked_lazy_loading_option_o2m(self): - User, Address = self._o2m_fixture() - self._test_baked_lazy_loading(set_option=True) - - def test_baked_lazy_loading_mapped_o2m(self): - User, Address = self._o2m_fixture(lazy="baked_select") - self._test_baked_lazy_loading(set_option=False) - - def _test_baked_lazy_loading(self, set_option): - User, Address = self.classes.User, self.classes.Address - - base_bq = self.bakery(lambda s: s.query(User)) - - if set_option: - base_bq += lambda q: q.options(lazyload(User.addresses)) - - base_bq += lambda q: q.order_by(User.id) - - assert_result = self.static.user_address_result - - for i in range(4): - for cond1, cond2 in itertools.product( - *[(False, True) for j in range(2)] - ): - - bq = base_bq._clone() - sess = Session() - - if cond1: - bq += lambda q: q.filter(User.name == "jack") - else: - bq += lambda q: q.filter(User.name.like("%ed%")) - - if cond2: - ct = func.count(Address.id).label("count") - subq = ( - sess.query(ct, Address.user_id) - .group_by(Address.user_id) - .having(ct > 2) - .subquery() - ) - - bq += lambda q: q.join(subq) - - if cond2: - if cond1: - - def go(): - result = bq(sess).all() - eq_([], result) - - self.assert_sql_count(testing.db, go, 1) - else: - - def go(): - result = bq(sess).all() - eq_(assert_result[1:2], result) - - self.assert_sql_count(testing.db, go, 2) - else: - if cond1: - - def go(): - result = bq(sess).all() - eq_(assert_result[0:1], result) - - self.assert_sql_count(testing.db, go, 2) - else: - - def go(): - result = bq(sess).all() - eq_(assert_result[1:3], result) - - self.assert_sql_count(testing.db, go, 3) - - sess.close() - - def test_baked_lazy_loading_m2o(self): - User, Address = self._m2o_fixture() - - base_bq = self.bakery(lambda s: s.query(Address)) - - base_bq += lambda q: q.options(lazyload(Address.user)) - base_bq += lambda q: q.order_by(Address.id) - - assert_result = self.static.address_user_result - - for i in range(4): - for cond1 in (False, True): - bq = base_bq._clone() - - sess = Session() - - if cond1: - bq += lambda q: q.filter( - Address.email_address == "jack@bean.com" - ) - else: - bq += lambda q: q.filter( - Address.email_address.like("ed@%") - ) - - if cond1: - - def go(): - result = bq(sess).all() - eq_(assert_result[0:1], result) - - self.assert_sql_count(testing.db, go, 2) - else: - - def go(): - result = bq(sess).all() - eq_(assert_result[1:4], result) - - self.assert_sql_count(testing.db, go, 2) - - sess.close() - - def test_useget_cancels_eager(self): - """test that a one to many lazyload cancels the unnecessary - eager many-to-one join on the other side.""" - - User = self.classes.User - Address = self.classes.Address - - mapper(User, self.tables.users) - mapper( - Address, - self.tables.addresses, - properties={ - "user": relationship( - User, - lazy="joined", - backref=backref("addresses", lazy="baked_select"), - ) - }, - ) - - sess = Session() - u1 = sess.query(User).filter(User.id == 8).one() - - def go(): - eq_(u1.addresses[0].user, u1) - - self.assert_sql_execution( - testing.db, - go, - CompiledSQL( - "SELECT addresses.id AS addresses_id, addresses.user_id AS " - "addresses_user_id, addresses.email_address AS " - "addresses_email_address FROM addresses WHERE :param_1 = " - "addresses.user_id", - {"param_1": 8}, - ), - ) - - def test_useget_cancels_eager_propagated_present(self): - """test that a one to many lazyload cancels the unnecessary - eager many-to-one join on the other side, even when a propagated - option is present.""" - - User = self.classes.User - Address = self.classes.Address - - mapper(User, self.tables.users) - mapper( - Address, - self.tables.addresses, - properties={ - "user": relationship( - User, - lazy="joined", - backref=backref("addresses", lazy="baked_select"), - ) - }, - ) - - from sqlalchemy.orm.interfaces import MapperOption - - class MyBogusOption(MapperOption): - propagate_to_loaders = True - - sess = Session() - u1 = ( - sess.query(User) - .options(MyBogusOption()) - .filter(User.id == 8) - .one() - ) - - def go(): - eq_(u1.addresses[0].user, u1) - - self.assert_sql_execution( - testing.db, - go, - CompiledSQL( - "SELECT addresses.id AS addresses_id, addresses.user_id AS " - "addresses_user_id, addresses.email_address AS " - "addresses_email_address FROM addresses WHERE :param_1 = " - "addresses.user_id", - {"param_1": 8}, - ), - ) - - def test_simple_lazy_clause_no_race_on_generate(self): - User, Address = self._o2m_fixture() - - ( - expr1, - paramdict1, - ) = User.addresses.property._lazy_strategy._simple_lazy_clause - - # delete the attr, as though a concurrent thread is also generating it - del User.addresses.property._lazy_strategy._simple_lazy_clause - ( - expr2, - paramdict2, - ) = User.addresses.property._lazy_strategy._simple_lazy_clause - - eq_(paramdict1, paramdict2) - - # additional tests: - # 1. m2m w lazyload - # 2. o2m lazyload where m2o backrefs have an eager load, test - # that eager load is canceled out - # 3. uselist = False, uselist=False assertion - - # assert that the integration style illustrated in the dogpile.cache # example works w/ baked class CustomIntegrationTest(testing.AssertsCompiledSQL, BakedTest): diff --git a/test/orm/inheritance/test_polymorphic_rel.py b/test/orm/inheritance/test_polymorphic_rel.py index d8214465a..e33e95cc0 100644 --- a/test/orm/inheritance/test_polymorphic_rel.py +++ b/test/orm/inheritance/test_polymorphic_rel.py @@ -1525,6 +1525,7 @@ class _PolymorphicTestBase(object): ) def test_self_referential_two(self): + sess = create_session() palias = aliased(Person) expected = [(m1, e1), (m1, e2), (m1, b1)] @@ -1540,13 +1541,62 @@ class _PolymorphicTestBase(object): expected, ) + def test_self_referential_two_point_five(self): + """Using two aliases, the above case works. + """ + sess = create_session() + palias = aliased(Person) + palias2 = aliased(Person) + + expected = [(m1, e1), (m1, e2), (m1, b1)] + + eq_( + sess.query(palias, palias2) + .filter(palias.company_id == palias2.company_id) + .filter(palias.name == "dogbert") + .filter(palias.person_id > palias2.person_id) + .from_self() + .order_by(palias.person_id, palias2.person_id) + .all(), + expected, + ) + def test_self_referential_two_future(self): + # TODO: this is the SECOND test *EVER* of an aliased class of + # an aliased class. + sess = create_session(testing.db, future=True) + expected = [(m1, e1), (m1, e2), (m1, b1)] + + # not aliasing the first class + p1 = Person + p2 = aliased(Person) + stmt = ( + select(p1, p2) + .filter(p1.company_id == p2.company_id) + .filter(p1.name == "dogbert") + .filter(p1.person_id > p2.person_id) + ) + + subq = stmt.subquery() + + pa1 = aliased(p1, subq) + pa2 = aliased(p2, subq) + + stmt2 = select(pa1, pa2).order_by(pa1.person_id, pa2.person_id) + + eq_( + sess.execute(stmt2).unique().all(), expected, + ) + + def test_self_referential_two_point_five_future(self): + # TODO: this is the first test *EVER* of an aliased class of # an aliased class. we should add many more tests for this. # new case added in Id810f485c5f7ed971529489b84694e02a3356d6d sess = create_session(testing.db, future=True) expected = [(m1, e1), (m1, e2), (m1, b1)] + # aliasing the first class p1 = aliased(Person) p2 = aliased(Person) stmt = ( @@ -1560,10 +1610,10 @@ class _PolymorphicTestBase(object): pa1 = aliased(p1, subq) pa2 = aliased(p2, subq) - stmt = select(pa1, pa2).order_by(pa1.person_id, pa2.person_id) + stmt2 = select(pa1, pa2).order_by(pa1.person_id, pa2.person_id) eq_( - sess.execute(stmt).unique().all(), expected, + sess.execute(stmt2).unique().all(), expected, ) def test_nesting_queries(self): diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index cb9eca479..c7d2042b3 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -1729,10 +1729,15 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes() s = Session() + + # as of from_self() changing in + # I3abfb45dd6e50f84f29d39434caa0b550ce27864, + # this query is coming out instead which is equivalent, but not + # totally sure where this happens + self.assert_compile( s.query(Sub2).from_self().join(Sub2.ep1).join(Sub2.ep2), "SELECT anon_1.sub2_id AS anon_1_sub2_id, " - "anon_1.base2_id AS anon_1_base2_id, " "anon_1.base2_base1_id AS anon_1_base2_base1_id, " "anon_1.base2_data AS anon_1_base2_data, " "anon_1.sub2_subdata AS anon_1_sub2_subdata " @@ -1740,14 +1745,19 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): "base2.base1_id AS base2_base1_id, base2.data AS base2_data, " "sub2.subdata AS sub2_subdata " "FROM base2 JOIN sub2 ON base2.id = sub2.id) AS anon_1 " - "JOIN ep1 ON anon_1.base2_id = ep1.base2_id " - "JOIN ep2 ON anon_1.base2_id = ep2.base2_id", + "JOIN ep1 ON anon_1.sub2_id = ep1.base2_id " + "JOIN ep2 ON anon_1.sub2_id = ep2.base2_id", ) def test_seven(self): Parent, Base1, Base2, Sub1, Sub2, EP1, EP2 = self._classes() s = Session() + + # as of from_self() changing in + # I3abfb45dd6e50f84f29d39434caa0b550ce27864, + # this query is coming out instead which is equivalent, but not + # totally sure where this happens self.assert_compile( # adding Sub2 to the entities list helps it, # otherwise the joins for Sub2.ep1/ep2 don't have columns @@ -1761,7 +1771,6 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): "SELECT anon_1.parent_id AS anon_1_parent_id, " "anon_1.parent_data AS anon_1_parent_data, " "anon_1.sub2_id AS anon_1_sub2_id, " - "anon_1.base2_id AS anon_1_base2_id, " "anon_1.base2_base1_id AS anon_1_base2_base1_id, " "anon_1.base2_data AS anon_1_base2_data, " "anon_1.sub2_subdata AS anon_1_sub2_subdata " @@ -1775,8 +1784,8 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): "ON parent.id = sub1.parent_id JOIN " "(base2 JOIN sub2 ON base2.id = sub2.id) " "ON base1.id = base2.base1_id) AS anon_1 " - "JOIN ep1 ON anon_1.base2_id = ep1.base2_id " - "JOIN ep2 ON anon_1.base2_id = ep2.base2_id", + "JOIN ep1 ON anon_1.sub2_id = ep1.base2_id " + "JOIN ep2 ON anon_1.sub2_id = ep2.base2_id", ) diff --git a/test/orm/test_lazy_relations.py b/test/orm/test_lazy_relations.py index b2477cba7..c1cc85261 100644 --- a/test/orm/test_lazy_relations.py +++ b/test/orm/test_lazy_relations.py @@ -22,13 +22,11 @@ from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import mapper from sqlalchemy.orm import relationship from sqlalchemy.orm import Session -from sqlalchemy.orm.interfaces import MapperOption from sqlalchemy.testing import assert_raises from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures from sqlalchemy.testing import is_false from sqlalchemy.testing import is_true -from sqlalchemy.testing import mock from sqlalchemy.testing.assertsql import CompiledSQL from sqlalchemy.testing.schema import Column from sqlalchemy.testing.schema import Table @@ -405,73 +403,6 @@ class LazyTest(_fixtures.FixtureTest): fred = s.query(User).filter_by(name="fred").one() eq_(fred.addresses, []) # fred is missing - def test_custom_bind(self): - Address, addresses, users, User = ( - self.classes.Address, - self.tables.addresses, - self.tables.users, - self.classes.User, - ) - - mapper( - User, - users, - properties=dict( - addresses=relationship( - mapper(Address, addresses), - lazy="select", - primaryjoin=and_( - users.c.id == addresses.c.user_id, - users.c.name == bindparam("name"), - ), - ) - ), - ) - - canary = mock.Mock() - - class MyOption(MapperOption): - propagate_to_loaders = True - - def __init__(self, crit): - self.crit = crit - - def process_query_conditionally(self, query): - """process query during a lazyload""" - canary() - query.params.non_generative(query, dict(name=self.crit)) - - s = Session() - ed = s.query(User).options(MyOption("ed")).filter_by(name="ed").one() - eq_( - ed.addresses, - [ - Address(id=2, user_id=8), - Address(id=3, user_id=8), - Address(id=4, user_id=8), - ], - ) - eq_(canary.mock_calls, [mock.call()]) - - fred = ( - s.query(User).options(MyOption("ed")).filter_by(name="fred").one() - ) - eq_(fred.addresses, []) # fred is missing - eq_(canary.mock_calls, [mock.call(), mock.call()]) - - # the lazy query was not cached; the option is re-applied to the - # Fred object due to populate_existing() - fred = ( - s.query(User) - .populate_existing() - .options(MyOption("fred")) - .filter_by(name="fred") - .one() - ) - eq_(fred.addresses, [Address(id=5, user_id=9)]) # fred is there - - eq_(canary.mock_calls, [mock.call(), mock.call(), mock.call()]) - def test_one_to_many_scalar(self): Address, addresses, users, User = ( self.classes.Address, @@ -1398,8 +1329,8 @@ class O2MWOSideFixedTest(fixtures.MappedTest): go, CompiledSQL( "SELECT person.id AS person_id, person.city_id AS " - "person_city_id FROM person " - "WHERE person.city_id = :param_1 AND :param_2 = 0", + "person_city_id FROM person WHERE person.city_id = :param_1 " + "AND :param_2 = 0", {"param_1": 2, "param_2": 1}, ), ) @@ -1585,9 +1516,9 @@ class TypeCoerceTest(fixtures.MappedTest, testing.AssertsExecutionResults): asserter.assert_( CompiledSQL( - "SELECT pets.id AS pets_id, pets.person_id " - "AS pets_person_id FROM pets " - "WHERE pets.person_id = CAST(:param_1 AS INTEGER)", + "SELECT pets.id AS pets_id, pets.person_id AS " + "pets_person_id FROM pets WHERE pets.person_id = " + "CAST(:param_1 AS INTEGER)", [{"param_1": 5}], ) ) diff --git a/test/profiles.txt b/test/profiles.txt index ffff55d0e..b849e7602 100644 --- a/test/profiles.txt +++ b/test/profiles.txt @@ -165,66 +165,66 @@ test.aaa_profiling.test_misc.EnumTest.test_create_enum_from_pep_435_w_expensive_ # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 45105 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 55905 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 49105 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 60705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 46105 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 57205 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 50105 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 62005 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 44005 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 54805 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 48005 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 59605 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 45005 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 56105 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_cextensions 49005 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 60905 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43105 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 55505 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 44105 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 52705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 47405 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56805 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 42305 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 50605 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 45605 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 54705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43305 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51905 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46605 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56005 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_cextensions 41705 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 45005 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_cextensions 44405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 48505 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_cextensions 42705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 46305 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_cextensions 45405 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 49805 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43105 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 55505 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 44105 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 52705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 47405 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56805 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 42305 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 50605 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 45605 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 54705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 43305 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51905 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 46605 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 56005 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 27205 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 29405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 30105 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 32505 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 28205 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 30705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 31105 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 33805 # TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 26405 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 28605 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 29305 -test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 31705 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 27405 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 29905 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_cextensions 30305 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 33005 # TEST: test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set @@ -256,59 +256,59 @@ test.aaa_profiling.test_orm.BranchedOptionTest.test_query_opts_unbound_branching # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline -test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_cextensions 15150 -test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26159 -test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_cextensions 15188 -test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27200 +test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_cextensions 15158 +test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26170 +test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_cextensions 15196 +test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27211 # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols -test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 21294 -test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26303 -test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 21339 -test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27351 +test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 21312 +test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26324 +test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 21357 +test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 27372 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 9703 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 9853 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 10154 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 10304 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 9853 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 10003 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 10304 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 10454 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_cextensions 3953 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 4103 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_cextensions 3954 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 4104 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_cextensions 4053 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 4203 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_cextensions 4054 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 4204 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_cextensions 93738 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 94088 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_cextensions 101554 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 101704 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_cextensions 94638 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 94788 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_cextensions 102254 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 102204 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 91788 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 92138 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 99919 -test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 100069 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_cextensions 92688 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 92838 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_cextensions 100604 +test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d_aliased 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 100569 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 434915 -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 436762 -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_cextensions 465676 -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 467518 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 438105 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 439952 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_cextensions 468876 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 470718 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 391100 -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 405907 -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 395113 -test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 412627 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 406507 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 396913 +test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 412027 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity @@ -319,24 +319,24 @@ test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_ # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 77560 -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 79780 -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_cextensions 80098 -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 83365 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 80113 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 82391 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_cextensions 83854 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 87383 # TEST: test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 18701 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 19147 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_cextensions 19670 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 20154 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 20145 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 20583 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_cextensions 21187 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 21723 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load -test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1035 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1062 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1079 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 1113 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1313 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1345 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1384 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 1425 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load @@ -347,23 +347,23 @@ test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.8_sqlite_pysqlite_dba # TEST: test.aaa_profiling.test_orm.QueryTest.test_query_cols -test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 5306 -test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6026 -test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 5674 -test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 6414 +test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_cextensions 5397 +test.aaa_profiling.test_orm.QueryTest.test_query_cols 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6147 +test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_cextensions 5765 +test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 6535 # TEST: test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 152251 -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 168676 -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 159629 -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 177451 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 232364 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 248890 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 244874 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 262595 # TEST: test.aaa_profiling.test_orm.SessionTest.test_expire_lots -test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1135 -test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1150 -test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1241 +test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1141 +test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1156 +test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.8_sqlite_pysqlite_dbapiunicode_cextensions 1259 test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 1256 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect |