diff --git a/server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java index f9b9de97715ed..ea1ffdb7c019f 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java @@ -88,6 +88,42 @@ public enum Mode { true ); + private static final SourceFieldMapper DEFAULT_DISABLED = new SourceFieldMapper( + Mode.DISABLED, + Explicit.IMPLICIT_TRUE, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + null, + true + ); + + private static final SourceFieldMapper DEFAULT_DISABLED_NO_RECOVERY_SOURCE = new SourceFieldMapper( + Mode.DISABLED, + Explicit.IMPLICIT_TRUE, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + null, + false + ); + + private static final SourceFieldMapper DEFAULT_SYNTHETIC = new SourceFieldMapper( + Mode.SYNTHETIC, + Explicit.IMPLICIT_TRUE, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + null, + true + ); + + private static final SourceFieldMapper DEFAULT_SYNTHETIC_NO_RECOVERY_SOURCE = new SourceFieldMapper( + Mode.SYNTHETIC, + Explicit.IMPLICIT_TRUE, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY, + null, + false + ); + private static final SourceFieldMapper DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper( null, Explicit.IMPLICIT_TRUE, @@ -297,7 +333,7 @@ public SourceFieldMapper build() { ? INDEX_MAPPER_SOURCE_MODE_SETTING.get(settings) : mode.get(); if (isDefault(sourceMode)) { - return resolveSourceMode(indexMode, sourceMode, enableRecoverySource); + return resolveSourceMode(indexMode, sourceMode == null ? Mode.STORED : sourceMode, enableRecoverySource); } if (supportsNonDefaultParameterValues == false) { @@ -340,25 +376,39 @@ public SourceFieldMapper build() { } private static SourceFieldMapper resolveSourceMode(final IndexMode indexMode, final Mode sourceMode, boolean enableRecoverySource) { - if (indexMode == IndexMode.STANDARD) { - return enableRecoverySource ? DEFAULT : DEFAULT_NO_RECOVERY_SOURCE; + switch (indexMode) { + case STANDARD: + switch (sourceMode) { + case SYNTHETIC: + return enableRecoverySource ? DEFAULT_SYNTHETIC : DEFAULT_SYNTHETIC_NO_RECOVERY_SOURCE; + case STORED: + return enableRecoverySource ? DEFAULT : DEFAULT_NO_RECOVERY_SOURCE; + case DISABLED: + return enableRecoverySource ? DEFAULT_DISABLED : DEFAULT_DISABLED_NO_RECOVERY_SOURCE; + default: + throw new IllegalArgumentException("Unsupported source mode: " + sourceMode); + } + case TIME_SERIES: + case LOGSDB: + switch (sourceMode) { + case SYNTHETIC: + return enableRecoverySource + ? (indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT : LOGSDB_DEFAULT) + : (indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT_NO_RECOVERY_SOURCE : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE); + case STORED: + return enableRecoverySource + ? (indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT_STORED : LOGSDB_DEFAULT_STORED) + : (indexMode == IndexMode.TIME_SERIES + ? TSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED + : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED); + case DISABLED: + throw new IllegalArgumentException("_source can not be disabled in index using [" + indexMode + "] index mode"); + default: + throw new IllegalArgumentException("Unsupported source mode: " + sourceMode); + } + default: + throw new IllegalArgumentException("Unsupported index mode: " + indexMode); } - final SourceFieldMapper syntheticWithoutRecoverySource = indexMode == IndexMode.TIME_SERIES - ? TSDB_DEFAULT_NO_RECOVERY_SOURCE - : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE; - final SourceFieldMapper syntheticWithRecoverySource = indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT : LOGSDB_DEFAULT; - final SourceFieldMapper storedWithoutRecoverySource = indexMode == IndexMode.TIME_SERIES - ? TSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED - : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED; - final SourceFieldMapper storedWithRecoverySource = indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT_STORED : LOGSDB_DEFAULT_STORED; - - return switch (sourceMode) { - case SYNTHETIC -> enableRecoverySource ? syntheticWithRecoverySource : syntheticWithoutRecoverySource; - case STORED -> enableRecoverySource ? storedWithRecoverySource : storedWithoutRecoverySource; - case DISABLED -> throw new IllegalArgumentException( - "_source can not be disabled in index using [" + indexMode + "] index mode" - ); - }; } public static final TypeParser PARSER = new ConfigurableTypeParser(c -> { @@ -371,7 +421,7 @@ private static SourceFieldMapper resolveSourceMode(final IndexMode indexMode, fi return enableRecoverySource ? TSDB_LEGACY_DEFAULT : TSDB_LEGACY_DEFAULT_NO_RECOVERY_SOURCE; } } - return resolveSourceMode(indexMode, settingSourceMode, enableRecoverySource); + return resolveSourceMode(indexMode, settingSourceMode == null ? Mode.STORED : settingSourceMode, enableRecoverySource); }, c -> new Builder( c.getIndexSettings().getMode(), @@ -541,4 +591,12 @@ public SourceLoader newSourceLoader(Mapping mapping, SourceFieldMetrics metrics) public boolean isSynthetic() { return mode == Mode.SYNTHETIC; } + + public boolean isDisabled() { + return mode == Mode.DISABLED; + } + + public boolean isStored() { + return mode == null || mode == Mode.STORED; + } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/SourceFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/SourceFieldMapperTests.java index 2f417e688cb97..df6d9380fd141 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/SourceFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/SourceFieldMapperTests.java @@ -443,6 +443,167 @@ public void testRecoverySourceWithLogs() throws IOException { } } + public void testStandardIndexModeWithSourceModeSetting() throws IOException { + // Test for IndexMode.STANDARD + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.STANDARD.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.SYNTHETIC) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isSynthetic()); + } + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.STANDARD.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.STORED) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + final DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isStored()); + } + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.STANDARD.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.DISABLED) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + final DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isDisabled()); + } + + // Test for IndexMode.LOGSDB + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.LOGSDB.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.SYNTHETIC) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isSynthetic()); + } + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.LOGSDB.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.STORED) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + final DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isStored()); + } + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.LOGSDB.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.DISABLED) + .build(); + var ex = expectThrows(MapperParsingException.class, () -> createMapperService(settings, mappings)); + assertEquals("Failed to parse mapping: _source can not be disabled in index using [logsdb] index mode", ex.getMessage()); + } + + // Test for IndexMode.TIME_SERIES + { + final String mappings = """ + { + "_doc" : { + "properties": { + "routing_field": { + "type": "keyword", + "time_series_dimension": true + } + } + } + } + """; + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.SYNTHETIC) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isSynthetic()); + } + { + final String mappings = """ + { + "_doc" : { + "properties": { + "routing_field": { + "type": "keyword", + "time_series_dimension": true + } + } + } + } + """; + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.STORED) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + final DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isStored()); + } + { + final String mappings = """ + { + "_doc" : { + "properties": { + "routing_field": { + "type": "keyword", + "time_series_dimension": true + } + } + } + } + """; + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.DISABLED) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") + .build(); + var ex = expectThrows(MapperParsingException.class, () -> createMapperService(settings, mappings)); + assertEquals("Failed to parse mapping: _source can not be disabled in index using [time_series] index mode", ex.getMessage()); + } + + // Test cases without IndexMode (default to standard) + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.SYNTHETIC) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isSynthetic()); + } + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.STORED) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + final DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isStored()); + } + { + final XContentBuilder mappings = topMapping(b -> {}); + final Settings settings = Settings.builder() + .put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.DISABLED) + .build(); + final MapperService mapperService = createMapperService(settings, mappings); + final DocumentMapper docMapper = mapperService.documentMapper(); + assertTrue(docMapper.sourceMapper().isDisabled()); + } + } + public void testRecoverySourceWithLogsCustom() throws IOException { XContentBuilder mappings = topMapping(b -> b.startObject(SourceFieldMapper.NAME).field("mode", "synthetic").endObject()); {