diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java index 81739536c657..3c096d10f7a8 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java @@ -29,7 +29,6 @@ import java.util.function.Function; import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toUnmodifiableMap; import static org.elasticsearch.xpack.esql.core.util.PlanStreamInput.readCachedStringWithVersionCheck; import static org.elasticsearch.xpack.esql.core.util.PlanStreamOutput.writeCachedStringWithVersionCheck; @@ -276,7 +275,7 @@ public enum DataType { private static final Collection STRING_TYPES = DataType.types().stream().filter(DataType::isString).toList(); - private static final Map NAME_TO_TYPE = TYPES.stream().collect(toUnmodifiableMap(DataType::typeName, t -> t)); + private static final Map NAME_TO_TYPE; private static final Map ES_TO_TYPE; @@ -287,6 +286,10 @@ public enum DataType { map.put("point", DataType.CARTESIAN_POINT); map.put("shape", DataType.CARTESIAN_SHAPE); ES_TO_TYPE = Collections.unmodifiableMap(map); + // DATETIME has different esType and typeName, add an entry in NAME_TO_TYPE with date as key + map = TYPES.stream().collect(toMap(DataType::typeName, t -> t)); + map.put("date", DataType.DATETIME); + NAME_TO_TYPE = Collections.unmodifiableMap(map); } private static final Map NAME_OR_ALIAS_TO_TYPE; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec index 9a991bb4d90b..7e7c561fac3a 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec @@ -1380,160 +1380,6 @@ a:integer | year_hired:date 1 | 1991-01-01T00:00:00.000Z ; -implicitCastingToDatePeriodPlusDate -required_capability: implicit_casting_string_literal_to_temporal_amount - -ROW x = "2024-01-01"::datetime -| EVAL y = x + "3 DAYS", - z = x - "3 days", - u = "3 DAYS" + x, - v = - "3 DAYS" + x -; - -x:datetime |y:datetime |z:datetime |u:datetime |v:datetime -2024-01-01 |2024-01-04 |2023-12-29 |2024-01-04 |2023-12-29 -; - -implicitCastingToDatePeriodPlusDateInString -required_capability: implicit_casting_string_literal_to_temporal_amount - -ROW x = "2024-01-01"::datetime -| EVAL y = "2024-01-01" + "3 DAYS", - z = "2024-01-01" - "3 days", - u = "3 DAYS" + "2024-01-01", - v = - "3 days" + "2024-01-01" -; - -x:datetime |y:datetime |z:datetime |u:datetime |v:datetime -2024-01-01 |2024-01-04 |2023-12-29 |2024-01-04 |2023-12-29 -; - -implicitCastingToTimeDurationPlusDate -required_capability: implicit_casting_string_literal_to_temporal_amount - -ROW x = "2024-01-01"::datetime -| EVAL y = x + "3 hours", - z = x - "3 hours", - u = "3 hours" + x, - v = - "3 hours" + x -; - -x:datetime |y:datetime |z:datetime |u:datetime |v:datetime -2024-01-01 |2024-01-01T03:00:00.000Z |2023-12-31T21:00:00.000Z |2024-01-01T03:00:00.000Z |2023-12-31T21:00:00.000Z -; - -implicitCastingToTimeDurationPlusDateInString -required_capability: implicit_casting_string_literal_to_temporal_amount - -ROW x = "2024-01-01"::datetime -| EVAL y = "2024-01-01" + "3 hours", - z = "2024-01-01" - "3 hours", - u = "3 hours" + "2024-01-01", - v = - "3 hours" + "2024-01-01" -; - -x:datetime |y:datetime |z:datetime |u:datetime |v:datetime -2024-01-01 |2024-01-01T03:00:00.000Z |2023-12-31T21:00:00.000Z |2024-01-01T03:00:00.000Z |2023-12-31T21:00:00.000Z -; - -implicitCastingToDatePeriodFromIndex -required_capability: implicit_casting_string_literal_to_temporal_amount - -FROM employees -| WHERE emp_no == 10001 -| EVAL x = birth_date + "3 days", - y = "3 days" + birth_date, - z = birth_date - "3 days", - u = - "3 days" + birth_date -| KEEP birth_date, x, y, z, u; - -birth_date:datetime |x:datetime |y:datetime |z:datetime |u:datetime -1953-09-02T00:00:00Z |1953-09-05T00:00:00Z |1953-09-05T00:00:00Z |1953-08-30T00:00:00Z |1953-08-30T00:00:00Z -; - -implicitCastingToTimeDurationFromIndex -required_capability: implicit_casting_string_literal_to_temporal_amount - -FROM employees -| WHERE emp_no == 10001 -| EVAL x = birth_date + "3 hours", - y = "3 hours" + birth_date, - z = birth_date - "3 hours", - u = - "3 hours" + birth_date -| KEEP birth_date, x, y, z, u; - -birth_date:datetime |x:datetime |y:datetime |z:datetime |u:datetime -1953-09-02T00:00:00Z |1953-09-02T03:00:00Z |1953-09-02T03:00:00Z |1953-09-01T21:00:00Z |1953-09-01T21:00:00Z -; - - -implicitCastingArithmeticOperationAddTemporalAmountInString -required_capability: implicit_casting_string_literal_to_temporal_amount - -FROM employees -| EVAL a = "1 day" + "2024-01-01", - b = "1 year" + "2024-04-01" + "1 month", - c = "2024-01-01" + "3600 seconds", - d = "2024-04-01" + ("1 year" + "1 day") -| KEEP a, b, c, d -| LIMIT 1 -; - -a:datetime | b:datetime | c:datetime | d:datetime -2024-01-02 | 2025-05-01 | 2024-01-01T01:00:00.000Z | 2025-04-02 -; - -implicitCastingArithmeticOperationSubTemporalAmountInString -required_capability: implicit_casting_string_literal_to_temporal_amount - -FROM employees -| EVAL a = "2024-01-01" - "1 day", - b = "2024-04-01" - "1 month", - c = "2024-01-01" - "3600 seconds", - d = "2024-04-01" - ("1 year" + "1 day") -| KEEP a, b, c, d -| LIMIT 1 -; - -a:datetime | b:datetime | c:datetime | d:datetime -2023-12-31 | 2024-03-01 | 2023-12-31T23:00:00.000Z | 2023-03-31 -; - -implicitCastingArithmeticOperationAddSubTemporalAmountInString -required_capability: implicit_casting_string_literal_to_temporal_amount - -FROM employees -| EVAL a = "1 month" + "2024-01-01" - "1 day", - b = - "1 year" + "2024-04-01" + "1 month", - c = "1 hour" + "2024-01-01" - "3600 seconds", - d = "2024-04-01" - ("1 year" + "1 day"), - e = "2024-01-01" + ("4 years" + "3 months") + "2 weeks" + "1 day", - f = "4 years" + ("3 months" + "2 weeks") + "1 day" + "2024-01-01", - g = "2024-01-01" + (-("4 years" + "3 months" + "2 weeks" + "1 day")), - h = "2024-01-01" - "4 years" - "3 months" - "2 weeks" - "1 day", - i = - "4 years" - "3 months" - "2 weeks" - "1 day" + "2024-01-01", - j = "2024-01-01" - (- "4 years" - "3 months" - "2 weeks" - "1 day"), - k = "2024-01-01" + "1 hour" + "1 minute" + "1 second" + "1 milliseconds" -| KEEP a, b, c, d, e, f, g, h, i, j, k -| LIMIT 1 -; - -a:datetime |b:datetime |c:datetime |d:datetime |e:datetime |f:datetime |g:datetime |h:datetime |i:datetime |j:datetime |k:datetime -2024-01-31 |2023-05-01 |2024-01-01 |2023-03-31 |2028-04-16 |2028-04-16 |2019-09-16 |2019-09-16 |2019-09-16 |2028-04-16 |2024-01-01T01:01:01.001Z -; - -implicitCastingTemporalAmountInStringWithNulls -required_capability: implicit_casting_string_literal_to_temporal_amount - -FROM employees -| EVAL a = to_dt(null) - "1 day", b = "2024-01-01" + null + "1 day", c = null + "1 day" + "2024-01-01" -| KEEP a, b, c -| LIMIT 1; - -a:datetime |b:datetime |c:datetime -null |null |null -; - filteringWithTemporalAmountInString required_capability: implicit_casting_string_literal_to_temporal_amount diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 3cffa9ead8cd..519ea11ad80f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -85,6 +85,8 @@ 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; @@ -120,7 +122,7 @@ 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.parseTemporalAmount; +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. @@ -145,8 +147,8 @@ public class Analyzer extends ParameterizedRuleExecutor( "Resolution", /* - * Move ImplicitCasting before ResolveRefs. Because a ref is created for a Bucket in Aggregate's aggregates, - * resolving this ref before implicit casting may cause this ref to have customMessage=true, it prevents further + * 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(), @@ -1055,33 +1057,33 @@ private static Expression processBinaryOperator(BinaryOperator o) { } List newChildren = new ArrayList<>(2); boolean childrenChanged = false; - String errorMessage = "Cannot convert string [{}] to any of [" + DATETIME + ", " + DATE_PERIOD + ", " + TIME_DURATION + "]"; - - if (o instanceof DateTimeArithmeticOperation) { - childrenChanged = processDateTimeArithmeticOperation(left, right, newChildren, errorMessage); - } else { - DataType targetDataType = DataType.NULL; - Expression from = Literal.NULL; - - if (left.dataType() == KEYWORD && left.foldable() && (left instanceof EsqlScalarFunction == false)) { - if (supportsStringImplicitCasting(right.dataType())) { - targetDataType = right.dataType(); - from = left; - } - } - if (right.dataType() == KEYWORD && right.foldable() && (right instanceof EsqlScalarFunction == false)) { - if (supportsStringImplicitCasting(left.dataType())) { - targetDataType = left.dataType(); - from = right; - } + DataType targetDataType = DataType.NULL; + Expression from = Literal.NULL; + + if (left.dataType() == KEYWORD && left.foldable() && (left instanceof EsqlScalarFunction == false)) { + if (supportsStringImplicitCasting(right.dataType())) { + targetDataType = right.dataType(); + from = left; + } else if (supportsImplicitTemporalCasting(right, o)) { + targetDataType = DATETIME; + from = left; } - if (from != Literal.NULL) { - Expression e = castStringLiteral(from, targetDataType); - newChildren.add(from == left ? e : left); - newChildren.add(from == right ? e : right); - childrenChanged = true; + } + if (right.dataType() == KEYWORD && right.foldable() && (right instanceof EsqlScalarFunction == false)) { + if (supportsStringImplicitCasting(left.dataType())) { + targetDataType = left.dataType(); + from = right; + } else if (supportsImplicitTemporalCasting(left, o)) { + targetDataType = DATETIME; + from = right; } } + if (from != Literal.NULL) { + Expression e = castStringLiteral(from, targetDataType); + newChildren.add(from == left ? e : left); + newChildren.add(from == right ? e : right); + childrenChanged = true; + } return childrenChanged ? o.replaceChildren(newChildren) : o; } @@ -1110,48 +1112,6 @@ private static Expression processIn(In in) { return childrenChanged ? in.replaceChildren(newChildren) : in; } - private static boolean processDateTimeArithmeticOperation( - Expression left, - Expression right, - List newChildren, - String errorMessage - ) { - boolean childrenChanged = false; - Expression newLeft = left; - Expression newRight = right; - if (isStringLiteral(left)) { - newLeft = castToTemporalOrDateTime(left, errorMessage); - } - if (isStringLiteral(right)) { - newRight = castToTemporalOrDateTime(right, errorMessage); - } - if (newLeft != left || newRight != right) { - newChildren.add(newLeft); - newChildren.add(newRight); - childrenChanged = true; - } - return childrenChanged; - } - - private static boolean isStringLiteral(Expression e) { - return e instanceof Literal l && l.dataType() == KEYWORD; - } - - private static Expression castToTemporalOrDateTime(Expression e, String errorMessage) { - Expression result = castStringLiteralToTemporalAmount(e); // try casting it to temporal first - if (result instanceof Literal nr && isTemporalAmount(nr.dataType())) { - return result; - } else { - result = castStringLiteral(e, DATETIME); // rhs is not a temporal, try casting it to datetime - if (result instanceof Literal nr && nr.dataType() == DATETIME) { - return result; - } else if (result instanceof UnresolvedAttribute ua) { - return ua.withUnresolvedMessage(format(errorMessage, e.fold())); - } - } - return e; - } - private static boolean canCastMixedNumericTypes(org.elasticsearch.xpack.esql.core.expression.function.Function f) { return f instanceof Coalesce; } @@ -1191,8 +1151,12 @@ private static Expression castMixedNumericTypes(EsqlScalarFunction f, DataType t return childrenChanged ? f.replaceChildren(newChildren) : f; } + private static boolean supportsImplicitTemporalCasting(Expression e, BinaryOperator o) { + return isTemporalAmount(e.dataType()) && (o instanceof DateTimeArithmeticOperation); + } + private static boolean supportsStringImplicitCasting(DataType type) { - return type == DATETIME || type == IP || type == VERSION || type == BOOLEAN || isTemporalAmount(type); + return type == DATETIME || type == IP || type == VERSION || type == BOOLEAN; } private static UnresolvedAttribute unresolvedAttribute(Expression value, String type, Exception e) { @@ -1207,8 +1171,12 @@ private static UnresolvedAttribute unresolvedAttribute(Expression value, String private static Expression castStringLiteralToTemporalAmount(Expression from) { try { - Literal result = parseTemporalAmount(from); - return result == null ? from : result; + 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); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java index 2cccc3df096b..12c2741e7ec2 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java @@ -163,20 +163,18 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; 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.UNSIGNED_LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; 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.core.type.DataType.isString; public class EsqlFunctionRegistry { - private static final Map, List> dataTypesForStringLiteralConversion = new LinkedHashMap<>(); + private static final Map, List> DATA_TYPES_FOR_STRING_LITERAL_CONVERSIONS = new LinkedHashMap<>(); - private static final Map dataTypeCastingPriority; + private static final Map DATA_TYPE_CASTING_PRIORITY; static { List typePriorityList = Arrays.asList( @@ -196,9 +194,9 @@ public class EsqlFunctionRegistry { UNSIGNED_LONG, UNSUPPORTED ); - dataTypeCastingPriority = new HashMap<>(); + DATA_TYPE_CASTING_PRIORITY = new HashMap<>(); for (int i = 0; i < typePriorityList.size(); i++) { - dataTypeCastingPriority.put(typePriorityList.get(i), i); + DATA_TYPE_CASTING_PRIORITY.put(typePriorityList.get(i), i); } } @@ -474,22 +472,24 @@ public List argDescriptions() { } } + /** + * This is called by ImplicitCasting to convert string literals to a target data type. + */ public static DataType getTargetType(String[] names) { List types = new ArrayList<>(); for (String name : names) { DataType type = DataType.fromTypeName(name); - if (isTemporalAmount(type)) { // DATE_PERIOD and TIME_DURATION are not ES types + if (type != null && type != UNSUPPORTED) { // A type should not be null or UNSUPPORTED, just a sanity check here + // If the function takes strings as input, there is no need to cast a string literal to it. + // Return UNSUPPORTED, so that ImplicitCasting doesn't process it. + if (isString(type)) { + return UNSUPPORTED; + } types.add(type); - } else { - types.add(DataType.fromEs(name)); } } - if (types.contains(KEYWORD) || types.contains(TEXT)) { - return UNSUPPORTED; - } - return types.stream() - .min((dt1, dt2) -> dataTypeCastingPriority.get(dt1).compareTo(dataTypeCastingPriority.get(dt2))) + .min((dt1, dt2) -> DATA_TYPE_CASTING_PRIORITY.get(dt1).compareTo(DATA_TYPE_CASTING_PRIORITY.get(dt2))) .orElse(UNSUPPORTED); } @@ -561,7 +561,7 @@ private void buildDataTypesForStringLiteralConversion(FunctionDefinition[]... gr for (FunctionDefinition[] group : groupFunctions) { for (FunctionDefinition def : group) { FunctionDescription signature = description(def); - dataTypesForStringLiteralConversion.put( + DATA_TYPES_FOR_STRING_LITERAL_CONVERSIONS.put( def.clazz(), signature.args().stream().map(EsqlFunctionRegistry.ArgSignature::targetDataType).collect(Collectors.toList()) ); @@ -570,26 +570,7 @@ private void buildDataTypesForStringLiteralConversion(FunctionDefinition[]... gr } public List getDataTypeForStringLiteralConversion(Class clazz) { - if (dataTypesForStringLiteralConversion.containsKey(clazz)) { - return dataTypesForStringLiteralConversion.get(clazz); - } else { // for unregistered EsqlScalarFunction, like Neg - Constructor constructor = constructorFor(clazz); - if (constructor == null) { - return null; - } - var params = constructor.getParameters(); - List targetDataTypes = new ArrayList<>(params.length - 1); - for (int i = 1; i < params.length; i++) { // skipping 1st argument, the source - if (Configuration.class.isAssignableFrom(params[i].getType()) == false) { - Param paramInfo = params[i].getAnnotation(Param.class); - if (paramInfo != null) { - DataType targetDataType = EsqlFunctionRegistry.getTargetType(paramInfo.type()); - targetDataTypes.add(targetDataType); - } - } - } - return targetDataTypes.size() == params.length - 1 ? targetDataTypes : null; - } + return DATA_TYPES_FOR_STRING_LITERAL_CONVERSIONS.get(clazz); } private static class SnapshotFunctionRegistry extends EsqlFunctionRegistry { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java index 33a0a42b70fc..a5edc38e2eae 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java @@ -18,7 +18,6 @@ import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expressions; -import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.Converter; import org.elasticsearch.xpack.esql.core.type.DataType; @@ -278,7 +277,7 @@ public static TemporalAmount parseTemporalAmount(Object val, DataType expectedTy } StringBuilder value = new StringBuilder(); StringBuilder qualifier = new StringBuilder(); - parseTemporalAmount(str, value, qualifier, errorMessage, expectedType.toString()); + separateValueAndQualifierForTemporalAmount(str.strip(), value, qualifier, errorMessage, expectedType.toString()); if ((value.isEmpty() || qualifier.isEmpty()) == false) { try { TemporalAmount result = parseTemporalAmount(Integer.parseInt(value.toString()), qualifier.toString(), Source.EMPTY); @@ -299,22 +298,16 @@ public static TemporalAmount parseTemporalAmount(Object val, DataType expectedTy throw new ParsingException(Source.EMPTY, errorMessage, val, expectedType); } - public static Literal parseTemporalAmount(Expression e) { + public static TemporalAmount maybeParseTemporalAmount(String str) { // The string literal can be either Date_Period or Time_Duration, derive the data type from its qualifier - if (e.fold() == null) { - return null; - } String errorMessage = "Cannot parse [{}] to {}"; String expectedTypes = DATE_PERIOD + " or " + TIME_DURATION; - String str = e.fold().toString().strip(); StringBuilder value = new StringBuilder(); StringBuilder qualifier = new StringBuilder(); - parseTemporalAmount(str, value, qualifier, errorMessage, expectedTypes); + separateValueAndQualifierForTemporalAmount(str, value, qualifier, errorMessage, expectedTypes); if ((value.isEmpty() || qualifier.isEmpty()) == false) { try { - TemporalAmount result = parseTemporalAmount(Integer.parseInt(value.toString()), qualifier.toString(), Source.EMPTY); - DataType target = result instanceof Duration ? TIME_DURATION : DATE_PERIOD; - return new Literal(e.source(), result, target); + return parseTemporalAmount(Integer.parseInt(value.toString()), qualifier.toString(), Source.EMPTY); } catch (NumberFormatException ex) { throw new ParsingException(Source.EMPTY, errorMessage, str, expectedTypes); } @@ -322,7 +315,7 @@ public static Literal parseTemporalAmount(Expression e) { return null; } - public static void parseTemporalAmount( + private static void separateValueAndQualifierForTemporalAmount( String temporalAmount, StringBuilder value, StringBuilder qualifier, @@ -331,7 +324,7 @@ public static void parseTemporalAmount( ) { StringBuilder nextBuffer = value; boolean lastWasSpace = false; - for (char c : temporalAmount.trim().toCharArray()) { + for (char c : temporalAmount.toCharArray()) { if (c == ' ') { if (lastWasSpace == false) { nextBuffer = nextBuffer == value ? qualifier : null; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index d26e6c0a230d..89e8bf8b8e17 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -807,13 +807,6 @@ public void testSubtractDateTimeFromTemporal() { + "\")]", error("row to_timeduration(\"1 " + unit + "\") - now() ") ); - assertEquals( - "1:5: [-] arguments are in unsupported order: cannot subtract a [DATETIME] value [now()] " - + "from a [TIME_DURATION] amount [\"1 " - + unit - + "\"]", - error("row \"1 " + unit + "\" - now() ") - ); } for (var unit : DATE_PERIODS) { assertEquals( @@ -851,13 +844,6 @@ public void testSubtractDateTimeFromTemporal() { + "\")]", error("row to_dateperiod(\"1 " + unit + "\") - now() ") ); - assertEquals( - "1:5: [-] arguments are in unsupported order: cannot subtract a [DATETIME] value [now()] " - + "from a [DATE_PERIOD] amount [\"1 " - + unit - + "\"]", - error("row \"1 " + unit + "\" - now() ") - ); } } @@ -1480,11 +1466,6 @@ public void testToDatePeriodTimeDurationInInvalidPosition() { error("row x = \"2024-01-01\"::datetime | eval y = \"3 months\"::date_period + \"5 days\"::date_period") ); - assertEquals( - "1:39: EVAL does not support type [date_period] as the return data type of expression " + "[\"3 months\" + \"5 days\"]", - error("row x = \"2024-01-01\"::datetime | eval y = \"3 months\" + \"5 days\"") - ); - assertEquals( "1:39: EVAL does not support type [time_duration] as the return data type of expression [3 hours + 5 minutes]", error("row x = \"2024-01-01\"::datetime | eval y = 3 hours + 5 minutes") @@ -1496,11 +1477,6 @@ public void testToDatePeriodTimeDurationInInvalidPosition() { error("row x = \"2024-01-01\"::datetime | eval y = \"3 hours\"::time_duration + \"5 minutes\"::time_duration") ); - assertEquals( - "1:39: EVAL does not support type [time_duration] as the return data type of expression " + "[\"3 hours\" + \"5 minutes\"]", - error("row x = \"2024-01-01\"::datetime | eval y = \"3 hours\" + \"5 minutes\"") - ); - // where assertEquals( "1:26: first argument of [\"3 days\"::date_period == to_dateperiod(\"3 days\")] must be " @@ -1629,40 +1605,6 @@ public void testIntervalAsString() { + "found value [\"1\"] type [keyword]", error("from test | eval x = emp_no, y = hire_date | stats max = max(x) by bucket(y, \"1\") | sort max ") ); - - // Add, Subtract, Neg - assertEquals( - "1:22: Cannot convert string [1 dy] to any of [DATETIME, DATE_PERIOD, TIME_DURATION]", - error("from test | eval x = \"1 dy\" + \"2024-01-01\" - \"1 yare\"") - ); - assertEquals( - "1:37: Cannot convert string [1 dy] to any of [DATETIME, DATE_PERIOD, TIME_DURATION]", - error("from test | eval x = \"2024-01-01\" + \"1 dy\" - \"1 yare\"") - ); - assertEquals( - "1:37: Cannot convert string [1 dy] to any of [DATETIME, DATE_PERIOD, TIME_DURATION]", - error("from test | eval x = \"2024-01-01\" - \"1 dy\" + \"1 yare\"") - ); - assertEquals( - "1:24: Cannot convert string [1 dy] to [DATE_PERIOD or TIME_DURATION], error [Unexpected time interval qualifier: 'dy']", - error("from test | eval x = - \"1 dy\" + \"2024-01-01\" + \"1 yare\"") - ); - assertEquals( - "1:22: Cannot convert string [1] to any of [DATETIME, DATE_PERIOD, TIME_DURATION]", - error("from test | eval x = \"1\" + \"2024-01-01\" - \"10 year\"") - ); - assertEquals( - "1:32: Cannot convert string [5] to any of [DATETIME, DATE_PERIOD, TIME_DURATION]", - error("from test | eval x = \"1 day\" + \"5\" - \"1 year\"") - ); - assertEquals( - "1:22: [+] has arguments with incompatible types [date_period] and [integer]", - error("from test | eval x = \"1 day\" + 5 - \"1 year\"") - ); - assertEquals( - "1:22: argument of [- \"1\"] must be [numeric, date_period or time_duration], found value [\"1\"] type [keyword]", - error("from test | eval x = - \"1\" + \"2024-01-01\" + \"1 year\"") - ); } private void query(String query) { diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/10_basic.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/10_basic.yml index 8d56ac3efdca..96145e84ad2c 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/10_basic.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/10_basic.yml @@ -422,35 +422,6 @@ setup: - length: {values: 1} - match: {values.0: ["2024-08-06T00:00:00.000Z","2024-08-03T00:00:00.000Z","2024-08-06T03:00:00.000Z"]} ---- -"Test Interval as String in Input Params": - - requires: - test_runner_features: [ capabilities ] - capabilities: - - method: POST - path: /_query - parameters: [ ] - capabilities: [ implicit_casting_string_literal_to_temporal_amount ] - reason: "interval in parameters" - - - do: - allowed_warnings_regex: - - "No limit defined, adding default limit of \\[.*\\]" - esql.query: - body: - query: 'row x = ?n1::datetime | eval y = x - ?n2, z = ?n1 + ?n3' - params: [{"n1" : "2024-08-06"}, {"n2" : "3 days"}, {"n3" : "3 hours"}] - - - length: {columns: 3} - - match: {columns.0.name: "x"} - - match: {columns.0.type: "date"} - - match: {columns.1.name: "y"} - - match: {columns.1.type: "date"} - - match: {columns.2.name: "z"} - - match: {columns.2.type: "date"} - - length: {values: 1} - - match: {values.0: ["2024-08-06T00:00:00.000Z","2024-08-03T00:00:00.000Z","2024-08-06T03:00:00.000Z"]} - --- "Test Named Input Params For Field Names": - requires: