Skip to content

Commit

Permalink
Add opt-in and opt in an easier field
Browse files Browse the repository at this point in the history
  • Loading branch information
lkts committed May 3, 2024
1 parent 72589fb commit cb1626e
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 221 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,11 @@ public Iterator<Mapper> sourcePathUsedBy() {
return subfieldsAndMultifieldsIterator();
}

@Override
protected boolean fallbackSyntheticSourceOptIn() {
return true;
}

/**
* An analyzer wrapper to add a shingle token filter, an edge ngram token filter or both to its wrapped analyzer. When adding an edge
* ngrams token filter, it also adds a {@link TrailingShingleTokenFilter} to add extra position increments at the end of the stream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SynonymQuery;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.common.geo.GeoJson;
import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.utils.WellKnownText;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.analysis.AnalyzerScope;
import org.elasticsearch.index.analysis.IndexAnalyzers;
Expand All @@ -54,6 +60,7 @@
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.index.search.QueryStringQueryParser;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xcontent.XContentBuilder;
import org.junit.AssumptionViolatedException;

Expand All @@ -65,6 +72,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -824,7 +832,36 @@ protected boolean supportsIgnoreMalformed() {

@Override
protected SyntheticSourceSupport syntheticSourceSupport(boolean syntheticSource) {
throw new AssumptionViolatedException("not supported");
return new SyntheticSourceSupport() {
public SyntheticSourceExample example(int maxValues) throws IOException {
String value = rarely() ? null : randomAlphaOfLengthBetween(0, 50);

return new SyntheticSourceExample(value, value, this::mapping);
}

private void mapping(XContentBuilder b) throws IOException {
b.field("type", "search_as_you_type");
if (rarely()) {
b.field("index", false);
}
if (rarely()) {
b.field("store", true);
}
}

@Override
public List<SyntheticSourceInvalidExample> invalidExample() throws IOException {
return List.of();
}
};
}

@Override
protected boolean supportsCopyTo() {
// TODO this is so that `testSyntheticSourceInvalid` does not complain.
// This field does support copy_to
// should we fail to construct synthetic source in `FieldMapper` if there is copy_to ?
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
supported:
unsupported:
- requires:
cluster_features: ["mapper.source.synthetic_source_fallback"]
reason: introduced in 8.15.0
cluster_features: ["gte_v8.3.0"]
reason: introduced in 8.3.0

- do:
catch: bad_request
indices.create:
index: test
body:
Expand All @@ -15,42 +16,3 @@ supported:
type: join
relations:
parent: child

- do:
index:
index: test
id: "1"
body: {"foo": "bar", "join_field": {"name" : "parent"} }

- do:
index:
index: test
id: "2"
routing: "1"
body: {"zab": "baz", "join_field": { "name" : "child", "parent": "1"} }

- do:
indices.refresh: {}

- do:
get:
index: test
id: "1"

- match:
_source:
foo: "bar"
join_field:
name: "parent"

- do:
get:
index: test
id: "2"

- match:
_source:
join_field:
name: "child"
parent: "1"
zab: "baz"
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ static void parseObjectOrField(DocumentParserContext context, Mapper mapper) thr
parseObjectOrNested(context.createFlattenContext(currentFieldName));
context.path().add(currentFieldName);
} else {
if (context.mappingLookup().isSourceSynthetic() && fieldMapper.supportsSyntheticSourceNatively() == false) {
if (context.mappingLookup().isSourceSynthetic() && fieldMapper.supportsSyntheticSourceNatively() == false && fieldMapper.fallbackSyntheticSourceOptIn()) {
Tuple<XContentParser, XContentParser> clonedParsers = XContentHelper.cloneParser(context.parser());

int parentOffset = context.parent() instanceof RootObjectMapper ? 0 : context.parent().fullPath().length() + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,14 @@ protected boolean supportsSyntheticSourceNatively() {
return false;
}

/**
* If a mapper opts in to use fallback synthetic source implementation.
* @return true or false
*/
protected boolean fallbackSyntheticSourceOptIn() {
return false;
}

/**
* Mappers override this method with native synthetic source support.
* If mapper does not support synthetic source, it is generated using generic implementation
Expand All @@ -456,7 +464,9 @@ protected boolean supportsSyntheticSourceNatively() {
*/
@Override
public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
return SourceLoader.SyntheticFieldLoader.NOTHING;
return fallbackSyntheticSourceOptIn()
? SourceLoader.SyntheticFieldLoader.NOTHING
: super.syntheticFieldLoader();
}

public static final class MultiFields implements Iterable<FieldMapper>, ToXContent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,9 @@
*/
package org.elasticsearch.index.mapper;

import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoJson;
import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.utils.GeometryValidator;
import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.geometry.utils.WellKnownText;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.TestGeoShapeFieldMapperPlugin;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentBuilder;
Expand All @@ -28,8 +19,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
Expand Down Expand Up @@ -230,171 +219,7 @@ protected Object generateRandomInputValue(MappedFieldType ft) {

@Override
protected SyntheticSourceSupport syntheticSourceSupport(boolean ignoreMalformed) {
// Almost like GeoShapeType but no circles
enum ShapeType {
POINT,
LINESTRING,
POLYGON,
MULTIPOINT,
MULTILINESTRING,
MULTIPOLYGON,
GEOMETRYCOLLECTION,
ENVELOPE
}

return new SyntheticSourceSupport() {
@Override
public SyntheticSourceExample example(int maxValues) throws IOException {
if (randomBoolean()) {
Value v = generateValue();
if (v.blockLoaderOutput != null) {
return new SyntheticSourceExample(v.input, v.output, v.blockLoaderOutput, this::mapping);
}
return new SyntheticSourceExample(v.input, v.output, this::mapping);
}

List<Value> values = randomList(1, maxValues, this::generateValue);
List<Object> in = values.stream().map(Value::input).toList();
List<Object> out = values.stream().map(Value::output).toList();

// Block loader infrastructure will never return nulls
List<Object> outBlockList = values.stream()
.filter(v -> v.input != null)
.map(v -> v.blockLoaderOutput != null ? v.blockLoaderOutput : v.output)
.toList();
var outBlock = outBlockList.size() == 1 ? outBlockList.get(0) : outBlockList;

return new SyntheticSourceExample(in, out, outBlock, this::mapping);
}

private record Value(Object input, Object output, String blockLoaderOutput) {
Value(Object input, Object output) {
this(input, output, null);
}
}

private Value generateValue() {
if (ignoreMalformed && randomBoolean()) {
List<Supplier<Object>> choices = List.of(
() -> randomAlphaOfLength(3),
ESTestCase::randomInt,
ESTestCase::randomLong,
ESTestCase::randomFloat,
ESTestCase::randomDouble
);
Object v = randomFrom(choices).get();
return new Value(v, v);
}
if (randomBoolean()) {
return new Value(null, null);
}

var type = randomFrom(ShapeType.values());
var isGeoJson = randomBoolean();

switch (type) {
case POINT -> {
var point = GeometryTestUtils.randomPoint(false);
return value(point, isGeoJson);
}
case LINESTRING -> {
var line = GeometryTestUtils.randomLine(false);
return value(line, isGeoJson);
}
case POLYGON -> {
var polygon = GeometryTestUtils.randomPolygon(false);
return value(polygon, isGeoJson);
}
case MULTIPOINT -> {
var multiPoint = GeometryTestUtils.randomMultiPoint(false);
return value(multiPoint, isGeoJson);
}
case MULTILINESTRING -> {
var multiPoint = GeometryTestUtils.randomMultiLine(false);
return value(multiPoint, isGeoJson);
}
case MULTIPOLYGON -> {
var multiPolygon = GeometryTestUtils.randomMultiPolygon(false);
return value(multiPolygon, isGeoJson);
}
case GEOMETRYCOLLECTION -> {
var multiPolygon = GeometryTestUtils.randomGeometryCollectionWithoutCircle(false);
return value(multiPolygon, isGeoJson);
}
case ENVELOPE -> {
var rectangle = GeometryTestUtils.randomRectangle();
var wktString = WellKnownText.toWKT(rectangle);

return new Value(wktString, wktString);
}
default -> throw new UnsupportedOperationException("Unsupported shape");
}
}

private static Value value(Geometry geometry, boolean isGeoJson) {
var wktString = WellKnownText.toWKT(geometry);
var normalizedWktString = GeometryNormalizer.needsNormalize(Orientation.RIGHT, geometry)
? WellKnownText.toWKT(GeometryNormalizer.apply(Orientation.RIGHT, geometry))
: wktString;

if (isGeoJson) {
var map = GeoJson.toMap(geometry);
return new Value(map, map, normalizedWktString);
}

return new Value(wktString, wktString, normalizedWktString);
}

private void mapping(XContentBuilder b) throws IOException {
b.field("type", "geo_shape");
if (rarely()) {
b.field("index", false);
}
// if (rarely()) {
// b.field("doc_values", false);
// }
if (ignoreMalformed) {
b.field("ignore_malformed", true);
}
}

@Override
public List<SyntheticSourceInvalidExample> invalidExample() throws IOException {
return List.of();
}
};
}

@Override
protected Function<Object, Object> loadBlockExpected(BlockReaderSupport blockReaderSupport, boolean columnReader) {
return v -> asWKT((BytesRef) v);
}

protected static Object asWKT(BytesRef value) {
// Internally we use WKB in BytesRef, but for test assertions we want to use WKT for readability
Geometry geometry = WellKnownBinary.fromWKB(GeometryValidator.NOOP, false, value.bytes);
return WellKnownText.toWKT(geometry);
}

@Override
protected BlockReaderSupport getSupportedReaders(MapperService mapper, String loaderFieldName) {
// TODO are we okay that we don't support synthetic source here?
return new BlockReaderSupport(false, false, mapper, loaderFieldName);
}

@Override
protected boolean supportsEmptyInputArray() {
// TODO currently we serialize empty array as is in synthetic source but apparently for other fields we don't
// and we skip the entire field
return false;
}

@Override
protected boolean supportsCopyTo() {
// TODO this is so that `testSyntheticSourceInvalid` does not complain
// in theory geo_shape could support copy_to
// should we fail to construct synthetic source in `FieldMapper` if there is copy_to ?
return false;
throw new AssumptionViolatedException("not supported");
}

@Override
Expand Down

0 comments on commit cb1626e

Please sign in to comment.