|  | @@ -13,20 +13,51 @@ import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsAction
 | 
	
		
			
				|  |  |  import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest;
 | 
	
		
			
				|  |  |  import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData;
 | 
	
		
			
				|  |  |  import org.elasticsearch.client.Client;
 | 
	
		
			
				|  |  | +import org.elasticsearch.index.mapper.NumberFieldMapper;
 | 
	
		
			
				|  |  |  import org.elasticsearch.search.aggregations.AggregationBuilder;
 | 
	
		
			
				|  |  |  import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder;
 | 
	
		
			
				|  |  | +import org.elasticsearch.xpack.core.ClientHelper;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.dataframe.transforms.pivot.PivotConfig;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import java.util.HashMap;
 | 
	
		
			
				|  |  |  import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.Set;
 | 
	
		
			
				|  |  | +import java.util.stream.Collectors;
 | 
	
		
			
				|  |  | +import java.util.stream.Stream;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -public class SchemaUtil {
 | 
	
		
			
				|  |  | +public final class SchemaUtil {
 | 
	
		
			
				|  |  |      private static final Logger logger = LogManager.getLogger(SchemaUtil.class);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // Full collection of numeric field type strings
 | 
	
		
			
				|  |  | +    private static final Set<String> NUMERIC_FIELD_MAPPER_TYPES;
 | 
	
		
			
				|  |  | +    static {
 | 
	
		
			
				|  |  | +        Set<String> types = Stream.of(NumberFieldMapper.NumberType.values())
 | 
	
		
			
				|  |  | +            .map(NumberFieldMapper.NumberType::typeName)
 | 
	
		
			
				|  |  | +            .collect(Collectors.toSet());
 | 
	
		
			
				|  |  | +        types.add("scaled_float"); // have to add manually since scaled_float is in a module
 | 
	
		
			
				|  |  | +        NUMERIC_FIELD_MAPPER_TYPES = types;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      private SchemaUtil() {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    public static void deduceMappings(final Client client, final PivotConfig config, final String source,
 | 
	
		
			
				|  |  | +    public static boolean isNumericType(String type) {
 | 
	
		
			
				|  |  | +        return type != null && NUMERIC_FIELD_MAPPER_TYPES.contains(type);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Deduce the mappings for the destination index given the source index
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * The Listener is alerted with a {@code Map<String, String>} that is a "field-name":"type" mapping
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param client Client from which to make requests against the cluster
 | 
	
		
			
				|  |  | +     * @param config The PivotConfig for which to deduce destination mapping
 | 
	
		
			
				|  |  | +     * @param source Source index that contains the data to pivot
 | 
	
		
			
				|  |  | +     * @param listener Listener to alert on success or failure.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public static void deduceMappings(final Client client,
 | 
	
		
			
				|  |  | +                                      final PivotConfig config,
 | 
	
		
			
				|  |  | +                                      final String source,
 | 
	
		
			
				|  |  |                                        final ActionListener<Map<String, String>> listener) {
 | 
	
		
			
				|  |  |          // collects the fieldnames used as source for aggregations
 | 
	
		
			
				|  |  |          Map<String, String> aggregationSourceFieldNames = new HashMap<>();
 | 
	
	
		
			
				|  | @@ -56,18 +87,42 @@ public class SchemaUtil {
 | 
	
		
			
				|  |  |          allFieldNames.putAll(fieldNamesForGrouping);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          getSourceFieldMappings(client, source, allFieldNames.values().toArray(new String[0]),
 | 
	
		
			
				|  |  | -                ActionListener.wrap(sourceMappings -> {
 | 
	
		
			
				|  |  | -                    Map<String, String> targetMapping = resolveMappings(aggregationSourceFieldNames, aggregationTypes,
 | 
	
		
			
				|  |  | -                            fieldNamesForGrouping, sourceMappings);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    listener.onResponse(targetMapping);
 | 
	
		
			
				|  |  | -                }, e -> {
 | 
	
		
			
				|  |  | -                    listener.onFailure(e);
 | 
	
		
			
				|  |  | -                }));
 | 
	
		
			
				|  |  | +                ActionListener.wrap(
 | 
	
		
			
				|  |  | +                    sourceMappings -> listener.onResponse(resolveMappings(aggregationSourceFieldNames,
 | 
	
		
			
				|  |  | +                        aggregationTypes,
 | 
	
		
			
				|  |  | +                        fieldNamesForGrouping,
 | 
	
		
			
				|  |  | +                        sourceMappings)),
 | 
	
		
			
				|  |  | +                    listener::onFailure));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Gathers the field mappings for the "destination" index. Listener will receive an error, or a {@code Map<String, String>} of
 | 
	
		
			
				|  |  | +     * "field-name":"type".
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param client Client used to execute the request
 | 
	
		
			
				|  |  | +     * @param index The index, or index pattern, from which to gather all the field mappings
 | 
	
		
			
				|  |  | +     * @param listener The listener to be alerted on success or failure.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public static void getDestinationFieldMappings(final Client client,
 | 
	
		
			
				|  |  | +                                                   final String index,
 | 
	
		
			
				|  |  | +                                                   final ActionListener<Map<String, String>> listener) {
 | 
	
		
			
				|  |  | +        GetFieldMappingsRequest fieldMappingRequest = new GetFieldMappingsRequest();
 | 
	
		
			
				|  |  | +        fieldMappingRequest.indices(index);
 | 
	
		
			
				|  |  | +        fieldMappingRequest.fields("*");
 | 
	
		
			
				|  |  | +        ClientHelper.executeAsyncWithOrigin(client,
 | 
	
		
			
				|  |  | +            ClientHelper.DATA_FRAME_ORIGIN,
 | 
	
		
			
				|  |  | +            GetFieldMappingsAction.INSTANCE,
 | 
	
		
			
				|  |  | +            fieldMappingRequest,
 | 
	
		
			
				|  |  | +            ActionListener.wrap(
 | 
	
		
			
				|  |  | +                r -> listener.onResponse(extractFieldMappings(r.mappings())),
 | 
	
		
			
				|  |  | +                listener::onFailure
 | 
	
		
			
				|  |  | +            ));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private static Map<String, String> resolveMappings(Map<String, String> aggregationSourceFieldNames,
 | 
	
		
			
				|  |  | -            Map<String, String> aggregationTypes, Map<String, String> fieldNamesForGrouping, Map<String, String> sourceMappings) {
 | 
	
		
			
				|  |  | +                                                       Map<String, String> aggregationTypes,
 | 
	
		
			
				|  |  | +                                                       Map<String, String> fieldNamesForGrouping,
 | 
	
		
			
				|  |  | +                                                       Map<String, String> sourceMappings) {
 | 
	
		
			
				|  |  |          Map<String, String> targetMapping = new HashMap<>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          aggregationTypes.forEach((targetFieldName, aggregationName) -> {
 | 
	
	
		
			
				|  | @@ -107,14 +162,12 @@ public class SchemaUtil {
 | 
	
		
			
				|  |  |          fieldMappingRequest.indices(index);
 | 
	
		
			
				|  |  |          fieldMappingRequest.fields(fields);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        client.execute(GetFieldMappingsAction.INSTANCE, fieldMappingRequest, ActionListener.wrap(response -> {
 | 
	
		
			
				|  |  | -            listener.onResponse(extractSourceFieldMappings(response.mappings()));
 | 
	
		
			
				|  |  | -        }, e -> {
 | 
	
		
			
				|  |  | -            listener.onFailure(e);
 | 
	
		
			
				|  |  | -        }));
 | 
	
		
			
				|  |  | +        client.execute(GetFieldMappingsAction.INSTANCE, fieldMappingRequest, ActionListener.wrap(
 | 
	
		
			
				|  |  | +            response -> listener.onResponse(extractFieldMappings(response.mappings())),
 | 
	
		
			
				|  |  | +            listener::onFailure));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private static Map<String, String> extractSourceFieldMappings(Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings) {
 | 
	
		
			
				|  |  | +    private static Map<String, String> extractFieldMappings(Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings) {
 | 
	
		
			
				|  |  |          Map<String, String> extractedTypes = new HashMap<>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          mappings.forEach((indexName, docTypeToMapping) -> {
 |