Skip to content

Provider differences

The two filter cases where providers disagree, and the features one provider lacks.

The compilers agree on scalar fields with present values. Two cases differ across providers. If your application depends on either, add an exists clause or store a scalar field to make the intent explicit.

Negation and missing fields

ne, notIn, and not match records that lack the field on Qdrant, pgvector, Supabase, and Redis. Pinecone, Upstash, and Vectorize evaluate operators against present fields only. A record without genre does not match ne("genre", "drama") on those three but does match it on the other four.

Array-valued fields

eq("tags", "x") and isIn("tags", ["x"]) mean “contains x” on Qdrant, pgvector, Supabase, and Redis, where each element of a string list is indexed as its own tag. Pinecone supports $in on list fields and rejects $eq. Upstash compares the array itself, so neither builder matches an element. Reach an element with the accessors Upstash documents, such as eq("tags[0]", "x"). Vectorize indexes string, number, and boolean properties only, so an array-valued field is not filterable there at all. notIn and not(isIn(...)) are the same filter on these four providers. A record matches when none of its list elements is in the values.

Features one provider lacks

  • Pinecone serverless indexes reject delete({ filter }). The adapter returns { kind: "unsupported", feature: "deleteByFilter" }.
  • Pinecone createIndex needs a deployment spec. The adapter defaults to serverless on AWS in us-east-1. Pass indexSpec to change it.
  • pgvector createIndex runs CREATE EXTENSION IF NOT EXISTS vector, which needs a role that can create extensions.
  • Supabase needs the SQL functions in sql/supabase.sql installed once. Until they are, every verb returns { kind: "unsupported" } naming the missing function.
  • Supabase createIndex and deleteIndex run DDL, so they need the service role key. The anon and authenticated roles get { kind: "unauthorized" }, and the install script revokes those two functions from them.
  • Upstash cannot create a vector index from @upstash/vector. A vecstore index is an Upstash namespace, and createIndex only checks the dimension and metric against the index you connected to. See Upstash Vector.
  • Upstash normalizes scores to the range 0 to 1 and orders every metric so that higher is more similar. Qdrant, pgvector, Supabase, Pinecone, and Vectorize return the provider's raw score, where higher is better for cosine and dot and lower is better for euclidean. Redis returns a distance for every metric, where lower is closer.
  • Upstash filters are a string, so the compiler rejects field names and string values it cannot write safely with an invalid_argument error.
  • Upstash gives each query a filtering budget and can return fewer than topK records when the filter is selective. The default namespace mode scopes every query by namespace, so this applies to every Upstash query. namespaceMode: "native" puts each namespace in a real Upstash namespace instead. See Upstash Vector.
  • Vectorize has no OR operator and no test for a missing property, so or(...) and exists(...) return { kind: "unsupported" } before the request is sent. See Cloudflare Vectorize.
  • Vectorize deletes by id only, so delete({ filter }) and delete({ all: true }) return { kind: "unsupported" }.
  • Vectorize filters match nothing on a property with no metadata index, and a metadata index has to exist before you insert. Pass metadataIndexes so createIndex opens them.
  • Vectorize writes are asynchronous. An upsert returns once the mutation is enqueued, and the records become searchable a moment later.
  • Redis needs the query engine and JSON, which ship with Redis 8, Redis Stack 7.4 or later, and Redis Cloud. On a server without them every verb returns { kind: "unsupported", feature: "queryEngine" }.
  • Redis searches a metadata field only when the index schema declares it, so createRedisStore takes metadataFields and the compiler rejects a filter on any other field with invalid_argument. A number belongs to a numeric field and everything else to a tag field, and upsert rejects a record that contradicts that, because Redis would leave the whole document out of the index.
  • Redis tag matching is case-sensitive, so eq("genre", "Drama") does not match drama.
  • Redis returns the vector distance as the score, where lower is closer, for every metric. See Redis.
  • Redis listIndexes returns every index in the database, including indexes another application created.

Metadata keys that contain a dot

Qdrant, Upstash, and Vectorize read a . in a filter field as a step into a nested object, and the adapters store metadata flat. A filter on a key that contains a dot, such as eq("file.name", "a.pdf"), therefore never sees the value. eq matches nothing on those three, and ne matches every record on Qdrant and none on Upstash and Vectorize, which evaluate operators against present fields only. pgvector and Supabase look the key up as written, and Redis rejects the field name with invalid_argument. Use metadata keys without dots for anything you filter on.

On this page