Skip to content

Commit 96402aa

Browse files
authored
Improve block loader for source only runtime fields of type double. (#134629)
By using the FallbackSyntheticSourceBlockLoader instead of generic DoubleScriptBlockLoader. Relates to #134121
1 parent 561ee67 commit 96402aa

File tree

6 files changed

+172
-28
lines changed

6 files changed

+172
-28
lines changed

docs/changelog/134629.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 134629
2+
summary: Improve block loader for source only runtime fields of type double
3+
area: Mapping
4+
type: enhancement
5+
issues: []

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import java.util.Locale;
3838
import java.util.Map;
3939
import java.util.Objects;
40+
import java.util.function.BiConsumer;
41+
import java.util.function.BiFunction;
4042
import java.util.function.Function;
4143

4244
import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;
@@ -203,6 +205,45 @@ protected final LeafFactory leafFactory(SearchLookup searchLookup) {
203205
}
204206
}
205207

208+
/**
209+
* Returns synthetic source fallback block loader if source mode is synthetic, runtime field is source only and field is only mapped
210+
* as a runtime field.
211+
*/
212+
protected final FallbackSyntheticSourceBlockLoader fallbackSyntheticSourceBlockLoader(
213+
BlockLoaderContext blContext,
214+
NumberFieldMapper.NumberType numberType,
215+
BiFunction<BlockLoader.BlockFactory, Integer, BlockLoader.Builder> builderSupplier,
216+
BiConsumer<List<Number>, BlockLoader.Builder> writeToBlock
217+
) {
218+
var indexSettings = blContext.indexSettings();
219+
// A runtime and normal field can share the same name.
220+
// In that case there is no ignored source entry, and so we need to fail back to LongScriptBlockLoader.
221+
// We could optimize this, but at this stage feels like a rare scenario.
222+
if (isParsedFromSource
223+
&& indexSettings.getIndexMappingSourceMode() == SourceFieldMapper.Mode.SYNTHETIC
224+
&& blContext.lookup().onlyMappedAsRuntimeField(name())) {
225+
var reader = new NumberFieldMapper.NumberType.NumberFallbackSyntheticSourceReader(numberType, null, true) {
226+
@Override
227+
public void writeToBlock(List<Number> values, BlockLoader.Builder blockBuilder) {
228+
writeToBlock.accept(values, blockBuilder);
229+
}
230+
};
231+
232+
return new FallbackSyntheticSourceBlockLoader(
233+
reader,
234+
name(),
235+
IgnoredSourceFieldMapper.ignoredSourceFormat(indexSettings.getIndexVersionCreated())
236+
) {
237+
@Override
238+
public Builder builder(BlockFactory factory, int expectedCount) {
239+
return builderSupplier.apply(factory, expectedCount);
240+
}
241+
};
242+
} else {
243+
return null;
244+
}
245+
}
246+
206247
/**
207248
* Create a script leaf factory for queries.
208249
*/

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,22 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) {
109109

110110
@Override
111111
public BlockLoader blockLoader(BlockLoaderContext blContext) {
112-
return new DoubleScriptBlockDocValuesReader.DoubleScriptBlockLoader(leafFactory(blContext.lookup()));
112+
var fallbackSyntheticSourceBlockLoader = fallbackSyntheticSourceBlockLoader(
113+
blContext,
114+
NumberType.DOUBLE,
115+
BlockLoader.BlockFactory::doubles,
116+
(values, blockBuilder) -> {
117+
var builder = (BlockLoader.DoubleBuilder) blockBuilder;
118+
for (var value : values) {
119+
builder.appendDouble(value.doubleValue());
120+
}
121+
}
122+
);
123+
if (fallbackSyntheticSourceBlockLoader != null) {
124+
return fallbackSyntheticSourceBlockLoader;
125+
} else {
126+
return new DoubleScriptBlockDocValuesReader.DoubleScriptBlockLoader(leafFactory(blContext.lookup()));
127+
}
113128
}
114129

115130
@Override

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

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
import java.time.ZoneId;
3232
import java.util.Collection;
33-
import java.util.List;
3433
import java.util.Map;
3534
import java.util.Set;
3635
import java.util.function.Function;
@@ -110,32 +109,19 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) {
110109

111110
@Override
112111
public BlockLoader blockLoader(BlockLoaderContext blContext) {
113-
var indexSettings = blContext.indexSettings();
114-
if (isParsedFromSource && indexSettings.getIndexMappingSourceMode() == SourceFieldMapper.Mode.SYNTHETIC
115-
// A runtime and normal field can share the same name.
116-
// In that case there is no ignored source entry, and so we need to fail back to LongScriptBlockLoader.
117-
// We could optimize this, but at this stage feels like a rare scenario.
118-
&& blContext.lookup().onlyMappedAsRuntimeField(name())) {
119-
var reader = new NumberType.NumberFallbackSyntheticSourceReader(NumberType.LONG, null, true) {
120-
@Override
121-
public void writeToBlock(List<Number> values, BlockLoader.Builder blockBuilder) {
122-
var builder = (BlockLoader.LongBuilder) blockBuilder;
123-
for (var value : values) {
124-
builder.appendLong(value.longValue());
125-
}
112+
var fallbackSyntheticSourceBlockLoader = fallbackSyntheticSourceBlockLoader(
113+
blContext,
114+
NumberType.LONG,
115+
BlockLoader.BlockFactory::longs,
116+
(values, blockBuilder) -> {
117+
var builder = (BlockLoader.LongBuilder) blockBuilder;
118+
for (var value : values) {
119+
builder.appendLong(value.longValue());
126120
}
127-
};
128-
129-
return new FallbackSyntheticSourceBlockLoader(
130-
reader,
131-
name(),
132-
IgnoredSourceFieldMapper.ignoredSourceFormat(indexSettings.getIndexVersionCreated())
133-
) {
134-
@Override
135-
public Builder builder(BlockFactory factory, int expectedCount) {
136-
return factory.longs(expectedCount);
137-
}
138-
};
121+
}
122+
);
123+
if (fallbackSyntheticSourceBlockLoader != null) {
124+
return fallbackSyntheticSourceBlockLoader;
139125
} else {
140126
return new LongScriptBlockDocValuesReader.LongScriptBlockLoader(leafFactory(blContext.lookup()));
141127
}

server/src/test/java/org/elasticsearch/index/mapper/DoubleScriptFieldTypeTests.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.lucene.tests.index.RandomIndexWriter;
2929
import org.apache.lucene.util.BytesRef;
3030
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
31+
import org.elasticsearch.common.settings.Settings;
3132
import org.elasticsearch.index.IndexVersion;
3233
import org.elasticsearch.index.fielddata.DoubleScriptFieldData;
3334
import org.elasticsearch.index.fielddata.ScriptDocValues;
@@ -40,15 +41,19 @@
4041
import org.elasticsearch.script.ScriptFactory;
4142
import org.elasticsearch.script.ScriptType;
4243
import org.elasticsearch.search.MultiValueMode;
44+
import org.elasticsearch.search.lookup.SearchLookup;
4345

4446
import java.io.IOException;
4547
import java.util.ArrayList;
4648
import java.util.List;
4749
import java.util.Map;
4850

4951
import static java.util.Collections.emptyMap;
52+
import static org.elasticsearch.index.mapper.LongScriptFieldTypeTests.createDocumentWithIgnoredSource;
5053
import static org.hamcrest.Matchers.containsInAnyOrder;
5154
import static org.hamcrest.Matchers.equalTo;
55+
import static org.hamcrest.Matchers.instanceOf;
56+
import static org.hamcrest.Matchers.nullValue;
5257

5358
public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTestCase {
5459

@@ -269,6 +274,69 @@ public void testBlockLoader() throws IOException {
269274
}
270275
}
271276

277+
public void testBlockLoaderSourceOnlyRuntimeField() throws IOException {
278+
try (
279+
Directory directory = newDirectory();
280+
RandomIndexWriter iw = new RandomIndexWriter(random(), directory, newIndexWriterConfig().setMergePolicy(NoMergePolicy.INSTANCE))
281+
) {
282+
iw.addDocuments(
283+
List.of(
284+
List.of(new StoredField("_source", new BytesRef("{\"test\": [1.1]}"))),
285+
List.of(new StoredField("_source", new BytesRef("{\"test\": [2.1]}")))
286+
)
287+
);
288+
try (DirectoryReader reader = iw.getReader()) {
289+
DoubleScriptFieldType fieldType = simpleSourceOnlyMappedFieldType();
290+
291+
// Assert implementations:
292+
BlockLoader loader = fieldType.blockLoader(blContext(Settings.EMPTY, true));
293+
assertThat(loader, instanceOf(DoubleScriptBlockDocValuesReader.DoubleScriptBlockLoader.class));
294+
// ignored source doesn't support column at a time loading:
295+
var columnAtATimeLoader = loader.columnAtATimeReader(reader.leaves().getFirst());
296+
assertThat(columnAtATimeLoader, instanceOf(DoubleScriptBlockDocValuesReader.class));
297+
var rowStrideReader = loader.rowStrideReader(reader.leaves().getFirst());
298+
assertThat(rowStrideReader, instanceOf(DoubleScriptBlockDocValuesReader.class));
299+
300+
// Assert values:
301+
assertThat(blockLoaderReadValuesFromColumnAtATimeReader(reader, fieldType, 0), equalTo(List.of(1.1, 2.1)));
302+
assertThat(blockLoaderReadValuesFromColumnAtATimeReader(reader, fieldType, 1), equalTo(List.of(2.1)));
303+
assertThat(blockLoaderReadValuesFromRowStrideReader(reader, fieldType), equalTo(List.of(1.1, 2.1)));
304+
}
305+
}
306+
}
307+
308+
public void testBlockLoaderSourceOnlyRuntimeFieldWithSyntheticSource() throws IOException {
309+
var settings = Settings.builder().put("index.mapping.source.mode", "synthetic").build();
310+
try (
311+
Directory directory = newDirectory();
312+
RandomIndexWriter iw = new RandomIndexWriter(random(), directory, newIndexWriterConfig().setMergePolicy(NoMergePolicy.INSTANCE))
313+
) {
314+
315+
var document1 = createDocumentWithIgnoredSource("[1.1]");
316+
var document2 = createDocumentWithIgnoredSource("[2.1]");
317+
318+
iw.addDocuments(List.of(document1, document2));
319+
try (DirectoryReader reader = iw.getReader()) {
320+
DoubleScriptFieldType fieldType = simpleSourceOnlyMappedFieldType();
321+
322+
// Assert implementations:
323+
BlockLoader loader = fieldType.blockLoader(blContext(settings, true));
324+
assertThat(loader, instanceOf(FallbackSyntheticSourceBlockLoader.class));
325+
// ignored source doesn't support column at a time loading:
326+
var columnAtATimeLoader = loader.columnAtATimeReader(reader.leaves().getFirst());
327+
assertThat(columnAtATimeLoader, nullValue());
328+
var rowStrideReader = loader.rowStrideReader(reader.leaves().getFirst());
329+
assertThat(
330+
rowStrideReader.getClass().getName(),
331+
equalTo("org.elasticsearch.index.mapper.FallbackSyntheticSourceBlockLoader$IgnoredSourceRowStrideReader")
332+
);
333+
334+
// Assert values:
335+
assertThat(blockLoaderReadValuesFromRowStrideReader(settings, reader, fieldType, true), equalTo(List.of(1.1, 2.1)));
336+
}
337+
}
338+
}
339+
272340
@Override
273341
protected Query randomTermsQuery(MappedFieldType ft, SearchExecutionContext ctx) {
274342
return ft.termsQuery(List.of(randomLong()), ctx);
@@ -279,6 +347,10 @@ protected DoubleScriptFieldType simpleMappedFieldType() {
279347
return build("read_foo", Map.of(), OnScriptError.FAIL);
280348
}
281349

350+
private DoubleScriptFieldType simpleSourceOnlyMappedFieldType() {
351+
return build("read_test", Map.of(), OnScriptError.FAIL);
352+
}
353+
282354
@Override
283355
protected MappedFieldType loopFieldType() {
284356
return build("loop", Map.of(), OnScriptError.FAIL);
@@ -296,6 +368,31 @@ protected DoubleScriptFieldType build(String code, Map<String, Object> params, O
296368

297369
private static DoubleFieldScript.Factory factory(Script script) {
298370
return switch (script.getIdOrCode()) {
371+
case "read_test" -> new DoubleFieldScript.Factory() {
372+
@Override
373+
public DoubleFieldScript.LeafFactory newFactory(
374+
String fieldName,
375+
Map<String, Object> params,
376+
SearchLookup lookup,
377+
OnScriptError onScriptError
378+
) {
379+
return (ctx) -> new DoubleFieldScript(fieldName, params, lookup, onScriptError, ctx) {
380+
@Override
381+
@SuppressWarnings("unchecked")
382+
public void execute() {
383+
Map<String, Object> source = (Map<String, Object>) this.getParams().get("_source");
384+
for (Object foo : (List<?>) source.get("test")) {
385+
emit(((Number) foo).doubleValue());
386+
}
387+
};
388+
};
389+
}
390+
391+
@Override
392+
public boolean isParsedFromSource() {
393+
return true;
394+
}
395+
};
299396
case "read_foo" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new DoubleFieldScript(
300397
fieldName,
301398
params,

server/src/test/java/org/elasticsearch/index/mapper/LongScriptFieldTypeTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ public void testBlockLoaderSourceOnlyRuntimeFieldWithSyntheticSource() throws IO
374374
}
375375
}
376376

377-
private static LuceneDocument createDocumentWithIgnoredSource(String bytes) throws IOException {
377+
static LuceneDocument createDocumentWithIgnoredSource(String bytes) throws IOException {
378378
var doc = new LuceneDocument();
379379
var parser = XContentHelper.createParser(
380380
XContentParserConfiguration.EMPTY,

0 commit comments

Comments
 (0)