Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.IndexVersions;
import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.FieldDataContext;
import org.elasticsearch.index.fielddata.IndexFieldData;
Expand All @@ -32,6 +34,7 @@
import org.elasticsearch.index.mapper.BlockDocValuesReader;
import org.elasticsearch.index.mapper.BlockLoader;
import org.elasticsearch.index.mapper.BlockSourceReader;
import org.elasticsearch.index.mapper.CompositeSyntheticFieldLoader;
import org.elasticsearch.index.mapper.DocumentParserContext;
import org.elasticsearch.index.mapper.FallbackSyntheticSourceBlockLoader;
import org.elasticsearch.index.mapper.FieldMapper;
Expand All @@ -40,6 +43,8 @@
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.SimpleMappedFieldType;
import org.elasticsearch.index.mapper.SortedNumericDocValuesSyntheticFieldLoader;
import org.elasticsearch.index.mapper.SortedNumericWithOffsetsDocValuesSyntheticFieldLoaderLayer;
import org.elasticsearch.index.mapper.SourceLoader;
import org.elasticsearch.index.mapper.SourceValueFetcher;
import org.elasticsearch.index.mapper.TextSearchInfo;
import org.elasticsearch.index.mapper.TimeSeriesParams;
Expand Down Expand Up @@ -67,6 +72,8 @@
import java.util.Objects;
import java.util.Set;

import static org.elasticsearch.index.mapper.FieldArrayContext.getOffsetsFieldName;

/** A {@link FieldMapper} for scaled floats. Values are internally multiplied
* by a scaling factor and rounded to the closest long. */
public class ScaledFloatFieldMapper extends FieldMapper {
Expand Down Expand Up @@ -125,12 +132,34 @@ public static class Builder extends FieldMapper.Builder {
private final Parameter<TimeSeriesParams.MetricType> metric;

private final IndexMode indexMode;
private final IndexVersion indexCreatedVersion;
private final SourceKeepMode indexSourceKeepMode;

public Builder(String name, Settings settings, IndexMode indexMode) {
this(name, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings), indexMode);
public Builder(
String name,
Settings settings,
IndexMode indexMode,
IndexVersion indexCreatedVersion,
SourceKeepMode indexSourceKeepMode
) {
this(
name,
IGNORE_MALFORMED_SETTING.get(settings),
COERCE_SETTING.get(settings),
indexMode,
indexCreatedVersion,
indexSourceKeepMode
);
}

public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDefault, IndexMode indexMode) {
public Builder(
String name,
boolean ignoreMalformedByDefault,
boolean coerceByDefault,
IndexMode indexMode,
IndexVersion indexCreatedVersion,
SourceKeepMode indexSourceKeepMode
) {
super(name);
this.ignoreMalformed = Parameter.explicitBoolParam(
"ignore_malformed",
Expand Down Expand Up @@ -159,6 +188,8 @@ public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDe
);
}
});
this.indexCreatedVersion = indexCreatedVersion;
this.indexSourceKeepMode = indexSourceKeepMode;
}

Builder scalingFactor(double scalingFactor) {
Expand Down Expand Up @@ -200,11 +231,35 @@ public ScaledFloatFieldMapper build(MapperBuilderContext context) {
coerce.getValue().value(),
context.isSourceSynthetic()
);
return new ScaledFloatFieldMapper(leafName(), type, builderParams(this, context), context.isSourceSynthetic(), this);
String offsetsFieldName = getOffsetsFieldName(
context,
indexSourceKeepMode,
hasDocValues.getValue(),
stored.getValue(),
this,
indexCreatedVersion,
IndexVersions.SYNTHETIC_SOURCE_STORE_ARRAYS_NATIVELY_SCALED_FLOAT
);
return new ScaledFloatFieldMapper(
leafName(),
type,
builderParams(this, context),
context.isSourceSynthetic(),
this,
offsetsFieldName
);
}
}

public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.getSettings(), c.getIndexSettings().getMode()));
public static final TypeParser PARSER = new TypeParser(
(n, c) -> new Builder(
n,
c.getSettings(),
c.getIndexSettings().getMode(),
c.indexVersionCreated(),
c.getIndexSettings().sourceKeepMode()
)
);

public static final class ScaledFloatFieldType extends SimpleMappedFieldType {

Expand Down Expand Up @@ -533,12 +588,17 @@ public String toString() {
private final TimeSeriesParams.MetricType metricType;
private final IndexMode indexMode;

private final IndexVersion indexCreatedVersion;
private final String offsetsFieldName;
private final SourceKeepMode indexSourceKeepMode;

private ScaledFloatFieldMapper(
String simpleName,
ScaledFloatFieldType mappedFieldType,
BuilderParams builderParams,
boolean isSourceSynthetic,
Builder builder
Builder builder,
String offsetsFieldName
) {
super(simpleName, mappedFieldType, builderParams);
this.isSourceSynthetic = isSourceSynthetic;
Expand All @@ -553,6 +613,9 @@ private ScaledFloatFieldMapper(
this.coerceByDefault = builder.coerce.getDefaultValue().value();
this.metricType = builder.metric.getValue();
this.indexMode = builder.indexMode;
this.indexCreatedVersion = builder.indexCreatedVersion;
this.offsetsFieldName = offsetsFieldName;
this.indexSourceKeepMode = builder.indexSourceKeepMode;
}

boolean coerce() {
Expand All @@ -564,6 +627,11 @@ public boolean ignoreMalformed() {
return ignoreMalformed.value();
}

@Override
public String getOffsetFieldName() {
return offsetsFieldName;
}

@Override
public ScaledFloatFieldType fieldType() {
return (ScaledFloatFieldType) super.fieldType();
Expand All @@ -576,7 +644,9 @@ protected String contentType() {

@Override
public FieldMapper.Builder getMergeBuilder() {
return new Builder(leafName(), ignoreMalformedByDefault, coerceByDefault, indexMode).metric(metricType).init(this);
return new Builder(leafName(), ignoreMalformedByDefault, coerceByDefault, indexMode, indexCreatedVersion, indexSourceKeepMode)
.metric(metricType)
.init(this);
}

@Override
Expand Down Expand Up @@ -606,11 +676,16 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio
value = numericValue;
}

boolean shouldStoreOffsets = offsetsFieldName != null && context.isImmediateParentAnArray() && context.canAddIgnoredField();

if (value == null) {
value = nullValue;
}

if (value == null) {
if (shouldStoreOffsets) {
context.getOffSetContext().recordNull(offsetsFieldName);
}
return;
}

Expand All @@ -636,6 +711,10 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio

NumberFieldMapper.NumberType.LONG.addFields(context.doc(), fieldType().name(), scaledValue, indexed, hasDocValues, stored);

if (shouldStoreOffsets) {
context.getOffSetContext().recordOffset(offsetsFieldName, scaledValue);
}

if (hasDocValues == false && (indexed || stored)) {
context.addToFieldNames(fieldType().name());
}
Expand Down Expand Up @@ -777,17 +856,34 @@ public int docValueCount() {
}
}

private SourceLoader.SyntheticFieldLoader docValuesSyntheticFieldLoader() {
if (offsetsFieldName != null) {
var layers = new ArrayList<CompositeSyntheticFieldLoader.Layer>(2);
layers.add(
new SortedNumericWithOffsetsDocValuesSyntheticFieldLoaderLayer(
fullPath(),
offsetsFieldName,
(b, value) -> b.value(decodeForSyntheticSource(value, scalingFactor))
)
);
if (ignoreMalformed.value()) {
layers.add(new CompositeSyntheticFieldLoader.MalformedValuesLayer(fullPath()));
}
return new CompositeSyntheticFieldLoader(leafName(), fullPath(), layers);
} else {
return new SortedNumericDocValuesSyntheticFieldLoader(fullPath(), leafName(), ignoreMalformed.value()) {
@Override
protected void writeValue(XContentBuilder b, long value) throws IOException {
b.value(decodeForSyntheticSource(value, scalingFactor));
}
};
}
}

@Override
protected SyntheticSourceSupport syntheticSourceSupport() {
if (hasDocValues) {
return new SyntheticSourceSupport.Native(
() -> new SortedNumericDocValuesSyntheticFieldLoader(fullPath(), leafName(), ignoreMalformed.value()) {
@Override
protected void writeValue(XContentBuilder b, long value) throws IOException {
b.value(decodeForSyntheticSource(value, scalingFactor));
}
}
);
return new SyntheticSourceSupport.Native(this::docValuesSyntheticFieldLoader);
}

return super.syntheticSourceSupport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.DocumentParsingException;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.NumberFieldMapperTests;
Expand Down Expand Up @@ -365,6 +366,24 @@ protected SyntheticSourceSupport syntheticSourceSupport(boolean ignoreMalformed)
return new ScaledFloatSyntheticSourceSupport(ignoreMalformed);
}

@Override
protected SyntheticSourceSupport syntheticSourceSupportForKeepTests(boolean ignoreMalformed, Mapper.SourceKeepMode sourceKeepMode) {
return new ScaledFloatSyntheticSourceSupport(ignoreMalformed) {
@Override
public SyntheticSourceExample example(int maxVals) {
var example = super.example(maxVals);
// Need the expectedForSyntheticSource as inputValue since MapperTestCase#testSyntheticSourceKeepArrays
// uses the inputValue as both the input and expected.
return new SyntheticSourceExample(
example.expectedForSyntheticSource(),
example.expectedForSyntheticSource(),
example.expectedForBlockLoader(),
example.mapping()
);
}
};
}

private static class ScaledFloatSyntheticSourceSupport implements SyntheticSourceSupport {
private final boolean ignoreMalformedEnabled;
private final double scalingFactor = randomDoubleBetween(0, Double.MAX_VALUE, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,14 @@ public void testFieldData() throws IOException {
}

public void testFetchSourceValue() throws IOException {
MappedFieldType mapper = new ScaledFloatFieldMapper.Builder("field", false, false, null).scalingFactor(100)
MappedFieldType mapper = new ScaledFloatFieldMapper.Builder("field", false, false, null, null, null).scalingFactor(100)
.build(MapperBuilderContext.root(false, false))
.fieldType();
assertEquals(List.of(3.14), fetchSourceValue(mapper, 3.1415926));
assertEquals(List.of(3.14), fetchSourceValue(mapper, "3.1415"));
assertEquals(List.of(), fetchSourceValue(mapper, ""));

MappedFieldType nullValueMapper = new ScaledFloatFieldMapper.Builder("field", false, false, null).scalingFactor(100)
MappedFieldType nullValueMapper = new ScaledFloatFieldMapper.Builder("field", false, false, null, null, null).scalingFactor(100)
.nullValue(2.71)
.build(MapperBuilderContext.root(false, false))
.fieldType();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.index.mapper.extras;

import org.elasticsearch.index.mapper.OffsetDocValuesLoaderTestCase;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Collection;

import static java.util.Collections.singletonList;

public class ScaledFloatOffsetDocValuesLoaderTests extends OffsetDocValuesLoaderTestCase {
private static final double TEST_SCALING_FACTOR = 10.0;

@Override
protected Collection<? extends Plugin> getPlugins() {
return singletonList(new MapperExtrasPlugin());
}

@Override
protected void minimalMapping(XContentBuilder b) throws IOException {
b.field("type", "scaled_float").field("scaling_factor", TEST_SCALING_FACTOR);
}

public void testOffsetArray() throws Exception {
verifyOffsets("{\"field\":[1.0,10.0,100.0,0.1,10.0,1.0,0.1,100.0]}");
verifyOffsets("{\"field\":[10.0,null,1.0,null,5.0,null,null,6.3,1.5]}");
}

@Override
protected String getFieldTypeName() {
fail("Should not be called because minimalMapping is overridden");
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this return null here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, because minimalMapping(...) gets overwritten here. So actually this method should never be invoked. Maybe add an assert false here? So it fails quick if this gets invoked?

}

@Override
protected Double randomValue() {
return randomLong() / TEST_SCALING_FACTOR;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.index.mapper.extras;

import com.carrotsearch.randomizedtesting.generators.RandomStrings;

import org.elasticsearch.index.mapper.NativeArrayIntegrationTestCase;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Collection;

import static java.util.Collections.singletonList;

public class ScaledFloatSyntheticSourceNativeArrayIntegrationTests extends NativeArrayIntegrationTestCase {
private static final double TEST_SCALING_FACTOR = 10.0;

@Override
protected Collection<Class<? extends Plugin>> getPlugins() {
return singletonList(MapperExtrasPlugin.class);
}

@Override
protected void minimalMapping(XContentBuilder b) throws IOException {
b.field("type", "scaled_float").field("scaling_factor", TEST_SCALING_FACTOR);
}

@Override
protected String getFieldTypeName() {
fail("Should not be called because minimalMapping is overridden");
return null;
}

@Override
protected Object getRandomValue() {
return randomLong() / TEST_SCALING_FACTOR;
}

@Override
protected Object getMalformedValue() {
return randomBoolean() ? RandomStrings.randomAsciiOfLength(random(), 8) : Double.POSITIVE_INFINITY;
}
}
Loading