Skip to content

Commit fae7de0

Browse files
authored
Merge pull request #2362 from MemPalace/fix/sqlite-exact-cache-and-id-filters
fix(sqlite_exact): cross-handle cache invalidation and filtered id lookups
2 parents 88247ac + 1076e68 commit fae7de0

2 files changed

Lines changed: 86 additions & 4 deletions

File tree

mempalace/backends/sqlite_exact.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,10 @@ def __init__(
464464
# collection_id -> (ids, float32 matrix, mini-metadata). Filled lazily
465465
# by query() so a long-lived hub does not re-read every embedding blob
466466
# on the next search. Mini-metadata is wing/room/source_file for
467-
# in-memory equality filters. Cleared on any write.
467+
# in-memory equality filters. Cleared on any write through this handle;
468+
# ``_vector_cache_data_version`` detects commits from other handles.
468469
self._vector_cache: dict[int, tuple[list[str], np.ndarray, list[dict]]] = {}
470+
self._vector_cache_data_version: Optional[int] = None
469471

470472

471473
class SQLiteExactCollection(BaseCollection):
@@ -958,6 +960,10 @@ def _rank_vectors(
958960
_validate_where(where_document)
959961
expected = self._collection_dimension(cur, collection_id)
960962
empty = np.zeros((0, expected or 0), dtype=np.float32)
963+
data_version = int(cur.execute("PRAGMA data_version").fetchone()[0])
964+
if self._handle._vector_cache_data_version != data_version:
965+
self._handle._vector_cache.clear()
966+
self._handle._vector_cache_data_version = data_version
961967
cached = self._handle._vector_cache.get(collection_id)
962968
if cached is None:
963969
cached = self._load_all_vectors(cur, collection_id, expected)
@@ -1054,11 +1060,25 @@ def get(
10541060
) -> GetResult:
10551061
spec = _IncludeSpec.resolve(include, default_distances=False)
10561062
# get(ids=...) must not scan the collection: look up by primary key.
1057-
if ids is not None and where is None and where_document is None:
1063+
if ids is not None:
1064+
_validate_where(where)
1065+
_validate_where(where_document)
1066+
lookup_spec = _IncludeSpec(
1067+
documents=spec.documents or bool(where_document),
1068+
metadatas=spec.metadatas or bool(where),
1069+
distances=False,
1070+
embeddings=spec.embeddings,
1071+
)
10581072
with self._cursor() as cur:
10591073
collection_id = self._collection_id(cur)
1060-
by_id = self._rows_by_ids(cur, collection_id, list(ids), spec)
1061-
rows = [by_id[doc_id] for doc_id in ids if doc_id in by_id]
1074+
by_id = self._rows_by_ids(cur, collection_id, list(ids), lookup_spec)
1075+
rows = [
1076+
by_id[doc_id]
1077+
for doc_id in ids
1078+
if doc_id in by_id
1079+
and _matches_where(by_id[doc_id]["metadata"], where)
1080+
and _matches_where_document(by_id[doc_id]["document"], where_document)
1081+
]
10621082
if offset:
10631083
rows = rows[offset:]
10641084
if limit is not None:

tests/test_sqlite_exact_backend.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,37 @@ def test_sqlite_exact_get_preserves_requested_id_order_and_duplicates(tmp_path):
145145
assert result.documents == ["doc b", "doc a", "doc b"]
146146

147147

148+
@pytest.mark.parametrize(
149+
"filters",
150+
[
151+
{"where": {"wing": "keep"}},
152+
{"where_document": {"$contains": "needle"}},
153+
{
154+
"where": {"wing": "keep"},
155+
"where_document": {"$contains": "needle"},
156+
},
157+
],
158+
)
159+
def test_sqlite_exact_get_ids_intersects_filters(tmp_path, filters):
160+
_backend, col = _collection(tmp_path)
161+
col.add(
162+
ids=["requested", "not-requested", "filtered-out"],
163+
documents=["needle requested", "needle other", "different"],
164+
metadatas=[{"wing": "keep"}, {"wing": "keep"}, {"wing": "drop"}],
165+
embeddings=[[1, 0], [0, 1], [0.5, 0.5]],
166+
)
167+
168+
result = col.get(
169+
ids=["filtered-out", "requested", "requested"],
170+
include=[],
171+
**filters,
172+
)
173+
174+
assert result.ids == ["requested", "requested"]
175+
assert result.documents == []
176+
assert result.metadatas == []
177+
178+
148179
def _doc_select_sql(col, action):
149180
"""Run ``action`` while tracing SQL; return (result, [documents SELECTs]).
150181
@@ -1211,6 +1242,37 @@ def test_sqlite_exact_query_cache_invalidates_on_add(tmp_path):
12111242
assert second.ids[0] == ["new"]
12121243

12131244

1245+
def test_sqlite_exact_query_cache_invalidates_after_external_handle_write(tmp_path):
1246+
reader_backend, reader = _collection(tmp_path)
1247+
reader.add(
1248+
ids=["old"],
1249+
documents=["old"],
1250+
metadatas=[{}],
1251+
embeddings=[[0.0, 1.0]],
1252+
)
1253+
first = reader.query(query_embeddings=[[1.0, 0.0]], n_results=1)
1254+
assert first.ids[0] == ["old"]
1255+
1256+
writer_backend = SQLiteExactBackend()
1257+
writer = writer_backend.get_collection(
1258+
palace=PalaceRef(id=str(tmp_path), local_path=str(tmp_path)),
1259+
collection_name="mempalace_drawers",
1260+
create=False,
1261+
)
1262+
writer.add(
1263+
ids=["new"],
1264+
documents=["new"],
1265+
metadatas=[{}],
1266+
embeddings=[[1.0, 0.0]],
1267+
)
1268+
1269+
second = reader.query(query_embeddings=[[1.0, 0.0]], n_results=2)
1270+
assert second.ids[0] == ["new", "old"]
1271+
1272+
writer_backend.close()
1273+
reader_backend.close()
1274+
1275+
12141276
def test_sqlite_exact_wing_room_counts(tmp_path):
12151277
from mempalace.backends.sqlite_exact import sqlite_wing_room_counts
12161278

0 commit comments

Comments
 (0)