MemPalace/mempalace · error · UnsupportedFilterError

$in requires a non-empty list for {field!r}

Error message

$in requires a non-empty list for {field!r}

What it means

Raised in _translate_field() when the $in operator's operand is not a list or is an empty list. Milvus 'in [..]' over an empty set is invalid/meaningless, so the translator refuses it up front rather than sending a malformed filter. It is an UnsupportedFilterError thrown synchronously during where-clause translation, before any network call.

Source

Thrown at mempalace/backends/milvus.py:120

def _field_name(name: str) -> str:
    if not isinstance(name, str) or not _FIELD_RE.match(name):
        raise UnsupportedFilterError(f"Milvus filter field {name!r} is not a safe identifier")
    return name


def _translate_field(field: str, expected: Any) -> str:
    field = _field_name(field)
    if isinstance(expected, dict):
        parts = []
        for op, operand in expected.items():
            if op == "$eq":
                parts.append(f"{field} == {_quote_value(operand)}")
            elif op == "$ne":
                parts.append(f"{field} != {_quote_value(operand)}")
            elif op == "$in":
                if not isinstance(operand, list) or not operand:
                    raise UnsupportedFilterError(f"$in requires a non-empty list for {field!r}")
                items = ", ".join(_quote_value(item) for item in operand)
                parts.append(f"{field} in [{items}]")
            elif op == "$nin":
                if not isinstance(operand, list) or not operand:
                    raise UnsupportedFilterError(f"$nin requires a non-empty list for {field!r}")
                items = ", ".join(_quote_value(item) for item in operand)
                parts.append(f"{field} not in [{items}]")
            elif op == "$gt":
                parts.append(f"{field} > {_quote_value(operand)}")
            elif op == "$gte":
                parts.append(f"{field} >= {_quote_value(operand)}")
            elif op == "$lt":
                parts.append(f"{field} < {_quote_value(operand)}")
            elif op == "$lte":
                parts.append(f"{field} <= {_quote_value(operand)}")
            elif op == "$contains":
                parts.append(f"{field} like {_like_value(operand)}")
            else:

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Skip the query (return empty results) when the $in list would be empty — it can match nothing anyway
  2. Guard before calling: if isinstance(v, dict) and '$in' in v and not v['$in']: return []
  3. Ensure the operand is a real list: wrap single values as [value]

Example fix

// before
where = {"wing": {"$in": found_wings}}  # found_wings == []
collection.query(query_texts=[q], where=where)

// after
if not found_wings:
    return []
collection.query(query_texts=[q], where={"wing": {"$in": found_wings}})
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_in_filter(where: dict) -> bool:
    for v in where.values():
        if isinstance(v, dict) and "$in" in v:
            operand = v["$in"]
            if not isinstance(operand, list) or not operand:
                return False
    return True

Try / catch

from mempalace.backends.base import UnsupportedFilterError
try:
    collection.query(query_texts=[q], where=where, n_results=k)
except UnsupportedFilterError as e:
    if "$in" in str(e):
        return []  # empty candidate set matches nothing
    raise

Prevention

When it happens

Trigger: where={"wing": {"$in": []}}, where={"wing": {"$in": "people"}} (string instead of list), or a filter built from an empty computed list, e.g. {"tag": {"$in": matching_tags}} where matching_tags ends up empty.

Common situations: Dynamically computed candidate sets (e.g. entity names found by a detector) that legitimately come back empty; the caller intends 'no matches' but the backend rejects the clause.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/8c10bb34f0bf0847. Report an issue: GitHub.