MemPalace/mempalace · error · UnsupportedFilterError

$and requires a non-empty list of clauses

Error message

$and requires a non-empty list of clauses

What it means

Raised in _translate_clause() when a top-level (or nested) $and key has a value that is not a non-empty list. $and composes sub-clauses, so its operand must be a list of at least one clause dict; anything else (dict, string, empty list) is an UnsupportedFilterError. Each sub-clause is itself recursively translated.

Source

Thrown at mempalace/backends/milvus.py:153

                parts.append(f"{field} <= {_quote_value(operand)}")
            elif op == "$contains":
                parts.append(f"{field} like {_like_value(operand)}")
            else:
                raise UnsupportedFilterError(f"operator {op!r} not supported by milvus")
        return " and ".join(parts)
    return f"{field} == {_quote_value(expected)}"


def _translate_clause(clause: dict) -> str:
    if not isinstance(clause, dict):
        raise UnsupportedFilterError(f"where clause must be a dict, got {type(clause).__name__}")
    if not clause:
        return ""
    parts = []
    for key, value in clause.items():
        if key == "$and":
            if not isinstance(value, list) or not value:
                raise UnsupportedFilterError("$and requires a non-empty list of clauses")
            nested = [_translate_clause(item) for item in value]
            parts.append("(" + " and ".join(part for part in nested if part) + ")")
        elif key == "$or":
            if not isinstance(value, list) or not value:
                raise UnsupportedFilterError("$or requires a non-empty list of clauses")
            nested = [_translate_clause(item) for item in value]
            parts.append("(" + " or ".join(part for part in nested if part) + ")")
        elif key.startswith("$"):
            raise UnsupportedFilterError(f"operator {key!r} not supported by milvus")
        else:
            parts.append(_translate_field(key, value))
    return " and ".join(part for part in parts if part)


def translate_where(where: Optional[dict]) -> str:
    """Translate the portable metadata where DSL into a Milvus filter string."""
    if not where:
        return ""

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Use list form: {"$and": [{"a": 1}, {"b": 2}]}
  2. Remember sibling keys in one dict are already ANDed — {"a": 1, "b": 2} needs no $and
  3. When the operand list may be empty, skip $and and pass None/{} as the where clause

Example fix

// before
where = {"$and": {"wing": "alice", "room": "2024"}}

// after
where = {"$and": [{"wing": "alice"}, {"room": "2024"}]}
# or simply
where = {"wing": "alice", "room": "2024"}
Defensive patterns

Strategy: validation

Validate before calling

def build_and(clauses):
    clauses = [c for c in clauses if c]
    if not clauses:
        return None
    if len(clauses) == 1:
        return clauses[0]
    return {"$and": clauses}  # every element a dict, list never empty

Try / catch

from mempalace.backends.base import UnsupportedFilterError
try:
    collection.query(query_texts=[q], where=where, n_results=k)
except UnsupportedFilterError as e:
    if "$and" in str(e):
        raise ValueError("$and must be a non-empty list of clause dicts") from e
    raise

Prevention

When it happens

Trigger: where={"$and": {"a": 1, "b": 2}} (dict instead of list), {"$and": []}, or {"$and": ["a=1"]} where the element later fails the dict check (error 25).

Common situations: Assuming $and takes a dict of conditions (some DSLs allow both shapes); programmatically building conjunctions that collapse to zero when a filter list is empty.

Related errors


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