4545import org .elasticsearch .cluster .service .MasterServiceTaskQueue ;
4646import org .elasticsearch .common .Priority ;
4747import org .elasticsearch .common .Strings ;
48+ import org .elasticsearch .common .TriConsumer ;
4849import org .elasticsearch .common .compress .CompressedXContent ;
4950import org .elasticsearch .common .settings .IndexScopedSettings ;
5051import org .elasticsearch .common .settings .Setting ;
8687import java .time .OffsetDateTime ;
8788import java .time .format .DateTimeFormatter ;
8889import java .util .ArrayList ;
90+ import java .util .HashMap ;
8991import java .util .List ;
9092import java .util .Map ;
9193import java .util .Objects ;
106108public class TransportDownsampleAction extends AcknowledgedTransportMasterNodeAction <DownsampleAction .Request > {
107109
108110 private static final Logger logger = LogManager .getLogger (TransportDownsampleAction .class );
109-
111+ private static final String MAPPING_PROPERTIES = "properties" ;
112+ private static final String MAPPING_DYNAMIC_TEMPLATES = "dynamic_templates" ;
113+ private static final String FIELD_TYPE = "type" ;
114+ private static final String MULTI_FIELDS = "fields" ;
110115 private final Client client ;
111116 private final IndicesService indicesService ;
112117 private final MasterServiceTaskQueue <DownsampleClusterStateUpdateTask > taskQueue ;
@@ -677,36 +682,105 @@ static String createDownsampleIndexMapping(
677682 final DownsampleConfig config ,
678683 final Map <String , Object > sourceIndexMappings
679684 ) throws IOException {
680- // TODO deep copy the map
681685 final String timestampField = config .getTimestampField ();
682686 final String dateIntervalType = config .getIntervalType ();
683687 final String dateInterval = config .getInterval ().toString ();
684688 final String timezone = config .getTimeZone ();
685- addDynamicTemplates (sourceIndexMappings );
686- MappingVisitor .visitMapping (sourceIndexMappings , (field , mapping ) -> {
687- @ SuppressWarnings ("unchecked" )
688- Map <String , Object > updatedMapping = (Map <String , Object >) mapping ;
689- if (timestampField .equals (field )) {
690- final String timestampType = String .valueOf (mapping .get ("type" ));
691- updatedMapping .put ("type" , timestampType != null ? timestampType : DateFieldMapper .CONTENT_TYPE );
689+ Map <String , Object > downsampledMapping = new HashMap <>();
690+ addDynamicTemplateForStrings (downsampledMapping );
691+ for (Map .Entry <String , Object > entry : sourceIndexMappings .entrySet ()) {
692+ if (entry .getKey ().equals (MAPPING_DYNAMIC_TEMPLATES )) {
693+ List <?> sourceDynamicTemplates = (List <?>) sourceIndexMappings .get (MAPPING_DYNAMIC_TEMPLATES );
694+ @ SuppressWarnings ("unchecked" )
695+ List <Object > downsampledDynamicTemplates = (List <Object >) downsampledMapping .get (MAPPING_DYNAMIC_TEMPLATES );
696+ downsampledDynamicTemplates .addAll (sourceDynamicTemplates );
697+ } else if (entry .getKey ().equals (MAPPING_PROPERTIES ) == false ) {
698+ downsampledMapping .put (entry .getKey (), entry .getValue ());
699+ }
700+ }
701+ populateMappingProperties (sourceIndexMappings , downsampledMapping , (fieldName , sourceMapping , updatedMapping ) -> {
702+ if (timestampField .equals (fieldName )) {
703+ final String timestampType = String .valueOf (sourceMapping .get (FIELD_TYPE ));
704+ updatedMapping .put (FIELD_TYPE , timestampType != null ? timestampType : DateFieldMapper .CONTENT_TYPE );
705+ if (sourceMapping .get ("format" ) != null ) {
706+ updatedMapping .put ("format" , sourceMapping .get ("format" ));
707+ }
708+ if (sourceMapping .get ("ignore_malformed" ) != null ) {
709+ updatedMapping .put ("ignore_malformed" , sourceMapping .get ("ignore_malformed" ));
710+ }
692711 updatedMapping .put ("meta" , Map .of (dateIntervalType , dateInterval , DownsampleConfig .TIME_ZONE , timezone ));
693- } else if (helper .isTimeSeriesMetric (field , mapping )) {
712+ return ;
713+ }
714+ if (helper .isTimeSeriesMetric (fieldName , sourceMapping )) {
694715 final TimeSeriesParams .MetricType metricType = TimeSeriesParams .MetricType .fromString (
695- mapping .get (TIME_SERIES_METRIC_PARAM ).toString ()
716+ sourceMapping .get (TIME_SERIES_METRIC_PARAM ).toString ()
696717 );
697718 if (metricType == TimeSeriesParams .MetricType .GAUGE
698- && AggregateMetricDoubleFieldMapper .CONTENT_TYPE .equals (mapping .get ("type" )) == false ) {
699- var supportedMetrics = getSupportedMetrics (metricType , mapping );
719+ && AggregateMetricDoubleFieldMapper .CONTENT_TYPE .equals (sourceMapping .get (FIELD_TYPE )) == false ) {
720+ var supportedMetrics = getSupportedMetrics (metricType , sourceMapping );
700721
701- updatedMapping .clear ();
702722 updatedMapping .put (TIME_SERIES_METRIC_PARAM , metricType .toString ());
703- updatedMapping .put ("type" , AggregateMetricDoubleFieldMapper .CONTENT_TYPE );
723+ updatedMapping .put (FIELD_TYPE , AggregateMetricDoubleFieldMapper .CONTENT_TYPE );
704724 updatedMapping .put (AggregateMetricDoubleFieldMapper .Names .METRICS , supportedMetrics .supportedMetrics );
705725 updatedMapping .put (AggregateMetricDoubleFieldMapper .Names .DEFAULT_METRIC , supportedMetrics .defaultMetric );
726+ return ;
706727 }
707728 }
729+ for (String f : sourceMapping .keySet ()) {
730+ if (f .equals (MAPPING_PROPERTIES ) || f .equals (MULTI_FIELDS )) {
731+ continue ;
732+ }
733+ updatedMapping .put (f , sourceMapping .get (f ));
734+ }
708735 });
709- return new CompressedXContent (sourceIndexMappings ).uncompressed ().utf8ToString ();
736+
737+ return new CompressedXContent (downsampledMapping ).uncompressed ().utf8ToString ();
738+ }
739+
740+ private static void populateMappingProperties (
741+ final Map <String , ?> sourceMapping ,
742+ final Map <String , Object > destMapping ,
743+ TriConsumer <String , Map <String , ?>, Map <String , Object >> fieldProcessor
744+ ) {
745+ Object sourceProperties0 = sourceMapping .get (MAPPING_PROPERTIES );
746+ if (sourceProperties0 instanceof Map ) {
747+ @ SuppressWarnings ("unchecked" )
748+ Map <String , ?> sourceProperties = (Map <String , ?>) sourceProperties0 ;
749+ var destProperties = new HashMap <>(sourceProperties .size ());
750+ destMapping .put (MAPPING_PROPERTIES , destProperties );
751+ for (Map .Entry <String , ?> entry : sourceProperties .entrySet ()) {
752+ String fieldName = entry .getKey ();
753+ final Object v = entry .getValue ();
754+ if (v instanceof Map ) {
755+ @ SuppressWarnings ("unchecked" )
756+ Map <String , ?> sourceFieldMapping = (Map <String , ?>) v ;
757+ var destFieldMapping = new HashMap <String , Object >(sourceFieldMapping .size ());
758+ destProperties .put (fieldName , destFieldMapping );
759+ // Process the field from properties and multi-fields.
760+ fieldProcessor .apply (entry .getKey (), sourceFieldMapping , destFieldMapping );
761+ populateMappingProperties (sourceFieldMapping , destFieldMapping , fieldProcessor );
762+
763+ // Multi fields
764+ Object sourceFieldsO = sourceFieldMapping .get (MULTI_FIELDS );
765+ if (sourceFieldsO instanceof Map ) {
766+ @ SuppressWarnings ("unchecked" )
767+ Map <String , ?> sourceFields = (Map <String , ?>) sourceFieldsO ;
768+ var destFields = new HashMap <String , Object >(sourceFields .size ());
769+ destFieldMapping .put (MULTI_FIELDS , destFields );
770+ for (Map .Entry <String , ?> multiFieldEntry : sourceFields .entrySet ()) {
771+ Object v2 = multiFieldEntry .getValue ();
772+ if (v2 instanceof Map ) {
773+ String multiFieldName = multiFieldEntry .getKey ();
774+ @ SuppressWarnings ("unchecked" )
775+ Map <String , ?> sourceMultiFieldMapping = (Map <String , ?>) v2 ;
776+ Map <String , Object > destMultiFieldMapping = new HashMap <>(sourceMultiFieldMapping .size ());
777+ fieldProcessor .apply (multiFieldName , sourceMultiFieldMapping , destMultiFieldMapping );
778+ }
779+ }
780+ }
781+ }
782+ }
783+ }
710784 }
711785
712786 // public for testing
@@ -717,7 +791,7 @@ public static AggregateMetricDoubleFieldSupportedMetrics getSupportedMetrics(
717791 final TimeSeriesParams .MetricType metricType ,
718792 final Map <String , ?> fieldProperties
719793 ) {
720- boolean sourceIsAggregate = fieldProperties .get ("type" ).equals (AggregateMetricDoubleFieldMapper .CONTENT_TYPE );
794+ boolean sourceIsAggregate = fieldProperties .get (FIELD_TYPE ).equals (AggregateMetricDoubleFieldMapper .CONTENT_TYPE );
721795 List <String > supportedAggs = List .of (metricType .supportedAggs ());
722796
723797 if (sourceIsAggregate ) {
@@ -834,11 +908,10 @@ static IndexMetadata.Builder copyIndexMetadata(
834908 /**
835909 * Configure the dynamic templates to always map strings to the keyword field type.
836910 */
837- private static void addDynamicTemplates (Map <String , Object > mapping ) throws IOException {
838- mapping .put (
839- "dynamic_templates" ,
840- List .of (Map .of ("strings" , Map .of ("match_mapping_type" , "string" , "mapping" , Map .of ("type" , "keyword" ))))
841- );
911+ private static void addDynamicTemplateForStrings (Map <String , Object > mapping ) {
912+ List <Object > dynamicTemplates = new ArrayList <>();
913+ dynamicTemplates .add (Map .of ("strings" , Map .of ("match_mapping_type" , "string" , "mapping" , Map .of (FIELD_TYPE , "keyword" ))));
914+ mapping .put (MAPPING_DYNAMIC_TEMPLATES , dynamicTemplates );
842915 }
843916
844917 private void createDownsampleIndex (
0 commit comments