Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ public SearchAsYouTypeFieldMapper(String simpleName,
PrefixFieldMapper prefixField,
ShingleFieldMapper[] shingleFields,
Builder builder) {
super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo);
super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo, false, null);
this.prefixField = prefixField;
this.shingleFields = shingleFields;
this.maxShingleSize = builder.maxShingleSize.getValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedF
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> ignoreZValue,
MultiFields multiFields, CopyTo copyTo,
Indexer<Parsed, Processed> indexer, Parser<Parsed> parser) {
super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo);
super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, false, null);
this.ignoreMalformed = ignoreMalformed;
this.ignoreZValue = ignoreZValue;
this.indexer = indexer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.util.BytesRef;
Expand All @@ -25,7 +26,11 @@
import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType;
import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.BooleanFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptCompiler;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.lookup.FieldValues;
import org.elasticsearch.search.lookup.SearchLookup;

import java.io.IOException;
Expand Down Expand Up @@ -72,43 +77,64 @@ public static class Builder extends FieldMapper.Builder {
(n, c, o) -> o == null ? null : XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue)
.acceptsNull();

private final Parameter<Script> script = Parameter.scriptParam(m -> toType(m).script);
private final Parameter<String> onScriptError = Parameter.onScriptErrorParam(m -> toType(m).onScriptError, script);

private final Parameter<Map<String, String>> meta = Parameter.metaParam();

public Builder(String name) {
private final ScriptCompiler scriptCompiler;

public Builder(String name, ScriptCompiler scriptCompiler) {
super(name);
this.scriptCompiler = scriptCompiler;
}

@Override
protected List<Parameter<?>> getParameters() {
return List.of(meta, docValues, indexed, nullValue, stored);
return List.of(meta, docValues, indexed, nullValue, stored, script, onScriptError);
}

@Override
public BooleanFieldMapper build(ContentPath contentPath) {
MappedFieldType ft = new BooleanFieldType(buildFullName(contentPath), indexed.getValue(), stored.getValue(),
docValues.getValue(), nullValue.getValue(), meta.getValue());
docValues.getValue(), nullValue.getValue(), scriptValues(), meta.getValue());

return new BooleanFieldMapper(name, ft, multiFieldsBuilder.build(this, contentPath), copyTo.build(), this);
}

private FieldValues<Boolean> scriptValues() {
if (script.get() == null) {
return null;
}
assert scriptCompiler != null;
BooleanFieldScript.Factory scriptFactory = scriptCompiler.compile(script.get(), BooleanFieldScript.CONTEXT);
return scriptFactory == null ? null : (lookup, ctx, doc, consumer) -> scriptFactory
.newFactory(name, script.get().getParams(), lookup)
.newInstance(ctx)
.runForDoc(doc, consumer);
}
}

public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n));
public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.scriptCompiler()));

public static final class BooleanFieldType extends TermBasedFieldType {

private final Boolean nullValue;
private final FieldValues<Boolean> scriptValues;

public BooleanFieldType(String name, boolean isSearchable, boolean isStored, boolean hasDocValues,
Boolean nullValue, Map<String, String> meta) {
Boolean nullValue, FieldValues<Boolean> scriptValues, Map<String, String> meta) {
super(name, isSearchable, isStored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
this.nullValue = nullValue;
this.scriptValues = scriptValues;
}

public BooleanFieldType(String name) {
this(name, true, false, true, false, Collections.emptyMap());
this(name, true, false, true, false, null, Collections.emptyMap());
}

public BooleanFieldType(String name, boolean searchable) {
this(name, searchable, false, true, false, Collections.emptyMap());
this(name, searchable, false, true, false, null, Collections.emptyMap());
}

@Override
Expand All @@ -121,7 +147,9 @@ public ValueFetcher valueFetcher(SearchExecutionContext context, String format)
if (format != null) {
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] doesn't support formats.");
}

if (this.scriptValues != null) {
return FieldValues.valueFetcher(this.scriptValues, context);
}
return new SourceValueFetcher(name(), context, nullValue) {
@Override
protected Boolean parseSourceValue(Object value) {
Expand Down Expand Up @@ -208,6 +236,9 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower
private final boolean indexed;
private final boolean hasDocValues;
private final boolean stored;
private final Script script;
private final FieldValues<Boolean> scriptValues;
private final ScriptCompiler scriptCompiler;
Copy link
Contributor

Choose a reason for hiding this comment

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

One feature freeze is gone I want to look seriously at moving merge to Builder objects, having to carry this stuff around is a real pain.

Copy link
Member Author

Choose a reason for hiding this comment

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

agreed! I am happy to help with that


protected BooleanFieldMapper(String simpleName, MappedFieldType mappedFieldType,
MultiFields multiFields, CopyTo copyTo, Builder builder) {
Expand All @@ -216,6 +247,9 @@ protected BooleanFieldMapper(String simpleName, MappedFieldType mappedFieldType,
this.stored = builder.stored.getValue();
this.indexed = builder.indexed.getValue();
this.hasDocValues = builder.docValues.getValue();
this.script = builder.script.get();
this.scriptValues = builder.scriptValues();
this.scriptCompiler = builder.scriptCompiler;
}

@Override
Expand All @@ -240,7 +274,10 @@ protected void parseCreateField(ParseContext context) throws IOException {
value = context.parser().booleanValue();
}
}
indexValue(context, value);
}

private void indexValue(ParseContext context, Boolean value) {
if (value == null) {
return;
}
Expand All @@ -257,14 +294,18 @@ protected void parseCreateField(ParseContext context) throws IOException {
}
}

@Override
protected void indexScriptValues(SearchLookup searchLookup, LeafReaderContext readerContext, int doc, ParseContext parseContext) {
this.scriptValues.valuesForDoc(searchLookup, readerContext, doc, value -> indexValue(parseContext, value));
}

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public void newDynamicDoubleField(ParseContext context, String name) throws IOEx

@Override
public void newDynamicBooleanField(ParseContext context, String name) throws IOException {
createDynamicField(new BooleanFieldMapper.Builder(name), context);
createDynamicField(new BooleanFieldMapper.Builder(name, null), context);
Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder if instead of null we should have some impl of ScriptCompiler that throws UnsupportedOperationException when used.

Copy link
Contributor

Choose a reason for hiding this comment

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

++, either here or in a follow up - it will be useful on the other field mappers as well

}

@Override
Expand Down
113 changes: 97 additions & 16 deletions server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public abstract class FieldMapper extends Mapper implements Cloneable {
protected final Map<String, NamedAnalyzer> indexAnalyzers;
protected final MultiFields multiFields;
protected final CopyTo copyTo;
protected final boolean hasScript;
protected final String onScriptError;

/**
* Create a FieldMapper with no index analyzers
Expand All @@ -69,9 +71,25 @@ public abstract class FieldMapper extends Mapper implements Cloneable {
*/
protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
MultiFields multiFields, CopyTo copyTo) {
this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo);
this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, false, null);
}

/**
* Create a FieldMapper with no index analyzers
* @param simpleName the leaf name of the mapper
* @param mappedFieldType the MappedFieldType associated with this mapper
* @param multiFields sub fields of this mapper
* @param copyTo copyTo fields of this mapper
* @param hasScript whether a script is defined for the field
* @param onScriptError the behaviour for when the defined script fails at runtime
*/
protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
MultiFields multiFields, CopyTo copyTo,
boolean hasScript, String onScriptError) {
this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, hasScript, onScriptError);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

if we agree on the new constructors, I think we should try to move all the callers of the ones without the additional parameters to these, otherwise we end up with too many constructors

Copy link
Contributor

Choose a reason for hiding this comment

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

I like pulling out the common behaviour, but maybe we should try and localise this more? Have an intermediate class called ScriptableFieldMapper or something like that?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure it's worth the complexity of one additional intermediate base class, especially as more types will support a script.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough - we can probably merge the single analyzer and multiple analyzer constructors at least without causing too much noise.

Copy link
Member Author

Choose a reason for hiding this comment

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

agreed, let's do it as a followup?



/**
* Create a FieldMapper with a single associated index analyzer
* @param simpleName the leaf name of the mapper
Expand All @@ -83,7 +101,26 @@ protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
NamedAnalyzer indexAnalyzer,
MultiFields multiFields, CopyTo copyTo) {
this(simpleName, mappedFieldType, Collections.singletonMap(mappedFieldType.name(), indexAnalyzer), multiFields, copyTo);
this(simpleName, mappedFieldType, Collections.singletonMap(mappedFieldType.name(), indexAnalyzer), multiFields, copyTo,
false, null);
}

/**
* Create a FieldMapper with a single associated index analyzer
* @param simpleName the leaf name of the mapper
* @param mappedFieldType the MappedFieldType associated with this mapper
* @param indexAnalyzer the index-time analyzer to use for this field
* @param multiFields sub fields of this mapper
* @param copyTo copyTo fields of this mapper
* @param hasScript whether a script is defined for the field
* @param onScriptError the behaviour for when the defined script fails at runtime
*/
protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
NamedAnalyzer indexAnalyzer,
MultiFields multiFields, CopyTo copyTo,
boolean hasScript, String onScriptError) {
this(simpleName, mappedFieldType, Collections.singletonMap(mappedFieldType.name(), indexAnalyzer), multiFields, copyTo,
hasScript, onScriptError);
}

/**
Expand All @@ -94,10 +131,13 @@ protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
* the mapper will add
* @param multiFields sub fields of this mapper
* @param copyTo copyTo fields of this mapper
* @param hasScript whether a script is defined for the field
* @param onScriptError the behaviour for when the defined script fails at runtime
*/
protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
Map<String, NamedAnalyzer> indexAnalyzers,
MultiFields multiFields, CopyTo copyTo) {
MultiFields multiFields, CopyTo copyTo,
boolean hasScript, String onScriptError) {
super(simpleName);
if (mappedFieldType.name().isEmpty()) {
throw new IllegalArgumentException("name cannot be empty string");
Expand All @@ -106,6 +146,8 @@ protected FieldMapper(String simpleName, MappedFieldType mappedFieldType,
this.indexAnalyzers = indexAnalyzers;
this.multiFields = multiFields;
this.copyTo = Objects.requireNonNull(copyTo);
this.hasScript = hasScript;
this.onScriptError = onScriptError;
}

@Override
Expand Down Expand Up @@ -148,6 +190,9 @@ public boolean parsesArrayValue() {
*/
public void parse(ParseContext context) throws IOException {
try {
if (hasScript) {
throw new IllegalArgumentException("Cannot index data directly into a field with a [script] parameter");
}
parseCreateField(context);
} catch (Exception e) {
String valuePreview = "";
Expand All @@ -172,32 +217,54 @@ public void parse(ParseContext context) throws IOException {
multiFields.parse(this, context);
}

/**
* Parse the field value and populate the fields on {@link ParseContext#doc()}.
*
* Implementations of this method should ensure that on failing to parse parser.currentToken() must be the
* current failing token
*/
protected abstract void parseCreateField(ParseContext context) throws IOException;

/**
* @return whether this field mapper uses a script to generate its values
*/
public boolean hasScript() {
return false;
public final boolean hasScript() {
return hasScript;
}

/**
* Execute the index-time script associated with this field mapper.
*
* This method should only be called if {@link #hasScript()} has returned {@code true}
* @param searchLookup a SearchLookup to be passed the script
* @param ctx a LeafReaderContext exposing values from an incoming document
* @param pc the ParseContext over the incoming document
* @param readerContext a LeafReaderContext exposing values from an incoming document
* @param doc the id of the document to execute the script against
* @param parseContext the ParseContext over the incoming document
*/
public void executeScript(SearchLookup searchLookup, LeafReaderContext ctx, int doc, ParseContext pc) {
throw new UnsupportedOperationException("FieldMapper " + name() + " does not have an index-time script");
public final void executeScript(SearchLookup searchLookup, LeafReaderContext readerContext, int doc, ParseContext parseContext) {
try {
indexScriptValues(searchLookup, readerContext, doc, parseContext);
} catch (Exception e) {
if ("ignore".equals(onScriptError)) {
parseContext.addIgnoredField(name());
} else {
throw new MapperParsingException("Error executing script on field [" + name() + "]", e);
}
}
}

/**
* Parse the field value and populate the fields on {@link ParseContext#doc()}.
* Run the script associated with the field and index the values that it emits
*
* Implementations of this method should ensure that on failing to parse parser.currentToken() must be the
* current failing token
* This method should only be called if {@link #hasScript()} has returned {@code true}
* @param searchLookup a SearchLookup to be passed the script
* @param readerContext a LeafReaderContext exposing values from an incoming document
* @param doc the id of the document to execute the script against
* @param parseContext the ParseContext over the incoming document
*/
protected abstract void parseCreateField(ParseContext context) throws IOException;
protected void indexScriptValues(SearchLookup searchLookup, LeafReaderContext readerContext, int doc, ParseContext parseContext) {
throw new UnsupportedOperationException("FieldMapper " + name() + " does not support [script]");
}

protected final void createFieldNamesField(ParseContext context) {
assert fieldType().hasDocValues() == false : "_field_names should only be used when doc_values are turned off";
Expand Down Expand Up @@ -532,8 +599,8 @@ public static final class Parameter<T> implements Supplier<T> {
private MergeValidator<T> mergeValidator;
private T value;
private boolean isSet;
private List<Parameter<?>> requires = new ArrayList<>();
private List<Parameter<?>> precludes = new ArrayList<>();
private final List<Parameter<?>> requires = new ArrayList<>();
private final List<Parameter<?>> precludes = new ArrayList<>();

/**
* Creates a new Parameter
Expand Down Expand Up @@ -875,7 +942,7 @@ public static Parameter<Boolean> docValuesParam(Function<FieldMapper, Boolean> i
* @param initializer retrieves the equivalent parameter from an existing FieldMapper for use in merges
* @return a script parameter
*/
public static FieldMapper.Parameter<Script> scriptParam(
public static Parameter<Script> scriptParam(
Function<FieldMapper, Script> initializer
) {
return new FieldMapper.Parameter<>(
Expand All @@ -896,6 +963,20 @@ public static FieldMapper.Parameter<Script> scriptParam(
).acceptsNull();
}

/**
* Defines an on_script_error parameter
* @param initializer retrieves the equivalent parameter from an existing FieldMapper for use in merges
* @param dependentScriptParam the corresponding required script parameter
* @return a new on_error_script parameter
*/
public static Parameter<String> onScriptErrorParam(Function<FieldMapper, String> initializer,
Parameter<Script> dependentScriptParam) {
return Parameter.restrictedStringParam(
"on_script_error",
true,
initializer,
"reject", "ignore").requiresParameters(dependentScriptParam);
}
}

public static final class Conflicts {
Expand Down
Loading