From 1a7bef614079eb3d66013f14b62c8f683db4bddb Mon Sep 17 00:00:00 2001 From: Florian Bernd Date: Mon, 3 Nov 2025 11:10:30 +0100 Subject: [PATCH] Fix deserialization of `SqlRow` values (#8767) (cherry picked from commit 084ed704b4b44d6b61b6eae9ff9d7cbf422bf20e) --- .../Api/Sql/GetAsyncResponse.Converters.g.cs | 11 ++++++- .../_Generated/Api/Sql/GetAsyncResponse.g.cs | 13 +++++++- .../Api/Sql/QueryResponse.Converters.g.cs | 11 ++++++- .../_Generated/Api/Sql/QueryResponse.g.cs | 13 +++++++- .../_Shared/Api/Sql/GetAsyncResponse.cs | 15 --------- .../_Shared/Api/Sql/QueryResponse.cs | 15 --------- .../_Shared/Core/LazyJson.cs | 18 ++++++----- .../_Shared/Core/LazyJsonConverter.cs | 31 +++++++++---------- .../_Shared/Types/Sql/SqlRowConverter.cs | 20 ++---------- 9 files changed, 72 insertions(+), 75 deletions(-) delete mode 100644 src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/GetAsyncResponse.cs delete mode 100644 src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/QueryResponse.cs diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.Converters.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.Converters.g.cs index f0b1af35415..b3705829e6d 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.Converters.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.Converters.g.cs @@ -30,6 +30,7 @@ public sealed partial class GetAsyncResponseConverter : System.Text.Json.Seriali private static readonly System.Text.Json.JsonEncodedText PropId = System.Text.Json.JsonEncodedText.Encode("id"); private static readonly System.Text.Json.JsonEncodedText PropIsPartial = System.Text.Json.JsonEncodedText.Encode("is_partial"); private static readonly System.Text.Json.JsonEncodedText PropIsRunning = System.Text.Json.JsonEncodedText.Encode("is_running"); + private static readonly System.Text.Json.JsonEncodedText PropRows = System.Text.Json.JsonEncodedText.Encode("rows"); public override Elastic.Clients.Elasticsearch.Sql.GetAsyncResponse Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { @@ -39,6 +40,7 @@ public override Elastic.Clients.Elasticsearch.Sql.GetAsyncResponse Read(ref Syst LocalJsonValue propId = default; LocalJsonValue propIsPartial = default; LocalJsonValue propIsRunning = default; + LocalJsonValue> propRows = default; while (reader.Read() && reader.TokenType is System.Text.Json.JsonTokenType.PropertyName) { if (propColumns.TryReadProperty(ref reader, options, PropColumns, static System.Collections.Generic.IReadOnlyCollection? (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => r.ReadCollectionValue(o, null))) @@ -66,6 +68,11 @@ public override Elastic.Clients.Elasticsearch.Sql.GetAsyncResponse Read(ref Syst continue; } + if (propRows.TryReadProperty(ref reader, options, PropRows, static System.Collections.Generic.IReadOnlyCollection (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => r.ReadCollectionValue(o, null)!)) + { + continue; + } + if (options.UnmappedMemberHandling is System.Text.Json.Serialization.JsonUnmappedMemberHandling.Skip) { reader.SafeSkip(); @@ -82,7 +89,8 @@ public override Elastic.Clients.Elasticsearch.Sql.GetAsyncResponse Read(ref Syst Cursor = propCursor.Value, Id = propId.Value, IsPartial = propIsPartial.Value, - IsRunning = propIsRunning.Value + IsRunning = propIsRunning.Value, + Rows = propRows.Value }; } @@ -94,6 +102,7 @@ public override void Write(System.Text.Json.Utf8JsonWriter writer, Elastic.Clien writer.WriteProperty(options, PropId, value.Id, null, null); writer.WriteProperty(options, PropIsPartial, value.IsPartial, null, null); writer.WriteProperty(options, PropIsRunning, value.IsRunning, null, null); + writer.WriteProperty(options, PropRows, value.Rows, null, static (System.Text.Json.Utf8JsonWriter w, System.Text.Json.JsonSerializerOptions o, System.Collections.Generic.IReadOnlyCollection v) => w.WriteCollectionValue(o, v, null)); writer.WriteEndObject(); } } \ No newline at end of file diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.g.cs index a37b797e8ce..833159e0b75 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/GetAsyncResponse.g.cs @@ -24,7 +24,7 @@ namespace Elastic.Clients.Elasticsearch.Sql; [System.Text.Json.Serialization.JsonConverter(typeof(Elastic.Clients.Elasticsearch.Sql.Json.GetAsyncResponseConverter))] -public partial class GetAsyncResponse : Elastic.Transport.Products.Elasticsearch.ElasticsearchResponse +public sealed partial class GetAsyncResponse : Elastic.Transport.Products.Elasticsearch.ElasticsearchResponse { [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] public GetAsyncResponse() @@ -93,4 +93,15 @@ internal GetAsyncResponse(Elastic.Clients.Elasticsearch.Serialization.JsonConstr required #endif bool IsRunning { get; set; } + + /// + /// + /// The values for the search results. + /// + /// + public +#if NET7_0_OR_GREATER + required +#endif + System.Collections.Generic.IReadOnlyCollection Rows { get; set; } } \ No newline at end of file diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.Converters.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.Converters.g.cs index 28090fdd3da..17bda1c4f4d 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.Converters.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.Converters.g.cs @@ -30,6 +30,7 @@ public sealed partial class QueryResponseConverter : System.Text.Json.Serializat private static readonly System.Text.Json.JsonEncodedText PropId = System.Text.Json.JsonEncodedText.Encode("id"); private static readonly System.Text.Json.JsonEncodedText PropIsPartial = System.Text.Json.JsonEncodedText.Encode("is_partial"); private static readonly System.Text.Json.JsonEncodedText PropIsRunning = System.Text.Json.JsonEncodedText.Encode("is_running"); + private static readonly System.Text.Json.JsonEncodedText PropRows = System.Text.Json.JsonEncodedText.Encode("rows"); public override Elastic.Clients.Elasticsearch.Sql.QueryResponse Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { @@ -39,6 +40,7 @@ public override Elastic.Clients.Elasticsearch.Sql.QueryResponse Read(ref System. LocalJsonValue propId = default; LocalJsonValue propIsPartial = default; LocalJsonValue propIsRunning = default; + LocalJsonValue> propRows = default; while (reader.Read() && reader.TokenType is System.Text.Json.JsonTokenType.PropertyName) { if (propColumns.TryReadProperty(ref reader, options, PropColumns, static System.Collections.Generic.IReadOnlyCollection? (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => r.ReadCollectionValue(o, null))) @@ -66,6 +68,11 @@ public override Elastic.Clients.Elasticsearch.Sql.QueryResponse Read(ref System. continue; } + if (propRows.TryReadProperty(ref reader, options, PropRows, static System.Collections.Generic.IReadOnlyCollection (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => r.ReadCollectionValue(o, null)!)) + { + continue; + } + if (options.UnmappedMemberHandling is System.Text.Json.Serialization.JsonUnmappedMemberHandling.Skip) { reader.SafeSkip(); @@ -82,7 +89,8 @@ public override Elastic.Clients.Elasticsearch.Sql.QueryResponse Read(ref System. Cursor = propCursor.Value, Id = propId.Value, IsPartial = propIsPartial.Value, - IsRunning = propIsRunning.Value + IsRunning = propIsRunning.Value, + Rows = propRows.Value }; } @@ -94,6 +102,7 @@ public override void Write(System.Text.Json.Utf8JsonWriter writer, Elastic.Clien writer.WriteProperty(options, PropId, value.Id, null, null); writer.WriteProperty(options, PropIsPartial, value.IsPartial, null, static (System.Text.Json.Utf8JsonWriter w, System.Text.Json.JsonSerializerOptions o, bool? v) => w.WriteNullableValue(o, v)); writer.WriteProperty(options, PropIsRunning, value.IsRunning, null, static (System.Text.Json.Utf8JsonWriter w, System.Text.Json.JsonSerializerOptions o, bool? v) => w.WriteNullableValue(o, v)); + writer.WriteProperty(options, PropRows, value.Rows, null, static (System.Text.Json.Utf8JsonWriter w, System.Text.Json.JsonSerializerOptions o, System.Collections.Generic.IReadOnlyCollection v) => w.WriteCollectionValue(o, v, null)); writer.WriteEndObject(); } } \ No newline at end of file diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.g.cs index 114d6089418..058ee5cd0d8 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Api/Sql/QueryResponse.g.cs @@ -24,7 +24,7 @@ namespace Elastic.Clients.Elasticsearch.Sql; [System.Text.Json.Serialization.JsonConverter(typeof(Elastic.Clients.Elasticsearch.Sql.Json.QueryResponseConverter))] -public partial class QueryResponse : Elastic.Transport.Products.Elasticsearch.ElasticsearchResponse +public sealed partial class QueryResponse : Elastic.Transport.Products.Elasticsearch.ElasticsearchResponse { [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] public QueryResponse() @@ -81,4 +81,15 @@ internal QueryResponse(Elastic.Clients.Elasticsearch.Serialization.JsonConstruct /// /// public bool? IsRunning { get; set; } + + /// + /// + /// The values for the search results. + /// + /// + public +#if NET7_0_OR_GREATER + required +#endif + System.Collections.Generic.IReadOnlyCollection Rows { get; set; } } \ No newline at end of file diff --git a/src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/GetAsyncResponse.cs b/src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/GetAsyncResponse.cs deleted file mode 100644 index 0ef4200ceb6..00000000000 --- a/src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/GetAsyncResponse.cs +++ /dev/null @@ -1,15 +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. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Elastic.Clients.Elasticsearch.Sql; - -public partial class GetAsyncResponse -{ - [JsonInclude] - [JsonPropertyName("rows")] - public IReadOnlyCollection Rows { get; init; } -} diff --git a/src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/QueryResponse.cs b/src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/QueryResponse.cs deleted file mode 100644 index b8b0f77dbc3..00000000000 --- a/src/Elastic.Clients.Elasticsearch/_Shared/Api/Sql/QueryResponse.cs +++ /dev/null @@ -1,15 +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. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Elastic.Clients.Elasticsearch.Sql; - -public partial class QueryResponse -{ - [JsonInclude] - [JsonPropertyName("rows")] - public IReadOnlyCollection Rows { get; init; } -} diff --git a/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJson.cs b/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJson.cs index 6554e86f3a7..dd3df743180 100644 --- a/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJson.cs +++ b/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJson.cs @@ -3,8 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Text.Json; using System.Text.Json.Serialization; +using Elastic.Transport.Extensions; + namespace Elastic.Clients.Elasticsearch; /// @@ -14,13 +17,13 @@ namespace Elastic.Clients.Elasticsearch; [JsonConverter(typeof(Json.LazyJsonConverter))] public readonly struct LazyJson { - internal LazyJson(byte[] bytes, IElasticsearchClientSettings settings) + internal LazyJson(JsonElement json, IElasticsearchClientSettings? settings) { - Bytes = bytes; + Json = json; Settings = settings; } - internal byte[]? Bytes { get; } + internal JsonElement Json { get; } internal IElasticsearchClientSettings? Settings { get; } /// @@ -30,10 +33,11 @@ internal LazyJson(byte[] bytes, IElasticsearchClientSettings settings) /// The type public T? As() { - if (Bytes is null || Settings is null || Bytes.Length == 0) - return default; + if (Settings is null) + { + throw new InvalidOperationException($"Can not deserialize value without '{nameof(Settings)}'."); + } - using var ms = Settings.MemoryStreamFactory.Create(Bytes); - return Settings.SourceSerializer.Deserialize(ms); + return Settings.SourceSerializer.Deserialize(Json); } } diff --git a/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJsonConverter.cs b/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJsonConverter.cs index d14d41919ab..5291695a874 100644 --- a/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJsonConverter.cs +++ b/src/Elastic.Clients.Elasticsearch/_Shared/Core/LazyJsonConverter.cs @@ -12,35 +12,32 @@ namespace Elastic.Clients.Elasticsearch.Json; public sealed class LazyJsonConverter : JsonConverter { - private IElasticsearchClientSettings _settings; + private IElasticsearchClientSettings? _settings; public override LazyJson Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { InitializeSettings(options); - // TODO: fixme -#pragma warning disable IL2026, IL3050 - using var jsonDoc = JsonSerializer.Deserialize(ref reader); +#pragma warning disable IL2026, IL3050 // The `TypeInfoResolver` for `RequestResponseConverter` knows how to handle `JsonElement`. + return new LazyJson(JsonSerializer.Deserialize(ref reader, options), _settings!); #pragma warning restore IL2026, IL3050 - using var stream = _settings.MemoryStreamFactory.Create(); - - var writer = new Utf8JsonWriter(stream); - jsonDoc.WriteTo(writer); - writer.Flush(); - - return new LazyJson(stream.ToArray(), _settings); } - public override void Write(Utf8JsonWriter writer, LazyJson value, JsonSerializerOptions options) => throw new NotImplementedException("We only ever expect to deserialize LazyJson on responses."); - private void InitializeSettings(JsonSerializerOptions options) { - if (_settings is null) + if (_settings is not null) { - if (!options.TryGetClientSettings(out var settings)) - ThrowHelper.ThrowJsonExceptionForMissingSettings(); + return; + } - _settings = settings; + if (!ContextProvider.TryGetContext(options, out _settings)) + { + ThrowHelper.ThrowJsonExceptionForMissingSettings(); } } + + public override void Write(Utf8JsonWriter writer, LazyJson value, JsonSerializerOptions options) + { + throw new NotImplementedException("We only ever expect to deserialize LazyJson on responses."); + } } diff --git a/src/Elastic.Clients.Elasticsearch/_Shared/Types/Sql/SqlRowConverter.cs b/src/Elastic.Clients.Elasticsearch/_Shared/Types/Sql/SqlRowConverter.cs index 46b39a310ed..67edbcd2efe 100644 --- a/src/Elastic.Clients.Elasticsearch/_Shared/Types/Sql/SqlRowConverter.cs +++ b/src/Elastic.Clients.Elasticsearch/_Shared/Types/Sql/SqlRowConverter.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; @@ -15,26 +14,13 @@ public sealed class SqlRowConverter : JsonConverter { public override SqlRow? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Null) + var values = reader.ReadCollectionValue(options, null); + if (values is null) { - reader.Read(); return null; } - if (reader.TokenType == JsonTokenType.StartArray) - { - var values = new List(); - - while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) - { - var value = reader.ReadValue(options); - values.Add(value); - } - - return new SqlRow(values); - } - - throw new JsonException($"Unexpected JSON token when deserializing {nameof(SqlRow)}."); + return new SqlRow(values); } public override void Write(Utf8JsonWriter writer, SqlRow value, JsonSerializerOptions options) => throw new NotImplementedException();