From 263ea3136c6e5053cc1c9f619fc9f8bf4b65924e Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Fri, 20 Jun 2025 13:41:44 +0200 Subject: [PATCH 1/9] Complete Collections interface --- src/Example/packages.lock.json | 3 ++- src/Weaviate.Client.Tests/packages.lock.json | 3 ++- src/Weaviate.Client/CollectionsClient.cs | 28 ++++++++++++++++++++ src/Weaviate.Client/Rest/Client.cs | 14 ++++++++++ src/Weaviate.Client/Weaviate.Client.csproj | 1 + src/Weaviate.Client/packages.lock.json | 6 +++++ 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Example/packages.lock.json b/src/Example/packages.lock.json index 87fe4d62..b5485e23 100644 --- a/src/Example/packages.lock.json +++ b/src/Example/packages.lock.json @@ -128,7 +128,8 @@ "type": "Project", "dependencies": { "Google.Protobuf": "[3.30.2, )", - "Grpc.Net.ClientFactory": "[2.70.0, )" + "Grpc.Net.ClientFactory": "[2.70.0, )", + "System.Linq.Async": "[6.0.3, )" } } } diff --git a/src/Weaviate.Client.Tests/packages.lock.json b/src/Weaviate.Client.Tests/packages.lock.json index 07ccff1a..9aa98256 100644 --- a/src/Weaviate.Client.Tests/packages.lock.json +++ b/src/Weaviate.Client.Tests/packages.lock.json @@ -263,7 +263,8 @@ "type": "Project", "dependencies": { "Google.Protobuf": "[3.30.2, )", - "Grpc.Net.ClientFactory": "[2.70.0, )" + "Grpc.Net.ClientFactory": "[2.70.0, )", + "System.Linq.Async": "[6.0.3, )" } } } diff --git a/src/Weaviate.Client/CollectionsClient.cs b/src/Weaviate.Client/CollectionsClient.cs index 9b6d0003..e2b4b0bd 100644 --- a/src/Weaviate.Client/CollectionsClient.cs +++ b/src/Weaviate.Client/CollectionsClient.cs @@ -1,3 +1,5 @@ +using Weaviate.Client.Models; + namespace Weaviate.Client; public struct CollectionsClient @@ -30,6 +32,32 @@ public async Task Delete(string collectionName) await _client.RestClient.CollectionDelete(collectionName); } + public async Task DeleteAll() + { + var list = await List().Select(l => l.Name).ToListAsync(); + + var tasks = list.Select(Delete); + + await Task.WhenAll(tasks); + } + + public async Task Exists(string collectionName) + { + return await _client.RestClient.CollectionExists(collectionName); + } + + public async Task Export(string collectionName) + { + var response = await _client.RestClient.CollectionGet(collectionName); + + if (response is null) + { + return null; + } + + return response.ToModel(); + } + public async IAsyncEnumerable List() { var response = await _client.RestClient.CollectionList(); diff --git a/src/Weaviate.Client/Rest/Client.cs b/src/Weaviate.Client/Rest/Client.cs index 3d12fb9a..beefb504 100644 --- a/src/Weaviate.Client/Rest/Client.cs +++ b/src/Weaviate.Client/Rest/Client.cs @@ -271,4 +271,18 @@ Models.DataReference[] references return await response.Content.ReadFromJsonAsync() ?? throw new WeaviateRestException(); } + + internal async Task CollectionExists(object collectionName) + { + var path = WeaviateEndpoints.Collection(); + + var response = await _httpClient.GetAsync(path); + + await response.EnsureExpectedStatusCodeAsync([200], "collection property add"); + + var schema = await response.Content.ReadFromJsonAsync(); + + return schema?.Classes?.Any(c => c.Class1 is not null && c.Class1!.Equals(collectionName)) + ?? false; + } } diff --git a/src/Weaviate.Client/Weaviate.Client.csproj b/src/Weaviate.Client/Weaviate.Client.csproj index 5c6f0bc6..524544a4 100644 --- a/src/Weaviate.Client/Weaviate.Client.csproj +++ b/src/Weaviate.Client/Weaviate.Client.csproj @@ -15,6 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + Date: Fri, 20 Jun 2025 15:54:32 +0200 Subject: [PATCH 2/9] Update System.Linq.Async to 6.0.3 --- src/Example/Example.csproj | 2 +- src/Example/packages.lock.json | 14 +++----------- .../Weaviate.Client.Tests.csproj | 2 +- src/Weaviate.Client.Tests/packages.lock.json | 9 +++------ 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/Example/Example.csproj b/src/Example/Example.csproj index 0fe9ba8c..136fa98f 100644 --- a/src/Example/Example.csproj +++ b/src/Example/Example.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Example/packages.lock.json b/src/Example/packages.lock.json index b5485e23..d205543e 100644 --- a/src/Example/packages.lock.json +++ b/src/Example/packages.lock.json @@ -4,12 +4,9 @@ "net8.0": { "System.Linq.Async": { "type": "Direct", - "requested": "[6.0.1, )", - "resolved": "6.0.1", - "contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "6.0.0" - } + "requested": "[6.0.3, )", + "resolved": "6.0.3", + "contentHash": "hSHiq2m1ky7zUQgTp+/2h1K3lABIQ+GltRixoclHPg/Sc1vnfeS6g/Uy5moOVZKrZJdQiFPFZd6OobBp3tZcFg==" }, "Google.Protobuf": { "type": "Transitive", @@ -47,11 +44,6 @@ "Grpc.Core.Api": "2.70.0" } }, - "Microsoft.Bcl.AsyncInterfaces": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" - }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "6.0.0", diff --git a/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj b/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj index 97e7e5b5..3558d3d3 100644 --- a/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj +++ b/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Weaviate.Client.Tests/packages.lock.json b/src/Weaviate.Client.Tests/packages.lock.json index 9aa98256..4102092b 100644 --- a/src/Weaviate.Client.Tests/packages.lock.json +++ b/src/Weaviate.Client.Tests/packages.lock.json @@ -14,12 +14,9 @@ }, "System.Linq.Async": { "type": "Direct", - "requested": "[6.0.1, )", - "resolved": "6.0.1", - "contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "6.0.0" - } + "requested": "[6.0.3, )", + "resolved": "6.0.3", + "contentHash": "hSHiq2m1ky7zUQgTp+/2h1K3lABIQ+GltRixoclHPg/Sc1vnfeS6g/Uy5moOVZKrZJdQiFPFZd6OobBp3tZcFg==" }, "xunit.runner.visualstudio": { "type": "Direct", From 19dce3ec68056d323eec80564716b470d8b3c006 Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 09:28:27 +0200 Subject: [PATCH 3/9] Add tests --- .../Integration/SingleTargetRef.cs | 9 - .../Integration/TestCollections.cs | 169 ++++++++++++++++++ ...tFilters.Integration.cs => TestFilters.cs} | 0 .../{TestFilters.Unit.cs => TestFilters.cs} | 0 src/Weaviate.Client/Extensions.cs | 29 +-- src/Weaviate.Client/Models/Collection.cs | 4 +- .../Models/ReplicationConfig.cs | 5 +- .../Models/Vectorizers/VectorIndex.cs | 7 + src/Weaviate.Client/ObjectHelper.cs | 43 +++++ 9 files changed, 243 insertions(+), 23 deletions(-) create mode 100644 src/Weaviate.Client.Tests/Integration/TestCollections.cs rename src/Weaviate.Client.Tests/Integration/{TestFilters.Integration.cs => TestFilters.cs} (100%) rename src/Weaviate.Client.Tests/Unit/{TestFilters.Unit.cs => TestFilters.cs} (100%) diff --git a/src/Weaviate.Client.Tests/Integration/SingleTargetRef.cs b/src/Weaviate.Client.Tests/Integration/SingleTargetRef.cs index 9ae28833..c38b2bda 100644 --- a/src/Weaviate.Client.Tests/Integration/SingleTargetRef.cs +++ b/src/Weaviate.Client.Tests/Integration/SingleTargetRef.cs @@ -294,21 +294,12 @@ public async Task Test_SingleTargetReference_Complex() // Act var fun = await reviews.Query.NearText("Fun for the whole family", limit: 2); - Console.WriteLine( - JsonSerializer.Serialize(fun, new JsonSerializerOptions { WriteIndented = true }) - ); var disappointed = await reviews.Query.NearText( "Disapointed by this movie", limit: 2, references: [new QueryReference("forMovie", ["title"])] ); - Console.WriteLine( - JsonSerializer.Serialize( - disappointed, - new JsonSerializerOptions { WriteIndented = true } - ) - ); // Assert var funObjects = fun.Objects.ToList(); diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs new file mode 100644 index 00000000..0bda1853 --- /dev/null +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -0,0 +1,169 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Vectorizers; + +namespace Weaviate.Client.Tests.Integration; + +[Collection("TestCollections")] +public partial class CollectionsTests : IntegrationTests +{ + [Fact] + public async Task Test_Collections_Exists() + { + var collection = await CollectionFactory( + properties: [Property.Text("Name")], + vectorConfig: Vector.Name("default").With(new VectorizerConfig.None()) + ); + + bool exists = await _weaviate.Collections.Exists(collection.Name); + + Assert.True(exists); + } + + [Fact] + public async Task Test_Collections_Not_Exists() + { + bool exists = await _weaviate.Collections.Exists("Some_Random_Name"); + + Assert.False(exists); + } + + [Fact] + public async Task Test_Collections_Export() + { + var collection = await CollectionFactory( + name: "MyOwnSuffix", + description: "My own description too", + properties: [Property.Text("Name")], + vectorConfig: Vector.Name("default").With(new VectorizerConfig.None()) + ); + + var export = await _weaviate.Collections.Export(collection.Name); + + // Basic collection properties + Assert.NotNull(export); + Assert.EndsWith("MyOwnSuffix", export.Name); + Assert.Equal("My own description too", export.Description); + + // Properties validation + Assert.NotNull(export.Properties); + Assert.Single(export.Properties); + var property = export.Properties.First(); + Assert.Equal("name", property.Name); + Assert.Contains("text", property.DataType); + + // InvertedIndexConfig validation + Assert.NotNull(export.InvertedIndexConfig); + Assert.NotNull(export.InvertedIndexConfig.Bm25); + Assert.Equal(0.75f, export.InvertedIndexConfig.Bm25.B); + Assert.Equal(1.2f, export.InvertedIndexConfig.Bm25.K1); + Assert.Equal(60, export.InvertedIndexConfig.CleanupIntervalSeconds); + Assert.False(export.InvertedIndexConfig.IndexNullState); + Assert.False(export.InvertedIndexConfig.IndexPropertyLength); + Assert.False(export.InvertedIndexConfig.IndexTimestamps); + + // Stopwords validation + Assert.NotNull(export.InvertedIndexConfig.Stopwords); + Assert.Equal("en", export.InvertedIndexConfig.Stopwords.Preset); + Assert.Empty(export.InvertedIndexConfig.Stopwords.Additions); + Assert.Empty(export.InvertedIndexConfig.Stopwords.Removals); + + // Module config should be null for base Collection + Assert.Null(export.ModuleConfig); + + // MultiTenancyConfig validation + Assert.NotNull(export.MultiTenancyConfig); + Assert.False(export.MultiTenancyConfig.AutoTenantActivation); + Assert.False(export.MultiTenancyConfig.AutoTenantCreation); + Assert.False(export.MultiTenancyConfig.Enabled); + + // ReplicationConfig validation + Assert.NotNull(export.ReplicationConfig); + Assert.False(export.ReplicationConfig.AsyncEnabled); + Assert.Equal( + DeletionStrategy.NoAutomatedResolution, + export.ReplicationConfig.DeletionStrategy + ); + Assert.Equal(1, export.ReplicationConfig.Factor); + + // ShardingConfig validation + Assert.NotNull(export.ShardingConfig); + Assert.Equal(1, ((dynamic?)export.ShardingConfig)?.actualCount); + Assert.Equal(128, export.ShardingConfig?.actualVirtualCount); + Assert.Equal(1, export.ShardingConfig?.desiredCount); + Assert.Equal(128, export.ShardingConfig?.desiredVirtualCount); + Assert.Equal("murmur3", export.ShardingConfig?.function); + Assert.Equal("_id", export.ShardingConfig?.key); + Assert.Equal("hash", export.ShardingConfig?.strategy); + Assert.Equal(128, export.ShardingConfig?.virtualPerPhysical); + + // VectorConfig validation + Assert.NotNull(export.VectorConfig); + Assert.True(export.VectorConfig.ContainsKey("default")); + + var defaultVectorConfig = export.VectorConfig["default"]; + Assert.Equal("default", defaultVectorConfig.Name); + Assert.Equal("hnsw", defaultVectorConfig.VectorIndexType); + Assert.NotNull(defaultVectorConfig.Vectorizer); + + // VectorIndexConfig validation + Assert.NotNull(defaultVectorConfig.VectorIndexConfig); + Assert.Equal("hnsw", defaultVectorConfig.VectorIndexConfig.Identifier); + Assert.NotNull(defaultVectorConfig.VectorIndexConfig.Configuration); + + var config = defaultVectorConfig.VectorIndexConfig.Configuration; + + // HNSW specific configuration assertions + Assert.Equal("cosine", config?.distance); + Assert.Equal(8, config?.dynamicEfFactor); + Assert.Equal(500, config?.dynamicEfMax); + Assert.Equal(100, config?.dynamicEfMin); + Assert.Equal(-1, config?.ef); + Assert.Equal(128, config?.efConstruction); + Assert.Equal("sweeping", config?.filterStrategy); + Assert.Equal(40000, config?.flatSearchCutoff); + Assert.Equal(32, config?.maxConnections); + Assert.Equal(300, config?.cleanupIntervalSeconds); + Assert.False(config?.skip); + Assert.Equal(1000000000000L, config?.vectorCacheMaxObjects); + + // Binary Quantization (bq) validation + Assert.NotNull(config?.bq); + Assert.False(config?.bq.enabled); + + // Product Quantization (pq) validation + Assert.NotNull(config?.pq); + Assert.False(config?.pq.enabled); + Assert.False(config?.pq.bitCompression); + Assert.Equal(256, config?.pq.centroids); + Assert.Equal(0, config?.pq.segments); + Assert.Equal(100000, config?.pq.trainingLimit); + Assert.NotNull(config?.pq.encoder); + Assert.Equal("log-normal", config?.pq.encoder.distribution); + Assert.Equal("kmeans", config?.pq.encoder.type); + + // Scalar Quantization (sq) validation + Assert.NotNull(config?.sq); + Assert.False(config?.sq.enabled); + Assert.Equal(20, config?.sq.rescoreLimit); + Assert.Equal(100000, config?.sq.trainingLimit); + + // Multivector validation + Assert.NotNull(config?.multivector); + Assert.False(config?.multivector.enabled); + Assert.Equal("maxSim", config?.multivector.aggregation); + Assert.NotNull(config?.multivector.muvera); + Assert.False(config?.multivector.muvera.enabled); + Assert.Equal(16, config?.multivector.muvera.dprojections); + Assert.Equal(4, config?.multivector.muvera.ksim); + Assert.Equal(10, config?.multivector.muvera.repetitions); + + // Obsolete properties should be null/empty for new VectorConfig usage +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Null(export.VectorIndexConfig); + Assert.Null(export.VectorIndexType); + Assert.Equal("", export.Vectorizer); +#pragma warning restore CS0618 // Type or member is obsolete + } +} diff --git a/src/Weaviate.Client.Tests/Integration/TestFilters.Integration.cs b/src/Weaviate.Client.Tests/Integration/TestFilters.cs similarity index 100% rename from src/Weaviate.Client.Tests/Integration/TestFilters.Integration.cs rename to src/Weaviate.Client.Tests/Integration/TestFilters.cs diff --git a/src/Weaviate.Client.Tests/Unit/TestFilters.Unit.cs b/src/Weaviate.Client.Tests/Unit/TestFilters.cs similarity index 100% rename from src/Weaviate.Client.Tests/Unit/TestFilters.Unit.cs rename to src/Weaviate.Client.Tests/Unit/TestFilters.cs diff --git a/src/Weaviate.Client/Extensions.cs b/src/Weaviate.Client/Extensions.cs index 65881b95..46fc2df2 100644 --- a/src/Weaviate.Client/Extensions.cs +++ b/src/Weaviate.Client/Extensions.cs @@ -135,6 +135,13 @@ internal static Collection ToModel(this Rest.Dto.Class collection) )) .ToDictionary() ?? new Dictionary(); + var shardingConfig = ObjectHelper.JsonElementToExpandoObject( + collection.ShardingConfig as JsonElement? + ); + var moduleConfig = ObjectHelper.JsonElementToExpandoObject( + collection.ModuleConfig as JsonElement? + ); + return new Collection() { Name = collection.Class1 ?? string.Empty, @@ -180,17 +187,7 @@ internal static Collection ToModel(this Rest.Dto.Class collection) iic.IndexTimestamps ?? InvertedIndexConfig.Default.IndexTimestamps, } : null, - ShardingConfig = collection?.ShardingConfig, - ModuleConfig = collection?.ModuleConfig, - ReplicationConfig = - (collection?.ReplicationConfig is Rest.Dto.ReplicationConfig rc) - ? new ReplicationConfig - { - AsyncEnabled = rc.AsyncEnabled ?? ReplicationConfig.Default.AsyncEnabled, - Factor = rc.Factor ?? ReplicationConfig.Default.Factor, - DeletionStrategy = (DeletionStrategy?)rc.DeletionStrategy, - } - : null, + ModuleConfig = moduleConfig, MultiTenancyConfig = (collection?.MultiTenancyConfig is Rest.Dto.MultiTenancyConfig mtc) ? new MultiTenancyConfig @@ -203,6 +200,16 @@ internal static Collection ToModel(this Rest.Dto.Class collection) mtc.AutoTenantCreation ?? MultiTenancyConfig.Default.AutoTenantCreation, } : null, + ReplicationConfig = + (collection?.ReplicationConfig is Rest.Dto.ReplicationConfig rc) + ? new ReplicationConfig + { + AsyncEnabled = rc.AsyncEnabled ?? ReplicationConfig.Default.AsyncEnabled, + Factor = rc.Factor ?? ReplicationConfig.Default.Factor, + DeletionStrategy = (DeletionStrategy?)rc.DeletionStrategy, + } + : null, + ShardingConfig = shardingConfig, VectorConfig = vectorConfig, }; } diff --git a/src/Weaviate.Client/Models/Collection.cs b/src/Weaviate.Client/Models/Collection.cs index f3ec1d21..fdc4b5b6 100644 --- a/src/Weaviate.Client/Models/Collection.cs +++ b/src/Weaviate.Client/Models/Collection.cs @@ -1,6 +1,8 @@ +using System.Dynamic; + namespace Weaviate.Client.Models; -public class Collection : CollectionBase { } +public class Collection : CollectionBase { } public class CollectionBase { diff --git a/src/Weaviate.Client/Models/ReplicationConfig.cs b/src/Weaviate.Client/Models/ReplicationConfig.cs index 96bb595d..9478fbf3 100644 --- a/src/Weaviate.Client/Models/ReplicationConfig.cs +++ b/src/Weaviate.Client/Models/ReplicationConfig.cs @@ -4,7 +4,7 @@ public enum DeletionStrategy { NoAutomatedResolution, DeleteOnConflict, - TimeBasedResolution + TimeBasedResolution, } /// @@ -28,7 +28,8 @@ public class ReplicationConfig /// Conflict resolution strategy for deleted objects. /// Enum: [NoAutomatedResolution DeleteOnConflict TimeBasedResolution] /// - public DeletionStrategy? DeletionStrategy { get; set; } = Models.DeletionStrategy.NoAutomatedResolution; + public DeletionStrategy? DeletionStrategy { get; set; } = + Models.DeletionStrategy.NoAutomatedResolution; /// /// Number of times a class is replicated (default: 1). diff --git a/src/Weaviate.Client/Models/Vectorizers/VectorIndex.cs b/src/Weaviate.Client/Models/Vectorizers/VectorIndex.cs index 36f40780..be670490 100644 --- a/src/Weaviate.Client/Models/Vectorizers/VectorIndex.cs +++ b/src/Weaviate.Client/Models/Vectorizers/VectorIndex.cs @@ -1,3 +1,5 @@ +using System.Text.Json; + namespace Weaviate.Client.Models.Vectorizers; public abstract record VectorIndexConfig(string Identifier, dynamic? Configuration) @@ -8,6 +10,11 @@ public abstract record VectorIndexConfig(string Identifier, dynamic? Configurati internal static VectorIndexConfig Factory(string type, object? vectorIndexConfig) { + if (vectorIndexConfig is JsonElement vic) + { + vectorIndexConfig = ObjectHelper.JsonElementToExpandoObject(vic); + } + return type switch { "hnsw" => new VectorIndexConfig.HNSW() { Configuration = vectorIndexConfig }, diff --git a/src/Weaviate.Client/ObjectHelper.cs b/src/Weaviate.Client/ObjectHelper.cs index 91444d4b..98314de2 100644 --- a/src/Weaviate.Client/ObjectHelper.cs +++ b/src/Weaviate.Client/ObjectHelper.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Dynamic; using System.Reflection; +using System.Text.Json; using Google.Protobuf.WellKnownTypes; using Weaviate.Client.Models; using Weaviate.Client.Rest.Dto; @@ -10,6 +11,48 @@ namespace Weaviate.Client; internal class ObjectHelper { + public static dynamic? JsonElementToExpandoObject(JsonElement? e) + { + if (e is null) + { + return null; + } + + var element = e.Value; + + if (element.ValueKind == JsonValueKind.Object) + { + var expando = new ExpandoObject(); + var dictionary = (IDictionary)expando; + + foreach (var property in element.EnumerateObject()) + { + dictionary[property.Name] = JsonElementToExpandoObject(property.Value); + } + + return expando; + } + + if (element.ValueKind == JsonValueKind.Array) + { + return element + .EnumerateArray() + .Cast() + .Select(JsonElementToExpandoObject) + .ToArray(); + } + + return element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.TryGetInt32(out int i) ? i : element.GetDouble(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Null => null, + _ => element.ToString(), + }; + } + public static string MakeBeaconSource(string collection, Guid fromUuid, string fromProperty) => $"weaviate://localhost/{collection}/{fromUuid}/{fromProperty}"; From 85d72d20369ff185292360dbd23e2700109e1a00 Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 09:34:18 +0200 Subject: [PATCH 4/9] Don't test 1.31 features --- .../Integration/TestCollections.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index 0bda1853..f6c778f5 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -153,11 +153,13 @@ public async Task Test_Collections_Export() Assert.NotNull(config?.multivector); Assert.False(config?.multivector.enabled); Assert.Equal("maxSim", config?.multivector.aggregation); - Assert.NotNull(config?.multivector.muvera); - Assert.False(config?.multivector.muvera.enabled); - Assert.Equal(16, config?.multivector.muvera.dprojections); - Assert.Equal(4, config?.multivector.muvera.ksim); - Assert.Equal(10, config?.multivector.muvera.repetitions); + + // Available from v1.31 + // Assert.NotNull(config?.multivector.muvera); + // Assert.False(config?.multivector.muvera.enabled); + // Assert.Equal(16, config?.multivector.muvera.dprojections); + // Assert.Equal(4, config?.multivector.muvera.ksim); + // Assert.Equal(10, config?.multivector.muvera.repetitions); // Obsolete properties should be null/empty for new VectorConfig usage #pragma warning disable CS0618 // Type or member is obsolete From f867916acbcac6402eb9b43a5445467859285daf Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 10:00:02 +0200 Subject: [PATCH 5/9] Add test for collections list --- .../Integration/TestCollections.cs | 34 +++++++++++++++++++ .../Integration/TestConnection.cs | 7 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index f6c778f5..720c7dbe 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -8,6 +8,40 @@ namespace Weaviate.Client.Tests.Integration; [Collection("TestCollections")] public partial class CollectionsTests : IntegrationTests { + [Fact] + public async Task Test_Collections_List() + { + // Arrange + var collection = new[] + { + await CollectionFactory( + name: "Collection1", + properties: [Property.Text("Name")], + vectorConfig: Vector.Name("default").With(new VectorizerConfig.None()) + ), + await CollectionFactory( + name: "Collection2", + properties: [Property.Text("Lastname")], + vectorConfig: Vector.Name("default").With(new VectorizerConfig.None()) + ), + await CollectionFactory( + name: "Collection3", + properties: [Property.Text("Address")], + vectorConfig: Vector.Name("default").With(new VectorizerConfig.None()) + ), + }; + + var collectionNames = collection.Select(c => c.Name).ToHashSet(); + + // Act + var list = await _weaviate + .Collections.List() + .ToListAsync(TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(collectionNames, list.Select(l => l.Name).ToHashSet()); + } + [Fact] public async Task Test_Collections_Exists() { diff --git a/src/Weaviate.Client.Tests/Integration/TestConnection.cs b/src/Weaviate.Client.Tests/Integration/TestConnection.cs index e34b6923..079e37a5 100644 --- a/src/Weaviate.Client.Tests/Integration/TestConnection.cs +++ b/src/Weaviate.Client.Tests/Integration/TestConnection.cs @@ -8,8 +8,11 @@ public async Task ConnectToLocal() { var client = Connect.Local(); - var ex = await Record.ExceptionAsync(() => - client.Collections.List().ToListAsync(TestContext.Current.CancellationToken).AsTask() + var ex = await Record.ExceptionAsync(async () => + await client + .Collections.List() + .ToListAsync(TestContext.Current.CancellationToken) + .AsTask() ); Assert.Null(ex); } From 349504ef6d23a9ccb0a0c2e5f402bb029221d805 Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 10:07:40 +0200 Subject: [PATCH 6/9] Fix test --- src/Weaviate.Client.Tests/Integration/TestCollections.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index 720c7dbe..d384ec7a 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -1,5 +1,3 @@ -using System.Text.Json; -using System.Text.Json.Serialization; using Weaviate.Client.Models; using Weaviate.Client.Models.Vectorizers; @@ -39,7 +37,7 @@ await CollectionFactory( .ToListAsync(TestContext.Current.CancellationToken); // Assert - Assert.Equal(collectionNames, list.Select(l => l.Name).ToHashSet()); + Assert.Subset(collectionNames, list.Select(l => l.Name).ToHashSet()); } [Fact] From be4bc79df510a4c399fe282b2c42243f8a34c08e Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 10:33:58 +0200 Subject: [PATCH 7/9] Fix collection list test when running concurrently --- src/Weaviate.Client.Tests/Integration/TestCollections.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index d384ec7a..b580a06d 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -37,7 +37,7 @@ await CollectionFactory( .ToListAsync(TestContext.Current.CancellationToken); // Assert - Assert.Subset(collectionNames, list.Select(l => l.Name).ToHashSet()); + Assert.ProperSubset(list.Select(l => l.Name).ToHashSet(), collectionNames); } [Fact] From 30b1d67cb58f5392dd3374a561d684eff73351fc Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 13:56:53 +0200 Subject: [PATCH 8/9] Add Export test with mostly non-default values --- .../Integration/TestCollections.cs | 194 +++++++++++++++++- .../Integration/_Integration.cs | 23 ++- src/Weaviate.Client/Extensions.cs | 5 +- src/Weaviate.Client/Models/Collection.cs | 6 +- .../Models/InvertedIndexConfig.cs | 3 +- .../Models/MultiTenancyConfig.cs | 1 - src/Weaviate.Client/Models/ShardingConfig.cs | 20 ++ src/Weaviate.Client/Models/StopwordConfig.cs | 6 +- .../Models/Vectorizers/Vectorizer.cs | 5 +- 9 files changed, 242 insertions(+), 21 deletions(-) create mode 100644 src/Weaviate.Client/Models/ShardingConfig.cs diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index b580a06d..11642315 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -121,14 +121,14 @@ public async Task Test_Collections_Export() // ShardingConfig validation Assert.NotNull(export.ShardingConfig); - Assert.Equal(1, ((dynamic?)export.ShardingConfig)?.actualCount); - Assert.Equal(128, export.ShardingConfig?.actualVirtualCount); - Assert.Equal(1, export.ShardingConfig?.desiredCount); - Assert.Equal(128, export.ShardingConfig?.desiredVirtualCount); - Assert.Equal("murmur3", export.ShardingConfig?.function); - Assert.Equal("_id", export.ShardingConfig?.key); - Assert.Equal("hash", export.ShardingConfig?.strategy); - Assert.Equal(128, export.ShardingConfig?.virtualPerPhysical); + Assert.Equal(1, export.ShardingConfig?.ActualCount); + Assert.Equal(128, export.ShardingConfig?.ActualVirtualCount); + Assert.Equal(1, export.ShardingConfig?.DesiredCount); + Assert.Equal(128, export.ShardingConfig?.DesiredVirtualCount); + Assert.Equal("murmur3", export.ShardingConfig?.Function); + Assert.Equal("_id", export.ShardingConfig?.Key); + Assert.Equal("hash", export.ShardingConfig?.Strategy); + Assert.Equal(128, export.ShardingConfig?.VirtualPerPhysical); // VectorConfig validation Assert.NotNull(export.VectorConfig); @@ -200,4 +200,182 @@ public async Task Test_Collections_Export() Assert.Equal("", export.Vectorizer); #pragma warning restore CS0618 // Type or member is obsolete } + + [Fact] + public async Task Test_Collections_Export_NonDefaultValues() + { + var collection = await CollectionFactory( + name: "MyOwnSuffixNonDefault", + description: "My own description too", + properties: [Property.Text("Name"), Property.Int("SomeNumber")], + references: null, + collectionNamePartSeparator: "", + vectorConfig: Vector + .Name("nondefault") + .With(new VectorizerConfig.Text2VecContextionary() { VectorizeClassName = false }), + // multiTenancyConfig: new() + // { + // AutoTenantActivation = true, + // AutoTenantCreation = true, + // Enabled = true, + // }, + invertedIndexConfig: new() + { + Bm25 = new() { B = 0.70f, K1 = 1.3f }, + CleanupIntervalSeconds = 30, + IndexNullState = true, + IndexPropertyLength = true, + IndexTimestamps = true, + Stopwords = new() + { + Preset = "none", + Additions = ["plus"], + Removals = ["minus"], + }, + }, + replicationConfig: new() + { + DeletionStrategy = DeletionStrategy.TimeBasedResolution, + AsyncEnabled = true, + Factor = 1, + }, + shardingConfig: new() + { + ActualCount = 1, + ActualVirtualCount = 136, + DesiredCount = 1, + DesiredVirtualCount = 136, + VirtualPerPhysical = 136, + } + ); + + var export = await _weaviate.Collections.Export(collection.Name); + + // Basic collection properties + Assert.NotNull(export); + Assert.EndsWith("MyOwnSuffixNonDefault", export.Name); + Assert.Equal("My own description too", export.Description); + + // Properties validation + Assert.NotNull(export.Properties); + Assert.Equal(2, export.Properties.Count); + var property = export.Properties.First(); + Assert.Equal("name", property.Name); + Assert.Contains("text", property.DataType); + + // InvertedIndexConfig validation + Assert.NotNull(export.InvertedIndexConfig); + Assert.NotNull(export.InvertedIndexConfig.Bm25); + Assert.Equal(0.70f, export.InvertedIndexConfig.Bm25.B); + Assert.Equal(1.3f, export.InvertedIndexConfig.Bm25.K1); + Assert.Equal(30, export.InvertedIndexConfig.CleanupIntervalSeconds); + Assert.True(export.InvertedIndexConfig.IndexNullState); + Assert.True(export.InvertedIndexConfig.IndexPropertyLength); + Assert.True(export.InvertedIndexConfig.IndexTimestamps); + + // Stopwords validation + Assert.NotNull(export.InvertedIndexConfig.Stopwords); + Assert.Equal("none", export.InvertedIndexConfig.Stopwords.Preset); + Assert.NotEmpty(export.InvertedIndexConfig.Stopwords.Additions); + Assert.NotEmpty(export.InvertedIndexConfig.Stopwords.Removals); + + // Module config should be null for base Collection + Assert.Null(export.ModuleConfig); + + // MultiTenancyConfig validation + Assert.NotNull(export.MultiTenancyConfig); + Assert.False(export.MultiTenancyConfig.AutoTenantActivation); + Assert.False(export.MultiTenancyConfig.AutoTenantCreation); + Assert.False(export.MultiTenancyConfig.Enabled); + + // ReplicationConfig validation + Assert.NotNull(export.ReplicationConfig); + Assert.True(export.ReplicationConfig.AsyncEnabled); + Assert.Equal( + DeletionStrategy.TimeBasedResolution, + export.ReplicationConfig.DeletionStrategy + ); + Assert.Equal(1, export.ReplicationConfig.Factor); + + // ShardingConfig validation + Assert.NotNull(export.ShardingConfig); + Assert.Equal(1, export.ShardingConfig?.ActualCount); + Assert.Equal(136, export.ShardingConfig?.ActualVirtualCount); + Assert.Equal(1, export.ShardingConfig?.DesiredCount); + Assert.Equal(136, export.ShardingConfig?.DesiredVirtualCount); + Assert.Equal("murmur3", export.ShardingConfig?.Function); + Assert.Equal("_id", export.ShardingConfig?.Key); + Assert.Equal("hash", export.ShardingConfig?.Strategy); + Assert.Equal(136, export.ShardingConfig?.VirtualPerPhysical); + + // VectorConfig validation + Assert.NotNull(export.VectorConfig); + Assert.True(export.VectorConfig.ContainsKey("nondefault")); + + var defaultVectorConfig = export.VectorConfig["nondefault"]; + Assert.Equal("nondefault", defaultVectorConfig.Name); + Assert.NotNull(defaultVectorConfig.Vectorizer); + Assert.Equal("text2vec-contextionary", defaultVectorConfig.Vectorizer.Identifier); + + // VectorIndexConfig validation + Assert.NotNull(defaultVectorConfig.VectorIndexConfig); + Assert.Equal("hnsw", defaultVectorConfig.VectorIndexConfig.Identifier); + Assert.NotNull(defaultVectorConfig.VectorIndexConfig.Configuration); + + var config = defaultVectorConfig.VectorIndexConfig.Configuration; + + // HNSW specific configuration assertions + Assert.Equal("cosine", config?.distance); + Assert.Equal(8, config?.dynamicEfFactor); + Assert.Equal(500, config?.dynamicEfMax); + Assert.Equal(100, config?.dynamicEfMin); + Assert.Equal(-1, config?.ef); + Assert.Equal(128, config?.efConstruction); + Assert.Equal("sweeping", config?.filterStrategy); + Assert.Equal(40000, config?.flatSearchCutoff); + Assert.Equal(32, config?.maxConnections); + Assert.Equal(300, config?.cleanupIntervalSeconds); + Assert.False(config?.skip); + Assert.Equal(1000000000000L, config?.vectorCacheMaxObjects); + + // Binary Quantization (bq) validation + Assert.NotNull(config?.bq); + Assert.False(config?.bq.enabled); + + // Product Quantization (pq) validation + Assert.NotNull(config?.pq); + Assert.False(config?.pq.enabled); + Assert.False(config?.pq.bitCompression); + Assert.Equal(256, config?.pq.centroids); + Assert.Equal(0, config?.pq.segments); + Assert.Equal(100000, config?.pq.trainingLimit); + Assert.NotNull(config?.pq.encoder); + Assert.Equal("log-normal", config?.pq.encoder.distribution); + Assert.Equal("kmeans", config?.pq.encoder.type); + + // Scalar Quantization (sq) validation + Assert.NotNull(config?.sq); + Assert.False(config?.sq.enabled); + Assert.Equal(20, config?.sq.rescoreLimit); + Assert.Equal(100000, config?.sq.trainingLimit); + + // Multivector validation + Assert.NotNull(config?.multivector); + Assert.False(config?.multivector.enabled); + Assert.Equal("maxSim", config?.multivector.aggregation); + + // Available from v1.31 + // Assert.NotNull(config?.multivector.muvera); + // Assert.False(config?.multivector.muvera.enabled); + // Assert.Equal(16, config?.multivector.muvera.dprojections); + // Assert.Equal(4, config?.multivector.muvera.ksim); + // Assert.Equal(10, config?.multivector.muvera.repetitions); + + // Obsolete properties should be null/empty for new VectorConfig usage +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Null(export.VectorIndexConfig); + Assert.Null(export.VectorIndexType); + Assert.Equal("", export.Vectorizer); +#pragma warning restore CS0618 // Type or member is obsolete + } } diff --git a/src/Weaviate.Client.Tests/Integration/_Integration.cs b/src/Weaviate.Client.Tests/Integration/_Integration.cs index 43e6cdc5..1dfe9287 100644 --- a/src/Weaviate.Client.Tests/Integration/_Integration.cs +++ b/src/Weaviate.Client.Tests/Integration/_Integration.cs @@ -54,7 +54,11 @@ protected async Task> CollectionFactory( IList? properties = null, IList? references = null, VectorConfigList? vectorConfig = null, - InvertedIndexConfig? invertedIndexConfig = null + MultiTenancyConfig? multiTenancyConfig = null, + InvertedIndexConfig? invertedIndexConfig = null, + ReplicationConfig? replicationConfig = null, + ShardingConfig? shardingConfig = null, + string collectionNamePartSeparator = "_" ) { description ??= TestContext.Current.TestMethod?.MethodName ?? string.Empty; @@ -69,7 +73,7 @@ protected async Task> CollectionFactory( .Where(s => !string.IsNullOrEmpty(s)) .Cast(); - name = string.Join("_", strings); + name = string.Join(collectionNamePartSeparator, strings); properties ??= Property.FromCollection(); @@ -86,7 +90,10 @@ protected async Task> CollectionFactory( Description = description, Properties = properties.Concat(references!.Select(p => (Property)p)).ToList(), VectorConfig = vectorConfig, + MultiTenancyConfig = multiTenancyConfig, InvertedIndexConfig = invertedIndexConfig, + ReplicationConfig = replicationConfig, + ShardingConfig = shardingConfig, }; await _weaviate.Collections.Delete(name); @@ -104,7 +111,11 @@ protected async Task> CollectionFactory( IList? properties = null, IList? references = null, VectorConfigList? vectorConfig = null, - InvertedIndexConfig? invertedIndexConfig = null + MultiTenancyConfig? multiTenancyConfig = null, + InvertedIndexConfig? invertedIndexConfig = null, + ReplicationConfig? replicationConfig = null, + ShardingConfig? shardingConfig = null, + string collectionNamePartSeparator = "_" ) { return await CollectionFactory( @@ -113,7 +124,11 @@ protected async Task> CollectionFactory( properties, references, vectorConfig, - invertedIndexConfig + multiTenancyConfig, + invertedIndexConfig, + replicationConfig, + shardingConfig, + collectionNamePartSeparator ); } } diff --git a/src/Weaviate.Client/Extensions.cs b/src/Weaviate.Client/Extensions.cs index 46fc2df2..0ede9f88 100644 --- a/src/Weaviate.Client/Extensions.cs +++ b/src/Weaviate.Client/Extensions.cs @@ -135,9 +135,12 @@ internal static Collection ToModel(this Rest.Dto.Class collection) )) .ToDictionary() ?? new Dictionary(); - var shardingConfig = ObjectHelper.JsonElementToExpandoObject( + ShardingConfig? shardingConfig = ( collection.ShardingConfig as JsonElement? + )?.Deserialize( + new JsonSerializerOptions() { PropertyNameCaseInsensitive = true } ); + var moduleConfig = ObjectHelper.JsonElementToExpandoObject( collection.ModuleConfig as JsonElement? ); diff --git a/src/Weaviate.Client/Models/Collection.cs b/src/Weaviate.Client/Models/Collection.cs index fdc4b5b6..770ccd04 100644 --- a/src/Weaviate.Client/Models/Collection.cs +++ b/src/Weaviate.Client/Models/Collection.cs @@ -2,9 +2,9 @@ namespace Weaviate.Client.Models; -public class Collection : CollectionBase { } +public class Collection : CollectionBase { } -public class CollectionBase +public class CollectionBase { public string Name { get; set; } = ""; public string Description { get; set; } = ""; @@ -25,7 +25,7 @@ public class CollectionBase public ReplicationConfig? ReplicationConfig { get; set; } // Manage how the index should be sharded and distributed in the cluster - public TShardingConfig? ShardingConfig { get; set; } + public ShardingConfig? ShardingConfig { get; set; } // Configure named vectors. Either use this field or `vectorizer`, `vectorIndexType`, and `vectorIndexConfig` fields. Available from `v1.24.0`. public Dictionary VectorConfig { get; set; } = default!; diff --git a/src/Weaviate.Client/Models/InvertedIndexConfig.cs b/src/Weaviate.Client/Models/InvertedIndexConfig.cs index 620c06d8..15f28c91 100644 --- a/src/Weaviate.Client/Models/InvertedIndexConfig.cs +++ b/src/Weaviate.Client/Models/InvertedIndexConfig.cs @@ -2,7 +2,8 @@ namespace Weaviate.Client.Models; public record InvertedIndexConfig { - private static readonly Lazy defaultInstance = new Lazy(() => new()); + private static readonly Lazy defaultInstance = + new Lazy(() => new()); public static InvertedIndexConfig Default => defaultInstance.Value; diff --git a/src/Weaviate.Client/Models/MultiTenancyConfig.cs b/src/Weaviate.Client/Models/MultiTenancyConfig.cs index f035dc12..dbfab240 100644 --- a/src/Weaviate.Client/Models/MultiTenancyConfig.cs +++ b/src/Weaviate.Client/Models/MultiTenancyConfig.cs @@ -27,4 +27,3 @@ public class MultiTenancyConfig /// public bool Enabled { get; set; } } - diff --git a/src/Weaviate.Client/Models/ShardingConfig.cs b/src/Weaviate.Client/Models/ShardingConfig.cs new file mode 100644 index 00000000..c8b074f3 --- /dev/null +++ b/src/Weaviate.Client/Models/ShardingConfig.cs @@ -0,0 +1,20 @@ +namespace Weaviate.Client.Models; + +public record ShardingConfig +{ + /// + /// Gets the default configuration. + /// + private static readonly Lazy _default = new(() => new()); + + public static ShardingConfig Default => _default.Value; + + public int VirtualPerPhysical { get; set; } = 128; + public int DesiredCount { get; set; } = 1; + public int ActualCount { get; set; } = 1; + public int DesiredVirtualCount { get; set; } = 128; + public int ActualVirtualCount { get; set; } = 128; + public string Key { get; set; } = "_id"; + public string Strategy { get; set; } = "hash"; + public string Function { get; set; } = "murmur3"; +} diff --git a/src/Weaviate.Client/Models/StopwordConfig.cs b/src/Weaviate.Client/Models/StopwordConfig.cs index 1911a366..4c8af55c 100644 --- a/src/Weaviate.Client/Models/StopwordConfig.cs +++ b/src/Weaviate.Client/Models/StopwordConfig.cs @@ -4,7 +4,9 @@ namespace Weaviate.Client.Models; // public class StopwordConfig { - private static readonly Lazy defaultInstance = new Lazy(() => new StopwordConfig()); + private static readonly Lazy defaultInstance = new Lazy(() => + new StopwordConfig() + ); public static StopwordConfig Default => defaultInstance.Value; @@ -22,4 +24,4 @@ public class StopwordConfig /// Stopwords to be removed from consideration (default: []). Can be any array of custom strings. /// public IList Removals { get; set; } = new List(); -} \ No newline at end of file +} diff --git a/src/Weaviate.Client/Models/Vectorizers/Vectorizer.cs b/src/Weaviate.Client/Models/Vectorizers/Vectorizer.cs index 3d81ca45..cd2b15b4 100644 --- a/src/Weaviate.Client/Models/Vectorizers/Vectorizer.cs +++ b/src/Weaviate.Client/Models/Vectorizers/Vectorizer.cs @@ -4,7 +4,7 @@ namespace Weaviate.Client.Models.Vectorizers; public abstract partial record VectorizerConfig { - protected readonly string _identifier; + private readonly string _identifier; protected HashSet _properties = new(); protected VectorizerConfig(string identifier) @@ -30,6 +30,9 @@ public ICollection? Properties } } + [JsonIgnore()] + public string Identifier => _identifier; + public virtual Dictionary ToDto() { return new() { [_identifier] = this }; From 5074c4e26924af1989062e4a7b09efd0b3ea3870 Mon Sep 17 00:00:00 2001 From: Michelangelo Partipilo Date: Tue, 24 Jun 2025 14:39:51 +0200 Subject: [PATCH 9/9] Add test for MultiTenancyConfig --- .../Integration/TestCollections.cs | 178 +++++++++++++++++- 1 file changed, 171 insertions(+), 7 deletions(-) diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index 11642315..3916b4b7 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -202,7 +202,7 @@ public async Task Test_Collections_Export() } [Fact] - public async Task Test_Collections_Export_NonDefaultValues() + public async Task Test_Collections_Export_NonDefaultValues_Sharding() { var collection = await CollectionFactory( name: "MyOwnSuffixNonDefault", @@ -213,12 +213,6 @@ public async Task Test_Collections_Export_NonDefaultValues() vectorConfig: Vector .Name("nondefault") .With(new VectorizerConfig.Text2VecContextionary() { VectorizeClassName = false }), - // multiTenancyConfig: new() - // { - // AutoTenantActivation = true, - // AutoTenantCreation = true, - // Enabled = true, - // }, invertedIndexConfig: new() { Bm25 = new() { B = 0.70f, K1 = 1.3f }, @@ -378,4 +372,174 @@ public async Task Test_Collections_Export_NonDefaultValues() Assert.Equal("", export.Vectorizer); #pragma warning restore CS0618 // Type or member is obsolete } + + [Fact] + public async Task Test_Collections_Export_NonDefaultValues_MultiTenacy() + { + var collection = await CollectionFactory( + name: "MyOwnSuffixNonDefault", + description: "My own description too", + properties: [Property.Text("Name"), Property.Int("SomeNumber")], + references: null, + collectionNamePartSeparator: "", + vectorConfig: Vector + .Name("nondefault") + .With(new VectorizerConfig.Text2VecContextionary() { VectorizeClassName = false }), + multiTenancyConfig: new() + { + AutoTenantActivation = true, + AutoTenantCreation = true, + Enabled = true, + }, + invertedIndexConfig: new() + { + Bm25 = new() { B = 0.70f, K1 = 1.3f }, + CleanupIntervalSeconds = 30, + IndexNullState = true, + IndexPropertyLength = true, + IndexTimestamps = true, + Stopwords = new() + { + Preset = "none", + Additions = ["plus"], + Removals = ["minus"], + }, + }, + replicationConfig: new() + { + DeletionStrategy = DeletionStrategy.TimeBasedResolution, + AsyncEnabled = true, + Factor = 1, + } + ); + + var export = await _weaviate.Collections.Export(collection.Name); + + // Basic collection properties + Assert.NotNull(export); + Assert.EndsWith("MyOwnSuffixNonDefault", export.Name); + Assert.Equal("My own description too", export.Description); + + // Properties validation + Assert.NotNull(export.Properties); + Assert.Equal(2, export.Properties.Count); + var property = export.Properties.First(); + Assert.Equal("name", property.Name); + Assert.Contains("text", property.DataType); + + // InvertedIndexConfig validation + Assert.NotNull(export.InvertedIndexConfig); + Assert.NotNull(export.InvertedIndexConfig.Bm25); + Assert.Equal(0.70f, export.InvertedIndexConfig.Bm25.B); + Assert.Equal(1.3f, export.InvertedIndexConfig.Bm25.K1); + Assert.Equal(30, export.InvertedIndexConfig.CleanupIntervalSeconds); + Assert.True(export.InvertedIndexConfig.IndexNullState); + Assert.True(export.InvertedIndexConfig.IndexPropertyLength); + Assert.True(export.InvertedIndexConfig.IndexTimestamps); + + // Stopwords validation + Assert.NotNull(export.InvertedIndexConfig.Stopwords); + Assert.Equal("none", export.InvertedIndexConfig.Stopwords.Preset); + Assert.NotEmpty(export.InvertedIndexConfig.Stopwords.Additions); + Assert.NotEmpty(export.InvertedIndexConfig.Stopwords.Removals); + + // Module config should be null for base Collection + Assert.Null(export.ModuleConfig); + + // MultiTenancyConfig validation + Assert.NotNull(export.MultiTenancyConfig); + Assert.True(export.MultiTenancyConfig.AutoTenantActivation); + Assert.True(export.MultiTenancyConfig.AutoTenantCreation); + Assert.True(export.MultiTenancyConfig.Enabled); + + // ReplicationConfig validation + Assert.NotNull(export.ReplicationConfig); + Assert.True(export.ReplicationConfig.AsyncEnabled); + Assert.Equal( + DeletionStrategy.TimeBasedResolution, + export.ReplicationConfig.DeletionStrategy + ); + Assert.Equal(1, export.ReplicationConfig.Factor); + + // ShardingConfig validation + Assert.NotNull(export.ShardingConfig); + Assert.Equal(0, export.ShardingConfig?.ActualCount); + Assert.Equal(0, export.ShardingConfig?.ActualVirtualCount); + Assert.Equal(0, export.ShardingConfig?.DesiredCount); + Assert.Equal(0, export.ShardingConfig?.DesiredVirtualCount); + Assert.Equal("", export.ShardingConfig?.Function); + Assert.Equal("", export.ShardingConfig?.Key); + Assert.Equal("", export.ShardingConfig?.Strategy); + Assert.Equal(0, export.ShardingConfig?.VirtualPerPhysical); + + // VectorConfig validation + Assert.NotNull(export.VectorConfig); + Assert.True(export.VectorConfig.ContainsKey("nondefault")); + + var defaultVectorConfig = export.VectorConfig["nondefault"]; + Assert.Equal("nondefault", defaultVectorConfig.Name); + Assert.NotNull(defaultVectorConfig.Vectorizer); + Assert.Equal("text2vec-contextionary", defaultVectorConfig.Vectorizer.Identifier); + + // VectorIndexConfig validation + Assert.NotNull(defaultVectorConfig.VectorIndexConfig); + Assert.Equal("hnsw", defaultVectorConfig.VectorIndexConfig.Identifier); + Assert.NotNull(defaultVectorConfig.VectorIndexConfig.Configuration); + + var config = defaultVectorConfig.VectorIndexConfig.Configuration; + + // HNSW specific configuration assertions + Assert.Equal("cosine", config?.distance); + Assert.Equal(8, config?.dynamicEfFactor); + Assert.Equal(500, config?.dynamicEfMax); + Assert.Equal(100, config?.dynamicEfMin); + Assert.Equal(-1, config?.ef); + Assert.Equal(128, config?.efConstruction); + Assert.Equal("sweeping", config?.filterStrategy); + Assert.Equal(40000, config?.flatSearchCutoff); + Assert.Equal(32, config?.maxConnections); + Assert.Equal(300, config?.cleanupIntervalSeconds); + Assert.False(config?.skip); + Assert.Equal(1000000000000L, config?.vectorCacheMaxObjects); + + // Binary Quantization (bq) validation + Assert.NotNull(config?.bq); + Assert.False(config?.bq.enabled); + + // Product Quantization (pq) validation + Assert.NotNull(config?.pq); + Assert.False(config?.pq.enabled); + Assert.False(config?.pq.bitCompression); + Assert.Equal(256, config?.pq.centroids); + Assert.Equal(0, config?.pq.segments); + Assert.Equal(100000, config?.pq.trainingLimit); + Assert.NotNull(config?.pq.encoder); + Assert.Equal("log-normal", config?.pq.encoder.distribution); + Assert.Equal("kmeans", config?.pq.encoder.type); + + // Scalar Quantization (sq) validation + Assert.NotNull(config?.sq); + Assert.False(config?.sq.enabled); + Assert.Equal(20, config?.sq.rescoreLimit); + Assert.Equal(100000, config?.sq.trainingLimit); + + // Multivector validation + Assert.NotNull(config?.multivector); + Assert.False(config?.multivector.enabled); + Assert.Equal("maxSim", config?.multivector.aggregation); + + // Available from v1.31 + // Assert.NotNull(config?.multivector.muvera); + // Assert.False(config?.multivector.muvera.enabled); + // Assert.Equal(16, config?.multivector.muvera.dprojections); + // Assert.Equal(4, config?.multivector.muvera.ksim); + // Assert.Equal(10, config?.multivector.muvera.repetitions); + + // Obsolete properties should be null/empty for new VectorConfig usage +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Null(export.VectorIndexConfig); + Assert.Null(export.VectorIndexType); + Assert.Equal("", export.Vectorizer); +#pragma warning restore CS0618 // Type or member is obsolete + } }