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 26 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 composite runtime fields

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

static_import {
# The `emit` callback to collect values for the fields
void emit(org.elasticsearch.script.CompositeFieldScript, String, Object) bound_to org.elasticsearch.script.CompositeFieldScript$EmitField
void emit(org.elasticsearch.script.CompositeFieldScript, Map) bound_to org.elasticsearch.script.CompositeFieldScript$EmitMap
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
setup:
- do:
indices.create:
index: http_logs
body:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
runtime:
http:
type: composite
script:
source: |
emit(grok('%{COMMONAPACHELOG}').extract(doc["message"].value));
fields:
clientip:
type: ip
verb:
type: keyword
response:
type: long
properties:
timestamp:
type: date
message:
type: keyword
- do:
bulk:
index: http_logs
refresh: true
body: |
{"index":{}}
{"timestamp": "1998-04-30T14:30:17-05:00", "message" : "40.135.0.0 - - [30/Apr/1998:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp": "1998-04-30T14:30:53-05:00", "message" : "232.0.0.0 - - [30/Apr/1998:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp": "1998-04-30T14:31:12-05:00", "message" : "26.1.0.0 - - [30/Apr/1998:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp": "1998-04-30T14:31:19-05:00", "message" : "247.37.0.0 - - [30/Apr/1998:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"}
{"index":{}}
{"timestamp": "1998-04-30T14:31:22-05:00", "message" : "247.37.0.0 - - [30/Apr/1998:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"}
{"index":{}}
{"timestamp": "1998-04-30T14:31:27-05:00", "message" : "252.0.0.0 - - [30/Apr/1998:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp": "1998-04-30T14:31:28-05:00", "message" : "not a valid apache log"}

---
fetch:
- do:
search:
index: http_logs
body:
sort: timestamp
fields:
- http.clientip
- http.verb
- http.response
- match: {hits.total.value: 7}
- match: {hits.hits.0.fields.http\.clientip: [40.135.0.0] }
- match: {hits.hits.0.fields.http\.verb: [GET] }
- match: {hits.hits.0.fields.http\.response: [200] }
- is_false: hits.hits.6.fields.http\.clientip
- is_false: hits.hits.6.fields.http\.verb
- is_false: hits.hits.6.fields.http\.response

---
query:
- do:
search:
index: http_logs
body:
query:
term:
http.verb: GET
- match: { hits.total.value: 6 }

- do:
search:
index: http_logs
body:
query:
range:
http.clientip:
from: 232.0.0.0
to: 253.0.0.0
- match: { hits.total.value: 4 }

---
"terms agg":
- do:
search:
index: http_logs
body:
aggs:
response:
terms:
field: http.response
- match: {hits.total.value: 7}
- match: {aggregations.response.buckets.0.key: 200 }
- match: {aggregations.response.buckets.0.doc_count: 5 }
- match: {aggregations.response.buckets.1.key: 304 }
- match: {aggregations.response.buckets.1.doc_count: 1 }
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.CompositeFieldScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptType;
Expand Down Expand Up @@ -194,33 +195,56 @@ 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 getCompositeLeafFactory(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory);

@Override
protected final RuntimeField createRuntimeField(MappingParserContext parserContext) {
protected RuntimeField createRuntimeField(MappingParserContext parserContext) {
if (script.get() == null) {
return createRuntimeField(parseFromSourceFactory);
return new LeafRuntimeField(
name,
createFieldType(name, getParseFromSourceFactory(), getScript(), meta()),
getParameters()
);
}
Factory factory = parserContext.scriptCompiler().compile(script.getValue(), scriptContext);
return createRuntimeField(factory);
return new LeafRuntimeField(name, createFieldType(name, factory, getScript(), meta()), getParameters());
}

final RuntimeField createRuntimeField(Factory scriptFactory) {
AbstractScriptFieldType<?> fieldType = createFieldType(name, scriptFactory, getScript(), meta());
@Override
protected final RuntimeField createChildRuntimeField(MappingParserContext parserContext,
String parent,
Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory) {
if (script.isConfigured()) {
throw new IllegalArgumentException("Cannot use [script] parameter on sub-field [" + name +
"] of composite field [" + parent + "]");
}
String fullName = parent + "." + name;
return new LeafRuntimeField(
name,
createFieldType(fullName, getCompositeLeafFactory(parentScriptFactory), getScript(), meta()),
getParameters()
);
}

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 +264,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.CompositeFieldScript;
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 getCompositeLeafFactory(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory) {
return BooleanFieldScript.leafAdapter(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
Loading