Skip to content

Commit c8b5af9

Browse files
authored
Add long flavored script field (#59721)
This adds the `long` runtime type to `script` fields and implements doc values, field data, the `term` and `exists` query.
1 parent 45d0dc5 commit c8b5af9

28 files changed

+1229
-264
lines changed

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -701,23 +701,7 @@ public List<Field> createFields(String name, Number value,
701701
LONG("long", NumericType.LONG) {
702702
@Override
703703
public Long parse(Object value, boolean coerce) {
704-
if (value instanceof Long) {
705-
return (Long)value;
706-
}
707-
708-
double doubleValue = objectToDouble(value);
709-
// this check does not guarantee that value is inside MIN_VALUE/MAX_VALUE because values up to 9223372036854776832 will
710-
// be equal to Long.MAX_VALUE after conversion to double. More checks ahead.
711-
if (doubleValue < Long.MIN_VALUE || doubleValue > Long.MAX_VALUE) {
712-
throw new IllegalArgumentException("Value [" + value + "] is out of range for a long");
713-
}
714-
if (!coerce && doubleValue % 1 != 0) {
715-
throw new IllegalArgumentException("Value [" + value + "] has a decimal part");
716-
}
717-
718-
// longs need special handling so we don't lose precision while parsing
719-
String stringValue = (value instanceof BytesRef) ? ((BytesRef) value).utf8ToString() : value.toString();
720-
return Numbers.toLong(stringValue, coerce);
704+
return objectToLong(value, coerce);
721705
}
722706

723707
@Override
@@ -854,7 +838,7 @@ Number valueForSearch(Number value) {
854838
/**
855839
* Returns true if the object is a number and has a decimal part
856840
*/
857-
boolean hasDecimalPart(Object number) {
841+
public static boolean hasDecimalPart(Object number) {
858842
if (number instanceof Number) {
859843
double doubleValue = ((Number) number).doubleValue();
860844
return doubleValue % 1 != 0;
@@ -898,6 +882,30 @@ private static double objectToDouble(Object value) {
898882

899883
return doubleValue;
900884
}
885+
886+
/**
887+
* Converts and Object to a {@code long} by checking it against known
888+
* types and checking its range.
889+
*/
890+
public static long objectToLong(Object value, boolean coerce) {
891+
if (value instanceof Long) {
892+
return (Long)value;
893+
}
894+
895+
double doubleValue = objectToDouble(value);
896+
// this check does not guarantee that value is inside MIN_VALUE/MAX_VALUE because values up to 9223372036854776832 will
897+
// be equal to Long.MAX_VALUE after conversion to double. More checks ahead.
898+
if (doubleValue < Long.MIN_VALUE || doubleValue > Long.MAX_VALUE) {
899+
throw new IllegalArgumentException("Value [" + value + "] is out of range for a long");
900+
}
901+
if (!coerce && doubleValue % 1 != 0) {
902+
throw new IllegalArgumentException("Value [" + value + "] has a decimal part");
903+
}
904+
905+
// longs need special handling so we don't lose precision while parsing
906+
String stringValue = (value instanceof BytesRef) ? ((BytesRef) value).utf8ToString() : value.toString();
907+
return Numbers.toLong(stringValue, coerce);
908+
}
901909
}
902910

903911
public static final class NumberFieldType extends SimpleMappedFieldType {

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.elasticsearch.xpack.runtimefields;
88

99
import org.apache.lucene.index.LeafReaderContext;
10+
import org.apache.lucene.util.ArrayUtil;
1011
import org.elasticsearch.painless.spi.Whitelist;
1112
import org.elasticsearch.painless.spi.WhitelistLoader;
1213
import org.elasticsearch.script.ScriptContext;
@@ -16,10 +17,9 @@
1617
import java.io.IOException;
1718
import java.util.List;
1819
import java.util.Map;
19-
import java.util.function.LongConsumer;
2020

2121
public abstract class LongScriptFieldScript extends AbstractScriptFieldScript {
22-
static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("long_script_field", Factory.class);
22+
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("long_script_field", Factory.class);
2323

2424
static List<Whitelist> whitelist() {
2525
return List.of(WhitelistLoader.loadFromResourceFiles(RuntimeFieldsPainlessExtension.class, "long_whitelist.txt"));
@@ -32,14 +32,47 @@ public interface Factory extends ScriptFactory {
3232
}
3333

3434
public interface LeafFactory {
35-
LongScriptFieldScript newInstance(LeafReaderContext ctx, LongConsumer sync) throws IOException;
35+
LongScriptFieldScript newInstance(LeafReaderContext ctx) throws IOException;
3636
}
3737

38-
private final LongConsumer sync;
38+
private long[] values = new long[1];
39+
private int count;
3940

40-
public LongScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx, LongConsumer sync) {
41+
public LongScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
4142
super(params, searchLookup, ctx);
42-
this.sync = sync;
43+
}
44+
45+
/**
46+
* Execute the script for the provided {@code docId}.
47+
*/
48+
public final void runForDoc(int docId) {
49+
count = 0;
50+
setDocument(docId);
51+
execute();
52+
}
53+
54+
/**
55+
* Values from the last time {@link #runForDoc(int)} was called. This array
56+
* is mutable and will change with the next call of {@link #runForDoc(int)}.
57+
* It is also oversized and will contain garbage at all indices at and
58+
* above {@link #count()}.
59+
*/
60+
public final long[] values() {
61+
return values;
62+
}
63+
64+
/**
65+
* The number of results produced the last time {@link #runForDoc(int)} was called.
66+
*/
67+
public final int count() {
68+
return count;
69+
}
70+
71+
private void collectValue(long v) {
72+
if (values.length < count + 1) {
73+
values = ArrayUtil.grow(values, count + 1);
74+
}
75+
values[count++] = v;
4376
}
4477

4578
public static class Value {
@@ -50,7 +83,7 @@ public Value(LongScriptFieldScript script) {
5083
}
5184

5285
public void value(long v) {
53-
script.sync.accept(v);
86+
script.collectValue(v);
5487
}
5588
}
5689
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public interface LeafFactory {
3535
StringScriptFieldScript newInstance(LeafReaderContext ctx) throws IOException;
3636
}
3737

38-
protected final List<String> results = new ArrayList<>();
38+
private final List<String> results = new ArrayList<>();
3939

4040
public StringScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
4141
super(params, searchLookup, ctx);

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import org.apache.lucene.index.LeafReaderContext;
1010
import org.apache.lucene.search.SortField;
1111
import org.apache.lucene.util.SetOnce;
12-
import org.elasticsearch.ElasticsearchException;
12+
import org.elasticsearch.ExceptionsHelper;
1313
import org.elasticsearch.common.util.BigArrays;
1414
import org.elasticsearch.index.AbstractIndexComponent;
1515
import org.elasticsearch.index.IndexSettings;
@@ -34,8 +34,6 @@
3434
import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript;
3535

3636
import java.io.IOException;
37-
import java.util.ArrayList;
38-
import java.util.List;
3937

4038
public final class ScriptBinaryFieldData extends AbstractIndexComponent
4139
implements
@@ -81,6 +79,7 @@ private ScriptBinaryFieldData(
8179
this.scriptFactory = scriptFactory;
8280
}
8381

82+
@Override
8483
public void setSearchLookup(SearchLookup searchLookup) {
8584
this.leafFactory.set(scriptFactory.newFactory(script.getParams(), searchLookup));
8685
}
@@ -100,11 +99,7 @@ public ScriptBinaryLeafFieldData load(LeafReaderContext context) {
10099
try {
101100
return loadDirect(context);
102101
} catch (Exception e) {
103-
if (e instanceof ElasticsearchException) {
104-
throw (ElasticsearchException) e;
105-
} else {
106-
throw new ElasticsearchException(e);
107-
}
102+
throw ExceptionsHelper.convertToElastic(e);
108103
}
109104
}
110105

@@ -165,16 +160,4 @@ public void close() {
165160

166161
}
167162
}
168-
169-
static class ScriptBinaryResult {
170-
private final List<String> result = new ArrayList<>();
171-
172-
void accept(String value) {
173-
this.result.add(value);
174-
}
175-
176-
List<String> getResult() {
177-
return result;
178-
}
179-
}
180163
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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.AbstractSortedNumericDocValues;
10+
import org.elasticsearch.xpack.runtimefields.LongScriptFieldScript;
11+
12+
import java.io.IOException;
13+
import java.util.Arrays;
14+
15+
public final class ScriptLongDocValues extends AbstractSortedNumericDocValues {
16+
private final LongScriptFieldScript script;
17+
private int cursor;
18+
19+
ScriptLongDocValues(LongScriptFieldScript script) {
20+
this.script = script;
21+
}
22+
23+
@Override
24+
public boolean advanceExact(int docId) {
25+
script.runForDoc(docId);
26+
if (script.count() == 0) {
27+
return false;
28+
}
29+
Arrays.sort(script.values(), 0, script.count());
30+
cursor = 0;
31+
return true;
32+
}
33+
34+
@Override
35+
public long nextValue() throws IOException {
36+
return script.values()[cursor++];
37+
}
38+
39+
@Override
40+
public int docValueCount() {
41+
return script.count();
42+
}
43+
}

0 commit comments

Comments
 (0)