]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Refactor "get" to allow for pluggable identity token schemes
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 20 Apr 2018 15:44:09 +0000 (11:44 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 20 Apr 2018 18:36:00 +0000 (14:36 -0400)
Fixed regression in 1.2 within sharded query feature where the
new "identity_token" element was not being correctly considered within
the scope of a lazy load operation, when searching the identity map
for a related many-to-one element.   The new behavior will allow for
making use of the "id_chooser" in order to determine the best identity
key to retrieve from the identity map.  In order to achieve this, some
refactoring of 1.2's "identity_token" approach has made some slight changes
to the implementation of ``ShardedQuery`` which should be noted for other
derivations of this class.

Change-Id: I04fa60535deec2d0cdec89f602935dfebeb9eb9d
Fixes: #4228
doc/build/changelog/unreleased_12/4228.rst [new file with mode: 0644]
lib/sqlalchemy/ext/baked.py
lib/sqlalchemy/ext/horizontal_shard.py
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
test/ext/test_horizontal_shard.py
test/profiles.txt

diff --git a/doc/build/changelog/unreleased_12/4228.rst b/doc/build/changelog/unreleased_12/4228.rst
new file mode 100644 (file)
index 0000000..c0e503e
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 4228
+
+    Fixed regression in 1.2 within sharded query feature where the
+    new "identity_token" element was not being correctly considered within
+    the scope of a lazy load operation, when searching the identity map
+    for a related many-to-one element.   The new behavior will allow for
+    making use of the "id_chooser" in order to determine the best identity
+    key to retrieve from the identity map.  In order to achieve this, some
+    refactoring of 1.2's "identity_token" approach has made some slight changes
+    to the implementation of ``ShardedQuery`` which should be noted for other
+    derivations of this class.
index 86eee831b0a488f23d3a8d88d638f54bc8f6476f..f4d71f4103bf2405f62378c7538dc91ef41982d9 100644 (file)
@@ -446,12 +446,10 @@ class Result(object):
         """
 
         query = self.bq.steps[0](self.session)
-        return query._get_impl(ident, self._load_on_ident)
+        return query._get_impl(ident, self._load_on_pk_identity)
 
-    def _load_on_ident(self, query, key):
-        """Load the given identity key from the database."""
-
-        ident = key[1]
+    def _load_on_pk_identity(self, query, primary_key_identity):
+        """Load the given primary key identity from the database."""
 
         mapper = query._mapper_zero()
 
@@ -465,10 +463,11 @@ class Result(object):
 
             # None present in ident - turn those comparisons
             # into "IS NULL"
-            if None in ident:
+            if None in primary_key_identity:
                 nones = set([
                     _get_params[col].key for col, value in
-                    zip(mapper.primary_key, ident) if value is None
+                    zip(mapper.primary_key, primary_key_identity)
+                    if value is None
                 ])
                 _lcl_get_clause = sql_util.adapt_criterion_to_null(
                     _lcl_get_clause, nones)
@@ -490,11 +489,13 @@ class Result(object):
         bq = bq._clone()
         bq._cache_key += (_get_clause, )
 
-        bq = bq.with_criteria(setup, tuple(elem is None for elem in ident))
+        bq = bq.with_criteria(
+            setup, tuple(elem is None for elem in primary_key_identity))
 
         params = dict([
             (_get_params[primary_key].key, id_val)
-            for id_val, primary_key in zip(ident, mapper.primary_key)
+            for id_val, primary_key
+            in zip(primary_key_identity, mapper.primary_key)
         ])
 
         result = list(bq.for_session(self.session).params(**params))
index 039a4656b117b79270cc1e641ddd7ea57b04da4c..266bd784ed5792b041e4eac4ef281fc838cf0b90 100644 (file)
@@ -62,30 +62,65 @@ class ShardedQuery(Query):
             # were done, this is where it would happen
             return iter(partial)
 
-    def _get_impl(self, ident, fallback_fn):
-        # TODO: the "ident" here should be getting the identity token
-        # which indicates that this area can likely be simplified, as the
-        # token will fall through into _execute_and_instances
-        def _fallback(query, ident):
+    @classmethod
+    def _identity_lookup(
+            cls, session, mapper, primary_key_identity, identity_token=None,
+            **kw):
+        """override the default Query._identity_lookup method so that we
+        search for a given non-token primary key identity across all
+        possible identity tokens (e.g. shard ids).
+
+        """
+
+        if identity_token is not None:
+            return super(ShardedQuery, cls)._identity_lookup(
+                session, mapper, primary_key_identity,
+                identity_token=identity_token,
+                **kw
+            )
+        else:
+            q = cls([mapper], session)
+            for shard_id in q.id_chooser(q, primary_key_identity):
+                obj = super(ShardedQuery, cls)._identity_lookup(
+                    session, mapper, primary_key_identity,
+                    identity_token=shard_id,
+                    **kw
+                )
+                if obj is not None:
+                    return obj
+
+            return None
+
+    def _get_impl(
+            self, primary_key_identity, db_load_fn, identity_token=None):
+        """Override the default Query._get_impl() method so that we emit
+        a query to the DB for each possible identity token, if we don't
+        have one already.
+
+        """
+        def _db_load_fn(query, primary_key_identity):
+            # load from the database.  The original db_load_fn will
+            # use the given Query object to load from the DB, so our
+            # shard_id is what will indicate the DB that we query from.
             if self._shard_id is not None:
-                return fallback_fn(self, ident)
+                return db_load_fn(self, primary_key_identity)
             else:
-                ident = util.to_list(ident)
+                ident = util.to_list(primary_key_identity)
+                # build a ShardedQuery for each shard identifier and
+                # try to load from the DB
                 for shard_id in self.id_chooser(self, ident):
                     q = self.set_shard(shard_id)
-                    o = fallback_fn(q, ident)
+                    o = db_load_fn(q, ident)
                     if o is not None:
                         return o
                 else:
                     return None
 
-        if self._shard_id is not None:
+        if identity_token is None and self._shard_id is not None:
             identity_token = self._shard_id
-        else:
-            identity_token = None
 
         return super(ShardedQuery, self)._get_impl(
-            ident, _fallback, identity_token=identity_token)
+            primary_key_identity, _db_load_fn, identity_token=identity_token)
 
 
 class ShardedSession(Session):
index 3599aa3e74d7bbfe88312045a39ba89b6f99f568..1728b2d37813d04cdacdc8c09f8dc8aad6ebecf5 100644 (file)
@@ -180,23 +180,37 @@ def load_on_ident(query, key,
     else:
         ident = None
 
+    return load_on_pk_identity(
+        query, ident, refresh_state=refresh_state,
+        with_for_update=with_for_update,
+        only_load_props=only_load_props
+    )
+
+
+def load_on_pk_identity(query, primary_key_identity,
+                        refresh_state=None, with_for_update=None,
+                        only_load_props=None):
+
+    """Load the given primary key identity from the database."""
+
     if refresh_state is None:
         q = query._clone()
         q._get_condition()
     else:
         q = query._clone()
 
-    if ident is not None:
+    if primary_key_identity is not None:
         mapper = query._mapper_zero()
 
         (_get_clause, _get_params) = mapper._get_clause
 
         # None present in ident - turn those comparisons
         # into "IS NULL"
-        if None in ident:
+        if None in primary_key_identity:
             nones = set([
                         _get_params[col].key for col, value in
-                        zip(mapper.primary_key, ident) if value is None
+                        zip(mapper.primary_key, primary_key_identity)
+                        if value is None
                         ])
             _get_clause = sql_util.adapt_criterion_to_null(
                 _get_clause, nones)
@@ -206,7 +220,8 @@ def load_on_ident(query, key,
 
         params = dict([
             (_get_params[primary_key].key, id_val)
-            for id_val, primary_key in zip(ident, mapper.primary_key)
+            for id_val, primary_key
+            in zip(primary_key_identity, mapper.primary_key)
         ])
 
         q._params = params
index 42f1b26732c464ab45f8a53aad01d13ca75f6e5d..6d2b144e32c08109161cec1370a2cd867e29318e 100644 (file)
@@ -879,32 +879,68 @@ class Query(object):
 
         """
         return self._get_impl(
-            ident, loading.load_on_ident)
+            ident, loading.load_on_pk_identity)
 
-    def _get_impl(self, ident, fallback_fn, identity_token=None):
+    @classmethod
+    def _identity_lookup(
+            cls, session, mapper, primary_key_identity, identity_token=None,
+            passive=attributes.PASSIVE_OFF):
+        """Locate an object in the identity map.
+
+        Given a primary key identity, constructs an identity key and then
+        looks in the session's identity map.  If present, the object may
+        be run through unexpiration rules (e.g. load unloaded attributes,
+        check if was deleted).
+
+        :param session: Session in use
+        :param mapper: target mapper
+        :param primary_key_identity: the primary key we are searching for, as
+         a tuple.
+        :param identity_token: identity token that should be used to create
+         the identity key.  Used as is, however overriding subclasses can
+         repurpose this in order to interpret the value in a special way,
+         such as if None then look among multple target tokens.
+        :param passive: passive load flag passed to
+         :func:`.loading.get_from_identity`, which impacts the behavior if
+         the object is found; the object may be validated and/or unexpired
+         if the flag allows for SQL to be emitted.
+        :return: None if the object is not found in the identity map, *or*
+         if the object was unexpired and found to have been deleted.
+         if passive flags disallow SQL and the object is expired, returns
+         PASSIVE_NO_RESULT.   In all other cases the instance is returned.
+
+        .. versionadded:: 1.2.7
+
+        """
+        key = mapper.identity_key_from_primary_key(
+            primary_key_identity, identity_token=identity_token)
+        return loading.get_from_identity(
+            session, key, passive)
+
+    def _get_impl(
+            self, primary_key_identity, db_load_fn, identity_token=None):
         # convert composite types to individual args
-        if hasattr(ident, '__composite_values__'):
-            ident = ident.__composite_values__()
+        if hasattr(primary_key_identity, '__composite_values__'):
+            primary_key_identity = primary_key_identity.__composite_values__()
 
-        ident = util.to_list(ident)
+        primary_key_identity = util.to_list(primary_key_identity)
 
         mapper = self._only_full_mapper_zero("get")
 
-        if len(ident) != len(mapper.primary_key):
+        if len(primary_key_identity) != len(mapper.primary_key):
             raise sa_exc.InvalidRequestError(
                 "Incorrect number of values in identifier to formulate "
                 "primary key for query.get(); primary key columns are %s" %
                 ','.join("'%s'" % c for c in mapper.primary_key))
 
-        key = mapper.identity_key_from_primary_key(
-            ident, identity_token=identity_token)
-
         if not self._populate_existing and \
                 not mapper.always_refresh and \
                 self._for_update_arg is None:
 
-            instance = loading.get_from_identity(
-                self.session, key, attributes.PASSIVE_OFF)
+            instance = self._identity_lookup(
+                self.session, mapper, primary_key_identity,
+                identity_token=identity_token)
+
             if instance is not None:
                 self._get_existing_condition()
                 # reject calls for id in identity map but class
@@ -913,7 +949,7 @@ class Query(object):
                     return None
                 return instance
 
-        return fallback_fn(self, key)
+        return db_load_fn(self, primary_key_identity)
 
     @_generative()
     def correlate(self, *args):
index 4312747ac5dbf3ce30c5f5864d0a46d4ecd35589..00c83cea49d20c2ce6bd2720fa02f8fd7575a254 100644 (file)
@@ -576,7 +576,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             return attributes.ATTR_EMPTY
 
         pending = not state.key
-        ident_key = None
+        primary_key_identity = None
 
         if (
             (not passive & attributes.SQL_OK and not self.use_get)
@@ -599,28 +599,36 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         # if we have a simple primary key load, check the
         # identity map without generating a Query at all
         if self.use_get:
-            ident = self._get_ident_for_use_get(
+            primary_key_identity = self._get_ident_for_use_get(
                 session,
                 state,
                 passive
             )
-            if attributes.PASSIVE_NO_RESULT in ident:
+            if attributes.PASSIVE_NO_RESULT in primary_key_identity:
                 return attributes.PASSIVE_NO_RESULT
-            elif attributes.NEVER_SET in ident:
+            elif attributes.NEVER_SET in primary_key_identity:
                 return attributes.NEVER_SET
 
-            if _none_set.issuperset(ident):
+            if _none_set.issuperset(primary_key_identity):
                 return None
 
-            ident_key = self.mapper.identity_key_from_primary_key(ident)
-            instance = loading.get_from_identity(session, ident_key, passive)
+            # look for this identity in the identity map.  Delegate to the
+            # Query class in use, as it may have special rules for how it
+            # does this, including how it decides what the correct
+            # identity_token would be for this identity
+            instance = session._query_cls._identity_lookup(
+                session, self.mapper, primary_key_identity,
+                passive=passive
+            )
+
             if instance is not None:
                 return instance
             elif not passive & attributes.SQL_OK or \
                     not passive & attributes.RELATED_OBJECT_OK:
                 return attributes.PASSIVE_NO_RESULT
 
-        return self._emit_lazyload(session, state, ident_key, passive)
+        return self._emit_lazyload(
+            session, state, primary_key_identity, passive)
 
     def _get_ident_for_use_get(self, session, state, passive):
         instance_mapper = state.manager.mapper
@@ -648,7 +656,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
     @util.dependencies(
         "sqlalchemy.orm.strategy_options")
     def _emit_lazyload(
-            self, strategy_options, session, state, ident_key, passive):
+            self, strategy_options, 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:
@@ -707,8 +716,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         if self.use_get:
             if self._raise_on_sql:
                 self._invoke_raise_load(state, passive, "raise_on_sql")
-            return q(session)._load_on_ident(
-                session.query(self.mapper), ident_key)
+            return q(session)._load_on_pk_identity(
+                session.query(self.mapper), primary_key_identity)
 
         if self.parent_property.order_by:
             q.add_criteria(
index a5ea44e04d7a234ffbb4e0e3c8439c6e06ba2e6d..0bcacad378f621d5321d5ae0def77ab72bb570d9 100644 (file)
@@ -372,3 +372,46 @@ class SelectinloadRegressionTest(fixtures.DeclarativeMappedTest):
 
         result = session.query(Book).options(selectinload('pages')).all()
         eq_(result, [book])
+
+
+class LazyLoadFromIdentityMapTest(fixtures.DeclarativeMappedTest):
+    @classmethod
+    def setup_classes(cls):
+        Base = cls.DeclarativeBasic
+
+        class Book(Base):
+            __tablename__ = 'book'
+            id = Column(Integer, primary_key=True)
+            pages = relationship('Page', backref='book')
+
+        class Page(Base):
+            __tablename__ = 'page'
+            id = Column(Integer, primary_key=True)
+            book_id = Column(ForeignKey('book.id'))
+
+    def test_lazy_load_from_identity_map(self):
+        session = ShardedSession(
+            shards={"test": testing.db},
+            shard_chooser=lambda *args: 'test',
+            id_chooser=lambda *args: ['test'],
+            query_chooser=lambda *args: ['test']
+        )
+
+        Book, Page = self.classes("Book", "Page")
+        book = Book()
+        book.pages.append(Page())
+
+        session.add(book)
+        session.commit()
+
+        book = session.query(Book).first()
+        page = session.query(Page).first()
+
+        def go():
+            eq_(page.book, book)
+
+        # doesn't emit SQL
+        self.assert_sql_count(
+            testing.db,
+            go,
+            0)
index a6fe2137e903901ed847f898fb6f77bf74e60661..7b53862698ed0e47b1667d8a25162a710e35b6a7 100644 (file)
@@ -169,11 +169,11 @@ test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.6_sqlite_pys
 # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query
 
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_cextensions 417531
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_nocextensions 417545
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_mysql_mysqldb_dbapiunicode_nocextensions 417538
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_cextensions 417545
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 417524
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 417552
-test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 417531
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 417538
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_cextensions 417538
+test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 417538
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.6_mysql_mysqldb_dbapiunicode_cextensions 445502
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.6_mysql_mysqldb_dbapiunicode_nocextensions 445502
 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.6_postgresql_psycopg2_dbapiunicode_cextensions 445502
@@ -198,63 +198,63 @@ test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 3.6_sqlite_py
 
 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity
 
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_mysql_mysqldb_dbapiunicode_cextensions 17988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_mysql_mysqldb_dbapiunicode_nocextensions 17988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_dbapiunicode_cextensions 17988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 17988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 17988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 17988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_mysql_mysqldb_dbapiunicode_cextensions 18988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_mysql_mysqldb_dbapiunicode_nocextensions 18988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_postgresql_psycopg2_dbapiunicode_cextensions 18988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 18988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_sqlite_pysqlite_dbapiunicode_cextensions 18988
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 18988
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_mysql_mysqldb_dbapiunicode_cextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_mysql_mysqldb_dbapiunicode_nocextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_dbapiunicode_cextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_mysql_mysqldb_dbapiunicode_cextensions 19987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_mysql_mysqldb_dbapiunicode_nocextensions 19987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_postgresql_psycopg2_dbapiunicode_cextensions 19987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 19987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_sqlite_pysqlite_dbapiunicode_cextensions 19987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 19987
 
 # 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_mysql_mysqldb_dbapiunicode_cextensions 96501
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_dbapiunicode_nocextensions 98253
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_cextensions 86999
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 88751
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 84453
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 86205
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_mysql_mysqldb_dbapiunicode_cextensions 98271
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_mysql_mysqldb_dbapiunicode_nocextensions 100025
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_postgresql_psycopg2_dbapiunicode_cextensions 89519
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 91273
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_sqlite_pysqlite_dbapiunicode_cextensions 86020
-test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 87774
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_dbapiunicode_cextensions 97500
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_dbapiunicode_nocextensions 99252
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_cextensions 87998
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 89750
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_cextensions 85499
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 87204
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_mysql_mysqldb_dbapiunicode_cextensions 99270
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_mysql_mysqldb_dbapiunicode_nocextensions 101024
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_postgresql_psycopg2_dbapiunicode_cextensions 90518
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 92272
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_sqlite_pysqlite_dbapiunicode_cextensions 87019
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 88773
 
 # 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_mysql_mysqldb_dbapiunicode_cextensions 18788
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_dbapiunicode_nocextensions 18964
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_cextensions 18188
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 18412
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 17999
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 18222
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_mysql_mysqldb_dbapiunicode_cextensions 19263
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_mysql_mysqldb_dbapiunicode_nocextensions 19507
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_dbapiunicode_cextensions 18753
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_dbapiunicode_nocextensions 18976
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_cextensions 18153
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 18376
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_cextensions 18070
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 18294
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_mysql_mysqldb_dbapiunicode_cextensions 19275
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_mysql_mysqldb_dbapiunicode_nocextensions 19519
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_postgresql_psycopg2_dbapiunicode_cextensions 18683
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 18927
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_sqlite_pysqlite_dbapiunicode_cextensions 18505
-test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 18737
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_sqlite_pysqlite_dbapiunicode_cextensions 18517
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 18749
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load
 
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_cextensions 1194
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1211
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1131
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1148
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 965
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 982
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_mysql_mysqldb_dbapiunicode_cextensions 1231
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_mysql_mysqldb_dbapiunicode_nocextensions 1250
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_postgresql_psycopg2_dbapiunicode_cextensions 1145
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 1164
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_sqlite_pysqlite_dbapiunicode_cextensions 992
-test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 1011
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_cextensions 1195
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1212
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1132
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1149
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_cextensions 966
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 983
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_mysql_mysqldb_dbapiunicode_cextensions 1232
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_mysql_mysqldb_dbapiunicode_nocextensions 1251
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_postgresql_psycopg2_dbapiunicode_cextensions 1146
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 1165
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_sqlite_pysqlite_dbapiunicode_cextensions 993
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 1012
 
 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load
 
@@ -288,9 +288,9 @@ test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.6_sqlite_pysqlite_dbapiu
 
 # TEST: test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results
 
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_mysql_mysqldb_dbapiunicode_cextensions 178136
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_mysql_mysqldb_dbapiunicode_nocextensions 182435
-test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_postgresql_psycopg2_dbapiunicode_cextensions 168499
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_mysql_mysqldb_dbapiunicode_cextensions 178129
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_mysql_mysqldb_dbapiunicode_nocextensions 182442
+test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_postgresql_psycopg2_dbapiunicode_cextensions 168506
 test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 172812
 test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 161507
 test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 165813
@@ -303,18 +303,18 @@ test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.6_sq
 
 # TEST: test.aaa_profiling.test_orm.SessionTest.test_expire_lots
 
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_cextensions 1151
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1151
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1154
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1153
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1134
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1139
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_mysql_mysqldb_dbapiunicode_cextensions 1264
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_mysql_mysqldb_dbapiunicode_nocextensions 1264
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_postgresql_psycopg2_dbapiunicode_cextensions 1264
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 1263
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_sqlite_pysqlite_dbapiunicode_cextensions 1251
-test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 1273
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_cextensions 1142
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_mysql_mysqldb_dbapiunicode_nocextensions 1149
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_cextensions 1133
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 1151
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_cextensions 1149
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 1138
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_mysql_mysqldb_dbapiunicode_cextensions 1271
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_mysql_mysqldb_dbapiunicode_nocextensions 1251
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_postgresql_psycopg2_dbapiunicode_cextensions 1261
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 1259
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_sqlite_pysqlite_dbapiunicode_cextensions 1267
+test.aaa_profiling.test_orm.SessionTest.test_expire_lots 3.6_sqlite_pysqlite_dbapiunicode_nocextensions 1264
 
 # TEST: test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect
 
@@ -506,4 +506,4 @@ test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 3.6_postgresql_psyco
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_cextensions 6567,413,6801,17811,1171,2666
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6659,418,6921,18880,1276,2700
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.6_postgresql_psycopg2_dbapiunicode_cextensions 6537,404,6933,18213,1170,2714
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 6627,409,7069,19324,1272,2755
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation 3.6_postgresql_psycopg2_dbapiunicode_nocextensions 6634,409,7069,19324,1272,2755