{"record":{"id":"58fbaeab268fcd8f","repo":"mem0ai/mem0","slug":"cannot-mix-range-operators-ops-filter-o-ra","errorCode":null,"errorMessage":"Cannot mix range operators (${ops.filter((o) => rangeOps.includes(o)).join(\", \")}) with non-range operators (${nonRangeOps.join(\", \")}) for field '${key}'. Use AND to combine them as separate conditions.","messagePattern":"Cannot mix range operators \\((.+?)\\) with non-range operators \\((.+?)\\) for field '(.+?)'\\. Use AND to combine them as separate conditions\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mem0-ts/src/oss/src/vector_stores/qdrant.ts","lineNumber":159,"sourceCode":"      }\n      // Simple equality\n      return { key, match: { value } };\n    }\n\n    // Handle array shorthand: {\"field\": [\"a\", \"b\"]} treated as \"in\" operator\n    if (Array.isArray(value)) {\n      return { key, match: { any: value } };\n    }\n\n    const ops = Object.keys(value);\n    const rangeOps = [\"gt\", \"gte\", \"lt\", \"lte\"];\n    const hasRangeOps = ops.some((op) => rangeOps.includes(op));\n    const nonRangeOps = ops.filter((op) => !rangeOps.includes(op));\n\n    // Handle range operators\n    if (hasRangeOps) {\n      if (nonRangeOps.length > 0) {\n        throw new Error(\n          `Cannot mix range operators (${ops.filter((o) => rangeOps.includes(o)).join(\", \")}) ` +\n            `with non-range operators (${nonRangeOps.join(\", \")}) for field '${key}'. ` +\n            `Use AND to combine them as separate conditions.`,\n        );\n      }\n      const range: Record<string, number | string> = {};\n      for (const op of rangeOps) {\n        if (op in value) {\n          range[op] = value[op];\n        }\n      }\n      return { key, range };\n    }\n\n    // Handle comparison operators\n    if (\"eq\" in value) {\n      return { key, match: { value: value.eq } };\n    }","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0-ts/src/oss/src/vector_stores/qdrant.ts#L141-L177","documentation":"In the Qdrant store, an object-valued filter field is converted into a single Qdrant range condition when it contains range operators (gt, gte, lt, lte). Qdrant's range model allows only one range object per field, so mixing range operators with non-range operators (eq, ne, in, nin, contains, ...) in the same object is rejected with guidance to split them into separate AND conditions.","triggerScenarios":"Filters like { ts: { gte: 1, eq: 5 } } or { price: { lt: 100, currency: 'USD' } } — any object with both a range op (gt/gte/lt/lte) and a non-range op under the same key.","commonSituations":"Naturally writing a compound predicate on one field ({ date: { gte: a, lte: b, ne: holiday } }); merging filter objects from multiple sources that collapse into a single key.","solutions":["Split the mixed condition into separate filter dicts combined with AND: { AND: [ { ts: { gte: 1 } }, { ts: { eq: 5 } } ] }","Keep pure range objects ({ gte, lte }) per field and put equality checks as their own top-level filter entries"],"exampleFix":"// before\nconst r = await vs.search(vec, { filters: { ts: { gte: 1, eq: 5 } } });\n\n// after\nconst r = await vs.search(vec, {\n  filters: { AND: [ { ts: { gte: 1 } }, { ts: { eq: 5 } } ] },\n});","handlingStrategy":"validation","validationCode":"const RANGE_OPS = new Set(['gt','gte','lt','lte']);\nfunction splitMixedRangeFilters(filters: any): any {\n  const out: any = {};\n  for (const [k, v] of Object.entries(filters)) {\n    if (v && typeof v === 'object' && !Array.isArray(v)) {\n      const ops = Object.keys(v);\n      if (ops.some(o => RANGE_OPS.has(o)) && ops.some(o => !RANGE_OPS.has(o))) {\n        const range: any = {}, eqs: any[] = [];\n        for (const [op, val] of Object.entries(v)) {\n          if (RANGE_OPS.has(op)) range[op] = val; else eqs.push({ [k]: { [op]: val } });\n        }\n        out.AND = [...(out.AND ?? []), { [k]: range }, ...eqs];\n        continue;\n      }\n    }\n    out[k] = v;\n  }\n  return out;\n}","typeGuard":"const isPureRangeObject = (v: Record<string, any>): boolean =>\n  Object.keys(v).length > 0 && Object.keys(v).every(o => ['gt','gte','lt','lte'].includes(o));","tryCatchPattern":"try { await vs.search(vec, { filters }); } catch (e) { if (e instanceof Error && e.message.includes('Cannot mix range operators')) { /* split field into AND entries, retry */ } throw e; }","preventionTips":["One operator class per field object: pure range or pure match, never both","Build compound predicates as AND lists from the start","Encapsulate filter construction behind a small query-builder in your codebase"],"tags":["qdrant","search-filters","operator-error","range-query","validation","typescript"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}