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
TKeyThe key type:
int,long,stringorGuid.TRecordThe record type.
- Inheritance
-
VectorStoreCollection<TKey, TRecord>EdgeVectorStoreCollection<TKey, TRecord>
- Implements
-
IVectorSearchable<TRecord>IKeywordHybridSearchable<TRecord>
- 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
TRecordthrough MEVD's reflection-basedCollectionModelBuilder.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.ScoreThresholdis applied to the fused score as a client-side>=after the query, where vector search pushes its threshold down asdistance <=.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 malformedMATCH.
- SearchAsync<TInput>(TInput, int, VectorSearchOptions<TRecord>?, CancellationToken)
A vec0 KNN search.
searchValuemay 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.
ScoreThresholdis pushed down asv.distance <= $t.Skipis honoured client-side, by discarding the first N rows while reading overk = top + Skip.SQLITE_INDEX_CONSTRAINT_OFFSETis skipped in every loop ofvec0BestIndexand never given anargvIndex, so anOFFSETin 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
GenerateAsynccall 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 workingINSERT OR REPLACEonly arrives in 0.1.10-alpha, and vec0 overloads SQLNULLon 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.