|
@@ -8,7 +8,6 @@
|
|
|
package org.elasticsearch.xpack.esql.analysis;
|
|
|
|
|
|
import org.elasticsearch.common.logging.HeaderWarning;
|
|
|
-import org.elasticsearch.common.logging.LoggerMessageFormat;
|
|
|
import org.elasticsearch.compute.data.Block;
|
|
|
import org.elasticsearch.logging.Logger;
|
|
|
import org.elasticsearch.xpack.core.enrich.EnrichPolicy;
|
|
@@ -31,7 +30,6 @@ import org.elasticsearch.xpack.esql.core.expression.Nullability;
|
|
|
import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute;
|
|
|
import org.elasticsearch.xpack.esql.core.expression.UnresolvedAttribute;
|
|
|
import org.elasticsearch.xpack.esql.core.expression.UnresolvedStar;
|
|
|
-import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction;
|
|
|
import org.elasticsearch.xpack.esql.core.expression.predicate.BinaryOperator;
|
|
|
import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison;
|
|
|
import org.elasticsearch.xpack.esql.core.tree.Source;
|
|
@@ -49,6 +47,7 @@ import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
|
|
|
import org.elasticsearch.xpack.esql.expression.function.FunctionDefinition;
|
|
|
import org.elasticsearch.xpack.esql.expression.function.UnresolvedFunction;
|
|
|
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
|
|
|
+import org.elasticsearch.xpack.esql.expression.function.grouping.GroupingFunction;
|
|
|
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
|
|
|
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
|
|
|
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FoldablesConvertFunction;
|
|
@@ -61,6 +60,7 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Dat
|
|
|
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.EsqlArithmeticOperation;
|
|
|
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.In;
|
|
|
import org.elasticsearch.xpack.esql.index.EsIndex;
|
|
|
+import org.elasticsearch.xpack.esql.parser.ParsingException;
|
|
|
import org.elasticsearch.xpack.esql.plan.TableIdentifier;
|
|
|
import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
|
|
|
import org.elasticsearch.xpack.esql.plan.logical.Drop;
|
|
@@ -86,6 +86,8 @@ import org.elasticsearch.xpack.esql.session.Configuration;
|
|
|
import org.elasticsearch.xpack.esql.stats.FeatureMetric;
|
|
|
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
|
|
|
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.temporal.TemporalAmount;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.BitSet;
|
|
@@ -107,6 +109,7 @@ import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
|
|
|
import static org.elasticsearch.xpack.core.enrich.EnrichPolicy.GEO_MATCH_TYPE;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME;
|
|
|
+import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_PERIOD;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.FLOAT;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT;
|
|
@@ -116,9 +119,11 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.IP;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.LONG;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT;
|
|
|
+import static org.elasticsearch.xpack.esql.core.type.DataType.TIME_DURATION;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.VERSION;
|
|
|
import static org.elasticsearch.xpack.esql.core.type.DataType.isTemporalAmount;
|
|
|
import static org.elasticsearch.xpack.esql.stats.FeatureMetric.LIMIT;
|
|
|
+import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.maybeParseTemporalAmount;
|
|
|
|
|
|
/**
|
|
|
* This class is part of the planner. Resolves references (such as variable and index names) and performs implicit casting.
|
|
@@ -142,9 +147,14 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
);
|
|
|
var resolution = new Batch<>(
|
|
|
"Resolution",
|
|
|
+ /*
|
|
|
+ * ImplicitCasting must be before ResolveRefs. Because a reference is created for a Bucket in Aggregate's aggregates,
|
|
|
+ * resolving this reference before implicit casting may cause this reference to have customMessage=true, it prevents further
|
|
|
+ * attempts to resolve this reference.
|
|
|
+ */
|
|
|
+ new ImplicitCasting(),
|
|
|
new ResolveRefs(),
|
|
|
- new ResolveUnionTypes(), // Must be after ResolveRefs, so union types can be found
|
|
|
- new ImplicitCasting()
|
|
|
+ new ResolveUnionTypes() // Must be after ResolveRefs, so union types can be found
|
|
|
);
|
|
|
var finish = new Batch<>("Finish Analysis", Limiter.ONCE, new AddImplicitLimit(), new UnionTypesCleanup());
|
|
|
rules = List.of(init, resolution, finish);
|
|
@@ -952,13 +962,15 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Cast string literals in ScalarFunction, EsqlArithmeticOperation, BinaryComparison and In to desired data types.
|
|
|
+ * Cast string literals in ScalarFunction, EsqlArithmeticOperation, BinaryComparison, In and GroupingFunction to desired data types.
|
|
|
* For example, the string literals in the following expressions will be cast implicitly to the field data type on the left hand side.
|
|
|
* date > "2024-08-21"
|
|
|
* date in ("2024-08-21", "2024-08-22", "2024-08-23")
|
|
|
* date = "2024-08-21" + 3 days
|
|
|
* ip == "127.0.0.1"
|
|
|
* version != "1.0"
|
|
|
+ * bucket(dateField, "1 month")
|
|
|
+ * date_trunc("1 minute", dateField)
|
|
|
*
|
|
|
* If the inputs to Coalesce are mixed numeric types, cast the rest of the numeric field or value to the first numeric data type if
|
|
|
* applicable. For example, implicit casting converts:
|
|
@@ -972,15 +984,18 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
private static class ImplicitCasting extends ParameterizedRule<LogicalPlan, LogicalPlan, AnalyzerContext> {
|
|
|
@Override
|
|
|
public LogicalPlan apply(LogicalPlan plan, AnalyzerContext context) {
|
|
|
- return plan.transformExpressionsUp(ScalarFunction.class, e -> ImplicitCasting.cast(e, context.functionRegistry()));
|
|
|
+ return plan.transformExpressionsUp(
|
|
|
+ org.elasticsearch.xpack.esql.core.expression.function.Function.class,
|
|
|
+ e -> ImplicitCasting.cast(e, context.functionRegistry())
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- private static Expression cast(ScalarFunction f, EsqlFunctionRegistry registry) {
|
|
|
+ private static Expression cast(org.elasticsearch.xpack.esql.core.expression.function.Function f, EsqlFunctionRegistry registry) {
|
|
|
if (f instanceof In in) {
|
|
|
return processIn(in);
|
|
|
}
|
|
|
- if (f instanceof EsqlScalarFunction esf) {
|
|
|
- return processScalarFunction(esf, registry);
|
|
|
+ if (f instanceof EsqlScalarFunction || f instanceof GroupingFunction) { // exclude AggregateFunction until it is needed
|
|
|
+ return processScalarOrGroupingFunction(f, registry);
|
|
|
}
|
|
|
if (f instanceof EsqlArithmeticOperation || f instanceof BinaryComparison) {
|
|
|
return processBinaryOperator((BinaryOperator) f);
|
|
@@ -988,7 +1003,10 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
return f;
|
|
|
}
|
|
|
|
|
|
- private static Expression processScalarFunction(EsqlScalarFunction f, EsqlFunctionRegistry registry) {
|
|
|
+ private static Expression processScalarOrGroupingFunction(
|
|
|
+ org.elasticsearch.xpack.esql.core.expression.function.Function f,
|
|
|
+ EsqlFunctionRegistry registry
|
|
|
+ ) {
|
|
|
List<Expression> args = f.arguments();
|
|
|
List<DataType> targetDataTypes = registry.getDataTypeForStringLiteralConversion(f.getClass());
|
|
|
if (targetDataTypes == null || targetDataTypes.isEmpty()) {
|
|
@@ -1011,9 +1029,11 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
}
|
|
|
if (targetDataType != DataType.NULL && targetDataType != DataType.UNSUPPORTED) {
|
|
|
Expression e = castStringLiteral(arg, targetDataType);
|
|
|
- childrenChanged = true;
|
|
|
- newChildren.add(e);
|
|
|
- continue;
|
|
|
+ if (e != arg) {
|
|
|
+ childrenChanged = true;
|
|
|
+ newChildren.add(e);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
} else if (dataType.isNumeric() && canCastMixedNumericTypes(f) && castNumericArgs) {
|
|
@@ -1095,7 +1115,7 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
return childrenChanged ? in.replaceChildren(newChildren) : in;
|
|
|
}
|
|
|
|
|
|
- private static boolean canCastMixedNumericTypes(EsqlScalarFunction f) {
|
|
|
+ private static boolean canCastMixedNumericTypes(org.elasticsearch.xpack.esql.core.expression.function.Function f) {
|
|
|
return f instanceof Coalesce;
|
|
|
}
|
|
|
|
|
@@ -1142,19 +1162,37 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
|
|
|
return type == DATETIME || type == IP || type == VERSION || type == BOOLEAN;
|
|
|
}
|
|
|
|
|
|
- public static Expression castStringLiteral(Expression from, DataType target) {
|
|
|
+ private static UnresolvedAttribute unresolvedAttribute(Expression value, String type, Exception e) {
|
|
|
+ String message = format(
|
|
|
+ "Cannot convert string [{}] to [{}], error [{}]",
|
|
|
+ value.fold(),
|
|
|
+ type,
|
|
|
+ (e instanceof ParsingException pe) ? pe.getErrorMessage() : e.getMessage()
|
|
|
+ );
|
|
|
+ return new UnresolvedAttribute(value.source(), String.valueOf(value.fold()), message);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Expression castStringLiteralToTemporalAmount(Expression from) {
|
|
|
+ try {
|
|
|
+ TemporalAmount result = maybeParseTemporalAmount(from.fold().toString().strip());
|
|
|
+ if (result == null) {
|
|
|
+ return from;
|
|
|
+ }
|
|
|
+ DataType target = result instanceof Duration ? TIME_DURATION : DATE_PERIOD;
|
|
|
+ return new Literal(from.source(), result, target);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return unresolvedAttribute(from, DATE_PERIOD + " or " + TIME_DURATION, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Expression castStringLiteral(Expression from, DataType target) {
|
|
|
assert from.foldable();
|
|
|
try {
|
|
|
- Object to = EsqlDataTypeConverter.convert(from.fold(), target);
|
|
|
- return new Literal(from.source(), to, target);
|
|
|
+ return isTemporalAmount(target)
|
|
|
+ ? castStringLiteralToTemporalAmount(from)
|
|
|
+ : new Literal(from.source(), EsqlDataTypeConverter.convert(from.fold(), target), target);
|
|
|
} catch (Exception e) {
|
|
|
- String message = LoggerMessageFormat.format(
|
|
|
- "Cannot convert string [{}] to [{}], error [{}]",
|
|
|
- from.fold(),
|
|
|
- target,
|
|
|
- e.getMessage()
|
|
|
- );
|
|
|
- return new UnresolvedAttribute(from.source(), String.valueOf(from.fold()), message);
|
|
|
+ return unresolvedAttribute(from, target.toString(), e);
|
|
|
}
|
|
|
}
|
|
|
}
|