summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_12/4507.rst13
-rw-r--r--lib/sqlalchemy/orm/strategies.py3
-rw-r--r--test/ext/test_baked.py15
3 files changed, 31 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_12/4507.rst b/doc/build/changelog/unreleased_12/4507.rst
new file mode 100644
index 000000000..39b37b93d
--- /dev/null
+++ b/doc/build/changelog/unreleased_12/4507.rst
@@ -0,0 +1,13 @@
+.. change::
+ :tags: bug, orm
+ :tickets: 4507
+
+ Fixed a regression in 1.2 due to the introduction of baked queries for
+ relationship lazy loaders, where a race condition is created during the
+ generation of the "lazy clause" which occurs within a memoized attribute. If
+ two threads initialize the memoized attribute concurrently, the baked query
+ could be generated with bind parameter keys that are then replaced with new
+ keys by the next run, leading to a lazy load query that specifies the
+ related criteria as ``None``. The fix establishes that the parameter names
+ are fixed before the new clause and parameter objects are generated, so that
+ the names are the same every time.
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 3e7372fac..ec3c9790f 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -595,6 +595,9 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
def visit_bindparam(bindparam):
bindparam.unique = False
+ visitors.traverse(criterion, {}, {"bindparam": visit_bindparam})
+
+ def visit_bindparam(bindparam):
if bindparam._identifying_key in bind_to_col:
params.append(
(
diff --git a/test/ext/test_baked.py b/test/ext/test_baked.py
index 57c944799..55cd9376b 100644
--- a/test/ext/test_baked.py
+++ b/test/ext/test_baked.py
@@ -1325,6 +1325,21 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
),
)
+ 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