-
Notifications
You must be signed in to change notification settings - Fork 25.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add synthetic_source
support to aggregate_metric_double
fields
#88909
Changes from 5 commits
0497813
7571ad4
4e9f616
8d62217
f2c02d8
86196bc
24e9bba
bef49b4
34ad768
faaa6c1
e5c18ad
0a9322a
97dcc6d
70dd5a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pr: 88909 | ||
summary: Add `synthetic_source` support to `aggregate_metric_double` fields | ||
area: Mapping | ||
type: enhancement | ||
issues: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,9 @@ | |
import org.apache.lucene.index.DocValues; | ||
import org.apache.lucene.index.IndexReader; | ||
import org.apache.lucene.index.IndexableField; | ||
import org.apache.lucene.index.LeafReader; | ||
import org.apache.lucene.index.LeafReaderContext; | ||
import org.apache.lucene.index.NumericDocValues; | ||
import org.apache.lucene.index.SortedNumericDocValues; | ||
import org.apache.lucene.search.Query; | ||
import org.apache.lucene.search.SortField; | ||
|
@@ -32,6 +34,7 @@ | |
import org.elasticsearch.index.mapper.MapperBuilderContext; | ||
import org.elasticsearch.index.mapper.NumberFieldMapper; | ||
import org.elasticsearch.index.mapper.SimpleMappedFieldType; | ||
import org.elasticsearch.index.mapper.SourceLoader; | ||
import org.elasticsearch.index.mapper.SourceValueFetcher; | ||
import org.elasticsearch.index.mapper.TextSearchInfo; | ||
import org.elasticsearch.index.mapper.TimeSeriesParams; | ||
|
@@ -284,6 +287,7 @@ public AggregateDoubleMetricFieldType(String name, Map<String, String> meta, Met | |
|
||
/** | ||
* Return a delegate field type for a given metric sub-field | ||
* | ||
* @return a field type | ||
*/ | ||
private NumberFieldMapper.NumberFieldType delegateFieldType(Metric metric) { | ||
|
@@ -292,6 +296,7 @@ private NumberFieldMapper.NumberFieldType delegateFieldType(Metric metric) { | |
|
||
/** | ||
* Return a delegate field type for the default metric sub-field | ||
* | ||
* @return a field type | ||
*/ | ||
private NumberFieldMapper.NumberFieldType delegateFieldType() { | ||
|
@@ -509,6 +514,7 @@ protected Object parseSourceValue(Object value) { | |
|
||
/** | ||
* If field is a time series metric field, returns its metric type | ||
* | ||
* @return the metric type or null | ||
*/ | ||
public MetricType getMetricType() { | ||
|
@@ -524,13 +530,19 @@ public MetricType getMetricType() { | |
|
||
private final Version indexCreatedVersion; | ||
|
||
/** A set of metrics supported */ | ||
/** | ||
* A set of metrics supported | ||
*/ | ||
private final EnumSet<Metric> metrics; | ||
|
||
/** The default metric to be when querying this field type */ | ||
/** | ||
* The default metric to be when querying this field type | ||
*/ | ||
protected Metric defaultMetric; | ||
|
||
/** The metric type (gauge, counter, summary) if field is a time series metric */ | ||
/** | ||
* The metric type (gauge, counter, summary) if field is a time series metric | ||
*/ | ||
private final TimeSeriesParams.MetricType metricType; | ||
|
||
private AggregateDoubleMetricFieldMapper( | ||
|
@@ -574,7 +586,6 @@ public Iterator<Mapper> iterator() { | |
|
||
@Override | ||
protected void parseCreateField(DocumentParserContext context) throws IOException { | ||
|
||
context.path().add(simpleName()); | ||
XContentParser.Token token; | ||
XContentSubParser subParser = null; | ||
|
@@ -675,4 +686,115 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio | |
public FieldMapper.Builder getMergeBuilder() { | ||
return new Builder(simpleName(), ignoreMalformedByDefault, indexCreatedVersion).metric(metricType).init(this); | ||
} | ||
|
||
@Override | ||
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() { | ||
if (ignoreMalformed) { | ||
throw new IllegalArgumentException( | ||
"field [" + name() + "] of type [" + typeName() + "] doesn't support synthetic source because it ignores malformed numbers" | ||
); | ||
} | ||
return new AggregateMetricSyntheticFieldLoader(name(), simpleName(), metrics); | ||
} | ||
|
||
public static class AggregateMetricSyntheticFieldLoader implements SourceLoader.SyntheticFieldLoader { | ||
private final String name; | ||
private final String simpleName; | ||
private final EnumSet<Metric> metrics; | ||
|
||
protected AggregateMetricSyntheticFieldLoader(String name, String simpleName, EnumSet<Metric> metrics) { | ||
this.name = name; | ||
this.simpleName = simpleName; | ||
this.metrics = metrics; | ||
} | ||
|
||
@Override | ||
public Leaf leaf(LeafReader reader, int[] docIdsInLeaf) throws IOException { | ||
Map<Metric, SortedNumericDocValues> metricDocValues = new EnumMap<>(Metric.class); | ||
for (Metric m : metrics) { | ||
SortedNumericDocValues dv = dv(reader, m); | ||
if (dv != null) { | ||
metricDocValues.put(m, dv); | ||
} | ||
} | ||
|
||
if (metricDocValues.isEmpty()) { | ||
return SourceLoader.SyntheticFieldLoader.NOTHING_LEAF; | ||
} | ||
|
||
return new AggregateMetricSyntheticFieldLoader.ImmediateLeaf(metricDocValues); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could not find an easy way to leverage the I skipped the singleton optimization because having mutliple docs in leaf requires an array of values. This combined with multiple metrics per doc (a |
||
|
||
private class ImmediateLeaf implements Leaf { | ||
private final Map<Metric, SortedNumericDocValues> metricDocValues; | ||
private Map<Metric, Boolean> dvHasValue; | ||
|
||
private boolean hasValues; | ||
|
||
ImmediateLeaf(Map<Metric, SortedNumericDocValues> metricDocValues) { | ||
this.metricDocValues = metricDocValues; | ||
} | ||
|
||
@Override | ||
public boolean empty() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean advanceToDoc(int docId) throws IOException { | ||
// It is required that all defined metrics must exist. In this case | ||
// it is enough to check for the first docValue. However, in the future | ||
// we may relax the requirement of all metrics existing. In this case | ||
// we should check the doc value for each metric separately | ||
dvHasValue = new EnumMap<>(Metric.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you use an |
||
hasValues = false; | ||
for (Map.Entry<Metric, SortedNumericDocValues> e : metricDocValues.entrySet()) { | ||
boolean b = e.getValue().advanceExact(docId); | ||
hasValues = hasValues || b; | ||
dvHasValue.put(e.getKey(), b); | ||
} | ||
|
||
return hasValues; | ||
nik9000 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think maybe |
||
} | ||
|
||
@Override | ||
public void write(XContentBuilder b) throws IOException { | ||
if (false == hasValues) { | ||
return; | ||
} | ||
b.startObject(simpleName); | ||
for (Map.Entry<Metric, SortedNumericDocValues> entry : metricDocValues.entrySet()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's cool. Consistency is lovely for reading these docs. Alphabetical just gets the consistency in an easy way. No need to do it here. |
||
if (dvHasValue.get(entry.getKey())) { | ||
String metricName = entry.getKey().name(); | ||
long value = entry.getValue().nextValue(); | ||
if (entry.getKey() == Metric.value_count) { | ||
b.field(metricName, value); | ||
} else { | ||
b.field(metricName, NumericUtils.sortableLongToDouble(value)); | ||
} | ||
} | ||
} | ||
b.endObject(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns a {@link SortedNumericDocValues} or null if it doesn't have any doc values. | ||
* See {@link DocValues#getSortedNumeric} which is *nearly* the same, but it returns | ||
* an "empty" implementation if there aren't any doc values. We need to be able to | ||
* tell if there aren't any and return our empty leaf source loader. | ||
*/ | ||
private SortedNumericDocValues dv(LeafReader reader, Metric metric) throws IOException { | ||
String fieldName = subfieldName(name, metric); | ||
SortedNumericDocValues dv = reader.getSortedNumericDocValues(fieldName); | ||
if (dv != null) { | ||
return dv; | ||
} | ||
NumericDocValues single = reader.getNumericDocValues(fieldName); | ||
if (single != null) { | ||
return DocValues.singleton(single); | ||
} | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since all metric subfields are stored using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try removing it! I believe if you store single values it'll flip to If you do have to keep loading from both of there it's probably worth making this method |
||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does your formatter do this? I think it's more standard, but I don't tend to do it myself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I undertand the question well, this has nothing to do with formatting the field in the output.
This has to do with the following:
term
or arange
query, the query is delegated to the subfield metric that has been marked asdefault_metric
in the field mapping.default_metric
is returned (this will change when Support aggregate_double_metric fields in the Field API #88534 is merged)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, no, my question was about the extra line inserted into the javadoc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry! I wasn't clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it was me messing with IntelliJ formatting. Reverted it now.