diff options
| -rw-r--r-- | doc/build/changelog/unreleased_12/4507.rst | 13 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 3 | ||||
| -rw-r--r-- | test/ext/test_baked.py | 15 |
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 |
