Skip to content

Commit bcdc1d6

Browse files
committed
Fix #3320 Add Painless script execute API (#3370)
* Generate ExcecutePainlessScript methods/descriptors etc * Implement ExecutePainlessScript API with tests Omitting `context` for now since that only takes a single option now which is also the default. Waiting for the API to crystalize here. * make Result generic in preparation for 6.4
1 parent 6007a70 commit bcdc1d6

12 files changed

+274
-2
lines changed

src/CodeGeneration/ApiGenerator/ApiGenerator.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ public static void Generate(string downloadBranch, params string[] folders)
8484
"xpack.sql.query.json",
8585
"xpack.sql.translate.json",
8686
"xpack.ssl.certificates.json",
87-
88-
"scripts_painless_execute.json",
8987
};
9088

9189
private static RestApiSpec CreateRestApiSpecModel(string downloadBranch, string[] folders)

src/Elasticsearch.Net/Domain/RequestParameters/RequestParameters.Generated.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,11 @@ public partial class RenderSearchTemplateRequestParameters : RequestParameters<R
17531753
{
17541754
public override HttpMethod DefaultHttpMethod => HttpMethod.POST;
17551755
}
1756+
///<summary>Request options for ScriptsPainlessExecute<pre>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</pre></summary>
1757+
public partial class ExecutePainlessScriptRequestParameters : RequestParameters<ExecutePainlessScriptRequestParameters>
1758+
{
1759+
public override HttpMethod DefaultHttpMethod => HttpMethod.POST;
1760+
}
17561761
///<summary>Request options for Scroll<pre>http://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-scroll.html</pre></summary>
17571762
public partial class ScrollRequestParameters : RequestParameters<ScrollRequestParameters>
17581763
{

src/Elasticsearch.Net/ElasticLowLevelClient.Generated.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,6 +2416,24 @@ public TResponse RenderSearchTemplate<TResponse>(string id, PostData body, Rende
24162416
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
24172417
public Task<TResponse> RenderSearchTemplateAsync<TResponse>(string id, PostData body, RenderSearchTemplateRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken))
24182418
where TResponse : class, IElasticsearchResponse, new() => this.DoRequestAsync<TResponse>(POST, Url($"_render/template/{id.NotNull("id")}"), ctx, body, _params(requestParameters));
2419+
///<summary>GET on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
2420+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
2421+
public TResponse ScriptsPainlessExecuteGet<TResponse>(ExecutePainlessScriptRequestParameters requestParameters = null)
2422+
where TResponse : class, IElasticsearchResponse, new() => this.DoRequest<TResponse>(GET, Url($"_scripts/painless/_execute"), null, _params(requestParameters));
2423+
///<summary>GET on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
2424+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
2425+
public Task<TResponse> ScriptsPainlessExecuteGetAsync<TResponse>(ExecutePainlessScriptRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken))
2426+
where TResponse : class, IElasticsearchResponse, new() => this.DoRequestAsync<TResponse>(GET, Url($"_scripts/painless/_execute"), ctx, null, _params(requestParameters));
2427+
///<summary>POST on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
2428+
///<param name="body">The script to execute</param>
2429+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
2430+
public TResponse ScriptsPainlessExecute<TResponse>(PostData body, ExecutePainlessScriptRequestParameters requestParameters = null)
2431+
where TResponse : class, IElasticsearchResponse, new() => this.DoRequest<TResponse>(POST, Url($"_scripts/painless/_execute"), body, _params(requestParameters));
2432+
///<summary>POST on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
2433+
///<param name="body">The script to execute</param>
2434+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
2435+
public Task<TResponse> ScriptsPainlessExecuteAsync<TResponse>(PostData body, ExecutePainlessScriptRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken))
2436+
where TResponse : class, IElasticsearchResponse, new() => this.DoRequestAsync<TResponse>(POST, Url($"_scripts/painless/_execute"), ctx, body, _params(requestParameters));
24192437
///<summary>GET on /_search/scroll <para>http://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-scroll.html</para></summary>
24202438
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
24212439
public TResponse ScrollGet<TResponse>(ScrollRequestParameters requestParameters = null)

src/Elasticsearch.Net/IElasticLowLevelClient.Generated.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,20 @@ public partial interface IElasticLowLevelClient
19561956
///<param name="body">The search definition template and its params</param>
19571957
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
19581958
Task<TResponse> RenderSearchTemplateAsync<TResponse>(string id, PostData body, RenderSearchTemplateRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken)) where TResponse : class, IElasticsearchResponse, new();
1959+
///<summary>GET on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
1960+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
1961+
TResponse ScriptsPainlessExecuteGet<TResponse>(ExecutePainlessScriptRequestParameters requestParameters = null) where TResponse : class, IElasticsearchResponse, new();
1962+
///<summary>GET on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
1963+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
1964+
Task<TResponse> ScriptsPainlessExecuteGetAsync<TResponse>(ExecutePainlessScriptRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken)) where TResponse : class, IElasticsearchResponse, new();
1965+
///<summary>POST on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
1966+
///<param name="body">The script to execute</param>
1967+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
1968+
TResponse ScriptsPainlessExecute<TResponse>(PostData body, ExecutePainlessScriptRequestParameters requestParameters = null) where TResponse : class, IElasticsearchResponse, new();
1969+
///<summary>POST on /_scripts/painless/_execute <para>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</para></summary>
1970+
///<param name="body">The script to execute</param>
1971+
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
1972+
Task<TResponse> ScriptsPainlessExecuteAsync<TResponse>(PostData body, ExecutePainlessScriptRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken)) where TResponse : class, IElasticsearchResponse, new();
19591973
///<summary>GET on /_search/scroll <para>http://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-scroll.html</para></summary>
19601974
///<param name="requestParameters">A func that allows you to describe the querystring parameters &amp; request specific connection settings.</param>
19611975
TResponse ScrollGet<TResponse>(ScrollRequestParameters requestParameters = null) where TResponse : class, IElasticsearchResponse, new();
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Elasticsearch.Net;
4+
using System.Threading;
5+
6+
namespace Nest
7+
{
8+
public partial interface IElasticClient
9+
{
10+
/// <summary>
11+
/// Executes an arbitrary Painless script and returns a result.
12+
/// Useful for testing the syntactical correctness of Painless scripts
13+
/// </summary>
14+
IExecutePainlessScriptResponse<TResult> ExecutePainlessScript<TResult>(Func<ExecutePainlessScriptDescriptor, IExecutePainlessScriptRequest> selector);
15+
16+
/// <inheritdoc cref="ExecutePainlessScript{TResult}(System.Func{Nest.ExecutePainlessScriptDescriptor,Nest.IExecutePainlessScriptRequest})"/>
17+
IExecutePainlessScriptResponse<TResult> ExecutePainlessScript<TResult>(IExecutePainlessScriptRequest request);
18+
19+
/// <inheritdoc cref="ExecutePainlessScript{TResult}(System.Func{Nest.ExecutePainlessScriptDescriptor,Nest.IExecutePainlessScriptRequest})"/>
20+
Task<IExecutePainlessScriptResponse<TResult>> ExecutePainlessScriptAsync<TResult>(Func<ExecutePainlessScriptDescriptor, IExecutePainlessScriptRequest> selector,
21+
CancellationToken cancellationToken = default(CancellationToken));
22+
23+
/// <inheritdoc cref="ExecutePainlessScript{TResult}(System.Func{Nest.ExecutePainlessScriptDescriptor,Nest.IExecutePainlessScriptRequest})"/>
24+
Task<IExecutePainlessScriptResponse<TResult>> ExecutePainlessScriptAsync<TResult>(IExecutePainlessScriptRequest request, CancellationToken cancellationToken = default(CancellationToken));
25+
26+
}
27+
28+
public partial class ElasticClient
29+
{
30+
/// <inheritdoc />
31+
public IExecutePainlessScriptResponse<TResult> ExecutePainlessScript<TResult>(Func<ExecutePainlessScriptDescriptor, IExecutePainlessScriptRequest> selector) =>
32+
this.ExecutePainlessScript<TResult>(selector?.Invoke(new ExecutePainlessScriptDescriptor()));
33+
34+
/// <inheritdoc />
35+
public IExecutePainlessScriptResponse<TResult> ExecutePainlessScript<TResult>(IExecutePainlessScriptRequest request) =>
36+
this.Dispatcher.Dispatch<IExecutePainlessScriptRequest, ExecutePainlessScriptRequestParameters, ExecutePainlessScriptResponse<TResult>>(
37+
request,
38+
this.LowLevelDispatch.ScriptsPainlessExecuteDispatch<ExecutePainlessScriptResponse<TResult>>
39+
);
40+
41+
/// <inheritdoc />
42+
public Task<IExecutePainlessScriptResponse<TResult>> ExecutePainlessScriptAsync<TResult>(Func<ExecutePainlessScriptDescriptor, IExecutePainlessScriptRequest> selector,
43+
CancellationToken cancellationToken = default(CancellationToken)) =>
44+
this.ExecutePainlessScriptAsync<TResult>(selector?.Invoke(new ExecutePainlessScriptDescriptor()), cancellationToken);
45+
46+
/// <inheritdoc />
47+
public Task<IExecutePainlessScriptResponse<TResult>> ExecutePainlessScriptAsync<TResult>(IExecutePainlessScriptRequest request, CancellationToken cancellationToken = default(CancellationToken)) =>
48+
this.Dispatcher.DispatchAsync<IExecutePainlessScriptRequest, ExecutePainlessScriptRequestParameters, ExecutePainlessScriptResponse<TResult>, IExecutePainlessScriptResponse<TResult>>(
49+
request,
50+
cancellationToken,
51+
this.LowLevelDispatch.ScriptsPainlessExecuteDispatchAsync<ExecutePainlessScriptResponse<TResult>>
52+
);
53+
}
54+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace Nest
5+
{
6+
public partial interface IExecutePainlessScriptRequest
7+
{
8+
[JsonProperty("script")]
9+
IInlineScript Script { get; set; }
10+
}
11+
12+
public partial class ExecutePainlessScriptRequest
13+
{
14+
public IInlineScript Script { get; set; }
15+
}
16+
17+
[DescriptorFor("ScriptsPainlessExecute")]
18+
public partial class ExecutePainlessScriptDescriptor
19+
{
20+
IInlineScript IExecutePainlessScriptRequest.Script { get; set; }
21+
22+
public ExecutePainlessScriptDescriptor Script(Func<InlineScriptDescriptor, IInlineScript> selector) =>
23+
Assign(a => a.Script = selector?.Invoke(new InlineScriptDescriptor()));
24+
}
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Nest
4+
{
5+
public interface IExecutePainlessScriptResponse<TResult> : IResponse
6+
{
7+
[JsonProperty("result")]
8+
TResult Result { get; }
9+
}
10+
11+
public class ExecutePainlessScriptResponse<TResult> : ResponseBase, IExecutePainlessScriptResponse<TResult>
12+
{
13+
public TResult Result { get; set; }
14+
}
15+
}

src/Nest/_Generated/_Descriptors.generated.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3126,6 +3126,14 @@ public RenderSearchTemplateDescriptor() : base(){}
31263126

31273127
// Request parameters
31283128

3129+
}
3130+
///<summary>descriptor for ScriptsPainlessExecute <pre>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</pre></summary>
3131+
public partial class ExecutePainlessScriptDescriptor : RequestDescriptorBase<ExecutePainlessScriptDescriptor,ExecutePainlessScriptRequestParameters, IExecutePainlessScriptRequest>, IExecutePainlessScriptRequest
3132+
{
3133+
// values part of the url path
3134+
3135+
// Request parameters
3136+
31293137
}
31303138
///<summary>descriptor for Scroll <pre>http://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-scroll.html</pre></summary>
31313139
public partial class ScrollDescriptor<T> : RequestDescriptorBase<ScrollDescriptor<T>,ScrollRequestParameters, IScrollRequest>, IScrollRequest

src/Nest/_Generated/_LowLevelDispatch.generated.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,6 +2408,30 @@ internal partial class LowLevelDispatch
24082408
throw InvalidDispatch("RenderSearchTemplate", p, new [] { GET, POST }, "/_render/template", "/_render/template/{id}");
24092409
}
24102410

2411+
internal TResponse ScriptsPainlessExecuteDispatch<TResponse>(IRequest<ExecutePainlessScriptRequestParameters> p,SerializableData<IExecutePainlessScriptRequest> body) where TResponse : class, IElasticsearchResponse, new()
2412+
{
2413+
switch(p.HttpMethod)
2414+
{
2415+
case GET:
2416+
return _lowLevel.ScriptsPainlessExecuteGet<TResponse>(p.RequestParameters);
2417+
case POST:
2418+
return _lowLevel.ScriptsPainlessExecute<TResponse>(body,p.RequestParameters);
2419+
}
2420+
throw InvalidDispatch("ScriptsPainlessExecute", p, new [] { GET, POST }, "/_scripts/painless/_execute");
2421+
}
2422+
2423+
internal Task<TResponse> ScriptsPainlessExecuteDispatchAsync<TResponse>(IRequest<ExecutePainlessScriptRequestParameters> p,SerializableData<IExecutePainlessScriptRequest> body, CancellationToken ct) where TResponse : class, IElasticsearchResponse, new()
2424+
{
2425+
switch(p.HttpMethod)
2426+
{
2427+
case GET:
2428+
return _lowLevel.ScriptsPainlessExecuteGetAsync<TResponse>(p.RequestParameters,ct);
2429+
case POST:
2430+
return _lowLevel.ScriptsPainlessExecuteAsync<TResponse>(body,p.RequestParameters,ct);
2431+
}
2432+
throw InvalidDispatch("ScriptsPainlessExecute", p, new [] { GET, POST }, "/_scripts/painless/_execute");
2433+
}
2434+
24112435
internal TResponse ScrollDispatch<TResponse>(IRequest<ScrollRequestParameters> p,SerializableData<IScrollRequest> body) where TResponse : class, IElasticsearchResponse, new()
24122436
{
24132437
switch(p.HttpMethod)

src/Nest/_Generated/_Requests.generated.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,6 +2247,18 @@ public EnableUserRequest(Name username) : base(r=>r.Optional("username", usernam
22472247
public Refresh? Refresh { get => Q<Refresh?>("refresh"); set => Q("refresh", value); }
22482248
}
22492249
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
2250+
public partial interface IExecutePainlessScriptRequest : IRequest<ExecutePainlessScriptRequestParameters>
2251+
{
2252+
}
2253+
///<summary>Request parameters for ScriptsPainlessExecute <pre>https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html</pre></summary>
2254+
public partial class ExecutePainlessScriptRequest : PlainRequestBase<ExecutePainlessScriptRequestParameters>, IExecutePainlessScriptRequest
2255+
{
2256+
protected IExecutePainlessScriptRequest Self => this;
2257+
// values part of the url path
2258+
2259+
// Request parameters
2260+
}
2261+
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
22502262
public partial interface IExecuteWatchRequest : IRequest<ExecuteWatchRequestParameters>
22512263
{
22522264
Id Id { get; }
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Elastic.Xunit.XunitPlumbing;
5+
using Elasticsearch.Net;
6+
using FluentAssertions;
7+
using Nest;
8+
using Tests.Core.Extensions;
9+
using Tests.Core.ManagedElasticsearch.Clusters;
10+
using Tests.Framework;
11+
using Tests.Framework.Integration;
12+
using Tests.Framework.ManagedElasticsearch.Clusters;
13+
using Xunit;
14+
15+
namespace Tests.Modules.Scripting.ExecutePainlessScript
16+
{
17+
[SkipVersion("<6.3.0", "this API was introduced in 6.3.0")]
18+
public class ExecutePainlessScriptApiTests
19+
: ApiIntegrationTestBase<ReadOnlyCluster, IExecutePainlessScriptResponse<string>, IExecutePainlessScriptRequest, ExecutePainlessScriptDescriptor, ExecutePainlessScriptRequest>
20+
{
21+
public ExecutePainlessScriptApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
22+
23+
private static readonly string _painlessScript = "params.count / params.total";
24+
25+
protected override LazyResponses ClientUsage() => Calls(
26+
fluent: (client, f) => client.ExecutePainlessScript<string>(f),
27+
fluentAsync: (client, f) => client.ExecutePainlessScriptAsync<string>(f),
28+
request: (client, r) => client.ExecutePainlessScript<string>(r),
29+
requestAsync: (client, r) => client.ExecutePainlessScriptAsync<string>(r)
30+
);
31+
32+
protected override HttpMethod HttpMethod => HttpMethod.POST;
33+
protected override string UrlPath => "/_scripts/painless/_execute";
34+
protected override int ExpectStatusCode => 200;
35+
protected override bool ExpectIsValid => true;
36+
37+
protected override bool SupportsDeserialization => false;
38+
39+
protected override object ExpectJson => new
40+
{
41+
script = new
42+
{
43+
source = _painlessScript,
44+
@params = new { count = 100.0, total = 1000.0 }
45+
},
46+
};
47+
48+
protected override Func<ExecutePainlessScriptDescriptor, IExecutePainlessScriptRequest> Fluent => d => d
49+
.Script(s=>s
50+
.Source(_painlessScript)
51+
.Params(p => p.Add("count", 100.0).Add("total", 1000.0))
52+
);
53+
54+
protected override ExecutePainlessScriptRequest Initializer => new ExecutePainlessScriptRequest
55+
{
56+
Script = new InlineScript(_painlessScript)
57+
{
58+
Params = new Dictionary<string, object>
59+
{
60+
{ "count", 100.0 },
61+
{ "total", 1000.0 },
62+
}
63+
}
64+
};
65+
66+
protected override void ExpectResponse(IExecutePainlessScriptResponse<string> response)
67+
{
68+
response.ShouldBeValid();
69+
response.Result.Should().NotBeNullOrWhiteSpace();
70+
}
71+
}
72+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Threading.Tasks;
2+
using Elastic.Xunit.XunitPlumbing;
3+
using Nest;
4+
using Tests.Framework;
5+
using static Tests.Framework.UrlTester;
6+
7+
namespace Tests.Modules.Scripting.ExecutePainlessScript
8+
{
9+
public class ExecutePainlessScriptUrlTests
10+
{
11+
[U] public async Task Urls()
12+
{
13+
var painless = "1 + 1";
14+
var request = new ExecutePainlessScriptRequest
15+
{
16+
Script = new InlineScript(painless)
17+
};
18+
19+
await POST("/_scripts/painless/_execute")
20+
.Fluent(c => c.ExecutePainlessScript<string>(f => f.Script(s => s.Source(painless))))
21+
.Request(c => c.ExecutePainlessScript<string>(request))
22+
.FluentAsync(c => c.ExecutePainlessScriptAsync<string>(f => f.Script(s => s.Source(painless))))
23+
.RequestAsync(c => c.ExecutePainlessScriptAsync<string>(request))
24+
;
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)