Parcourir la source

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)
Costin Leau il y a 6 ans
Parent
commit
d10195594c

+ 5 - 0
docs/reference/sql/functions/type-conversion.asciidoc

@@ -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`

+ 5 - 38
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java

@@ -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;
     }
 
     //

+ 65 - 40
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java

@@ -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
      */

+ 5 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/SqlParserTests.java

@@ -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());

+ 33 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/type/DataTypesTests.java

@@ -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()));
     }