SQL: CAST supports both SQL and ES types (#40365)

Extend CAST to support all data types notations (whether SQL or ES
specific)

Fix #40282

(cherry picked from commit eb2ee8a344da946920598839a5db76c8bb9bc3fe)
This commit is contained in:
Costin Leau 2019-03-22 20:42:54 +02:00 committed by Costin Leau
parent eb10afabb8
commit 496070fda6
5 changed files with 113 additions and 78 deletions

View File

@ -38,6 +38,11 @@ include-tagged::{sql-specs}/docs.csv-spec[conversionIntToStringCast]
include-tagged::{sql-specs}/docs.csv-spec[conversionStringToDateTimeCast]
----
IMPORTANT: Both ANSI SQL and {es-sql} types are supported with the former taking
precedence. This only affects `FLOAT` which due naming conflict, is interpreted as ANSI SQL
and thus maps to `double` in {es} as oppose to `float`.
To obtain an {es} `float`, perform casting to its SQL equivalent, `real` type.
[[sql-functions-type-conversion-convert]]
==== `CONVERT`

View File

@ -387,44 +387,11 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
@Override
public DataType visitPrimitiveDataType(PrimitiveDataTypeContext ctx) {
String type = visitIdentifier(ctx.identifier()).toLowerCase(Locale.ROOT);
switch (type) {
case "bit":
case "bool":
case "boolean":
return DataType.BOOLEAN;
case "tinyint":
case "byte":
return DataType.BYTE;
case "smallint":
case "short":
return DataType.SHORT;
case "int":
case "integer":
return DataType.INTEGER;
case "long":
case "bigint":
return DataType.LONG;
case "real":
return DataType.FLOAT;
case "float":
case "double":
return DataType.DOUBLE;
case "date":
return DataType.DATE;
case "datetime":
case "timestamp":
return DataType.DATETIME;
case "char":
case "varchar":
case "string":
return DataType.KEYWORD;
case "ip":
return DataType.IP;
default:
throw new ParsingException(source(ctx), "Does not recognize type [{}]", ctx.getText());
}
String type = visitIdentifier(ctx.identifier());
DataType dataType = DataType.fromSqlOrEsType(type);
if (dataType == null) {
throw new ParsingException(source(ctx), "Does not recognize type [{}]", type); }
return dataType;
}
//

View File

@ -13,6 +13,7 @@ import java.sql.Types;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
/**
* Elasticsearch SQL data types.
@ -73,58 +74,78 @@ public enum DataType {
INTERVAL_MINUTE_TO_SECOND(ExtTypes.INTERVAL_MINUTE_TO_SECOND,Long.BYTES, 23, 23, false, false, false);
// @formatter:on
private static final Map<String, DataType> odbcToEs;
private static final Map<String, DataType> ODBC_TO_ES = new HashMap<>(36);
static {
odbcToEs = new HashMap<>(36);
// Numeric
odbcToEs.put("SQL_BIT", BOOLEAN);
odbcToEs.put("SQL_TINYINT", BYTE);
odbcToEs.put("SQL_SMALLINT", SHORT);
odbcToEs.put("SQL_INTEGER", INTEGER);
odbcToEs.put("SQL_BIGINT", LONG);
odbcToEs.put("SQL_FLOAT", FLOAT);
odbcToEs.put("SQL_REAL", FLOAT);
odbcToEs.put("SQL_DOUBLE", DOUBLE);
odbcToEs.put("SQL_DECIMAL", DOUBLE);
odbcToEs.put("SQL_NUMERIC", DOUBLE);
ODBC_TO_ES.put("SQL_BIT", BOOLEAN);
ODBC_TO_ES.put("SQL_TINYINT", BYTE);
ODBC_TO_ES.put("SQL_SMALLINT", SHORT);
ODBC_TO_ES.put("SQL_INTEGER", INTEGER);
ODBC_TO_ES.put("SQL_BIGINT", LONG);
ODBC_TO_ES.put("SQL_REAL", FLOAT);
ODBC_TO_ES.put("SQL_FLOAT", DOUBLE);
ODBC_TO_ES.put("SQL_DOUBLE", DOUBLE);
ODBC_TO_ES.put("SQL_DECIMAL", DOUBLE);
ODBC_TO_ES.put("SQL_NUMERIC", DOUBLE);
// String
odbcToEs.put("SQL_GUID", KEYWORD);
odbcToEs.put("SQL_CHAR", KEYWORD);
odbcToEs.put("SQL_WCHAR", KEYWORD);
odbcToEs.put("SQL_VARCHAR", TEXT);
odbcToEs.put("SQL_WVARCHAR", TEXT);
odbcToEs.put("SQL_LONGVARCHAR", TEXT);
odbcToEs.put("SQL_WLONGVARCHAR", TEXT);
ODBC_TO_ES.put("SQL_GUID", KEYWORD);
ODBC_TO_ES.put("SQL_CHAR", KEYWORD);
ODBC_TO_ES.put("SQL_WCHAR", KEYWORD);
ODBC_TO_ES.put("SQL_VARCHAR", TEXT);
ODBC_TO_ES.put("SQL_WVARCHAR", TEXT);
ODBC_TO_ES.put("SQL_LONGVARCHAR", TEXT);
ODBC_TO_ES.put("SQL_WLONGVARCHAR", TEXT);
// Binary
odbcToEs.put("SQL_BINARY", BINARY);
odbcToEs.put("SQL_VARBINARY", BINARY);
odbcToEs.put("SQL_LONGVARBINARY", BINARY);
ODBC_TO_ES.put("SQL_BINARY", BINARY);
ODBC_TO_ES.put("SQL_VARBINARY", BINARY);
ODBC_TO_ES.put("SQL_LONGVARBINARY", BINARY);
// Date
odbcToEs.put("SQL_DATE", DATE);
odbcToEs.put("SQL_TIME", DATETIME);
odbcToEs.put("SQL_TIMESTAMP", DATETIME);
ODBC_TO_ES.put("SQL_DATE", DATE);
ODBC_TO_ES.put("SQL_TIME", DATETIME);
ODBC_TO_ES.put("SQL_TIMESTAMP", DATETIME);
// Intervals
odbcToEs.put("SQL_INTERVAL_HOUR_TO_MINUTE", INTERVAL_HOUR_TO_MINUTE);
odbcToEs.put("SQL_INTERVAL_HOUR_TO_SECOND", INTERVAL_HOUR_TO_SECOND);
odbcToEs.put("SQL_INTERVAL_MINUTE_TO_SECOND", INTERVAL_MINUTE_TO_SECOND);
odbcToEs.put("SQL_INTERVAL_MONTH", INTERVAL_MONTH);
odbcToEs.put("SQL_INTERVAL_YEAR", INTERVAL_YEAR);
odbcToEs.put("SQL_INTERVAL_YEAR_TO_MONTH", INTERVAL_YEAR_TO_MONTH);
odbcToEs.put("SQL_INTERVAL_DAY", INTERVAL_DAY);
odbcToEs.put("SQL_INTERVAL_HOUR", INTERVAL_HOUR);
odbcToEs.put("SQL_INTERVAL_MINUTE", INTERVAL_MINUTE);
odbcToEs.put("SQL_INTERVAL_SECOND", INTERVAL_SECOND);
odbcToEs.put("SQL_INTERVAL_DAY_TO_HOUR", INTERVAL_DAY_TO_HOUR);
odbcToEs.put("SQL_INTERVAL_DAY_TO_MINUTE", INTERVAL_DAY_TO_MINUTE);
odbcToEs.put("SQL_INTERVAL_DAY_TO_SECOND", INTERVAL_DAY_TO_SECOND);
ODBC_TO_ES.put("SQL_INTERVAL_HOUR_TO_MINUTE", INTERVAL_HOUR_TO_MINUTE);
ODBC_TO_ES.put("SQL_INTERVAL_HOUR_TO_SECOND", INTERVAL_HOUR_TO_SECOND);
ODBC_TO_ES.put("SQL_INTERVAL_MINUTE_TO_SECOND", INTERVAL_MINUTE_TO_SECOND);
ODBC_TO_ES.put("SQL_INTERVAL_MONTH", INTERVAL_MONTH);
ODBC_TO_ES.put("SQL_INTERVAL_YEAR", INTERVAL_YEAR);
ODBC_TO_ES.put("SQL_INTERVAL_YEAR_TO_MONTH", INTERVAL_YEAR_TO_MONTH);
ODBC_TO_ES.put("SQL_INTERVAL_DAY", INTERVAL_DAY);
ODBC_TO_ES.put("SQL_INTERVAL_HOUR", INTERVAL_HOUR);
ODBC_TO_ES.put("SQL_INTERVAL_MINUTE", INTERVAL_MINUTE);
ODBC_TO_ES.put("SQL_INTERVAL_SECOND", INTERVAL_SECOND);
ODBC_TO_ES.put("SQL_INTERVAL_DAY_TO_HOUR", INTERVAL_DAY_TO_HOUR);
ODBC_TO_ES.put("SQL_INTERVAL_DAY_TO_MINUTE", INTERVAL_DAY_TO_MINUTE);
ODBC_TO_ES.put("SQL_INTERVAL_DAY_TO_SECOND", INTERVAL_DAY_TO_SECOND);
}
private static final Map<String, DataType> SQL_TO_ES = new HashMap<>(45);
static {
// first add ES types
for (DataType type : DataType.values()) {
if (type.isPrimitive()) {
SQL_TO_ES.put(type.name(), type);
}
}
// reuse the ODBC definition (without SQL_)
// note that this will override existing types in particular FLOAT
for (Entry<String, DataType> entry : ODBC_TO_ES.entrySet()) {
SQL_TO_ES.put(entry.getKey().substring(4), entry.getValue());
}
// special ones
SQL_TO_ES.put("BOOL", DataType.BOOLEAN);
SQL_TO_ES.put("INT", DataType.INTEGER);
SQL_TO_ES.put("STRING", DataType.KEYWORD);
}
/**
* Type's name used for error messages and column info for the clients
*/
@ -234,9 +255,13 @@ public enum DataType {
}
public static DataType fromOdbcType(String odbcType) {
return odbcToEs.get(odbcType);
return ODBC_TO_ES.get(odbcType);
}
public static DataType fromSqlOrEsType(String typeName) {
return SQL_TO_ES.get(typeName.toUpperCase(Locale.ROOT));
}
/**
* Creates returns DataType enum corresponding to the specified es type
*/

View File

@ -81,6 +81,11 @@ public class SqlParserTests extends ESTestCase {
assertEquals("CONVERT(POWER(languages, 2), SQL_DOUBLE)", f.sourceText());
}
public void testSelectCastToEsType() {
Cast f = singleProjection(project(parseStatement("SELECT CAST('0.' AS SCALED_FLOAT)")), Cast.class);
assertEquals("CAST('0.' AS SCALED_FLOAT)", f.sourceText());
}
public void testSelectAddWithParanthesis() {
Add f = singleProjection(project(parseStatement("SELECT (1 + 2)")), Add.class);
assertEquals("1 + 2", f.sourceText());

View File

@ -7,8 +7,13 @@ package org.elasticsearch.xpack.sql.type;
import org.elasticsearch.test.ESTestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import static org.elasticsearch.xpack.sql.type.DataType.DATETIME;
import static org.elasticsearch.xpack.sql.type.DataType.FLOAT;
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_DAY;
@ -108,6 +113,34 @@ public class DataTypesTests extends ESTestCase {
assertNull(compatibleInterval(INTERVAL_MINUTE_TO_SECOND, INTERVAL_MONTH));
}
public void testEsToDataType() throws Exception {
List<String> types = new ArrayList<>(Arrays.asList("null", "boolean", "bool",
"byte", "tinyint",
"short", "smallint",
"integer",
"long", "bigint",
"double", "real",
"half_float", "scaled_float", "float",
"decimal", "numeric",
"keyword", "text", "varchar",
"date", "datetime", "timestamp",
"binary", "varbinary",
"ip",
"interval_year", "interval_month", "interval_year_to_month",
"interval_day", "interval_hour", "interval_minute", "interval_second",
"interval_day_to_hour", "interval_day_to_minute", "interval_day_to_second",
"interval_hour_to_minute", "interval_hour_to_second",
"interval_minute_to_second"));
types.addAll(Stream.of(DataType.values())
.filter(DataType::isPrimitive)
.map(DataType::name)
.collect(toList()));
String type = randomFrom(types.toArray(new String[0]));
DataType dataType = DataType.fromSqlOrEsType(type);
assertNotNull(dataType);
}
private DataType randomDataTypeNoDateTime() {
return randomValueOtherThan(DataType.DATETIME, () -> randomFrom(DataType.values()));
}