Skip to content

Commit d6ccc8e

Browse files
authored
[Enhancement] optimize sort rewrite logic (#434)
Optimize sort rewrite logic by using first and last parameter in Composite aggregation. Signed-off-by: penghuo <[email protected]>
1 parent 1823cea commit d6ccc8e

File tree

11 files changed

+121
-113
lines changed

11 files changed

+121
-113
lines changed

docs/user/optimization/optimization.rst

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ The Aggregation operator will merge into OpenSearch Aggregation::
287287
{
288288
"name": "OpenSearchIndexScan",
289289
"description": {
290-
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
290+
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"missing_order\":\"first\",\"order\":\"asc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
291291
},
292292
"children": []
293293
}
@@ -313,49 +313,14 @@ The Sort operator will merge into OpenSearch Aggregation.::
313313
{
314314
"name": "OpenSearchIndexScan",
315315
"description": {
316-
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"order\":\"desc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
316+
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"missing_order\":\"last\",\"order\":\"desc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
317317
},
318318
"children": []
319319
}
320320
]
321321
}
322322
}
323323

324-
Because the OpenSearch Composite Aggregation order doesn't support separate NULL_FIRST/NULL_LAST option. only the default sort option (ASC NULL_FIRST/DESC NULL_LAST) will be supported for push down to OpenSearch Aggregation, otherwise it will fall back to the default memory based operator::
325-
326-
sh$ curl -sS -H 'Content-Type: application/json' \
327-
... -X POST localhost:9200/_plugins/_sql/_explain \
328-
... -d '{"query" : "SELECT gender, avg(age) FROM accounts GROUP BY gender ORDER BY gender ASC NULLS LAST"}'
329-
{
330-
"root": {
331-
"name": "ProjectOperator",
332-
"description": {
333-
"fields": "[gender, avg(age)]"
334-
},
335-
"children": [
336-
{
337-
"name": "SortOperator",
338-
"description": {
339-
"sortList": {
340-
"gender": {
341-
"sortOrder": "ASC",
342-
"nullOrder": "NULL_LAST"
343-
}
344-
}
345-
},
346-
"children": [
347-
{
348-
"name": "OpenSearchIndexScan",
349-
"description": {
350-
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
351-
},
352-
"children": []
353-
}
354-
]
355-
}
356-
]
357-
}
358-
}
359324

360325
Because the OpenSearch Composite Aggregation doesn't support order by metrics field, then if the sort list include fields which refer to metrics aggregation, then the sort operator can't be push down to OpenSearch Aggregation::
361326

@@ -383,7 +348,7 @@ Because the OpenSearch Composite Aggregation doesn't support order by metrics fi
383348
{
384349
"name": "OpenSearchIndexScan",
385350
"description": {
386-
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
351+
"request": "OpenSearchQueryRequest(indexName=accounts, sourceBuilder={\"from\":0,\"size\":0,\"timeout\":\"1m\",\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"gender\":{\"terms\":{\"field\":\"gender.keyword\",\"missing_bucket\":true,\"missing_order\":\"first\",\"order\":\"asc\"}}}]},\"aggregations\":{\"avg(age)\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone=false)"
387352
},
388353
"children": []
389354
}
@@ -408,4 +373,4 @@ At the moment there is no optimization to merge similar sort operators to avoid
408373

409374
Sort Push Down
410375
--------------
411-
Without sort push down optimization, the sort operator will sort the result from child operator. By default, only 200 docs will extracted from the source index, `you can change this value by using size_limit setting <../admin/settings.rst#opensearch-query-size-limit>`_.
376+
Without sort push down optimization, the sort operator will sort the result from child operator. By default, only 200 docs will extracted from the source index, `you can change this value by using size_limit setting <../admin/settings.rst#opensearch-query-size-limit>`_.

integ-test/src/test/resources/expectedOutput/ppl/explain_filter_agg_push.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{
99
"name": "OpenSearchIndexScan",
1010
"description": {
11-
"request": "OpenSearchQueryRequest(indexName\u003dopensearch-sql_test_index_account, sourceBuilder\u003d{\"from\":0,\"size\":0,\"timeout\":\"1m\",\"query\":{\"range\":{\"age\":{\"from\":30,\"to\":null,\"include_lower\":false,\"include_upper\":true,\"boost\":1.0}}},\"sort\":[{\"_doc\":{\"order\":\"asc\"}}],\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"state\":{\"terms\":{\"field\":\"state.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}},{\"city\":{\"terms\":{\"field\":\"city.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}}]},\"aggregations\":{\"avg_age\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone\u003dfalse)"
11+
"request": "OpenSearchQueryRequest(indexName\u003dopensearch-sql_test_index_account, sourceBuilder\u003d{\"from\":0,\"size\":0,\"timeout\":\"1m\",\"query\":{\"range\":{\"age\":{\"from\":30,\"to\":null,\"include_lower\":false,\"include_upper\":true,\"boost\":1.0}}},\"sort\":[{\"_doc\":{\"order\":\"asc\"}}],\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"state\":{\"terms\":{\"field\":\"state.keyword\",\"missing_bucket\":true,\"missing_order\":\"first\",\"order\":\"asc\"}}},{\"city\":{\"terms\":{\"field\":\"city.keyword\",\"missing_bucket\":true,\"missing_order\":\"first\",\"order\":\"asc\"}}}]},\"aggregations\":{\"avg_age\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone\u003dfalse)"
1212
},
1313
"children": []
1414
}

integ-test/src/test/resources/expectedOutput/ppl/explain_output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
{
3232
"name": "OpenSearchIndexScan",
3333
"description": {
34-
"request": "OpenSearchQueryRequest(indexName\u003dopensearch-sql_test_index_account, sourceBuilder\u003d{\"from\":0,\"size\":0,\"timeout\":\"1m\",\"query\":{\"range\":{\"age\":{\"from\":30,\"to\":null,\"include_lower\":false,\"include_upper\":true,\"boost\":1.0}}},\"sort\":[{\"_doc\":{\"order\":\"asc\"}}],\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"state\":{\"terms\":{\"field\":\"state.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}},{\"city\":{\"terms\":{\"field\":\"city.keyword\",\"missing_bucket\":true,\"order\":\"asc\"}}}]},\"aggregations\":{\"avg_age\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone\u003dfalse)"
34+
"request": "OpenSearchQueryRequest(indexName\u003dopensearch-sql_test_index_account, sourceBuilder\u003d{\"from\":0,\"size\":0,\"timeout\":\"1m\",\"query\":{\"range\":{\"age\":{\"from\":30,\"to\":null,\"include_lower\":false,\"include_upper\":true,\"boost\":1.0}}},\"sort\":[{\"_doc\":{\"order\":\"asc\"}}],\"aggregations\":{\"composite_buckets\":{\"composite\":{\"size\":1000,\"sources\":[{\"state\":{\"terms\":{\"field\":\"state.keyword\",\"missing_bucket\":true,\"missing_order\":\"first\",\"order\":\"asc\"}}},{\"city\":{\"terms\":{\"field\":\"city.keyword\",\"missing_bucket\":true,\"missing_order\":\"first\",\"order\":\"asc\"}}}]},\"aggregations\":{\"avg_age\":{\"avg\":{\"field\":\"age\"}}}}}}, searchDone\u003dfalse)"
3535
},
3636
"children": []
3737
}

opensearch/src/main/java/org/opensearch/sql/opensearch/planner/logical/rule/MergeSortAndIndexAgg.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public MergeSortAndIndexAgg() {
4747

4848
this.pattern = typeOf(LogicalSort.class)
4949
.matching(OptimizationRuleUtils::sortByFieldsOnly)
50-
.matching(OptimizationRuleUtils::sortByDefaultOptionOnly)
5150
.matching(sort -> {
5251
sortRef.set(sort);
5352
return true;

opensearch/src/main/java/org/opensearch/sql/opensearch/planner/logical/rule/OptimizationRuleUtils.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.util.List;
1212
import java.util.Set;
1313
import lombok.experimental.UtilityClass;
14-
import org.opensearch.sql.ast.tree.Sort;
1514
import org.opensearch.sql.expression.ExpressionNodeVisitor;
1615
import org.opensearch.sql.expression.NamedExpression;
1716
import org.opensearch.sql.expression.ReferenceExpression;
@@ -32,20 +31,6 @@ public static boolean sortByFieldsOnly(LogicalSort logicalSort) {
3231
.reduce(true, Boolean::logicalAnd);
3332
}
3433

35-
/**
36-
* Does the sort list has option other than default options.
37-
* The default sort options are (ASC NULL_FIRST) or (DESC NULL LAST)
38-
*
39-
* @param logicalSort LogicalSort.
40-
* @return true sort list only option default options, otherwise false.
41-
*/
42-
public static boolean sortByDefaultOptionOnly(LogicalSort logicalSort) {
43-
return logicalSort.getSortList().stream()
44-
.map(sort -> Sort.SortOption.DEFAULT_ASC.equals(sort.getLeft())
45-
|| Sort.SortOption.DEFAULT_DESC.equals(sort.getLeft()))
46-
.reduce(true, Boolean::logicalAnd);
47-
}
48-
4934
/**
5035
* Find reference expression from expression.
5136
* @param expressions a list of expression.

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/aggregation/AggregationQueryBuilder.java

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
import java.util.HashMap;
1515
import java.util.List;
1616
import java.util.Map;
17-
import java.util.function.Function;
1817
import java.util.stream.Collectors;
1918
import lombok.RequiredArgsConstructor;
2019
import org.apache.commons.lang3.tuple.Pair;
20+
import org.apache.commons.lang3.tuple.Triple;
2121
import org.opensearch.search.aggregations.AggregationBuilder;
2222
import org.opensearch.search.aggregations.AggregationBuilders;
2323
import org.opensearch.search.aggregations.AggregatorFactories;
24+
import org.opensearch.search.aggregations.bucket.missing.MissingOrder;
2425
import org.opensearch.search.sort.SortOrder;
2526
import org.opensearch.sql.ast.tree.Sort;
2627
import org.opensearch.sql.data.type.ExprType;
@@ -92,7 +93,9 @@ public AggregationQueryBuilder(
9293
bucketBuilder.build(
9394
groupByList.stream()
9495
.sorted(groupSortOrder)
95-
.map(expr -> Pair.of(expr, groupSortOrder.apply(expr)))
96+
.map(expr -> Triple.of(expr,
97+
groupSortOrder.sortOrder(expr),
98+
groupSortOrder.missingOrder(expr)))
9699
.collect(Collectors.toList())))
97100
.subAggregations(metrics.getLeft())
98101
.size(AGGREGATION_BUCKET_SIZE)),
@@ -112,27 +115,37 @@ public Map<String, ExprType> buildTypeMapping(
112115
return builder.build();
113116
}
114117

118+
/**
119+
* Group By field sort order.
120+
*/
115121
@VisibleForTesting
116-
public static class GroupSortOrder implements Comparator<NamedExpression>,
117-
Function<NamedExpression, SortOrder> {
122+
public static class GroupSortOrder implements Comparator<NamedExpression> {
118123

119124
/**
120125
* The default order of group field.
121126
* The order is ASC NULL_FIRST.
122127
* The field should be the last one in the group list.
123128
*/
124-
private static final Pair<SortOrder, Integer> DEFAULT_ORDER =
125-
Pair.of(SortOrder.ASC, Integer.MAX_VALUE);
129+
private static final Pair<Sort.SortOption, Integer> DEFAULT_ORDER =
130+
Pair.of(Sort.SortOption.DEFAULT_ASC, Integer.MAX_VALUE);
131+
132+
/**
133+
* The mapping between {@link Sort.SortOrder} and {@link SortOrder}.
134+
*/
135+
private static final Map<Sort.SortOrder, SortOrder> SORT_MAP =
136+
new ImmutableMap.Builder<Sort.SortOrder, SortOrder>()
137+
.put(Sort.SortOrder.ASC, SortOrder.ASC)
138+
.put(Sort.SortOrder.DESC, SortOrder.DESC).build();
126139

127140
/**
128-
* The mapping betwen {@link Sort.SortOption} and {@link SortOrder}.
141+
* The mapping between {@link Sort.NullOrder} and {@link MissingOrder}.
129142
*/
130-
private static final Map<Sort.SortOption, SortOrder> SORT_MAP =
131-
new ImmutableMap.Builder<Sort.SortOption, SortOrder>()
132-
.put(Sort.SortOption.DEFAULT_ASC, SortOrder.ASC)
133-
.put(Sort.SortOption.DEFAULT_DESC, SortOrder.DESC).build();
143+
private static final Map<Sort.NullOrder, MissingOrder> NULL_MAP =
144+
new ImmutableMap.Builder<Sort.NullOrder, MissingOrder>()
145+
.put(Sort.NullOrder.NULL_FIRST, MissingOrder.FIRST)
146+
.put(Sort.NullOrder.NULL_LAST, MissingOrder.LAST).build();
134147

135-
private final Map<String, Pair<SortOrder, Integer>> map = new HashMap<>();
148+
private final Map<String, Pair<Sort.SortOption, Integer>> map = new HashMap<>();
136149

137150
/**
138151
* Constructor of GroupSortOrder.
@@ -144,7 +157,7 @@ public GroupSortOrder(List<Pair<Sort.SortOption, Expression>> sortList) {
144157
int pos = 0;
145158
for (Pair<Sort.SortOption, Expression> sortPair : sortList) {
146159
map.put(((ReferenceExpression) sortPair.getRight()).getAttr(),
147-
Pair.of(SORT_MAP.getOrDefault(sortPair.getLeft(), SortOrder.ASC), pos++));
160+
Pair.of(sortPair.getLeft(), pos++));
148161
}
149162
}
150163

@@ -161,9 +174,9 @@ public GroupSortOrder(List<Pair<Sort.SortOption, Expression>> sortList) {
161174
*/
162175
@Override
163176
public int compare(NamedExpression o1, NamedExpression o2) {
164-
final Pair<SortOrder, Integer> o1Value =
177+
final Pair<Sort.SortOption, Integer> o1Value =
165178
map.getOrDefault(o1.getName(), DEFAULT_ORDER);
166-
final Pair<SortOrder, Integer> o2Value =
179+
final Pair<Sort.SortOption, Integer> o2Value =
167180
map.getOrDefault(o2.getName(), DEFAULT_ORDER);
168181
return o1Value.getRight().compareTo(o2Value.getRight());
169182
}
@@ -172,8 +185,19 @@ public int compare(NamedExpression o1, NamedExpression o2) {
172185
* Get the {@link SortOrder} for expression.
173186
* By default, the {@link SortOrder} is ASC.
174187
*/
175-
@Override
176-
public SortOrder apply(NamedExpression expression) {
188+
public SortOrder sortOrder(NamedExpression expression) {
189+
return SORT_MAP.get(sortOption(expression).getSortOrder());
190+
}
191+
192+
/**
193+
* Get the {@link MissingOrder} for expression.
194+
* By default, the {@link MissingOrder} is ASC missing first / DESC missing last.
195+
*/
196+
public MissingOrder missingOrder(NamedExpression expression) {
197+
return NULL_MAP.get(sortOption(expression).getNullOrder());
198+
}
199+
200+
private Sort.SortOption sortOption(NamedExpression expression) {
177201
return map.getOrDefault(expression.getName(), DEFAULT_ORDER).getLeft();
178202
}
179203
}

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/script/aggregation/dsl/BucketAggregationBuilder.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
import com.google.common.collect.ImmutableList;
99
import java.util.List;
10-
import org.apache.commons.lang3.tuple.Pair;
10+
import org.apache.commons.lang3.tuple.Triple;
1111
import org.opensearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
1212
import org.opensearch.search.aggregations.bucket.composite.DateHistogramValuesSourceBuilder;
1313
import org.opensearch.search.aggregations.bucket.composite.HistogramValuesSourceBuilder;
1414
import org.opensearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
1515
import org.opensearch.search.aggregations.bucket.histogram.DateHistogramInterval;
16+
import org.opensearch.search.aggregations.bucket.missing.MissingOrder;
1617
import org.opensearch.search.sort.SortOrder;
1718
import org.opensearch.sql.ast.expression.SpanUnit;
1819
import org.opensearch.sql.expression.NamedExpression;
@@ -35,50 +36,56 @@ public BucketAggregationBuilder(
3536
* Build the list of CompositeValuesSourceBuilder.
3637
*/
3738
public List<CompositeValuesSourceBuilder<?>> build(
38-
List<Pair<NamedExpression, SortOrder>> groupList) {
39+
List<Triple<NamedExpression, SortOrder, MissingOrder>> groupList) {
3940
ImmutableList.Builder<CompositeValuesSourceBuilder<?>> resultBuilder =
4041
new ImmutableList.Builder<>();
41-
for (Pair<NamedExpression, SortOrder> groupPair : groupList) {
42+
for (Triple<NamedExpression, SortOrder, MissingOrder> groupPair : groupList) {
4243
resultBuilder.add(
43-
buildCompositeValuesSourceBuilder(groupPair.getLeft(), groupPair.getRight()));
44+
buildCompositeValuesSourceBuilder(groupPair.getLeft(),
45+
groupPair.getMiddle(), groupPair.getRight()));
4446
}
4547
return resultBuilder.build();
4648
}
4749

4850
// todo, Expression should implement buildCompositeValuesSourceBuilder() interface.
4951
private CompositeValuesSourceBuilder<?> buildCompositeValuesSourceBuilder(
50-
NamedExpression expr, SortOrder order) {
52+
NamedExpression expr, SortOrder sortOrder, MissingOrder missingOrder) {
5153
if (expr.getDelegated() instanceof SpanExpression) {
5254
SpanExpression spanExpr = (SpanExpression) expr.getDelegated();
5355
return buildHistogram(
5456
expr.getNameOrAlias(),
5557
spanExpr.getField().toString(),
5658
spanExpr.getValue().valueOf(null).doubleValue(),
57-
spanExpr.getUnit());
59+
spanExpr.getUnit(),
60+
missingOrder);
5861
} else {
5962
CompositeValuesSourceBuilder<?> sourceBuilder =
60-
new TermsValuesSourceBuilder(expr.getNameOrAlias()).missingBucket(true).order(order);
63+
new TermsValuesSourceBuilder(expr.getNameOrAlias())
64+
.missingBucket(true)
65+
.missingOrder(missingOrder)
66+
.order(sortOrder);
6167
return helper.build(expr.getDelegated(), sourceBuilder::field, sourceBuilder::script);
6268
}
6369
}
6470

6571
private CompositeValuesSourceBuilder<?> buildHistogram(
66-
String name, String field, Double value, SpanUnit unit) {
72+
String name, String field, Double value, SpanUnit unit, MissingOrder missingOrder) {
6773
switch (unit) {
6874
case NONE:
6975
return new HistogramValuesSourceBuilder(name)
7076
.field(field)
7177
.interval(value)
72-
.missingBucket(true);
78+
.missingBucket(true)
79+
.missingOrder(missingOrder);
7380
case UNKNOWN:
7481
throw new IllegalStateException("Invalid span unit");
7582
default:
76-
return buildDateHistogram(name, field, value.intValue(), unit);
83+
return buildDateHistogram(name, field, value.intValue(), unit, missingOrder);
7784
}
7885
}
7986

8087
private CompositeValuesSourceBuilder<?> buildDateHistogram(
81-
String name, String field, Integer value, SpanUnit unit) {
88+
String name, String field, Integer value, SpanUnit unit, MissingOrder missingOrder) {
8289
String spanValue = value + unit.getName();
8390
switch (unit) {
8491
case MILLISECOND:
@@ -94,11 +101,13 @@ private CompositeValuesSourceBuilder<?> buildDateHistogram(
94101
return new DateHistogramValuesSourceBuilder(name)
95102
.field(field)
96103
.missingBucket(true)
104+
.missingOrder(missingOrder)
97105
.fixedInterval(new DateHistogramInterval(spanValue));
98106
default:
99107
return new DateHistogramValuesSourceBuilder(name)
100108
.field(field)
101109
.missingBucket(true)
110+
.missingOrder(missingOrder)
102111
.calendarInterval(new DateHistogramInterval(spanValue));
103112
}
104113
}

0 commit comments

Comments
 (0)