SQL: Consolidate more type information into DataType (elastic/x-pack-elasticsearch#3850)

Consolidates type handling into DataType, makes DataType available to 
JDBC by moving to sql-proto and removes support for all parameter types 
that cannot be handled by the server.

Original commit: elastic/x-pack-elasticsearch@b8024f5c46
This commit is contained in:
Igor Motov 2018-02-12 16:40:09 -05:00 committed by GitHub
parent 5dbbe8fef8
commit c82fdad41d
3 changed files with 179 additions and 259 deletions

View File

@ -6,10 +6,8 @@
package org.elasticsearch.xpack.sql.jdbc.jdbc;
import org.elasticsearch.xpack.sql.jdbc.JdbcSQLException;
import org.elasticsearch.xpack.sql.type.DataType;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.SQLException;
@ -38,13 +36,10 @@ import static java.util.Calendar.YEAR;
/**
* Conversion utilities for conversion of JDBC types to Java type and back
* <p>
* The following JDBC types are supported as part of Elasticsearch Response. See org.elasticsearch.xpack.sql.type.DataType for details.
* Only the following JDBC types are supported as part of Elasticsearch response and parameters.
* See org.elasticsearch.xpack.sql.type.DataType for details.
* <p>
* NULL, BOOLEAN, TINYINT, SMALLINT, INTEGER, BIGINT, DOUBLE, REAL, FLOAT, VARCHAR, VARBINARY and TIMESTAMP
* <p>
* The following additional types are also supported as parameters:
* <p>
* NUMERIC, DECIMAL, BIT, BINARY, LONGVARBINARY, CHAR, LONGVARCHAR, DATE, TIME, BLOB, CLOB, TIMESTAMP_WITH_TIMEZONE
*/
final class TypeConverter {
@ -172,57 +167,17 @@ final class TypeConverter {
* https://db.apache.org/derby/docs/10.5/ref/rrefjdbc20377.html
*/
public static String classNameOf(JDBCType jdbcType) throws JdbcSQLException {
switch (jdbcType) {
// ES - supported types
case BOOLEAN:
return Boolean.class.getName();
case TINYINT: // BYTE DataType
return Byte.class.getName();
case SMALLINT: // SHORT DataType
return Short.class.getName();
case INTEGER:
return Integer.class.getName();
case BIGINT: // LONG DataType
return Long.class.getName();
case DOUBLE:
return Double.class.getName();
case REAL: // FLOAT DataType
return Float.class.getName();
case FLOAT: // HALF_FLOAT DataType
return Double.class.getName(); // TODO: Is this correct?
case VARCHAR: // KEYWORD or TEXT DataType
return String.class.getName();
case VARBINARY: // BINARY DataType
return byte[].class.getName();
case TIMESTAMP: // DATE DataType
return Timestamp.class.getName();
// Parameters data types that cannot be returned by ES but can appear in client - supplied parameters
case NUMERIC:
case DECIMAL:
return BigDecimal.class.getName();
case BIT:
return Boolean.class.getName();
case BINARY:
case LONGVARBINARY:
return byte[].class.getName();
case CHAR:
case LONGVARCHAR:
return String.class.getName();
case DATE:
return Date.class.getName();
case TIME:
return Time.class.getName();
case BLOB:
return Blob.class.getName();
case CLOB:
return Clob.class.getName();
case TIMESTAMP_WITH_TIMEZONE:
return Long.class.getName();
default:
throw new JdbcSQLException("Unsupported JDBC type [" + jdbcType + "]");
final DataType dataType;
try {
dataType = DataType.fromJdbcType(jdbcType);
} catch (IllegalArgumentException ex) {
// Convert unsupported exception to JdbcSQLException
throw new JdbcSQLException(ex, ex.getMessage());
}
if (dataType.javaName == null) {
throw new JdbcSQLException("Unsupported JDBC type [" + jdbcType + "]");
}
return dataType.javaName;
}
/**
@ -235,8 +190,6 @@ final class TypeConverter {
case NULL:
return null;
case BOOLEAN:
case BINARY:
case VARBINARY:
case VARCHAR:
return v; // These types are already represented correctly in JSON
case TINYINT:
@ -265,43 +218,15 @@ final class TypeConverter {
* <p>
* It needs to support both params and column types
*/
static boolean isSigned(JDBCType type) throws SQLException {
switch (type) {
// ES Supported types
case BIGINT:
case DOUBLE:
case FLOAT:
case INTEGER:
case TINYINT:
case SMALLINT:
return true;
case NULL:
case BOOLEAN:
case VARCHAR:
case VARBINARY:
case TIMESTAMP:
return false;
// Parameter types
case REAL:
case DECIMAL:
case NUMERIC:
return true;
case BIT:
case BINARY:
case LONGVARBINARY:
case CHAR:
case LONGVARCHAR:
case DATE:
case TIME:
case BLOB:
case CLOB:
case TIMESTAMP_WITH_TIMEZONE:
return false;
default:
throw new SQLException("Unexpected column or parameter type [" + type + "]");
static boolean isSigned(JDBCType jdbcType) throws SQLException {
final DataType dataType;
try {
dataType = DataType.fromJdbcType(jdbcType);
} catch (IllegalArgumentException ex) {
// Convert unsupported exception to JdbcSQLException
throw new JdbcSQLException(ex, ex.getMessage());
}
return dataType.isSigned();
}
private static Double doubleValue(Object v) {
@ -427,12 +352,7 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return safeToLong(((Number) val).doubleValue());
case DATE:
return utcMillisRemoveTime(((Number) val).longValue());
case TIME:
return utcMillisRemoveDate(((Number) val).longValue());
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
return ((Number) val).longValue();
default:
}
@ -479,47 +399,23 @@ final class TypeConverter {
}
private static Date asDate(Object val, JDBCType columnType) throws SQLException {
switch (columnType) {
case TIME:
// time has no date component
return new Date(0);
case DATE:
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
return new Date(utcMillisRemoveTime(((Number) val).longValue()));
default:
if (columnType == JDBCType.TIMESTAMP) {
return new Date(utcMillisRemoveTime(((Number) val).longValue()));
}
throw new SQLException("Conversion from type [" + columnType + "] to [Date] not supported");
}
private static Time asTime(Object val, JDBCType columnType) throws SQLException {
switch (columnType) {
case DATE:
// date has no time component
return new Time(0);
case TIME:
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
return new Time(utcMillisRemoveDate(((Number) val).longValue()));
default:
if (columnType == JDBCType.TIMESTAMP) {
return new Time(utcMillisRemoveDate(((Number) val).longValue()));
}
throw new SQLException("Conversion from type [" + columnType + "] to [Time] not supported");
}
private static Timestamp asTimestamp(Object val, JDBCType columnType) throws SQLException {
switch (columnType) {
case DATE:
return new Timestamp(utcMillisRemoveTime(((Number) val).longValue()));
case TIME:
return new Timestamp(utcMillisRemoveDate(((Number) val).longValue()));
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
return new Timestamp(((Number) val).longValue());
default:
if (columnType == JDBCType.TIMESTAMP) {
return new Timestamp(((Number) val).longValue());
}
throw new SQLException("Conversion from type [" + columnType + "] to [Timestamp] not supported");
}

View File

@ -0,0 +1,152 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.type;
import java.sql.JDBCType;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Elasticsearch data types that supported by SQL interface
*/
public enum DataType {
// @formatter:off
// jdbc type, Java Class size, defPrecision, dispSize, int, rat, docvals
NULL( JDBCType.NULL, null, 0, 0, 0),
UNSUPPORTED( JDBCType.OTHER, null, 0, 0, 0),
BOOLEAN( JDBCType.BOOLEAN, Boolean.class, 1, 1, 1),
BYTE( JDBCType.TINYINT, Byte.class, Byte.BYTES, 3, 5, true, false, true),
SHORT( JDBCType.SMALLINT, Short.class, Short.BYTES, 5, 6, true, false, true),
INTEGER( JDBCType.INTEGER, Integer.class, Integer.BYTES, 10, 11, true, false, true),
LONG( JDBCType.BIGINT, Long.class, Long.BYTES, 19, 20, true, false, true),
// 53 bits defaultPrecision ~ 16(15.95) decimal digits (53log10(2)),
DOUBLE( JDBCType.DOUBLE, Double.class, Double.BYTES, 16, 25, false, true, true),
// 24 bits defaultPrecision - 24*log10(2) =~ 7 (7.22)
FLOAT( JDBCType.REAL, Float.class, Float.BYTES, 7, 15, false, true, true),
HALF_FLOAT( JDBCType.FLOAT, Double.class, Double.BYTES, 16, 25, false, true, true),
// precision is based on long
SCALED_FLOAT(JDBCType.FLOAT, Double.class, Double.BYTES, 19, 25, false, true, true),
KEYWORD( JDBCType.VARCHAR, String.class, Integer.MAX_VALUE, 256, 0),
TEXT( JDBCType.VARCHAR, String.class, Integer.MAX_VALUE, Integer.MAX_VALUE, 0, false, false, false),
OBJECT( JDBCType.STRUCT, null, -1, 0, 0),
NESTED( JDBCType.STRUCT, null, -1, 0, 0),
BINARY( JDBCType.VARBINARY, byte[].class, -1, Integer.MAX_VALUE, 0),
DATE( JDBCType.TIMESTAMP, Timestamp.class, Long.BYTES, 19, 20);
// @formatter:on
private static final Map<JDBCType, DataType> jdbcToEs;
static {
jdbcToEs = Arrays.stream(DataType.values())
.filter(dataType -> dataType != TEXT && dataType != NESTED && dataType != SCALED_FLOAT) // Remove duplicates
.collect(Collectors.toMap(dataType -> dataType.jdbcType, dataType -> dataType));
}
/**
* Elasticsearch type name
*/
public final String esType;
/**
* Compatible JDBC type
*/
public final JDBCType jdbcType;
/**
* Name of corresponding java class
*/
public final String javaName;
/**
* Size of the type in bytes
* <p>
* -1 if the size can vary
*/
public final int size;
/**
* Precision
* <p>
* Specified column size. For numeric data, this is the maximum precision. For character
* data, this is the length in characters. For datetime datatypes, this is the length in characters of the
* String representation (assuming the maximum allowed defaultPrecision of the fractional seconds component).
*/
public final int defaultPrecision;
/**
* Display Size
* <p>
* Normal maximum width in characters.
*/
public final int displaySize;
/**
* True if the type represents an integer number
*/
public final boolean isInteger;
/**
* True if the type represents a rational number
*/
public final boolean isRational;
/**
* True if the type supports doc values by default
*/
public final boolean defaultDocValues;
DataType(JDBCType jdbcType, Class<?> javaClass, int size, int defaultPrecision, int displaySize, boolean isInteger, boolean isRational,
boolean defaultDocValues) {
this.esType = name().toLowerCase(Locale.ROOT);
this.javaName = javaClass == null ? null : javaClass.getName();
this.jdbcType = jdbcType;
this.size = size;
this.defaultPrecision = defaultPrecision;
this.displaySize = displaySize;
this.isInteger = isInteger;
this.isRational = isRational;
this.defaultDocValues = defaultDocValues;
}
DataType(JDBCType jdbcType, Class<?> javaClass, int size, int defaultPrecision, int displaySize) {
this(jdbcType, javaClass, size, defaultPrecision, displaySize, false, false, true);
}
public String sqlName() {
return jdbcType.getName();
}
public boolean isNumeric() {
return isInteger || isRational;
}
/**
* Returns true if value is signed, false if it is unsigned or null if the type doesn't represent a number
*/
public Boolean isSigned() {
// For now all numeric values that es supports are signed
return isNumeric() ? true : null;
}
public boolean isString() {
return this == KEYWORD || this == TEXT;
}
public boolean isPrimitive() {
return this != OBJECT && this != NESTED;
}
public static DataType fromJdbcType(JDBCType jdbcType) {
if (jdbcToEs.containsKey(jdbcType) == false) {
throw new IllegalArgumentException("Unsupported JDBC type [" + jdbcType + "]");
}
return jdbcToEs.get(jdbcType);
}
}

View File

@ -1,128 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.type;
import java.sql.JDBCType;
import java.util.Locale;
/**
* Elasticsearch data types that supported by SQL interface
*/
public enum DataType {
// @formatter:off
// jdbc type, size, defPrecision, dispSize, int, rat, docvals
NULL( JDBCType.NULL, 0, 0, 0),
UNSUPPORTED( JDBCType.OTHER, 0, 0, 0),
BOOLEAN( JDBCType.BOOLEAN, 1, 1, 1),
BYTE( JDBCType.TINYINT, Byte.BYTES, 3, 5, true, false, true),
SHORT( JDBCType.SMALLINT, Short.BYTES, 5, 6, true, false, true),
INTEGER( JDBCType.INTEGER, Integer.BYTES, 10, 11, true, false, true),
LONG( JDBCType.BIGINT, Long.BYTES, 19, 20, true, false, true),
// 53 bits defaultPrecision ~ 16(15.95) decimal digits (53log10(2)),
DOUBLE( JDBCType.DOUBLE, Double.BYTES, 16, 25, false, true, true),
// 24 bits defaultPrecision - 24*log10(2) =~ 7 (7.22)
FLOAT( JDBCType.REAL, Float.BYTES, 7, 15, false, true, true),
HALF_FLOAT( JDBCType.FLOAT, Double.BYTES, 16, 25, false, true, true),
// precision is based on long
SCALED_FLOAT(JDBCType.FLOAT, Double.BYTES, 19, 25, false, true, true),
KEYWORD( JDBCType.VARCHAR, Integer.MAX_VALUE, 256, 0),
TEXT( JDBCType.VARCHAR, Integer.MAX_VALUE, Integer.MAX_VALUE, 0, false, false, false),
OBJECT( JDBCType.STRUCT, -1, 0, 0),
NESTED( JDBCType.STRUCT, -1, 0, 0),
BINARY( JDBCType.VARBINARY, -1, Integer.MAX_VALUE, 0),
DATE( JDBCType.TIMESTAMP, Long.BYTES, 19, 20);
// @formatter:on
/**
* Elasticsearch type name
*/
public final String esType;
/**
* Compatible JDBC type
*/
public final JDBCType jdbcType;
/**
* Size of the type in bytes
* <p>
* -1 if the size can vary
*/
public final int size;
/**
* Precision
* <p>
* Specified column size. For numeric data, this is the maximum precision. For character
* data, this is the length in characters. For datetime datatypes, this is the length in characters of the
* String representation (assuming the maximum allowed defaultPrecision of the fractional seconds component).
*/
public final int defaultPrecision;
/**
* Display Size
* <p>
* Normal maximum width in characters.
*/
public final int displaySize;
/**
* True if the type represents an integer number
*/
public final boolean isInteger;
/**
* True if the type represents a rational number
*/
public final boolean isRational;
/**
* True if the type supports doc values by default
*/
public final boolean defaultDocValues;
DataType(JDBCType jdbcType, int size, int defaultPrecision, int displaySize, boolean isInteger, boolean isRational,
boolean defaultDocValues) {
this.esType = name().toLowerCase(Locale.ROOT);
this.jdbcType = jdbcType;
this.size = size;
this.defaultPrecision = defaultPrecision;
this.displaySize = displaySize;
this.isInteger = isInteger;
this.isRational = isRational;
this.defaultDocValues = defaultDocValues;
}
DataType(JDBCType jdbcType, int size, int defaultPrecision, int displaySize) {
this(jdbcType, size, defaultPrecision, displaySize, false, false, true);
}
public String sqlName() {
return jdbcType.getName();
}
public boolean isNumeric() {
return isInteger || isRational;
}
/**
* Returns true if value is signed, false if it is unsigned or null if the type doesn't represent a number
*/
public Boolean isSigned() {
// For now all numeric values that es supports are signed
return isNumeric() ? true : null;
}
public boolean isString() {
return this == KEYWORD || this == TEXT;
}
public boolean isPrimitive() {
return this != OBJECT && this != NESTED;
}
}