Allowed moving local data sources without embedding them again (#1091)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Paul KoudelkaandThorsten Sommer authored and GitHub committed 2026-10-20 18:50:09 +02:00
1 parent f6e48e95f3
commit 5d6e48ef93
49 files changed
+2280 -297

No files matched your search

@@ -29,6 +29,11 @@ public abstract partial class IndexStoreClient(string name, string path) : Datab
public abstract Task UpsertFileAsync(string dataSourceId, EmbeddingStateFile file, CancellationToken token);
/// <summary>
/// Updates the location and fingerprint of a document, preserving its chunks and their ids.
/// </summary>
public abstract Task RelocateFileAsync(string dataSourceId, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token);
public abstract Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token);
public abstract Task UpsertPermanentFailureAsync(string dataSourceId, PermanentIndexingFailure failure, CancellationToken token);
@@ -52,6 +52,9 @@ public sealed partial class NoIndexStoreClient(string name, string? unavailableR
public override Task UpsertFileAsync(string dataSourceId, EmbeddingStateFile file, CancellationToken token) => Task.CompletedTask;
public override Task RelocateFileAsync(string dataSourceId, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token) =>
Task.FromException(new InvalidOperationException(unavailableReason ?? "The index of the data sources is not available."));
public override Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token) => Task.CompletedTask;
public override Task UpsertPermanentFailureAsync(string dataSourceId, PermanentIndexingFailure failure, CancellationToken token) => Task.CompletedTask;
@@ -149,7 +149,9 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
file.LastWriteUtc,
file.EmbeddedAtUtc,
file.ChunkCount,
file.TranscriptKey);
file.TranscriptKey,
file.RelativePath,
file.ParentFileId);
}
var permanentFailures = await context.PermanentIndexingFailures
@@ -233,6 +235,18 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
await context.SaveChangesAsync(token);
}
public override async Task RelocateFileAsync(string dataSourceId, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token)
{
await using var context = this.CreateContext();
var file = await context.EmbeddedFiles.SingleAsync(file => file.DataSourceId == dataSourceId && file.ParentFileId == parentFileId, token);
// Keep the parent and chunk ids: moving a document does not change its stored vectors.
file.AbsolutePath = absolutePath;
file.RelativePath = relativePath;
file.FileName = Path.GetFileName(absolutePath);
file.Fingerprint = fingerprint;
await context.SaveChangesAsync(token);
}
public override async Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token)
{
await using var context = this.CreateContext();
@@ -762,4 +776,4 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
client.SetLogger(databaseClientLogger);
return client;
}
}
}
@@ -37,7 +37,10 @@ public sealed class NoVectorStoreClient(string name, string? unavailableReason,
public override Task<IReadOnlyList<VectorSearchResult>> SearchEmbeddingAsync(string storeName, IReadOnlyList<float> vector, int maxMatches, VectorSearchFilter filter, CancellationToken token) =>
Task.FromException<IReadOnlyList<VectorSearchResult>>(this.CreateUnavailableException());
public override Task DeleteEmbeddingByFile(string storeName, string filePath, CancellationToken token) =>
public override Task DeleteEmbeddingByFile(string storeName, string filePath, string parentFileId, CancellationToken token) =>
Task.FromException(this.CreateUnavailableException());
public override Task RelocateEmbeddingFileAsync(string storeName, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token) =>
Task.FromException(this.CreateUnavailableException());
public override Task OptimizeVectorStore(string storeName, CancellationToken token) =>
@@ -27,6 +27,7 @@ public sealed class QdrantEdgeClientImplementation(
private const string INSERT_PATH = "/system/qdrant-edge/insert";
private const string SEARCH_PATH = "/system/qdrant-edge/search";
private const string DELETE_FILE_PATH = "/system/qdrant-edge/delete-file";
private const string RELOCATE_FILE_PATH = "/system/qdrant-edge/relocate-file";
private const string OPTIMIZE_PATH = "/system/qdrant-edge/optimize";
private const string DELETE_STORE_PATH = "/system/qdrant-edge/delete-store";
private const string PREPARE_BACKUP_PATH = "/system/qdrant-edge/backup/prepare";
@@ -156,8 +157,11 @@ public sealed class QdrantEdgeClientImplementation(
token) ?? [];
}
public override Task DeleteEmbeddingByFile(string storeName, string filePath, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, DELETE_FILE_PATH, new DeleteEmbeddingByFileRequest(storeName, filePath), token);
public override Task DeleteEmbeddingByFile(string storeName, string filePath, string parentFileId, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, DELETE_FILE_PATH, new DeleteEmbeddingByFileRequest(storeName, filePath, parentFileId), token);
public override Task RelocateEmbeddingFileAsync(string storeName, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, RELOCATE_FILE_PATH, new RelocateEmbeddingFileRequest(storeName, parentFileId, absolutePath, Path.GetFileName(absolutePath), relativePath, fingerprint), token);
public override Task OptimizeVectorStore(string storeName, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, OPTIMIZE_PATH, new OptimizeVectorStoreRequest(storeName), token);
@@ -194,7 +198,9 @@ public sealed class QdrantEdgeClientImplementation(
/// <param name="PointIds">The only points the search may return, or null for the whole store.</param>
private sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds);
private sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath);
private sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath, string ParentFileId);
private sealed record RelocateEmbeddingFileRequest(string StoreName, string ParentFileId, string AbsolutePath, string FileName, string RelativePath, string Fingerprint);
private sealed record OptimizeVectorStoreRequest(string StoreName);
@@ -27,7 +27,12 @@ public abstract class VectorStoreClient(string name, string path): DatabaseClien
/// <returns>The matches, best first, all of them inside the filter.</returns>
public abstract Task<IReadOnlyList<VectorSearchResult>> SearchEmbeddingAsync(string storeName, IReadOnlyList<float> vector, int maxMatches, VectorSearchFilter filter, CancellationToken token);
public abstract Task DeleteEmbeddingByFile(string storeName, string filePath, CancellationToken token);
public abstract Task DeleteEmbeddingByFile(string storeName, string filePath, string parentFileId, CancellationToken token);
/// <summary>
/// Updates the location of a document's points without changing their ids or vectors.
/// </summary>
public abstract Task RelocateEmbeddingFileAsync(string storeName, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token);
public abstract Task OptimizeVectorStore(string storeName, CancellationToken token);