Expand description
Filtered vector search: strategies, the byte-budget cost model, and the planner that chooses between them (§5.3, D-007).
A vector query rarely arrives naked. The caller wants the ten nearest neighbours of an embedding among concepts reachable in two hops, and the two access paths cannot be nested: the DiskANN index is opaque to SQL predicates, and the relational filter is opaque to the index. Composing them is a cost decision, and this module makes it arithmetically rather than by rule of thumb.
§What changed in this cycle, and why the third strategy is gone
§5.3 specified three strategies. TwoPhaseTempTable was to push the
candidate id set into the vector query as an allow-list, staging it through a
TEMP table. Both of its mechanisms are absent from libSQL 0.9.30, and
both were measured rather than reasoned about:
CREATE TEMP TABLEon the read connection fails withSQLITE_READONLY (8).PRAGMA query_only = ON(D-019) covers the TEMP database too, and D-019 is the runtime half of the write-serialization guarantee, so it is the strategy that gives way, not the pragma.- There is no allow-list to push into.
vector_top_krefuses a fourth argument at runtime — “too many arguments on vector_top_k() - max 3” — andvectorIndexSearchin the bundled amalgamation rejectsargc != 3before it looks at anything else.
So the variant named an access path this engine does not offer, and the cost
table priced an operation that cannot be issued. It is removed rather than
kept as decoration: that is the precedent D-039 set with louvain_communities
returning one community per node. If a future libSQL gains a constrained
index walk, the variant comes back with a body, which is a smaller change
than the confusion of shipping a name with nothing behind it.
§Why the strategy can never change the answer
PostFilter retrieves a generous k′ from the index and then discards
whatever fails the predicate. When the filter is tight the answer set falls
off the end of k′, and the classic implementation returns four rows for a
top-ten query without saying so. That is a silent wrong answer, which
Doctrine II exists to prevent, so it is not merely documented here — it is
detected. When a post-filtered pass comes back short and the underlying
index scan was saturated, the planner cannot conclude the matches do not
exist, so it escalates to the exact strategy and says so at debug.
That gives the module its acceptance gate: the two strategies must agree, on every query, for every graph. Strategy is then a performance decision and nothing else, which is the only form in which a planner is safe.
Structs§
- Cost
Estimate - One planning decision, with the arithmetic that produced it (D-007).
- Cost
Estimator - Byte-budget cost model estimator for vector filter strategies (§5.3, D-007).
- Filtered
Vector Search - A vector search restricted to the nodes a traversal reaches (§5.3).
Enums§
- Candidate
Count - What the counting probe learned about the candidate set.
- Vector
Filter Strategy - Strategy for combining vector search and graph traversal filters (§5.3).
Constants§
- DEFAULT_
BYTE_ BUDGET - Default ceiling on the candidate set, in bytes.
- DEFAULT_
PROBE_ CAP - Default cap on the counting probe.