| 
 | 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 | + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side  | 
 | 5 | + * Public License v 1"; you may not use this file except in compliance with, at  | 
 | 6 | + * your election, the "Elastic License 2.0", the "GNU Affero General Public  | 
 | 7 | + * License v3.0 only", or the "Server Side Public License, v 1".  | 
 | 8 | + */  | 
 | 9 | + | 
 | 10 | +package org.elasticsearch.index.mapper;  | 
 | 11 | + | 
 | 12 | +import org.elasticsearch.core.Nullable;  | 
 | 13 | +import org.elasticsearch.index.IndexSettings;  | 
 | 14 | + | 
 | 15 | +import java.util.HashMap;  | 
 | 16 | +import java.util.Map;  | 
 | 17 | + | 
 | 18 | +/**  | 
 | 19 | + * Contains lookup information needed to perform custom synthetic source logic.  | 
 | 20 | + * For example fields that use fallback synthetic source implementation or fields that preserve array ordering  | 
 | 21 | + * in synthetic source;  | 
 | 22 | + */  | 
 | 23 | +public class CustomSyntheticSourceFieldLookup {  | 
 | 24 | +    private final Map<String, Reason> fieldsWithCustomSyntheticSourceHandling;  | 
 | 25 | + | 
 | 26 | +    public CustomSyntheticSourceFieldLookup(Mapping mapping, @Nullable IndexSettings indexSettings, boolean isSourceSynthetic) {  | 
 | 27 | +        var fields = new HashMap<String, Reason>();  | 
 | 28 | +        if (isSourceSynthetic && indexSettings != null) {  | 
 | 29 | +            populateFields(fields, mapping.getRoot(), indexSettings.sourceKeepMode());  | 
 | 30 | +        }  | 
 | 31 | +        this.fieldsWithCustomSyntheticSourceHandling = Map.copyOf(fields);  | 
 | 32 | +    }  | 
 | 33 | + | 
 | 34 | +    private void populateFields(Map<String, Reason> fields, ObjectMapper currentLevel, Mapper.SourceKeepMode defaultSourceKeepMode) {  | 
 | 35 | +        if (currentLevel.isEnabled() == false) {  | 
 | 36 | +            fields.put(currentLevel.fullPath(), Reason.DISABLED_OBJECT);  | 
 | 37 | +            return;  | 
 | 38 | +        }  | 
 | 39 | +        if (sourceKeepMode(currentLevel, defaultSourceKeepMode) == Mapper.SourceKeepMode.ALL) {  | 
 | 40 | +            fields.put(currentLevel.fullPath(), Reason.SOURCE_KEEP_ALL);  | 
 | 41 | +            return;  | 
 | 42 | +        }  | 
 | 43 | +        if (currentLevel.isNested() == false && sourceKeepMode(currentLevel, defaultSourceKeepMode) == Mapper.SourceKeepMode.ARRAYS) {  | 
 | 44 | +            fields.put(currentLevel.fullPath(), Reason.SOURCE_KEEP_ARRAYS);  | 
 | 45 | +        }  | 
 | 46 | + | 
 | 47 | +        for (Mapper child : currentLevel) {  | 
 | 48 | +            if (child instanceof ObjectMapper objectMapper) {  | 
 | 49 | +                populateFields(fields, objectMapper, defaultSourceKeepMode);  | 
 | 50 | +            } else if (child instanceof FieldMapper fieldMapper) {  | 
 | 51 | +                // The order here is important.  | 
 | 52 | +                // If fallback logic is used, it should be always correctly marked as FALLBACK_SYNTHETIC_SOURCE.  | 
 | 53 | +                // This allows us to apply an optimization for SOURCE_KEEP_ARRAYS and don't store arrays that have one element.  | 
 | 54 | +                // If this order is changed and a field that both has SOURCE_KEEP_ARRAYS and FALLBACK_SYNTHETIC_SOURCE  | 
 | 55 | +                // is marked as SOURCE_KEEP_ARRAYS we would lose data for this field by applying such an optimization.  | 
 | 56 | +                if (fieldMapper.syntheticSourceMode() == FieldMapper.SyntheticSourceMode.FALLBACK) {  | 
 | 57 | +                    fields.put(fieldMapper.fullPath(), Reason.FALLBACK_SYNTHETIC_SOURCE);  | 
 | 58 | +                } else if (sourceKeepMode(fieldMapper, defaultSourceKeepMode) == Mapper.SourceKeepMode.ALL) {  | 
 | 59 | +                    fields.put(fieldMapper.fullPath(), Reason.SOURCE_KEEP_ALL);  | 
 | 60 | +                } else if (sourceKeepMode(fieldMapper, defaultSourceKeepMode) == Mapper.SourceKeepMode.ARRAYS) {  | 
 | 61 | +                    fields.put(fieldMapper.fullPath(), Reason.SOURCE_KEEP_ARRAYS);  | 
 | 62 | +                }  | 
 | 63 | +            }  | 
 | 64 | +        }  | 
 | 65 | +    }  | 
 | 66 | + | 
 | 67 | +    private Mapper.SourceKeepMode sourceKeepMode(ObjectMapper mapper, Mapper.SourceKeepMode defaultSourceKeepMode) {  | 
 | 68 | +        return mapper.sourceKeepMode().orElse(defaultSourceKeepMode);  | 
 | 69 | +    }  | 
 | 70 | + | 
 | 71 | +    private Mapper.SourceKeepMode sourceKeepMode(FieldMapper mapper, Mapper.SourceKeepMode defaultSourceKeepMode) {  | 
 | 72 | +        return mapper.sourceKeepMode().orElse(defaultSourceKeepMode);  | 
 | 73 | +    }  | 
 | 74 | + | 
 | 75 | +    public Map<String, Reason> getFieldsWithCustomSyntheticSourceHandling() {  | 
 | 76 | +        return fieldsWithCustomSyntheticSourceHandling;  | 
 | 77 | +    }  | 
 | 78 | + | 
 | 79 | +    /**  | 
 | 80 | +     * Specifies why this field needs custom handling.  | 
 | 81 | +     */  | 
 | 82 | +    public enum Reason {  | 
 | 83 | +        SOURCE_KEEP_ARRAYS,  | 
 | 84 | +        SOURCE_KEEP_ALL,  | 
 | 85 | +        FALLBACK_SYNTHETIC_SOURCE,  | 
 | 86 | +        DISABLED_OBJECT  | 
 | 87 | +    }  | 
 | 88 | +}  | 
0 commit comments