diff --git a/dotnet/src/Connectors/Connectors.AzureCosmosDBMongoDB.UnitTests/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs b/dotnet/src/Connectors/Connectors.AzureCosmosDBMongoDB.UnitTests/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs index 6ef6337e3426..60e2584bf754 100644 --- a/dotnet/src/Connectors/Connectors.AzureCosmosDBMongoDB.UnitTests/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/Connectors/Connectors.AzureCosmosDBMongoDB.UnitTests/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs @@ -121,6 +121,15 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int .Setup(l => l.Current) .Returns(indexes); + var mockCursor = new Mock>(); + mockCursor + .Setup(l => l.MoveNextAsync(It.IsAny())) + .ReturnsAsync(true); + + mockCursor + .Setup(l => l.Current) + .Returns([]); + var mockMongoIndexManager = new Mock>(); mockMongoIndexManager @@ -131,6 +140,10 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int .Setup(l => l.Indexes) .Returns(mockMongoIndexManager.Object); + this._mockMongoDatabase + .Setup(l => l.ListCollectionNamesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(mockCursor.Object); + var sut = new AzureCosmosDBMongoDBVectorStoreRecordCollection(this._mockMongoDatabase.Object, CollectionName); // Act @@ -140,7 +153,11 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int this._mockMongoDatabase.Verify(l => l.CreateCollectionAsync( CollectionName, It.IsAny(), - It.IsAny()), Times.Once()); + It.IsAny()), Times.Once); + + this._mockMongoDatabase.Verify(l => l.ListCollectionNamesAsync( + It.IsAny(), + It.IsAny()), Times.Once); this._mockMongoDatabase.Verify(l => l.RunCommandAsync( It.Is>(command => @@ -151,9 +168,8 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int It.IsAny()), Times.Exactly(actualIndexCreations)); } - [Theory] - [MemberData(nameof(CreateCollectionIfNotExistsData))] - public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync(List collections, int actualCollectionCreations) + [Fact] + public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync() { // Arrange const string CollectionName = "collection"; @@ -165,7 +181,7 @@ public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync(List l.Current) - .Returns(collections); + .Returns([]); this._mockMongoDatabase .Setup(l => l.ListCollectionNamesAsync(It.IsAny(), It.IsAny())) @@ -202,7 +218,11 @@ public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync(List l.CreateCollectionAsync( CollectionName, It.IsAny(), - It.IsAny()), Times.Exactly(actualCollectionCreations)); + It.IsAny()), Times.Exactly(1)); + + this._mockMongoDatabase.Verify(l => l.ListCollectionNamesAsync( + It.IsAny(), + It.IsAny()), Times.Never); } [Fact] diff --git a/dotnet/src/Connectors/Connectors.Memory.AzureAISearch/AzureAISearchVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.AzureAISearch/AzureAISearchVectorStoreRecordCollection.cs index 5f99cbb16a67..eda71258ef24 100644 --- a/dotnet/src/Connectors/Connectors.Memory.AzureAISearch/AzureAISearchVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.AzureAISearch/AzureAISearchVectorStoreRecordCollection.cs @@ -229,9 +229,19 @@ public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken can /// public virtual Task DeleteCollectionAsync(CancellationToken cancellationToken = default) { - return this.RunOperationAsync( + return this.RunOperationAsync( "DeleteIndex", - () => this._searchIndexClient.DeleteIndexAsync(this._collectionName, cancellationToken)); + async () => + { + try + { + return await this._searchIndexClient.DeleteIndexAsync(this._collectionName, cancellationToken).ConfigureAwait(false); + } + catch (RequestFailedException ex) when (ex.Status == 404) + { + return null!; + } + }); } /// diff --git a/dotnet/src/Connectors/Connectors.Memory.AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollection.cs index 67bf3197395d..13e31475447d 100644 --- a/dotnet/src/Connectors/Connectors.Memory.AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollection.cs @@ -102,20 +102,29 @@ public virtual Task CollectionExistsAsync(CancellationToken cancellationTo /// public virtual async Task CreateCollectionAsync(CancellationToken cancellationToken = default) { - await this.RunOperationAsync("CreateCollection", - () => this._mongoDatabase.CreateCollectionAsync(this.CollectionName, cancellationToken: cancellationToken)).ConfigureAwait(false); + // The IMongoDatabase.CreateCollectionAsync "Creates a new collection if not already available". + // To make sure that all the connectors are consistent, we throw when the collection exists. + if (await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) + { + throw new VectorStoreOperationException("Collection already exists.") + { + VectorStoreType = DatabaseName, + CollectionName = this.CollectionName, + OperationName = "CreateCollection" + }; + } - await this.RunOperationAsync("CreateIndexes", - () => this.CreateIndexesAsync(this.CollectionName, cancellationToken: cancellationToken)).ConfigureAwait(false); + await this.CreateCollectionIfNotExistsAsync(cancellationToken).ConfigureAwait(false); } /// public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken cancellationToken = default) { - if (!await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) - { - await this.CreateCollectionAsync(cancellationToken).ConfigureAwait(false); - } + await this.RunOperationAsync("CreateCollection", + () => this._mongoDatabase.CreateCollectionAsync(this.CollectionName, cancellationToken: cancellationToken)).ConfigureAwait(false); + + await this.RunOperationAsync("CreateIndexes", + () => this.CreateIndexesAsync(this.CollectionName, cancellationToken: cancellationToken)).ConfigureAwait(false); } /// diff --git a/dotnet/src/Connectors/Connectors.Memory.InMemory/InMemoryVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.InMemory/InMemoryVectorStoreRecordCollection.cs index 449a50796a13..6fbcdf2633bf 100644 --- a/dotnet/src/Connectors/Connectors.Memory.InMemory/InMemoryVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.InMemory/InMemoryVectorStoreRecordCollection.cs @@ -113,13 +113,19 @@ public Task CollectionExistsAsync(CancellationToken cancellationToken = de /// public Task CreateCollectionAsync(CancellationToken cancellationToken = default) { - if (!this._internalCollections.ContainsKey(this._collectionName)) + if (!this._internalCollections.ContainsKey(this._collectionName) + && this._internalCollections.TryAdd(this._collectionName, new ConcurrentDictionary()) + && this._internalCollectionTypes.TryAdd(this._collectionName, typeof(TRecord))) { - this._internalCollections.TryAdd(this._collectionName, new ConcurrentDictionary()); - this._internalCollectionTypes.TryAdd(this._collectionName, typeof(TRecord)); + return Task.CompletedTask; } - return Task.CompletedTask; + return Task.FromException(new VectorStoreOperationException("Collection already exists.") + { + VectorStoreType = "InMemory", + CollectionName = this.CollectionName, + OperationName = "CreateCollection" + }); } /// diff --git a/dotnet/src/Connectors/Connectors.Memory.MongoDB/MongoDBVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.MongoDB/MongoDBVectorStoreRecordCollection.cs index 7b749c04dc16..dc2aa163a803 100644 --- a/dotnet/src/Connectors/Connectors.Memory.MongoDB/MongoDBVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.MongoDB/MongoDBVectorStoreRecordCollection.cs @@ -104,6 +104,26 @@ public virtual Task CollectionExistsAsync(CancellationToken cancellationTo /// public virtual async Task CreateCollectionAsync(CancellationToken cancellationToken = default) { + // The IMongoDatabase.CreateCollectionAsync "Creates a new collection if not already available". + // To make sure that all the connectors are consistent, we throw when the collection exists. + if (await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) + { + throw new VectorStoreOperationException("Collection already exists.") + { + VectorStoreType = DatabaseName, + CollectionName = this.CollectionName, + OperationName = "CreateCollection" + }; + } + + await this.CreateCollectionIfNotExistsAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken cancellationToken = default) + { + // The IMongoDatabase.CreateCollectionAsync "Creates a new collection if not already available". + // So for CreateCollectionIfNotExistsAsync, we don't perform an additional check. await this.RunOperationAsync("CreateCollection", () => this._mongoDatabase.CreateCollectionAsync(this.CollectionName, cancellationToken: cancellationToken)).ConfigureAwait(false); @@ -115,15 +135,6 @@ await this.RunOperationWithRetryAsync( cancellationToken).ConfigureAwait(false); } - /// - public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken cancellationToken = default) - { - if (!await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) - { - await this.CreateCollectionAsync(cancellationToken).ConfigureAwait(false); - } - } - /// public virtual async Task DeleteAsync(string key, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorStoreRecordCollection.cs index 4d8db3e63e81..760aeaae24f4 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorStoreRecordCollection.cs @@ -238,11 +238,26 @@ public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken can /// public virtual Task DeleteCollectionAsync(CancellationToken cancellationToken = default) - { - return this.RunOperationAsync( - "DeleteCollection", - () => this._qdrantClient.DeleteCollectionAsync(this._collectionName, null, cancellationToken)); - } + => this.RunOperationAsync("DeleteCollection", + async () => + { + try + { + await this._qdrantClient.DeleteCollectionAsync(this._collectionName, null, cancellationToken).ConfigureAwait(false); + } + catch (QdrantException) + { + // There is no reliable way to check if the operation failed because the + // collection does not exist based on the exception itself. + // So we just check here if it exists, and if not, ignore the exception. + if (!await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + + throw; + } + }); /// public virtual async Task GetAsync(ulong key, GetRecordOptions? options = null, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Connectors/Connectors.Memory.Redis/RedisHashSetVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.Redis/RedisHashSetVectorStoreRecordCollection.cs index 6c99d9b03d8e..a08fe1e86628 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Redis/RedisHashSetVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Redis/RedisHashSetVectorStoreRecordCollection.cs @@ -195,9 +195,25 @@ public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken can } /// - public virtual Task DeleteCollectionAsync(CancellationToken cancellationToken = default) + public virtual async Task DeleteCollectionAsync(CancellationToken cancellationToken = default) { - return this.RunOperationAsync("FT.DROPINDEX", () => this._database.FT().DropIndexAsync(this._collectionName)); + try + { + await this.RunOperationAsync("FT.DROPINDEX", + () => this._database.FT().DropIndexAsync(this._collectionName)).ConfigureAwait(false); + } + catch (VectorStoreOperationException ex) when (ex.InnerException is RedisServerException) + { + // The RedisServerException does not expose any reliable way of checking if the index does not exist. + // It just sets the message to "Unknown index name". + // We catch the exception and ignore it, but only after checking that the index does not exist. + if (!await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + + throw; + } } /// @@ -425,7 +441,7 @@ private async Task RunOperationAsync(string operationName, Func> o { return await operation.Invoke().ConfigureAwait(false); } - catch (RedisConnectionException ex) + catch (RedisException ex) { throw new VectorStoreOperationException("Call to vector store failed.", ex) { diff --git a/dotnet/src/Connectors/Connectors.Memory.Redis/RedisJsonVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.Redis/RedisJsonVectorStoreRecordCollection.cs index 14a8e56222d9..af6a0a7d220f 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Redis/RedisJsonVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Redis/RedisJsonVectorStoreRecordCollection.cs @@ -177,9 +177,25 @@ public virtual async Task CreateCollectionIfNotExistsAsync(CancellationToken can } /// - public virtual Task DeleteCollectionAsync(CancellationToken cancellationToken = default) + public virtual async Task DeleteCollectionAsync(CancellationToken cancellationToken = default) { - return this.RunOperationAsync("FT.DROPINDEX", () => this._database.FT().DropIndexAsync(this._collectionName)); + try + { + await this.RunOperationAsync("FT.DROPINDEX", + () => this._database.FT().DropIndexAsync(this._collectionName)).ConfigureAwait(false); + } + catch (VectorStoreOperationException ex) when (ex.InnerException is RedisServerException) + { + // The RedisServerException does not expose any reliable way of checking if the index does not exist. + // It just sets the message to "Unknown index name". + // We catch the exception and ignore it, but only after checking that the index does not exist. + if (!await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + + throw; + } } /// diff --git a/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteVectorStoreCollectionCommandBuilder.cs b/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteVectorStoreCollectionCommandBuilder.cs index 837e3044ddc7..701c61bb3236 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteVectorStoreCollectionCommandBuilder.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteVectorStoreCollectionCommandBuilder.cs @@ -87,7 +87,7 @@ public DbCommand BuildCreateVirtualTableCommand( public DbCommand BuildDropTableCommand(string tableName) { - string query = $"DROP TABLE [{tableName}];"; + string query = $"DROP TABLE IF EXISTS [{tableName}];"; var command = this._connection.CreateCommand(); diff --git a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStore.cs b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStore.cs index dbf7f46b5f59..1c45d1e3ac65 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStore.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStore.cs @@ -43,6 +43,7 @@ public WeaviateVectorStore(HttpClient httpClient, WeaviateVectorStoreOptions? op } /// + /// The collection name must start with a capital letter and contain only ASCII letters and digits. public virtual IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null) where TKey : notnull { diff --git a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStoreRecordCollection.cs b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStoreRecordCollection.cs index 98d38fc9a8c1..393b9a841cbb 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStoreRecordCollection.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateVectorStoreRecordCollection.cs @@ -118,6 +118,7 @@ public class WeaviateVectorStoreRecordCollection : IVectorStoreRecordCo /// /// The name of the collection that this will access. /// Optional configuration options for this class. + /// The collection name must start with a capital letter and contain only ASCII letters and digits. public WeaviateVectorStoreRecordCollection( HttpClient httpClient, string collectionName, @@ -125,7 +126,7 @@ public WeaviateVectorStoreRecordCollection( { // Verify. Verify.NotNull(httpClient); - Verify.NotNullOrWhiteSpace(collectionName); + VerifyCollectionName(collectionName); var endpoint = (options?.Endpoint ?? httpClient.BaseAddress) ?? throw new ArgumentException($"Weaviate endpoint should be provided via HttpClient.BaseAddress property or {nameof(WeaviateVectorStoreRecordCollectionOptions)} options parameter."); @@ -535,5 +536,24 @@ private static void VerifyVectorParam(TVector vector) } } + private static void VerifyCollectionName(string collectionName) + { + Verify.NotNullOrWhiteSpace(collectionName); + + // Based on https://weaviate.io/developers/weaviate/starter-guides/managing-collections#collection--property-names + char first = collectionName[0]; + if (!(first is >= 'A' and <= 'Z')) + { + throw new ArgumentException("Collection name must start with an uppercase ASCII letter.", nameof(collectionName)); + } + + foreach (char character in collectionName) + { + if (!((character is >= 'a' and <= 'z') || (character is >= 'A' and <= 'Z') || (character is >= '0' and <= '9'))) + { + throw new ArgumentException("Collection name must contain only ASCII letters and digits.", nameof(collectionName)); + } + } + } #endregion } diff --git a/dotnet/src/Connectors/Connectors.MongoDB.UnitTests/MongoDBVectorStoreRecordCollectionTests.cs b/dotnet/src/Connectors/Connectors.MongoDB.UnitTests/MongoDBVectorStoreRecordCollectionTests.cs index 6265c18bd6b9..ddf71955621e 100644 --- a/dotnet/src/Connectors/Connectors.MongoDB.UnitTests/MongoDBVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/Connectors/Connectors.MongoDB.UnitTests/MongoDBVectorStoreRecordCollectionTests.cs @@ -121,6 +121,15 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int .Setup(l => l.Current) .Returns(indexes); + var mockCursor = new Mock>(); + mockCursor + .Setup(l => l.MoveNextAsync(It.IsAny())) + .ReturnsAsync(true); + + mockCursor + .Setup(l => l.Current) + .Returns([]); + var mockMongoIndexManager = new Mock>(); mockMongoIndexManager @@ -131,6 +140,10 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int .Setup(l => l.Indexes) .Returns(mockMongoIndexManager.Object); + this._mockMongoDatabase + .Setup(l => l.ListCollectionNamesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(mockCursor.Object); + var sut = new MongoDBVectorStoreRecordCollection(this._mockMongoDatabase.Object, CollectionName); // Act @@ -140,7 +153,11 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int this._mockMongoDatabase.Verify(l => l.CreateCollectionAsync( CollectionName, It.IsAny(), - It.IsAny()), Times.Once()); + It.IsAny()), Times.Once); + + this._mockMongoDatabase.Verify(l => l.ListCollectionNamesAsync( + It.IsAny(), + It.IsAny()), Times.Once); this._mockMongoDatabase.Verify(l => l.RunCommandAsync( It.Is>(command => @@ -151,9 +168,8 @@ public async Task CreateCollectionInvokesValidMethodsAsync(bool indexExists, int It.IsAny()), Times.Exactly(actualIndexCreations)); } - [Theory] - [MemberData(nameof(CreateCollectionIfNotExistsData))] - public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync(List collections, int actualCollectionCreations) + [Fact] + public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync() { // Arrange const string CollectionName = "collection"; @@ -165,7 +181,7 @@ public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync(List l.Current) - .Returns(collections); + .Returns([]); this._mockMongoDatabase .Setup(l => l.ListCollectionNamesAsync(It.IsAny(), It.IsAny())) @@ -202,7 +218,11 @@ public async Task CreateCollectionIfNotExistsInvokesValidMethodsAsync(List l.CreateCollectionAsync( CollectionName, It.IsAny(), - It.IsAny()), Times.Exactly(actualCollectionCreations)); + It.IsAny()), Times.Exactly(1)); + + this._mockMongoDatabase.Verify(l => l.ListCollectionNamesAsync( + It.IsAny(), + It.IsAny()), Times.Never); } [Fact] diff --git a/dotnet/src/Connectors/Connectors.Sqlite.UnitTests/SqliteVectorStoreCollectionCommandBuilderTests.cs b/dotnet/src/Connectors/Connectors.Sqlite.UnitTests/SqliteVectorStoreCollectionCommandBuilderTests.cs index 370756cb4344..314e1e162420 100644 --- a/dotnet/src/Connectors/Connectors.Sqlite.UnitTests/SqliteVectorStoreCollectionCommandBuilderTests.cs +++ b/dotnet/src/Connectors/Connectors.Sqlite.UnitTests/SqliteVectorStoreCollectionCommandBuilderTests.cs @@ -104,7 +104,7 @@ public void ItBuildsDropTableCommand() var command = this._commandBuilder.BuildDropTableCommand(TableName); // Assert - Assert.Equal("DROP TABLE [TestTable];", command.CommandText); + Assert.Equal("DROP TABLE IF EXISTS [TestTable];", command.CommandText); } [Theory] diff --git a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateKernelBuilderExtensionsTests.cs b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateKernelBuilderExtensionsTests.cs index fb4c27773431..23b34cdbc2ba 100644 --- a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateKernelBuilderExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateKernelBuilderExtensionsTests.cs @@ -43,7 +43,7 @@ public void AddWeaviateVectorStoreRecordCollectionRegistersClass() this._kernelBuilder.Services.AddSingleton(httpClient); // Act - this._kernelBuilder.AddWeaviateVectorStoreRecordCollection("testcollection"); + this._kernelBuilder.AddWeaviateVectorStoreRecordCollection("Testcollection"); var kernel = this._kernelBuilder.Build(); diff --git a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateServiceCollectionExtensionsTests.cs b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateServiceCollectionExtensionsTests.cs index 07efc26dd22d..e33f735ebc4f 100644 --- a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateServiceCollectionExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateServiceCollectionExtensionsTests.cs @@ -43,7 +43,7 @@ public void AddVectorStoreRecordCollectionRegistersClass() this._serviceCollection.AddSingleton(httpClient); // Act - this._serviceCollection.AddWeaviateVectorStoreRecordCollection("testcollection"); + this._serviceCollection.AddWeaviateVectorStoreRecordCollection("Testcollection"); // Assert this.AssertVectorStoreRecordCollectionCreated(); diff --git a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreRecordCollectionTests.cs b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreRecordCollectionTests.cs index f31cd9929360..0b3e39cac291 100644 --- a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreRecordCollectionTests.cs @@ -32,7 +32,7 @@ public WeaviateVectorStoreRecordCollectionTests() public void ConstructorForModelWithoutKeyThrowsException() { // Act & Assert - var exception = Assert.Throws(() => new WeaviateVectorStoreRecordCollection(this._mockHttpClient, "collection")); + var exception = Assert.Throws(() => new WeaviateVectorStoreRecordCollection(this._mockHttpClient, "Collection")); Assert.Contains("No key property found", exception.Message); } @@ -43,7 +43,7 @@ public void ConstructorWithoutEndpointThrowsException() using var httpClient = new HttpClient(); // Act & Assert - var exception = Assert.Throws(() => new WeaviateVectorStoreRecordCollection(httpClient, "collection")); + var exception = Assert.Throws(() => new WeaviateVectorStoreRecordCollection(httpClient, "Collection")); Assert.Contains("Weaviate endpoint should be provided", exception.Message); } @@ -53,7 +53,7 @@ public void ConstructorWithDeclarativeModelInitializesCollection() // Act & Assert var collection = new WeaviateVectorStoreRecordCollection( this._mockHttpClient, - "collection"); + "Collection"); Assert.NotNull(collection); } @@ -70,7 +70,7 @@ public void ConstructorWithImperativeModelInitializesCollection() // Act var collection = new WeaviateVectorStoreRecordCollection( this._mockHttpClient, - "collection", + "Collection", new() { VectorStoreRecordDefinition = definition }); // Assert @@ -93,6 +93,30 @@ public async Task CollectionExistsReturnsValidResultAsync(HttpResponseMessage re Assert.Equal(expectedResult, actualResult); } + [Theory] + [InlineData("notStartingWithCapitalLetter")] + [InlineData("0startingWithDigit")] + [InlineData("contains spaces")] + [InlineData("contains-dashes")] + [InlineData("contains_underscores")] + [InlineData("contains$specialCharacters")] + [InlineData("contains!specialCharacters")] + [InlineData("contains@specialCharacters")] + [InlineData("contains#specialCharacters")] + [InlineData("contains%specialCharacters")] + [InlineData("contains^specialCharacters")] + [InlineData("contains&specialCharacters")] + [InlineData("contains*specialCharacters")] + [InlineData("contains(specialCharacters")] + [InlineData("contains)specialCharacters")] + [InlineData("containsNonAsciiĄ")] + [InlineData("containsNonAsciią")] + public void CollectionCtorRejectsInvalidNames(string collectionName) + { + ArgumentException argumentException = Assert.Throws(() => new WeaviateVectorStoreRecordCollection(this._mockHttpClient, collectionName)); + Assert.Equal("collectionName", argumentException.ParamName); + } + [Fact] public async Task CreateCollectionUsesValidCollectionSchemaAsync() { diff --git a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreTests.cs b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreTests.cs index e51af8124daf..5a99f4c1ee20 100644 --- a/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreTests.cs +++ b/dotnet/src/Connectors/Connectors.Weaviate.UnitTests/WeaviateVectorStoreTests.cs @@ -44,7 +44,7 @@ public void GetCollectionWithSupportedKeyReturnsCollection() var sut = new WeaviateVectorStore(this._mockHttpClient); // Act - var collection = sut.GetCollection("collection1"); + var collection = sut.GetCollection("Collection1"); // Assert Assert.NotNull(collection); diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs index 773315a285ee..3a9aff375be3 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs @@ -15,11 +15,13 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory; public abstract class BaseVectorStoreTests(IVectorStore vectorStore) where TKey : notnull { + protected virtual IEnumerable CollectionNames => ["listcollectionnames1", "listcollectionnames2", "listcollectionnames3"]; + [VectorStoreFact] public virtual async Task ItCanGetAListOfExistingCollectionNamesAsync() { // Arrange - var expectedCollectionNames = new List { "listcollectionnames1", "listcollectionnames2", "listcollectionnames3" }; + var expectedCollectionNames = this.CollectionNames; foreach (var collectionName in expectedCollectionNames) { diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs index ce278486e9bc..ed42a3322744 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using Microsoft.SemanticKernel.Connectors.Weaviate; using Xunit; @@ -9,4 +10,7 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; [Collection("WeaviateVectorStoreCollection")] public sealed class WeaviateVectorStoreTests(WeaviateVectorStoreFixture fixture) : BaseVectorStoreTests(new WeaviateVectorStore(fixture.HttpClient!)) -{ } +{ + // Weaviate requires each collection name to start with uppercase ASCII letter. + protected override IEnumerable CollectionNames => ["Listcollectionnames1", "Listcollectionnames2", "Listcollectionnames3"]; +} diff --git a/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/AzureAISearchIntegrationTests.csproj b/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/AzureAISearchIntegrationTests.csproj index 0fcc13f45809..688796758267 100644 --- a/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/AzureAISearchIntegrationTests.csproj +++ b/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/AzureAISearchIntegrationTests.csproj @@ -28,4 +28,13 @@ + + + Always + + + Always + + + diff --git a/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/Collections/AzureAISearchCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/Collections/AzureAISearchCollectionConformanceTests.cs new file mode 100644 index 000000000000..5ae3ad191f4b --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/Collections/AzureAISearchCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using AzureAISearchIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace AzureAISearchIntegrationTests.Collections; + +public class AzureAISearchCollectionConformanceTests(AzureAISearchFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/Support/AzureAISearchFixture.cs b/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/Support/AzureAISearchFixture.cs new file mode 100644 index 000000000000..b5a6cf4f5c49 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/AzureAISearchIntegrationTests/Support/AzureAISearchFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace AzureAISearchIntegrationTests.Support; + +public class AzureAISearchFixture : VectorStoreFixture +{ + public override TestStore TestStore => AzureAISearchTestStore.Instance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Collections/CosmosMongoDBCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Collections/CosmosMongoDBCollectionConformanceTests.cs new file mode 100644 index 000000000000..f8f03906747a --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Collections/CosmosMongoDBCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using CosmosMongoDBIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace CosmosMongoDBIntegrationTests.Collections; + +public class CosmosMongoDBCollectionConformanceTests(CosmosMongoDBFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/CosmosMongoDBIntegrationTests.csproj b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/CosmosMongoDBIntegrationTests.csproj index aaf0dcf8160b..59a720d7dddd 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/CosmosMongoDBIntegrationTests.csproj +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/CosmosMongoDBIntegrationTests.csproj @@ -6,7 +6,7 @@ enable true false - MongoDBIntegrationTests + CosmosMongoDBIntegrationTests @@ -26,4 +26,13 @@ + + + Always + + + Always + + + diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Filter/CosmosMongoBasicFilterTests.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Filter/CosmosMongoBasicFilterTests.cs index f7e52a5ec19a..50dfc677ad00 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Filter/CosmosMongoBasicFilterTests.cs +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Filter/CosmosMongoBasicFilterTests.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. -using MongoDBIntegrationTests.Support; +using CosmosMongoDBIntegrationTests.Support; using VectorDataSpecificationTests.Filter; using VectorDataSpecificationTests.Support; using VectorDataSpecificationTests.Xunit; using Xunit; -namespace MongoDBIntegrationTests.Filter; +namespace CosmosMongoDBIntegrationTests.Filter; public class CosmosMongoBasicFilterTests(CosmosMongoBasicFilterTests.Fixture fixture) : BasicFilterTests(fixture), IClassFixture diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Properties/AssemblyAttributes.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Properties/AssemblyAttributes.cs index 4e8438d68759..ed3fb05ab20f 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Properties/AssemblyAttributes.cs +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Properties/AssemblyAttributes.cs @@ -1,3 +1,3 @@ // Copyright (c) Microsoft. All rights reserved. -[assembly: CosmosIntegrationTests.Support.CosmosConnectionStringRequired] +[assembly: CosmosMongoDBIntegrationTests.Support.CosmosConnectionStringRequired] diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosConnectionStringRequiredAttribute.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosConnectionStringRequiredAttribute.cs index c944d36eb78c..7aaba96f3cfe 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosConnectionStringRequiredAttribute.cs +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosConnectionStringRequiredAttribute.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using CosmosNoSQLIntegrationTests.Support; using VectorDataSpecificationTests.Xunit; -namespace CosmosIntegrationTests.Support; +namespace CosmosMongoDBIntegrationTests.Support; /// /// Checks whether the sqlite_vec extension is properly installed, and skips the test(s) otherwise. diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBFixture.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBFixture.cs new file mode 100644 index 000000000000..69be997865f6 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace CosmosMongoDBIntegrationTests.Support; + +public class CosmosMongoDBFixture : VectorStoreFixture +{ + public override TestStore TestStore => CosmosMongoDBTestStore.Instance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestEnvironment.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestEnvironment.cs index 1adcb225e66d..df6550d05237 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestEnvironment.cs +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestEnvironment.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Configuration; -namespace CosmosNoSQLIntegrationTests.Support; +namespace CosmosMongoDBIntegrationTests.Support; #pragma warning disable CA1810 // Initialize all static fields when those fields are declared diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestStore.cs b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestStore.cs index b0d4c379ecf4..fba1d18c8a7f 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestStore.cs +++ b/dotnet/src/VectorDataIntegrationTests/CosmosMongoDBIntegrationTests/Support/CosmosMongoDBTestStore.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. -using CosmosNoSQLIntegrationTests.Support; using Microsoft.Extensions.VectorData; using Microsoft.SemanticKernel.Connectors.AzureCosmosDBMongoDB; using MongoDB.Driver; using VectorDataSpecificationTests.Support; -namespace MongoDBIntegrationTests.Support; +namespace CosmosMongoDBIntegrationTests.Support; public sealed class CosmosMongoDBTestStore : TestStore { diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/Collections/CosmosNoSQLCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/Collections/CosmosNoSQLCollectionConformanceTests.cs new file mode 100644 index 000000000000..222e868a8e4b --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/Collections/CosmosNoSQLCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using CosmosNoSQLIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace CosmosNoSQLIntegrationTests.Collections; + +public class CosmosNoSQLCollectionConformanceTests(CosmosNoSQLFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/CosmosNoSQLIntegrationTests.csproj b/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/CosmosNoSQLIntegrationTests.csproj index dd8e3f7a9ba0..715b01505df7 100644 --- a/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/CosmosNoSQLIntegrationTests.csproj +++ b/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/CosmosNoSQLIntegrationTests.csproj @@ -26,4 +26,13 @@ + + + Always + + + Always + + + diff --git a/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/Support/CosmosNoSQLFixture.cs b/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/Support/CosmosNoSQLFixture.cs new file mode 100644 index 000000000000..9ddaad05be85 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/CosmosNoSQLIntegrationTests/Support/CosmosNoSQLFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace CosmosNoSQLIntegrationTests.Support; + +public class CosmosNoSQLFixture : VectorStoreFixture +{ + public override TestStore TestStore => CosmosNoSqlTestStore.Instance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/InMemoryIntegrationTests/Collections/InMemoryCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/InMemoryIntegrationTests/Collections/InMemoryCollectionConformanceTests.cs new file mode 100644 index 000000000000..ceddaf074da9 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/InMemoryIntegrationTests/Collections/InMemoryCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using InMemoryIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace InMemoryIntegrationTests.Collections; + +public class InMemoryCollectionConformanceTests(InMemoryFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/InMemoryIntegrationTests/Support/InMemoryFixture.cs b/dotnet/src/VectorDataIntegrationTests/InMemoryIntegrationTests/Support/InMemoryFixture.cs new file mode 100644 index 000000000000..6a7eddb89dc3 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/InMemoryIntegrationTests/Support/InMemoryFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace InMemoryIntegrationTests.Support; + +public class InMemoryFixture : VectorStoreFixture +{ + public override TestStore TestStore => InMemoryTestStore.Instance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/MongoDBIntegrationTests/Collections/MongoDBCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/MongoDBIntegrationTests/Collections/MongoDBCollectionConformanceTests.cs new file mode 100644 index 000000000000..587dd6541f33 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/MongoDBIntegrationTests/Collections/MongoDBCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using MongoDBIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace MongoDBIntegrationTests.Collections; + +public class MongoDBCollectionConformanceTests(MongoDBFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/MongoDBIntegrationTests/Support/MongoDBFixture.cs b/dotnet/src/VectorDataIntegrationTests/MongoDBIntegrationTests/Support/MongoDBFixture.cs new file mode 100644 index 000000000000..00c429506cec --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/MongoDBIntegrationTests/Support/MongoDBFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace MongoDBIntegrationTests.Support; + +public class MongoDBFixture : VectorStoreFixture +{ + public override TestStore TestStore => MongoDBTestStore.Instance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Collections/QdrantCollectionConformanceTests_NamedVectors.cs b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Collections/QdrantCollectionConformanceTests_NamedVectors.cs new file mode 100644 index 000000000000..7f4d0f138907 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Collections/QdrantCollectionConformanceTests_NamedVectors.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using QdrantIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace QdrantIntegrationTests.Collections; + +public class QdrantCollectionConformanceTests_NamedVectors(QdrantNamedVectorsFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Collections/QdrantCollectionConformanceTests_UnnamedVector.cs b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Collections/QdrantCollectionConformanceTests_UnnamedVector.cs new file mode 100644 index 000000000000..5471d83c8996 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Collections/QdrantCollectionConformanceTests_UnnamedVector.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using QdrantIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace QdrantIntegrationTests.Collections; + +public class QdrantCollectionConformanceTests_UnnamedVector(QdrantUnnamedVectorFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Filter/QdrantBasicFilterTests.cs b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Filter/QdrantBasicFilterTests.cs index 13ed231a3207..2ba3b454231b 100644 --- a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Filter/QdrantBasicFilterTests.cs +++ b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Filter/QdrantBasicFilterTests.cs @@ -12,7 +12,7 @@ public class QdrantBasicFilterTests(QdrantBasicFilterTests.Fixture fixture) { public new class Fixture : BasicFilterTests.Fixture { - public override TestStore TestStore => QdrantTestStore.Instance; + public override TestStore TestStore => QdrantTestStore.NamedVectorsInstance; // Qdrant doesn't support the default Flat index kind protected override string IndexKind => Microsoft.Extensions.VectorData.IndexKind.Hnsw; diff --git a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantNamedVectorsFixture.cs b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantNamedVectorsFixture.cs new file mode 100644 index 000000000000..7544ae1653b4 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantNamedVectorsFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace QdrantIntegrationTests.Support; + +public class QdrantNamedVectorsFixture : VectorStoreFixture +{ + public override TestStore TestStore => QdrantTestStore.NamedVectorsInstance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantTestStore.cs b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantTestStore.cs index 3537cf8c64e9..c7148c291de4 100644 --- a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantTestStore.cs +++ b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantTestStore.cs @@ -12,9 +12,11 @@ namespace QdrantIntegrationTests.Support; internal sealed class QdrantTestStore : TestStore { - public static QdrantTestStore Instance { get; } = new(); + public static QdrantTestStore NamedVectorsInstance { get; } = new(hasNamedVectors: true); + public static QdrantTestStore UnnamedVectorInstance { get; } = new(hasNamedVectors: false); private readonly QdrantContainer _container = new QdrantBuilder().Build(); + private readonly bool _hasNamedVectors; private QdrantClient? _client; private QdrantVectorStore? _defaultVectorStore; @@ -25,15 +27,13 @@ internal sealed class QdrantTestStore : TestStore public QdrantVectorStore GetVectorStore(QdrantVectorStoreOptions options) => new(this.Client, options); - private QdrantTestStore() - { - } + private QdrantTestStore(bool hasNamedVectors) => this._hasNamedVectors = hasNamedVectors; protected override async Task StartAsync() { await this._container.StartAsync(); this._client = new QdrantClient(this._container.Hostname, this._container.GetMappedPublicPort(QdrantBuilder.QdrantGrpcPort)); - this._defaultVectorStore = new(this._client); + this._defaultVectorStore = new(this._client, new() { HasNamedVectors = this._hasNamedVectors }); } protected override Task StopAsync() diff --git a/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantUnnamedVectorFixture.cs b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantUnnamedVectorFixture.cs new file mode 100644 index 000000000000..678f8c820fcc --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/QdrantIntegrationTests/Support/QdrantUnnamedVectorFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace QdrantIntegrationTests.Support; + +public class QdrantUnnamedVectorFixture : VectorStoreFixture +{ + public override TestStore TestStore => QdrantTestStore.UnnamedVectorInstance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Collections/RedisCollectionConformanceTests_HashSet.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Collections/RedisCollectionConformanceTests_HashSet.cs new file mode 100644 index 000000000000..a3b7c411d8e4 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Collections/RedisCollectionConformanceTests_HashSet.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using RedisIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace RedisIntegrationTests.Collections; + +public class RedisCollectionConformanceTests_HashSet(RedisHashSetFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Collections/RedisCollectionConformanceTests_Json.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Collections/RedisCollectionConformanceTests_Json.cs new file mode 100644 index 000000000000..97d28ef6d17e --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Collections/RedisCollectionConformanceTests_Json.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using RedisIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace RedisIntegrationTests.Collections; + +public class RedisCollectionConformanceTests_Json(RedisJsonFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Filter/RedisBasicFilterTests.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Filter/RedisBasicFilterTests.cs index fe50977e006e..7ec4f834a5f0 100644 --- a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Filter/RedisBasicFilterTests.cs +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Filter/RedisBasicFilterTests.cs @@ -70,7 +70,7 @@ public class RedisJsonCollectionBasicFilterTests(RedisJsonCollectionBasicFilterT { public new class Fixture : BasicFilterTests.Fixture { - public override TestStore TestStore => RedisTestStore.Instance; + public override TestStore TestStore => RedisTestStore.JsonInstance; protected override string CollectionName => "JsonCollectionFilterTests"; @@ -83,7 +83,7 @@ protected override VectorStoreRecordDefinition GetRecordDefinition() protected override IVectorStoreRecordCollection CreateCollection() => new RedisJsonVectorStoreRecordCollection( - RedisTestStore.Instance.Database, + RedisTestStore.JsonInstance.Database, this.CollectionName, new() { VectorStoreRecordDefinition = this.GetRecordDefinition() }); } @@ -122,7 +122,7 @@ public override Task Legacy_AnyTagEqualTo_List() public new class Fixture : BasicFilterTests.Fixture { - public override TestStore TestStore => RedisTestStore.Instance; + public override TestStore TestStore => RedisTestStore.HashSetInstance; protected override string CollectionName => "HashSetCollectionFilterTests"; @@ -138,7 +138,7 @@ protected override VectorStoreRecordDefinition GetRecordDefinition() protected override IVectorStoreRecordCollection CreateCollection() => new RedisHashSetVectorStoreRecordCollection( - RedisTestStore.Instance.Database, + RedisTestStore.HashSetInstance.Database, this.CollectionName, new() { VectorStoreRecordDefinition = this.GetRecordDefinition() }); diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisFixture.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisFixture.cs index a8c7004eaec2..993aa1c530bd 100644 --- a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisFixture.cs +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisFixture.cs @@ -6,5 +6,5 @@ namespace RedisIntegrationTests.Support; public class RedisFixture : VectorStoreFixture { - public override TestStore TestStore => RedisTestStore.Instance; + public override TestStore TestStore => RedisTestStore.JsonInstance; } diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisGenericDataModelFixture.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisGenericDataModelFixture.cs index f5297b67bcb0..3a63d1d77f76 100644 --- a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisGenericDataModelFixture.cs +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisGenericDataModelFixture.cs @@ -6,5 +6,5 @@ namespace RedisIntegrationTests.Support; public class RedisGenericDataModelFixture : GenericDataModelFixture { - public override TestStore TestStore => RedisTestStore.Instance; + public override TestStore TestStore => RedisTestStore.JsonInstance; } diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisHashSetFixture.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisHashSetFixture.cs new file mode 100644 index 000000000000..72e624712364 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisHashSetFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace RedisIntegrationTests.Support; + +public class RedisHashSetFixture : VectorStoreFixture +{ + public override TestStore TestStore => RedisTestStore.HashSetInstance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisJsonFixture.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisJsonFixture.cs new file mode 100644 index 000000000000..45fe503ba13c --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisJsonFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace RedisIntegrationTests.Support; + +public class RedisJsonFixture : VectorStoreFixture +{ + public override TestStore TestStore => RedisTestStore.JsonInstance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisSimpleModelFixture.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisSimpleModelFixture.cs index eb1d8dd70160..f91aefd9055c 100644 --- a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisSimpleModelFixture.cs +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisSimpleModelFixture.cs @@ -6,5 +6,5 @@ namespace RedisIntegrationTests.Support; public class RedisSimpleModelFixture : SimpleModelFixture { - public override TestStore TestStore => RedisTestStore.Instance; + public override TestStore TestStore => RedisTestStore.JsonInstance; } diff --git a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisTestStore.cs b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisTestStore.cs index a1dd2f02c0bc..6ee6f058da46 100644 --- a/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisTestStore.cs +++ b/dotnet/src/VectorDataIntegrationTests/RedisIntegrationTests/Support/RedisTestStore.cs @@ -10,15 +10,19 @@ namespace RedisIntegrationTests.Support; internal sealed class RedisTestStore : TestStore { - public static RedisTestStore Instance { get; } = new(); + public static RedisTestStore JsonInstance { get; } = new(RedisStorageType.Json); + public static RedisTestStore HashSetInstance { get; } = new(RedisStorageType.HashSet); private readonly RedisContainer _container = new RedisBuilder() .WithImage("redis/redis-stack") .Build(); + private readonly RedisStorageType _storageType; private IDatabase? _database; private RedisVectorStore? _defaultVectorStore; + private RedisTestStore(RedisStorageType storageType) => this._storageType = storageType; + public IDatabase Database => this._database ?? throw new InvalidOperationException("Not initialized"); public override IVectorStore DefaultVectorStore => this._defaultVectorStore ?? throw new InvalidOperationException("Not initialized"); @@ -26,16 +30,12 @@ internal sealed class RedisTestStore : TestStore public RedisVectorStore GetVectorStore(RedisVectorStoreOptions options) => new(this.Database, options); - private RedisTestStore() - { - } - protected override async Task StartAsync() { await this._container.StartAsync(); var redis = await ConnectionMultiplexer.ConnectAsync($"{this._container.Hostname}:{this._container.GetMappedPublicPort(6379)},connectTimeout=60000,connectRetry=5"); this._database = redis.GetDatabase(); - this._defaultVectorStore = new(this._database); + this._defaultVectorStore = new(this._database, new() { StorageType = this._storageType }); } protected override Task StopAsync() diff --git a/dotnet/src/VectorDataIntegrationTests/SqliteIntegrationTests/Collections/SqliteCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/SqliteIntegrationTests/Collections/SqliteCollectionConformanceTests.cs new file mode 100644 index 000000000000..32af892024c0 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/SqliteIntegrationTests/Collections/SqliteCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using SqliteIntegrationTests.Support; +using VectorDataSpecificationTests.Collections; +using Xunit; + +namespace SqliteIntegrationTests.Collections; + +public class SqliteCollectionConformanceTests(SqliteFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/SqliteIntegrationTests/Support/SqliteFixture.cs b/dotnet/src/VectorDataIntegrationTests/SqliteIntegrationTests/Support/SqliteFixture.cs new file mode 100644 index 000000000000..7cd6da2d48c1 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/SqliteIntegrationTests/Support/SqliteFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace SqliteIntegrationTests.Support; + +public class SqliteFixture : VectorStoreFixture +{ + public override TestStore TestStore => SqliteTestStore.Instance; +} diff --git a/dotnet/src/VectorDataIntegrationTests/VectorDataIntegrationTests/Collections/CollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/VectorDataIntegrationTests/Collections/CollectionConformanceTests.cs index ca5a0c1dfc20..16f8679df842 100644 --- a/dotnet/src/VectorDataIntegrationTests/VectorDataIntegrationTests/Collections/CollectionConformanceTests.cs +++ b/dotnet/src/VectorDataIntegrationTests/VectorDataIntegrationTests/Collections/CollectionConformanceTests.cs @@ -8,7 +8,7 @@ namespace VectorDataSpecificationTests.Collections; -public class CollectionConformanceTests(VectorStoreFixture fixture) where TKey : notnull +public abstract class CollectionConformanceTests(VectorStoreFixture fixture) where TKey : notnull { [ConditionalFact] public Task DeleteCollectionDoesNotThrowForNonExistingCollection() @@ -18,6 +18,14 @@ public Task DeleteCollectionDoesNotThrowForNonExistingCollection() public Task DeleteCollectionDoesNotThrowForNonExistingCollection_GenericDataModel() => this.DeleteNonExistingCollection>(); + [ConditionalFact] + public Task CreateCollectionCreatesTheCollection() + => this.CreateCollection>(); + + [ConditionalFact] + public Task CreateCollectionCreatesTheCollection_GenericDataModel() + => this.CreateCollection>(); + [ConditionalFact] public Task CreateCollectionIfNotExistsCalledMoreThanOnceDoesNotThrow() => this.CreateCollectionIfNotExistsMoreThanOnce>(); @@ -69,14 +77,31 @@ private async Task DeleteNonExistingCollection() await collection.DeleteCollectionAsync(); } - private async Task CreateCollectionIfNotExistsMoreThanOnce() + private async Task CreateCollection() { var collection = await this.GetNonExistingCollectionAsync(); + await collection.CreateCollectionAsync(); + try { - await collection.CreateCollectionIfNotExistsAsync(); + Assert.True(await collection.CollectionExistsAsync()); + Assert.True(await fixture.TestStore.DefaultVectorStore.ListCollectionNamesAsync().ContainsAsync(collection.CollectionName)); + } + finally + { + await collection.DeleteCollectionAsync(); + } + } + + private async Task CreateCollectionIfNotExistsMoreThanOnce() + { + var collection = await this.GetNonExistingCollectionAsync(); + + await collection.CreateCollectionIfNotExistsAsync(); + try + { Assert.True(await collection.CollectionExistsAsync()); Assert.True(await fixture.TestStore.DefaultVectorStore.ListCollectionNamesAsync().ContainsAsync(collection.CollectionName)); @@ -92,10 +117,10 @@ private async Task CreateCollectionMoreThanOnce() { var collection = await this.GetNonExistingCollectionAsync(); + await collection.CreateCollectionAsync(); + try { - await collection.CreateCollectionAsync(); - Assert.True(await collection.CollectionExistsAsync()); Assert.True(await fixture.TestStore.DefaultVectorStore.ListCollectionNamesAsync().ContainsAsync(collection.CollectionName)); diff --git a/dotnet/src/VectorDataIntegrationTests/WeaviateIntegrationTests/Collections/WeaviateCollectionConformanceTests.cs b/dotnet/src/VectorDataIntegrationTests/WeaviateIntegrationTests/Collections/WeaviateCollectionConformanceTests.cs new file mode 100644 index 000000000000..e839b02ad942 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/WeaviateIntegrationTests/Collections/WeaviateCollectionConformanceTests.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Collections; +using WeaviateIntegrationTests.Support; +using Xunit; + +namespace WeaviateIntegrationTests.Collections; + +public class WeaviateCollectionConformanceTests(WeaviateFixture fixture) + : CollectionConformanceTests(fixture), IClassFixture +{ +} diff --git a/dotnet/src/VectorDataIntegrationTests/WeaviateIntegrationTests/Support/WeaviateFixture.cs b/dotnet/src/VectorDataIntegrationTests/WeaviateIntegrationTests/Support/WeaviateFixture.cs new file mode 100644 index 000000000000..ac3b64f89006 --- /dev/null +++ b/dotnet/src/VectorDataIntegrationTests/WeaviateIntegrationTests/Support/WeaviateFixture.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. + +using VectorDataSpecificationTests.Support; + +namespace WeaviateIntegrationTests.Support; + +public class WeaviateFixture : VectorStoreFixture +{ + public override TestStore TestStore => WeaviateTestStore.Instance; + + // Weaviate requires the name to start with a capital letter and not contain any chars other than a-Z and 0-9. + // Source: https://weaviate.io/developers/weaviate/starter-guides/managing-collections#collection--property-names + public override string GetUniqueCollectionName() => $"A{Guid.NewGuid():N}"; +}