Skip to content

[Backport 7.x] Support runtime mappings for SQL #5631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Nest/Mapping/RuntimeFields/RuntimeFields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,16 @@ public RuntimeFieldsDescriptor<TDocument> RuntimeField(string name, FieldType ty
public RuntimeFieldsDescriptor<TDocument> RuntimeField(Expression<Func<TDocument, Field>> field, FieldType type) =>
Assign(field, new RuntimeFieldDescriptor(type));
}

public class RuntimeFieldsDescriptor
: IsADictionaryDescriptorBase<RuntimeFieldsDescriptor, RuntimeFields, Field, IRuntimeField>
{
public RuntimeFieldsDescriptor() : base(new RuntimeFields()) { }

public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type, Func<RuntimeFieldDescriptor, IRuntimeField> selector) =>
Assign(name, selector?.Invoke(new RuntimeFieldDescriptor(type)));

public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type) =>
Assign(name, new RuntimeFieldDescriptor(type));
}
}
6 changes: 6 additions & 0 deletions src/Nest/XPack/Sql/ISqlRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,11 @@ public interface ISqlRequest
/// </summary>
[DataMember(Name ="time_zone")]
string TimeZone { get; set; }

/// <summary>
/// Specifies runtime fields which exist only as part of the query.
/// </summary>
[DataMember(Name = "runtime_mappings")]
IRuntimeFields RuntimeFields { get; set; }
}
}
8 changes: 8 additions & 0 deletions src/Nest/XPack/Sql/QuerySql/QuerySqlRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public partial class QuerySqlRequest
/// <inheritdoc cref="ISqlRequest.TimeZone" />
/// >
public string TimeZone { get; set; }

/// <inheritdoc />
public IRuntimeFields RuntimeFields { get; set; }
}

public partial class QuerySqlDescriptor
Expand All @@ -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; }

/// <inheritdoc cref="ISqlRequest.Query" />
/// >
Expand All @@ -90,5 +94,9 @@ public QuerySqlDescriptor Filter<T>(Func<QueryContainerDescriptor<T>, QueryConta
/// <inheritdoc cref="IQuerySqlRequest.Columnar" />
/// >
public QuerySqlDescriptor Columnar(bool? columnar = true) => Assign(columnar, (a, v) => a.Columnar = v);

/// <inheritdoc cref="ISqlRequest.RuntimeFields" />
public QuerySqlDescriptor RuntimeFields(Func<RuntimeFieldsDescriptor, IPromise<IRuntimeFields>> runtimeFieldsSelector) =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value);
}
}
12 changes: 8 additions & 4 deletions src/Nest/XPack/Sql/TranslateSql/TranslateSqlRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@ protected sealed override void RequestDefaults(TranslateSqlRequestParameters par
parameters.CustomResponseBuilder = TranslateSqlResponseBuilder.Instance;

/// <inheritdoc cref="ISqlRequest.FetchSize" />
/// >
public int? FetchSize { get; set; }

/// <inheritdoc cref="ISqlRequest.Filter" />
/// >
public QueryContainer Filter { get; set; }

/// <inheritdoc cref="ISqlRequest.Query" />
/// >
public string Query { get; set; }

/// <inheritdoc cref="ISqlRequest.TimeZone" />
/// >
public string TimeZone { get; set; }

/// <inheritdoc cref="ISqlRequest.RuntimeFields" />
public IRuntimeFields RuntimeFields { get; set; }
}

public partial class TranslateSqlDescriptor
Expand All @@ -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; }

/// <inheritdoc cref="ISqlRequest.Query" />
/// >
Expand All @@ -59,5 +59,9 @@ protected sealed override void RequestDefaults(TranslateSqlRequestParameters par
/// >
public TranslateSqlDescriptor Filter<T>(Func<QueryContainerDescriptor<T>, QueryContainer> querySelector)
where T : class => Assign(querySelector, (a, v) => a.Filter = v?.Invoke(new QueryContainerDescriptor<T>()));

/// <inheritdoc cref="ISqlRequest.RuntimeFields" />
public TranslateSqlDescriptor RuntimeFields<TSource>(Func<RuntimeFieldsDescriptor<TSource>, IPromise<IRuntimeFields>> runtimeFieldsSelector) where TSource : class =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor<TSource>())?.Value);
}
}
87 changes: 84 additions & 3 deletions tests/Tests/XPack/Sql/QuerySql/QuerySqlApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<XPackCluster, QuerySqlResponse, IQuerySqlRequest, QuerySqlDescriptor, QuerySqlRequest>
{
private static readonly string SqlQuery =
Expand Down Expand Up @@ -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<XPackCluster, QuerySqlResponse, IQuerySqlRequest, QuerySqlDescriptor, QuerySqlRequest>
{
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<QuerySqlDescriptor, IQuerySqlRequest> 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<int?>();
var doublesCommits = r[1].As<long?>();

if (!numberOfCommits.HasValue) continue;

doublesCommits.HasValue.Should().BeTrue();
doublesCommits!.Value.Should().Be(numberOfCommits.Value * 2);
}
}
}
}