mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
refactor(proxy): fold the predicate renderer instead of recursing
recursive_detector flags `_render_all`, and the flag is fair: it recursed once per predicate, so the stack grew with the number of filters on the request for no reason. Walking a predicate list is a running bind index, which is a fold. `_render` still re-enters for `AnyOf`, but its clauses are plain comparisons built by `?q=`, so that nesting is one level deep and no caller can drive it deeper.
This commit is contained in:
parent
78c756dff9
commit
858ba17430
2 changed files with 34 additions and 5 deletions
|
|
@ -15,6 +15,7 @@ raw-SQL executor with every caller-supplied value bound to a placeholder.
|
|||
from collections.abc import Callable, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from functools import partial, reduce
|
||||
from math import ceil
|
||||
from typing import Generic, Literal, Protocol, TypeVar
|
||||
|
||||
|
|
@ -242,12 +243,26 @@ def _render(predicate: Predicate, index: int) -> tuple[str, tuple[object, ...]]:
|
|||
assert_never(predicate)
|
||||
|
||||
|
||||
def _render_one(
|
||||
rendered: tuple[tuple[str, ...], tuple[object, ...]],
|
||||
predicate: Predicate,
|
||||
first_index: int,
|
||||
) -> tuple[tuple[str, ...], tuple[object, ...]]:
|
||||
"""Append one predicate, numbering it after the binds already consumed."""
|
||||
clauses, params = rendered
|
||||
clause, clause_params = _render(predicate, first_index + len(params))
|
||||
return (*clauses, clause), (*params, *clause_params)
|
||||
|
||||
|
||||
def _render_all(predicates: tuple[Predicate, ...], index: int) -> tuple[tuple[str, ...], tuple[object, ...]]:
|
||||
if not predicates:
|
||||
return (), ()
|
||||
head, head_params = _render(predicates[0], index)
|
||||
tail, tail_params = _render_all(predicates[1:], index + len(head_params))
|
||||
return (head, *tail), head_params + tail_params
|
||||
"""Render every predicate, numbering placeholders continuously across them.
|
||||
|
||||
Folded rather than self-recursive: walking a predicate list is a running index, and
|
||||
recursing per predicate grew the stack with the filter count for nothing. `_render`
|
||||
still re-enters here for `AnyOf`, whose clauses are plain `Compare`s from `?q=`, so
|
||||
that nesting is one level deep and cannot be driven deeper by a caller.
|
||||
"""
|
||||
return reduce(partial(_render_one, first_index=index), predicates, ((), ()))
|
||||
|
||||
|
||||
def where_sql(where: tuple[Predicate, ...], first_index: int = 1) -> tuple[str, tuple[object, ...]]:
|
||||
|
|
|
|||
|
|
@ -382,6 +382,20 @@ def test_created_at_range_is_bound_as_a_timestamp(query_raw, as_proxy_admin):
|
|||
assert params[0] == datetime(2026, 7, 1, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_numbers_placeholders_continuously_across_predicates(query_raw, as_proxy_admin):
|
||||
"""Each predicate is numbered after the binds the ones before it consumed. Restart
|
||||
the count and `$1` gets read as the duration while the search string goes unbound."""
|
||||
_serve(query_raw, [])
|
||||
|
||||
_get("filter[budget_duration][in]=7d,30d&filter[max_budget][gte]=5&q=prod")
|
||||
|
||||
sql, *params = _select_call(query_raw)
|
||||
assert '"budget_duration" IN ($1, $2)' in sql
|
||||
assert '"max_budget" >= $3' in sql
|
||||
assert '"budget_id" ILIKE $4' in sql
|
||||
assert params[:4] == ["7d", "30d", 5.0, "%prod%"]
|
||||
|
||||
|
||||
def test_a_non_datetime_bind_is_not_cast(query_raw, as_proxy_admin):
|
||||
"""Guards the cast above from being applied to every placeholder."""
|
||||
_serve(query_raw, [])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue