Skip to content

Commit 83650e9

Browse files
clean up
1 parent b0abae7 commit 83650e9

File tree

1 file changed

+14
-196
lines changed
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis

1 file changed

+14
-196
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java

Lines changed: 14 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import org.elasticsearch.common.logging.HeaderWarning;
1111
import org.elasticsearch.common.logging.LoggerMessageFormat;
12-
import org.elasticsearch.common.util.Maps;
1312
import org.elasticsearch.compute.data.Block;
1413
import org.elasticsearch.core.Strings;
1514
import org.elasticsearch.index.IndexMode;
@@ -18,7 +17,6 @@
1817
import org.elasticsearch.xpack.esql.Column;
1918
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
2019
import org.elasticsearch.xpack.esql.VerificationException;
21-
import org.elasticsearch.xpack.esql.action.EsqlCapabilities;
2220
import org.elasticsearch.xpack.esql.analysis.AnalyzerRules.ParameterizedAnalyzerRule;
2321
import org.elasticsearch.xpack.esql.common.Failure;
2422
import org.elasticsearch.xpack.esql.core.capabilities.Resolvables;
@@ -63,7 +61,6 @@
6361
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ConvertFunction;
6462
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FoldablesConvertFunction;
6563
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDateNanos;
66-
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDatetime;
6764
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble;
6865
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToInteger;
6966
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToLong;
@@ -94,7 +91,6 @@
9491
import org.elasticsearch.xpack.esql.plan.logical.Project;
9592
import org.elasticsearch.xpack.esql.plan.logical.Rename;
9693
import org.elasticsearch.xpack.esql.plan.logical.RrfScoreEval;
97-
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan;
9894
import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation;
9995
import org.elasticsearch.xpack.esql.plan.logical.inference.Completion;
10096
import org.elasticsearch.xpack.esql.plan.logical.inference.InferencePlan;
@@ -153,7 +149,6 @@
153149
import static org.elasticsearch.xpack.esql.core.type.DataType.TIME_DURATION;
154150
import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED;
155151
import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION;
156-
import static org.elasticsearch.xpack.esql.core.type.DataType.isMillisOrNanos;
157152
import static org.elasticsearch.xpack.esql.core.type.DataType.isTemporalAmount;
158153
import static org.elasticsearch.xpack.esql.telemetry.FeatureMetric.LIMIT;
159154
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.maybeParseTemporalAmount;
@@ -180,10 +175,11 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
180175
new ResolveFunctions(),
181176
new ResolveUnionTypesInEsRelation()
182177
),
183-
new Batch<>("Resolution", new ResolveRefs(), new ImplicitCasting(), new ResolveUnionTypes() // Must be after ResolveRefs, so union
184-
// types can be found
185-
// Must be after ResolveUnionTypes, if there is explicit casting on the union typed fields, implicit casting won't be added
186-
// new ImplicitCastingForUnionTypedFields()
178+
new Batch<>(
179+
"Resolution",
180+
new ResolveRefs(),
181+
new ImplicitCasting(),
182+
new ResolveUnionTypes() // Must be after ResolveRefs, so union types can be found
187183
),
188184
new Batch<>("Finish Analysis", Limiter.ONCE, new AddImplicitLimit(), new AddImplicitForkLimit(), new UnionTypesCleanup())
189185
);
@@ -1680,15 +1676,16 @@ private Expression resolveConvertFunction(ConvertFunction convert, List<FieldAtt
16801676
return createIfDoesNotAlreadyExist(fa, resolvedField, unionFieldAttributes);
16811677
}
16821678
} else if (convert.field() instanceof FieldAttribute fa
1683-
&& fa.synthetic() == false
1679+
&& fa.synthetic() == false // MultiTypeEsField in EsRelation created by ResolveUnionTypesInEsRelation has synthetic = false
16841680
&& fa.field() instanceof MultiTypeEsField mtf) {
16851681
// This is an explicit casting of a union typed field that has been converted to MultiTypeEsField in EsRelation by
1686-
// ResolveUnionTypesInEsRelation, do not do double casting.
1682+
// ResolveUnionTypesInEsRelation, it is not necessary to cast it into date_nanos and then do explicit casting.
16871683
if (((Expression) convert).dataType() == mtf.getDataType()) {
1688-
// The same data type between implicit and explicit casting, explicit conversion is not needed
1684+
// The same data type between implicit(date_nanos) and explicit casting, explicit conversion is not needed
16891685
return convert.field();
16901686
} else {
1691-
// TODO Is there an easy way to convert from one MultiTypeEsField to another MultiTypeEsField?
1687+
// Data type is different between implicit(date_nanos) and explicit casting, create a new MultiTypeEsField with
1688+
// explicit casting type. TODO Is there an easy way to convert one MultiTypeEsField to another MultiTypeEsField?
16921689
HashMap<TypeResolutionKey, Expression> typeResolutions = new HashMap<>();
16931690
Set<DataType> supportedTypes = convert.supportedTypes();
16941691
Holder<Boolean> conversionSupported = new Holder<>(true);
@@ -1705,10 +1702,11 @@ private Expression resolveConvertFunction(ConvertFunction convert, List<FieldAtt
17051702
conversionSupported.set(false);
17061703
}
17071704
});
1708-
// If the conversions are supported, create a new FieldAttribute with a new MultiTypeEsField, and add it to
1709-
// unionFieldAttributes.
1705+
// If the conversions are supported, all the data types in a MultiTypeEsField can be cast to the explicit casting
1706+
// data type, create a new FieldAttribute with a new MultiTypeEsField, and add it to unionFieldAttributes.
17101707
if (conversionSupported.get()) {
1711-
// build the map between index name and conversion expressions
1708+
// Build the mapping between index name and conversion expressions, as a MultiTypeEsField does not store the
1709+
// mapping between data types and index names,
17121710
Map<String, Expression> indexToConversionExpressions = new HashMap<>();
17131711
for (Map.Entry<String, Expression> entry : mtf.getIndexToConversionExpressions().entrySet()) {
17141712
String indexName = entry.getKey();
@@ -1851,186 +1849,6 @@ private static LogicalPlan planWithoutSyntheticAttributes(LogicalPlan plan) {
18511849
}
18521850
}
18531851

1854-
/**
1855-
* Cast union typed fields that are mixed of date and date_nanos types into date_nanos.
1856-
*/
1857-
private static class ImplicitCastingForUnionTypedFields extends ParameterizedRule<LogicalPlan, LogicalPlan, AnalyzerContext> {
1858-
@Override
1859-
public LogicalPlan apply(LogicalPlan plan, AnalyzerContext context) {
1860-
if (EsqlCapabilities.Cap.IMPLICIT_CASTING_DATE_AND_DATE_NANOS.isEnabled() == false) {
1861-
return plan;
1862-
}
1863-
// This rule should be applied after ResolveUnionTypes, so that the InvalidMappedFields with explicit casting are converted into
1864-
// MultiTypeEsField, and don't get double cast here.
1865-
Map<FieldAttribute, Alias> invalidMappedFieldCasted = new HashMap<>();
1866-
LogicalPlan transformedPlan = plan.transformUp(LogicalPlan.class, p -> {
1867-
// exclude LookupJoin for now, as it doesn't support date_nanos as join key yet
1868-
if (p instanceof UnaryPlan == false) {
1869-
return p;
1870-
}
1871-
Set<FieldAttribute> invalidMappedFields = invalidMappedFieldsInLogicalPlan(p);
1872-
if (invalidMappedFields.isEmpty() == false) {
1873-
// If we are at a plan node that has invalid mapped fields, we need to either add an EVAL, or if that has been done
1874-
// we should instead replace with the already cast field
1875-
Map<FieldAttribute, Alias> newAliases = Maps.newHashMapWithExpectedSize(invalidMappedFields.size());
1876-
Map<FieldAttribute, Alias> existingAliases = Maps.newHashMapWithExpectedSize(invalidMappedFields.size());
1877-
for (FieldAttribute fa : invalidMappedFields) {
1878-
if (invalidMappedFieldCasted.containsKey(fa)) {
1879-
// There is already an eval plan created for the implicit cast field, just reference to it
1880-
Alias alias = invalidMappedFieldCasted.get(fa);
1881-
existingAliases.put(fa, alias);
1882-
} else {
1883-
// Create a new alias and later on add a new EVAL with this new aliases for implicit casting
1884-
DataType targetType = commonDataType(fa);
1885-
if (targetType != null) {
1886-
Expression conversionFunction = castInvalidMappedField(targetType, fa);
1887-
Alias alias = new Alias(fa.source(), fa.name(), conversionFunction);
1888-
newAliases.put(fa, alias);
1889-
invalidMappedFieldCasted.put(fa, alias);
1890-
}
1891-
}
1892-
}
1893-
// If there are new aliases created, create a new eval child with new aliases for the current plan。
1894-
// How many children does a LogicalPlan have? Only deal with UnaryPlan and LookupJoin for now.
1895-
if (newAliases.isEmpty() == false) { // create a new eval child plan
1896-
UnaryPlan u = (UnaryPlan) p; // this must be a unary plan, as it is checked at the beginning of plan loop
1897-
Eval eval = new Eval(u.source(), u.child(), newAliases.values().stream().toList());
1898-
p = u.replaceChild(eval);
1899-
// TODO Lookup join does not work on date_nanos field yet, joining on a date_nanos field does not find a match.
1900-
// And lookup up join is a special case as a lookup join has two children, after date_nanos is supported as a join
1901-
// key, the transformation needs to take it into account.
1902-
}
1903-
// If there are new or existing aliases identified, combine them into one map
1904-
Map<FieldAttribute, Alias> allAliases = Maps.newHashMapWithExpectedSize(invalidMappedFields.size());
1905-
allAliases.putAll(newAliases);
1906-
allAliases.putAll(existingAliases);
1907-
if (allAliases.isEmpty() == false) { // there is already eval plan for that union typed field, reference to the aliases
1908-
p = p.transformExpressionsOnly(FieldAttribute.class, fa -> {
1909-
Alias alias = allAliases.get(fa);
1910-
return alias != null ? alias.toAttribute() : fa;
1911-
});
1912-
// MvExpand and Stats have ReferenceAttribute referencing the FieldAttribute in the same plan.
1913-
// The ReferenceAttribute need to be updated to point to the casting expression.
1914-
if (p instanceof MvExpand mvExpand) {
1915-
p = transformMvExpand(mvExpand);
1916-
} else if (p instanceof Aggregate aggregate) {
1917-
p = transformAggregate(aggregate);
1918-
}
1919-
}
1920-
}
1921-
return p;
1922-
});
1923-
transformedPlan = castInvalidMappedFieldInFinalOutput(transformedPlan);
1924-
return transformedPlan;
1925-
}
1926-
1927-
/**
1928-
* Find a common data type that the union typed field can cast to, only date and date_nanos types are supported.
1929-
* This method can be extended to support implicit casting for the other data types.
1930-
*/
1931-
private static DataType commonDataType(FieldAttribute unionTypedField) {
1932-
DataType targetType = null;
1933-
if (unionTypedField.field() instanceof InvalidMappedField imf) {
1934-
for (DataType type : imf.types()) {
1935-
if (isMillisOrNanos(type) == false) { // if there is field that is no date or date_nanos, don't do implicit casting
1936-
return null;
1937-
}
1938-
if (targetType == null) { // initialize the target type to the first type
1939-
targetType = type;
1940-
} else if (targetType == DATE_NANOS || type == DATE_NANOS) {
1941-
targetType = DATE_NANOS;
1942-
}
1943-
}
1944-
}
1945-
return targetType;
1946-
}
1947-
1948-
/**
1949-
* Do implicit casting for date and date_nanos only.
1950-
*/
1951-
private static Expression castInvalidMappedField(DataType targetType, FieldAttribute fa) {
1952-
Source source = fa.source();
1953-
return switch (targetType) {
1954-
case DATETIME -> new ToDatetime(source, fa); // in case we decided to use DATE as a common type instead of DATE_NANOS
1955-
case DATE_NANOS -> new ToDateNanos(source, fa);
1956-
default -> throw new EsqlIllegalArgumentException("unexpected data type: " + targetType);
1957-
};
1958-
}
1959-
1960-
/**
1961-
* Return all the FieldAttribute that contain InvalidMappedField in the current plan.
1962-
*/
1963-
private static Set<FieldAttribute> invalidMappedFieldsInLogicalPlan(LogicalPlan plan) {
1964-
Set<FieldAttribute> fas = new HashSet<>();
1965-
// Invalid mapped fields are legal at EsRelation level, as long as they are not used elsewhere. In the final output, if they
1966-
// have not been dropped, implicit cast will be added for them, so that we can return not null values, the implicit casting is
1967-
// deferred to when the fields are used or returned.
1968-
if (plan instanceof EsRelation == false) {
1969-
plan.forEachExpression(FieldAttribute.class, fa -> {
1970-
if (fa.field() instanceof InvalidMappedField) {
1971-
fas.add(fa);
1972-
}
1973-
});
1974-
}
1975-
return fas;
1976-
}
1977-
1978-
/**
1979-
* Cast the InvalidMappedFields in the final output of the query, this is needed when these fields are not referenced in the query
1980-
* explicitly, so there is no chance to cast them to a common type earlier, an example of such query is from index*.
1981-
*/
1982-
private static LogicalPlan castInvalidMappedFieldInFinalOutput(LogicalPlan logicalPlan) {
1983-
// Check the output of the query, if the top level plan is resolved, check if there is InvalidMappedField in its output,
1984-
// if so add a project with eval, so that a not null value can be returned for a union typed field
1985-
if (logicalPlan.resolved()) {
1986-
List<Attribute> output = logicalPlan.output();
1987-
Map<FieldAttribute, Alias> newAliases = Maps.newHashMapWithExpectedSize(output.size());
1988-
output.forEach(a -> {
1989-
if (a instanceof FieldAttribute fa && fa.field() instanceof InvalidMappedField) {
1990-
DataType targetType = commonDataType(fa);
1991-
if (targetType != null) {
1992-
Expression conversionFunction = castInvalidMappedField(targetType, fa);
1993-
Alias alias = new Alias(fa.source(), fa.name(), conversionFunction);
1994-
newAliases.put(fa, alias);
1995-
}
1996-
}
1997-
});
1998-
if (newAliases.isEmpty() == false) { // add an Eval for the union typed fields left that are not cast implicitly yet
1999-
if (logicalPlan instanceof EsRelation esr) {
2000-
// EsRelation does not have a child, we should not see row here, add a eval on top of it
2001-
logicalPlan = new Eval(esr.source(), esr, newAliases.values().stream().toList());
2002-
} else if (logicalPlan instanceof UnaryPlan unary) {
2003-
// Add an Eval as the child of this plan
2004-
Eval eval = new Eval(unary.source(), unary.child(), newAliases.values().stream().toList());
2005-
logicalPlan = unary.replaceChild(eval);
2006-
}
2007-
// TODO LookupJoin is a binary plan, it does not create a new field, ideally adding an Eval on top of it should be fine,
2008-
// however because the output of a LookupJoin does not include InvalidMappedFields even the LHS output has
2009-
// InvalidMappedFields, it is a bug need to be addressed
2010-
}
2011-
}
2012-
return logicalPlan;
2013-
}
2014-
2015-
private static MvExpand transformMvExpand(MvExpand mvExpand) {
2016-
NamedExpression target = mvExpand.target();
2017-
return new MvExpand(mvExpand.source(), mvExpand.child(), target, target.toAttribute());
2018-
}
2019-
2020-
private static Aggregate transformAggregate(Aggregate aggregate) {
2021-
List<? extends NamedExpression> aggregates = aggregate.aggregates();
2022-
List<Expression> groupings = aggregate.groupings();
2023-
List<NamedExpression> aggregatesWithNewRefs = new ArrayList<>(aggregates.size());
2024-
for (int i = 0; i < aggregates.size() - groupings.size(); i++) {
2025-
aggregatesWithNewRefs.add(aggregates.get(i));
2026-
}
2027-
for (Expression e : groupings) { // Add groupings
2028-
aggregatesWithNewRefs.add(Expressions.attribute(e));
2029-
}
2030-
return aggregate.with(groupings, aggregatesWithNewRefs);
2031-
}
2032-
}
2033-
20341852
/**
20351853
* Cast the union typed fields in EsRelation to date_nanos if they are mixed date and date_nanos types.
20361854
*/

0 commit comments

Comments
 (0)