MemPalace/mempalace · error · UnsupportedFilterError

where clause must be a dict, got {type(clause).__name__}

Error message

where clause must be a dict, got {type(clause).__name__}

What it means

Raised by _translate_clause() when the where argument (or a nested $and/$or element) is not a dict — e.g. a list, string, or None inside a nested position. The portable DSL requires every clause level to be a mapping of field→condition or operator→subclauses; anything else fails translation with UnsupportedFilterError before Milvus is contacted. Note the top-level falsy case ({}, None) is fine and yields an empty filter — only non-dict truthy values fail.

Source

Thrown at mempalace/backends/milvus.py:146

            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:
                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))

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Pass a plain dict: where={"wing": "alice"}, not a list or expression string
  2. For raw Milvus filter expressions, use the backend's native filter escape hatch (if exposed) instead of where
  3. Validate nested $and/$or elements are all dicts before calling query

Example fix

// before
results = collection.query(query_texts=[q], where=[{"wing": "alice"}])

// after
results = collection.query(query_texts=[q], where={"wing": "alice"})
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_where(where) -> dict:
    if not where:
        return {}
    if not isinstance(where, dict):
        raise TypeError(f"where must be a dict, got {type(where).__name__}")
    for k, v in where.items():
        if k in ("$and", "$or"):
            for item in v:
                validate_where(item)
    return where

Type guard

def is_where_dict(where) -> bool:
    """True when every clause node is a dict and $and/$or carry list-of-dict."""
    if not isinstance(where, dict):
        return False
    for k, v in where.items():
        if k in ("$and", "$or"):
            if not isinstance(v, list) or not v:
                return False
            if not all(is_where_dict(item) for item in v):
                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 "must be a dict" in str(e):
        raise TypeError("pass where as a dict, not a list or expression string") from e
    raise

Prevention

When it happens

Trigger: where=[{"wing": "a"}] (list-wrapped clause, Mongo habit), where="wing = 'a'" (raw expression string), or nested={"$or": [{"a": 1}, "b"]} where one $or element is a bare string.

Common situations: Copy-pasting raw Milvus filter expression strings from Milvus docs into the where parameter; JSON-decoded filters where a nested element is not an object; wrappers that accept multiple clause shapes.

Related errors


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