diff options
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/baked.py | 6 | ||||
-rw-r--r-- | test/ext/test_baked.py | 24 |
3 files changed, 42 insertions, 0 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 7e289a526..8d6dcd2a9 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -19,6 +19,18 @@ :version: 1.0.10 .. change:: + :tags: bug, ext + :versions: 1.1.0b1 + :tickets: 3597 + + Fixed an issue in baked queries where the .get() method, used either + directly or within lazy loads, didn't consider the mapper's "get clause" + as part of the cache key, causing bound parameter mismatches if the + clause got re-generated. This clause is cached by mappers + on the fly but in highly concurrent scenarios may be generated more + than once when first accessed. + + .. change:: :tags: feature, sql :versions: 1.1.0b1 :pullreq: github:200 diff --git a/lib/sqlalchemy/ext/baked.py b/lib/sqlalchemy/ext/baked.py index ee9f6f9bb..eb0c36805 100644 --- a/lib/sqlalchemy/ext/baked.py +++ b/lib/sqlalchemy/ext/baked.py @@ -354,6 +354,12 @@ class Result(object): # (remember, we can map to an OUTER JOIN) bq = self.bq + # add the clause we got from mapper._get_clause to the cache + # key so that if a race causes multiple calls to _get_clause, + # we've cached on ours + bq = bq._clone() + bq._cache_key += (_get_clause, ) + bq = bq.with_criteria(setup, tuple(elem is None for elem in ident)) params = dict([ diff --git a/test/ext/test_baked.py b/test/ext/test_baked.py index dcf333184..7004358fc 100644 --- a/test/ext/test_baked.py +++ b/test/ext/test_baked.py @@ -271,6 +271,30 @@ class LikeQueryTest(BakedTest): eq_(u2.name, 'chuck') self.assert_sql_count(testing.db, go, 0) + def test_get_includes_getclause(self): + # test issue #3597 + User = self.classes.User + + bq = self.bakery(lambda s: s.query(User)) + + for i in range(5): + sess = Session() + u1 = bq(sess).get(7) + eq_(u1.name, 'jack') + + eq_(len(bq._bakery), 2) + + # simulate race where mapper._get_clause + # may be generated more than once + from sqlalchemy import inspect + del inspect(User).__dict__['_get_clause'] + + for i in range(5): + sess = Session() + u1 = bq(sess).get(7) + eq_(u1.name, 'jack') + eq_(len(bq._bakery), 4) + class ResultTest(BakedTest): __backend__ = True |