Skip to content

Commit f54e193

Browse files
committed
Fix memory leak in SQLCompiler._get_pushable_conditions()
Detected/tested in Django 6.1; see https://code.djangoproject.com/ticket/36674 Regression in 86771d5.
1 parent 4761579 commit f54e193

File tree

1 file changed

+44
-44
lines changed

1 file changed

+44
-44
lines changed

django_mongodb_backend/compiler.py

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -666,56 +666,56 @@ def _get_pushable_conditions(self):
666666
Return a dict mapping each alias to a WhereNode holding its pushable
667667
condition.
668668
"""
669+
return self._collect_pushable(self.get_where())
669670

670-
def collect_pushable(expr, negated=False):
671-
if expr is None or isinstance(expr, NothingNode):
671+
@classmethod
672+
def _collect_pushable(cls, expr, negated=False):
673+
if expr is None or isinstance(expr, NothingNode):
674+
return {}
675+
if isinstance(expr, WhereNode):
676+
# Apply De Morgan: track negation so connectors are flipped
677+
# when needed.
678+
negated ^= expr.negated
679+
pushable_expressions = [
680+
cls._collect_pushable(sub_expr, negated=negated)
681+
for sub_expr in expr.children
682+
if sub_expr is not None
683+
]
684+
operator = expr.connector
685+
if operator == XOR:
672686
return {}
673-
if isinstance(expr, WhereNode):
674-
# Apply De Morgan: track negation so connectors are flipped
675-
# when needed.
676-
negated ^= expr.negated
677-
pushable_expressions = [
678-
collect_pushable(sub_expr, negated=negated)
679-
for sub_expr in expr.children
680-
if sub_expr is not None
681-
]
682-
operator = expr.connector
683-
if operator == XOR:
684-
return {}
685-
if negated:
686-
operator = OR if operator == AND else AND
687-
alias_children = defaultdict(list)
688-
for pe in pushable_expressions:
689-
for alias, expressions in pe.items():
690-
alias_children[alias].append(expressions)
691-
# Build per-alias pushable condition nodes.
692-
if operator == AND:
693-
return {
694-
alias: WhereNode(children=children, negated=False, connector=operator)
695-
for alias, children in alias_children.items()
696-
}
697-
# Only aliases shared across all branches are pushable for OR.
698-
shared_alias = (
699-
set.intersection(*(set(pe) for pe in pushable_expressions))
700-
if pushable_expressions
701-
else set()
702-
)
687+
if negated:
688+
operator = OR if operator == AND else AND
689+
alias_children = defaultdict(list)
690+
for pe in pushable_expressions:
691+
for alias, expressions in pe.items():
692+
alias_children[alias].append(expressions)
693+
# Build per-alias pushable condition nodes.
694+
if operator == AND:
703695
return {
704696
alias: WhereNode(children=children, negated=False, connector=operator)
705697
for alias, children in alias_children.items()
706-
if alias in shared_alias
707698
}
708-
# A leaf is pushable only when comparing a field to a constant or
709-
# simple value.
710-
if isinstance(expr.lhs, Col) and (
711-
is_constant_value(expr.rhs) or getattr(expr.rhs, "is_simple_column", False)
712-
):
713-
alias = expr.lhs.alias
714-
expr = WhereNode(children=[expr], negated=negated)
715-
return {alias: expr}
716-
return {}
717-
718-
return collect_pushable(self.get_where())
699+
# Only aliases shared across all branches are pushable for OR.
700+
shared_alias = (
701+
set.intersection(*(set(pe) for pe in pushable_expressions))
702+
if pushable_expressions
703+
else set()
704+
)
705+
return {
706+
alias: WhereNode(children=children, negated=False, connector=operator)
707+
for alias, children in alias_children.items()
708+
if alias in shared_alias
709+
}
710+
# A leaf is pushable only when comparing a field to a constant or
711+
# simple value.
712+
if isinstance(expr.lhs, Col) and (
713+
is_constant_value(expr.rhs) or getattr(expr.rhs, "is_simple_column", False)
714+
):
715+
alias = expr.lhs.alias
716+
expr = WhereNode(children=[expr], negated=negated)
717+
return {alias: expr}
718+
return {}
719719

720720
def get_lookup_pipeline(self):
721721
result = []

0 commit comments

Comments
 (0)