Skip to content

Commit

Permalink
ESQL: Make a table of all inline casts (elastic#109713)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nik9000 authored Jun 18, 2024
1 parent 3bfecc8 commit b35f0ed
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 17 deletions.
19 changes: 19 additions & 0 deletions docs/reference/esql/functions/kibana/inline_cast.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -282,4 +293,13 @@ public static DataType readFrom(StreamInput in) throws IOException {
}
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
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;
Expand Down Expand Up @@ -549,7 +548,7 @@ public Expression visitInlineCast(EsqlBaseParser.InlineCastContext ctx) {
@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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
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;
Expand Down Expand Up @@ -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) {
Expand All @@ -72,11 +62,6 @@ public static DataType fromName(String name) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -53,6 +72,49 @@ public void testLeastFunctionInvalidInputs() {
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();
Expand Down

0 comments on commit b35f0ed

Please sign in to comment.