1
0
Эх сурвалжийг харах

ESQL: Make a table of all inline casts (#109713)

This adds a test that generates
`docs/reference/esql/functions/kibana/inline_cast.json` which is a json
object who's keys are the names of valid inline casts and who's values
are the resulting data types.

I also moved one of the maps we use to make the inline casts to
`DataType`, which is a place where we want it.
Nik Everett 1 жил өмнө
parent
commit
b35f0ed48d

+ 19 - 0
docs/reference/esql/functions/kibana/inline_cast.json

@@ -0,0 +1,19 @@
+{
+  "bool" : "to_boolean",
+  "boolean" : "to_boolean",
+  "cartesian_point" : "to_cartesianpoint",
+  "cartesian_shape" : "to_cartesianshape",
+  "datetime" : "to_datetime",
+  "double" : "to_double",
+  "geo_point" : "to_geopoint",
+  "geo_shape" : "to_geoshape",
+  "int" : "to_integer",
+  "integer" : "to_integer",
+  "ip" : "to_ip",
+  "keyword" : "to_string",
+  "long" : "to_long",
+  "string" : "to_string",
+  "text" : "to_string",
+  "unsigned_long" : "to_unsigned_long",
+  "version" : "to_version"
+}

+ 20 - 0
x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java

@@ -18,6 +18,8 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
 import java.util.stream.Stream;
 
 import static java.util.stream.Collectors.toMap;
@@ -144,6 +146,15 @@ public enum DataType {
         ES_TO_TYPE = Collections.unmodifiableMap(map);
     }
 
+    private static final Map<String, DataType> NAME_OR_ALIAS_TO_TYPE;
+    static {
+        Map<String, DataType> map = DataType.types().stream().collect(toMap(DataType::typeName, Function.identity()));
+        map.put("bool", BOOLEAN);
+        map.put("int", INTEGER);
+        map.put("string", KEYWORD);
+        NAME_OR_ALIAS_TO_TYPE = Collections.unmodifiableMap(map);
+    }
+
     public static Collection<DataType> types() {
         return TYPES;
     }
@@ -282,4 +293,13 @@ public enum DataType {
         }
         return dataType;
     }
+
+    public static Set<String> namesAndAliases() {
+        return NAME_OR_ALIAS_TO_TYPE.keySet();
+    }
+
+    public static DataType fromNameOrAlias(String typeName) {
+        DataType type = NAME_OR_ALIAS_TO_TYPE.get(typeName.toLowerCase(Locale.ROOT));
+        return type != null ? type : UNSUPPORTED;
+    }
 }

+ 1 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/ExpressionBuilder.java

@@ -58,7 +58,6 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Ins
 import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan;
 import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual;
 import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
-import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
 
 import java.math.BigInteger;
 import java.time.Duration;
@@ -549,7 +548,7 @@ public abstract class ExpressionBuilder extends IdentifierBuilder {
     @Override
     public DataType visitToDataType(EsqlBaseParser.ToDataTypeContext ctx) {
         String typeName = visitIdentifier(ctx.identifier());
-        DataType dataType = EsqlDataTypes.fromNameOrAlias(typeName);
+        DataType dataType = DataType.fromNameOrAlias(typeName);
         if (dataType == DataType.UNSUPPORTED) {
             throw new ParsingException(source(ctx), "Unknown data type named [{}]", typeName);
         }

+ 0 - 15
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypes.java

@@ -12,7 +12,6 @@ import org.elasticsearch.xpack.esql.core.type.DataType;
 import java.util.Collections;
 import java.util.Locale;
 import java.util.Map;
-import java.util.function.Function;
 
 import static java.util.stream.Collectors.toMap;
 import static java.util.stream.Collectors.toUnmodifiableMap;
@@ -52,15 +51,6 @@ public final class EsqlDataTypes {
         ES_TO_TYPE = Collections.unmodifiableMap(map);
     }
 
-    private static final Map<String, DataType> NAME_OR_ALIAS_TO_TYPE;
-    static {
-        Map<String, DataType> map = DataType.types().stream().collect(toMap(DataType::typeName, Function.identity()));
-        map.put("bool", BOOLEAN);
-        map.put("int", INTEGER);
-        map.put("string", KEYWORD);
-        NAME_OR_ALIAS_TO_TYPE = Collections.unmodifiableMap(map);
-    }
-
     private EsqlDataTypes() {}
 
     public static DataType fromTypeName(String name) {
@@ -72,11 +62,6 @@ public final class EsqlDataTypes {
         return type != null ? type : UNSUPPORTED;
     }
 
-    public static DataType fromNameOrAlias(String typeName) {
-        DataType type = NAME_OR_ALIAS_TO_TYPE.get(typeName.toLowerCase(Locale.ROOT));
-        return type != null ? type : UNSUPPORTED;
-    }
-
     public static DataType fromJava(Object value) {
         if (value == null) {
             return NULL;

+ 62 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java

@@ -7,17 +7,36 @@
 
 package org.elasticsearch.xpack.esql.analysis;
 
+import org.elasticsearch.core.PathUtils;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.xcontent.json.JsonXContent;
 import org.elasticsearch.xpack.esql.core.ParsingException;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.function.FunctionDefinition;
 import org.elasticsearch.xpack.esql.core.index.EsIndex;
 import org.elasticsearch.xpack.esql.core.index.IndexResolution;
+import org.elasticsearch.xpack.esql.core.plan.logical.LogicalPlan;
+import org.elasticsearch.xpack.esql.core.type.DataType;
 import org.elasticsearch.xpack.esql.core.type.TypesTests;
 import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
 import org.elasticsearch.xpack.esql.parser.EsqlParser;
+import org.elasticsearch.xpack.esql.plan.logical.Row;
+import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_CFG;
 import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.as;
 import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyPolicyResolution;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
 
 public class ParsingTests extends ESTestCase {
     private static final String INDEX_NAME = "test";
@@ -53,6 +72,49 @@ public class ParsingTests extends ESTestCase {
         assertEquals("1:23: error building [least]: expects at least one argument", error("row a = 1 | eval x = least()"));
     }
 
+    /**
+     * Tests the inline cast syntax {@code <value>::<type>} for all supported types and
+     * builds a little json report of the valid types.
+     */
+    public void testInlineCast() throws IOException {
+        EsqlFunctionRegistry registry = new EsqlFunctionRegistry();
+        Path dir = PathUtils.get(System.getProperty("java.io.tmpdir")).resolve("esql").resolve("functions").resolve("kibana");
+        Files.createDirectories(dir);
+        Path file = dir.resolve("inline_cast.json");
+        try (XContentBuilder report = new XContentBuilder(JsonXContent.jsonXContent, Files.newOutputStream(file))) {
+            report.humanReadable(true).prettyPrint();
+            report.startObject();
+            List<String> namesAndAliases = new ArrayList<>(DataType.namesAndAliases());
+            Collections.sort(namesAndAliases);
+            for (String nameOrAlias : namesAndAliases) {
+                DataType expectedType = DataType.fromNameOrAlias(nameOrAlias);
+                if (expectedType == DataType.TEXT) {
+                    expectedType = DataType.KEYWORD;
+                }
+                if (EsqlDataTypeConverter.converterFunctionFactory(expectedType) == null) {
+                    continue;
+                }
+                LogicalPlan plan = parser.createStatement("ROW a = 1::" + nameOrAlias);
+                Row row = as(plan, Row.class);
+                assertThat(row.fields(), hasSize(1));
+                Expression functionCall = row.fields().get(0).child();
+                assertThat(functionCall.dataType(), equalTo(expectedType));
+                report.field(nameOrAlias, functionName(registry, functionCall));
+            }
+            report.endObject();
+        }
+        logger.info("Wrote to file: {}", file);
+    }
+
+    private String functionName(EsqlFunctionRegistry registry, Expression functionCall) {
+        for (FunctionDefinition def : registry.listFunctions()) {
+            if (functionCall.getClass().equals(def.clazz())) {
+                return def.name();
+            }
+        }
+        throw new IllegalArgumentException("can't find name for " + functionCall);
+    }
+
     private String error(String query) {
         ParsingException e = expectThrows(ParsingException.class, () -> defaultAnalyzer.analyze(parser.createStatement(query)));
         String message = e.getMessage();