Skip to content

Commit e98dfcc

Browse files
authored
Implement runtime script ips (#60533)
This implements the `ip` typed runtime fields. They share a fair bit with `string` runtime fields but we represent them as a `BytesRef` containing 128 bits so that the comparisons all happen in the same way as Lucene's `InetAddressPoint`.
1 parent 4540211 commit e98dfcc

28 files changed

+1909
-65
lines changed

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.util.Iterator;
5555
import java.util.List;
5656
import java.util.Map;
57+
import java.util.function.BiFunction;
5758
import java.util.function.Supplier;
5859

5960
/** A {@link FieldMapper} for ip addresses. */
@@ -155,7 +156,7 @@ public String typeName() {
155156
return CONTENT_TYPE;
156157
}
157158

158-
private InetAddress parse(Object value) {
159+
private static InetAddress parse(Object value) {
159160
if (value instanceof InetAddress) {
160161
return (InetAddress) value;
161162
} else {
@@ -221,6 +222,26 @@ public Query termsQuery(List<?> values, QueryShardContext context) {
221222
@Override
222223
public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, QueryShardContext context) {
223224
failIfNotIndexed();
225+
return rangeQuery(
226+
lowerTerm,
227+
upperTerm,
228+
includeLower,
229+
includeUpper,
230+
(lower, upper) -> InetAddressPoint.newRangeQuery(name(), lower, upper)
231+
);
232+
}
233+
234+
/**
235+
* Processes query bounds into {@code long}s and delegates the
236+
* provided {@code builder} to build a range query.
237+
*/
238+
public static Query rangeQuery(
239+
Object lowerTerm,
240+
Object upperTerm,
241+
boolean includeLower,
242+
boolean includeUpper,
243+
BiFunction<InetAddress, InetAddress, Query> builder
244+
) {
224245
InetAddress lower;
225246
if (lowerTerm == null) {
226247
lower = InetAddressPoint.MIN_VALUE;
@@ -247,7 +268,7 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower
247268
}
248269
}
249270

250-
return InetAddressPoint.newRangeQuery(name(), lower, upper);
271+
return builder.apply(lower, upper);
251272
}
252273

253274
public static final class IpScriptDocValues extends ScriptDocValues<String> {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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;
8+
9+
import org.apache.lucene.document.InetAddressPoint;
10+
import org.apache.lucene.index.LeafReaderContext;
11+
import org.apache.lucene.util.ArrayUtil;
12+
import org.apache.lucene.util.BytesRef;
13+
import org.elasticsearch.common.network.InetAddresses;
14+
import org.elasticsearch.index.mapper.IpFieldMapper;
15+
import org.elasticsearch.painless.spi.Whitelist;
16+
import org.elasticsearch.painless.spi.WhitelistLoader;
17+
import org.elasticsearch.script.ScriptContext;
18+
import org.elasticsearch.script.ScriptFactory;
19+
import org.elasticsearch.search.lookup.SearchLookup;
20+
21+
import java.io.IOException;
22+
import java.net.Inet4Address;
23+
import java.net.Inet6Address;
24+
import java.net.InetAddress;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
/**
29+
* Script producing IP addresses. Unlike the other {@linkplain AbstractScriptFieldScript}s
30+
* which deal with their native java objects this converts its values to the same format
31+
* that Lucene uses to store its fields, {@link InetAddressPoint}. There are a few compelling
32+
* reasons to do this:
33+
* <ul>
34+
* <li>{@link Inet4Address}es and {@link Inet6Address} are not comparable with one another.
35+
* That is correct in some contexts, but not for our queries. Our queries must consider the
36+
* IPv4 address equal to the address that it maps to in IPv6 <a href="https://tools.ietf.org/html/rfc4291">rfc4291</a>).
37+
* <li>{@link InetAddress}es are not ordered, but we need to implement range queries with
38+
* same same ordering as {@link IpFieldMapper}. That also uses {@link InetAddressPoint}
39+
* so it saves us a lot of trouble to use the same representation.
40+
* </ul>
41+
*/
42+
public abstract class IpScriptFieldScript extends AbstractScriptFieldScript {
43+
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("ip_script_field", Factory.class);
44+
45+
static List<Whitelist> whitelist() {
46+
return List.of(WhitelistLoader.loadFromResourceFiles(RuntimeFieldsPainlessExtension.class, "ip_whitelist.txt"));
47+
}
48+
49+
public static final String[] PARAMETERS = {};
50+
51+
public interface Factory extends ScriptFactory {
52+
LeafFactory newFactory(Map<String, Object> params, SearchLookup searchLookup);
53+
}
54+
55+
public interface LeafFactory {
56+
IpScriptFieldScript newInstance(LeafReaderContext ctx) throws IOException;
57+
}
58+
59+
private BytesRef[] values = new BytesRef[1];
60+
private int count;
61+
62+
public IpScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
63+
super(params, searchLookup, ctx);
64+
}
65+
66+
/**
67+
* Execute the script for the provided {@code docId}.
68+
*/
69+
public final void runForDoc(int docId) {
70+
count = 0;
71+
setDocument(docId);
72+
execute();
73+
}
74+
75+
/**
76+
* Values from the last time {@link #runForDoc(int)} was called. This array
77+
* is mutable and will change with the next call of {@link #runForDoc(int)}.
78+
* It is also oversized and will contain garbage at all indices at and
79+
* above {@link #count()}.
80+
* <p>
81+
* All values are IPv6 addresses so they are 16 bytes. IPv4 addresses are
82+
* encoded by <a href="https://tools.ietf.org/html/rfc4291">rfc4291</a>.
83+
*/
84+
public final BytesRef[] values() {
85+
return values;
86+
}
87+
88+
/**
89+
* The number of results produced the last time {@link #runForDoc(int)} was called.
90+
*/
91+
public final int count() {
92+
return count;
93+
}
94+
95+
private void collectValue(String v) {
96+
if (values.length < count + 1) {
97+
values = ArrayUtil.grow(values, count + 1);
98+
}
99+
values[count++] = new BytesRef(InetAddressPoint.encode(InetAddresses.forString(v)));
100+
}
101+
102+
public static class StringValue {
103+
private final IpScriptFieldScript script;
104+
105+
public StringValue(IpScriptFieldScript script) {
106+
this.script = script;
107+
}
108+
109+
public void stringValue(String v) {
110+
script.collectValue(v);
111+
}
112+
}
113+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public List<ScriptContext<?>> getContexts() {
2929
return List.of(
3030
DateScriptFieldScript.CONTEXT,
3131
DoubleScriptFieldScript.CONTEXT,
32+
IpScriptFieldScript.CONTEXT,
3233
LongScriptFieldScript.CONTEXT,
3334
StringScriptFieldScript.CONTEXT
3435
);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
1919
return Map.ofEntries(
2020
Map.entry(DateScriptFieldScript.CONTEXT, DateScriptFieldScript.whitelist()),
2121
Map.entry(DoubleScriptFieldScript.CONTEXT, DoubleScriptFieldScript.whitelist()),
22+
Map.entry(IpScriptFieldScript.CONTEXT, IpScriptFieldScript.whitelist()),
2223
Map.entry(LongScriptFieldScript.CONTEXT, LongScriptFieldScript.whitelist()),
2324
Map.entry(StringScriptFieldScript.CONTEXT, StringScriptFieldScript.whitelist())
2425
);

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

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,58 +11,25 @@
1111
import org.elasticsearch.ExceptionsHelper;
1212
import org.elasticsearch.common.util.BigArrays;
1313
import org.elasticsearch.index.fielddata.IndexFieldData;
14-
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
1514
import org.elasticsearch.index.fielddata.LeafFieldData;
16-
import org.elasticsearch.index.fielddata.ScriptDocValues;
17-
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
1815
import org.elasticsearch.index.fielddata.fieldcomparator.BytesRefFieldComparatorSource;
19-
import org.elasticsearch.index.mapper.MapperService;
20-
import org.elasticsearch.indices.breaker.CircuitBreakerService;
2116
import org.elasticsearch.search.DocValueFormat;
2217
import org.elasticsearch.search.MultiValueMode;
23-
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
24-
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
2518
import org.elasticsearch.search.sort.BucketedSort;
2619
import org.elasticsearch.search.sort.SortOrder;
27-
import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript;
28-
29-
import java.io.IOException;
30-
31-
public final class ScriptBinaryFieldData implements IndexFieldData<ScriptBinaryFieldData.ScriptBinaryLeafFieldData> {
32-
33-
public static class Builder implements IndexFieldData.Builder {
34-
private final String name;
35-
private final StringScriptFieldScript.LeafFactory leafFactory;
36-
37-
public Builder(String name, StringScriptFieldScript.LeafFactory leafFactory) {
38-
this.name = name;
39-
this.leafFactory = leafFactory;
40-
}
41-
42-
@Override
43-
public ScriptBinaryFieldData build(IndexFieldDataCache cache, CircuitBreakerService breakerService, MapperService mapperService) {
44-
return new ScriptBinaryFieldData(name, leafFactory);
45-
}
46-
}
4720

21+
public abstract class ScriptBinaryFieldData implements IndexFieldData<ScriptBinaryFieldData.ScriptBinaryLeafFieldData> {
4822
private final String fieldName;
49-
private final StringScriptFieldScript.LeafFactory leafFactory;
5023

51-
private ScriptBinaryFieldData(String fieldName, StringScriptFieldScript.LeafFactory leafFactory) {
24+
protected ScriptBinaryFieldData(String fieldName) {
5225
this.fieldName = fieldName;
53-
this.leafFactory = leafFactory;
5426
}
5527

5628
@Override
5729
public String getFieldName() {
5830
return fieldName;
5931
}
6032

61-
@Override
62-
public ValuesSourceType getValuesSourceType() {
63-
return CoreValuesSourceType.BYTES;
64-
}
65-
6633
@Override
6734
public ScriptBinaryLeafFieldData load(LeafReaderContext context) {
6835
try {
@@ -72,11 +39,6 @@ public ScriptBinaryLeafFieldData load(LeafReaderContext context) {
7239
}
7340
}
7441

75-
@Override
76-
public ScriptBinaryLeafFieldData loadDirect(LeafReaderContext context) throws IOException {
77-
return new ScriptBinaryLeafFieldData(new ScriptBinaryDocValues(leafFactory.newInstance(context)));
78-
}
79-
8042
@Override
8143
public SortField sortField(Object missingValue, MultiValueMode sortMode, XFieldComparatorSource.Nested nested, boolean reverse) {
8244
final XFieldComparatorSource source = new BytesRefFieldComparatorSource(this, missingValue, sortMode, nested);
@@ -97,23 +59,7 @@ public BucketedSort newBucketedSort(
9759
throw new IllegalArgumentException("only supported on numeric fields");
9860
}
9961

100-
public static class ScriptBinaryLeafFieldData implements LeafFieldData {
101-
private final ScriptBinaryDocValues scriptBinaryDocValues;
102-
103-
ScriptBinaryLeafFieldData(ScriptBinaryDocValues scriptBinaryDocValues) {
104-
this.scriptBinaryDocValues = scriptBinaryDocValues;
105-
}
106-
107-
@Override
108-
public ScriptDocValues<?> getScriptValues() {
109-
return new ScriptDocValues.Strings(getBytesValues());
110-
}
111-
112-
@Override
113-
public SortedBinaryDocValues getBytesValues() {
114-
return scriptBinaryDocValues;
115-
}
116-
62+
public abstract class ScriptBinaryLeafFieldData implements LeafFieldData {
11763
@Override
11864
public long ramBytesUsed() {
11965
return 0;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.apache.lucene.util.BytesRef;
10+
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
11+
import org.elasticsearch.xpack.runtimefields.IpScriptFieldScript;
12+
13+
import java.io.IOException;
14+
import java.util.Arrays;
15+
16+
public final class ScriptIpDocValues extends SortedBinaryDocValues {
17+
private final IpScriptFieldScript script;
18+
private int cursor;
19+
20+
ScriptIpDocValues(IpScriptFieldScript script) {
21+
this.script = script;
22+
}
23+
24+
@Override
25+
public boolean advanceExact(int docId) {
26+
script.runForDoc(docId);
27+
if (script.count() == 0) {
28+
return false;
29+
}
30+
Arrays.sort(script.values(), 0, script.count());
31+
cursor = 0;
32+
return true;
33+
}
34+
35+
@Override
36+
public BytesRef nextValue() throws IOException {
37+
return script.values()[cursor++];
38+
}
39+
40+
@Override
41+
public int docValueCount() {
42+
return script.count();
43+
}
44+
}

0 commit comments

Comments
 (0)