Skip to content

Commit e833e8e

Browse files
authored
Scripted keyword field (#58939)
This is the first draft implementation of scripted field of type keyword. Still lots of TODOs to address, this PR targets a feature branch.
1 parent 59fc3d7 commit e833e8e

File tree

18 files changed

+562
-76
lines changed

18 files changed

+562
-76
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.fielddata;
21+
22+
import org.elasticsearch.search.lookup.SearchLookup;
23+
24+
//TODO this is a temporary interface only to avoid changing signature of MappedFieldType#fielddataBuilder
25+
public interface SearchLookupAware {
26+
27+
void setSearchLookup(SearchLookup searchLookup);
28+
}

server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.elasticsearch.index.analysis.IndexAnalyzers;
4444
import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
4545
import org.elasticsearch.index.fielddata.IndexFieldData;
46+
import org.elasticsearch.index.fielddata.SearchLookupAware;
4647
import org.elasticsearch.index.mapper.ContentPath;
4748
import org.elasticsearch.index.mapper.MappedFieldType;
4849
import org.elasticsearch.index.mapper.Mapper;
@@ -208,7 +209,13 @@ public boolean allowExpensiveQueries() {
208209

209210
@SuppressWarnings("unchecked")
210211
public <IFD extends IndexFieldData<?>> IFD getForField(MappedFieldType fieldType) {
211-
return (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndex.getName());
212+
IFD indexFieldData = (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndex.getName());
213+
//TODO this is a temporary hack to inject search lookup to the scripted fielddata
214+
// implementations without changing MappedFieldType#fielddataBuilder signature, as that would cause daily merge conflicts
215+
if (indexFieldData instanceof SearchLookupAware) {
216+
((SearchLookupAware) indexFieldData).setSearchLookup(lookup());
217+
}
218+
return indexFieldData;
212219
}
213220

214221
public void addNamedQuery(String name, Query query) {

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/AbstractScriptFieldScript.java

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
import org.apache.lucene.index.LeafReaderContext;
1010
import org.elasticsearch.index.fielddata.ScriptDocValues;
1111
import org.elasticsearch.script.AggregationScript;
12-
import org.elasticsearch.search.lookup.DocLookup;
13-
import org.elasticsearch.search.lookup.LeafDocLookup;
14-
import org.elasticsearch.search.lookup.SourceLookup;
12+
import org.elasticsearch.search.lookup.LeafSearchLookup;
13+
import org.elasticsearch.search.lookup.SearchLookup;
1514

1615
import java.util.Map;
1716

@@ -21,23 +20,19 @@
2120
*/
2221
public abstract class AbstractScriptFieldScript {
2322
private final Map<String, Object> params;
24-
private final LeafReaderContext ctx;
25-
private final SourceLookup source;
26-
private final LeafDocLookup fieldData;
23+
private final LeafSearchLookup leafSearchLookup;
2724

28-
public AbstractScriptFieldScript(Map<String, Object> params, SourceLookup source, DocLookup fieldData, LeafReaderContext ctx) {
25+
public AbstractScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
26+
this.leafSearchLookup = searchLookup.getLeafSearchLookup(ctx);
27+
// TODO how do other scripts get stored fields exposed? Through asMap? I don't see any getters for them.
2928
this.params = params;
30-
this.source = source;
31-
this.fieldData = fieldData.getLeafDocLookup(ctx);
32-
this.ctx = ctx;
3329
}
3430

3531
/**
3632
* Set the document to run the script against.
3733
*/
3834
public final void setDocument(int docId) {
39-
source.setSegmentAndDocument(ctx, docId);
40-
fieldData.setDocument(docId);
35+
this.leafSearchLookup.setDocument(docId);
4136
}
4237

4338
/**
@@ -51,16 +46,15 @@ public final Map<String, Object> getParams() {
5146
* Expose the {@code _source} to the script.
5247
*/
5348
public final Map<String, Object> getSource() {
54-
return source;
49+
return leafSearchLookup.source();
5550
}
5651

5752
/**
5853
* Expose field data to the script as {@code doc}.
5954
*/
6055
public final Map<String, ScriptDocValues<?>> getDoc() {
61-
return fieldData;
56+
return leafSearchLookup.doc();
6257
}
6358

6459
public abstract void execute();
65-
6660
}

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/DoubleScriptFieldScript.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
import org.elasticsearch.painless.spi.WhitelistLoader;
1212
import org.elasticsearch.script.ScriptContext;
1313
import org.elasticsearch.script.ScriptFactory;
14-
import org.elasticsearch.search.lookup.DocLookup;
15-
import org.elasticsearch.search.lookup.SourceLookup;
14+
import org.elasticsearch.search.lookup.SearchLookup;
1615

1716
import java.io.IOException;
1817
import java.util.List;
@@ -29,7 +28,7 @@ static List<Whitelist> whitelist() {
2928
public static final String[] PARAMETERS = {};
3029

3130
public interface Factory extends ScriptFactory {
32-
LeafFactory newFactory(Map<String, Object> params, SourceLookup source, DocLookup fieldData);
31+
LeafFactory newFactory(Map<String, Object> params, SearchLookup searchLookup);
3332
}
3433

3534
public interface LeafFactory {
@@ -38,14 +37,8 @@ public interface LeafFactory {
3837

3938
private final DoubleConsumer sync;
4039

41-
public DoubleScriptFieldScript(
42-
Map<String, Object> params,
43-
SourceLookup source,
44-
DocLookup fieldData,
45-
LeafReaderContext ctx,
46-
DoubleConsumer sync
47-
) {
48-
super(params, source, fieldData, ctx);
40+
public DoubleScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx, DoubleConsumer sync) {
41+
super(params, searchLookup, ctx);
4942
this.sync = sync;
5043
}
5144

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/LongScriptFieldScript.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
import org.elasticsearch.painless.spi.WhitelistLoader;
1212
import org.elasticsearch.script.ScriptContext;
1313
import org.elasticsearch.script.ScriptFactory;
14-
import org.elasticsearch.search.lookup.DocLookup;
15-
import org.elasticsearch.search.lookup.SourceLookup;
14+
import org.elasticsearch.search.lookup.SearchLookup;
1615

1716
import java.io.IOException;
1817
import java.util.List;
@@ -29,7 +28,7 @@ static List<Whitelist> whitelist() {
2928
public static final String[] PARAMETERS = {};
3029

3130
public interface Factory extends ScriptFactory {
32-
LeafFactory newFactory(Map<String, Object> params, SourceLookup source, DocLookup fieldData);
31+
LeafFactory newFactory(Map<String, Object> params, SearchLookup searchLookup);
3332
}
3433

3534
public interface LeafFactory {
@@ -38,14 +37,8 @@ public interface LeafFactory {
3837

3938
private final LongConsumer sync;
4039

41-
public LongScriptFieldScript(
42-
Map<String, Object> params,
43-
SourceLookup source,
44-
DocLookup fieldData,
45-
LeafReaderContext ctx,
46-
LongConsumer sync
47-
) {
48-
super(params, source, fieldData, ctx);
40+
public LongScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx, LongConsumer sync) {
41+
super(params, searchLookup, ctx);
4942
this.sync = sync;
5043
}
5144

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/RuntimeFields.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,60 @@
66

77
package org.elasticsearch.xpack.runtimefields;
88

9+
import org.elasticsearch.client.Client;
10+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
11+
import org.elasticsearch.cluster.service.ClusterService;
12+
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
13+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
14+
import org.elasticsearch.env.Environment;
15+
import org.elasticsearch.env.NodeEnvironment;
916
import org.elasticsearch.index.mapper.Mapper;
1017
import org.elasticsearch.plugins.MapperPlugin;
1118
import org.elasticsearch.plugins.Plugin;
1219
import org.elasticsearch.plugins.ScriptPlugin;
20+
import org.elasticsearch.repositories.RepositoriesService;
1321
import org.elasticsearch.script.ScriptContext;
22+
import org.elasticsearch.script.ScriptService;
23+
import org.elasticsearch.threadpool.ThreadPool;
24+
import org.elasticsearch.watcher.ResourceWatcherService;
25+
import org.elasticsearch.xpack.runtimefields.mapper.ScriptFieldMapper;
1426

27+
import java.util.Collection;
1528
import java.util.Collections;
1629
import java.util.List;
1730
import java.util.Map;
31+
import java.util.function.Supplier;
1832

1933
public final class RuntimeFields extends Plugin implements MapperPlugin, ScriptPlugin {
2034

35+
private final ScriptFieldMapper.TypeParser scriptTypeParser = new ScriptFieldMapper.TypeParser();
36+
2137
@Override
2238
public Map<String, Mapper.TypeParser> getMappers() {
23-
return Collections.emptyMap();
39+
return Collections.singletonMap(ScriptFieldMapper.CONTENT_TYPE, scriptTypeParser);
2440
}
2541

2642
@Override
2743
public List<ScriptContext<?>> getContexts() {
2844
return List.of(DoubleScriptFieldScript.CONTEXT, LongScriptFieldScript.CONTEXT, StringScriptFieldScript.CONTEXT);
2945
}
46+
47+
@Override
48+
public Collection<Object> createComponents(
49+
Client client,
50+
ClusterService clusterService,
51+
ThreadPool threadPool,
52+
ResourceWatcherService resourceWatcherService,
53+
ScriptService scriptService,
54+
NamedXContentRegistry xContentRegistry,
55+
Environment environment,
56+
NodeEnvironment nodeEnvironment,
57+
NamedWriteableRegistry namedWriteableRegistry,
58+
IndexNameExpressionResolver indexNameExpressionResolver,
59+
Supplier<RepositoriesService> repositoriesServiceSupplier
60+
) {
61+
// looks like createComponents gets called after getMappers
62+
this.scriptTypeParser.setScriptService(scriptService);
63+
return Collections.emptyList();
64+
}
3065
}

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScript.java

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,15 @@
1111
import org.elasticsearch.painless.spi.WhitelistLoader;
1212
import org.elasticsearch.script.ScriptContext;
1313
import org.elasticsearch.script.ScriptFactory;
14-
import org.elasticsearch.search.lookup.DocLookup;
15-
import org.elasticsearch.search.lookup.SourceLookup;
14+
import org.elasticsearch.search.lookup.SearchLookup;
1615

1716
import java.io.IOException;
1817
import java.util.List;
1918
import java.util.Map;
2019
import java.util.function.Consumer;
2120

2221
public abstract class StringScriptFieldScript extends AbstractScriptFieldScript {
23-
static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("string_script_field", Factory.class);
22+
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("string_script_field", Factory.class);
2423

2524
static List<Whitelist> whitelist() {
2625
return List.of(WhitelistLoader.loadFromResourceFiles(RuntimeFieldsPainlessExtension.class, "string_whitelist.txt"));
@@ -29,7 +28,7 @@ static List<Whitelist> whitelist() {
2928
public static final String[] PARAMETERS = {};
3029

3130
public interface Factory extends ScriptFactory {
32-
LeafFactory newFactory(Map<String, Object> params, SourceLookup source, DocLookup fieldData);
31+
LeafFactory newFactory(Map<String, Object> params, SearchLookup searchLookup);
3332
}
3433

3534
public interface LeafFactory {
@@ -38,14 +37,8 @@ public interface LeafFactory {
3837

3938
private final Consumer<String> sync;
4039

41-
public StringScriptFieldScript(
42-
Map<String, Object> params,
43-
SourceLookup source,
44-
DocLookup fieldData,
45-
LeafReaderContext ctx,
46-
Consumer<String> sync
47-
) {
48-
super(params, source, fieldData, ctx);
40+
public StringScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx, Consumer<String> sync) {
41+
super(params, searchLookup, ctx);
4942
this.sync = sync;
5043
}
5144

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.runtimefields.fielddata;
8+
9+
import org.elasticsearch.index.fielddata.SortingBinaryDocValues;
10+
import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript;
11+
12+
public final class ScriptBinaryDocValues extends SortingBinaryDocValues {
13+
14+
private final StringScriptFieldScript script;
15+
private final ScriptBinaryFieldData.ScriptBinaryResult scriptBinaryResult;
16+
17+
ScriptBinaryDocValues(StringScriptFieldScript script, ScriptBinaryFieldData.ScriptBinaryResult scriptBinaryResult) {
18+
this.script = script;
19+
this.scriptBinaryResult = scriptBinaryResult;
20+
}
21+
22+
@Override
23+
public boolean advanceExact(int doc) {
24+
script.setDocument(doc);
25+
script.execute();
26+
27+
count = scriptBinaryResult.getResult().size();
28+
if (count == 0) {
29+
grow();
30+
return false;
31+
}
32+
33+
int i = 0;
34+
for (String value : scriptBinaryResult.getResult()) {
35+
grow();
36+
values[i++].copyChars(value);
37+
}
38+
sort();
39+
return true;
40+
}
41+
}

0 commit comments

Comments
 (0)