Skip to content

Commit be690fa

Browse files
authored
Support async SQL searches (#5869)
1 parent aa77e7e commit be690fa

File tree

4 files changed

+200
-21
lines changed

4 files changed

+200
-21
lines changed

src/Nest/XPack/Sql/QuerySql/QuerySqlRequest.cs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ namespace Nest
1111
[ReadAs(typeof(QuerySqlRequest))]
1212
public partial interface IQuerySqlRequest : ISqlRequest
1313
{
14+
/// <summary>
15+
/// Return the results in a columnar fashion: one row represents all the values of a certain column from the current page
16+
/// of results.
17+
/// The following formats can be returned in columnar orientation: json, yaml, cbor and smile.
18+
/// </summary>
19+
[DataMember(Name = "columnar")]
20+
bool? Columnar { get; set; }
21+
1422
/// <summary>
1523
/// Continue to the next page by sending back the cursor field returned in the previous response.
1624
/// <para>
@@ -19,56 +27,53 @@ public partial interface IQuerySqlRequest : ISqlRequest
1927
/// Unlike scroll, receiving the last page is enough to guarantee that the Elasticsearch state is cleared.
2028
/// </para>
2129
/// </summary>
22-
[DataMember(Name="cursor")]
30+
[DataMember(Name = "cursor")]
2331
string Cursor { get; set; }
2432

2533
/// <summary>
26-
/// Return the results in a columnar fashion: one row represents all the values of a certain column from the current page of results.
27-
/// The following formats can be returned in columnar orientation: json, yaml, cbor and smile.
34+
/// Make the search asynchronous by setting a duration you’d like to wait for synchronous results.
2835
/// </summary>
29-
[DataMember(Name="columnar")]
30-
bool? Columnar { get; set; }
36+
[DataMember(Name = "wait_for_completion_timeout")]
37+
Time WaitForCompletionTimeout { get; set; }
3138
}
3239

3340
public partial class QuerySqlRequest
3441
{
35-
/// <inheritdoc cref="IQuerySqlRequest.Cursor" />
36-
/// >
37-
public string Cursor { get; set; }
38-
3942
/// <inheritdoc cref="IQuerySqlRequest.Columnar" />
40-
/// >
4143
public bool? Columnar { get; set; }
4244

45+
/// <inheritdoc cref="IQuerySqlRequest.Cursor" />
46+
public string Cursor { get; set; }
47+
4348
/// <inheritdoc cref="ISqlRequest.FetchSize" />
44-
/// >
4549
public int? FetchSize { get; set; }
4650

4751
/// <inheritdoc cref="ISqlRequest.Filter" />
48-
/// >
4952
public QueryContainer Filter { get; set; }
5053

5154
/// <inheritdoc cref="ISqlRequest.Query" />
52-
/// >
5355
public string Query { get; set; }
5456

57+
/// <inheritdoc cref="ISqlRequest.RuntimeFields" />
58+
public IRuntimeFields RuntimeFields { get; set; }
59+
5560
/// <inheritdoc cref="ISqlRequest.TimeZone" />
56-
/// >
5761
public string TimeZone { get; set; }
5862

59-
/// <inheritdoc />
60-
public IRuntimeFields RuntimeFields { get; set; }
63+
/// <inheritdoc cref="IQuerySqlRequest.WaitForCompletionTimeout" />
64+
public Time WaitForCompletionTimeout { get; set; }
6165
}
6266

6367
public partial class QuerySqlDescriptor
6468
{
65-
string IQuerySqlRequest.Cursor { get; set; }
6669
bool? IQuerySqlRequest.Columnar { get; set; }
70+
string IQuerySqlRequest.Cursor { get; set; }
6771
int? ISqlRequest.FetchSize { get; set; }
6872
QueryContainer ISqlRequest.Filter { get; set; }
6973
string ISqlRequest.Query { get; set; }
70-
string ISqlRequest.TimeZone { get; set; }
7174
IRuntimeFields ISqlRequest.RuntimeFields { get; set; }
75+
string ISqlRequest.TimeZone { get; set; }
76+
Time IQuerySqlRequest.WaitForCompletionTimeout { get; set; }
7277

7378
/// <inheritdoc cref="ISqlRequest.Query" />
7479
/// >
@@ -98,5 +103,8 @@ public QuerySqlDescriptor Filter<T>(Func<QueryContainerDescriptor<T>, QueryConta
98103
/// <inheritdoc cref="ISqlRequest.RuntimeFields" />
99104
public QuerySqlDescriptor RuntimeFields(Func<RuntimeFieldsDescriptor, IPromise<IRuntimeFields>> runtimeFieldsSelector) =>
100105
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value);
106+
107+
/// <inheritdoc cref="IQuerySqlRequest.WaitForCompletionTimeout" />
108+
public QuerySqlDescriptor WaitForCompletionTimeout(Time frequency) => Assign(frequency, (a, v) => a.WaitForCompletionTimeout = v);
101109
}
102110
}

src/Nest/XPack/Sql/QuerySql/QuerySqlResponse.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,31 @@ public class QuerySqlResponse : ResponseBase
2525
public string Cursor { get; internal set; }
2626

2727
/// <summary>
28-
/// If <see cref="IQuerySqlRequest.Columnar"/> has been set to false, this property will contain the row values
28+
/// Identifier for the search.
29+
/// </summary>
30+
[DataMember(Name = "id")]
31+
public string Id { get; internal set; }
32+
33+
/// <summary>
34+
/// If true, the response does not contain complete search results.
35+
/// </summary>
36+
[DataMember(Name = "is_partial")]
37+
public bool IsPartial { get; internal set; }
38+
39+
/// <summary>
40+
/// If true, the search request is still executing.
41+
/// </summary>
42+
[DataMember(Name = "is_running")]
43+
public bool IsRunning { get; internal set; }
44+
45+
/// <summary>
46+
/// If <see cref="IQuerySqlRequest.Columnar" /> has been set to false, this property will contain the row values
2947
/// </summary>
3048
[DataMember(Name = "rows")]
3149
public IReadOnlyCollection<SqlRow> Rows { get; internal set; } = EmptyReadOnly<SqlRow>.Collection;
3250

3351
/// <summary>
34-
/// If <see cref="IQuerySqlRequest.Columnar"/> has been set to true, this property will contain the column values
52+
/// If <see cref="IQuerySqlRequest.Columnar" /> has been set to true, this property will contain the column values
3553
/// </summary>
3654
[DataMember(Name = "values")]
3755
public IReadOnlyCollection<SqlRow> Values { get; internal set; } = EmptyReadOnly<SqlRow>.Collection;

src/Nest/XPack/Sql/Status/SqlSearchStatusResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class SqlSearchStatusResponse : ResponseBase
1515
/// For a completed search shows the http status code of the completed search.
1616
/// </summary>
1717
[DataMember(Name = "completion_status")]
18-
public int CompletionStatus { get; internal set; }
18+
public int? CompletionStatus { get; internal set; }
1919

2020
/// <summary>
2121
/// For a running search shows a timestamp when the eql search started, in milliseconds since the Unix epoch.
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.Threading.Tasks;
7+
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
8+
using FluentAssertions;
9+
using Nest;
10+
using Tests.Core.Extensions;
11+
using Tests.Core.ManagedElasticsearch.Clusters;
12+
using Tests.Domain;
13+
using Tests.Domain.Helpers;
14+
using Tests.Framework.EndpointTests;
15+
using Tests.Framework.EndpointTests.TestState;
16+
17+
namespace Tests.XPack.Sql
18+
{
19+
[SkipVersion("<7.14.0", "All endpoints GA in 7.14.0")]
20+
public class SqlSearchApiCoordinatedTests : CoordinatedIntegrationTestBase<XPackCluster>
21+
{
22+
private const string DeleteStep = nameof(DeleteStep);
23+
private const string GetStep = nameof(GetStep);
24+
private const string StatusStep = nameof(StatusStep);
25+
private const string SubmitStep = nameof(SubmitStep);
26+
private const string WaitStep = nameof(WaitStep);
27+
28+
private static readonly string SqlQuery =
29+
$@"SELECT type, name, startedOn, numberOfCommits
30+
FROM {TestValueHelper.ProjectsIndex}
31+
WHERE type = '{Project.TypeName}'
32+
ORDER BY numberOfContributors DESC";
33+
34+
public SqlSearchApiCoordinatedTests(XPackCluster cluster, EndpointUsage usage) : base(new CoordinatedUsage(cluster, usage, testOnlyOne: true)
35+
{
36+
{
37+
SubmitStep, u =>
38+
u.Calls<QuerySqlDescriptor, QuerySqlRequest, IQuerySqlRequest, QuerySqlResponse>(
39+
_ => new QuerySqlRequest { Query = SqlQuery, FetchSize = 5, WaitForCompletionTimeout = "0s" },
40+
(_, d) => d
41+
.Query(SqlQuery)
42+
.FetchSize(5)
43+
.WaitForCompletionTimeout("0s"),
44+
(_, c, f) => c.Sql.Query(f),
45+
(_, c, f) => c.Sql.QueryAsync(f),
46+
(_, c, r) => c.Sql.Query(r),
47+
(_, c, r) => c.Sql.QueryAsync(r),
48+
(r, values) => values.ExtendedValue("id", r.Id)
49+
)
50+
},
51+
{
52+
StatusStep, u =>
53+
u.Calls<SqlSearchStatusDescriptor, SqlSearchStatusRequest, ISqlSearchStatusRequest, SqlSearchStatusResponse>(
54+
v => new SqlSearchStatusRequest(v),
55+
(v, d) => d,
56+
(v, c, f) => c.Sql.SearchStatus(v, f),
57+
(v, c, f) => c.Sql.SearchStatusAsync(v, f),
58+
(v, c, r) => c.Sql.SearchStatus(r),
59+
(v, c, r) => c.Sql.SearchStatusAsync(r),
60+
uniqueValueSelector: values => values.ExtendedValue<string>("id")
61+
)
62+
},
63+
{
64+
// allows the search to complete
65+
WaitStep, u => u.Call(async (_, c) =>
66+
{
67+
// wait for the search to complete
68+
var complete = false;
69+
var count = 0;
70+
71+
while (!complete && count++ < 10)
72+
{
73+
await Task.Delay(100);
74+
var status = await c.Sql.SearchStatusAsync(u.Usage.CallUniqueValues.ExtendedValue<string>("id"));
75+
complete = !status.IsRunning && status.CompletionStatus.HasValue;
76+
}
77+
})
78+
},
79+
{
80+
GetStep, u =>
81+
u.Calls<SqlGetDescriptor, SqlGetRequest, ISqlGetRequest, SqlGetResponse>(
82+
v => new SqlGetRequest(v),
83+
(_, d) => d,
84+
(v, c, f) => c.Sql.Get(v, f),
85+
(v, c, f) => c.Sql.GetAsync(v, f),
86+
(_, c, r) => c.Sql.Get(r),
87+
(_, c, r) => c.Sql.GetAsync(r),
88+
uniqueValueSelector: values => values.ExtendedValue<string>("id")
89+
)
90+
},
91+
{
92+
DeleteStep, u =>
93+
u.Calls<SqlDeleteDescriptor, SqlDeleteRequest, ISqlDeleteRequest, SqlDeleteResponse>(
94+
v => new SqlDeleteRequest(v),
95+
(_, d) => d,
96+
(v, c, f) => c.Sql.Delete(v, f),
97+
(v, c, f) => c.Sql.DeleteAsync(v, f),
98+
(_, c, r) => c.Sql.Delete(r),
99+
(_, c, r) => c.Sql.DeleteAsync(r),
100+
uniqueValueSelector: values => values.ExtendedValue<string>("id")
101+
)
102+
}
103+
}) { }
104+
105+
[I] public async Task SqlSearchResponse() => await Assert<QuerySqlResponse>(SubmitStep, r =>
106+
{
107+
r.ShouldBeValid();
108+
r.Id.Should().NotBeNullOrEmpty();
109+
r.IsPartial.Should().BeTrue();
110+
r.IsRunning.Should().BeTrue();
111+
});
112+
113+
[I] public async Task SqlSearchStatusResponse() => await Assert<SqlSearchStatusResponse>(StatusStep, r =>
114+
{
115+
r.ShouldBeValid();
116+
r.Id.Should().NotBeNullOrEmpty();
117+
r.IsPartial.Should().BeTrue();
118+
r.IsRunning.Should().BeTrue();
119+
r.ExpirationTimeInMillis.Should().BeGreaterThan(0);
120+
r.StartTimeInMillis.Should().BeGreaterThan(0);
121+
});
122+
123+
[I] public async Task SqlGetResponse() => await Assert<SqlGetResponse>(GetStep, r =>
124+
{
125+
r.ShouldBeValid();
126+
r.IsPartial.Should().BeFalse();
127+
r.IsRunning.Should().BeFalse();
128+
129+
r.Cursor.Should().NotBeNullOrWhiteSpace("response cursor");
130+
r.Rows.Should().NotBeNullOrEmpty();
131+
r.Columns.Should().NotBeNullOrEmpty().And.HaveCount(4);
132+
foreach (var c in r.Columns)
133+
{
134+
c.Name.Should().NotBeNullOrWhiteSpace("column name");
135+
c.Type.Should().NotBeNullOrWhiteSpace("column type");
136+
}
137+
foreach (var row in r.Rows)
138+
{
139+
row.Should().NotBeNull().And.HaveCount(4);
140+
var type = row[0].As<string>().Should().NotBeNullOrWhiteSpace("a type returned null");
141+
var name = row[1].As<string>().Should().NotBeNullOrWhiteSpace("a name returned null");
142+
var date = row[2].As<DateTime>().Should().BeAfter(default);
143+
var numberOfCommits = row[3].As<int?>();
144+
}
145+
});
146+
147+
[I] public async Task SqlDeleteResponse() => await Assert<SqlDeleteResponse>(DeleteStep, r =>
148+
{
149+
r.ShouldBeValid();
150+
r.Acknowledged.Should().BeTrue();
151+
});
152+
}
153+
}

0 commit comments

Comments
 (0)