Skip to content

Commit 83c4f87

Browse files
authored
sliced scroll support (#2436)
1 parent 2161b4a commit 83c4f87

File tree

8 files changed

+154
-3
lines changed

8 files changed

+154
-3
lines changed

src/Nest/Nest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,7 @@
11751175
<Compile Include="Search\Scroll\ClearScroll\ElasticClient-ClearScroll.cs" />
11761176
<Compile Include="Search\Scroll\Scroll\ElasticClient-Scroll.cs" />
11771177
<Compile Include="Search\Scroll\Scroll\ScrollRequest.cs" />
1178+
<Compile Include="Search\Scroll\Scroll\SlicedScroll.cs" />
11781179
<Compile Include="Search\SearchShards\ElasticClient-SearchShards.cs" />
11791180
<Compile Include="Search\SearchShards\SearchShardsRequest.cs" />
11801181
<Compile Include="Search\SearchShards\SearchShardsResponse.cs" />

src/Nest/Search/Scroll/Scroll/ScrollRequest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ public partial interface IScrollRequest : ICovariantSearchRequest
1111
[JsonProperty("scroll_id")]
1212
string ScrollId { get; set; }
1313
}
14-
15-
public partial class ScrollRequest
14+
15+
public partial class ScrollRequest
1616
{
1717
private Type _clrType { get; set; }
1818
Type ICovariantSearchRequest.ClrType => this._clrType;
@@ -48,7 +48,7 @@ public partial class ScrollDescriptor<T> where T : class
4848

4949
public ScrollDescriptor<T> ScrollId(string scrollId) => Assign(a => a.ScrollId = scrollId);
5050

51-
public ScrollDescriptor<T> ConcreteTypeSelector(Func<dynamic, Hit<dynamic>, Type> typeSelector) =>
51+
public ScrollDescriptor<T> ConcreteTypeSelector(Func<dynamic, Hit<dynamic>, Type> typeSelector) =>
5252
Assign(a => a.TypeSelector = typeSelector);
5353

5454
public ScrollDescriptor<T> CovariantTypes(Types covariantTypes) => Assign(a=> this._covariantTypes = covariantTypes);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using Newtonsoft.Json;
4+
5+
namespace Nest
6+
{
7+
[JsonConverter(typeof(ReadAsTypeJsonConverter<SlicedScroll>))]
8+
public interface ISlicedScroll
9+
{
10+
[JsonProperty("id")]
11+
int? Id { get; set; }
12+
[JsonProperty("max")]
13+
int? Max { get; set; }
14+
[JsonProperty("field")]
15+
Field Field { get; set; }
16+
}
17+
18+
public class SlicedScroll : ISlicedScroll
19+
{
20+
public int? Id { get; set; }
21+
public int? Max { get; set; }
22+
public Field Field { get; set; }
23+
24+
}
25+
26+
27+
public class SlicedScrollDescriptor<T> : DescriptorBase<SlicedScrollDescriptor<T>, ISlicedScroll>, ISlicedScroll
28+
where T : class
29+
{
30+
int? ISlicedScroll.Id { get; set; }
31+
int? ISlicedScroll.Max { get; set; }
32+
Field ISlicedScroll.Field { get; set; }
33+
34+
public SlicedScrollDescriptor<T> Id(int id) => Assign(a => a.Id = id);
35+
36+
public SlicedScrollDescriptor<T> Max(int max) => Assign(a => a.Max = max);
37+
38+
public SlicedScrollDescriptor<T> Field(Field field) => Assign(a => a.Field = field);
39+
40+
public SlicedScrollDescriptor<T> Field(Expression<Func<T, object>> objectPath) => Assign(a => a.Field = objectPath);
41+
42+
}
43+
}

src/Nest/Search/Search/SearchRequest.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public partial interface ISearchRequest : ICovariantSearchRequest
7272
[JsonProperty("aggs")]
7373
AggregationDictionary Aggregations { get; set; }
7474

75+
[JsonProperty("slice")]
76+
ISlicedScroll Slice { get; set; }
77+
7578
[JsonProperty("query")]
7679
QueryContainer Query { get; set; }
7780

@@ -114,6 +117,7 @@ public partial class SearchRequest
114117
public IList<object> SearchAfter { get; set; }
115118
public IDictionary<IndexName, double> IndicesBoost { get; set; }
116119
public QueryContainer PostFilter { get; set; }
120+
public ISlicedScroll Slice { get; set; }
117121
public QueryContainer Query { get; set; }
118122
public IList<IRescore> Rescore { get; set; }
119123
public ISuggestContainer Suggest { get; set; }
@@ -157,6 +161,7 @@ public partial class SearchRequest<T>
157161
public IList<object> SearchAfter { get; set; }
158162
public IDictionary<IndexName, double> IndicesBoost { get; set; }
159163
public QueryContainer PostFilter { get; set; }
164+
public ISlicedScroll Slice { get; set; }
160165
public QueryContainer Query { get; set; }
161166
public IList<IRescore> Rescore { get; set; }
162167
public ISuggestContainer Suggest { get; set; }
@@ -212,6 +217,7 @@ public partial class SearchDescriptor<T> where T : class
212217
ISuggestContainer ISearchRequest.Suggest { get; set; }
213218
IHighlight ISearchRequest.Highlight { get; set; }
214219
IList<IRescore> ISearchRequest.Rescore { get; set; }
220+
ISlicedScroll ISearchRequest.Slice { get; set; }
215221
QueryContainer ISearchRequest.Query { get; set; }
216222
QueryContainer ISearchRequest.PostFilter { get; set; }
217223
Fields ISearchRequest.StoredFields { get; set; }
@@ -398,6 +404,12 @@ public SearchDescriptor<T> Suggest(Func<SuggestContainerDescriptor<T>, IPromise<
398404
public SearchDescriptor<T> Query(Func<QueryContainerDescriptor<T>, QueryContainer> query) =>
399405
Assign(a => a.Query = query?.Invoke(new QueryContainerDescriptor<T>()));
400406

407+
/// <summary>
408+
/// For scroll queries that return a lot of documents it is possible to split the scroll in multiple slices which can be consumed independently
409+
/// </summary>
410+
public SearchDescriptor<T> Slice(Func<SlicedScrollDescriptor<T>, ISlicedScroll> query) =>
411+
Assign(a => a.Slice = query?.Invoke(new SlicedScrollDescriptor<T>()));
412+
401413
/// <summary>
402414
/// Shortcut to default to a match all query
403415
/// </summary>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using Nest;
3+
using Tests.Framework.Integration;
4+
using Tests.Framework.MockData;
5+
using static Nest.Infer;
6+
7+
namespace Tests.Search.Request
8+
{
9+
public class SlicedScrollSearchUsageTests : SearchUsageTestBase
10+
{
11+
12+
public SlicedScrollSearchUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
13+
14+
protected override string UrlPath => "/project/project/_search?scroll=1m";
15+
16+
protected override object ExpectJson =>
17+
new { slice = new { id = 0, max = 5 } };
18+
19+
protected override SearchRequest<Project> Initializer =>
20+
new SearchRequest<Project>()
21+
{
22+
Scroll = "1m",
23+
Slice = new SlicedScroll { Id = 0, Max = 5 }
24+
};
25+
26+
protected override Func<SearchDescriptor<Project>, ISearchRequest> Fluent => s => s
27+
.Scroll("1m")
28+
.Slice(ss=>ss.Id(0).Max(5));
29+
}
30+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using System.Threading;
3+
using Elasticsearch.Net;
4+
using Nest;
5+
using Tests.Framework;
6+
using Tests.Framework.Integration;
7+
using Tests.Framework.MockData;
8+
using Xunit;
9+
10+
namespace Tests.Search.Scroll.Scroll
11+
{
12+
public class SlicedScrollApiTests : ApiIntegrationTestBase<ReadOnlyCluster, ISearchResponse<Project>, IScrollRequest, ScrollDescriptor<Project>, ScrollRequest>
13+
{
14+
public SlicedScrollApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
15+
16+
private string _scrollId = "default-for-unit-tests";
17+
18+
protected override LazyResponses ClientUsage() => Calls(
19+
fluent: (c, f) => c.Scroll("1m", _scrollId, f),
20+
fluentAsync: (c, f) => c.ScrollAsync("1m", _scrollId, f),
21+
request: (c, r) => c.Scroll<Project>(r),
22+
requestAsync: (c, r) => c.ScrollAsync<Project>(r)
23+
);
24+
25+
protected override object ExpectJson => new
26+
{
27+
scroll = "1m",
28+
scroll_id = _scrollId
29+
};
30+
31+
protected override int ExpectStatusCode => 200;
32+
protected override bool ExpectIsValid => true;
33+
protected override HttpMethod HttpMethod => HttpMethod.POST;
34+
protected override string UrlPath => $"/_search/scroll";
35+
protected override bool SupportsDeserialization => false;
36+
37+
protected override Func<ScrollDescriptor<Project>, IScrollRequest> Fluent => s => s.Scroll("1m").ScrollId(_scrollId);
38+
39+
protected override ScrollRequest Initializer => new ScrollRequest(_scrollId, "1m");
40+
41+
protected int _slice = 0;
42+
protected override void OnBeforeCall(IElasticClient client)
43+
{
44+
var maxSlices = 2; // number of shards we use by default for test indices
45+
var currentSlice = Interlocked.Increment(ref this._slice) % maxSlices;
46+
var scrollTimeout = TimeSpan.FromMinutes(1);
47+
var response = client.Search<Project>(s => s
48+
.Scroll(scrollTimeout)
49+
.Slice(ss=>ss.Max(maxSlices).Id(currentSlice))
50+
.Sort(ss=>ss.Field("_doc", SortOrder.Ascending))
51+
);
52+
if (!response.IsValid)
53+
throw new Exception("Scroll setup failed");
54+
_scrollId = response.ScrollId ?? _scrollId;
55+
}
56+
57+
protected override void OnAfterCall(IElasticClient client)
58+
{
59+
client.ClearScroll(cs => cs.ScrollId(_scrollId));
60+
}
61+
}
62+
}

src/Tests/Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,9 @@
654654
<Compile Include="Search\Percolator\UnregisterPercolator\UnregisterPercolatorUrlTests.cs" />
655655
<Compile Include="Reproduce\GithubIssue2052.cs" />
656656
<Compile Include="Reproduce\GithubIssue2173.cs" />
657+
<Compile Include="Search\Request\SlicedScrollSearchUsageTests.cs" />
657658
<Compile Include="Search\Request\SearchAfterUsageTests.cs" />
659+
<Compile Include="Search\Scroll\Scroll\SlicedScrollApiTests.cs" />
658660
<Compile Include="Search\Search\Rescoring\RescoreUsageTests.cs" />
659661
<Compile Include="Search\Search\InvalidSearchApiTests.cs" />
660662
<Compile Include="Search\Suggesters\SuggestUrlTests.cs" />

src/Tests/tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ force_reseed: true
1010
# do not spawn nodes as part of the test setup if we find a node is already running
1111
# this is opt in during development in CI we never want to see our tests running against an already running node
1212
test_against_already_running_elasticsearch: true
13+

0 commit comments

Comments
 (0)