diff --git a/src/Elastic.Clients.Elasticsearch/Api/UpdateRequestDescriptor.cs b/src/Elastic.Clients.Elasticsearch/Api/UpdateRequestDescriptor.cs deleted file mode 100644 index ee03d57d08c..00000000000 --- a/src/Elastic.Clients.Elasticsearch/Api/UpdateRequestDescriptor.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Elastic.Clients.Elasticsearch -{ - public sealed partial class UpdateRequestDescriptor - { - public UpdateRequestDescriptor Document(TDocument document) - { - DocumentValue = document; - return Self; - } - - public UpdateRequestDescriptor PartialDocument(TPartialDocument document) => this; - } -} diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Api/UpdateRequest.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Api/UpdateRequest.g.cs index 9dc18ec1713..91253832142 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Api/UpdateRequest.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Api/UpdateRequest.g.cs @@ -70,9 +70,6 @@ public UpdateRequest(Elastic.Clients.Elasticsearch.IndexName index, Elastic.Clie internal override ApiUrls ApiUrls => ApiUrlsLookups.NoNamespaceUpdate; protected override HttpMethod HttpMethod => HttpMethod.POST; protected override bool SupportsBody => true; - [JsonIgnore] - public TDocument Document { get; set; } - [JsonIgnore] public long? IfPrimaryTerm { get => Q("if_primary_term"); set => Q("if_primary_term", value); } @@ -133,20 +130,33 @@ public UpdateRequest(Elastic.Clients.Elasticsearch.IndexName index, Elastic.Clie [JsonInclude] [JsonPropertyName("upsert")] + [SourceConverter] public TDocument? Upsert { get; set; } } public sealed partial class UpdateRequestDescriptor : RequestDescriptorBase, UpdateRequestParameters> { internal UpdateRequestDescriptor(Action> configure) => configure.Invoke(this); - internal UpdateRequestDescriptor(Elastic.Clients.Elasticsearch.IndexName index, Elastic.Clients.Elasticsearch.Id id) : base(r => r.Required("index", index).Required("id", id)) + public UpdateRequestDescriptor(Elastic.Clients.Elasticsearch.IndexName index, Elastic.Clients.Elasticsearch.Id id) : base(r => r.Required("index", index).Required("id", id)) + { + } + + public UpdateRequestDescriptor(TDocument document) : this(typeof(TDocument), Elasticsearch.Id.From(document)) + { + } + + public UpdateRequestDescriptor(TDocument document, IndexName index, Id id) : this(index, id) + { + } + + public UpdateRequestDescriptor(TDocument document, IndexName index) : this(index, Elasticsearch.Id.From(document)) + { + } + + public UpdateRequestDescriptor(TDocument document, Id id) : this(typeof(TDocument), id) { } - public UpdateRequestDescriptor(TDocument document) : this(typeof(TDocument), Elasticsearch.Id.From(document)) => DocumentValue = document; - public UpdateRequestDescriptor(TDocument document, IndexName index, Id id) : this(index, id) => DocumentValue = document; - public UpdateRequestDescriptor(TDocument document, IndexName index) : this(index, Elasticsearch.Id.From(document)) => DocumentValue = document; - public UpdateRequestDescriptor(TDocument document, Id id) : this(typeof(TDocument), id) => DocumentValue = document; public UpdateRequestDescriptor(Id id) : this(typeof(TDocument), id) { } @@ -195,8 +205,6 @@ public UpdateRequestDescriptor Index(Elastic.Client private TDocument? UpsertValue { get; set; } - private TDocument DocumentValue { get; set; } - public UpdateRequestDescriptor Source(Elastic.Clients.Elasticsearch.SourceConfig? source) { SourceValue = source; @@ -281,7 +289,7 @@ protected override void Serialize(Utf8JsonWriter writer, JsonSerializerOptions o if (UpsertValue is not null) { writer.WritePropertyName("upsert"); - JsonSerializer.Serialize(writer, UpsertValue, options); + SourceSerialisation.Serialize(UpsertValue, writer, settings); } writer.WriteEndObject(); diff --git a/tests/Tests/Document/Single/Update/UpdateDocumentsCoordinatedTests.cs b/tests/Tests/Document/Single/Update/UpdateDocumentsCoordinatedTests.cs new file mode 100644 index 00000000000..f2ebe4a4926 --- /dev/null +++ b/tests/Tests/Document/Single/Update/UpdateDocumentsCoordinatedTests.cs @@ -0,0 +1,236 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Framework.EndpointTests; +using Tests.Framework.EndpointTests.TestState; +using System.Text.Json.Serialization; + +namespace Tests.Document.Single.Update; + +public class UpdateDocumentsCoordinatedTests : CoordinatedIntegrationTestBase +{ + private const string IndexDocumentStep = nameof(IndexDocumentStep); + private const string GetAfterScriptedUpdateStep = nameof(GetAfterScriptedUpdateStep); + private const string UpdateWithScriptStep = nameof(UpdateWithScriptStep); + private const string UpdateWithPartialStep = nameof(UpdateWithPartialStep); + private const string GetAfterPartialUpdateStep = nameof(GetAfterPartialUpdateStep); + private const string UpdateWithPartialStepTwo = nameof(UpdateWithPartialStepTwo); + private const string UpsertExistingStep = nameof(UpsertExistingStep); + private const string GetAfterUpsertExistingStep = nameof(GetAfterUpsertExistingStep); + private const string UpsertNewStep = nameof(UpsertNewStep); + private const string GetAfterUpsertNewStep = nameof(GetAfterUpsertNewStep); + + private static Script TestScript => new(new InlineScript + { + Source = "ctx._source.counter += params.count", + Language = ScriptLanguage.Painless, + Params = new() { { "count", 4 } } + }); + + public UpdateDocumentsCoordinatedTests(WritableCluster cluster, EndpointUsage usage) : base( + new CoordinatedUsage(cluster, usage) + { + { + IndexDocumentStep, u => + u.Calls, IndexRequest, IndexResponse>( + v => new IndexRequest(UpdateTestDocument.InitialValues, v), + (v, d) => d, + (v, c, f) => c.Index(UpdateTestDocument.InitialValues, Infer.Index(), r => r.Id(v)), + (v, c, f) => c.IndexAsync(UpdateTestDocument.InitialValues, Infer.Index(),r => r.Id(v)), + (_, c, r) => c.Index(r), + (_, c, r) => c.IndexAsync(r) + ) + }, + { + UpdateWithScriptStep, u => + u.Calls, UpdateRequest, UpdateResponse>( + v => new UpdateRequest(typeof(UpdateTestDocument), v) { Script = TestScript }, + (v, d) => d.Script(TestScript), + (v, c, f) => c.Update(Infer.Index(), v, f), + (v, c, f) => c.UpdateAsync(Infer.Index(), v, f), + (_, c, r) => c.Update(r), + (_, c, r) => c.UpdateAsync(r) + ) + }, + { + GetAfterScriptedUpdateStep, u => + u.Calls, GetRequest, GetResponse>( + v => new GetRequest(typeof(UpdateTestDocument), v), + (v, d) => d, + (v, c, f) => c.Get(Infer.Index(), v, f), + (v, c, f) => c.GetAsync(Infer.Index(), v, f), + (_, c, r) => c.Get(r), + (_, c, r) => c.GetAsync(r) + ) + }, + { + UpdateWithPartialStep, u => + u.Calls, UpdateRequest, UpdateResponse>( + v => new UpdateRequest(typeof(UpdateTestDocument), v) { Doc = new UpdateTestDocumentPartial() }, + (v, d) => d.Doc(new UpdateTestDocumentPartial()), + (v, c, f) => c.Update(Infer.Index(), v, f), + (v, c, f) => c.UpdateAsync(Infer.Index(), v, f), + (_, c, r) => c.Update(r), + (_, c, r) => c.UpdateAsync(r) + ) + }, + { + GetAfterPartialUpdateStep, u => + u.Calls, GetRequest, GetResponse>( + v => new GetRequest(typeof(UpdateTestDocument), v), + (v, d) => d, + (v, c, f) => c.Get(Infer.Index(), v, f), + (v, c, f) => c.GetAsync(Infer.Index(), v, f), + (_, c, r) => c.Get(r), + (_, c, r) => c.GetAsync(r) + ) + }, + { + UpdateWithPartialStepTwo, u => + u.Calls, UpdateRequest, UpdateResponse>( + v => new UpdateRequest(typeof(UpdateTestDocument), v) { Doc = new UpdateTestDocumentPartial() }, + (v, d) => d.Doc(new UpdateTestDocumentPartial()), + (v, c, f) => c.Update(Infer.Index(), v, f), + (v, c, f) => c.UpdateAsync(Infer.Index(), v, f), + (_, c, r) => c.Update(r), + (_, c, r) => c.UpdateAsync(r) + ) + }, + { + UpsertExistingStep, u => + u.Calls, UpdateRequest, UpdateResponse>( + v => new UpdateRequest(typeof(UpdateTestDocument), v) { Script = TestScript, Upsert = new UpdateTestDocument { Counter = 100, Name = "Newly inserted" } }, + (v, d) => d.Script(TestScript).Upsert(new UpdateTestDocument { Counter = 100, Name = "Newly inserted" }), + (v, c, f) => c.Update(Infer.Index(), v, f), + (v, c, f) => c.UpdateAsync(Infer.Index(), v, f), + (_, c, r) => c.Update(r), + (_, c, r) => c.UpdateAsync(r) + ) + }, + { + GetAfterUpsertExistingStep, u => + u.Calls, GetRequest, GetResponse>( + v => new GetRequest(typeof(UpdateTestDocument), v), + (v, d) => d, + (v, c, f) => c.Get(Infer.Index(), v, f), + (v, c, f) => c.GetAsync(Infer.Index(), v, f), + (_, c, r) => c.Get(r), + (_, c, r) => c.GetAsync(r) + ) + }, + { + UpsertNewStep, u => + u.Calls, UpdateRequest, UpdateResponse>( + v => new UpdateRequest(typeof(UpdateTestDocument), $"{v}-new") { Script = TestScript, Upsert = new UpdateTestDocument { Counter = 100, Name = "Newly inserted" } }, + (v, d) => d.Script(TestScript).Upsert(new UpdateTestDocument { Counter = 100, Name = "Newly inserted" }), + (v, c, f) => c.Update(Infer.Index(), $"{v}-new", f), + (v, c, f) => c.UpdateAsync(Infer.Index(), $"{v}-new", f), + (_, c, r) => c.Update(r), + (_, c, r) => c.UpdateAsync(r) + ) + }, + { + GetAfterUpsertNewStep, u => + u.Calls, GetRequest, GetResponse>( + v => new GetRequest(typeof(UpdateTestDocument), $"{v}-new"), + (v, d) => d, + (v, c, f) => c.Get(Infer.Index(), $"{v}-new", f), + (v, c, f) => c.GetAsync(Infer.Index(), $"{v}-new", f), + (_, c, r) => c.Get(r), + (_, c, r) => c.GetAsync(r) + ) + }, + }) + { + } + + [I] + public async Task UpdateWithScriptResponse() => await Assert>(UpdateWithScriptStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Result.Should().Be(Result.Updated); + }); + + [I] + public async Task GetResponseAfterScriptedUpdateStep() => await Assert>(GetAfterScriptedUpdateStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Source.Name.Should().Be("Initial"); // The name should not have changed + r.Source.Counter.Should().Be(5); // The script updates by 4 + r.Source.RenamedField.Should().BeNull(); // This hasn't been set yet and should not exist on the source + }); + + [I] + public async Task UpdateWithPartialResponse() => await Assert>(UpdateWithPartialStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Result.Should().Be(Result.Updated); + }); + + [I] + public async Task GetResponseAfterPartialUpdateStep() => await Assert>(GetAfterPartialUpdateStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Source.Name.Should().Be("Initial"); // The name should not have changed + r.Source.Counter.Should().Be(5); // The count should still be five + r.Source.RenamedField.Should().Be("Partial"); // The partial update should have added the partial value for RenamedField + }); + + [I] + public async Task UpdateWithPartialNoOpResponse() => await Assert>(UpdateWithPartialStepTwo, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Result.Should().Be(Result.NoOp); + }); + + [I] + public async Task UpsertExistingResponse() => await Assert>(UpsertExistingStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Result.Should().Be(Result.Updated); + }); + + [I] + public async Task GetResponseAfterUpsertExistingStep() => await Assert>(GetAfterUpsertExistingStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Source.Name.Should().Be("Initial"); // The name should not have changed + r.Source.Counter.Should().Be(9); // The count should have been updated by the script + r.Source.RenamedField.Should().Be("Partial"); // The RenamedField should not have changed + }); + + [I] + public async Task UpsertNewResponse() => await Assert>(UpsertNewStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Result.Should().Be(Result.Created); + }); + + [I] + public async Task GetResponseAfterUpsertNewStep() => await Assert>(GetAfterUpsertNewStep, (v, r) => + { + r.IsValid.Should().BeTrue(); + r.Source.Name.Should().Be("Newly inserted"); + r.Source.Counter.Should().Be(100); + r.Source.RenamedField.Should().BeNull(); + }); + + private class UpdateTestDocument + { + [JsonPropertyName("another_field")] + public string RenamedField { get; set; } + public int Counter { get; set; } + public string Name { get; set; } + + public static UpdateTestDocument InitialValues => new() { Counter = 1, Name = "Initial" }; + } + + private class UpdateTestDocumentPartial + { + [JsonPropertyName("another_field")] + public string RenamedField { get; set; } = "Partial"; + } +}