Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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