From 395801cc66c0503a67d5f9dce5f82906a2c3c7d7 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 19 May 2025 15:29:27 +1000 Subject: [PATCH 1/7] Payload Schema type structural tests (#91) Structural test to ensure that all payload schema types are mapped to field types. This will fail if the proto introduces new mappings, so that they can be added for release. --- Directory.Build.props | 2 ++ src/Directory.Build.props | 3 -- src/Qdrant.Client/Qdrant.Client.csproj | 5 ++- src/Qdrant.Client/QdrantClient.cs | 35 +++++++++++--------- tests/Directory.Build.props | 7 ++++ tests/Qdrant.Client.Tests/StructuralTests.cs | 25 ++++++++++++++ 6 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 tests/Directory.Build.props create mode 100644 tests/Qdrant.Client.Tests/StructuralTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 4135923..d702f67 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,6 +21,8 @@ enable 1.0 true + $(SolutionRoot)build\keys\keypair.snk + 0024000004800000940000000602000000240000525341310004000001000100ed1d190ea5406d996c0f5b1a8acbf865fab69e948e50f3621ee36a586566357356ccb63d2bddbc967bef647c2ea7e2871c479c4aaf9dc4d4570022e0dd23d2126e60a69600d6d446b64c4413dcd07d92ee208a59f3531afc6a2afd512e8cbbdf8576887a7eaf72933f041e7134f78b358ee97fc66e80c4604c6bad7a5124fcca diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 41d1c6d..1551268 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,7 @@ true - true - $(SolutionRoot)build\keys\keypair.snk - nuget-icon.png README.md $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/src/Qdrant.Client/Qdrant.Client.csproj b/src/Qdrant.Client/Qdrant.Client.csproj index 5d06cfe..5ad71f6 100644 --- a/src/Qdrant.Client/Qdrant.Client.csproj +++ b/src/Qdrant.Client/Qdrant.Client.csproj @@ -20,7 +20,10 @@ - + + + + CreatePayloadIndexAsync( CollectionName = collectionName, FieldName = fieldName, Wait = wait, - FieldType = schemaType switch - { - PayloadSchemaType.Keyword => FieldType.Keyword, - PayloadSchemaType.Integer => FieldType.Integer, - PayloadSchemaType.Float => FieldType.Float, - PayloadSchemaType.Bool => FieldType.Bool, - PayloadSchemaType.Geo => FieldType.Geo, - PayloadSchemaType.Text => FieldType.Text, - PayloadSchemaType.Datetime => FieldType.Datetime, - PayloadSchemaType.Uuid => FieldType.Uuid, - - _ => throw new ArgumentException("Invalid PayloadSchemaType: " + schemaType, nameof(schemaType)) - } + FieldType = FieldTypeFromPayloadSchemaType(schemaType) }; if (indexParams is not null) @@ -4000,7 +3988,7 @@ public async Task CreateSnapshotAsync(string collectionName /// Universally query points. /// Covers all capabilities of search, recommend, discover, filters. /// Also enables hybrid and multi-stage queries. - /// + /// /// /// The name of the collection. /// Query to perform. If missing, returns points ordered by their IDs. @@ -4100,7 +4088,7 @@ public async Task> QueryAsync( /// Universally query points in batch. /// Covers all capabilities of search, recommend, discover, filters. /// Also enables hybrid and multi-stage queries. - /// + /// /// /// The name of the collection. /// The queries to be performed in the batch. @@ -4153,7 +4141,7 @@ public async Task> QueryBatchAsync( /// Universally query points. /// Covers all capabilities of search, recommend, discover, filters. /// Grouped by a payload field. - /// + /// /// /// The name of the collection. /// Payload field to group by, must be a string or number field. @@ -4521,6 +4509,21 @@ await _grpcClient.Qdrant.HealthCheckAsync( deadline: _grpcTimeout == default ? null : DateTime.UtcNow.Add(_grpcTimeout), cancellationToken: cancellationToken).ConfigureAwait(false); + internal static FieldType FieldTypeFromPayloadSchemaType(PayloadSchemaType schemaType) => + schemaType switch + { + PayloadSchemaType.Keyword => FieldType.Keyword, + PayloadSchemaType.Integer => FieldType.Integer, + PayloadSchemaType.Float => FieldType.Float, + PayloadSchemaType.Bool => FieldType.Bool, + PayloadSchemaType.Geo => FieldType.Geo, + PayloadSchemaType.Text => FieldType.Text, + PayloadSchemaType.Datetime => FieldType.Datetime, + PayloadSchemaType.Uuid => FieldType.Uuid, + + _ => throw new ArgumentException("Invalid PayloadSchemaType: " + schemaType, nameof(schemaType)) + }; + private static void Populate(RepeatedField repeatedField, ReadOnlyMemory memory) { if (MemoryMarshal.TryGetArray(memory, out var segment) && diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..fd9898a --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + true + + diff --git a/tests/Qdrant.Client.Tests/StructuralTests.cs b/tests/Qdrant.Client.Tests/StructuralTests.cs new file mode 100644 index 0000000..67abca5 --- /dev/null +++ b/tests/Qdrant.Client.Tests/StructuralTests.cs @@ -0,0 +1,25 @@ +using Qdrant.Client.Grpc; +using Xunit; + +namespace Qdrant.Client; + +public class StructuralTests +{ + [Fact] + public void All_PayloadSchemaTypes_map_to_FieldType() + { + var schemaTypes = + ((PayloadSchemaType[])Enum.GetValues(typeof(PayloadSchemaType))) + .Except([PayloadSchemaType.UnknownType]); + + foreach (var schemaType in schemaTypes) + { + var ex = Record.Exception(() => + { + var fieldType = QdrantClient.FieldTypeFromPayloadSchemaType(schemaType); + }); + + Assert.Null(ex); + } + } +} From 66d349860dabc6452c21f27c7138a90ab77fc0e4 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 19 May 2025 15:32:32 +1000 Subject: [PATCH 2/7] Update dependencies --- build/Build.csproj | 4 +- build/Main.cs | 12 +- src/Qdrant.Client/Grpc/Expression.cs | 246 +++++++++--------- src/Qdrant.Client/Qdrant.Client.csproj | 6 +- .../Qdrant.Client.Tests.csproj | 8 +- 5 files changed, 138 insertions(+), 138 deletions(-) diff --git a/build/Build.csproj b/build/Build.csproj index c21042f..c055f56 100644 --- a/build/Build.csproj +++ b/build/Build.csproj @@ -8,10 +8,10 @@ - + - + diff --git a/build/Main.cs b/build/Main.cs index a9ccc60..a2f473c 100644 --- a/build/Main.cs +++ b/build/Main.cs @@ -53,7 +53,7 @@ Run("dotnet", "tool restore"); }); - Target(CleanBuildOutput, DependsOn(Restore), () => + Target(CleanBuildOutput, [Restore], () => { Run("dotnet", "clean -c Release -v m --nologo"); }); @@ -129,17 +129,17 @@ } }); - Target(Format, DependsOn(Restore), () => + Target(Format, [Restore], () => { Run("dotnet", "format"); }); - Target(Build, DependsOn(DownloadProtos, CleanBuildOutput, Format), () => + Target(Build, [DownloadProtos, CleanBuildOutput, Format], () => { Run("dotnet", "build -c Release --nologo"); }); - Target(Test, DependsOn(Build), () => + Target(Test, [Build], () => { Run("dotnet", "test -c Release --no-build"); }); @@ -150,14 +150,14 @@ Directory.Delete(packOutput, true); }); - Target(Pack, DependsOn(Build, CleanPackOutput), () => + Target(Pack, [Build, CleanPackOutput], () => { var outputDir = Directory.CreateDirectory(packOutput); Run("dotnet", $"pack src/{project}/{project}.csproj -c Release -o \"{outputDir.FullName}\" --no-build --nologo"); }); - Target(Default, DependsOn(Test)); + Target(Default, [Test]); await RunTargetsAndExitAsync(targets, options, messageOnly: ex => ex is SimpleExec.ExitCodeException); }); diff --git a/src/Qdrant.Client/Grpc/Expression.cs b/src/Qdrant.Client/Grpc/Expression.cs index 5e9d5cd..9d05ba8 100644 --- a/src/Qdrant.Client/Grpc/Expression.cs +++ b/src/Qdrant.Client/Grpc/Expression.cs @@ -1,126 +1,126 @@ namespace Qdrant.Client.Grpc { - /// - /// To construct a formula for score boosting. - /// - public partial class Expression - { - /// - /// Implicitly converts a float constant to a new instance of . - /// - public static implicit operator Expression(float constant) => - new() { Constant = constant }; - - /// - /// Implicitly converts a variable name (string) to a new instance of . - /// Used to reference a payload key or a score variable. - /// - public static implicit operator Expression(string variable) => - new() { Variable = variable }; - - /// - /// Implicitly converts a to a new instance of . - /// Evaluates to 1.0 if true, 0.0 if false. - /// - public static implicit operator Expression(Condition condition) => - new() { Condition = condition }; - - /// - /// Implicitly converts a to a new instance of . - /// Represents a geographic distance in meters. - /// - public static implicit operator Expression(GeoDistance geoDistance) => - new() { GeoDistance = geoDistance }; - - /// - /// Converts a date-time constant string to a new instance of . - /// - public static Expression FromDateTime(string dateTime) => - new() { Datetime = dateTime }; - - /// - /// Converts a date-time key string to a new instance of . - /// Used to reference a payload key with date-time values. - /// - public static Expression FromDateTimeKey(string dateTimeKey) => - new() { DatetimeKey = dateTimeKey }; - - /// - /// Converts a to a new instance of . - /// - public static implicit operator Expression(MultExpression mult) => - new() { Mult = mult }; - - /// - /// Implicitly converts a to a new instance of . - /// - public static implicit operator Expression(SumExpression sum) => - new() { Sum = sum }; - - /// - /// Implicitly converts a to a new instance of . - /// - public static implicit operator Expression(DivExpression div) => - new() { Div = div }; - - /// - /// Converts an to a negated expression. - /// - public static Expression FromNeg(Expression expr) => - new() { Neg = expr }; - - /// - /// Converts an to its absolute value. - /// - public static Expression FromAbs(Expression expr) => - new() { Abs = expr }; - - /// - /// Converts an to its square root. - /// - public static Expression FromSqrt(Expression expr) => - new() { Sqrt = expr }; - - /// - /// Implicitly converts a to an . - /// - public static implicit operator Expression(PowExpression pow) => - new() { Pow = pow }; - - /// - /// Converts an to an exponential. - /// - public static Expression FromExp(Expression expr) => - new() { Exp = expr }; - - /// - /// Converts an to a base-10 logarithm. - /// - public static Expression FromLog10(Expression expr) => - new() { Log10 = expr }; - - /// - /// Converts an to a natural logarithm. - /// - public static Expression FromLn(Expression expr) => - new() { Ln = expr }; - - /// - /// Converts an exponential decay parameter to a new instance of . - /// - public static Expression FromExpDecay(DecayParamsExpression decay) => - new() { ExpDecay = decay }; - - /// - /// Converts a Gaussian decay parameter to a new instance of . - /// - public static Expression FromGaussDecay(DecayParamsExpression decay) => - new() { GaussDecay = decay }; - - /// - /// Converts a linear decay parameter to a new instance of . - /// - public static Expression FromLinDecay(DecayParamsExpression decay) => - new() { LinDecay = decay }; - } + /// + /// To construct a formula for score boosting. + /// + public partial class Expression + { + /// + /// Implicitly converts a float constant to a new instance of . + /// + public static implicit operator Expression(float constant) => + new() { Constant = constant }; + + /// + /// Implicitly converts a variable name (string) to a new instance of . + /// Used to reference a payload key or a score variable. + /// + public static implicit operator Expression(string variable) => + new() { Variable = variable }; + + /// + /// Implicitly converts a to a new instance of . + /// Evaluates to 1.0 if true, 0.0 if false. + /// + public static implicit operator Expression(Condition condition) => + new() { Condition = condition }; + + /// + /// Implicitly converts a to a new instance of . + /// Represents a geographic distance in meters. + /// + public static implicit operator Expression(GeoDistance geoDistance) => + new() { GeoDistance = geoDistance }; + + /// + /// Converts a date-time constant string to a new instance of . + /// + public static Expression FromDateTime(string dateTime) => + new() { Datetime = dateTime }; + + /// + /// Converts a date-time key string to a new instance of . + /// Used to reference a payload key with date-time values. + /// + public static Expression FromDateTimeKey(string dateTimeKey) => + new() { DatetimeKey = dateTimeKey }; + + /// + /// Converts a to a new instance of . + /// + public static implicit operator Expression(MultExpression mult) => + new() { Mult = mult }; + + /// + /// Implicitly converts a to a new instance of . + /// + public static implicit operator Expression(SumExpression sum) => + new() { Sum = sum }; + + /// + /// Implicitly converts a to a new instance of . + /// + public static implicit operator Expression(DivExpression div) => + new() { Div = div }; + + /// + /// Converts an to a negated expression. + /// + public static Expression FromNeg(Expression expr) => + new() { Neg = expr }; + + /// + /// Converts an to its absolute value. + /// + public static Expression FromAbs(Expression expr) => + new() { Abs = expr }; + + /// + /// Converts an to its square root. + /// + public static Expression FromSqrt(Expression expr) => + new() { Sqrt = expr }; + + /// + /// Implicitly converts a to an . + /// + public static implicit operator Expression(PowExpression pow) => + new() { Pow = pow }; + + /// + /// Converts an to an exponential. + /// + public static Expression FromExp(Expression expr) => + new() { Exp = expr }; + + /// + /// Converts an to a base-10 logarithm. + /// + public static Expression FromLog10(Expression expr) => + new() { Log10 = expr }; + + /// + /// Converts an to a natural logarithm. + /// + public static Expression FromLn(Expression expr) => + new() { Ln = expr }; + + /// + /// Converts an exponential decay parameter to a new instance of . + /// + public static Expression FromExpDecay(DecayParamsExpression decay) => + new() { ExpDecay = decay }; + + /// + /// Converts a Gaussian decay parameter to a new instance of . + /// + public static Expression FromGaussDecay(DecayParamsExpression decay) => + new() { GaussDecay = decay }; + + /// + /// Converts a linear decay parameter to a new instance of . + /// + public static Expression FromLinDecay(DecayParamsExpression decay) => + new() { LinDecay = decay }; + } } diff --git a/src/Qdrant.Client/Qdrant.Client.csproj b/src/Qdrant.Client/Qdrant.Client.csproj index 5ad71f6..b115010 100644 --- a/src/Qdrant.Client/Qdrant.Client.csproj +++ b/src/Qdrant.Client/Qdrant.Client.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj b/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj index 55e9627..24f42bf 100644 --- a/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj +++ b/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj @@ -9,11 +9,11 @@ - + - - + + all runtime; build; native; contentfiles; analyzers @@ -22,7 +22,7 @@ - + From d573d1649040c86597933ed2c71a669db4869369 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 19 May 2025 15:44:23 +1000 Subject: [PATCH 3/7] Use Testcontainers.Qdrant Use the released Testcontainers.Qdrant package, removing the need to maintain a custom Testcontainer. --- .../Container/QdrantBuilder.cs | 55 ------------------- .../Container/QdrantConfiguration.cs | 52 ------------------ .../Container/QdrantContainer.cs | 11 ---- .../Grpc/GrpcHealthTests.cs | 3 - .../Qdrant.Client.Tests.csproj | 1 + tests/Qdrant.Client.Tests/QdrantFixture.cs | 16 ++++-- .../QdrantSecuredFixture.cs | 13 +++-- 7 files changed, 21 insertions(+), 130 deletions(-) delete mode 100644 tests/Qdrant.Client.Tests/Container/QdrantBuilder.cs delete mode 100644 tests/Qdrant.Client.Tests/Container/QdrantConfiguration.cs delete mode 100644 tests/Qdrant.Client.Tests/Container/QdrantContainer.cs diff --git a/tests/Qdrant.Client.Tests/Container/QdrantBuilder.cs b/tests/Qdrant.Client.Tests/Container/QdrantBuilder.cs deleted file mode 100644 index bc682aa..0000000 --- a/tests/Qdrant.Client.Tests/Container/QdrantBuilder.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Docker.DotNet.Models; -using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Configurations; -using Qdrant.Client.Grpc; - -namespace Qdrant.Client.Tests.Container; - -public sealed class QdrantBuilder : ContainerBuilder -{ - public const string QdrantImage = "qdrant/qdrant:" + QdrantGrpcClient.QdrantVersion; - - public const ushort QdrantHttpPort = 6333; - - public const ushort QdrantGrpcPort = 6334; - - public QdrantBuilder() : this(new QdrantConfiguration()) => - DockerResourceConfiguration = Init().DockerResourceConfiguration; - - private QdrantBuilder(QdrantConfiguration dockerResourceConfiguration) : base(dockerResourceConfiguration) => - DockerResourceConfiguration = dockerResourceConfiguration; - - public QdrantBuilder WithConfigFile(string configPath) => - Merge(DockerResourceConfiguration, new QdrantConfiguration()) - .WithBindMount(configPath, "/qdrant/config/custom_config.yaml"); - - public QdrantBuilder WithCertificate(string certPath, string keyPath) => - Merge(DockerResourceConfiguration, new QdrantConfiguration()) - .WithBindMount(certPath, "/qdrant/tls/cert.pem") - .WithBindMount(keyPath, "/qdrant/tls/key.pem"); - - public override QdrantContainer Build() - { - Validate(); - return new QdrantContainer(DockerResourceConfiguration); - } - - protected override QdrantBuilder Init() => - base.Init() - .WithImage(QdrantImage) - .WithPortBinding(QdrantHttpPort, true) - .WithPortBinding(QdrantGrpcPort, true) - .WithWaitStrategy(Wait.ForUnixContainer() - .UntilMessageIsLogged(".*Actix runtime found; starting in Actix runtime.*")); - - protected override QdrantBuilder Clone(IResourceConfiguration resourceConfiguration) => - Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration)); - - protected override QdrantBuilder Merge(QdrantConfiguration oldValue, QdrantConfiguration newValue) => - new(new QdrantConfiguration(oldValue, newValue)); - - protected override QdrantConfiguration DockerResourceConfiguration { get; } - - protected override QdrantBuilder Clone(IContainerConfiguration resourceConfiguration) => - Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration)); -} diff --git a/tests/Qdrant.Client.Tests/Container/QdrantConfiguration.cs b/tests/Qdrant.Client.Tests/Container/QdrantConfiguration.cs deleted file mode 100644 index 743e82f..0000000 --- a/tests/Qdrant.Client.Tests/Container/QdrantConfiguration.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Docker.DotNet.Models; -using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Configurations; - -namespace Qdrant.Client.Tests.Container; - -public sealed class QdrantConfiguration : ContainerConfiguration -{ - /// - /// Initializes a new instance of the class. - /// - public QdrantConfiguration() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public QdrantConfiguration(IResourceConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public QdrantConfiguration(IContainerConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public QdrantConfiguration(QdrantConfiguration resourceConfiguration) - : this(new QdrantConfiguration(), resourceConfiguration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The old Docker resource configuration. - /// The new Docker resource configuration. - public QdrantConfiguration(QdrantConfiguration oldValue, QdrantConfiguration newValue) - : base(oldValue, newValue) - { - } -} diff --git a/tests/Qdrant.Client.Tests/Container/QdrantContainer.cs b/tests/Qdrant.Client.Tests/Container/QdrantContainer.cs deleted file mode 100644 index 4434087..0000000 --- a/tests/Qdrant.Client.Tests/Container/QdrantContainer.cs +++ /dev/null @@ -1,11 +0,0 @@ -using DotNet.Testcontainers.Containers; -using Microsoft.Extensions.Logging; - -namespace Qdrant.Client.Tests.Container; - -public class QdrantContainer : DockerContainer -{ - public QdrantContainer(QdrantConfiguration configuration) : base(configuration) - { - } -} diff --git a/tests/Qdrant.Client.Tests/Grpc/GrpcHealthTests.cs b/tests/Qdrant.Client.Tests/Grpc/GrpcHealthTests.cs index c3b8a97..386ad1f 100644 --- a/tests/Qdrant.Client.Tests/Grpc/GrpcHealthTests.cs +++ b/tests/Qdrant.Client.Tests/Grpc/GrpcHealthTests.cs @@ -1,7 +1,4 @@ using FluentAssertions; -using Qdrant.Client; -using Qdrant.Client.Grpc; -using Qdrant.Client.Tests; using Xunit; namespace Qdrant.Client.Grpc; diff --git a/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj b/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj index 24f42bf..7a86765 100644 --- a/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj +++ b/tests/Qdrant.Client.Tests/Qdrant.Client.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Qdrant.Client.Tests/QdrantFixture.cs b/tests/Qdrant.Client.Tests/QdrantFixture.cs index 8f9039f..bf654d9 100644 --- a/tests/Qdrant.Client.Tests/QdrantFixture.cs +++ b/tests/Qdrant.Client.Tests/QdrantFixture.cs @@ -4,8 +4,9 @@ using Grpc.Net.Client; #endif +using System.Text; using Qdrant.Client.Grpc; -using Qdrant.Client.Tests.Container; +using Testcontainers.Qdrant; using Xunit; namespace Qdrant.Client; @@ -21,14 +22,19 @@ public QdrantFixture() => #if NETFRAMEWORK // .NET Framework must use TLS with HTTPS _container = new QdrantBuilder() - .WithConfigFile(Path.Combine(SolutionPaths.Root, "tests/config.yaml")) + .WithImage("qdrant/qdrant:" + QdrantGrpcClient.QdrantVersion) + .WithBindMount( + Path.Combine(SolutionPaths.Root, "tests/config.yaml"), + "/qdrant/config/custom_config.yaml") .WithCertificate( - Path.Combine(SolutionPaths.Root, "tests/cert.pem"), - Path.Combine(SolutionPaths.Root, "tests/key.pem")) + File.ReadAllText(Path.Combine(SolutionPaths.Root, "tests/cert.pem"), Encoding.UTF8), + File.ReadAllText(Path.Combine(SolutionPaths.Root, "tests/key.pem"), Encoding.UTF8)) .WithCommand("./entrypoint.sh", "--config-path", "config/custom_config.yaml") .Build(); #else - _container = new QdrantBuilder().Build(); + _container = new QdrantBuilder() + .WithImage("qdrant/qdrant:" + QdrantGrpcClient.QdrantVersion) + .Build(); #endif diff --git a/tests/Qdrant.Client.Tests/QdrantSecuredFixture.cs b/tests/Qdrant.Client.Tests/QdrantSecuredFixture.cs index 32a2a84..509e138 100644 --- a/tests/Qdrant.Client.Tests/QdrantSecuredFixture.cs +++ b/tests/Qdrant.Client.Tests/QdrantSecuredFixture.cs @@ -1,4 +1,6 @@ -using Qdrant.Client.Tests.Container; +using System.Text; +using Qdrant.Client.Grpc; +using Testcontainers.Qdrant; using Xunit; namespace Qdrant.Client; @@ -9,10 +11,13 @@ public sealed class QdrantSecuredCollection : ICollectionFixture Date: Sat, 24 May 2025 19:21:59 +1000 Subject: [PATCH 4/7] Retrieve single PointId This commit adds an overloaded method to retrieve a single point with `PointId`. This allows ids returned from other APIs to be passed easily in subsequent calls. --- src/Qdrant.Client/Grpc/PointId.cs | 2 +- src/Qdrant.Client/QdrantClient.cs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Qdrant.Client/Grpc/PointId.cs b/src/Qdrant.Client/Grpc/PointId.cs index 3aa512d..32058fc 100644 --- a/src/Qdrant.Client/Grpc/PointId.cs +++ b/src/Qdrant.Client/Grpc/PointId.cs @@ -6,7 +6,7 @@ namespace Qdrant.Client.Grpc; public partial class PointId { /// - /// Implicitly converts a ulong to a new instance of + /// Implicitly converts an to a new instance of /// /// the id /// a new instance of diff --git a/src/Qdrant.Client/QdrantClient.cs b/src/Qdrant.Client/QdrantClient.cs index ecb3dd8..e07a6bf 100644 --- a/src/Qdrant.Client/QdrantClient.cs +++ b/src/Qdrant.Client/QdrantClient.cs @@ -1184,6 +1184,35 @@ private async Task DeleteAsync( } } + /// + /// Retrieve a point. + /// + /// The name of the collection. + /// The ID of a point to retrieve. + /// Whether to include the payload or not. + /// Whether to include the vectors or not. + /// Options for specifying read consistency guarantees. + /// Option for custom sharding to specify used shard keys. + /// + /// The token to monitor for cancellation requests. The default value is . + /// + public Task> RetrieveAsync( + string collectionName, + PointId id, + bool withPayload = true, + bool withVectors = false, + ReadConsistency? readConsistency = null, + ShardKeySelector? shardKeySelector = null, + CancellationToken cancellationToken = default) + => RetrieveAsync( + collectionName, + new[] { id }, + new WithPayloadSelector { Enable = withPayload }, + new WithVectorsSelector { Enable = withVectors }, + readConsistency, + shardKeySelector, + cancellationToken); + /// /// Retrieve a point. /// From da446f676c295eb2883a570a9e0e33090ed11eaf Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Sat, 24 May 2025 19:34:44 +1000 Subject: [PATCH 5/7] Delete overloads accepting points This commit adds Delete overloads to be delete points with a single of list of `PointId`. This allows ids returned from other APIs to be passed easily in subsequent calls. --- src/Qdrant.Client/QdrantClient.cs | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/Qdrant.Client/QdrantClient.cs b/src/Qdrant.Client/QdrantClient.cs index e07a6bf..6c255ff 100644 --- a/src/Qdrant.Client/QdrantClient.cs +++ b/src/Qdrant.Client/QdrantClient.cs @@ -1123,6 +1123,56 @@ public Task DeleteAsync( return DeleteAsync(collectionName, new PointsSelector { Points = idsList }, wait, ordering, shardKeySelector, cancellationToken); } + /// + /// Delete a point. + /// + /// The name of the collection. + /// The IDs to delete. + /// Whether to wait until the changes have been applied. Defaults to true. + /// Write ordering guarantees. Defaults to Weak. + /// Option for custom sharding to specify used shard keys. + /// + /// The token to monitor for cancellation requests. The default value is . + /// + public Task DeleteAsync( + string collectionName, + PointId id, + bool wait = true, + WriteOrderingType? ordering = null, + ShardKeySelector? shardKeySelector = null, + CancellationToken cancellationToken = default) + => DeleteAsync(collectionName, + new PointsSelector { Points = new PointsIdsList { Ids = { id } } }, + wait, + ordering, + shardKeySelector, + cancellationToken); + + /// + /// Delete points. + /// + /// The name of the collection. + /// The IDs to delete. + /// Whether to wait until the changes have been applied. Defaults to true. + /// Write ordering guarantees. Defaults to Weak. + /// Option for custom sharding to specify used shard keys. + /// + /// The token to monitor for cancellation requests. The default value is . + /// + public Task DeleteAsync( + string collectionName, + IReadOnlyList ids, + bool wait = true, + WriteOrderingType? ordering = null, + ShardKeySelector? shardKeySelector = null, + CancellationToken cancellationToken = default) + { + var idsList = new PointsIdsList(); + idsList.Ids.AddRange(ids); + + return DeleteAsync(collectionName, new PointsSelector { Points = idsList }, wait, ordering, shardKeySelector, cancellationToken); + } + /// /// Delete points. /// From ade31646c1a2200a2f412a0cf8a8beca1253d856 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Sat, 24 May 2025 20:11:58 +1000 Subject: [PATCH 6/7] Implement Dispose pattern This commit updates the Dispose implementations of the low level and high level client. Since both are public and not sealed, they can be inherited from, and therefore should expose an overrideable Dispose method. --- src/Qdrant.Client/Grpc/QdrantGrpcClient.cs | 21 +++++++++++++++++---- src/Qdrant.Client/QdrantClient.cs | 21 ++++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/Qdrant.Client/Grpc/QdrantGrpcClient.cs b/src/Qdrant.Client/Grpc/QdrantGrpcClient.cs index d87bc2a..20454ce 100644 --- a/src/Qdrant.Client/Grpc/QdrantGrpcClient.cs +++ b/src/Qdrant.Client/Grpc/QdrantGrpcClient.cs @@ -85,10 +85,23 @@ protected QdrantGrpcClient() : this(new UnimplementedCallInvoker()) /// public void Dispose() { - if (_isDisposed) - return; + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the resources used by the current instance of . + /// + /// Indicates whether the method is called from + /// or a finalizer. + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + _isDisposed = true; - _ownedChannel?.Dispose(); - _isDisposed = true; + if (disposing) + _ownedChannel?.Dispose(); + } } } diff --git a/src/Qdrant.Client/QdrantClient.cs b/src/Qdrant.Client/QdrantClient.cs index 6c255ff..d36d19b 100644 --- a/src/Qdrant.Client/QdrantClient.cs +++ b/src/Qdrant.Client/QdrantClient.cs @@ -4631,12 +4631,23 @@ private static ulong ConvertTimeout(TimeSpan? timeout) /// public void Dispose() { - if (_isDisposed) - return; + Dispose(true); + GC.SuppressFinalize(this); + } - if (_ownsGrpcClient) - _grpcClient.Dispose(); + /// + /// Releases the resources used by the current instance of . + /// + /// Indicates whether the method is called from + /// or a finalizer. + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + _isDisposed = true; - _isDisposed = true; + if (disposing && _ownsGrpcClient) + _grpcClient.Dispose(); + } } } From f0e1c3a2bede1c0aa0a8f9af4339c1436dbaa2ac Mon Sep 17 00:00:00 2001 From: Anush Date: Mon, 23 Jun 2025 15:29:45 +0530 Subject: [PATCH 7/7] chore: Cloud inference helpers (#96) * chore: Cloud inference helpers Signed-off-by: Anush008 * chore: Bump version Signed-off-by: Anush008 * chore: Review updates Signed-off-by: Anush008 --------- Signed-off-by: Anush008 --- Directory.Build.props | 2 +- src/Qdrant.Client/Grpc/PointId.cs | 2 +- src/Qdrant.Client/Grpc/Query.cs | 28 ++++++++++++-- src/Qdrant.Client/Grpc/Vector.cs | 31 +++++++++++++++ src/Qdrant.Client/Grpc/VectorInput.cs | 54 +++++++++++++-------------- src/Qdrant.Client/Grpc/Vectors.cs | 31 +++++++++++++++ 6 files changed, 115 insertions(+), 33 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index d702f67..a5642b3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ https://github.com/qdrant/qdrant-dotnet https://github.com/qdrant/qdrant-dotnet/releases qdrant, database, vector, search - v1.14.0 + v1.14.1 diff --git a/src/Qdrant.Client/Grpc/PointId.cs b/src/Qdrant.Client/Grpc/PointId.cs index 32058fc..08c4503 100644 --- a/src/Qdrant.Client/Grpc/PointId.cs +++ b/src/Qdrant.Client/Grpc/PointId.cs @@ -6,7 +6,7 @@ namespace Qdrant.Client.Grpc; public partial class PointId { /// - /// Implicitly converts an to a new instance of + /// Implicitly converts an to a new instance of /// /// the id /// a new instance of diff --git a/src/Qdrant.Client/Grpc/Query.cs b/src/Qdrant.Client/Grpc/Query.cs index a28151f..d14b10f 100644 --- a/src/Qdrant.Client/Grpc/Query.cs +++ b/src/Qdrant.Client/Grpc/Query.cs @@ -96,7 +96,7 @@ public partial class Query /// /// the array of floats /// a new instance of - public static implicit operator Query(float[] values) => (VectorInput)values; + public static implicit operator Query(float[] values) => new() { Nearest = (VectorInput)values }; /// /// Implicitly converts a tuple of sparse values array of @@ -104,14 +104,14 @@ public partial class Query /// /// a tuple of arrays of values and indices /// a new instance of - public static implicit operator Query((float[], uint[]) sparseValues) => (VectorInput)sparseValues; + public static implicit operator Query((float[], uint[]) sparseValues) => new() { Nearest = (VectorInput)sparseValues }; /// /// Implicitly converts an array of sparse value index tuples to a new instance of /// /// the array of value-index pairs /// a new instance of - public static implicit operator Query((float, uint)[] sparseValues) => (VectorInput)sparseValues; + public static implicit operator Query((float, uint)[] sparseValues) => new() { Nearest = (VectorInput)sparseValues }; /// /// Implicitly converts a nested array of representing a multi-vector @@ -119,7 +119,27 @@ public partial class Query /// /// the array of floats /// a new instance of - public static implicit operator Query(float[][] values) => (VectorInput)values; + public static implicit operator Query(float[][] values) => new() { Nearest = (VectorInput)values }; + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to query against + /// a new instance of + public static implicit operator Query(Document document) => new() { Nearest = (VectorInput)document }; + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to query against + public static implicit operator Query(Image image) => new() { Nearest = (VectorInput)image }; + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to query against + /// a new instance of + public static implicit operator Query(InferenceObject inferenceObject) => new() { Nearest = (VectorInput)inferenceObject }; #endregion } diff --git a/src/Qdrant.Client/Grpc/Vector.cs b/src/Qdrant.Client/Grpc/Vector.cs index a1837a9..e584f87 100644 --- a/src/Qdrant.Client/Grpc/Vector.cs +++ b/src/Qdrant.Client/Grpc/Vector.cs @@ -53,4 +53,35 @@ public static implicit operator Vector(float[][] values) }; } + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to vectorize + /// a new instance of + public static implicit operator Vector(Document document) => new() + { + Document = document, + }; + + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to vectorize + /// a new instance of + public static implicit operator Vector(Image image) => new() + { + Image = image, + }; + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to vectorize + /// a new instance of + public static implicit operator Vector(InferenceObject inferenceObject) => new() + { + Object = inferenceObject, + }; } diff --git a/src/Qdrant.Client/Grpc/VectorInput.cs b/src/Qdrant.Client/Grpc/VectorInput.cs index 17a7c72..29db55c 100644 --- a/src/Qdrant.Client/Grpc/VectorInput.cs +++ b/src/Qdrant.Client/Grpc/VectorInput.cs @@ -85,33 +85,33 @@ public static implicit operator VectorInput(float[][] values) Id = id }; - // /// - // /// Implicitly converts a to a new instance of - // /// - // /// an instance of - // /// a new instance of - // public static implicit operator VectorInput(Document document) => new() - // { - // Document = document - // }; + /// + /// Implicitly converts a to a new instance of for cloud inference. + /// + /// an instance of + /// a new instance of + public static implicit operator VectorInput(Document document) => new() + { + Document = document + }; - // /// - // /// Implicitly converts an to a new instance of - // /// - // /// an instance of - // /// a new instance of - // public static implicit operator VectorInput(Image image) => new() - // { - // Image = image - // }; + /// + /// Implicitly converts an to a new instance of for cloud inference. + /// + /// an instance of + /// a new instance of + public static implicit operator VectorInput(Image image) => new() + { + Image = image + }; - // /// - // /// Implicitly converts an to a new instance of - // /// - // /// an instance of - // /// a new instance of - // public static implicit operator VectorInput(InferenceObject obj) => new() - // { - // Object = obj - // }; + /// + /// Implicitly converts an to a new instance of for cloud inference. + /// + /// an instance of + /// a new instance of + public static implicit operator VectorInput(InferenceObject @object) => new() + { + Object = @object + }; } diff --git a/src/Qdrant.Client/Grpc/Vectors.cs b/src/Qdrant.Client/Grpc/Vectors.cs index cd1173a..788128c 100644 --- a/src/Qdrant.Client/Grpc/Vectors.cs +++ b/src/Qdrant.Client/Grpc/Vectors.cs @@ -54,4 +54,35 @@ public static implicit operator Vectors((string, Vector) value) var namedVectors = new NamedVectors { Vectors = { [value.Item1] = value.Item2 } }; return new Vectors { Vectors_ = namedVectors }; } + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to vectorize + /// a new instance of + public static implicit operator Vectors(Document document) => new() + { + Vector = document, + }; + + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to vectorize + /// /// a new instance of + public static implicit operator Vectors(Image image) => new() + { + Vector = image, + }; + + /// + /// Implicitly converts an instance of to a new instance of for cloud inference. + /// + /// An instance of to vectorize + /// a new instance of + public static implicit operator Vectors(InferenceObject inferenceObject) => new() + { + Vector = inferenceObject, + }; }