Skip to content

Commit 5dd906e

Browse files
refactor and remove pushdown optimization
1 parent 236bb57 commit 5dd906e

File tree

11 files changed

+218
-182
lines changed

11 files changed

+218
-182
lines changed

docs/changelog/123678.yaml

Lines changed: 0 additions & 6 deletions
This file was deleted.

x-pack/plugin/esql/qa/server/single-node/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
javaRestTestImplementation project(xpackModule('esql:qa:testFixtures'))
1414
javaRestTestImplementation project(xpackModule('esql:qa:server'))
1515
javaRestTestImplementation project(xpackModule('esql:tools'))
16+
javaRestTestImplementation project(xpackModule('esql'))
1617
yamlRestTestImplementation project(xpackModule('esql:qa:server'))
1718

1819
javaRestTestImplementation('org.apache.arrow:arrow-vector:16.1.0')

x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/RestEsqlIT.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import static org.elasticsearch.test.ListMatcher.matchesList;
5656
import static org.elasticsearch.test.MapMatcher.assertMap;
5757
import static org.elasticsearch.test.MapMatcher.matchesMap;
58+
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.IMPLICIT_CASTING_DATE_AND_DATE_NANOS;
5859
import static org.elasticsearch.xpack.esql.core.type.DataType.isMillisOrNanos;
5960
import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode.SYNC;
6061
import static org.elasticsearch.xpack.esql.tools.ProfileParser.parseProfile;
@@ -725,10 +726,6 @@ public void testSuggestedCast() throws IOException {
725726

726727
for (int i = 0; i < listOfTypes.size(); i++) {
727728
for (int j = i + 1; j < listOfTypes.size(); j++) {
728-
if (isMillisOrNanos(listOfTypes.get(i)) && isMillisOrNanos(listOfTypes.get(j))) {
729-
// datetime and date_nanos are casted to date_nanos implicitly
730-
continue;
731-
}
732729
String query = String.format(Locale.ROOT, """
733730
{
734731
"query": "FROM index-%s,index-%s | LIMIT 100 | KEEP my_field"
@@ -740,19 +737,26 @@ public void testSuggestedCast() throws IOException {
740737
Map<String, Object> results = entityAsMap(resp);
741738
List<?> columns = (List<?>) results.get("columns");
742739
DataType suggestedCast = DataType.suggestedCast(Set.of(listOfTypes.get(i), listOfTypes.get(j)));
743-
assertThat(
744-
columns,
745-
equalTo(
746-
List.of(
747-
Map.ofEntries(
748-
Map.entry("name", "my_field"),
749-
Map.entry("type", "unsupported"),
750-
Map.entry("original_types", List.of(listOfTypes.get(i).typeName(), listOfTypes.get(j).typeName())),
751-
Map.entry("suggested_cast", suggestedCast.typeName())
740+
if (IMPLICIT_CASTING_DATE_AND_DATE_NANOS.isEnabled()
741+
&& isMillisOrNanos(listOfTypes.get(i))
742+
&& isMillisOrNanos(listOfTypes.get(j))) {
743+
// datetime and date_nanos are casted to date_nanos implicitly
744+
assertThat(columns, equalTo(List.of(Map.ofEntries(Map.entry("name", "my_field"), Map.entry("type", "date_nanos")))));
745+
} else {
746+
assertThat(
747+
columns,
748+
equalTo(
749+
List.of(
750+
Map.ofEntries(
751+
Map.entry("name", "my_field"),
752+
Map.entry("type", "unsupported"),
753+
Map.entry("original_types", List.of(listOfTypes.get(i).typeName(), listOfTypes.get(j).typeName())),
754+
Map.entry("suggested_cast", suggestedCast.typeName())
755+
)
752756
)
753757
)
754-
)
755-
);
758+
);
759+
}
756760

757761
String castedQuery = String.format(
758762
Locale.ROOT,

x-pack/plugin/esql/qa/testFixtures/src/main/resources/union_types.csv-spec

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,15 +1712,15 @@ required_capability: date_nanos_type
17121712
required_capability: implicit_casting_date_and_date_nanos
17131713

17141714
FROM employees, employees_incompatible
1715-
| EVAL x = emp_no::long, avg_worked_seconds = avg_worked_seconds::unsigned_long, y = hire_date::datetime, z = hire_date::date_nanos
1716-
| KEEP x, languages, hire_date, avg_worked_seconds, y, z
1715+
| EVAL x = emp_no::long, avg_worked_seconds = avg_worked_seconds::unsigned_long, y = hire_date::datetime, z = hire_date::date_nanos, w = hire_date
1716+
| KEEP x, languages, hire_date, avg_worked_seconds, y, z, w
17171717
| SORT x, z
17181718
| LIMIT 2
17191719
;
17201720

1721-
x:long |languages:unsupported |hire_date:date_nanos |avg_worked_seconds:unsigned_long |y:datetime |z:date_nanos
1722-
10001 |null |1986-06-26T00:00:00.000Z |268728049 |1986-06-26T00:00:00.000Z |1986-06-26T00:00:00.000Z
1723-
10001 |null |1986-06-26T00:00:00.000Z |268728049 |1986-06-26T00:00:00.000Z |1986-06-26T00:00:00.000Z
1721+
x:long |languages:unsupported |hire_date:date_nanos |avg_worked_seconds:unsigned_long |y:datetime |z:date_nanos |w:date_nanos
1722+
10001 |null |1986-06-26T00:00:00.000Z |268728049 |1986-06-26T00:00:00.000Z |1986-06-26T00:00:00.000Z |1986-06-26T00:00:00.000Z
1723+
10001 |null |1986-06-26T00:00:00.000Z |268728049 |1986-06-26T00:00:00.000Z |1986-06-26T00:00:00.000Z |1986-06-26T00:00:00.000Z
17241724
;
17251725

17261726
ImplicitCastingMultiTypedFieldsDropKeepSort
@@ -2075,6 +2075,39 @@ millis:date_nanos | nanos:date_nanos | num:long
20752075
1999-10-23T12:15:03.360Z | 2023-02-23T13:33:34.937193Z | 0
20762076
;
20772077

2078+
ImplicitCastingMultiTypedFieldsBackAndForth
2079+
required_capability: date_nanos_type
2080+
required_capability: implicit_casting_date_and_date_nanos
2081+
2082+
FROM date_nanos, date_nanos_union_types
2083+
| MV_EXPAND nanos
2084+
| EVAL a = nanos::datetime, b = nanos::date_nanos, c = nanos, d = nanos::datetime::date_nanos, e = nanos::date_nanos::datetime
2085+
| KEEP millis, nanos, a, b, c, d, e
2086+
| SORT nanos, millis
2087+
| LIMIT 14
2088+
;
2089+
2090+
warningRegex:evaluation of \[FROM .*date_nanos.*date_nanos_union_types.*\] failed, treating result as null. Only first 20 failures recorded.
2091+
warningRegex:java.lang.IllegalArgumentException: milliSeconds \[-1457696696640\] are before the epoch in 1970 and cannot be converted to nanoseconds
2092+
2093+
millis:date_nanos | nanos:date_nanos | a:datetime | b:date_nanos | c:date_nanos | d:date_nanos | e:datetime
2094+
1999-10-23T12:15:03.360103847Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543Z
2095+
1999-10-23T12:15:03.360Z | 2023-01-23T13:55:01.543123456Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543123456Z | 2023-01-23T13:55:01.543123456Z | 2023-01-23T13:55:01.543Z | 2023-01-23T13:55:01.543Z
2096+
1999-10-23T12:15:03.360103847Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937Z
2097+
1999-10-23T12:15:03.360Z | 2023-02-23T13:33:34.937193Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937193Z | 2023-02-23T13:33:34.937193Z | 2023-02-23T13:33:34.937Z | 2023-02-23T13:33:34.937Z
2098+
1999-10-22T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2099+
1999-10-22T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2100+
1999-10-22T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2101+
1999-10-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2102+
1999-10-22T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2103+
1999-10-22T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2104+
1999-10-22T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2105+
1999-10-23T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360103847Z | 2023-03-23T12:15:03.360Z | 2023-03-23T12:15:03.360Z
2106+
2023-10-23T12:15:03.360103847Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z
2107+
2023-10-23T12:15:03.360103847Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360Z
2108+
;
2109+
2110+
20782111
ImplicitCastingMultiTypedMVFieldsEval
20792112
required_capability: date_nanos_type
20802113
required_capability: implicit_casting_date_and_date_nanos

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

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
173173
new ResolveInference(),
174174
new ResolveLookupTables(),
175175
new ResolveFunctions(),
176-
new ResolveUnionTypesInEsRelation()
176+
new DateMillisToNanosInEsRelation()
177177
),
178178
new Batch<>(
179179
"Resolution",
@@ -1665,9 +1665,7 @@ private Expression resolveConvertFunction(ConvertFunction convert, List<FieldAtt
16651665
}
16661666
imf.types().forEach(type -> {
16671667
if (supportedTypes.contains(type.widenSmallNumeric())) {
1668-
TypeResolutionKey key = new TypeResolutionKey(fa.name(), type);
1669-
var concreteConvert = typeSpecificConvert(convert, fa.source(), type, imf);
1670-
typeResolutions.put(key, concreteConvert);
1668+
typeResolutions(fa, convert, type, imf, typeResolutions);
16711669
}
16721670
});
16731671
// If all mapped types were resolved, create a new FieldAttribute with the resolved MultiTypeEsField
@@ -1676,53 +1674,36 @@ private Expression resolveConvertFunction(ConvertFunction convert, List<FieldAtt
16761674
return createIfDoesNotAlreadyExist(fa, resolvedField, unionFieldAttributes);
16771675
}
16781676
} else if (convert.field() instanceof FieldAttribute fa
1679-
&& fa.synthetic() == false // MultiTypeEsField in EsRelation created by ResolveUnionTypesInEsRelation has synthetic = false
1677+
&& fa.synthetic() == false // MultiTypeEsField in EsRelation created by DateMillisToNanosInEsRelation has synthetic = false
16801678
&& fa.field() instanceof MultiTypeEsField mtf) {
16811679
// This is an explicit casting of a union typed field that has been converted to MultiTypeEsField in EsRelation by
16821680
// ResolveUnionTypesInEsRelation, it is not necessary to cast it into date_nanos and then do explicit casting.
16831681
if (((Expression) convert).dataType() == mtf.getDataType()) {
1684-
// The same data type between implicit(date_nanos) and explicit casting, explicit conversion is not needed
1685-
return convert.field();
1686-
} else {
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?
1689-
HashMap<TypeResolutionKey, Expression> typeResolutions = new HashMap<>();
1690-
Set<DataType> supportedTypes = convert.supportedTypes();
1691-
Holder<Boolean> conversionSupported = new Holder<>(true);
1692-
// Get the type of each field in the multi typed field and check if the explicit conversion is supported
1693-
mtf.getIndexToConversionExpressions().forEach((indexName, conversionExpression) -> {
1694-
AbstractConvertFunction c = (AbstractConvertFunction) conversionExpression;
1695-
FieldAttribute fieldAttribute = (FieldAttribute) c.field();
1696-
DataType type = fieldAttribute.dataType();
1697-
if (supportedTypes.contains(type.widenSmallNumeric())) {
1698-
TypeResolutionKey key = new TypeResolutionKey(fa.name(), type);
1699-
var concreteConvert = typeSpecificConvert(convert, fa.source(), fieldAttribute.field());
1700-
typeResolutions.putIfAbsent(key, concreteConvert);
1701-
} else {
1702-
conversionSupported.set(false);
1703-
}
1704-
});
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.
1707-
if (conversionSupported.get()) {
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,
1710-
Map<String, Expression> indexToConversionExpressions = new HashMap<>();
1711-
for (Map.Entry<String, Expression> entry : mtf.getIndexToConversionExpressions().entrySet()) {
1712-
String indexName = entry.getKey();
1713-
AbstractConvertFunction originalConversionFunction = (AbstractConvertFunction) entry.getValue();
1714-
TypeResolutionKey key = new TypeResolutionKey(fa.name(), originalConversionFunction.field().dataType());
1715-
Expression newConversionFunction = typeResolutions.get(key);
1716-
indexToConversionExpressions.put(indexName, newConversionFunction);
1717-
}
1718-
MultiTypeEsField multiTypeEsField = new MultiTypeEsField(
1719-
fa.name(),
1720-
((Expression) convert).dataType(),
1721-
false,
1722-
indexToConversionExpressions
1723-
);
1724-
return createIfDoesNotAlreadyExist(fa, multiTypeEsField, unionFieldAttributes);
1682+
// The same data type between implicit(date_nanos) and explicit casting, explicit conversion is not needed, mark is
1683+
// as synthetic = true as it is an explicit conversion
1684+
return createIfDoesNotAlreadyExist(fa, mtf, unionFieldAttributes);
1685+
}
1686+
1687+
// Data type is different between implicit(date_nanos) and explicit casting, if the conversion is supported, create a
1688+
// new MultiTypeEsField with explicit casting type, and add it to unionFieldAttributes.
1689+
Set<DataType> supportedTypes = convert.supportedTypes();
1690+
if (supportedTypes.contains(fa.dataType())) {
1691+
// Build the mapping between index name and conversion expressions
1692+
Map<String, Expression> indexToConversionExpressions = new HashMap<>();
1693+
for (Map.Entry<String, Expression> entry : mtf.getIndexToConversionExpressions().entrySet()) {
1694+
String indexName = entry.getKey();
1695+
AbstractConvertFunction originalConversionFunction = (AbstractConvertFunction) entry.getValue();
1696+
Expression originalField = originalConversionFunction.field();
1697+
Expression newConvertFunction = convertExpression.replaceChildren(Collections.singletonList(originalField));
1698+
indexToConversionExpressions.put(indexName, newConvertFunction);
17251699
}
1700+
MultiTypeEsField multiTypeEsField = new MultiTypeEsField(
1701+
fa.name(),
1702+
convertExpression.dataType(),
1703+
false,
1704+
indexToConversionExpressions
1705+
);
1706+
return createIfDoesNotAlreadyExist(fa, multiTypeEsField, unionFieldAttributes);
17261707
}
17271708
} else if (convert.field() instanceof AbstractConvertFunction subConvert) {
17281709
return convertExpression.replaceChildren(
@@ -1852,7 +1833,7 @@ private static LogicalPlan planWithoutSyntheticAttributes(LogicalPlan plan) {
18521833
/**
18531834
* Cast the union typed fields in EsRelation to date_nanos if they are mixed date and date_nanos types.
18541835
*/
1855-
private static class ResolveUnionTypesInEsRelation extends Rule<LogicalPlan, LogicalPlan> {
1836+
private static class DateMillisToNanosInEsRelation extends Rule<LogicalPlan, LogicalPlan> {
18561837
@Override
18571838
public LogicalPlan apply(LogicalPlan plan) {
18581839
return plan.transformUp(EsRelation.class, relation -> {
@@ -1863,11 +1844,7 @@ public LogicalPlan apply(LogicalPlan plan) {
18631844
if (f.field() instanceof InvalidMappedField imf && imf.types().stream().allMatch(DataType::isDate)) {
18641845
HashMap<ResolveUnionTypes.TypeResolutionKey, Expression> typeResolutions = new HashMap<>();
18651846
var convert = new ToDateNanos(f.source(), f);
1866-
imf.types().forEach(type -> {
1867-
ResolveUnionTypes.TypeResolutionKey key = new ResolveUnionTypes.TypeResolutionKey(f.name(), type);
1868-
var concreteConvert = ResolveUnionTypes.typeSpecificConvert(convert, f.source(), type, imf);
1869-
typeResolutions.put(key, concreteConvert);
1870-
});
1847+
imf.types().forEach(type -> typeResolutions(f, convert, type, imf, typeResolutions));
18711848
var resolvedField = ResolveUnionTypes.resolvedMultiTypeEsField(f, typeResolutions);
18721849
return new FieldAttribute(f.source(), f.parentName(), f.name(), resolvedField, f.nullable(), f.id(), f.synthetic());
18731850
}
@@ -1876,4 +1853,16 @@ public LogicalPlan apply(LogicalPlan plan) {
18761853
});
18771854
}
18781855
}
1856+
1857+
private static void typeResolutions(
1858+
FieldAttribute fieldAttribute,
1859+
ConvertFunction convert,
1860+
DataType type,
1861+
InvalidMappedField imf,
1862+
HashMap<ResolveUnionTypes.TypeResolutionKey, Expression> typeResolutions
1863+
) {
1864+
ResolveUnionTypes.TypeResolutionKey key = new ResolveUnionTypes.TypeResolutionKey(fieldAttribute.name(), type);
1865+
var concreteConvert = ResolveUnionTypes.typeSpecificConvert(convert, fieldAttribute.source(), type, imf);
1866+
typeResolutions.put(key, concreteConvert);
1867+
}
18791868
}

0 commit comments

Comments
 (0)