mem0ai/mem0 · error · Error

Oracle filter operator '${operator}' requires a non-empty ar

Error message

Oracle filter operator '${operator}' requires a non-empty array

What it means

The in/nin operators compile to a JSON_EXISTS '@ in (...)' list, which requires at least one element to form valid predicate syntax and a NOT-wrap for nin. Passing a non-array operand or an empty array is rejected up front.

Source

Thrown at mem0-ts/src/oss/src/vector_stores/oracledb.ts:189

      }
      if (operand === null) {
        if (operator !== "eq" && operator !== "ne") {
          throw new Error(
            `Oracle filter operator '${operator}' does not support null`,
          );
        }
        predicates.push(`@ ${COMPARISON_OPERATORS[operator]} null`);
        continue;
      }
      const [variable, passing] = bindFilterValue(operand, binds);
      predicates.push(`@ ${COMPARISON_OPERATORS[operator]} ${variable}`);
      passings.push(passing);
      continue;
    }

    if (operator === "in" || operator === "nin") {
      if (!Array.isArray(operand) || operand.length === 0) {
        throw new Error(
          `Oracle filter operator '${operator}' requires a non-empty array`,
        );
      }

      const variables: string[] = [];
      const listPassings: string[] = [];
      for (const item of operand) {
        if (!isScalar(item)) {
          throw new Error(
            `Oracle filter operator '${operator}' requires scalar values`,
          );
        }
        if (item === null) {
          variables.push("null");
          continue;
        }
        const [variable, passing] = bindFilterValue(item, binds);
        variables.push(variable);

View on GitHub (pinned to 001c235229)

Solutions

  1. Skip the filter clause entirely when the list is empty rather than sending { in: [] }.
  2. Wrap single values in an array: { status: { in: ['active'] } }.
  3. Split strings into arrays: ids.split(',') before filtering.
  4. For 'match anything' semantics with nin and an empty exclusion list, omit the nin clause instead.

Example fix

// before
const filters = statuses.length ? { status: { in: statuses } } : { status: { in: [] } };

// after
const filters = statuses.length > 0 ? { status: { in: statuses } } : undefined;
await memory.search('q', { filters });
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeInFilters(filters: any): any {
  if (Array.isArray(filters)) return filters.map(sanitizeInFilters);
  if (!filters || typeof filters !== 'object') return filters;
  const out: Record<string, any> = {};
  for (const [k, v] of Object.entries(filters)) {
    if (v && typeof v === 'object' && ('in' in v || 'nin' in v)) {
      const cleaned: Record<string, any> = {};
      for (const [op, list] of Object.entries(v)) {
        if ((op === 'in' || op === 'nin') && (!Array.isArray(list) || list.length === 0)) continue; // drop empty/invalid
        cleaned[op] = list;
      }
      if (Object.keys(cleaned).length) out[k] = cleaned;
    } else out[k] = v;
  }
  return Object.keys(out).length ? out : undefined;
}

Type guard

const isNonEmptyArray = (v: unknown): v is unknown[] => Array.isArray(v) && v.length > 0;

Prevention

When it happens

Trigger: { status: { in: [] } } (empty array, usually from dynamic list-building), { status: 'active' } where a bare string reaches the operator branch, { ids: { in: 'abc' } } (string instead of array), or { ids: { in: null } }.

Common situations: Whitelist/allowlist filters computed at runtime that end up empty (e.g. no user roles matched); passing a comma-separated string instead of an array; a client sending a scalar where the server forwards it as the in-operand.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/68dc84a9b097c2f5. Report an issue: GitHub.