From c457fe5b5142b1a2b7e7200afcb06bf5ee94cade Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 2 Nov 2025 10:12:37 +0100 Subject: [PATCH] [MEVD] Conformance tests for multi-vector records Closes #11709 --- .../AzureAISearchMultiVectorModelTests.cs | 19 ++ .../Support/AzureAISearchTestStore.cs | 3 +- .../CosmosMongoMultiVectorModelTests.cs | 17 ++ .../CosmosNoSqlMultiVectorModelTests.cs | 17 ++ .../InMemoryMultiVectorModelTests.cs | 17 ++ .../InMemoryDynamicDataModelFixture.cs | 10 -- .../Support/InMemorySimpleModelFixture.cs | 10 -- .../ModelTests/MongoMultiVectorModelTests.cs | 17 ++ .../Support/MongoSimpleModelFixture.cs | 11 -- .../PostgresMultiVectorModelTests.cs | 17 ++ .../PineconeBasicModelTests.cs | 0 .../PineconeDynamicModelTests.cs | 0 .../PineconeNoDataModelTests.cs | 0 .../PineconeTestSuiteImplementationTests.cs | 4 + .../ModelTests/QdrantMultiVectorModelTests.cs | 17 ++ .../RedisHashSetMultiVectorModelTests.cs | 17 ++ .../RedisJsonMultiVectorModelTests.cs | 17 ++ .../SqlServerMultiVectorModelTests.cs | 17 ++ .../Support/SqlServerSimpleModelFixture.cs | 10 -- .../ModelTests/SqliteMultiVectorModelTests.cs | 17 ++ .../ModelTests/MultiVectorConformanceTests.cs | 162 ++++++++++++++++++ .../Support/TestStore.cs | 7 +- .../VectorStoreCollectionFixtureBase.cs | 2 +- .../WeaviateMultiVectorModelTests.cs | 17 ++ 24 files changed, 381 insertions(+), 44 deletions(-) create mode 100644 dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/CosmosNoSql.ConformanceTests/ModelTests/CosmosNoSqlMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/InMemory.ConformanceTests/ModelTests/InMemoryMultiVectorModelTests.cs delete mode 100644 dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemoryDynamicDataModelFixture.cs delete mode 100644 dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemorySimpleModelFixture.cs create mode 100644 dotnet/test/VectorData/MongoDB.ConformanceTests/ModelTests/MongoMultiVectorModelTests.cs delete mode 100644 dotnet/test/VectorData/MongoDB.ConformanceTests/Support/MongoSimpleModelFixture.cs create mode 100644 dotnet/test/VectorData/PgVector.ConformanceTests/ModelTests/PostgresMultiVectorModelTests.cs rename dotnet/test/VectorData/Pinecone.ConformanceTests/{TestModels => ModelTests}/PineconeBasicModelTests.cs (100%) rename dotnet/test/VectorData/Pinecone.ConformanceTests/{TestModels => ModelTests}/PineconeDynamicModelTests.cs (100%) rename dotnet/test/VectorData/Pinecone.ConformanceTests/{TestModels => ModelTests}/PineconeNoDataModelTests.cs (100%) create mode 100644 dotnet/test/VectorData/Qdrant.ConformanceTests/ModelTests/QdrantMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisHashSetMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisJsonMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/SqlServer.ConformanceTests/ModelTests/SqlServerMultiVectorModelTests.cs delete mode 100644 dotnet/test/VectorData/SqlServer.ConformanceTests/Support/SqlServerSimpleModelFixture.cs create mode 100644 dotnet/test/VectorData/SqliteVec.ConformanceTests/ModelTests/SqliteMultiVectorModelTests.cs create mode 100644 dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/MultiVectorConformanceTests.cs create mode 100644 dotnet/test/VectorData/Weaviate.ConformanceTests/ModelTests/WeaviateMultiVectorModelTests.cs diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs new file mode 100644 index 000000000000..a7ef05da6134 --- /dev/null +++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +using AzureAISearch.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace AzureAISearch.ConformanceTests.ModelTests; + +public class AzureAISearchMultiVectorModelTests(AzureAISearchMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override string CollectionName => "multi-vector-" + AzureAISearchTestEnvironment.TestIndexPostfix; + + public override TestStore TestStore => AzureAISearchTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs index 9664377ac897..c06a19b5de59 100644 --- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs +++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs @@ -48,10 +48,11 @@ public override async Task WaitForDataAsync( VectorStoreCollection collection, int recordCount, Expression>? filter = null, + Expression>? vectorProperty = null, int? vectorSize = null, object? dummyVector = null) { - await base.WaitForDataAsync(collection, recordCount, filter, vectorSize, dummyVector); + await base.WaitForDataAsync(collection, recordCount, filter, vectorProperty, vectorSize, dummyVector); // There seems to be some asynchronicity/race condition specific to Azure AI Search which isn't taken care // of by the generic retry loop in the base implementation. diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs new file mode 100644 index 000000000000..fccab93351aa --- /dev/null +++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using CosmosMongoDB.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace CosmosMongoDB.ConformanceTests.ModelTests; + +public class CosmosMongoMultiVectorModelTests(CosmosMongoMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => CosmosMongoTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/CosmosNoSql.ConformanceTests/ModelTests/CosmosNoSqlMultiVectorModelTests.cs b/dotnet/test/VectorData/CosmosNoSql.ConformanceTests/ModelTests/CosmosNoSqlMultiVectorModelTests.cs new file mode 100644 index 000000000000..a544d43829a6 --- /dev/null +++ b/dotnet/test/VectorData/CosmosNoSql.ConformanceTests/ModelTests/CosmosNoSqlMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using CosmosNoSql.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace CosmosNoSql.ConformanceTests.ModelTests; + +public class CosmosNoSqlMultiVectorModelTests(CosmosNoSqlMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => CosmosNoSqlTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/InMemory.ConformanceTests/ModelTests/InMemoryMultiVectorModelTests.cs b/dotnet/test/VectorData/InMemory.ConformanceTests/ModelTests/InMemoryMultiVectorModelTests.cs new file mode 100644 index 000000000000..d215b332a87c --- /dev/null +++ b/dotnet/test/VectorData/InMemory.ConformanceTests/ModelTests/InMemoryMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using InMemory.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace InMemory.ConformanceTests.ModelTests; + +public class InMemoryMultiVectorModelTests(InMemoryMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => InMemoryTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemoryDynamicDataModelFixture.cs b/dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemoryDynamicDataModelFixture.cs deleted file mode 100644 index e7482c000f95..000000000000 --- a/dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemoryDynamicDataModelFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using VectorData.ConformanceTests.Support; - -namespace InMemory.ConformanceTests.Support; - -public class InMemoryDynamicDataModelFixture : DynamicDataModelFixture -{ - public override TestStore TestStore => InMemoryTestStore.Instance; -} diff --git a/dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemorySimpleModelFixture.cs b/dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemorySimpleModelFixture.cs deleted file mode 100644 index c745c06c1ec4..000000000000 --- a/dotnet/test/VectorData/InMemory.ConformanceTests/Support/InMemorySimpleModelFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using VectorData.ConformanceTests.Support; - -namespace InMemory.ConformanceTests.Support; - -public class InMemorySimpleModelFixture : SimpleModelFixture -{ - public override TestStore TestStore => InMemoryTestStore.Instance; -} diff --git a/dotnet/test/VectorData/MongoDB.ConformanceTests/ModelTests/MongoMultiVectorModelTests.cs b/dotnet/test/VectorData/MongoDB.ConformanceTests/ModelTests/MongoMultiVectorModelTests.cs new file mode 100644 index 000000000000..9c04f2484434 --- /dev/null +++ b/dotnet/test/VectorData/MongoDB.ConformanceTests/ModelTests/MongoMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using MongoDB.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace MongoDB.ConformanceTests.ModelTests; + +public class MongoMultiVectorModelTests(MongoMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => MongoTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/MongoDB.ConformanceTests/Support/MongoSimpleModelFixture.cs b/dotnet/test/VectorData/MongoDB.ConformanceTests/Support/MongoSimpleModelFixture.cs deleted file mode 100644 index ec264726f40c..000000000000 --- a/dotnet/test/VectorData/MongoDB.ConformanceTests/Support/MongoSimpleModelFixture.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using VectorData.ConformanceTests.Support; - -namespace MongoDB.ConformanceTests.Support; - -public class MongoSimpleModelFixture : SimpleModelFixture - where TKey : notnull -{ - public override TestStore TestStore => MongoTestStore.Instance; -} diff --git a/dotnet/test/VectorData/PgVector.ConformanceTests/ModelTests/PostgresMultiVectorModelTests.cs b/dotnet/test/VectorData/PgVector.ConformanceTests/ModelTests/PostgresMultiVectorModelTests.cs new file mode 100644 index 000000000000..e44e7b05fff1 --- /dev/null +++ b/dotnet/test/VectorData/PgVector.ConformanceTests/ModelTests/PostgresMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using PgVector.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace PgVector.ConformanceTests.ModelTests; + +public class PostgresMultiVectorModelTests(PostgresMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => PostgresTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/Pinecone.ConformanceTests/TestModels/PineconeBasicModelTests.cs b/dotnet/test/VectorData/Pinecone.ConformanceTests/ModelTests/PineconeBasicModelTests.cs similarity index 100% rename from dotnet/test/VectorData/Pinecone.ConformanceTests/TestModels/PineconeBasicModelTests.cs rename to dotnet/test/VectorData/Pinecone.ConformanceTests/ModelTests/PineconeBasicModelTests.cs diff --git a/dotnet/test/VectorData/Pinecone.ConformanceTests/TestModels/PineconeDynamicModelTests.cs b/dotnet/test/VectorData/Pinecone.ConformanceTests/ModelTests/PineconeDynamicModelTests.cs similarity index 100% rename from dotnet/test/VectorData/Pinecone.ConformanceTests/TestModels/PineconeDynamicModelTests.cs rename to dotnet/test/VectorData/Pinecone.ConformanceTests/ModelTests/PineconeDynamicModelTests.cs diff --git a/dotnet/test/VectorData/Pinecone.ConformanceTests/TestModels/PineconeNoDataModelTests.cs b/dotnet/test/VectorData/Pinecone.ConformanceTests/ModelTests/PineconeNoDataModelTests.cs similarity index 100% rename from dotnet/test/VectorData/Pinecone.ConformanceTests/TestModels/PineconeNoDataModelTests.cs rename to dotnet/test/VectorData/Pinecone.ConformanceTests/ModelTests/PineconeNoDataModelTests.cs diff --git a/dotnet/test/VectorData/Pinecone.ConformanceTests/PineconeTestSuiteImplementationTests.cs b/dotnet/test/VectorData/Pinecone.ConformanceTests/PineconeTestSuiteImplementationTests.cs index d5548b043106..c6cc328420e6 100644 --- a/dotnet/test/VectorData/Pinecone.ConformanceTests/PineconeTestSuiteImplementationTests.cs +++ b/dotnet/test/VectorData/Pinecone.ConformanceTests/PineconeTestSuiteImplementationTests.cs @@ -2,6 +2,7 @@ using VectorData.ConformanceTests; using VectorData.ConformanceTests.HybridSearch; +using VectorData.ConformanceTests.ModelTests; using VectorData.ConformanceTests.VectorSearch; namespace Pinecone.ConformanceTests; @@ -12,6 +13,9 @@ public class PineconeTestSuiteImplementationTests : TestSuiteImplementationTests [ typeof(VectorSearchWithFilterConformanceTests<>), + // Pinecone does not support multiple vectors + typeof(MultiVectorModelTests<>), + // Hybrid search not supported typeof(KeywordVectorizedHybridSearchComplianceTests<>) ]; diff --git a/dotnet/test/VectorData/Qdrant.ConformanceTests/ModelTests/QdrantMultiVectorModelTests.cs b/dotnet/test/VectorData/Qdrant.ConformanceTests/ModelTests/QdrantMultiVectorModelTests.cs new file mode 100644 index 000000000000..aa753bf5a9d4 --- /dev/null +++ b/dotnet/test/VectorData/Qdrant.ConformanceTests/ModelTests/QdrantMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Qdrant.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace Qdrant.ConformanceTests.ModelTests; + +public class QdrantMultiVectorModelTests(QdrantMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => QdrantTestStore.NamedVectorsInstance; + } +} diff --git a/dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisHashSetMultiVectorModelTests.cs b/dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisHashSetMultiVectorModelTests.cs new file mode 100644 index 000000000000..14ebf5a78d7f --- /dev/null +++ b/dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisHashSetMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Redis.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace Redis.ConformanceTests.ModelTests; + +public class RedisHashSetMultiVectorModelTests(RedisHashSetMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => RedisTestStore.HashSetInstance; + } +} diff --git a/dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisJsonMultiVectorModelTests.cs b/dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisJsonMultiVectorModelTests.cs new file mode 100644 index 000000000000..9e9cf147f9e3 --- /dev/null +++ b/dotnet/test/VectorData/Redis.ConformanceTests/ModelTests/RedisJsonMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Redis.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace Redis.ConformanceTests.ModelTests; + +public class RedisJsonMultiVectorModelTests(RedisJsonMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => RedisTestStore.JsonInstance; + } +} diff --git a/dotnet/test/VectorData/SqlServer.ConformanceTests/ModelTests/SqlServerMultiVectorModelTests.cs b/dotnet/test/VectorData/SqlServer.ConformanceTests/ModelTests/SqlServerMultiVectorModelTests.cs new file mode 100644 index 000000000000..10b5ad8b4737 --- /dev/null +++ b/dotnet/test/VectorData/SqlServer.ConformanceTests/ModelTests/SqlServerMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using SqlServer.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace SqlServer.ConformanceTests.ModelTests; + +public class SqlServerMultiVectorModelTests(SqlServerMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => SqlServerTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/SqlServer.ConformanceTests/Support/SqlServerSimpleModelFixture.cs b/dotnet/test/VectorData/SqlServer.ConformanceTests/Support/SqlServerSimpleModelFixture.cs deleted file mode 100644 index 0a40af934f1b..000000000000 --- a/dotnet/test/VectorData/SqlServer.ConformanceTests/Support/SqlServerSimpleModelFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using VectorData.ConformanceTests.Support; - -namespace SqlServer.ConformanceTests.Support; - -public class SqlServerSimpleModelFixture : SimpleModelFixture -{ - public override TestStore TestStore => SqlServerTestStore.Instance; -} diff --git a/dotnet/test/VectorData/SqliteVec.ConformanceTests/ModelTests/SqliteMultiVectorModelTests.cs b/dotnet/test/VectorData/SqliteVec.ConformanceTests/ModelTests/SqliteMultiVectorModelTests.cs new file mode 100644 index 000000000000..fbdeb8753b2b --- /dev/null +++ b/dotnet/test/VectorData/SqliteVec.ConformanceTests/ModelTests/SqliteMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using SqliteVec.ConformanceTests.Support; +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Xunit; + +namespace SqliteVec.ConformanceTests.ModelTests; + +public class SqliteMultiVectorModelTests(SqliteMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => SqliteTestStore.Instance; + } +} diff --git a/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/MultiVectorConformanceTests.cs b/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/MultiVectorConformanceTests.cs new file mode 100644 index 000000000000..a47b3cdbc0cb --- /dev/null +++ b/dotnet/test/VectorData/VectorData.ConformanceTests/ModelTests/MultiVectorConformanceTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.VectorData; +using VectorData.ConformanceTests.Support; +using VectorData.ConformanceTests.Xunit; +using Xunit; + +namespace VectorData.ConformanceTests.ModelTests; + +/// +/// Tests using a model with multiple vectors. +/// +public class MultiVectorModelTests(MultiVectorModelTests.Fixture fixture) : IAsyncLifetime + where TKey : notnull +{ + [ConditionalTheory, MemberData(nameof(IncludeVectorsData))] + public virtual async Task GetAsync_single_record(bool includeVectors) + { + var expectedRecord = fixture.TestData[0]; + + var received = await this.Collection.GetAsync(expectedRecord.Key, new() { IncludeVectors = includeVectors }); + + expectedRecord.AssertEqual(received, includeVectors, fixture.TestStore.VectorsComparable); + } + + [ConditionalFact] + public virtual async Task Insert_single_record() + { + TKey expectedKey = fixture.GenerateNextKey(); + MultiVectorRecord inserted = new() + { + Key = expectedKey, + Number = 10, + Vector1 = new([10, 0, 0]), + Vector2 = new([10, 0, 0]), + }; + + Assert.Null(await this.Collection.GetAsync(expectedKey)); + await this.Collection.UpsertAsync(inserted); + + var received = await this.Collection.GetAsync(expectedKey, new() { IncludeVectors = true }); + inserted.AssertEqual(received, includeVectors: true, fixture.TestStore.VectorsComparable); + } + + [ConditionalFact] + public virtual async Task Delete_single_record() + { + var keyToRemove = fixture.TestData[0].Key; + + await this.Collection.DeleteAsync(keyToRemove); + Assert.Null(await this.Collection.GetAsync(keyToRemove)); + } + + [ConditionalFact] + public virtual async Task SearchAsync_with_multiple_vector_properties() + { + var result = await this.Collection + .SearchAsync(new ReadOnlyMemory([1, 2, 3]), top: 1, new() { VectorProperty = r => r.Vector1, IncludeVectors = true }) + .SingleAsync(); + fixture.TestData[0].AssertEqual(result.Record, includeVectors: true, fixture.TestStore.VectorsComparable); + + result = await this.Collection + .SearchAsync(new ReadOnlyMemory([10, 2, 6]), top: 1, new() { VectorProperty = r => r.Vector2, IncludeVectors = true }) + .SingleAsync(); + fixture.TestData[1].AssertEqual(result.Record, includeVectors: true, fixture.TestStore.VectorsComparable); + } + + [ConditionalFact] + public virtual async Task Search_without_explicitly_specified_vector_property_fails() + { + var exception = await Assert.ThrowsAsync(async () => + await this.Collection.SearchAsync(new ReadOnlyMemory([1, 2, 3]), top: 1).ToListAsync()); + + Assert.Equal($"The '{nameof(MultiVectorRecord)}' type has multiple vector properties, please specify your chosen property via options.", exception.Message); + } + + protected VectorStoreCollection Collection => fixture.Collection; + + public abstract class Fixture : VectorStoreCollectionFixture + { + public override string CollectionName => "MultiVectorModelTests"; + + protected override List BuildTestData() => + [ + new() + { + Key = this.GenerateNextKey(), + Number = 1, + Vector1 = new([1, 2, 3]), + Vector2 = new([10, 2, 4]) + }, + new() + { + Key = this.GenerateNextKey(), + Number = 2, + Vector1 = new([1, 2, 5]), + Vector2 = new([10, 2, 6]) + } + ]; + + public override VectorStoreCollectionDefinition CreateRecordDefinition() + => new() + { + Properties = + [ + new VectorStoreKeyProperty(nameof(MultiVectorRecord.Key), typeof(TKey)), + new VectorStoreDataProperty(nameof(MultiVectorRecord.Number), typeof(int)), + + new VectorStoreVectorProperty(nameof(MultiVectorRecord.Vector1), typeof(ReadOnlyMemory), 3) + { + DistanceFunction = this.DistanceFunction, + IndexKind = this.IndexKind + }, + + new VectorStoreVectorProperty(nameof(MultiVectorRecord.Vector2), typeof(ReadOnlyMemory), 3) + { + DistanceFunction = this.DistanceFunction, + IndexKind = this.IndexKind + } + ] + }; + + protected override Task WaitForDataAsync() + => this.TestStore.WaitForDataAsync(this.Collection, recordCount: this.TestData.Count, vectorProperty: r => r.Vector1); + } + + public sealed class MultiVectorRecord : TestRecord + { + public int Number { get; set; } + + public ReadOnlyMemory Vector1 { get; set; } + public ReadOnlyMemory Vector2 { get; set; } + + public void AssertEqual(MultiVectorRecord? other, bool includeVectors, bool compareVectors) + { + Assert.NotNull(other); + + Assert.Equal(this.Key, other.Key); + Assert.Equal(this.Number, other.Number); + + if (includeVectors) + { + Assert.Equal(this.Vector1.Span.Length, other.Vector1.Span.Length); + Assert.Equal(this.Vector2.Span.Length, other.Vector2.Span.Length); + + if (compareVectors) + { + Assert.True(this.Vector1.Span.SequenceEqual(other.Vector1.Span)); + Assert.True(this.Vector2.Span.SequenceEqual(other.Vector2.Span)); + } + } + } + } + + public Task InitializeAsync() + => fixture.ReseedAsync(); + + public Task DisposeAsync() + => Task.CompletedTask; + + public static readonly TheoryData IncludeVectorsData = [false, true]; +} diff --git a/dotnet/test/VectorData/VectorData.ConformanceTests/Support/TestStore.cs b/dotnet/test/VectorData/VectorData.ConformanceTests/Support/TestStore.cs index da7c6205a1a1..8143c787fe85 100644 --- a/dotnet/test/VectorData/VectorData.ConformanceTests/Support/TestStore.cs +++ b/dotnet/test/VectorData/VectorData.ConformanceTests/Support/TestStore.cs @@ -84,6 +84,7 @@ public virtual async Task WaitForDataAsync( VectorStoreCollection collection, int recordCount, Expression>? filter = null, + Expression>? vectorProperty = null, int? vectorSize = null, object? dummyVector = null) where TKey : notnull @@ -103,7 +104,11 @@ public virtual async Task WaitForDataAsync( var results = collection.SearchAsync( vector, top: recordCount is 0 ? 1 : recordCount, - new() { Filter = filter }); + new() + { + Filter = filter, + VectorProperty = vectorProperty + }); var count = await results.CountAsync(); if (count == recordCount) { diff --git a/dotnet/test/VectorData/VectorData.ConformanceTests/Support/VectorStoreCollectionFixtureBase.cs b/dotnet/test/VectorData/VectorData.ConformanceTests/Support/VectorStoreCollectionFixtureBase.cs index 38191885362b..b296de06cfcc 100644 --- a/dotnet/test/VectorData/VectorData.ConformanceTests/Support/VectorStoreCollectionFixtureBase.cs +++ b/dotnet/test/VectorData/VectorData.ConformanceTests/Support/VectorStoreCollectionFixtureBase.cs @@ -19,7 +19,7 @@ public abstract class VectorStoreCollectionFixtureBase : VectorSt public abstract VectorStoreCollectionDefinition CreateRecordDefinition(); protected virtual List BuildTestData() => []; - public virtual string CollectionName => Guid.NewGuid().ToString(); + public abstract string CollectionName { get; } protected virtual string DistanceFunction => this.TestStore.DefaultDistanceFunction; protected virtual string IndexKind => this.TestStore.DefaultIndexKind; diff --git a/dotnet/test/VectorData/Weaviate.ConformanceTests/ModelTests/WeaviateMultiVectorModelTests.cs b/dotnet/test/VectorData/Weaviate.ConformanceTests/ModelTests/WeaviateMultiVectorModelTests.cs new file mode 100644 index 000000000000..d4136058005f --- /dev/null +++ b/dotnet/test/VectorData/Weaviate.ConformanceTests/ModelTests/WeaviateMultiVectorModelTests.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorData.ConformanceTests.ModelTests; +using VectorData.ConformanceTests.Support; +using Weaviate.ConformanceTests.Support; +using Xunit; + +namespace Weaviate.ConformanceTests.ModelTests; + +public class WeaviateMultiVectorModelTests(WeaviateMultiVectorModelTests.Fixture fixture) + : MultiVectorModelTests(fixture), IClassFixture +{ + public new class Fixture : MultiVectorModelTests.Fixture + { + public override TestStore TestStore => WeaviateTestStore.NamedVectorsInstance; + } +}