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

Emit multiple fields from a runtime field script #75108

Merged
merged 31 commits into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a6b13fc
wip
javanna May 19, 2021
83ecb52
add some more TODOs
javanna Jun 7, 2021
6440373
wip
javanna Jun 7, 2021
325f33f
Merge remote-tracking branch 'origin/master' into runtime/multiple-fi…
romseygeek Jun 18, 2021
ad0ac3d
Merge branch 'master' into poc/emit_multiple_fields
javanna Jun 22, 2021
5a6a0f9
Merge branch 'master' into poc/emit_multiple_fields
javanna Jul 6, 2021
15bd02f
Merge branch 'master' into poc/emit_multiple_fields
javanna Jul 7, 2021
af19b2e
remove comment on naming
javanna Jul 7, 2021
2c9d0d5
add parent name
javanna Jul 7, 2021
3c033d0
javadocs
javanna Jul 7, 2021
0c8edbd
update assertion
javanna Jul 7, 2021
a13d476
update assertion
javanna Jul 7, 2021
9066065
Fix serialization
javanna Jul 8, 2021
52c2e43
Merge branch 'master' into feature/runtime_fields_multi_emit
javanna Jul 8, 2021
5023ecb
Add tests
javanna Jul 8, 2021
0eb1439
Merge remote-tracking branch 'origin/master' into feature/runtime_fie…
romseygeek Jul 20, 2021
32a4f0c
rename to 'composite'
romseygeek Jul 20, 2021
b91d07e
Fix dynamic field shadowing
romseygeek Jul 21, 2021
add5537
Add yaml test
romseygeek Jul 21, 2021
23dbff7
Merge remote-tracking branch 'origin/master' into feature/runtime_fie…
romseygeek Jul 22, 2021
67577dc
Merge remote-tracking branch 'origin/master' into feature/runtime_fie…
romseygeek Jul 22, 2021
a78c453
Merge remote-tracking branch 'origin/master' into feature/runtime_fie…
romseygeek Jul 26, 2021
6752847
Merge remote-tracking branch 'origin/master' into feature/runtime_fie…
romseygeek Aug 3, 2021
256e770
Merge branch 'master' into feature/runtime_fields_multi_emit
elasticmachine Aug 4, 2021
7872c94
well that would never have worked would it?
romseygeek Aug 4, 2021
5bd7e51
feedback
romseygeek Aug 5, 2021
9d48afb
Merge remote-tracking branch 'origin/master' into feature/runtime_fie…
romseygeek Aug 9, 2021
767633d
more cleanups
romseygeek Aug 9, 2021
803ed32
feedback
romseygeek Aug 9, 2021
4741f5c
Merge branch 'master' into feature/runtime_fields_multi_emit
elasticmachine Aug 10, 2021
66b7cb6
wtf
romseygeek Aug 10, 2021
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
@@ -0,0 +1,21 @@
#
# 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 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 or the Server
# Side Public License, v 1.
#

# The whitelist for object runtime fields

# These two whitelists are required for painless to find the classes
class org.elasticsearch.script.ObjectFieldScript @no_import {
}
class org.elasticsearch.script.ObjectFieldScript$Factory @no_import {
}

static_import {
# The `emit` callback to collect values for the fields
void emit(org.elasticsearch.script.ObjectFieldScript, String, Object) bound_to org.elasticsearch.script.ObjectFieldScript$EmitField
void emit(org.elasticsearch.script.ObjectFieldScript, Map) bound_to org.elasticsearch.script.ObjectFieldScript$EmitMap
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.ObjectFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptType;
Expand Down Expand Up @@ -194,33 +195,42 @@ protected final LeafFactory leafFactory(SearchExecutionContext context) {

abstract static class Builder<Factory> extends RuntimeField.Builder {
private final ScriptContext<Factory> scriptContext;
private final Factory parseFromSourceFactory;

final FieldMapper.Parameter<Script> script = new FieldMapper.Parameter<>(
"script",
true,
() -> null,
Builder::parseScript,
initializerNotSupported()
RuntimeField.initializerNotSupported()
).setSerializerCheck((id, ic, v) -> ic);

Builder(String name, ScriptContext<Factory> scriptContext, Factory parseFromSourceFactory) {
Builder(String name, ScriptContext<Factory> scriptContext) {
super(name);
this.scriptContext = scriptContext;
this.parseFromSourceFactory = parseFromSourceFactory;
}

abstract Factory getParseFromSourceFactory();

abstract Factory getObjectSubfieldFactory(Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory);

@Override
protected final RuntimeField createRuntimeField(MappingParserContext parserContext) {
protected final RuntimeField createRuntimeField(MappingParserContext parserContext,
String parent,
Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory) {
if (script.get() == null) {
return createRuntimeField(parseFromSourceFactory);
if (parentScriptFactory == null) {
return createRuntimeField(parent, getParseFromSourceFactory());
}
return createRuntimeField(parent, getObjectSubfieldFactory(parentScriptFactory));
}
assert parent == null && parentScriptFactory == null;
Factory factory = parserContext.scriptCompiler().compile(script.getValue(), scriptContext);
return createRuntimeField(factory);
return createRuntimeField(null, factory);
}

final RuntimeField createRuntimeField(Factory scriptFactory) {
AbstractScriptFieldType<?> fieldType = createFieldType(name, scriptFactory, getScript(), meta());
final RuntimeField createRuntimeField(String parent, Factory scriptFactory) {
jtibshirani marked this conversation as resolved.
Show resolved Hide resolved
String fullName = parent == null ? name : parent + "." + name;
AbstractScriptFieldType<?> fieldType = createFieldType(fullName, scriptFactory, getScript(), meta());
return new LeafRuntimeField(name, fieldType, getParameters());
}

Expand All @@ -240,16 +250,12 @@ protected final Script getScript() {
return script.get();
}

private static Script parseScript(String name, MappingParserContext parserContext, Object scriptObject) {
static Script parseScript(String name, MappingParserContext parserContext, Object scriptObject) {
Script script = Script.parse(scriptObject);
if (script.getType() == ScriptType.STORED) {
throw new IllegalArgumentException("stored scripts are not supported for runtime field [" + name + "]");
}
return script;
}
}

static <T> Function<FieldMapper, T> initializerNotSupported() {
return mapper -> { throw new UnsupportedOperationException(); };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.elasticsearch.index.fielddata.BooleanScriptFieldData;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.BooleanFieldScript;
import org.elasticsearch.script.ObjectFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.lookup.SearchLookup;
Expand All @@ -27,6 +28,7 @@
import java.time.ZoneId;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

public final class BooleanScriptFieldType extends AbstractScriptFieldType<BooleanFieldScript.LeafFactory> {
Expand All @@ -35,7 +37,7 @@ public final class BooleanScriptFieldType extends AbstractScriptFieldType<Boolea

private static class Builder extends AbstractScriptFieldType.Builder<BooleanFieldScript.Factory> {
Builder(String name) {
super(name, BooleanFieldScript.CONTEXT, BooleanFieldScript.PARSE_FROM_SOURCE);
super(name, BooleanFieldScript.CONTEXT);
}

@Override
Expand All @@ -45,10 +47,21 @@ AbstractScriptFieldType<?> createFieldType(String name,
Map<String, String> meta) {
return new BooleanScriptFieldType(name, factory, script, meta);
}

@Override
BooleanFieldScript.Factory getParseFromSourceFactory() {
return BooleanFieldScript.PARSE_FROM_SOURCE;
}

@Override
BooleanFieldScript.Factory getObjectSubfieldFactory(Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory) {
return BooleanFieldScript.objectAdapter(parentScriptFactory);
}

}

public static RuntimeField sourceOnly(String name) {
return new Builder(name).createRuntimeField(BooleanFieldScript.PARSE_FROM_SOURCE);
return new Builder(name).createRuntimeField(null, BooleanFieldScript.PARSE_FROM_SOURCE);
}

BooleanScriptFieldType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.elasticsearch.index.mapper.DateFieldMapper.Resolution;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.DateFieldScript;
import org.elasticsearch.script.ObjectFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.lookup.SearchLookup;
Expand All @@ -40,6 +41,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

public class DateScriptFieldType extends AbstractScriptFieldType<DateFieldScript.LeafFactory> {
Expand All @@ -50,7 +52,7 @@ private static class Builder extends AbstractScriptFieldType.Builder<DateFieldSc
private final FieldMapper.Parameter<String> format = FieldMapper.Parameter.stringParam(
"format",
true,
initializerNotSupported(),
RuntimeField.initializerNotSupported(),
null
).setSerializer((b, n, v) -> {
if (v != null && false == v.equals(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.pattern())) {
Expand All @@ -63,15 +65,15 @@ private static class Builder extends AbstractScriptFieldType.Builder<DateFieldSc
true,
() -> null,
(n, c, o) -> o == null ? null : LocaleUtils.parse(o.toString()),
initializerNotSupported()
RuntimeField.initializerNotSupported()
).setSerializer((b, n, v) -> {
if (v != null && false == v.equals(Locale.ROOT)) {
b.field(n, v.toString());
}
}, Object::toString).acceptsNull();

Builder(String name) {
super(name, DateFieldScript.CONTEXT, DateFieldScript.PARSE_FROM_SOURCE);
super(name, DateFieldScript.CONTEXT);
}

@Override
Expand All @@ -89,12 +91,22 @@ AbstractScriptFieldType<?> createFieldType(String name, DateFieldScript.Factory
DateFormatter dateTimeFormatter = DateFormatter.forPattern(pattern).withLocale(locale);
return new DateScriptFieldType(name, factory, dateTimeFormatter, script, meta);
}

@Override
DateFieldScript.Factory getParseFromSourceFactory() {
return DateFieldScript.PARSE_FROM_SOURCE;
}

@Override
DateFieldScript.Factory getObjectSubfieldFactory(Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory) {
return DateFieldScript.objectAdapter(parentScriptFactory);
}
}

public static RuntimeField sourceOnly(String name, DateFormatter dateTimeFormatter) {
Builder builder = new Builder(name);
builder.format.setValue(dateTimeFormatter.pattern());
return builder.createRuntimeField(DateFieldScript.PARSE_FROM_SOURCE);
return builder.createRuntimeField(null, DateFieldScript.PARSE_FROM_SOURCE);
}

private final DateFormatter dateTimeFormatter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,10 @@ private static Mapper getLeafMapper(final DocumentParserContext context,
// don't create a dynamic mapping for it and don't index it.
String fieldPath = context.path().pathAsText(fieldName);
MappedFieldType fieldType = context.mappingLookup().getFieldType(fieldPath);
//TODO test that we don't index sub-fields that are part of a runtime object, unless explicitly mapped.
if (fieldType != null) {
//we haven't found a mapper with this name above, which means if a field type is found it is for sure a runtime field.
//we have looked for the mapper above and we did not find it. If we do find a MappedFieldType for the field,
//we can assume it comes from a runtime field. That is what the assertion enforces.
assert fieldType.hasDocValues() == false && fieldType.isAggregatable() && fieldType.isSearchable();
return new NoOpFieldMapper(subfields[subfields.length - 1], fieldType.name());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.DoubleFieldScript;
import org.elasticsearch.script.ObjectFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.lookup.SearchLookup;
Expand All @@ -29,6 +30,7 @@
import java.time.ZoneId;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

public final class DoubleScriptFieldType extends AbstractScriptFieldType<DoubleFieldScript.LeafFactory> {
Expand All @@ -37,7 +39,7 @@ public final class DoubleScriptFieldType extends AbstractScriptFieldType<DoubleF

private static class Builder extends AbstractScriptFieldType.Builder<DoubleFieldScript.Factory> {
Builder(String name) {
super(name, DoubleFieldScript.CONTEXT, DoubleFieldScript.PARSE_FROM_SOURCE);
super(name, DoubleFieldScript.CONTEXT);
}

@Override
Expand All @@ -47,10 +49,20 @@ AbstractScriptFieldType<?> createFieldType(String name,
Map<String, String> meta) {
return new DoubleScriptFieldType(name, factory, script, meta);
}

@Override
DoubleFieldScript.Factory getParseFromSourceFactory() {
return DoubleFieldScript.PARSE_FROM_SOURCE;
}

@Override
DoubleFieldScript.Factory getObjectSubfieldFactory(Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory) {
return DoubleFieldScript.objectAdapter(parentScriptFactory);
}
}

public static RuntimeField sourceOnly(String name) {
return new Builder(name).createRuntimeField(DoubleFieldScript.PARSE_FROM_SOURCE);
return new Builder(name).createRuntimeField(null, DoubleFieldScript.PARSE_FROM_SOURCE);
}

DoubleScriptFieldType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ private static boolean applyMatchingTemplate(DocumentParserContext context,
if (parser == null) {
throw new MapperParsingException("failed to find type parsed [" + mappingType + "] for [" + fullName + "]");
}
RuntimeField runtimeField = parser.parse(fullName, mapping, parserContext);
RuntimeField runtimeField = parser.parse(fullName, mapping, parserContext, null, null);
Runtime.createDynamicField(runtimeField, context);
} else {
Mapper.Builder builder = parseDynamicTemplateMapping(name, mappingType, mapping, dateFormatter, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.elasticsearch.index.fielddata.GeoPointScriptFieldData;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.GeoPointFieldScript;
import org.elasticsearch.script.ObjectFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.search.runtime.GeoPointScriptFieldDistanceFeatureQuery;
Expand All @@ -31,19 +32,31 @@
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

public final class GeoPointScriptFieldType extends AbstractScriptFieldType<GeoPointFieldScript.LeafFactory> implements GeoShapeQueryable {

public static final RuntimeField.Parser PARSER = new RuntimeField.Parser(name ->
new Builder<>(name, GeoPointFieldScript.CONTEXT, GeoPointFieldScript.PARSE_FROM_SOURCE) {
new Builder<>(name, GeoPointFieldScript.CONTEXT) {
@Override
AbstractScriptFieldType<?> createFieldType(String name,
GeoPointFieldScript.Factory factory,
Script script,
Map<String, String> meta) {
return new GeoPointScriptFieldType(name, factory, getScript(), meta());
}

@Override
GeoPointFieldScript.Factory getParseFromSourceFactory() {
return GeoPointFieldScript.PARSE_FROM_SOURCE;
}

@Override
GeoPointFieldScript.Factory getObjectSubfieldFactory(
Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory) {
return GeoPointFieldScript.objectAdapter(parentScriptFactory);
}
});

GeoPointScriptFieldType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.elasticsearch.index.fielddata.IpScriptFieldData;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.IpFieldScript;
import org.elasticsearch.script.ObjectFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.lookup.SearchLookup;
Expand All @@ -37,19 +38,30 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

public final class IpScriptFieldType extends AbstractScriptFieldType<IpFieldScript.LeafFactory> {

public static final RuntimeField.Parser PARSER = new RuntimeField.Parser(name ->
new Builder<>(name, IpFieldScript.CONTEXT, IpFieldScript.PARSE_FROM_SOURCE) {
new Builder<>(name, IpFieldScript.CONTEXT) {
@Override
AbstractScriptFieldType<?> createFieldType(String name,
IpFieldScript.Factory factory,
Script script,
Map<String, String> meta) {
return new IpScriptFieldType(name, factory, getScript(), meta());
}

@Override
IpFieldScript.Factory getParseFromSourceFactory() {
return IpFieldScript.PARSE_FROM_SOURCE;
}

@Override
IpFieldScript.Factory getObjectSubfieldFactory(Function<SearchLookup, ObjectFieldScript.LeafFactory> parentScriptFactory) {
return IpFieldScript.objectAdapter(parentScriptFactory);
}
});

IpScriptFieldType(
Expand Down
Loading