What happens
Measured on main at ad8ff24 with nine records in one collection: five carry an integer property d_int (2, 1, -1, 0, and 3) and four do not carry it. Count of records each filter returns:
| filter |
SQLiteVectorStore, SQLiteVecVectorStore |
MilvusVectorStore (Milvus 2.6.24 and Milvus Lite 3.2.1) |
QdrantVectorStore (Qdrant 1.17.0, REST and gRPC) |
d_int = 2 |
1 |
1 |
1 |
not (d_int = 2) |
4 |
4 |
8 |
d_int != 2 |
4 |
4 |
8 |
not (d_int = 2) on the SQLite stores and Milvus returns only the four records holding another value and drops the four without the property, so a filter and its negation together miss records. Qdrant returns all eight. The same split holds for string properties and for properties not in indexed_properties_schema. not (d_int in [1, 2]) drops the four on the SQLite stores and Milvus 2.6.24 and keeps them on Qdrant and Milvus Lite.
Why
- The SQL stores compile a leaf to
and_(type_check, column <op> value) (packages/server/src/memmachine_server/common/filter/sql_filter_util.py:183-187) and Not to ~inner (:229-230). For a record without the property the leaf is SQL NULL, NOT NULL is NULL, and the row is filtered out. != compiles to column != value, which is NULL there as well.
- The Milvus store renders
Not as Milvus not (...) and != as Milvus != over the property's dynamic field (packages/server/src/memmachine_server/common/vector_store/milvus_vector_store.py:116-119, :132-136); neither matches an entity that lacks the field.
- The Qdrant store renders both
!= and Not as must_not (packages/server/src/memmachine_server/common/vector_store/qdrant_vector_store.py:96-99, :150-157), which admits points without the field.
Expected
Negation is the complement on every store: not (f) matches exactly the records f does not match, records without the property included, and x != v matches the same records as not (x = v).
Fix
#1616 makes negation the complement on every backend: != parses as Not(Equals), and the SQL compilers render Not as NOT COALESCE(x, FALSE). For Milvus it builds on #1736, which pushes negation down to the conditions and admits entities lacking the property.
🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.
What happens
Measured on
mainat ad8ff24 with nine records in one collection: five carry an integer propertyd_int(2, 1, -1, 0, and 3) and four do not carry it. Count of records each filter returns:SQLiteVectorStore,SQLiteVecVectorStoreMilvusVectorStore(Milvus 2.6.24 and Milvus Lite 3.2.1)QdrantVectorStore(Qdrant 1.17.0, REST and gRPC)d_int = 2not (d_int = 2)d_int != 2not (d_int = 2)on the SQLite stores and Milvus returns only the four records holding another value and drops the four without the property, so a filter and its negation together miss records. Qdrant returns all eight. The same split holds for string properties and for properties not inindexed_properties_schema.not (d_int in [1, 2])drops the four on the SQLite stores and Milvus 2.6.24 and keeps them on Qdrant and Milvus Lite.Why
and_(type_check, column <op> value)(packages/server/src/memmachine_server/common/filter/sql_filter_util.py:183-187) andNotto~inner(:229-230). For a record without the property the leaf is SQLNULL,NOT NULLisNULL, and the row is filtered out.!=compiles tocolumn != value, which isNULLthere as well.Notas Milvusnot (...)and!=as Milvus!=over the property's dynamic field (packages/server/src/memmachine_server/common/vector_store/milvus_vector_store.py:116-119,:132-136); neither matches an entity that lacks the field.!=andNotasmust_not(packages/server/src/memmachine_server/common/vector_store/qdrant_vector_store.py:96-99,:150-157), which admits points without the field.Expected
Negation is the complement on every store:
not (f)matches exactly the recordsfdoes not match, records without the property included, andx != vmatches the same records asnot (x = v).Fix
#1616 makes negation the complement on every backend:
!=parses asNot(Equals), and the SQL compilers renderNotasNOT COALESCE(x, FALSE). For Milvus it builds on #1736, which pushes negation down to the conditions and admits entities lacking the property.🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.