MemPalace/mempalace · error · UnsupportedFilterError

operator {key!r} not supported by sqlite_exact

Error message

operator {key!r} not supported by sqlite_exact

What it means

Raised by sqlite_exact's pre-flight validation of a `where` filter. The backend walks every dict node of the filter and rejects any key starting with `$` that is not in `_SUPPORTED_OPERATORS`. It exists because this backend implements only a small Mongo-like operator subset and would otherwise silently mis-evaluate unknown operators.

Source

Thrown at mempalace/backends/sqlite_exact.py:147

        for term, freq in tf.items():
            num = freq * (k1 + 1)
            den = freq + k1 * (1 - b + b * dl / avgdl)
            score += float(idf[term]) * num / den
        scores.append(score)
    return scores


def _validate_where(where: Optional[dict]) -> None:
    if not where:
        return
    stack = [where]
    while stack:
        node = stack.pop()
        if not isinstance(node, dict):
            continue
        for key, value in node.items():
            if key.startswith("$") and key not in _SUPPORTED_OPERATORS:
                raise UnsupportedFilterError(f"operator {key!r} not supported by sqlite_exact")
            if isinstance(value, dict):
                stack.append(value)
            elif isinstance(value, list):
                stack.extend(item for item in value if isinstance(item, dict))


def _coerce_comparable(value: Any):
    if isinstance(value, bool):
        return int(value)
    return value


def _compare(actual: Any, op: str, expected: Any) -> bool:
    actual = _coerce_comparable(actual)
    expected = _coerce_comparable(expected)
    if op == "$eq":
        return actual == expected
    if op == "$ne":

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Check `_SUPPORTED_OPERATORS` in mempalace/backends/sqlite_exact.py and rewrite the filter using only those operators (typically $and/$or/$gt/$gte/$lt/$lte/$ne/$eq/$in/$contains).
  2. Replace unsupported logical operators ($nor, $not) with $and/$or combinations of supported comparisons.
  3. Move exotic matching out of the backend: fetch with a broader supported filter and post-filter in Python.
  4. If you need richer filters, use the ChromaDB backend instead of sqlite_exact.

Example fix

# before
where = {"$nor": [{"status": "draft"}, {"status": "archived"}]}
col.get(where=where)

# after
where = {"$and": [{"status": {"$ne": "draft"}}, {"status": {"$ne": "archived"}}]}
col.get(where=where)
Defensive patterns

Strategy: validation

Validate before calling

from mempalace.backends.sqlite_exact import _SUPPORTED_OPERATORS

def validate_where(where):
    stack = [where]
    while stack:
        node = stack.pop()
        if not isinstance(node, dict):
            continue
        for key, value in node.items():
            if key.startswith("$") and key not in _SUPPORTED_OPERATORS:
                raise ValueError(f"unsupported filter operator {key!r}; rewrite with {_SUPPORTED_OPERATORS}")
            if isinstance(value, dict):
                stack.append(value)
            elif isinstance(value, list):
                stack.extend(i for i in value if isinstance(i, dict))
    return where

Type guard

def is_supported_filter(where: dict) -> bool:
    stack = [where]
    while stack:
        node = stack.pop()
        if not isinstance(node, dict):
            continue
        for key, value in node.items():
            if key.startswith("$") and key not in _SUPPORTED_OPERATORS:
                return False
            if isinstance(value, dict):
                stack.append(value)
            elif isinstance(value, list):
                stack.extend(i for i in value if isinstance(i, dict))
    return True

Try / catch

try:
    col.get(where=where)
except UnsupportedFilterError as e:
    logger.warning("rewriting filter %s: %s", where, e)
    where = simplify_to_supported_ops(where)
    col.get(where=where)

Prevention

When it happens

Trigger: Calling count/get/query/delete on a SQLiteExactCollection with a `where` dict containing an operator outside the supported set, e.g. `{"$nor": [...]}`, `{"$not": {...}}`, or a typo like `"$GTE"`. Nested operator dicts and operator dicts inside lists are also traversed, so the bad key can sit at any depth.

Common situations: Porting filter code from ChromaDB/MongoDB syntax to the sqlite_exact backend; using `$in`/`$nin` list forms the backend never implemented; copy-pasting filters from another storage layer during a backend migration.

Related errors


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