Skip to main content

search_vector

Function search_vector 

Source
pub async fn search_vector(
    conn: &Connection,
    query_vec: &[f32],
    model: &ModelName,
    top_k: usize,
    as_of_valid: Option<&str>,
    half_life: Option<Duration>,
) -> Result<Vec<VectorSearchResult>>
Expand description

Top-k nearest visible neighbours for query_vec under model (§5.9).

Goes through vector_top_k, which consults the DiskANN index, rather than scanning the table and sorting: the index is what §9’s “top-10 over 100K concepts in ≤20 ms” budget assumes, and an ORDER BY vector_distance_cos(…) over the whole table is linear in the corpus no matter how small k is. vector_top_k yields base-table rowids, so the distance is recomputed on the k rows it selects — k distance evaluations, not one per concept.

§top_k is a count, and keeping it one is the whole of the loop

The index chooses its k rows before the visibility predicate can see them, so a filter applied afterwards returns fewer than k whenever a retired concept is among them. Letting top_k become a ceiling would be a silent behaviour change for every existing caller, so the index is asked for a larger k' instead — the escalation crate::graph::FilteredVectorSearch already performs against the same problem, and the inflation CostEstimator::k_prime computes from selectivity.

It runs only when the first pass comes up short, which is the case where something was actually filtered out. A corpus with nothing retired — the overwhelmingly common one — pays one query and no count, which is why the loop is here rather than a selectivity estimate computed up front: that would put two COUNT(*)s on every search to serve the case that almost never arises.

Termination is by exhaustion, not by a retry budget. k' doubles until the index has been asked for the whole table, and a k' at or above the row count means what came back is every visible neighbour — a complete answer, not a truncated one. The row count is read at most once and only on the escalating path.

§as_of_valid: what was true then, or the corpus (0.13.19, W9.4, F-32)

With an instant, a concept is a result only while its own valid interval contains it — the half-open bound the whole crate uses, and the same clause the visibility predicate already carries, so it costs no extra join. Without one, the statement is byte-for-byte what 0.13.18 issued: an absent knob leaves the mechanism alone (D-155).

It is as_of_valid and not as_of because crate::graph::TraversalBuilder::as_of_valid split that word into two axes in 0.13.2, and one spelling per axis is the point of having split it. Transaction time is deliberately not offered here: reading the index as it stood at a past recorded_at would mean searching vectors that have since been replaced, and the DiskANN index holds one row per concept with no history to search. A caller who wants that asks the ledger, not the index.

The escalation above needs no adjustment for it. It keys on a pass coming up short and not on why it came up short, so a corpus thinned by valid time re-asks the index exactly as one thinned by retirement does.