1212import java .io .IOException ;
1313import java .util .Collections ;
1414import java .util .List ;
15+ import java .util .Map ;
1516import java .util .function .Consumer ;
1617import java .util .function .Function ;
1718import lombok .EqualsAndHashCode ;
2829import org .opensearch .search .SearchHit ;
2930import org .opensearch .search .SearchHits ;
3031import org .opensearch .search .SearchModule ;
32+ import org .opensearch .search .aggregations .Aggregation ;
33+ import org .opensearch .search .aggregations .bucket .composite .CompositeAggregation ;
34+ import org .opensearch .search .aggregations .bucket .composite .CompositeAggregationBuilder ;
3135import org .opensearch .search .builder .PointInTimeBuilder ;
3236import org .opensearch .search .builder .SearchSourceBuilder ;
3337import org .opensearch .search .sort .FieldSortBuilder ;
@@ -75,34 +79,42 @@ public class OpenSearchQueryRequest implements OpenSearchRequest {
7579
7680 private SearchResponse searchResponse = null ;
7781
78- /** Constructor of OpenSearchQueryRequest. */
79- public OpenSearchQueryRequest (
80- String indexName , int size , OpenSearchExprValueFactory factory , List <String > includes ) {
81- this (new IndexName (indexName ), size , factory , includes );
82- }
82+ private boolean paginatingAgg ;
8383
84- /** Constructor of OpenSearchQueryRequest. */
85- public OpenSearchQueryRequest (
86- IndexName indexName , int size , OpenSearchExprValueFactory factory , List <String > includes ) {
87- this .indexName = indexName ;
88- this .sourceBuilder = new SearchSourceBuilder ();
84+ private Map <String , Object > afterKey ;
85+
86+ /** For testing only. */
87+ static OpenSearchQueryRequest of (
88+ String indexName , int size , OpenSearchExprValueFactory factory , List <String > includes ) {
89+ SearchSourceBuilder sourceBuilder = new SearchSourceBuilder ();
8990 sourceBuilder .from (0 );
9091 sourceBuilder .size (size );
9192 sourceBuilder .timeout (DEFAULT_QUERY_TIMEOUT );
92- this .exprValueFactory = factory ;
93- this .includes = includes ;
93+ return of (indexName , sourceBuilder , factory , includes );
9494 }
9595
96- /** Constructor of OpenSearchQueryRequest. */
96+ /** For testing only. */
97+ static OpenSearchQueryRequest of (
98+ String indexName ,
99+ SearchSourceBuilder sourceBuilder ,
100+ OpenSearchExprValueFactory factory ,
101+ List <String > includes ) {
102+ return new OpenSearchQueryRequest (
103+ new IndexName (indexName ), sourceBuilder , factory , includes , false );
104+ }
105+
106+ /** Constructor of OpenSearchQueryRequest without PIT support. */
97107 public OpenSearchQueryRequest (
98108 IndexName indexName ,
99109 SearchSourceBuilder sourceBuilder ,
100110 OpenSearchExprValueFactory factory ,
101- List <String > includes ) {
111+ List <String > includes ,
112+ boolean paginatingAgg ) {
102113 this .indexName = indexName ;
103114 this .sourceBuilder = sourceBuilder ;
104115 this .exprValueFactory = factory ;
105116 this .includes = includes ;
117+ this .paginatingAgg = paginatingAgg ;
106118 }
107119
108120 /** Constructor of OpenSearchQueryRequest with PIT support. */
@@ -119,6 +131,7 @@ public OpenSearchQueryRequest(
119131 this .includes = includes ;
120132 this .cursorKeepAlive = cursorKeepAlive ;
121133 this .pitId = pitId ;
134+ this .paginatingAgg = false ; // mutex with pitId
122135 }
123136
124137 /** true if the request is a count aggregation request. */
@@ -170,28 +183,67 @@ public OpenSearchQueryRequest(StreamInput in, OpenSearchStorageEngine engine) th
170183 public OpenSearchResponse search (
171184 Function <SearchRequest , SearchResponse > searchAction ,
172185 Function <SearchScrollRequest , SearchResponse > scrollAction ) {
173- if (this .pitId == null ) {
174- // When SearchRequest doesn't contain PitId, fetch single page request
175- if (searchDone ) {
176- return new OpenSearchResponse (
177- SearchHits .empty (), exprValueFactory , includes , isCountAggRequest ());
178- } else {
179- // get the value before set searchDone = true
180- boolean isCountAggRequest = isCountAggRequest ();
181- searchDone = true ;
182- return new OpenSearchResponse (
183- searchAction .apply (
184- new SearchRequest ().indices (indexName .getIndexNames ()).source (sourceBuilder )),
185- exprValueFactory ,
186- includes ,
187- isCountAggRequest );
188- }
186+ if (paginatingAgg ) {
187+ return searchPaginatingAgg (searchAction );
188+ } else if (this .pitId == null ) {
189+ return searchSinglePage (searchAction );
189190 } else {
190191 // Search with PIT instead of scroll API
191192 return searchWithPIT (searchAction );
192193 }
193194 }
194195
196+ private OpenSearchResponse searchSinglePage (
197+ Function <SearchRequest , SearchResponse > searchAction ) {
198+ // When SearchRequest doesn't contain PitId, fetch single page request
199+ if (searchDone ) {
200+ return new OpenSearchResponse (
201+ SearchHits .empty (), exprValueFactory , includes , isCountAggRequest ());
202+ } else {
203+ // get the value before set searchDone = true
204+ boolean isCountAggRequest = isCountAggRequest ();
205+ searchDone = true ;
206+ return new OpenSearchResponse (
207+ searchAction .apply (
208+ new SearchRequest ().indices (indexName .getIndexNames ()).source (sourceBuilder )),
209+ exprValueFactory ,
210+ includes ,
211+ isCountAggRequest );
212+ }
213+ }
214+
215+ private OpenSearchResponse searchPaginatingAgg (
216+ Function <SearchRequest , SearchResponse > searchAction ) {
217+ OpenSearchResponse openSearchResponse ;
218+ if (searchDone ) {
219+ openSearchResponse =
220+ new OpenSearchResponse (
221+ SearchHits .empty (), exprValueFactory , includes , isCountAggRequest ());
222+ } else {
223+ SearchRequest searchRequest =
224+ new SearchRequest ().indices (indexName .getIndexNames ()).source (this .sourceBuilder );
225+ this .searchResponse = searchAction .apply (searchRequest );
226+
227+ openSearchResponse =
228+ new OpenSearchResponse (
229+ this .searchResponse , exprValueFactory , includes , isCountAggRequest ());
230+ // get the value before set searchDone = true
231+ boolean isCountAggRequest = isCountAggRequest ();
232+ needClean = openSearchResponse .isEmpty ();
233+ searchDone = openSearchResponse .isEmpty ();
234+ Aggregation agg = this .searchResponse .getAggregations ().asList ().get (0 );
235+ if (agg instanceof CompositeAggregation compositeAgg ) {
236+ afterKey = compositeAgg .afterKey ();
237+ if (afterKey != null && !afterKey .isEmpty ()) {
238+ this .sourceBuilder .aggregations ().getAggregatorFactories ().stream ()
239+ .filter (b -> b instanceof CompositeAggregationBuilder )
240+ .forEach (c -> ((CompositeAggregationBuilder ) c ).aggregateAfter (afterKey ));
241+ }
242+ }
243+ }
244+ return openSearchResponse ;
245+ }
246+
195247 public OpenSearchResponse searchWithPIT (Function <SearchRequest , SearchResponse > searchAction ) {
196248 OpenSearchResponse openSearchResponse ;
197249 if (searchDone ) {
@@ -252,6 +304,8 @@ public void clean(Consumer<String> cleanAction) {
252304 }
253305 } finally {
254306 this .pitId = null ;
307+ this .searchAfter = null ;
308+ this .afterKey = null ;
255309 }
256310 }
257311
@@ -264,6 +318,8 @@ public void forceClean(Consumer<String> cleanAction) {
264318 }
265319 } finally {
266320 this .pitId = null ;
321+ this .searchAfter = null ;
322+ this .afterKey = null ;
267323 }
268324 }
269325
0 commit comments