|
@@ -13,82 +13,159 @@ import org.apache.lucene.index.DirectoryReader;
|
|
|
import org.apache.lucene.index.LeafReaderContext;
|
|
|
import org.apache.lucene.store.Directory;
|
|
|
import org.apache.lucene.tests.index.RandomIndexWriter;
|
|
|
+import org.elasticsearch.common.bytes.BytesReference;
|
|
|
import org.elasticsearch.index.IndexSettings;
|
|
|
import org.elasticsearch.index.fieldvisitor.StoredFieldLoader;
|
|
|
import org.elasticsearch.logsdb.datageneration.DataGeneratorSpecification;
|
|
|
-import org.elasticsearch.logsdb.datageneration.FieldDataGenerator;
|
|
|
+import org.elasticsearch.logsdb.datageneration.DocumentGenerator;
|
|
|
import org.elasticsearch.logsdb.datageneration.FieldType;
|
|
|
import org.elasticsearch.logsdb.datageneration.MappingGenerator;
|
|
|
import org.elasticsearch.logsdb.datageneration.Template;
|
|
|
import org.elasticsearch.logsdb.datageneration.datasource.DataSourceHandler;
|
|
|
import org.elasticsearch.logsdb.datageneration.datasource.DataSourceRequest;
|
|
|
import org.elasticsearch.logsdb.datageneration.datasource.DataSourceResponse;
|
|
|
+import org.elasticsearch.plugins.internal.XContentMeteringParserDecorator;
|
|
|
import org.elasticsearch.search.fetch.StoredFieldsSpec;
|
|
|
import org.elasticsearch.search.lookup.SearchLookup;
|
|
|
import org.elasticsearch.xcontent.XContentBuilder;
|
|
|
import org.elasticsearch.xcontent.XContentType;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Set;
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
|
public abstract class BlockLoaderTestCase extends MapperServiceTestCase {
|
|
|
+ private final FieldType fieldType;
|
|
|
private final String fieldName;
|
|
|
- private final Template template;
|
|
|
private final MappingGenerator mappingGenerator;
|
|
|
- private final FieldDataGenerator generator;
|
|
|
+ private final DocumentGenerator documentGenerator;
|
|
|
|
|
|
protected BlockLoaderTestCase(FieldType fieldType) {
|
|
|
+ this.fieldType = fieldType;
|
|
|
this.fieldName = randomAlphaOfLengthBetween(5, 10);
|
|
|
|
|
|
- // Disable all dynamic mapping
|
|
|
var specification = DataGeneratorSpecification.builder()
|
|
|
.withFullyDynamicMapping(false)
|
|
|
+ // Disable dynamic mapping and disabled objects
|
|
|
.withDataSourceHandlers(List.of(new DataSourceHandler() {
|
|
|
@Override
|
|
|
public DataSourceResponse.DynamicMappingGenerator handle(DataSourceRequest.DynamicMappingGenerator request) {
|
|
|
return new DataSourceResponse.DynamicMappingGenerator(isObject -> false);
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public DataSourceResponse.ObjectMappingParametersGenerator handle(
|
|
|
+ DataSourceRequest.ObjectMappingParametersGenerator request
|
|
|
+ ) {
|
|
|
+ return new DataSourceResponse.ObjectMappingParametersGenerator(HashMap::new); // just defaults
|
|
|
+ }
|
|
|
}))
|
|
|
.build();
|
|
|
|
|
|
- this.template = new Template(Map.of(fieldName, new Template.Leaf(fieldName, fieldType)));
|
|
|
this.mappingGenerator = new MappingGenerator(specification);
|
|
|
- this.generator = fieldType.generator(fieldName, specification.dataSource());
|
|
|
+ this.documentGenerator = new DocumentGenerator(specification);
|
|
|
}
|
|
|
|
|
|
public void testBlockLoader() throws IOException {
|
|
|
+ var template = new Template(Map.of(fieldName, new Template.Leaf(fieldName, fieldType)));
|
|
|
+ runTest(template, fieldName);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testBlockLoaderForFieldInObject() throws IOException {
|
|
|
+ int depth = randomIntBetween(0, 3);
|
|
|
+
|
|
|
+ Map<String, Template.Entry> currentLevel = new HashMap<>();
|
|
|
+ Map<String, Template.Entry> top = Map.of("top", new Template.Object("top", false, currentLevel));
|
|
|
+
|
|
|
+ var fullFieldName = new StringBuilder("top");
|
|
|
+ int currentDepth = 0;
|
|
|
+ while (currentDepth++ < depth) {
|
|
|
+ fullFieldName.append('.').append("level").append(currentDepth);
|
|
|
+
|
|
|
+ Map<String, Template.Entry> nextLevel = new HashMap<>();
|
|
|
+ currentLevel.put("level" + currentDepth, new Template.Object("level" + currentDepth, false, nextLevel));
|
|
|
+ currentLevel = nextLevel;
|
|
|
+ }
|
|
|
+
|
|
|
+ fullFieldName.append('.').append(fieldName);
|
|
|
+ currentLevel.put(fieldName, new Template.Leaf(fieldName, fieldType));
|
|
|
+ var template = new Template(top);
|
|
|
+ runTest(template, fullFieldName.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void runTest(Template template, String fieldName) throws IOException {
|
|
|
var mapping = mappingGenerator.generate(template);
|
|
|
var mappingXContent = XContentBuilder.builder(XContentType.JSON.xContent()).map(mapping.raw());
|
|
|
|
|
|
var syntheticSource = randomBoolean();
|
|
|
var mapperService = syntheticSource ? createSytheticSourceMapperService(mappingXContent) : createMapperService(mappingXContent);
|
|
|
|
|
|
- var fieldValue = generator.generateValue();
|
|
|
+ var document = documentGenerator.generate(template, mapping);
|
|
|
+ var documentXContent = XContentBuilder.builder(XContentType.JSON.xContent()).map(document);
|
|
|
|
|
|
- Object blockLoaderResult = setupAndInvokeBlockLoader(mapperService, fieldValue);
|
|
|
- Object expected = expected(mapping.lookup().get(fieldName), fieldValue, syntheticSource);
|
|
|
+ Object blockLoaderResult = setupAndInvokeBlockLoader(mapperService, documentXContent, fieldName);
|
|
|
+ Object expected = expected(mapping.lookup().get(fieldName), getFieldValue(document, fieldName), syntheticSource);
|
|
|
assertEquals(expected, blockLoaderResult);
|
|
|
}
|
|
|
|
|
|
protected abstract Object expected(Map<String, Object> fieldMapping, Object value, boolean syntheticSource);
|
|
|
|
|
|
- private Object setupAndInvokeBlockLoader(MapperService mapperService, Object fieldValue) throws IOException {
|
|
|
+ private Object getFieldValue(Map<String, Object> document, String fieldName) {
|
|
|
+ var rawValues = new ArrayList<>();
|
|
|
+ processLevel(document, fieldName, rawValues);
|
|
|
+
|
|
|
+ if (rawValues.size() == 1) {
|
|
|
+ return rawValues.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ return rawValues.stream().flatMap(v -> v instanceof List<?> l ? l.stream() : Stream.of(v)).toList();
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void processLevel(Map<String, Object> level, String field, ArrayList<Object> values) {
|
|
|
+ if (field.contains(".") == false) {
|
|
|
+ var value = level.get(field);
|
|
|
+ values.add(value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var nameInLevel = field.split("\\.")[0];
|
|
|
+ var entry = level.get(nameInLevel);
|
|
|
+ if (entry instanceof Map<?, ?> m) {
|
|
|
+ processLevel((Map<String, Object>) m, field.substring(field.indexOf('.') + 1), values);
|
|
|
+ }
|
|
|
+ if (entry instanceof List<?> l) {
|
|
|
+ for (var object : l) {
|
|
|
+ processLevel((Map<String, Object>) object, field.substring(field.indexOf('.') + 1), values);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object setupAndInvokeBlockLoader(MapperService mapperService, XContentBuilder document, String fieldName) throws IOException {
|
|
|
try (Directory directory = newDirectory()) {
|
|
|
RandomIndexWriter iw = new RandomIndexWriter(random(), directory);
|
|
|
|
|
|
- LuceneDocument doc = mapperService.documentMapper().parse(source(b -> {
|
|
|
- b.field(fieldName);
|
|
|
- b.value(fieldValue);
|
|
|
- })).rootDoc();
|
|
|
+ var source = new SourceToParse(
|
|
|
+ "1",
|
|
|
+ BytesReference.bytes(document),
|
|
|
+ XContentType.JSON,
|
|
|
+ null,
|
|
|
+ Map.of(),
|
|
|
+ true,
|
|
|
+ XContentMeteringParserDecorator.NOOP
|
|
|
+ );
|
|
|
+ LuceneDocument doc = mapperService.documentMapper().parse(source).rootDoc();
|
|
|
|
|
|
iw.addDocument(doc);
|
|
|
iw.close();
|
|
|
|
|
|
try (DirectoryReader reader = DirectoryReader.open(directory)) {
|
|
|
LeafReaderContext context = reader.leaves().get(0);
|
|
|
- return load(createBlockLoader(mapperService), context, mapperService);
|
|
|
+ return load(createBlockLoader(mapperService, fieldName), context, mapperService);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -98,6 +175,9 @@ public abstract class BlockLoaderTestCase extends MapperServiceTestCase {
|
|
|
var columnAtATimeReader = blockLoader.columnAtATimeReader(context);
|
|
|
if (columnAtATimeReader != null) {
|
|
|
var block = (TestBlock) columnAtATimeReader.read(TestBlock.factory(context.reader().numDocs()), TestBlock.docs(0));
|
|
|
+ if (block.size() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
return block.get(0);
|
|
|
}
|
|
|
|
|
@@ -119,10 +199,13 @@ public abstract class BlockLoaderTestCase extends MapperServiceTestCase {
|
|
|
BlockLoader.Builder builder = blockLoader.builder(TestBlock.factory(context.reader().numDocs()), 1);
|
|
|
blockLoader.rowStrideReader(context).read(0, storedFieldsLoader, builder);
|
|
|
var block = (TestBlock) builder.build();
|
|
|
+ if (block.size() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
return block.get(0);
|
|
|
}
|
|
|
|
|
|
- private BlockLoader createBlockLoader(MapperService mapperService) {
|
|
|
+ private BlockLoader createBlockLoader(MapperService mapperService, String fieldName) {
|
|
|
SearchLookup searchLookup = new SearchLookup(mapperService.mappingLookup().fieldTypesLookup()::get, null, null);
|
|
|
|
|
|
return mapperService.fieldType(fieldName).blockLoader(new MappedFieldType.BlockLoaderContext() {
|