From 77eb22bc44a548390251fc6acd9eee6bbc583b46 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Mon, 26 Apr 2021 08:15:55 +0100 Subject: [PATCH] Support runtime mappings for SQL (#5624) --- .../Mapping/RuntimeFields/RuntimeFields.cs | 12 +++ src/Nest/XPack/Sql/ISqlRequest.cs | 6 ++ .../XPack/Sql/QuerySql/QuerySqlRequest.cs | 8 ++ .../Sql/TranslateSql/TranslateSqlRequest.cs | 12 ++- .../XPack/Sql/QuerySql/QuerySqlApiTests.cs | 87 ++++++++++++++++++- 5 files changed, 118 insertions(+), 7 deletions(-) diff --git a/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs b/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs index fe65900a730..40a838bfbcb 100644 --- a/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs +++ b/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs @@ -40,4 +40,16 @@ public RuntimeFieldsDescriptor RuntimeField(string name, FieldType ty public RuntimeFieldsDescriptor RuntimeField(Expression> field, FieldType type) => Assign(field, new RuntimeFieldDescriptor(type)); } + + public class RuntimeFieldsDescriptor + : IsADictionaryDescriptorBase + { + public RuntimeFieldsDescriptor() : base(new RuntimeFields()) { } + + public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type, Func selector) => + Assign(name, selector?.Invoke(new RuntimeFieldDescriptor(type))); + + public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type) => + Assign(name, new RuntimeFieldDescriptor(type)); + } } diff --git a/src/Nest/XPack/Sql/ISqlRequest.cs b/src/Nest/XPack/Sql/ISqlRequest.cs index efe37069d88..2b9da50573e 100644 --- a/src/Nest/XPack/Sql/ISqlRequest.cs +++ b/src/Nest/XPack/Sql/ISqlRequest.cs @@ -29,5 +29,11 @@ public interface ISqlRequest /// [DataMember(Name ="time_zone")] string TimeZone { get; set; } + + /// + /// Specifies runtime fields which exist only as part of the query. + /// + [DataMember(Name = "runtime_mappings")] + IRuntimeFields RuntimeFields { get; set; } } } diff --git a/src/Nest/XPack/Sql/QuerySql/QuerySqlRequest.cs b/src/Nest/XPack/Sql/QuerySql/QuerySqlRequest.cs index 92b4eccf922..33cb1598f79 100644 --- a/src/Nest/XPack/Sql/QuerySql/QuerySqlRequest.cs +++ b/src/Nest/XPack/Sql/QuerySql/QuerySqlRequest.cs @@ -55,6 +55,9 @@ public partial class QuerySqlRequest /// /// > public string TimeZone { get; set; } + + /// + public IRuntimeFields RuntimeFields { get; set; } } public partial class QuerySqlDescriptor @@ -65,6 +68,7 @@ public partial class QuerySqlDescriptor QueryContainer ISqlRequest.Filter { get; set; } string ISqlRequest.Query { get; set; } string ISqlRequest.TimeZone { get; set; } + IRuntimeFields ISqlRequest.RuntimeFields { get; set; } /// /// > @@ -90,5 +94,9 @@ public QuerySqlDescriptor Filter(Func, QueryConta /// /// > public QuerySqlDescriptor Columnar(bool? columnar = true) => Assign(columnar, (a, v) => a.Columnar = v); + + /// + public QuerySqlDescriptor RuntimeFields(Func> runtimeFieldsSelector) => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); } } diff --git a/src/Nest/XPack/Sql/TranslateSql/TranslateSqlRequest.cs b/src/Nest/XPack/Sql/TranslateSql/TranslateSqlRequest.cs index 66e326b1b24..a44d0951eaf 100644 --- a/src/Nest/XPack/Sql/TranslateSql/TranslateSqlRequest.cs +++ b/src/Nest/XPack/Sql/TranslateSql/TranslateSqlRequest.cs @@ -17,20 +17,19 @@ protected sealed override void RequestDefaults(TranslateSqlRequestParameters par parameters.CustomResponseBuilder = TranslateSqlResponseBuilder.Instance; /// - /// > public int? FetchSize { get; set; } /// - /// > public QueryContainer Filter { get; set; } /// - /// > public string Query { get; set; } /// - /// > public string TimeZone { get; set; } + + /// + public IRuntimeFields RuntimeFields { get; set; } } public partial class TranslateSqlDescriptor @@ -42,6 +41,7 @@ protected sealed override void RequestDefaults(TranslateSqlRequestParameters par QueryContainer ISqlRequest.Filter { get; set; } string ISqlRequest.Query { get; set; } string ISqlRequest.TimeZone { get; set; } + IRuntimeFields ISqlRequest.RuntimeFields { get; set; } /// /// > @@ -59,5 +59,9 @@ protected sealed override void RequestDefaults(TranslateSqlRequestParameters par /// > public TranslateSqlDescriptor Filter(Func, QueryContainer> querySelector) where T : class => Assign(querySelector, (a, v) => a.Filter = v?.Invoke(new QueryContainerDescriptor())); + + /// + public TranslateSqlDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) where TSource : class => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); } } diff --git a/tests/Tests/XPack/Sql/QuerySql/QuerySqlApiTests.cs b/tests/Tests/XPack/Sql/QuerySql/QuerySqlApiTests.cs index 17279860178..d20d11ae135 100644 --- a/tests/Tests/XPack/Sql/QuerySql/QuerySqlApiTests.cs +++ b/tests/Tests/XPack/Sql/QuerySql/QuerySqlApiTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System; +using System.Linq; using Elastic.Elasticsearch.Xunit.XunitPlumbing; using Elasticsearch.Net; using FluentAssertions; @@ -15,9 +16,7 @@ namespace Tests.XPack.Sql.QuerySql { - //[SkipVersion("<6.4.0", "")] - // TODO: unskip when https://github.com/elastic/elasticsearch/issues/44320 is fixed - [SkipVersion(">1.0.0", "open issue https://github.com/elastic/elasticsearch/issues/44320")] + [SkipVersion("<6.4.0", "Added in 6.4.0")] public class QuerySqlApiTests : ApiIntegrationTestBase { private static readonly string SqlQuery = @@ -79,4 +78,86 @@ protected override void ExpectResponse(QuerySqlResponse response) } } } + + [SkipVersion("<7.13.0", "Runtime mappings added in 7.13.0")] + public class QuerySqlApiRuntimeMappingsTests : ApiIntegrationTestBase + { + private static readonly string SqlQuery = + $@"SELECT numberOfCommits, doublesCommits FROM {TestValueHelper.ProjectsIndex}"; + + public QuerySqlApiRuntimeMappingsTests(XPackCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override bool ExpectIsValid => true; + + protected override object ExpectJson { get; } = new + { + query = SqlQuery, + fetch_size = 5, + runtime_mappings = new + { + doublesCommits = new + { + script = new + { + source = "emit((long)(doc['numberOfCommits'].value * 2))" + }, + type = "long" + } + } + }; + + protected override int ExpectStatusCode => 200; + + protected override Func Fluent => d => d + .Query(SqlQuery) + .FetchSize(5) + .RuntimeFields(rtf => rtf.RuntimeField("doublesCommits", FieldType.Long, s => s.Script("emit((long)(doc['numberOfCommits'].value * 2))"))); + + protected override HttpMethod HttpMethod => HttpMethod.POST; + + protected override QuerySqlRequest Initializer => new() + { + Query = SqlQuery, + FetchSize = 5, + RuntimeFields = new RuntimeFields + { + { "doublesCommits", new RuntimeField + { + Type = FieldType.Long, + Script = new InlineScript("emit((long)(doc['numberOfCommits'].value * 2))") + } + } + } + }; + + protected override string UrlPath => "/_sql"; + + protected override LazyResponses ClientUsage() => Calls( + (client, f) => client.Sql.Query(f), + (client, f) => client.Sql.QueryAsync(f), + (client, r) => client.Sql.Query(r), + (client, r) => client.Sql.QueryAsync(r) + ); + + protected override void ExpectResponse(QuerySqlResponse response) + { + response.Cursor.Should().NotBeNullOrWhiteSpace("response cursor"); + response.Rows.Should().NotBeNullOrEmpty(); + + response.Columns.Single(x => x.Name == "numberOfCommits").Type.Should().Be("integer"); + response.Columns.Single(x => x.Name == "doublesCommits").Type.Should().Be("long"); + + foreach (var r in response.Rows) + { + r.Should().NotBeNull().And.HaveCount(2); + var numberOfCommits = r[0].As(); + var doublesCommits = r[1].As(); + + if (!numberOfCommits.HasValue) continue; + + doublesCommits.HasValue.Should().BeTrue(); + doublesCommits!.Value.Should().Be(numberOfCommits.Value * 2); + } + } + } }