Skip to content
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

derived fields changes #81

Draft
wants to merge 13 commits into
base: mo-derived-fields
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.index.mapper;

import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.script.Script;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
* A field mapper for derived fields
*
* @opensearch.internal
*/
public class DerivedFieldMapper extends ParametrizedFieldMapper {

public static final String CONTENT_TYPE = "derived";

/**
* Default parameters for the boolean field mapper
*
* @opensearch.internal
*/
public static class Defaults {
public static final FieldType FIELD_TYPE = new FieldType();

static {
FIELD_TYPE.setOmitNorms(true);
FIELD_TYPE.setStored(false);
FIELD_TYPE.setTokenized(false);
FIELD_TYPE.setIndexOptions(IndexOptions.NONE);
FIELD_TYPE.freeze();
}
}

private static DerivedFieldMapper toType(FieldMapper in) {
return (DerivedFieldMapper) in;
}

/**
* Builder for this field mapper
*
* @opensearch.internal
*/
public static class Builder extends ParametrizedFieldMapper.Builder {
// TODO: The type of parameter may change here if the actual underlying FieldType object is needed
private final Parameter<String> type = Parameter.stringParam(
"type",
false,
m -> toType(m).type,
"text"
);

private final Parameter<Script> script = new Parameter<>(
"script",
false,
() -> null,
(n, c, o) -> o == null ? null : Script.parse(o),
m -> toType(m).script
).setSerializerCheck((id, ic, value) -> value != null);

public Builder(String name) {
super(name);
}

@Override
protected List<Parameter<?>> getParameters() {
return Arrays.asList(type, script);
}

@Override
public DerivedFieldMapper build(BuilderContext context) {
FieldMapper fieldMapper = DerivedFieldSupportedTypes.getFieldMapperFromType(type.getValue(), name, context);
Function<Object, IndexableField> fieldFunction =
DerivedFieldSupportedTypes.getIndexableFieldGeneratorType(type.getValue(), name);
DerivedFieldType ft = new DerivedFieldType(buildFullName(context), type.getValue(), script.getValue(), fieldMapper, fieldFunction);
return new DerivedFieldMapper(name, ft, multiFieldsBuilder.build(this, context), copyTo.build(), this);
}
}

public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n));
private final String type;
private final Script script;

protected DerivedFieldMapper(
String simpleName,
MappedFieldType mappedFieldType,
MultiFields multiFields,
CopyTo copyTo,
Builder builder
) {
super(simpleName, mappedFieldType, multiFields, copyTo);
this.type = builder.type.getValue();
this.script = builder.script.getValue();
}

@Override
public DerivedFieldType fieldType() {
return (DerivedFieldType) super.fieldType();
}

@Override
protected void parseCreateField(ParseContext context) throws IOException {
// Leaving this empty as the parsing should be handled via the Builder when root object is parsed.
// The context would not contain anything in this case since the DerivedFieldMapper is not indexed or stored.
throw new UnsupportedOperationException("should not be invoked");
}

@Override
public ParametrizedFieldMapper.Builder getMergeBuilder() {
return new Builder(simpleName()).init(this);
}

@Override
protected String contentType() {
return CONTENT_TYPE;
}

@Override
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
getMergeBuilder().toXContent(builder, includeDefaults);
multiFields.toXContent(builder, params);
copyTo.toXContent(builder, params);
}

public String getType() {
return type;
}

public Script getScript() {
return script;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.index.mapper;

import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.KeywordField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.opensearch.common.lucene.Lucene;

import java.util.Arrays;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

public enum DerivedFieldSupportedTypes {
KEYWORD (
"keyword",
(name, context) -> {
FieldType dummyFieldType = new FieldType();
dummyFieldType.setIndexOptions(IndexOptions.DOCS_AND_FREQS);
KeywordFieldMapper.Builder keywordBuilder = new KeywordFieldMapper.Builder(name);
KeywordFieldMapper.KeywordFieldType keywordFieldType = keywordBuilder.buildFieldType(context, dummyFieldType);
keywordFieldType.setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
return new KeywordFieldMapper(
name, dummyFieldType, keywordFieldType,
keywordBuilder.multiFieldsBuilder.build(keywordBuilder, context),
keywordBuilder.copyTo.build(),
keywordBuilder
);
},
name -> o -> new KeywordField(name, (String) o, Field.Store.NO)
),
LONG (
"long",
(name, context) -> {
NumberFieldMapper.Builder longBuilder = new NumberFieldMapper.Builder(name, NumberFieldMapper.NumberType.LONG, false, false);
return longBuilder.build(context);
},
name -> o -> new LongField(name, Long.parseLong(o.toString()), Field.Store.NO)
),
DOUBLE (
"double",
(name, context) -> {
NumberFieldMapper.Builder doubleBuilder = new NumberFieldMapper.Builder(name, NumberFieldMapper.NumberType.DOUBLE, false, false);
return doubleBuilder.build(context);
},
name -> o -> new DoubleField(name, Double.parseDouble(o.toString()), Field.Store.NO)
);
// TODO: add logic all supported type in derived fields
// TODO: should we support mapping settings exposed by a given field type from derived fields too?
// for example, support `format` for date type?
final String name;
private final BiFunction<String, Mapper.BuilderContext, FieldMapper> builder;

private final Function<String, Function<Object, IndexableField>> indexableFieldBuilder;

DerivedFieldSupportedTypes(String name, BiFunction<String, Mapper.BuilderContext, FieldMapper> builder,
Function<String, Function<Object, IndexableField>> indexableFieldBuilder){
this.name = name;
this.builder = builder;
this.indexableFieldBuilder = indexableFieldBuilder;
}

public String getName() {
return name;
}
private FieldMapper getFieldMapper(String name, Mapper.BuilderContext context) {
return builder.apply(name, context);
}

private Function<Object, IndexableField> getIndexableFieldGenerator(String name) {
return indexableFieldBuilder.apply(name);
}
private static final Map<String, DerivedFieldSupportedTypes> enumMap = Arrays.stream(DerivedFieldSupportedTypes.values())
.collect(Collectors.toMap(DerivedFieldSupportedTypes::getName, enumValue -> enumValue));

public static FieldMapper getFieldMapperFromType(String type, String name, Mapper.BuilderContext context) {
if (!enumMap.containsKey(type)) {
throw new IllegalArgumentException("Type [" + type + "] isn't supported in Derived field context.");
}
return enumMap.get(type).getFieldMapper(name, context);
}

public static Function<Object, IndexableField> getIndexableFieldGeneratorType(String type, String name) {
if (!enumMap.containsKey(type)) {
throw new IllegalArgumentException("Type [" + type + "] isn't supported in Derived field context.");
}
return enumMap.get(type).getIndexableFieldGenerator(name);
}
}
Loading