|
@@ -7,6 +7,9 @@
|
|
|
|
|
|
package org.elasticsearch.xpack.esql.expression.function;
|
|
|
|
|
|
+import org.apache.lucene.document.InetAddressPoint;
|
|
|
+import org.apache.lucene.util.BytesRef;
|
|
|
+import org.elasticsearch.common.network.InetAddresses;
|
|
|
import org.elasticsearch.test.ESTestCase;
|
|
|
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
|
|
|
import org.elasticsearch.xpack.ql.expression.Expression;
|
|
@@ -15,9 +18,11 @@ import org.elasticsearch.xpack.ql.tree.Source;
|
|
|
import org.elasticsearch.xpack.ql.type.DataType;
|
|
|
import org.elasticsearch.xpack.ql.type.DataTypes;
|
|
|
import org.elasticsearch.xpack.ql.util.NumericUtils;
|
|
|
+import org.elasticsearch.xpack.versionfield.Version;
|
|
|
import org.hamcrest.Matcher;
|
|
|
|
|
|
import java.math.BigInteger;
|
|
|
+import java.time.Instant;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.List;
|
|
@@ -269,6 +274,89 @@ public record TestCaseSupplier(String name, List<DataType> types, Supplier<TestC
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Generate positive test cases for a unary function operating on an {@link DataTypes#BOOLEAN}.
|
|
|
+ */
|
|
|
+ public static void forUnaryBoolean(
|
|
|
+ List<TestCaseSupplier> suppliers,
|
|
|
+ String expectedEvaluatorToString,
|
|
|
+ DataType expectedType,
|
|
|
+ Function<Boolean, Object> expectedValue
|
|
|
+ ) {
|
|
|
+ unary(suppliers, expectedEvaluatorToString, DataTypes.BOOLEAN, booleanCases(), expectedType, v -> expectedValue.apply((Boolean) v));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate positive test cases for a unary function operating on an {@link DataTypes#DATETIME}.
|
|
|
+ */
|
|
|
+ public static void forUnaryDatetime(
|
|
|
+ List<TestCaseSupplier> suppliers,
|
|
|
+ String expectedEvaluatorToString,
|
|
|
+ DataType expectedType,
|
|
|
+ Function<Instant, Object> expectedValue
|
|
|
+ ) {
|
|
|
+ unaryNumeric(
|
|
|
+ suppliers,
|
|
|
+ expectedEvaluatorToString,
|
|
|
+ DataTypes.DATETIME,
|
|
|
+ dateCases(),
|
|
|
+ expectedType,
|
|
|
+ n -> expectedValue.apply(Instant.ofEpochMilli(n.longValue()))
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate positive test cases for a unary function operating on an {@link DataTypes#IP}.
|
|
|
+ */
|
|
|
+ public static void forUnaryIp(
|
|
|
+ List<TestCaseSupplier> suppliers,
|
|
|
+ String expectedEvaluatorToString,
|
|
|
+ DataType expectedType,
|
|
|
+ Function<BytesRef, Object> expectedValue
|
|
|
+ ) {
|
|
|
+ unary(suppliers, expectedEvaluatorToString, DataTypes.IP, ipCases(), expectedType, v -> expectedValue.apply((BytesRef) v));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate positive test cases for a unary function operating on an {@link DataTypes#KEYWORD} and {@link DataTypes#TEXT}.
|
|
|
+ */
|
|
|
+ public static void forUnaryStrings(
|
|
|
+ List<TestCaseSupplier> suppliers,
|
|
|
+ String expectedEvaluatorToString,
|
|
|
+ DataType expectedType,
|
|
|
+ Function<BytesRef, Object> expectedValue
|
|
|
+ ) {
|
|
|
+ for (DataType type : EsqlDataTypes.types().stream().filter(EsqlDataTypes::isString).toList()) {
|
|
|
+ unary(
|
|
|
+ suppliers,
|
|
|
+ expectedEvaluatorToString,
|
|
|
+ type,
|
|
|
+ stringCases(type.typeName()),
|
|
|
+ expectedType,
|
|
|
+ v -> expectedValue.apply((BytesRef) v)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate positive test cases for a unary function operating on an {@link DataTypes#VERSION}.
|
|
|
+ */
|
|
|
+ public static void forUnaryVersion(
|
|
|
+ List<TestCaseSupplier> suppliers,
|
|
|
+ String expectedEvaluatorToString,
|
|
|
+ DataType expectedType,
|
|
|
+ Function<Version, Object> expectedValue
|
|
|
+ ) {
|
|
|
+ unary(
|
|
|
+ suppliers,
|
|
|
+ expectedEvaluatorToString,
|
|
|
+ DataTypes.VERSION,
|
|
|
+ versionCases(""),
|
|
|
+ expectedType,
|
|
|
+ v -> expectedValue.apply(new Version((BytesRef) v))
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
private static void unaryNumeric(
|
|
|
List<TestCaseSupplier> suppliers,
|
|
|
String expectedEvaluatorToString,
|
|
@@ -277,10 +365,21 @@ public record TestCaseSupplier(String name, List<DataType> types, Supplier<TestC
|
|
|
DataType expectedOutputType,
|
|
|
Function<Number, Object> expected,
|
|
|
List<String> warnings
|
|
|
+ ) {
|
|
|
+ unary(suppliers, expectedEvaluatorToString, inputType, valueSuppliers, expectedOutputType, v -> expected.apply((Number) v));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void unary(
|
|
|
+ List<TestCaseSupplier> suppliers,
|
|
|
+ String expectedEvaluatorToString,
|
|
|
+ DataType inputType,
|
|
|
+ List<Map.Entry<String, Supplier<Object>>> valueSuppliers,
|
|
|
+ DataType expectedOutputType,
|
|
|
+ Function<Object, Object> expected
|
|
|
) {
|
|
|
for (Map.Entry<String, Supplier<Object>> supplier : valueSuppliers) {
|
|
|
suppliers.add(new TestCaseSupplier(supplier.getKey(), List.of(inputType), () -> {
|
|
|
- Number value = (Number) supplier.getValue().get();
|
|
|
+ Object value = supplier.getValue().get();
|
|
|
TypedData typed = new TypedData(
|
|
|
// TODO there has to be a better way to handle unsigned long
|
|
|
value instanceof BigInteger b ? NumericUtils.asLongUnsigned(b) : value,
|
|
@@ -436,6 +535,63 @@ public record TestCaseSupplier(String name, List<DataType> types, Supplier<TestC
|
|
|
return cases;
|
|
|
}
|
|
|
|
|
|
+ private static List<Map.Entry<String, Supplier<Object>>> booleanCases() {
|
|
|
+ return List.of(Map.entry("<true>", () -> true), Map.entry("<false>", () -> false));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<Map.Entry<String, Supplier<Object>>> dateCases() {
|
|
|
+ return List.of(
|
|
|
+ Map.entry("<1970-01-01T00:00:00Z>", () -> 0L),
|
|
|
+ Map.entry(
|
|
|
+ "<date>",
|
|
|
+ () -> ESTestCase.randomLongBetween(0, 10 * (long) 10e11) // 1970-01-01T00:00:00Z - 2286-11-20T17:46:40Z
|
|
|
+ ),
|
|
|
+ Map.entry(
|
|
|
+ "<far future date>",
|
|
|
+ // 2286-11-20T17:46:40Z - +292278994-08-17T07:12:55.807Z
|
|
|
+ () -> ESTestCase.randomLongBetween(10 * (long) 10e11, Long.MAX_VALUE)
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<Map.Entry<String, Supplier<Object>>> ipCases() {
|
|
|
+ return List.of(
|
|
|
+ Map.entry("<127.0.0.1 ip>", () -> new BytesRef(InetAddressPoint.encode(InetAddresses.forString("127.0.0.1")))),
|
|
|
+ Map.entry("<ipv4>", () -> new BytesRef(InetAddressPoint.encode(ESTestCase.randomIp(true)))),
|
|
|
+ Map.entry("<ipv6>", () -> new BytesRef(InetAddressPoint.encode(ESTestCase.randomIp(false))))
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<Map.Entry<String, Supplier<Object>>> stringCases(String type) {
|
|
|
+ List<Map.Entry<String, Supplier<Object>>> result = new ArrayList<>();
|
|
|
+ result.add(Map.entry("<empty " + type + ">", () -> new BytesRef("")));
|
|
|
+ result.add(Map.entry("<short alpha " + type + ">", () -> new BytesRef(ESTestCase.randomAlphaOfLengthBetween(1, 30))));
|
|
|
+ result.add(Map.entry("<long alpha " + type + ">", () -> new BytesRef(ESTestCase.randomAlphaOfLengthBetween(300, 3000))));
|
|
|
+ result.add(Map.entry("<short unicode " + type + ">", () -> new BytesRef(ESTestCase.randomRealisticUnicodeOfLengthBetween(1, 30))));
|
|
|
+ result.add(
|
|
|
+ Map.entry("<long unicode " + type + ">", () -> new BytesRef(ESTestCase.randomRealisticUnicodeOfLengthBetween(300, 3000)))
|
|
|
+ );
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Supplier test case data for {@link Version} fields.
|
|
|
+ */
|
|
|
+ public static List<Map.Entry<String, Supplier<Object>>> versionCases(String prefix) {
|
|
|
+ return List.of(
|
|
|
+ Map.entry("<" + prefix + "version major>", () -> new Version(Integer.toString(ESTestCase.between(0, 100))).toBytesRef()),
|
|
|
+ Map.entry(
|
|
|
+ "<" + prefix + "version major.minor>",
|
|
|
+ () -> new Version(ESTestCase.between(0, 100) + "." + ESTestCase.between(0, 100)).toBytesRef()
|
|
|
+ ),
|
|
|
+ Map.entry(
|
|
|
+ "<" + prefix + "version major.minor.patch>",
|
|
|
+ () -> new Version(ESTestCase.between(0, 100) + "." + ESTestCase.between(0, 100) + "." + ESTestCase.between(0, 100))
|
|
|
+ .toBytesRef()
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
private static final Map<DataType, List<Map.Entry<String, Supplier<Object>>>> RANDOM_VALUE_SUPPLIERS = Map.ofEntries(
|
|
|
Map.entry(
|
|
|
DataTypes.DOUBLE,
|