99
1010import org .elasticsearch .common .logging .HeaderWarning ;
1111import org .elasticsearch .common .logging .LoggerMessageFormat ;
12+ import org .elasticsearch .compute .data .AggregateMetricDoubleBlockBuilder ;
1213import org .elasticsearch .compute .data .Block ;
1314import org .elasticsearch .core .Strings ;
1415import org .elasticsearch .index .IndexMode ;
5253import org .elasticsearch .xpack .esql .expression .function .FunctionDefinition ;
5354import org .elasticsearch .xpack .esql .expression .function .UnresolvedFunction ;
5455import org .elasticsearch .xpack .esql .expression .function .UnsupportedAttribute ;
56+ import org .elasticsearch .xpack .esql .expression .function .aggregate .AggregateFunction ;
57+ import org .elasticsearch .xpack .esql .expression .function .aggregate .Avg ;
58+ import org .elasticsearch .xpack .esql .expression .function .aggregate .Count ;
59+ import org .elasticsearch .xpack .esql .expression .function .aggregate .Max ;
60+ import org .elasticsearch .xpack .esql .expression .function .aggregate .Min ;
61+ import org .elasticsearch .xpack .esql .expression .function .aggregate .Sum ;
5562import org .elasticsearch .xpack .esql .expression .function .grouping .GroupingFunction ;
5663import org .elasticsearch .xpack .esql .expression .function .scalar .EsqlScalarFunction ;
5764import org .elasticsearch .xpack .esql .expression .function .scalar .conditional .Case ;
6067import org .elasticsearch .xpack .esql .expression .function .scalar .convert .AbstractConvertFunction ;
6168import org .elasticsearch .xpack .esql .expression .function .scalar .convert .ConvertFunction ;
6269import org .elasticsearch .xpack .esql .expression .function .scalar .convert .FoldablesConvertFunction ;
70+ import org .elasticsearch .xpack .esql .expression .function .scalar .convert .FromAggregateMetricDouble ;
71+ import org .elasticsearch .xpack .esql .expression .function .scalar .convert .ToAggregateMetricDouble ;
6372import org .elasticsearch .xpack .esql .expression .function .scalar .convert .ToDateNanos ;
6473import org .elasticsearch .xpack .esql .expression .function .scalar .convert .ToDouble ;
6574import org .elasticsearch .xpack .esql .expression .function .scalar .convert .ToInteger ;
132141import static java .util .Collections .emptyList ;
133142import static java .util .Collections .singletonList ;
134143import static org .elasticsearch .xpack .core .enrich .EnrichPolicy .GEO_MATCH_TYPE ;
144+ import static org .elasticsearch .xpack .esql .core .type .DataType .AGGREGATE_METRIC_DOUBLE ;
135145import static org .elasticsearch .xpack .esql .core .type .DataType .BOOLEAN ;
136146import static org .elasticsearch .xpack .esql .core .type .DataType .DATETIME ;
137147import static org .elasticsearch .xpack .esql .core .type .DataType .DATE_NANOS ;
@@ -179,7 +189,8 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
179189 "Resolution" ,
180190 new ResolveRefs (),
181191 new ImplicitCasting (),
182- new ResolveUnionTypes () // Must be after ResolveRefs, so union types can be found
192+ new ResolveUnionTypes (), // Must be after ResolveRefs, so union types can be found
193+ new ImplicitCastAggregateMetricDoubles ()
183194 ),
184195 new Batch <>("Finish Analysis" , Limiter .ONCE , new AddImplicitLimit (), new AddImplicitForkLimit (), new UnionTypesCleanup ())
185196 );
@@ -1642,9 +1653,15 @@ private LogicalPlan doRule(LogicalPlan plan) {
16421653 return plan ;
16431654 }
16441655
1645- // And add generated fields to EsRelation, so these new attributes will appear in the OutputExec of the Fragment
1646- // and thereby get used in FieldExtractExec
1647- plan = plan .transformDown (EsRelation .class , esr -> {
1656+ return addGeneratedFieldsToEsRelations (plan , unionFieldAttributes );
1657+ }
1658+
1659+ /**
1660+ * Add generated fields to EsRelation, so these new attributes will appear in the OutputExec of the Fragment
1661+ * and thereby get used in FieldExtractExec
1662+ */
1663+ private static LogicalPlan addGeneratedFieldsToEsRelations (LogicalPlan plan , List <FieldAttribute > unionFieldAttributes ) {
1664+ return plan .transformDown (EsRelation .class , esr -> {
16481665 List <Attribute > missing = new ArrayList <>();
16491666 for (FieldAttribute fa : unionFieldAttributes ) {
16501667 // Using outputSet().contains looks by NameId, resp. uses semanticEquals.
@@ -1664,7 +1681,6 @@ private LogicalPlan doRule(LogicalPlan plan) {
16641681 }
16651682 return esr ;
16661683 });
1667- return plan ;
16681684 }
16691685
16701686 private Expression resolveConvertFunction (ConvertFunction convert , List <FieldAttribute > unionFieldAttributes ) {
@@ -1734,7 +1750,7 @@ private Expression resolveConvertFunction(ConvertFunction convert, List<FieldAtt
17341750 return convertExpression ;
17351751 }
17361752
1737- private Expression createIfDoesNotAlreadyExist (
1753+ private static Expression createIfDoesNotAlreadyExist (
17381754 FieldAttribute fa ,
17391755 MultiTypeEsField resolvedField ,
17401756 List <FieldAttribute > unionFieldAttributes
@@ -1896,4 +1912,102 @@ private static void typeResolutions(
18961912 var concreteConvert = ResolveUnionTypes .typeSpecificConvert (convert , fieldAttribute .source (), type , imf );
18971913 typeResolutions .put (key , concreteConvert );
18981914 }
1915+
1916+ /**
1917+ * Take InvalidMappedFields in specific aggregations (min, max, sum, count, and avg) and if all original data types
1918+ * are aggregate metric double + any combination of numerics, implicitly cast them to the same type: aggregate metric
1919+ * double for count, and double for min, max, and sum. Avg gets replaced with its surrogate (Div(Sum, Count))
1920+ */
1921+ private static class ImplicitCastAggregateMetricDoubles extends Rule <LogicalPlan , LogicalPlan > {
1922+
1923+ private List <FieldAttribute > unionFieldAttributes ;
1924+
1925+ @ Override
1926+ public LogicalPlan apply (LogicalPlan plan ) {
1927+ unionFieldAttributes = new ArrayList <>();
1928+ return plan .transformUp (LogicalPlan .class , p -> p .childrenResolved () == false ? p : doRule (p ));
1929+ }
1930+
1931+ private LogicalPlan doRule (LogicalPlan plan ) {
1932+ int alreadyAddedUnionFieldAttributes = unionFieldAttributes .size ();
1933+ plan = plan .transformExpressionsOnly (e -> switch (e ) {
1934+ case Max max -> resolveMetricFunction (max , AggregateMetricDoubleBlockBuilder .Metric .MAX );
1935+ case Min min -> resolveMetricFunction (min , AggregateMetricDoubleBlockBuilder .Metric .MIN );
1936+ case Sum sum -> resolveMetricFunction (sum , AggregateMetricDoubleBlockBuilder .Metric .SUM );
1937+ case Count count -> resolveMetricFunction (count , AggregateMetricDoubleBlockBuilder .Metric .COUNT );
1938+ case Avg avg -> substituteSurrogates (avg );
1939+ default -> e ;
1940+ });
1941+
1942+ if (unionFieldAttributes .size () == alreadyAddedUnionFieldAttributes ) {
1943+ return plan ;
1944+ }
1945+ return ResolveUnionTypes .addGeneratedFieldsToEsRelations (plan , unionFieldAttributes );
1946+ }
1947+
1948+ private Expression resolveMetricFunction (Expression expression , AggregateMetricDoubleBlockBuilder .Metric metric ) {
1949+ AggregateFunction aggregateFunction = (AggregateFunction ) expression ;
1950+ if (aggregateFunction .field () instanceof FieldAttribute fa && fa .field () instanceof InvalidMappedField imf ) {
1951+ HashMap <ResolveUnionTypes .TypeResolutionKey , Expression > typeResolutions = new HashMap <>();
1952+ if (typesShouldBeConverted (imf .types ()) == false ) {
1953+ return expression ;
1954+ }
1955+ for (DataType type : imf .types ()) {
1956+ // Effectively the contents of ResolveUnionTypes::typeSpecificConvert(...)
1957+ // except convertFunction is not necessarily a ConvertFunction (as in the case of Sum's FromAggregateMetricDouble)
1958+ // and we do not substitute surrogates because Count does have a surrogate in the case of aggregate metric double
1959+ ResolveUnionTypes .TypeResolutionKey key = new ResolveUnionTypes .TypeResolutionKey (fa .name (), type );
1960+ EsField field = new EsField (imf .getName (), type , imf .getProperties (), imf .isAggregatable ());
1961+ FieldAttribute originalFieldAttr = (FieldAttribute ) aggregateFunction .field ();
1962+ FieldAttribute resolved = new FieldAttribute (
1963+ fa .source (),
1964+ originalFieldAttr .parentName (),
1965+ originalFieldAttr .name (),
1966+ field ,
1967+ originalFieldAttr .nullable (),
1968+ originalFieldAttr .id (),
1969+ true
1970+ );
1971+
1972+ Expression convertExpression ;
1973+ if (metric == AggregateMetricDoubleBlockBuilder .Metric .COUNT ) {
1974+ convertExpression = new ToAggregateMetricDouble (fa .source (), resolved );
1975+ } else if (type == AGGREGATE_METRIC_DOUBLE ) {
1976+ convertExpression = FromAggregateMetricDouble .withMetric (fa .source (), resolved , metric );
1977+ } else {
1978+ convertExpression = new ToDouble (fa .source (), resolved );
1979+ }
1980+ Expression e = expression .replaceChildren (List .of (convertExpression , expression .children ().get (1 )));
1981+ typeResolutions .put (key , e .children ().getFirst ());
1982+ }
1983+ var resolvedField = ResolveUnionTypes .resolvedMultiTypeEsField (fa , typeResolutions );
1984+ var newFieldAttribute = ResolveUnionTypes .createIfDoesNotAlreadyExist (fa , resolvedField , unionFieldAttributes );
1985+ return expression .replaceChildren (List .of (newFieldAttribute , expression .children ().get (1 )));
1986+ }
1987+ return expression ;
1988+ }
1989+
1990+ private Expression substituteSurrogates (Expression expression ) {
1991+ AggregateFunction aggregateFunction = (AggregateFunction ) expression ;
1992+ if (aggregateFunction .field () instanceof FieldAttribute fa && fa .field () instanceof InvalidMappedField imf ) {
1993+ if (typesShouldBeConverted (imf .types ()) == false ) {
1994+ return expression ;
1995+ }
1996+ return SubstituteSurrogateExpressions .rule (expression );
1997+ }
1998+ return expression ;
1999+ }
2000+
2001+ private boolean typesShouldBeConverted (Set <DataType > types ) {
2002+ if (types .contains (AGGREGATE_METRIC_DOUBLE ) == false ) {
2003+ return false ;
2004+ }
2005+ for (DataType type : types ) {
2006+ if (type .isNumeric () == false && type != AGGREGATE_METRIC_DOUBLE ) {
2007+ return false ;
2008+ }
2009+ }
2010+ return true ;
2011+ }
2012+ }
18992013}
0 commit comments