Skip to content

Commit 174fa45

Browse files
authored
Integrate "fields" API into QL (#68467)
1 parent 8b51548 commit 174fa45

File tree

29 files changed

+321
-1070
lines changed

29 files changed

+321
-1070
lines changed

docs/reference/sql/endpoints/translate.asciidoc

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@ Which returns:
2222
--------------------------------------------------
2323
{
2424
"size": 10,
25-
"docvalue_fields": [
25+
"_source": false,
26+
"fields": [
27+
{
28+
"field": "author"
29+
},
30+
{
31+
"field": "name"
32+
},
33+
{
34+
"field": "page_count"
35+
},
2636
{
2737
"field": "release_date",
2838
"format": "epoch_millis"
2939
}
3040
],
31-
"_source": {
32-
"includes": [
33-
"author",
34-
"name",
35-
"page_count"
36-
],
37-
"excludes": []
38-
},
3941
"sort": [
4042
{
4143
"page_count": {

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/execution/search/RuntimeUtils.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public static List<HitExtractor> createExtractor(List<FieldExtraction> fields, E
117117
public static HitExtractor createExtractor(FieldExtraction ref, EqlConfiguration cfg) {
118118
if (ref instanceof SearchHitFieldRef) {
119119
SearchHitFieldRef f = (SearchHitFieldRef) ref;
120-
return new FieldHitExtractor(f.name(), f.fullFieldName(), f.getDataType(), cfg.zoneId(), f.useDocValue(), f.hitName(), false);
120+
return new FieldHitExtractor(f.name(), f.getDataType(), cfg.zoneId(), f.hitName(), false);
121121
}
122122

123123
if (ref instanceof ComputedRef) {
@@ -150,11 +150,11 @@ public static SearchRequest prepareRequest(Client client,
150150
boolean includeFrozen,
151151
String... indices) {
152152
return client.prepareSearch(indices)
153-
.setSource(source)
154-
.setAllowPartialSearchResults(false)
155-
.setIndicesOptions(
156-
includeFrozen ? IndexResolver.FIELD_CAPS_FROZEN_INDICES_OPTIONS : IndexResolver.FIELD_CAPS_INDICES_OPTIONS)
157-
.request();
153+
.setSource(source)
154+
.setAllowPartialSearchResults(false)
155+
.setIndicesOptions(
156+
includeFrozen ? IndexResolver.FIELD_CAPS_FROZEN_INDICES_OPTIONS : IndexResolver.FIELD_CAPS_INDICES_OPTIONS)
157+
.request();
158158
}
159159

160160
public static List<SearchHit> searchHits(SearchResponse response) {

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/execution/search/SourceGenerator.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
*/
77
package org.elasticsearch.xpack.eql.execution.search;
88

9-
import org.elasticsearch.common.util.CollectionUtils;
109
import org.elasticsearch.index.query.QueryBuilder;
1110
import org.elasticsearch.search.builder.SearchSourceBuilder;
12-
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
1311
import org.elasticsearch.search.sort.FieldSortBuilder;
1412
import org.elasticsearch.search.sort.NestedSortBuilder;
1513
import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType;
@@ -60,13 +58,8 @@ public static SearchSourceBuilder sourceBuilder(QueryContainer container, QueryB
6058

6159
sorting(container, source);
6260

63-
// disable the source if there are no includes
64-
if (source.fetchSource() == null || CollectionUtils.isEmpty(source.fetchSource().includes())) {
65-
source.fetchSource(FetchSourceContext.DO_NOT_FETCH_SOURCE);
66-
} else {
67-
// use true to fetch only the needed bits from the source
68-
source.fetchSource(true);
69-
}
61+
// disable the source, as we rely on "fields" API
62+
source.fetchSource(false);
7063

7164
if (container.limit() != null) {
7265
// add size and from

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/execution/search/extractor/FieldHitExtractor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ public FieldHitExtractor(StreamInput in) throws IOException {
2929
super(in);
3030
}
3131

32-
public FieldHitExtractor(String name, String fullFieldName, DataType dataType, ZoneId zoneId, boolean useDocValue, String hitName,
32+
public FieldHitExtractor(String name, DataType dataType, ZoneId zoneId, String hitName,
3333
boolean arrayLeniency) {
34-
super(name, fullFieldName, dataType, zoneId, useDocValue, hitName, arrayLeniency);
34+
super(name, dataType, zoneId, hitName, arrayLeniency);
3535
}
3636

3737
@Override

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/execution/search/extractor/TimestampFieldHitExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
public class TimestampFieldHitExtractor extends FieldHitExtractor {
1111

1212
public TimestampFieldHitExtractor(FieldHitExtractor target) {
13-
super(target.fieldName(), target.fullFieldName(), target.dataType(), target.zoneId(), target.useDocValues(), target.hitName(),
13+
super(target.fieldName(), target.dataType(), target.zoneId(), target.hitName(),
1414
target.arrayLeniency());
1515
}
1616

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/querydsl/container/FieldExtractorRegistry.java

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.elasticsearch.xpack.ql.expression.Expressions;
1414
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
1515
import org.elasticsearch.xpack.ql.expression.gen.pipeline.ConstantInput;
16-
import org.elasticsearch.xpack.ql.type.DataTypes;
1716

1817
import java.util.HashMap;
1918
import java.util.Map;
@@ -46,39 +45,6 @@ private FieldExtraction createFieldExtractionFor(Expression expression) {
4645
}
4746

4847
private FieldExtraction topHitFieldExtractor(FieldAttribute fieldAttr) {
49-
FieldAttribute actualField = fieldAttr;
50-
FieldAttribute rootField = fieldAttr;
51-
StringBuilder fullFieldName = new StringBuilder(fieldAttr.field().getName());
52-
53-
// Only if the field is not an alias (in which case it will be taken out from docvalue_fields if it's isAggregatable()),
54-
// go up the tree of parents until a non-object (and non-nested) type of field is found and use that specific parent
55-
// as the field to extract data from, from _source. We do it like this because sub-fields are not in the _source, only
56-
// the root field to which those sub-fields belong to, are. Instead of "text_field.keyword_subfield" for _source extraction,
57-
// we use "text_field", because there is no source for "keyword_subfield".
58-
/*
59-
* "text_field": {
60-
* "type": "text",
61-
* "fields": {
62-
* "keyword_subfield": {
63-
* "type": "keyword"
64-
* }
65-
* }
66-
* }
67-
*/
68-
if (fieldAttr.field().isAlias() == false) {
69-
while (actualField.parent() != null
70-
&& actualField.parent().field().getDataType() != DataTypes.OBJECT
71-
&& actualField.parent().field().getDataType() != DataTypes.NESTED
72-
&& actualField.field().getDataType().hasDocValues() == false) {
73-
actualField = actualField.parent();
74-
}
75-
}
76-
while (rootField.parent() != null) {
77-
fullFieldName.insert(0, ".").insert(0, rootField.parent().field().getName());
78-
rootField = rootField.parent();
79-
}
80-
81-
return new SearchHitFieldRef(actualField.name(), fullFieldName.toString(), fieldAttr.field().getDataType(),
82-
fieldAttr.field().isAggregatable(), fieldAttr.field().isAlias());
48+
return new SearchHitFieldRef(fieldAttr.name(), fieldAttr.field().getDataType(), fieldAttr.field().isAlias());
8349
}
8450
}

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/querydsl/container/SearchHitFieldRef.java

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,23 @@
1313

1414
import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME;
1515
import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME_NANOS;
16-
import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD;
1716

1817
// NB: this class is taken from SQL - it hasn't been ported over to QL
1918
// since at this stage is unclear whether the whole FieldExtraction infrastructure
2019
// needs porting or just the field extraction
2120
public class SearchHitFieldRef implements FieldExtraction {
2221

2322
private final String name;
24-
private final String fullFieldName; // path included. If field full path is a.b.c, full field name is "a.b.c" and name is "c"
2523
private final DataType dataType;
26-
private final boolean docValue;
2724
private final String hitName;
2825

29-
public SearchHitFieldRef(String name, String fullFieldName, DataType dataType, boolean useDocValueInsteadOfSource, boolean isAlias) {
30-
this(name, fullFieldName, dataType, useDocValueInsteadOfSource, isAlias, null);
26+
public SearchHitFieldRef(String name, DataType dataType, boolean isAlias) {
27+
this(name, dataType, isAlias, null);
3128
}
3229

33-
public SearchHitFieldRef(String name, String fullFieldName, DataType dataType, boolean useDocValueInsteadOfSource, boolean isAlias,
34-
String hitName) {
30+
public SearchHitFieldRef(String name, DataType dataType, boolean isAlias, String hitName) {
3531
this.name = name;
36-
this.fullFieldName = fullFieldName;
3732
this.dataType = dataType;
38-
// these field types can only be extracted from docvalue_fields (ie, values already computed by Elasticsearch)
39-
// because, for us to be able to extract them from _source, we would need the mapping of those fields (which we don't have)
40-
this.docValue = isAlias ? useDocValueInsteadOfSource : (hasDocValues(dataType) ? useDocValueInsteadOfSource : false);
4133
this.hitName = hitName;
4234
}
4335

@@ -49,29 +41,17 @@ public String name() {
4941
return name;
5042
}
5143

52-
public String fullFieldName() {
53-
return fullFieldName;
54-
}
55-
5644
public DataType getDataType() {
5745
return dataType;
5846
}
5947

60-
public boolean useDocValue() {
61-
return docValue;
62-
}
63-
6448
@Override
6549
public void collectFields(QlSourceBuilder sourceBuilder) {
6650
// nested fields are handled by inner hits
6751
if (hitName != null) {
6852
return;
6953
}
70-
if (docValue) {
71-
sourceBuilder.addDocField(name, format(dataType));
72-
} else {
73-
sourceBuilder.addSourceField(name);
74-
}
54+
sourceBuilder.addFetchField(name, format(dataType));
7555
}
7656

7757
@Override
@@ -84,10 +64,6 @@ public String toString() {
8464
return name;
8565
}
8666

87-
private static boolean hasDocValues(DataType dataType) {
88-
return dataType == KEYWORD || dataType == DATETIME || dataType == DATETIME_NANOS;
89-
}
90-
9167
private static String format(DataType dataType) {
9268
if (dataType == DATETIME_NANOS) {
9369
return "strict_date_optional_time_nanos";

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/search/CriterionOrdinalExtractionTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public class CriterionOrdinalExtractionTests extends ESTestCase {
3030
private String tsField = "timestamp";
3131
private String tbField = "tiebreaker";
3232

33-
private HitExtractor tsExtractor = new FieldHitExtractor(tsField, tsField, DataTypes.LONG, null, true, null, false);
34-
private HitExtractor tbExtractor = new FieldHitExtractor(tbField, tbField, DataTypes.LONG, null, true, null, false);
33+
private HitExtractor tsExtractor = new FieldHitExtractor(tsField, DataTypes.LONG, null, null, false);
34+
private HitExtractor tbExtractor = new FieldHitExtractor(tbField, DataTypes.LONG, null, null, false);
3535

3636
public void testTimeOnly() throws Exception {
3737
long time = randomLong();
@@ -56,7 +56,7 @@ public void testTimeAndTiebreakerNull() throws Exception {
5656
}
5757

5858
public void testTimeNotComparable() throws Exception {
59-
HitExtractor badExtractor = new FieldHitExtractor(tsField, tsField, DataTypes.BINARY, null, true, null, false);
59+
HitExtractor badExtractor = new FieldHitExtractor(tsField, DataTypes.BINARY, null, null, false);
6060
SearchHit hit = searchHit(randomAlphaOfLength(10), null);
6161
Criterion<BoxedQueryRequest> criterion = new Criterion<BoxedQueryRequest>(0, null, emptyList(), badExtractor, null, false);
6262
EqlIllegalArgumentException exception = expectThrows(EqlIllegalArgumentException.class, () -> criterion.ordinal(hit));

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/execution/search/QlSourceBuilder.java

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77
package org.elasticsearch.xpack.ql.execution.search;
88

9-
import org.elasticsearch.common.Strings;
109
import org.elasticsearch.script.Script;
1110
import org.elasticsearch.search.builder.SearchSourceBuilder;
1211
import org.elasticsearch.search.fetch.subphase.FieldAndFormat;
@@ -23,8 +22,7 @@
2322
*/
2423
public class QlSourceBuilder {
2524
// The LinkedHashMaps preserve the order of the fields in the response
26-
private final Set<String> sourceFields = new LinkedHashSet<>();
27-
private final Set<FieldAndFormat> docFields = new LinkedHashSet<>();
25+
private final Set<FieldAndFormat> fetchFields = new LinkedHashSet<>();
2826
private final Map<String, Script> scriptFields = new LinkedHashMap<>();
2927

3028
boolean trackScores = false;
@@ -40,17 +38,10 @@ public void trackScores() {
4038
}
4139

4240
/**
43-
* Retrieve the requested field from the {@code _source} of the document
41+
* Retrieve the requested field using the "fields" API
4442
*/
45-
public void addSourceField(String field) {
46-
sourceFields.add(field);
47-
}
48-
49-
/**
50-
* Retrieve the requested field from doc values (or fielddata) of the document
51-
*/
52-
public void addDocField(String field, String format) {
53-
docFields.add(new FieldAndFormat(field, format));
43+
public void addFetchField(String field, String format) {
44+
fetchFields.add(new FieldAndFormat(field, format));
5445
}
5546

5647
/**
@@ -66,14 +57,7 @@ public void addScriptField(String name, Script script) {
6657
*/
6758
public void build(SearchSourceBuilder sourceBuilder) {
6859
sourceBuilder.trackScores(this.trackScores);
69-
if (!sourceFields.isEmpty()) {
70-
sourceBuilder.fetchSource(sourceFields.toArray(Strings.EMPTY_ARRAY), null);
71-
}
72-
docFields.forEach(field -> sourceBuilder.docValueField(field.field, field.format));
60+
fetchFields.forEach(field -> sourceBuilder.fetchField(new FieldAndFormat(field.field, field.format, null)));
7361
scriptFields.forEach(sourceBuilder::scriptField);
7462
}
75-
76-
public boolean noSource() {
77-
return sourceFields.isEmpty();
78-
}
7963
}

0 commit comments

Comments
 (0)