{"record":{"id":"bff3a80755c93314","repo":"RyanCodrai/turbovec","slug":"both-metadata-value-and-filter-value-must-be-strin","errorCode":null,"errorMessage":"Both metadata value and filter value must be strings for the TEXT_MATCH operator","messagePattern":"Both metadata value and filter value must be strings for the TEXT_MATCH operator","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"turbovec-python/python/turbovec/llama_index.py","lineNumber":764,"sourceCode":"            return value >= target\n        if op == FilterOperator.LTE:\n            return value <= target\n        if op == FilterOperator.IN:\n            return value in target\n        if op == FilterOperator.NIN:\n            return value not in target\n        if op == FilterOperator.CONTAINS:\n            return target in value\n        if op == FilterOperator.TEXT_MATCH:\n            # Case-SENSITIVE substring. `FilterOperator` defines\n            # TEXT_MATCH and TEXT_MATCH_INSENSITIVE as distinct operators,\n            # so folding case here would collapse that distinction and\n            # leave no way to ask for a case-sensitive match. The type\n            # guard is ours: the reference raises AttributeError on a\n            # non-string (issue #302).\n            if isinstance(target, str) and isinstance(value, str):\n                return target in value\n            raise TypeError(\n                \"Both metadata value and filter value must be strings \"\n                \"for the TEXT_MATCH operator\"\n            )\n        if _TEXT_MATCH_INSENSITIVE is not None and op == _TEXT_MATCH_INSENSITIVE:\n            if isinstance(target, str) and isinstance(value, str):\n                return target.lower() in value.lower()\n            raise TypeError(\n                \"Both metadata value and filter value must be strings \"\n                \"for the TEXT_MATCH_INSENSITIVE operator\"\n            )\n        if op == FilterOperator.ALL:\n            # Reference (`utils.py:152-153`): every element of `target`\n            # must be present in the metadata value (which is typically\n            # a list — tag-set matching).\n            return all(t in value for t in target)\n        if op == FilterOperator.ANY:\n            return any(t in value for t in target)\n        raise NotImplementedError(","sourceCodeStart":746,"sourceCodeEnd":782,"githubUrl":"https://github.com/RyanCodrai/turbovec/blob/ccab9f325e6ce2a270a87daf01ae4e443bcf2d49/turbovec-python/python/turbovec/llama_index.py#L746-L782","documentation":"TEXT_MATCH requires both the metadata value and the filter value to be strings so substring semantics are well-defined. The reference implementation raises AttributeError on non-strings (llama_index issue #302); this library raises a clearer TypeError instead.","triggerScenarios":"Querying with FilterOperator.TEXT_MATCH where the stored metadata field is a number/bool/list or the filter value is a non-string.","commonSituations":"Numeric fields (ids, counts) queried with TEXT_MATCH; forgetting str() conversion on the filter value; metadata schema drift after a pipeline change.","solutions":["Convert the metadata field to a string at ingestion time (store it as str)","Convert the filter value to a string: FilterValue=str(123)","Use a numeric operator (EQ/GT) instead of TEXT_MATCH for non-string fields"],"exampleFix":"// before\nMetadataFilter(key=\"count\", value=5, operator=FilterOperator.TEXT_MATCH)\n// after\nMetadataFilter(key=\"count\", value=\"5\", operator=FilterOperator.TEXT_MATCH)","handlingStrategy":"type-guard","validationCode":"if op == FilterOperator.TEXT_MATCH and not (isinstance(meta_val, str) and isinstance(filt_val, str)):\n    raise TypeError(\"TEXT_MATCH requires string metadata and filter values\")","typeGuard":"def text_match_safe(meta_val, filt_val) -> bool:\n    return isinstance(meta_val, str) and isinstance(filt_val, str) and meta_val in filt_val","tryCatchPattern":"try:\n    store.query(q)\nexcept TypeError as e:\n    if \"TEXT_MATCH operator\" in str(e):\n        coerce_filter_values_to_str(q.filters)\n        store.query(q)\n    else:\n        raise","preventionTips":["Store metadata fields that will be text-matched as strings","Coerce filter values with str() when building filters","Validate filter value types against the metadata schema"],"tags":["python","filters","type-mismatch","text-match"],"backgroundTag":"type-mismatch","analyzedSha":"ccab9f325e6ce2a270a87daf01ae4e443bcf2d49","analyzedAt":"2026-09-06T08:39:18.516Z","contentChangedAt":"2026-09-06T08:39:18.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}