Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions docs/changelog/135393.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 135393
summary: Improve block loader for source only runtime IP fields
area: Mapping
type: enhancement
issues: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.index.mapper;

import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.net.InetAddress;
import java.util.List;

public class IpFallbackSyntheticSourceReader extends FallbackSyntheticSourceBlockLoader.SingleValueReader<InetAddress> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the code between IpScriptFieldType and IpFieldMapper is identical, so I extracted it into a standalone class.


public IpFallbackSyntheticSourceReader(Object nullValue) {
super(nullValue);
}

@Override
public void convertValue(Object value, List<InetAddress> accumulator) {
try {
if (value instanceof InetAddress ia) {
accumulator.add(ia);
} else {
InetAddress address = InetAddresses.forString(value.toString());
accumulator.add(address);
}
} catch (Exception e) {
// value is malformed, skip it
}
}

@Override
public void writeToBlock(List<InetAddress> values, BlockLoader.Builder blockBuilder) {
BlockLoader.BytesRefBuilder builder = (BlockLoader.BytesRefBuilder) blockBuilder;
for (InetAddress address : values) {
var bytesRef = new BytesRef(InetAddressPoint.encode(address));
builder.appendBytesRef(bytesRef);
}
}

@Override
protected void parseNonNullValue(XContentParser parser, List<InetAddress> accumulator) throws IOException {
try {
InetAddress address = InetAddresses.forString(parser.text());
accumulator.add(address);
} catch (Exception e) {
// value is malformed, skip it
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
import org.elasticsearch.search.lookup.FieldValues;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentString;

import java.io.IOException;
Expand All @@ -52,7 +51,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -480,44 +478,7 @@ public BlockLoader blockLoader(BlockLoaderContext blContext) {
}

private BlockLoader blockLoaderFromFallbackSyntheticSource(BlockLoaderContext blContext) {
var reader = new FallbackSyntheticSourceBlockLoader.SingleValueReader<InetAddress>(nullValue) {
@Override
public void convertValue(Object value, List<InetAddress> accumulator) {
if (value instanceof InetAddress ia) {
accumulator.add(ia);
}

try {
var address = InetAddresses.forString(value.toString());
accumulator.add(address);
} catch (Exception e) {
// Malformed value, skip it
}
}

@Override
protected void parseNonNullValue(XContentParser parser, List<InetAddress> accumulator) throws IOException {
// aligned with #parseCreateField()
String value = parser.text();

try {
var address = InetAddresses.forString(value);
accumulator.add(address);
} catch (Exception e) {
// Malformed value, skip it
}
}

@Override
public void writeToBlock(List<InetAddress> values, BlockLoader.Builder blockBuilder) {
var bytesRefBuilder = (BlockLoader.BytesRefBuilder) blockBuilder;

for (var value : values) {
bytesRefBuilder.appendBytesRef(new BytesRef(InetAddressPoint.encode(value)));
}
}
};

var reader = new IpFallbackSyntheticSourceReader(nullValue);
return new FallbackSyntheticSourceBlockLoader(
reader,
name(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ private Query cidrQuery(String term, SearchExecutionContext context) {

@Override
public BlockLoader blockLoader(BlockLoaderContext blContext) {
FallbackSyntheticSourceBlockLoader fallbackSyntheticSourceBlockLoader = fallbackSyntheticSourceBlockLoader(
blContext,
BlockLoader.BlockFactory::bytesRefs,
() -> new IpFallbackSyntheticSourceReader(null)
);

if (fallbackSyntheticSourceBlockLoader != null) {
return fallbackSyntheticSourceBlockLoader;
}
return new IpScriptBlockDocValuesReader.IpScriptBlockLoader(leafFactory(blContext.lookup()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.fielddata.BinaryScriptFieldData;
import org.elasticsearch.index.fielddata.ScriptDocValues.Strings;
Expand All @@ -43,20 +44,27 @@
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.MultiValueMode;
import org.elasticsearch.search.lookup.SearchLookup;

import java.io.IOException;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;

public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {

private static final BytesRef EMPTY_IP = null;
private static final BytesRef MALFORMED_IP = null;

@Override
protected ScriptFactory parseFromSource() {
return IpFieldScript.PARSE_FROM_SOURCE;
Expand Down Expand Up @@ -280,6 +288,141 @@ public void testBlockLoader() throws IOException {
}
}

public void testBlockLoaderSourceOnlyRuntimeField() throws IOException {
try (
Directory directory = newDirectory();
RandomIndexWriter iw = new RandomIndexWriter(random(), directory, newIndexWriterConfig().setMergePolicy(NoMergePolicy.INSTANCE))
) {
// given
// try multiple variations of boolean as they're all encoded slightly differently
iw.addDocuments(
List.of(
List.of(new StoredField("_source", new BytesRef("{\"test\": [\"192.168.0.1\"]}"))),
List.of(new StoredField("_source", new BytesRef("{\"test\": [\"2001:db8::1\"]}"))),
List.of(new StoredField("_source", new BytesRef("{\"test\": [\"\"]}"))),
// ensure a malformed value doesn't crash
List.of(new StoredField("_source", new BytesRef("{\"test\": [\"potato\"]}")))
)
);
IpScriptFieldType fieldType = simpleSourceOnlyMappedFieldType();
List<BytesRef> expected = Arrays.asList(
new BytesRef(InetAddressPoint.encode(InetAddresses.forString("192.168.0.1"))),
new BytesRef(InetAddressPoint.encode(InetAddresses.forString("2001:db8::1"))),
EMPTY_IP,
MALFORMED_IP
);

try (DirectoryReader reader = iw.getReader()) {
// when
BlockLoader loader = fieldType.blockLoader(blContext(Settings.EMPTY, true));

// then

// assert loader is of expected instance type
assertThat(loader, instanceOf(IpScriptBlockDocValuesReader.IpScriptBlockLoader.class));

// ignored source doesn't support column at a time loading:
var columnAtATimeLoader = loader.columnAtATimeReader(reader.leaves().getFirst());
assertThat(columnAtATimeLoader, instanceOf(IpScriptBlockDocValuesReader.class));

var rowStrideReader = loader.rowStrideReader(reader.leaves().getFirst());
assertThat(rowStrideReader, instanceOf(IpScriptBlockDocValuesReader.class));

// assert values
assertThat(blockLoaderReadValuesFromColumnAtATimeReader(reader, fieldType, 0), equalTo(expected));
assertThat(blockLoaderReadValuesFromRowStrideReader(reader, fieldType), equalTo(expected));
}
}
}

public void testBlockLoaderSourceOnlyRuntimeFieldWithSyntheticSource() throws IOException {
try (
Directory directory = newDirectory();
RandomIndexWriter iw = new RandomIndexWriter(random(), directory, newIndexWriterConfig().setMergePolicy(NoMergePolicy.INSTANCE))
) {
// given
// try multiple variations of boolean as they're all encoded slightly differently
iw.addDocuments(
List.of(
createDocumentWithIgnoredSource("[\"192.168.0.1\"]"),
createDocumentWithIgnoredSource("[\"2001:db8::1\"]"),
createDocumentWithIgnoredSource("[\"\"]"),
// ensure a malformed value doesn't crash
createDocumentWithIgnoredSource("[\"potato\"]")
)
);

Settings settings = Settings.builder().put("index.mapping.source.mode", "synthetic").build();
IpScriptFieldType fieldType = simpleSourceOnlyMappedFieldType();
List<BytesRef> expected = Arrays.asList(
new BytesRef(InetAddressPoint.encode(InetAddresses.forString("192.168.0.1"))),
new BytesRef(InetAddressPoint.encode(InetAddresses.forString("2001:db8::1"))),
EMPTY_IP,
MALFORMED_IP
);

try (DirectoryReader reader = iw.getReader()) {
// when
BlockLoader loader = fieldType.blockLoader(blContext(settings, true));

// then

// assert loader is of expected instance type
assertThat(loader, instanceOf(FallbackSyntheticSourceBlockLoader.class));

// ignored source doesn't support column at a time loading:
var columnAtATimeLoader = loader.columnAtATimeReader(reader.leaves().getFirst());
assertThat(columnAtATimeLoader, nullValue());

var rowStrideReader = loader.rowStrideReader(reader.leaves().getFirst());
assertThat(
rowStrideReader.getClass().getName(),
equalTo("org.elasticsearch.index.mapper.FallbackSyntheticSourceBlockLoader$IgnoredSourceRowStrideReader")
);

// assert values
assertThat(blockLoaderReadValuesFromRowStrideReader(settings, reader, fieldType, true), equalTo(expected));
}
}
}

/**
* Returns a source only mapped field type. This is useful, since the available build() function doesn't override isParsedFromSource()
*/
private IpScriptFieldType simpleSourceOnlyMappedFieldType() {
Script script = new Script(ScriptType.INLINE, "test", "", emptyMap());
IpFieldScript.Factory factory = new IpFieldScript.Factory() {
@Override
public IpFieldScript.LeafFactory newFactory(
String fieldName,
Map<String, Object> params,
SearchLookup searchLookup,
OnScriptError onScriptError
) {
return ctx -> new IpFieldScript(fieldName, params, searchLookup, onScriptError, ctx) {
@Override
@SuppressWarnings("unchecked")
public void execute() {
Map<String, Object> source = (Map<String, Object>) this.getParams().get("_source");
for (Object foo : (List<?>) source.get("test")) {
try {
emit(foo.toString());
} catch (Exception e) {
// skip
}
}
}
};
}

@Override
public boolean isParsedFromSource() {
return true;
}
};
return new IpScriptFieldType("test", factory, script, emptyMap(), OnScriptError.FAIL);
}

@Override
protected Query randomTermsQuery(MappedFieldType ft, SearchExecutionContext ctx) {
return ft.termsQuery(randomList(100, () -> randomIp(randomBoolean())), ctx);
Expand Down