{"record":{"id":"0548384f9ea2dc7e","repo":"MemPalace/mempalace","slug":"operator-key-r-not-supported-by-sqlite-exact","errorCode":null,"errorMessage":"operator {key!r} not supported by sqlite_exact","messagePattern":"operator (.+?) not supported by sqlite_exact","errorType":"validation","errorClass":"UnsupportedFilterError","httpStatus":null,"severity":"error","filePath":"mempalace/backends/sqlite_exact.py","lineNumber":147,"sourceCode":"        for term, freq in tf.items():\n            num = freq * (k1 + 1)\n            den = freq + k1 * (1 - b + b * dl / avgdl)\n            score += float(idf[term]) * num / den\n        scores.append(score)\n    return scores\n\n\ndef _validate_where(where: Optional[dict]) -> None:\n    if not where:\n        return\n    stack = [where]\n    while stack:\n        node = stack.pop()\n        if not isinstance(node, dict):\n            continue\n        for key, value in node.items():\n            if key.startswith(\"$\") and key not in _SUPPORTED_OPERATORS:\n                raise UnsupportedFilterError(f\"operator {key!r} not supported by sqlite_exact\")\n            if isinstance(value, dict):\n                stack.append(value)\n            elif isinstance(value, list):\n                stack.extend(item for item in value if isinstance(item, dict))\n\n\ndef _coerce_comparable(value: Any):\n    if isinstance(value, bool):\n        return int(value)\n    return value\n\n\ndef _compare(actual: Any, op: str, expected: Any) -> bool:\n    actual = _coerce_comparable(actual)\n    expected = _coerce_comparable(expected)\n    if op == \"$eq\":\n        return actual == expected\n    if op == \"$ne\":","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/sqlite_exact.py#L129-L165","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Replace unsupported logical operators ($nor, $not) with $and/$or combinations of supported comparisons.","Move exotic matching out of the backend: fetch with a broader supported filter and post-filter in Python.","If you need richer filters, use the ChromaDB backend instead of sqlite_exact."],"exampleFix":"# before\nwhere = {\"$nor\": [{\"status\": \"draft\"}, {\"status\": \"archived\"}]}\ncol.get(where=where)\n\n# after\nwhere = {\"$and\": [{\"status\": {\"$ne\": \"draft\"}}, {\"status\": {\"$ne\": \"archived\"}}]}\ncol.get(where=where)","handlingStrategy":"validation","validationCode":"from mempalace.backends.sqlite_exact import _SUPPORTED_OPERATORS\n\ndef validate_where(where):\n    stack = [where]\n    while stack:\n        node = stack.pop()\n        if not isinstance(node, dict):\n            continue\n        for key, value in node.items():\n            if key.startswith(\"$\") and key not in _SUPPORTED_OPERATORS:\n                raise ValueError(f\"unsupported filter operator {key!r}; rewrite with {_SUPPORTED_OPERATORS}\")\n            if isinstance(value, dict):\n                stack.append(value)\n            elif isinstance(value, list):\n                stack.extend(i for i in value if isinstance(i, dict))\n    return where","typeGuard":"def is_supported_filter(where: dict) -> bool:\n    stack = [where]\n    while stack:\n        node = stack.pop()\n        if not isinstance(node, dict):\n            continue\n        for key, value in node.items():\n            if key.startswith(\"$\") and key not in _SUPPORTED_OPERATORS:\n                return False\n            if isinstance(value, dict):\n                stack.append(value)\n            elif isinstance(value, list):\n                stack.extend(i for i in value if isinstance(i, dict))\n    return True","tryCatchPattern":"try:\n    col.get(where=where)\nexcept UnsupportedFilterError as e:\n    logger.warning(\"rewriting filter %s: %s\", where, e)\n    where = simplify_to_supported_ops(where)\n    col.get(where=where)","preventionTips":["Keep a single shared filter-builder helper that only emits supported operators.","Add unit tests asserting every filter your app produces passes the supported-operator check.","When porting from Chroma/Mongo syntax, grep filters for $nor/$not/$regex/$nin before switching backends."],"tags":["sqlite-exact","filter","where","validation","backend"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}