fix(find): connected finds are graph-first — neighbours, then the filter over those ids, then the page
With `connected` present, find() materialized the whole-store filtered id list, paged it, hydrated the page, and only then intersected with the neighbour set. Every such call paid O(store) for the filter and the hydration of rows that were never neighbours, and a neighbour outside the first page of the filtered STORE was silently dropped — the answer depended on the store's order and the page size. The neighbour set is now the candidate universe: resolved first from the adjacency, the metadata filter evaluated over those ids only through the provider's own evaluation (a new optional `filterIdsWithin` door on MetadataIndexProvider; the reference index implements it from its own getIdsForFilter so the two can never disagree; a provider without it is served by the whole-store answer intersected here), `orderBy` sorts the whole neighbour set before the page is cut, and the vector leg walks the neighbours as its candidate set. The text leg of a hybrid find keeps its post-intersection — it has no candidate door. Pinned in tests/integration/find-connected-order.test.ts: paging reaches every matching neighbour and never a non-neighbour; a `missing` negation is evaluated over the neighbours; the index is asked about the neighbour ids only and hydration is one page; orderBy sorts the whole set; the vector leg stays inside the neighbours; an edgeless anchor answers [] before the filter is asked.
This commit is contained in:
parent
5e3b343a0e
commit
077cbc0b6f
4 changed files with 290 additions and 22 deletions
|
|
@ -2575,6 +2575,19 @@ export class MetadataIndexManager implements MetadataIndexProvider {
|
|||
/** Once-per-field flag for the fallback-degradation announcement. */
|
||||
private static announcedFallbackSorts = new Set<string>()
|
||||
|
||||
/**
|
||||
* Evaluate `filter` over `ids` only — the graph-first find's door (the
|
||||
* neighbour set filtered by id, never the store filtered and then
|
||||
* intersected). This index answers from its own `getIdsForFilter`, so the
|
||||
* two doors cannot disagree; the cost is that of the filter over this
|
||||
* in-memory index, and the answer keeps the caller's order.
|
||||
*/
|
||||
async filterIdsWithin(filter: any, ids: readonly string[]): Promise<string[]> {
|
||||
if (ids.length === 0) return []
|
||||
const matched = new Set(await this.getIdsForFilter(filter))
|
||||
return ids.filter((id) => matched.has(id))
|
||||
}
|
||||
|
||||
async getSortedIdsForFilter(
|
||||
filter: any,
|
||||
orderBy: string,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue