{"record":{"id":"51ba2ace7a4807f5","repo":"MemPalace/mempalace","slug":"operator-op-r-not-supported-by-sqlite-exact","errorCode":null,"errorMessage":"operator {op!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":184,"sourceCode":"        return actual != expected\n    if op == \"$in\":\n        return actual in (expected or [])\n    if op == \"$nin\":\n        return actual not in (expected or [])\n    if op == \"$contains\":\n        return str(expected) in str(actual or \"\")\n    try:\n        if op == \"$gt\":\n            return actual > expected\n        if op == \"$gte\":\n            return actual >= expected\n        if op == \"$lt\":\n            return actual < expected\n        if op == \"$lte\":\n            return actual <= expected\n    except TypeError:\n        return False\n    raise UnsupportedFilterError(f\"operator {op!r} not supported by sqlite_exact\")\n\n\ndef _matches_where(meta: dict, where: Optional[dict]) -> bool:\n    if not where:\n        return True\n    if not isinstance(where, dict):\n        return False\n    for key, expected in where.items():\n        if key == \"$and\":\n            if not all(_matches_where(meta, clause) for clause in expected or []):\n                return False\n            continue\n        if key == \"$or\":\n            if not any(_matches_where(meta, clause) for clause in expected or []):\n                return False\n            continue\n        if key.startswith(\"$\"):\n            raise UnsupportedFilterError(f\"operator {key!r} not supported by sqlite_exact\")","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/sqlite_exact.py#L166-L202","documentation":"Raised by sqlite_exact's `_compare` helper when it is handed a comparison operator it does not implement. `_compare` handles equality/inequality, containment, `$contains`, and `$gt/$gte/$lt/$lte`; reaching the final `raise` means the operator string fell through every branch. It is a defensive backstop for operators that slipped past `_validate_where`.","triggerScenarios":"A `where` clause like `{\"field\": {\"$between\": [1, 5]}}` or any `$op` not in the supported comparison set reaching `_matches_where` -> `_compare`. Because `_validate_where` normally rejects unknown operators first, this typically fires when validation is skipped, the supported-set and compare branches drift out of sync, or a caller invokes the private matcher directly.","commonSituations":"Version skew between the operator allow-list and `_compare` after a partial upgrade; calling internal `_matches_where`/`_compare` helpers from custom code or tests; filters constructed dynamically from user input without validation.","solutions":["Rewrite the filter using only supported comparison operators ($eq, $ne, $gt, $gte, $lt, $lte, $in, $contains).","If you expect this operator to exist, check `_SUPPORTED_OPERATORS` in your installed version — the allow-list and `_compare` may have drifted; upgrade mempalace.","Report the mismatch as a bug: `_validate_where` should have rejected the operator earlier, so both lists need to agree."],"exampleFix":"# before\nwhere = {\"score\": {\"$between\": [1, 5]}}\n\n# after\nwhere = {\"$and\": [{\"score\": {\"$gte\": 1}}, {\"score\": {\"$lte\": 5}}]}","handlingStrategy":"try-catch","validationCode":"SUPPORTED_COMPARE = {\"$eq\", \"$ne\", \"$gt\", \"$gte\", \"$lt\", \"$lte\", \"$in\", \"$contains\"}\n\ndef safe_field_filter(field_op: dict) -> dict:\n    bad = [op for op in field_op if op.startswith(\"$\") and op not in SUPPORTED_COMPARE]\n    if bad:\n        raise ValueError(f\"unsupported comparison ops {bad}\")\n    return field_op","typeGuard":"def is_supported_compare(op: str) -> bool:\n    return op in {\"$eq\", \"$ne\", \"$gt\", \"$gte\", \"$lt\", \"$lte\", \"$in\", \"$contains\"}","tryCatchPattern":"try:\n    results = col.get(where=where)\nexcept UnsupportedFilterError:\n    results = col.get()  # broad fetch, then post-filter in Python\n    results = post_filter(results, where)","preventionTips":["Never call private _matches_where/_compare directly; go through the public API so validation runs.","After upgrading mempalace, re-check the supported operator list — it can grow between versions.","Express range logic as $gte/$lte pairs instead of exotic operators."],"tags":["sqlite-exact","filter","compare","internal-api"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}