summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_10.rst12
-rw-r--r--lib/sqlalchemy/ext/baked.py6
-rw-r--r--test/ext/test_baked.py24
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