Skip to content

Commit d4cecea

Browse files
committed
Extract "visit-and-copy" logic to the MappingVisitor
1 parent 7205afc commit d4cecea

File tree

3 files changed

+178
-81
lines changed

3 files changed

+178
-81
lines changed

server/src/main/java/org/elasticsearch/action/admin/cluster/stats/MappingVisitor.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@
99

1010
package org.elasticsearch.action.admin.cluster.stats;
1111

12+
import org.elasticsearch.common.TriConsumer;
13+
14+
import java.util.HashMap;
1215
import java.util.Map;
1316
import java.util.function.BiConsumer;
1417

1518
public final class MappingVisitor {
19+
public static final String PROPERTIES = "properties";
20+
public static final String FIELD_TYPE = "type";
21+
public static final String MULTI_FIELDS = "fields";
1622

1723
private MappingVisitor() {}
1824

@@ -25,7 +31,7 @@ private static void visitMapping(
2531
final String path,
2632
final BiConsumer<String, Map<String, ?>> fieldMappingConsumer
2733
) {
28-
Object properties = mapping.get("properties");
34+
Object properties = mapping.get(PROPERTIES);
2935
if (properties instanceof Map) {
3036
@SuppressWarnings("unchecked")
3137
Map<String, ?> propertiesAsMap = (Map<String, ?>) properties;
@@ -40,7 +46,7 @@ private static void visitMapping(
4046
visitMapping(fieldMapping, prefix + ".", fieldMappingConsumer);
4147

4248
// Multi fields
43-
Object fieldsO = fieldMapping.get("fields");
49+
Object fieldsO = fieldMapping.get(MULTI_FIELDS);
4450
if (fieldsO instanceof Map) {
4551
@SuppressWarnings("unchecked")
4652
Map<String, ?> fields = (Map<String, ?>) fieldsO;
@@ -75,4 +81,67 @@ public static void visitRuntimeMapping(Map<String, ?> mapping, BiConsumer<String
7581
runtimeFieldMappingConsumer.accept(entry.getKey(), runtimeFieldMapping);
7682
}
7783
}
84+
85+
/**
86+
* This visitor traverses the source mapping and copies the structure to the destination mapping after applying
87+
* the fieldMappingConsumer to the individual field mappings.
88+
*/
89+
public static void visitAndCopyMapping(
90+
final Map<String, ?> sourceMapping,
91+
final Map<String, Object> destMapping,
92+
final TriConsumer<String, Map<String, ?>, Map<String, Object>> fieldMappingConsumer
93+
) {
94+
Map<String, ?> sourceProperties = getMapOrNull(sourceMapping.get(PROPERTIES));
95+
if (sourceProperties == null) {
96+
return;
97+
}
98+
Map<String, Object> destProperties = new HashMap<>(sourceProperties.size());
99+
destMapping.put(PROPERTIES, destProperties);
100+
101+
for (Map.Entry<String, ?> entry : sourceProperties.entrySet()) {
102+
Map<String, ?> sourceFieldMapping = getMapOrNull(entry.getValue());
103+
if (sourceFieldMapping == null) {
104+
return;
105+
}
106+
var destFieldMapping = processAndCopy(entry.getKey(), sourceFieldMapping, destProperties, fieldMappingConsumer);
107+
visitAndCopyMapping(sourceFieldMapping, destFieldMapping, fieldMappingConsumer);
108+
109+
// Multi fields
110+
Map<String, ?> sourceMultiFields = getMapOrNull(sourceFieldMapping.get(MULTI_FIELDS));
111+
if (sourceMultiFields == null) {
112+
continue;
113+
}
114+
Map<String, Object> destFields = new HashMap<>(sourceMultiFields.size());
115+
destFieldMapping.put(MULTI_FIELDS, destFields);
116+
for (Map.Entry<String, ?> multiFieldEntry : sourceMultiFields.entrySet()) {
117+
String multiFieldName = multiFieldEntry.getKey();
118+
Map<String, ?> sourceMultiFieldMapping = getMapOrNull(multiFieldEntry.getValue());
119+
if (sourceMultiFieldMapping == null) {
120+
continue;
121+
}
122+
processAndCopy(multiFieldName, sourceMultiFieldMapping, destFields, fieldMappingConsumer);
123+
}
124+
}
125+
}
126+
127+
private static Map<String, ?> getMapOrNull(Object object) {
128+
if (object instanceof Map) {
129+
@SuppressWarnings("unchecked")
130+
Map<String, ?> map = (Map<String, ?>) object;
131+
return map;
132+
}
133+
return null;
134+
}
135+
136+
private static Map<String, Object> processAndCopy(
137+
String fieldName,
138+
Map<String, ?> sourceFieldMapping,
139+
Map<String, Object> destParentMap,
140+
TriConsumer<String, Map<String, ?>, Map<String, Object>> fieldMappingConsumer
141+
) {
142+
Map<String, Object> destFieldMapping = new HashMap<>(sourceFieldMapping.size());
143+
destParentMap.put(fieldName, destFieldMapping);
144+
fieldMappingConsumer.apply(fieldName, sourceFieldMapping, destFieldMapping);
145+
return destFieldMapping;
146+
}
78147
}

server/src/test/java/org/elasticsearch/action/admin/cluster/stats/MappingVisitorTests.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,61 @@ public void testCountRuntimeFields() {
136136
private static void collectRuntimeTypes(Map<String, ?> mapping, Set<String> types) {
137137
MappingVisitor.visitRuntimeMapping(mapping, (f, m) -> types.add(m.get("type").toString()));
138138
}
139+
140+
@SuppressWarnings("unchecked")
141+
public void testConvertLongToKeyword() {
142+
Map<String, Object> longType = Map.of("type", "long");
143+
Map<String, Object> textType = Map.of("type", "text");
144+
Map<String, Object> floatType = Map.of("type", "float", "scaling_factor", 1000);
145+
Map<String, Object> multiField = Map.of("type", "keyword", "fields", Map.of("my-long", longType, "my-float", floatType));
146+
Map<String, Object> objectField = Map.of("type", "keyword", "properties", Map.of("my-text", textType, "my-long", longType));
147+
Map<String, Object> expectedProperties = Map.of(
148+
"properties",
149+
Map.of("my-long", longType, "my-float", floatType, "my-multi-field", multiField, "my-object", objectField)
150+
);
151+
152+
HashMap<String, Object> result = new HashMap<>();
153+
MappingVisitor.visitAndCopyMapping(expectedProperties, result, (fieldName, source, dest) -> {
154+
for (String key : source.keySet()) {
155+
if (key.equals("type") && source.get(key).equals("long")) {
156+
dest.put(key, "keyword");
157+
} else {
158+
dest.put(key, source.get(key));
159+
}
160+
}
161+
});
162+
163+
assertTrue(result.containsKey("properties"));
164+
Map<String, Object> properties = (Map<String, Object>) result.get("properties");
165+
166+
assertTrue(properties.containsKey("my-long"));
167+
Map<String, Object> myLong = (Map<String, Object>) properties.get("my-long");
168+
assertEquals("keyword", myLong.get("type"));
169+
170+
assertTrue(properties.containsKey("my-float"));
171+
Map<String, Object> myFloat = (Map<String, Object>) properties.get("my-float");
172+
assertEquals("float", myFloat.get("type"));
173+
assertEquals(1000, myFloat.get("scaling_factor"));
174+
175+
assertTrue(properties.containsKey("my-multi-field"));
176+
Map<String, Object> myMultiField = (Map<String, Object>) properties.get("my-multi-field");
177+
assertEquals("keyword", myMultiField.get("type"));
178+
assertTrue(myMultiField.containsKey("fields"));
179+
Map<String, Object> foundFields = (Map<String, Object>) myMultiField.get("fields");
180+
assertTrue(foundFields.containsKey("my-long"));
181+
assertEquals("keyword", ((Map<String, Object>) foundFields.get("my-long")).get("type"));
182+
assertTrue(foundFields.containsKey("my-float"));
183+
assertEquals("float", ((Map<String, Object>) foundFields.get("my-float")).get("type"));
184+
assertEquals(1000, ((Map<String, Object>) foundFields.get("my-float")).get("scaling_factor"));
185+
186+
assertTrue(properties.containsKey("my-object"));
187+
Map<String, Object> myObject = (Map<String, Object>) properties.get("my-object");
188+
assertEquals("keyword", myObject.get("type"));
189+
assertTrue(myObject.containsKey("properties"));
190+
Map<String, Object> foundSubObjects = (Map<String, Object>) myObject.get("properties");
191+
assertTrue(foundSubObjects.containsKey("my-long"));
192+
assertEquals("keyword", ((Map<String, Object>) foundSubObjects.get("my-long")).get("type"));
193+
assertTrue(foundSubObjects.containsKey("my-text"));
194+
assertEquals("text", ((Map<String, Object>) foundSubObjects.get("my-text")).get("type"));
195+
}
139196
}

x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java

Lines changed: 50 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import org.elasticsearch.cluster.service.MasterServiceTaskQueue;
4646
import org.elasticsearch.common.Priority;
4747
import org.elasticsearch.common.Strings;
48-
import org.elasticsearch.common.TriConsumer;
4948
import org.elasticsearch.common.compress.CompressedXContent;
5049
import org.elasticsearch.common.settings.IndexScopedSettings;
5150
import org.elasticsearch.common.settings.Setting;
@@ -96,6 +95,10 @@
9695
import java.util.concurrent.atomic.AtomicInteger;
9796
import java.util.function.Predicate;
9897

98+
import static org.elasticsearch.action.admin.cluster.stats.MappingVisitor.FIELD_TYPE;
99+
import static org.elasticsearch.action.admin.cluster.stats.MappingVisitor.MULTI_FIELDS;
100+
import static org.elasticsearch.action.admin.cluster.stats.MappingVisitor.PROPERTIES;
101+
import static org.elasticsearch.action.admin.cluster.stats.MappingVisitor.visitAndCopyMapping;
99102
import static org.elasticsearch.index.mapper.TimeSeriesParams.TIME_SERIES_METRIC_PARAM;
100103
import static org.elasticsearch.xpack.core.ilm.DownsampleAction.DOWNSAMPLED_INDEX_PREFIX;
101104

@@ -108,10 +111,7 @@
108111
public class TransportDownsampleAction extends AcknowledgedTransportMasterNodeAction<DownsampleAction.Request> {
109112

110113
private static final Logger logger = LogManager.getLogger(TransportDownsampleAction.class);
111-
private static final String MAPPING_PROPERTIES = "properties";
112114
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";
115115
private final Client client;
116116
private final IndicesService indicesService;
117117
private final MasterServiceTaskQueue<DownsampleClusterStateUpdateTask> taskQueue;
@@ -694,96 +694,67 @@ static String createDownsampleIndexMapping(
694694
@SuppressWarnings("unchecked")
695695
List<Object> downsampledDynamicTemplates = (List<Object>) downsampledMapping.get(MAPPING_DYNAMIC_TEMPLATES);
696696
downsampledDynamicTemplates.addAll(sourceDynamicTemplates);
697-
} else if (entry.getKey().equals(MAPPING_PROPERTIES) == false) {
697+
} else if (entry.getKey().equals(MappingVisitor.PROPERTIES) == false) {
698698
downsampledMapping.put(entry.getKey(), entry.getValue());
699699
}
700700
}
701-
populateMappingProperties(sourceIndexMappings, downsampledMapping, (fieldName, sourceMapping, updatedMapping) -> {
701+
visitAndCopyMapping(sourceIndexMappings, downsampledMapping, (fieldName, sourceMapping, updatedMapping) -> {
702702
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-
}
711-
updatedMapping.put("meta", Map.of(dateIntervalType, dateInterval, DownsampleConfig.TIME_ZONE, timezone));
703+
updateTimestampField(sourceMapping, updatedMapping, dateIntervalType, dateInterval, timezone);
712704
return;
713-
}
714-
if (helper.isTimeSeriesMetric(fieldName, sourceMapping)) {
715-
final TimeSeriesParams.MetricType metricType = TimeSeriesParams.MetricType.fromString(
716-
sourceMapping.get(TIME_SERIES_METRIC_PARAM).toString()
717-
);
718-
if (metricType == TimeSeriesParams.MetricType.GAUGE
719-
&& AggregateMetricDoubleFieldMapper.CONTENT_TYPE.equals(sourceMapping.get(FIELD_TYPE)) == false) {
720-
var supportedMetrics = getSupportedMetrics(metricType, sourceMapping);
721-
722-
updatedMapping.put(TIME_SERIES_METRIC_PARAM, metricType.toString());
723-
updatedMapping.put(FIELD_TYPE, AggregateMetricDoubleFieldMapper.CONTENT_TYPE);
724-
updatedMapping.put(AggregateMetricDoubleFieldMapper.Names.METRICS, supportedMetrics.supportedMetrics);
725-
updatedMapping.put(AggregateMetricDoubleFieldMapper.Names.DEFAULT_METRIC, supportedMetrics.defaultMetric);
726-
return;
727-
}
728-
}
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));
705+
} else if (helper.isTimeSeriesMetric(fieldName, sourceMapping)) {
706+
processMetricField(sourceMapping, updatedMapping);
707+
} else {
708+
copyMapping(sourceMapping, updatedMapping);
734709
}
735710
});
736711

737712
return new CompressedXContent(downsampledMapping).uncompressed().utf8ToString();
738713
}
739714

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-
destFields.put(multiFieldName, destMultiFieldMapping);
778-
fieldProcessor.apply(multiFieldName, sourceMultiFieldMapping, destMultiFieldMapping);
779-
}
780-
}
781-
}
782-
}
715+
private static void processMetricField(Map<String, ?> sourceMapping, Map<String, Object> updatedMapping) {
716+
final TimeSeriesParams.MetricType metricType = TimeSeriesParams.MetricType.fromString(
717+
sourceMapping.get(TIME_SERIES_METRIC_PARAM).toString()
718+
);
719+
if (metricType == TimeSeriesParams.MetricType.GAUGE
720+
&& AggregateMetricDoubleFieldMapper.CONTENT_TYPE.equals(sourceMapping.get(FIELD_TYPE)) == false) {
721+
var supportedMetrics = getSupportedMetrics(metricType, sourceMapping);
722+
723+
updatedMapping.put(TIME_SERIES_METRIC_PARAM, metricType.toString());
724+
updatedMapping.put(FIELD_TYPE, AggregateMetricDoubleFieldMapper.CONTENT_TYPE);
725+
updatedMapping.put(AggregateMetricDoubleFieldMapper.Names.METRICS, supportedMetrics.supportedMetrics);
726+
updatedMapping.put(AggregateMetricDoubleFieldMapper.Names.DEFAULT_METRIC, supportedMetrics.defaultMetric);
727+
} else {
728+
copyMapping(sourceMapping, updatedMapping);
729+
}
730+
}
731+
732+
private static void copyMapping(Map<String, ?> sourceMapping, Map<String, Object> updatedMapping) {
733+
for (String f : sourceMapping.keySet()) {
734+
if (f.equals(PROPERTIES) == false && f.equals(MULTI_FIELDS) == false) {
735+
updatedMapping.put(f, sourceMapping.get(f));
783736
}
784737
}
785738
}
786739

740+
private static void updateTimestampField(
741+
Map<String, ?> sourceMapping,
742+
Map<String, Object> updatedMapping,
743+
String dateIntervalType,
744+
String dateInterval,
745+
String timezone
746+
) {
747+
final String timestampType = String.valueOf(sourceMapping.get(FIELD_TYPE));
748+
updatedMapping.put(FIELD_TYPE, timestampType != null ? timestampType : DateFieldMapper.CONTENT_TYPE);
749+
if (sourceMapping.get("format") != null) {
750+
updatedMapping.put("format", sourceMapping.get("format"));
751+
}
752+
if (sourceMapping.get("ignore_malformed") != null) {
753+
updatedMapping.put("ignore_malformed", sourceMapping.get("ignore_malformed"));
754+
}
755+
updatedMapping.put("meta", Map.of(dateIntervalType, dateInterval, DownsampleConfig.TIME_ZONE, timezone));
756+
}
757+
787758
// public for testing
788759
public record AggregateMetricDoubleFieldSupportedMetrics(String defaultMetric, List<String> supportedMetrics) {}
789760

0 commit comments

Comments
 (0)