Table of Contents

Class EdgeVectorStoreCollection<TKey, TRecord>

Namespace
Qavren.Edge.VectorData
Assembly
Qavren.Edge.VectorData.dll

One collection: a data table, a vec0 sidecar, an optional FTS5 sidecar, and the triggers that keep the three in step. Every statement it runs is text EdgeVectorSchema emits, so a consumer can read, log or hand-execute it.

[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "The name mirrors the MEVD base type VectorStoreCollection<TKey, TRecord> this derives from. Renaming it would break the one-to-one reading of the provider against the abstraction it implements.")]
public class EdgeVectorStoreCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRecord>, IVectorSearchable<TRecord>, IDisposable, IKeywordHybridSearchable<TRecord> where TKey : notnull where TRecord : class

Type Parameters

TKey

The key type: int, long, string or Guid.

TRecord

The record type.

Inheritance
VectorStoreCollection<TKey, TRecord>
EdgeVectorStoreCollection<TKey, TRecord>
Implements
Derived
Inherited Members

Constructors

EdgeVectorStoreCollection(IEdgeDatabase, string, CollectionModel, EdgeVectorStoreCollectionOptions)

The trim/AOT-clean constructor, and the only one EdgeDynamicVectorStoreCollection chains to. It takes a model that is already built and carries no trim annotations, so a derived constructor chaining to it inherits neither IL2026 nor IL3050. Without it the "dynamic is the AOT-safe path" claim would be unverifiable: this repo sets TreatWarningsAsErrors, and the only way to compile a derived type over an annotated base constructor would be a suppression.

EdgeVectorStoreCollection(IEdgeDatabase, string, EdgeVectorStoreCollectionOptions?)

Reflects over TRecord through MEVD's reflection-based CollectionModelBuilder.Build, which is why it is annotated. The dynamic collection never reaches this constructor.

Properties

KeywordCombinator

How this collection joins hybrid-search keywords by default.

Model

The built collection model.

Name

Gets the name of the collection.

Rrf

The reciprocal-rank-fusion defaults in force for this collection.

Schema

The DDL and queries this collection runs, as plain strings.

Methods

CollectionExistsAsync(CancellationToken)

Checks if the collection exists in the vector store.

DeleteAsync(IEnumerable<TKey>, CancellationToken)

Deletes by key from the data table only: DELETE FROM "notes" WHERE "Key" = $key. The four triggers clean vec0 and FTS5, so no orphan survives even when an app deletes rows with its own SQL against the same IEdgeDatabase.

DeleteAsync(TKey, CancellationToken)

Deletes a record from the vector store. Does not guarantee that the collection exists.

EnsureCollectionDeletedAsync(CancellationToken)

Deletes the collection from the vector store if it exists.

EnsureCollectionExistsAsync(CancellationToken)

Creates the data table, its indexes, the vec0 table, the FTS5 sidecar and all four triggers in one transaction. Before any of it: asserts the SQLite floor, then validates the configured generator's width against the declared vector width.

GetAsync(IEnumerable<TKey>, RecordRetrievalOptions?, CancellationToken)

Gets a batch of records from the vector store. Does not guarantee that the collection exists.

GetAsync(Expression<Func<TRecord, bool>>, int, FilteredRecordRetrievalOptions<TRecord>?, CancellationToken)

Gets matching records from the vector store. Does not guarantee that the collection exists.

GetAsync(TKey, RecordRetrievalOptions?, CancellationToken)

Gets a record from the vector store. Does not guarantee that the collection exists. Returns null if the record is not found.

GetService(Type, object?)

Asks the IVectorSearchable<TRecord> for an object of the specified type serviceType.

HybridSearchAsync<TInput>(TInput, ICollection<string>, int, HybridSearchOptions<TRecord>?, CancellationToken)

vec0 KNN fused with FTS5 bm25 by reciprocal rank fusion.

Score here is the RRF score: higher is better - the opposite of SearchAsync<TInput>(TInput, int, VectorSearchOptions<TRecord>?, CancellationToken)'s distance. Consistently, HybridSearchOptions.ScoreThreshold is applied to the fused score as a client-side >= after the query, where vector search pushes its threshold down as distance <=.

Pass EdgeHybridSearchOptions<TRecord> to tune rrf_k (default 60), the lane weights (default 1.0 each) and the per-lane candidate count (default (top + Skip) * 4, capped at 4096). An empty or all-whitespace keyword collection short-circuits the FTS lane and degenerates to plain KNN rather than emitting a malformed MATCH.

SearchAsync<TInput>(TInput, int, VectorSearchOptions<TRecord>?, CancellationToken)

A vec0 KNN search. searchValue may be a string (embedded through the query generator), ReadOnlyMemory<T> of float, float[] or Embedding<T> of float.

Score is the vec0 distance: lower is better - the opposite polarity to HybridSearchAsync<TInput>(TInput, ICollection<string>, int, HybridSearchOptions<TRecord>?, CancellationToken)'s fused score. ScoreThreshold is pushed down as v.distance <= $t.

Skip is honoured client-side, by discarding the first N rows while reading over k = top + Skip. SQLITE_INDEX_CONSTRAINT_OFFSET is skipped in every loop of vec0BestIndex and never given an argvIndex, so an OFFSET in the KNN query would be ignored rather than applied. It is never silently dropped.

UpsertAsync(IEnumerable<TRecord>, CancellationToken)

Overridden rather than inherited, because MEVD leaves the batch overload abstract with no default: every embedding in the batch is generated in one GenerateAsync call before the transaction opens, and the whole batch then commits together.

The vec0 side is delete-then-insert, never UPDATE, for two reasons both true of sqlite-vec 0.1.9: a working INSERT OR REPLACE only arrives in 0.1.10-alpha, and vec0 overloads SQL NULL on a vector column to mean "no change". FTS5 is trigger-maintained off the data table and never appears in the write path.

UpsertAsync(TRecord, CancellationToken)

Upserts a record into the vector store. Does not guarantee that the collection exists. If the record already exists, it is updated. If the record does not exist, it is created.