Filters
Reference for the filter builders and the native syntax each adapter compiles them to.
You build a filter once with the exported helpers. Each adapter compiles the same filter to its provider's native syntax. The compilers are total: every filter compiles on every provider.
import { and, eq, gt, isIn, not, or } from "vecstore-sdk";
const filter = and(
eq("genre", "drama"),
gt("year", 2000),
or(isIn("lang", ["en", "th"]), not(eq("region", "eu")))
);Builders
| Builder | Qdrant | pgvector and Supabase | Pinecone | Upstash | Vectorize | Redis |
|---|---|---|---|---|---|---|
eq(field, value) | match.value, or a closed range for floats | metadata @> '{"field": value}' | { field: { $eq } } | field = value | { field: { $eq } } | @field:{"value"} on a tag, @field:[v v] on a number |
ne(field, value) | must_not of the above | NOT (metadata @> …) | $ne | field != value | $ne | -(…) of the above |
gt, gte, lt, lte | range | A jsonb comparison guarded by jsonb_typeof | $gt, $gte, $lt, $lte | >, >=, <, <= | $gt, $gte, $lt, $lte | @field:[(v +inf] and the other three bounds |
isIn(field, values) | match.any, or should for mixed types | metadata->field <@ values | $in, or $or of $eq for booleans | field IN (…) | $in | @field:{"a" | "b"} on a tag, a union of ranges on a number |
notIn(field, values) | match.except, or must_not | NOT COALESCE(… <@ …, false) | $nin, or $and of $ne for booleans | field NOT IN (…) | $nin | -(…) of the above |
exists(field) | must_not is_empty | IS NOT NULL AND jsonb_typeof <> 'null' | $exists: true | HAS FIELD field | unsupported | -ismissing(@field) |
and, or | must, should | AND, OR | $and, $or | AND, OR | and merges fields into one object, or is unsupported | a space, | |
not(filter) | must_not | NOT (…) | Pushed to the leaves with De Morgan's laws | Pushed to the leaves with De Morgan's laws | Pushed to the leaves with De Morgan's laws | -(…) around the clause |
isIn, notIn, and, and or take at least one element. The types enforce this.
Compiled output
The filter and(eq("genre", "drama"), gt("year", 2000)) compiles to:
{
"must": [
{ "key": "genre", "match": { "value": "drama" } },
{ "key": "year", "range": { "gt": 2000 } }
]
}(metadata @> $1::jsonb
AND (jsonb_typeof((metadata->$2::text)) = 'number'
AND (metadata->$2::text) > $3::jsonb))
-- $1 = '{"genre":"drama"}', $2 = 'year', $3 = '2000'{
"$and": [{ "genre": { "$eq": "drama" } }, { "year": { "$gt": 2000 } }]
}(genre = 'drama' AND year > 2000){
"genre": { "$eq": "drama" },
"year": { "$gt": 2000 }
}(@genre:{"drama"} @year:[(2000 +inf])Inspect the native filter
Qdrant, pgvector, Pinecone, Upstash, Vectorize, and Redis each export their compiler. Import it to log or reuse the native output:
import { compileQdrantFilter } from "vecstore-sdk/qdrant";
const native = compileQdrantFilter(filter);Supabase has no compiler to import. The filter travels to Postgres as JSON and vecstore_filter_sql emits the pgvector predicates there, as the Supabase page describes.
compileRedisFilter takes a second argument, the metadata fields the index schema declares, because a Redis query names a field the schema has to hold.
Where a provider lacks an operator, the compiler rewrites the filter instead of failing. Three providers are exceptions. Upstash's filter is a string, so the compiler rejects a field name or a string value it cannot write safely and the verb returns an invalid_argument error. Vectorize has no OR and no presence test, so compileVectorizeFilter returns a Result and the verb returns an unsupported error before it sends a request. Redis matches nothing on a field its schema does not declare, so compileRedisFilter returns a Result and the verb returns an invalid_argument error instead of an empty page. See Provider differences for the two cases where the semantics diverge.