51 lines
1.6 KiB
Markdown
51 lines
1.6 KiB
Markdown
# UNDF: UNDF-2026-000000542
|
||
# sqlalchemy-0003: evaluated_keys list → set in _apply_evaluators()
|
||
|
||
## Severity
|
||
MEDIUM
|
||
|
||
## Location
|
||
`lib/sqlalchemy/orm/bulk_persistence.py` — `_apply_evaluators()` (approx. line 1873)
|
||
|
||
## Description
|
||
`evaluated_keys = list(value_evaluators.keys())` converts a dict's key view
|
||
to a plain list. The list is then used for:
|
||
|
||
1. `c.key not in evaluated_keys` — O(K) membership scan inside a set
|
||
comprehension over `prefetch_cols` (one test per column)
|
||
2. `.difference(evaluated_keys)` — called on a set; this is O(P×K) rather
|
||
than the O(P) it would be with a set argument
|
||
|
||
Fix: `evaluated_keys = set(value_evaluators)` (or just use `value_evaluators`
|
||
directly for membership, since dict `in` is O(1)).
|
||
|
||
## Defective code (lines 1873–1889)
|
||
```python
|
||
evaluated_keys = list(value_evaluators.keys()) # ← plain list
|
||
|
||
to_prefetch = {
|
||
c
|
||
for c in prefetch_cols
|
||
if c.key in effective_params
|
||
and c in mapper._columntoproperty
|
||
and c.key not in evaluated_keys # ← O(K) linear scan per column
|
||
}
|
||
to_expire = {
|
||
mapper._columntoproperty[c].key
|
||
for c in postfetch_cols
|
||
if c in mapper._columntoproperty
|
||
}.difference(evaluated_keys) # ← set.difference(list) is O(P×K)
|
||
```
|
||
|
||
## Fix
|
||
```diff
|
||
--- a/lib/sqlalchemy/orm/bulk_persistence.py
|
||
+++ b/lib/sqlalchemy/orm/bulk_persistence.py
|
||
@@ -1873,1 +1873,1 @@
|
||
- evaluated_keys = list(value_evaluators.keys())
|
||
+ evaluated_keys = set(value_evaluators)
|
||
```
|
||
|
||
## Complexity
|
||
- Before: O(C×K) for the `to_prefetch` comprehension, O(P×K) for `.difference()`
|
||
- After: O(C) + O(P) — both operations drop to O(1) membership
|