diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java index c8117256c4f81..8c7c7e22a417b 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java @@ -17,9 +17,16 @@ import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.CharType; import com.facebook.presto.common.type.DecimalType; +import com.facebook.presto.common.type.TimestampType; import com.facebook.presto.common.type.Type; import com.facebook.presto.common.type.UuidType; import com.facebook.presto.common.type.VarcharType; +import com.facebook.presto.plugin.jdbc.mapping.ColumnMapping; +import com.facebook.presto.plugin.jdbc.mapping.WriteMapping; +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.ConnectorSession; @@ -57,6 +64,7 @@ import static com.facebook.presto.common.type.BigintType.BIGINT; import static com.facebook.presto.common.type.BooleanType.BOOLEAN; +import static com.facebook.presto.common.type.CharType.MAX_LENGTH; import static com.facebook.presto.common.type.DateType.DATE; import static com.facebook.presto.common.type.DoubleType.DOUBLE; import static com.facebook.presto.common.type.IntegerType.INTEGER; @@ -64,14 +72,33 @@ import static com.facebook.presto.common.type.SmallintType.SMALLINT; import static com.facebook.presto.common.type.TimeType.TIME; import static com.facebook.presto.common.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; -import static com.facebook.presto.common.type.TimestampType.TIMESTAMP; import static com.facebook.presto.common.type.TimestampWithTimeZoneType.TIMESTAMP_WITH_TIME_ZONE; import static com.facebook.presto.common.type.TinyintType.TINYINT; import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; import static com.facebook.presto.common.type.Varchars.isVarcharType; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; import static com.facebook.presto.plugin.jdbc.JdbcWarningCode.USE_OF_DEPRECATED_CONFIGURATION_PROPERTY; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.jdbcTypeToPrestoType; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.bigintColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.booleanColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.charColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.dateColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.decimalColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.doubleColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.integerColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.jdbcTypeToPrestoType; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.realColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.smallintColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.timeColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.timeWithTimeZoneColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.timestampColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.timestampWithTimeZoneColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.tinyintColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.uuidColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varbinaryColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.booleanMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.longMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.sliceMapping; import static com.facebook.presto.spi.StandardErrorCode.NOT_FOUND; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; import static com.google.common.base.MoreObjects.firstNonNull; @@ -94,21 +121,21 @@ public class BaseJdbcClient { private static final Logger log = Logger.get(BaseJdbcClient.class); - private static final Map SQL_TYPES = ImmutableMap.builder() - .put(BOOLEAN, "boolean") - .put(BIGINT, "bigint") - .put(INTEGER, "integer") - .put(SMALLINT, "smallint") - .put(TINYINT, "tinyint") - .put(DOUBLE, "double precision") - .put(REAL, "real") - .put(VARBINARY, "varbinary") - .put(DATE, "date") - .put(TIME, "time") - .put(TIME_WITH_TIME_ZONE, "time with timezone") - .put(TIMESTAMP, "timestamp") - .put(TIMESTAMP_WITH_TIME_ZONE, "timestamp with timezone") - .put(UuidType.UUID, "uuid") + private static final Map TYPE_MAPPINGS = ImmutableMap.builder() + .put(BOOLEAN, booleanMapping("boolean", (BooleanWriteFunction) booleanColumnMapping().getWriteFunction())) + .put(BIGINT, longMapping("bigint", (LongWriteFunction) bigintColumnMapping().getWriteFunction())) + .put(INTEGER, longMapping("integer", (LongWriteFunction) integerColumnMapping().getWriteFunction())) + .put(SMALLINT, longMapping("smallint", (LongWriteFunction) smallintColumnMapping().getWriteFunction())) + .put(TINYINT, longMapping("tinyint", (LongWriteFunction) tinyintColumnMapping().getWriteFunction())) + .put(DOUBLE, WriteMapping.doubleMapping("double precision", (DoubleWriteFunction) doubleColumnMapping().getWriteFunction())) + .put(REAL, longMapping("real", (LongWriteFunction) realColumnMapping().getWriteFunction())) + .put(VARBINARY, sliceMapping("varbinary", (SliceWriteFunction) varbinaryColumnMapping().getWriteFunction())) + .put(DATE, longMapping("date", (LongWriteFunction) dateColumnMapping().getWriteFunction())) + .put(TIME, longMapping("time", (LongWriteFunction) timeColumnMapping().getWriteFunction())) + .put(UuidType.UUID, sliceMapping("uuid", (SliceWriteFunction) uuidColumnMapping().getWriteFunction())) + + .put(TIME_WITH_TIME_ZONE, longMapping("time with timezone", (LongWriteFunction) timeWithTimeZoneColumnMapping().getWriteFunction())) + .put(TIMESTAMP_WITH_TIME_ZONE, longMapping("timestamp with timezone", (LongWriteFunction) timestampWithTimeZoneColumnMapping().getWriteFunction())) .build(); protected final String connectorId; @@ -246,7 +273,7 @@ public List getColumns(ConnectorSession session, JdbcTableHand resultSet.getString("TYPE_NAME"), resultSet.getInt("COLUMN_SIZE"), resultSet.getInt("DECIMAL_DIGITS")); - Optional columnMapping = toPrestoType(session, typeHandle); + Optional columnMapping = toPrestoType(session, typeHandle); // skip unsupported column types if (columnMapping.isPresent()) { String columnName = resultSet.getString("COLUMN_NAME"); @@ -272,7 +299,7 @@ public List getColumns(ConnectorSession session, JdbcTableHand } @Override - public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) + public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) { return jdbcTypeToPrestoType(typeHandle); } @@ -408,7 +435,7 @@ private String getColumnString(ColumnMetadata column, String columnName) StringBuilder sb = new StringBuilder() .append(quoted(columnName)) .append(" ") - .append(toSqlType(column.getType())); + .append(toWriteMapping(column.getType()).getDataType()); if (!column.isNullable()) { sb.append(" NOT NULL"); } @@ -752,28 +779,45 @@ protected void execute(Connection connection, String query) } } - protected String toSqlType(Type type) + public WriteMapping toWriteMapping(Type type) { + String dataType; if (isVarcharType(type)) { VarcharType varcharType = (VarcharType) type; if (varcharType.isUnbounded()) { - return "varchar"; + dataType = "varchar"; } - return "varchar(" + varcharType.getLengthSafe() + ")"; + else { + dataType = "varchar(" + varcharType.getLengthSafe() + ")"; + } + return sliceMapping(dataType, (SliceWriteFunction) varcharColumnMapping(varcharType).getWriteFunction()); + } + else if (type instanceof CharType) { + CharType charType = (CharType) type; + if (charType.getLength() == MAX_LENGTH) { + dataType = "char"; + } + else { + dataType = "char(" + ((CharType) type).getLength() + ")"; + } + return sliceMapping(dataType, (SliceWriteFunction) charColumnMapping(charType).getWriteFunction()); } - if (type instanceof CharType) { - if (((CharType) type).getLength() == CharType.MAX_LENGTH) { - return "char"; + else if (type instanceof DecimalType) { + DecimalType decimalType = (DecimalType) type; + dataType = format("decimal(%s, %s)", ((DecimalType) type).getPrecision(), ((DecimalType) type).getScale()); + if (decimalType.isShort()) { + return longMapping(dataType, (LongWriteFunction) decimalColumnMapping(decimalType).getWriteFunction()); + } + else { + return sliceMapping(dataType, (SliceWriteFunction) decimalColumnMapping(decimalType).getWriteFunction()); } - return "char(" + ((CharType) type).getLength() + ")"; } - if (type instanceof DecimalType) { - return format("decimal(%s, %s)", ((DecimalType) type).getPrecision(), ((DecimalType) type).getScale()); + else if (type instanceof TimestampType) { + return longMapping("timestamp", (LongWriteFunction) timestampColumnMapping((TimestampType) type).getWriteFunction()); } - - String sqlType = SQL_TYPES.get(type); - if (sqlType != null) { - return sqlType; + WriteMapping writeMapping = TYPE_MAPPINGS.get(type); + if (writeMapping != null) { + return writeMapping; } throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java index 591a73ea2ce7c..48d18460a32a1 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java @@ -14,6 +14,9 @@ package com.facebook.presto.plugin.jdbc; import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.common.type.Type; +import com.facebook.presto.plugin.jdbc.mapping.ColumnMapping; +import com.facebook.presto.plugin.jdbc.mapping.WriteMapping; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.ConnectorSession; @@ -49,7 +52,9 @@ default boolean schemaExists(ConnectorSession session, JdbcIdentity identity, St List getColumns(ConnectorSession session, JdbcTableHandle tableHandle); - Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle); + Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle); + + WriteMapping toWriteMapping(Type type); ConnectorSplitSource getSplits(ConnectorSession session, JdbcIdentity identity, JdbcTableLayoutHandle layoutHandle); diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcPageSink.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcPageSink.java index 50d6ed03082fe..81e5a74edc8dd 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcPageSink.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcPageSink.java @@ -16,51 +16,34 @@ import com.facebook.airlift.log.Logger; import com.facebook.presto.common.Page; import com.facebook.presto.common.block.Block; -import com.facebook.presto.common.type.DecimalType; -import com.facebook.presto.common.type.TimestampType; import com.facebook.presto.common.type.Type; -import com.facebook.presto.common.type.UuidType; +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.ObjectWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; import com.facebook.presto.spi.ConnectorPageSink; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.PrestoException; import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Shorts; -import com.google.common.primitives.SignedBytes; import io.airlift.slice.Slice; -import org.joda.time.DateTimeZone; import java.sql.Connection; -import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.SQLNonTransientException; -import java.sql.Timestamp; -import java.time.Instant; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; -import static com.facebook.presto.common.type.BigintType.BIGINT; -import static com.facebook.presto.common.type.BooleanType.BOOLEAN; -import static com.facebook.presto.common.type.Chars.isCharType; -import static com.facebook.presto.common.type.DateType.DATE; -import static com.facebook.presto.common.type.Decimals.readBigDecimal; -import static com.facebook.presto.common.type.DoubleType.DOUBLE; -import static com.facebook.presto.common.type.IntegerType.INTEGER; -import static com.facebook.presto.common.type.RealType.REAL; -import static com.facebook.presto.common.type.SmallintType.SMALLINT; -import static com.facebook.presto.common.type.TinyintType.TINYINT; -import static com.facebook.presto.common.type.UuidType.prestoUuidToJavaUuid; -import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; -import static com.facebook.presto.common.type.Varchars.isVarcharType; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_NON_TRANSIENT_ERROR; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; -import static java.lang.Float.intBitsToFloat; -import static java.lang.Math.toIntExact; +import static com.google.common.base.Verify.verify; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.lang.String.format; import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.TimeUnit.DAYS; -import static org.joda.time.chrono.ISOChronology.getInstanceUTC; public class JdbcPageSink implements ConnectorPageSink @@ -71,6 +54,7 @@ public class JdbcPageSink private final PreparedStatement statement; private final List columnTypes; + private final List columnWriters; private int batchSize; public JdbcPageSink(ConnectorSession session, JdbcOutputTableHandle handle, JdbcClient jdbcClient) @@ -92,6 +76,12 @@ public JdbcPageSink(ConnectorSession session, JdbcOutputTableHandle handle, Jdbc } columnTypes = handle.getColumnTypes(); + columnWriters = columnTypes.stream().map(type -> { + WriteFunction writeFunction = jdbcClient.toWriteMapping(type).getWriteFunction(); + verify(type.getJavaType() == writeFunction.getJavaType(), + format("Presto type %s is not compatible with write function %s accepting %s", type, writeFunction, writeFunction.getJavaType())); + return writeFunction; + }).collect(toImmutableList()); } @Override @@ -132,55 +122,27 @@ private void appendColumn(Page page, int position, int channel) } Type type = columnTypes.get(channel); - if (BOOLEAN.equals(type)) { - statement.setBoolean(parameter, type.getBoolean(block, position)); + Class javaType = type.getJavaType(); + WriteFunction writeFunction = columnWriters.get(channel); + if (javaType == boolean.class) { + ((BooleanWriteFunction) writeFunction).set(statement, parameter, type.getBoolean(block, position)); } - else if (BIGINT.equals(type)) { - statement.setLong(parameter, type.getLong(block, position)); + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameter, type.getLong(block, position)); } - else if (INTEGER.equals(type)) { - statement.setInt(parameter, toIntExact(type.getLong(block, position))); + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameter, type.getDouble(block, position)); } - else if (SMALLINT.equals(type)) { - statement.setShort(parameter, Shorts.checkedCast(type.getLong(block, position))); - } - else if (TINYINT.equals(type)) { - statement.setByte(parameter, SignedBytes.checkedCast(type.getLong(block, position))); - } - else if (DOUBLE.equals(type)) { - statement.setDouble(parameter, type.getDouble(block, position)); - } - else if (REAL.equals(type)) { - statement.setFloat(parameter, intBitsToFloat(toIntExact(type.getLong(block, position)))); - } - else if (type instanceof DecimalType) { - statement.setBigDecimal(parameter, readBigDecimal((DecimalType) type, block, position)); - } - else if (isVarcharType(type) || isCharType(type)) { - statement.setString(parameter, type.getSlice(block, position).toStringUtf8()); - } - else if (VARBINARY.equals(type)) { - statement.setBytes(parameter, type.getSlice(block, position).getBytes()); - } - else if (DATE.equals(type)) { - // convert to midnight in default time zone - long utcMillis = DAYS.toMillis(type.getLong(block, position)); - long localMillis = getInstanceUTC().getZone().getMillisKeepLocal(DateTimeZone.getDefault(), utcMillis); - statement.setDate(parameter, new Date(localMillis)); - } - else if (type instanceof TimestampType) { - long timestampValue = type.getLong(block, position); - statement.setTimestamp(parameter, - Timestamp.from(Instant.ofEpochSecond( - ((TimestampType) type).getEpochSecond(timestampValue), - ((TimestampType) type).getNanos(timestampValue)))); - } - else if (UuidType.UUID.equals(type)) { - Slice slice = type.getSlice(block, position); - statement.setObject(parameter, prestoUuidToJavaUuid(slice)); + else if (javaType == Slice.class) { + ((SliceWriteFunction) writeFunction).set(statement, parameter, type.getSlice(block, position)); } else { - throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); + try { + ((ObjectWriteFunction) writeFunction).set(statement, parameter, type.getObject(block, position)); + } + catch (SQLException e) { + throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); + } } } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcRecordCursor.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcRecordCursor.java index 2c8f8b2328d8f..4e4305db71519 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcRecordCursor.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcRecordCursor.java @@ -15,6 +15,13 @@ import com.facebook.airlift.log.Logger; import com.facebook.presto.common.type.Type; +import com.facebook.presto.plugin.jdbc.mapping.ColumnMapping; +import com.facebook.presto.plugin.jdbc.mapping.ReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.ObjectReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceReadFunction; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.RecordCursor; @@ -31,7 +38,6 @@ import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; public class JdbcRecordCursor @@ -44,6 +50,7 @@ public class JdbcRecordCursor private final DoubleReadFunction[] doubleReadFunctions; private final LongReadFunction[] longReadFunctions; private final SliceReadFunction[] sliceReadFunctions; + private final ObjectReadFunction[] objectReadFunctions; private final JdbcClient jdbcClient; private final Connection connection; @@ -61,12 +68,13 @@ public JdbcRecordCursor(JdbcClient jdbcClient, ConnectorSession session, JdbcSpl doubleReadFunctions = new DoubleReadFunction[columnHandles.size()]; longReadFunctions = new LongReadFunction[columnHandles.size()]; sliceReadFunctions = new SliceReadFunction[columnHandles.size()]; + objectReadFunctions = new ObjectReadFunction[columnHandles.size()]; for (int i = 0; i < this.columnHandles.length; i++) { - ReadMapping readMapping = jdbcClient.toPrestoType(session, columnHandles.get(i).getJdbcTypeHandle()) + ColumnMapping columnMapping = jdbcClient.toPrestoType(session, columnHandles.get(i).getJdbcTypeHandle()) .orElseThrow(() -> new VerifyException("Unsupported column type")); - Class javaType = readMapping.getType().getJavaType(); - ReadFunction readFunction = readMapping.getReadFunction(); + Class javaType = columnMapping.getType().getJavaType(); + ReadFunction readFunction = columnMapping.getReadFunction(); if (javaType == boolean.class) { booleanReadFunctions[i] = (BooleanReadFunction) readFunction; @@ -81,7 +89,7 @@ else if (javaType == Slice.class) { sliceReadFunctions[i] = (SliceReadFunction) readFunction; } else { - throw new IllegalStateException(format("Unsupported java type %s", javaType)); + objectReadFunctions[i] = (ObjectReadFunction) readFunction; } } @@ -180,7 +188,13 @@ public Slice getSlice(int field) @Override public Object getObject(int field) { - throw new UnsupportedOperationException(); + checkState(!closed, "cursor is closed"); + try { + return objectReadFunctions[field].readObject(resultSet, field + 1); + } + catch (SQLException | RuntimeException e) { + throw handleSqlException(e); + } } @Override diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/QueryBuilder.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/QueryBuilder.java index 65c9c703a3d2c..6526798f00809 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/QueryBuilder.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/QueryBuilder.java @@ -31,38 +31,40 @@ import com.facebook.presto.common.type.TinyintType; import com.facebook.presto.common.type.Type; import com.facebook.presto.common.type.VarcharType; +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.ObjectWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; import com.facebook.presto.plugin.jdbc.optimization.JdbcExpression; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ConnectorSession; +import com.facebook.presto.spi.PrestoException; import com.google.common.base.Joiner; +import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.Slice; -import org.joda.time.DateTimeZone; import java.sql.Connection; -import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Time; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; -import static com.facebook.presto.common.type.DateTimeEncoding.unpackMillisUtc; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.getColumnMappingFromPrestoType; +import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Iterables.getOnlyElement; -import static java.lang.Float.intBitsToFloat; import static java.lang.String.format; import static java.util.Collections.nCopies; import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.DAYS; import static java.util.stream.Collectors.joining; -import static org.joda.time.DateTimeZone.UTC; public class QueryBuilder { @@ -76,11 +78,13 @@ private static class TypeAndValue { private final Type type; private final Object value; + private final JdbcTypeHandle typeHandle; - public TypeAndValue(Type type, Object value) + public TypeAndValue(Type type, Object value, JdbcTypeHandle typeHandle) { this.type = requireNonNull(type, "type is null"); this.value = requireNonNull(value, "value is null"); + this.typeHandle = typeHandle; } public Type getType() @@ -92,6 +96,11 @@ public Object getValue() { return value; } + + public JdbcTypeHandle getTypeHandle() + { + return typeHandle; + } } public QueryBuilder(String quote) @@ -134,67 +143,51 @@ public PreparedStatement buildSql( .addAll(clauses) .add(additionalPredicate.get().getExpression()) .build(); + + /* We are passing JdbcTypeHandle as null and later on getting the columnMapping based on presto type itself since there is no way of getting the JdbcTypeHandle at this place. JdbcTypeHandle can be fetched only from JdbcColumnHandle, and we do not have that object in this lambda. The accumulator has no good use since the corresponding function mapping is not rich enough currently and also addition of accumulator results in duplicate where clauses which does not solve any purpose (at least for [equals operator](https://github.com/prestodb/presto/blob/e1f8b4b7adcbec565215b4d81e688f76634db6ed/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/optimization/function/OperatorTranslators.java#L49) ). + The basic idea behind introducing ColumnMapping was to cater to requirements wherein we want to have control over the writeFunctions. This is particularly useful where we try to map some presto type to some other presto type (e.g array type mapped to json) or for mapping unsupported types to string. The visitFilter method in JdbcComputePushdown() is responsible for creating additionalPredicates. The where clause passed in the query ultimately needs to go through the functionMapping [here](https://github.com/prestodb/presto/blob/e1f8b4b7adcbec565215b4d81e688f76634db6ed/presto-expressions/src/main/java/com/facebook/presto/expressions/translator/FunctionTranslator.java#L49) as part of visitFilter execution. + The idea here is functionMapping contains standard operators on standard presto types (currently only bigint) and any new functions added will also mostly cater to other standard types, and it seems very unlikely that some user is trying to map array type to json type along with having where clause on this array type itself. Or some user is trying to map some unsupported type as string and trying to use this unsupported type in where clause as well. https://github.com/trinodb/trino/pull/186#issuecomment-462114999 also suggest that these non standard types are only supported to be able to fetch the data for analytics purpose, and we do not intend to have any additional support for them. + So the current code works well until any of above situations arises and more functions are added in presto which belong to these non standard types for row expression translation. + */ accumulator.addAll(additionalPredicate.get().getBoundConstantValues().stream() - .map(constantExpression -> new TypeAndValue(constantExpression.getType(), constantExpression.getValue())) + .map(constantExpression -> new TypeAndValue(constantExpression.getType(), constantExpression.getValue(), null)) .collect(ImmutableList.toImmutableList())); } if (!clauses.isEmpty()) { sql.append(" WHERE ") .append(Joiner.on(" AND ").join(clauses)); } - sql.append(String.format("/* %s : %s */", session.getUser(), session.getQueryId())); + sql.append(format("/* %s : %s */", session.getUser(), session.getQueryId())); PreparedStatement statement = client.getPreparedStatement(session, connection, sql.toString()); for (int i = 0; i < accumulator.size(); i++) { TypeAndValue typeAndValue = accumulator.get(i); - if (typeAndValue.getType().equals(BigintType.BIGINT)) { - statement.setLong(i + 1, (long) typeAndValue.getValue()); - } - else if (typeAndValue.getType().equals(IntegerType.INTEGER)) { - statement.setInt(i + 1, ((Number) typeAndValue.getValue()).intValue()); - } - else if (typeAndValue.getType().equals(SmallintType.SMALLINT)) { - statement.setShort(i + 1, ((Number) typeAndValue.getValue()).shortValue()); - } - else if (typeAndValue.getType().equals(TinyintType.TINYINT)) { - statement.setByte(i + 1, ((Number) typeAndValue.getValue()).byteValue()); - } - else if (typeAndValue.getType().equals(DoubleType.DOUBLE)) { - statement.setDouble(i + 1, (double) typeAndValue.getValue()); - } - else if (typeAndValue.getType().equals(RealType.REAL)) { - statement.setFloat(i + 1, intBitsToFloat(((Number) typeAndValue.getValue()).intValue())); - } - else if (typeAndValue.getType().equals(BooleanType.BOOLEAN)) { - statement.setBoolean(i + 1, (boolean) typeAndValue.getValue()); - } - else if (typeAndValue.getType().equals(DateType.DATE)) { - long millis = DAYS.toMillis((long) typeAndValue.getValue()); - statement.setDate(i + 1, new Date(UTC.getMillisKeepLocal(DateTimeZone.getDefault(), millis))); + int parameterIndex = i + 1; + Type type = typeAndValue.getType(); + WriteFunction writeFunction = getWriteFunction(typeAndValue.getTypeHandle(), client, session, type); + Class javaType = type.getJavaType(); + Object value = typeAndValue.getValue(); + if (javaType == boolean.class) { + ((BooleanWriteFunction) writeFunction).set(statement, parameterIndex, (boolean) value); } - else if (typeAndValue.getType().equals(TimeType.TIME)) { - statement.setTime(i + 1, new Time((long) typeAndValue.getValue())); + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameterIndex, (double) value); } - else if (typeAndValue.getType().equals(TimeWithTimeZoneType.TIME_WITH_TIME_ZONE)) { - statement.setTime(i + 1, new Time(unpackMillisUtc((long) typeAndValue.getValue()))); + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameterIndex, (long) value); } - else if (typeAndValue.getType().equals(TimestampType.TIMESTAMP)) { - statement.setTimestamp(i + 1, new Timestamp((long) typeAndValue.getValue())); - } - else if (typeAndValue.getType().equals(TimestampWithTimeZoneType.TIMESTAMP_WITH_TIME_ZONE)) { - statement.setTimestamp(i + 1, new Timestamp(unpackMillisUtc((long) typeAndValue.getValue()))); - } - else if (typeAndValue.getType() instanceof VarcharType) { - statement.setString(i + 1, ((Slice) typeAndValue.getValue()).toStringUtf8()); - } - else if (typeAndValue.getType() instanceof CharType) { - statement.setString(i + 1, ((Slice) typeAndValue.getValue()).toStringUtf8()); + else if (javaType == Slice.class) { + ((SliceWriteFunction) writeFunction).set(statement, parameterIndex, (Slice) value); } else { - throw new UnsupportedOperationException("Can't handle type: " + typeAndValue.getType()); + try { + ((ObjectWriteFunction) writeFunction).set(statement, parameterIndex, value); + } + catch (SQLException e) { + throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); + } } } - return statement; } @@ -232,7 +225,7 @@ private String addColumns(List columns, Map co .collect(joining(", ")); } - private static boolean isAcceptedType(Type type) + protected boolean isAcceptedType(Type type) { Type validType = requireNonNull(type, "type is null"); return validType.equals(BigintType.BIGINT) || @@ -251,6 +244,18 @@ private static boolean isAcceptedType(Type type) validType instanceof CharType; } + private WriteFunction getWriteFunction(JdbcTypeHandle typeHandle, JdbcClient client, ConnectorSession session, Type type) + { + if (typeHandle != null) { + return client.toPrestoType(session, typeHandle) + .orElseThrow(() -> new VerifyException(format("Unsupported type %s with handle %s", type, typeHandle))) + .getWriteFunction(); + } + return getColumnMappingFromPrestoType(type) + .orElseThrow(() -> new VerifyException(format("Unsupported type %s for adding in accumulators", type))) + .getWriteFunction(); + } + private List toConjuncts(List columns, TupleDomain tupleDomain, List accumulator) { ImmutableList.Builder builder = ImmutableList.builder(); @@ -259,14 +264,14 @@ private List toConjuncts(List columns, TupleDomain accumulator) + private String toPredicate(String columnName, Domain domain, JdbcColumnHandle columnHandle, List accumulator) { checkArgument(domain.getType().isOrderable(), "Domain type must be orderable"); @@ -288,10 +293,10 @@ private String toPredicate(String columnName, Domain domain, Type type, List rangeConjuncts = new ArrayList<>(); if (!range.isLowUnbounded()) { - rangeConjuncts.add(toPredicate(columnName, range.isLowInclusive() ? ">=" : ">", range.getLowBoundedValue(), type, accumulator)); + rangeConjuncts.add(toPredicate(columnName, range.isLowInclusive() ? ">=" : ">", range.getLowBoundedValue(), columnHandle, accumulator)); } if (!range.isHighUnbounded()) { - rangeConjuncts.add(toPredicate(columnName, range.isHighInclusive() ? "<=" : "<", range.getHighBoundedValue(), type, accumulator)); + rangeConjuncts.add(toPredicate(columnName, range.isHighInclusive() ? "<=" : "<", range.getHighBoundedValue(), columnHandle, accumulator)); } // If rangeConjuncts is null, then the range was ALL, which should already have been checked for checkState(!rangeConjuncts.isEmpty()); @@ -301,11 +306,11 @@ private String toPredicate(String columnName, Domain domain, Type type, List 1) { for (Object value : singleValues) { - bindValue(value, type, accumulator); + bindValue(value, columnHandle, accumulator); } String values = Joiner.on(",").join(nCopies(singleValues.size(), "?")); disjuncts.add(quote(columnName) + " IN (" + values + ")"); @@ -320,9 +325,9 @@ else if (singleValues.size() > 1) { return "(" + Joiner.on(" OR ").join(disjuncts) + ")"; } - private String toPredicate(String columnName, String operator, Object value, Type type, List accumulator) + private String toPredicate(String columnName, String operator, Object value, JdbcColumnHandle columnHandle, List accumulator) { - bindValue(value, type, accumulator); + bindValue(value, columnHandle, accumulator); return quote(columnName) + " " + operator + " ?"; } @@ -337,9 +342,9 @@ public static String quote(String identifierQuote, String name) return identifierQuote + name + identifierQuote; } - private static void bindValue(Object value, Type type, List accumulator) + private static void bindValue(Object value, JdbcColumnHandle columnHandle, List accumulator) { - checkArgument(isAcceptedType(type), "Can't handle type: %s", type); - accumulator.add(new TypeAndValue(type, value)); + Type type = columnHandle.getColumnType(); + accumulator.add(new TypeAndValue(type, value, columnHandle.getJdbcTypeHandle())); } } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadMapping.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadMapping.java deleted file mode 100644 index 4751a84765c06..0000000000000 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadMapping.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.facebook.presto.plugin.jdbc; - -import com.facebook.presto.common.type.Type; - -import static com.google.common.base.MoreObjects.toStringHelper; -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.requireNonNull; - -public final class ReadMapping -{ - public static ReadMapping booleanReadMapping(Type prestoType, BooleanReadFunction readFunction) - { - return new ReadMapping(prestoType, readFunction); - } - - public static ReadMapping longReadMapping(Type prestoType, LongReadFunction readFunction) - { - return new ReadMapping(prestoType, readFunction); - } - - public static ReadMapping doubleReadMapping(Type prestoType, DoubleReadFunction readFunction) - { - return new ReadMapping(prestoType, readFunction); - } - - public static ReadMapping sliceReadMapping(Type prestoType, SliceReadFunction readFunction) - { - return new ReadMapping(prestoType, readFunction); - } - - private final Type type; - private final ReadFunction readFunction; - - private ReadMapping(Type type, ReadFunction readFunction) - { - this.type = requireNonNull(type, "type is null"); - this.readFunction = requireNonNull(readFunction, "readFunction is null"); - checkArgument( - type.getJavaType() == readFunction.getJavaType(), - "Presto type %s is not compatible with read function %s returning %s", - type, - readFunction, - readFunction.getJavaType()); - } - - public Type getType() - { - return type; - } - - public ReadFunction getReadFunction() - { - return readFunction; - } - - @Override - public String toString() - { - return toStringHelper(this) - .add("type", type) - .toString(); - } -} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardReadMappings.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardReadMappings.java deleted file mode 100644 index 182997e37036c..0000000000000 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardReadMappings.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.facebook.presto.plugin.jdbc; - -import com.facebook.presto.common.type.CharType; -import com.facebook.presto.common.type.DecimalType; -import com.facebook.presto.common.type.Decimals; -import com.facebook.presto.common.type.VarcharType; -import com.google.common.base.CharMatcher; -import org.joda.time.chrono.ISOChronology; - -import java.sql.ResultSet; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.Optional; - -import static com.facebook.presto.common.type.BigintType.BIGINT; -import static com.facebook.presto.common.type.BooleanType.BOOLEAN; -import static com.facebook.presto.common.type.CharType.createCharType; -import static com.facebook.presto.common.type.DateType.DATE; -import static com.facebook.presto.common.type.DecimalType.createDecimalType; -import static com.facebook.presto.common.type.Decimals.encodeScaledValue; -import static com.facebook.presto.common.type.Decimals.encodeShortScaledValue; -import static com.facebook.presto.common.type.DoubleType.DOUBLE; -import static com.facebook.presto.common.type.IntegerType.INTEGER; -import static com.facebook.presto.common.type.RealType.REAL; -import static com.facebook.presto.common.type.SmallintType.SMALLINT; -import static com.facebook.presto.common.type.TimeType.TIME; -import static com.facebook.presto.common.type.TimestampType.TIMESTAMP; -import static com.facebook.presto.common.type.TinyintType.TINYINT; -import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; -import static com.facebook.presto.common.type.VarcharType.createUnboundedVarcharType; -import static com.facebook.presto.common.type.VarcharType.createVarcharType; -import static com.facebook.presto.plugin.jdbc.ReadMapping.longReadMapping; -import static com.facebook.presto.plugin.jdbc.ReadMapping.sliceReadMapping; -import static io.airlift.slice.Slices.utf8Slice; -import static io.airlift.slice.Slices.wrappedBuffer; -import static java.lang.Float.floatToRawIntBits; -import static java.lang.Math.max; -import static java.lang.Math.min; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.joda.time.DateTimeZone.UTC; - -public final class StandardReadMappings -{ - private StandardReadMappings() {} - - private static final ISOChronology UTC_CHRONOLOGY = ISOChronology.getInstanceUTC(); - - public static ReadMapping booleanReadMapping() - { - return ReadMapping.booleanReadMapping(BOOLEAN, ResultSet::getBoolean); - } - - public static ReadMapping tinyintReadMapping() - { - return longReadMapping(TINYINT, ResultSet::getByte); - } - - public static ReadMapping smallintReadMapping() - { - return longReadMapping(SMALLINT, ResultSet::getShort); - } - - public static ReadMapping integerReadMapping() - { - return longReadMapping(INTEGER, ResultSet::getInt); - } - - public static ReadMapping bigintReadMapping() - { - return longReadMapping(BIGINT, ResultSet::getLong); - } - - public static ReadMapping realReadMapping() - { - return longReadMapping(REAL, (resultSet, columnIndex) -> floatToRawIntBits(resultSet.getFloat(columnIndex))); - } - - public static ReadMapping doubleReadMapping() - { - return ReadMapping.doubleReadMapping(DOUBLE, ResultSet::getDouble); - } - - public static ReadMapping decimalReadMapping(DecimalType decimalType) - { - // JDBC driver can return BigDecimal with lower scale than column's scale when there are trailing zeroes - int scale = decimalType.getScale(); - if (decimalType.isShort()) { - return longReadMapping(decimalType, (resultSet, columnIndex) -> encodeShortScaledValue(resultSet.getBigDecimal(columnIndex), scale)); - } - return sliceReadMapping(decimalType, (resultSet, columnIndex) -> encodeScaledValue(resultSet.getBigDecimal(columnIndex), scale)); - } - - public static ReadMapping charReadMapping(CharType charType) - { - requireNonNull(charType, "charType is null"); - return sliceReadMapping(charType, (resultSet, columnIndex) -> utf8Slice(CharMatcher.is(' ').trimTrailingFrom(resultSet.getString(columnIndex)))); - } - - public static ReadMapping varcharReadMapping(VarcharType varcharType) - { - return sliceReadMapping(varcharType, (resultSet, columnIndex) -> utf8Slice(resultSet.getString(columnIndex))); - } - - public static ReadMapping varbinaryReadMapping() - { - return sliceReadMapping(VARBINARY, (resultSet, columnIndex) -> wrappedBuffer(resultSet.getBytes(columnIndex))); - } - - public static ReadMapping dateReadMapping() - { - return longReadMapping(DATE, (resultSet, columnIndex) -> { - /* - * JDBC returns a date using a timestamp at midnight in the JVM timezone, or earliest time after that if there was no midnight. - * This works correctly for all dates and zones except when the missing local times 'gap' is 24h. I.e. this fails when JVM time - * zone is Pacific/Apia and date to be returned is 2011-12-30. - * - * `return resultSet.getObject(columnIndex, LocalDate.class).toEpochDay()` avoids these problems but - * is currently known not to work with Redshift (old Postgres connector) and SQL Server. - */ - long localMillis = resultSet.getDate(columnIndex).getTime(); - // Convert it to a ~midnight in UTC. - long utcMillis = ISOChronology.getInstance().getZone().getMillisKeepLocal(UTC, localMillis); - // convert to days - return MILLISECONDS.toDays(utcMillis); - }); - } - - public static ReadMapping timeReadMapping() - { - return longReadMapping(TIME, (resultSet, columnIndex) -> { - /* - * TODO `resultSet.getTime(columnIndex)` returns wrong value if JVM's zone had forward offset change during 1970-01-01 - * and the time value being retrieved was not present in local time (a 'gap'), e.g. time retrieved is 00:10:00 and JVM zone is America/Hermosillo - * The problem can be averted by using `resultSet.getObject(columnIndex, LocalTime.class)` -- but this is not universally supported by JDBC drivers. - */ - Time time = resultSet.getTime(columnIndex); - return UTC_CHRONOLOGY.millisOfDay().get(time.getTime()); - }); - } - - public static ReadMapping timestampReadMapping() - { - return longReadMapping(TIMESTAMP, (resultSet, columnIndex) -> { - /* - * TODO `resultSet.getTimestamp(columnIndex)` returns wrong value if JVM's zone had forward offset change and the local time - * corresponding to timestamp value being retrieved was not present (a 'gap'), this includes regular DST changes (e.g. Europe/Warsaw) - * and one-time policy changes (Asia/Kathmandu's shift by 15 minutes on January 1, 1986, 00:00:00). - * The problem can be averted by using `resultSet.getObject(columnIndex, LocalDateTime.class)` -- but this is not universally supported by JDBC drivers. - */ - Timestamp timestamp = resultSet.getTimestamp(columnIndex); - return timestamp.getTime(); - }); - } - - public static Optional jdbcTypeToPrestoType(JdbcTypeHandle type) - { - int columnSize = type.getColumnSize(); - switch (type.getJdbcType()) { - case Types.BIT: - case Types.BOOLEAN: - return Optional.of(booleanReadMapping()); - - case Types.TINYINT: - return Optional.of(tinyintReadMapping()); - - case Types.SMALLINT: - return Optional.of(smallintReadMapping()); - - case Types.INTEGER: - return Optional.of(integerReadMapping()); - - case Types.BIGINT: - return Optional.of(bigintReadMapping()); - - case Types.REAL: - return Optional.of(realReadMapping()); - - case Types.FLOAT: - case Types.DOUBLE: - return Optional.of(doubleReadMapping()); - - case Types.NUMERIC: - case Types.DECIMAL: - int decimalDigits = type.getDecimalDigits(); - int precision = columnSize + max(-decimalDigits, 0); // Map decimal(p, -s) (negative scale) to decimal(p+s, 0). - if (precision > Decimals.MAX_PRECISION) { - return Optional.empty(); - } - return Optional.of(decimalReadMapping(createDecimalType(precision, max(decimalDigits, 0)))); - - case Types.CHAR: - case Types.NCHAR: - // TODO this is wrong, we're going to construct malformed Slice representation if source > charLength - int charLength = min(columnSize, CharType.MAX_LENGTH); - return Optional.of(charReadMapping(createCharType(charLength))); - - case Types.VARCHAR: - case Types.NVARCHAR: - case Types.LONGVARCHAR: - case Types.LONGNVARCHAR: - if (columnSize > VarcharType.MAX_LENGTH) { - return Optional.of(varcharReadMapping(createUnboundedVarcharType())); - } - return Optional.of(varcharReadMapping(createVarcharType(columnSize))); - - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - return Optional.of(varbinaryReadMapping()); - - case Types.DATE: - return Optional.of(dateReadMapping()); - - case Types.TIME: - return Optional.of(timeReadMapping()); - - case Types.TIMESTAMP: - return Optional.of(timestampReadMapping()); - } - return Optional.empty(); - } -} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ColumnMapping.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ColumnMapping.java new file mode 100644 index 0000000000000..aec1cd0bfd949 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ColumnMapping.java @@ -0,0 +1,109 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping; + +import com.facebook.presto.common.type.Type; +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.ObjectReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.ObjectWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +/* + * JDBC based connectors can control how data should be read from a ResultSet via ReadMapping definitions. But writing was + * hard-coded in JdbcPageSink#appendColumn. This class provides the control to define how data should be written back to + * data source by introducing WriteFunctions. + */ +public final class ColumnMapping +{ + public static ColumnMapping booleanMapping(Type prestoType, BooleanReadFunction readFunction, BooleanWriteFunction writeFunction) + { + return new ColumnMapping(prestoType, readFunction, writeFunction); + } + + public static ColumnMapping longMapping(Type prestoType, LongReadFunction readFunction, LongWriteFunction writeFunction) + { + return new ColumnMapping(prestoType, readFunction, writeFunction); + } + + public static ColumnMapping doubleMapping(Type prestoType, DoubleReadFunction readFunction, DoubleWriteFunction writeFunction) + { + return new ColumnMapping(prestoType, readFunction, writeFunction); + } + + public static ColumnMapping sliceMapping(Type prestoType, SliceReadFunction readFunction, SliceWriteFunction writeFunction) + { + return new ColumnMapping(prestoType, readFunction, writeFunction); + } + + public static ColumnMapping objectMapping(Type prestoType, ObjectReadFunction readFunction, ObjectWriteFunction writeFunction) + { + return new ColumnMapping(prestoType, readFunction, writeFunction); + } + + private final Type type; + private final ReadFunction readFunction; + private final WriteFunction writeFunction; + + private ColumnMapping(Type type, ReadFunction readFunction, WriteFunction writeFunction) + { + this.type = requireNonNull(type, "type is null"); + this.readFunction = requireNonNull(readFunction, "readFunction is null"); + this.writeFunction = requireNonNull(writeFunction, "writeFunction is null"); + checkArgument( + type.getJavaType() == readFunction.getJavaType(), + "Presto type %s is not compatible with read function %s using %s", + type, + readFunction, + readFunction.getJavaType()); + checkArgument( + type.getJavaType() == writeFunction.getJavaType(), + "Presto type %s is not compatible with write function %s using %s", + type, + writeFunction, + writeFunction.getJavaType()); + } + + public Type getType() + { + return type; + } + + public ReadFunction getReadFunction() + { + return readFunction; + } + + public WriteFunction getWriteFunction() + { + return writeFunction; + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("type", type) + .toString(); + } +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ReadFunction.java similarity index 94% rename from presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadFunction.java rename to presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ReadFunction.java index 80a9c78f765e5..6c576da5c5d7a 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadFunction.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ReadFunction.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.facebook.presto.plugin.jdbc; +package com.facebook.presto.plugin.jdbc.mapping; public interface ReadFunction { diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/StandardColumnMappings.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/StandardColumnMappings.java new file mode 100644 index 0000000000000..257b7ad8edc38 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/StandardColumnMappings.java @@ -0,0 +1,385 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping; + +import com.facebook.presto.common.type.BigintType; +import com.facebook.presto.common.type.BooleanType; +import com.facebook.presto.common.type.CharType; +import com.facebook.presto.common.type.DateType; +import com.facebook.presto.common.type.DecimalType; +import com.facebook.presto.common.type.Decimals; +import com.facebook.presto.common.type.DoubleType; +import com.facebook.presto.common.type.IntegerType; +import com.facebook.presto.common.type.RealType; +import com.facebook.presto.common.type.SmallintType; +import com.facebook.presto.common.type.TimeType; +import com.facebook.presto.common.type.TimeWithTimeZoneType; +import com.facebook.presto.common.type.TimestampType; +import com.facebook.presto.common.type.TimestampWithTimeZoneType; +import com.facebook.presto.common.type.TinyintType; +import com.facebook.presto.common.type.Type; +import com.facebook.presto.common.type.UuidType; +import com.facebook.presto.common.type.VarbinaryType; +import com.facebook.presto.common.type.VarcharType; +import com.facebook.presto.plugin.jdbc.JdbcTypeHandle; +import com.google.common.base.CharMatcher; +import com.google.common.primitives.Shorts; +import com.google.common.primitives.SignedBytes; +import org.joda.time.DateTimeZone; +import org.joda.time.chrono.ISOChronology; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Instant; +import java.util.Optional; + +import static com.facebook.presto.common.type.BigintType.BIGINT; +import static com.facebook.presto.common.type.BooleanType.BOOLEAN; +import static com.facebook.presto.common.type.CharType.createCharType; +import static com.facebook.presto.common.type.DateTimeEncoding.unpackMillisUtc; +import static com.facebook.presto.common.type.DateType.DATE; +import static com.facebook.presto.common.type.DecimalType.createDecimalType; +import static com.facebook.presto.common.type.Decimals.decodeUnscaledValue; +import static com.facebook.presto.common.type.Decimals.encodeScaledValue; +import static com.facebook.presto.common.type.Decimals.encodeShortScaledValue; +import static com.facebook.presto.common.type.DoubleType.DOUBLE; +import static com.facebook.presto.common.type.IntegerType.INTEGER; +import static com.facebook.presto.common.type.RealType.REAL; +import static com.facebook.presto.common.type.SmallintType.SMALLINT; +import static com.facebook.presto.common.type.TimeType.TIME; +import static com.facebook.presto.common.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static com.facebook.presto.common.type.TimestampType.TIMESTAMP; +import static com.facebook.presto.common.type.TimestampWithTimeZoneType.TIMESTAMP_WITH_TIME_ZONE; +import static com.facebook.presto.common.type.TinyintType.TINYINT; +import static com.facebook.presto.common.type.UuidType.UUID; +import static com.facebook.presto.common.type.UuidType.javaUuidToPrestoUuid; +import static com.facebook.presto.common.type.UuidType.prestoUuidToJavaUuid; +import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; +import static com.facebook.presto.common.type.VarcharType.createUnboundedVarcharType; +import static com.facebook.presto.common.type.VarcharType.createVarcharType; +import static com.facebook.presto.plugin.jdbc.mapping.ColumnMapping.booleanMapping; +import static com.facebook.presto.plugin.jdbc.mapping.ColumnMapping.doubleMapping; +import static com.facebook.presto.plugin.jdbc.mapping.ColumnMapping.longMapping; +import static com.facebook.presto.plugin.jdbc.mapping.ColumnMapping.sliceMapping; +import static io.airlift.slice.Slices.utf8Slice; +import static io.airlift.slice.Slices.wrappedBuffer; +import static java.lang.Float.floatToRawIntBits; +import static java.lang.Float.intBitsToFloat; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.Math.toIntExact; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.joda.time.DateTimeZone.UTC; + +public final class StandardColumnMappings +{ + private StandardColumnMappings() {} + + private static final ISOChronology UTC_CHRONOLOGY = ISOChronology.getInstanceUTC(); + + public static ColumnMapping booleanColumnMapping() + { + return booleanMapping(BOOLEAN, ResultSet::getBoolean, PreparedStatement::setBoolean); + } + + public static ColumnMapping tinyintColumnMapping() + { + return longMapping(TINYINT, ResultSet::getByte, + ((statement, index, value) -> statement.setByte(index, SignedBytes.checkedCast(value)))); + } + + public static ColumnMapping smallintColumnMapping() + { + return longMapping(SMALLINT, ResultSet::getShort, + ((statement, index, value) -> statement.setShort(index, Shorts.checkedCast(value)))); + } + + public static ColumnMapping integerColumnMapping() + { + return longMapping(INTEGER, ResultSet::getInt, + (((statement, index, value) -> statement.setInt(index, toIntExact(value))))); + } + + public static ColumnMapping bigintColumnMapping() + { + return longMapping(BIGINT, ResultSet::getLong, PreparedStatement::setLong); + } + + public static ColumnMapping realColumnMapping() + { + return longMapping(REAL, (resultSet, columnIndex) -> floatToRawIntBits(resultSet.getFloat(columnIndex)), + ((statement, index, value) -> statement.setFloat(index, intBitsToFloat(toIntExact(value))))); + } + + public static ColumnMapping doubleColumnMapping() + { + return doubleMapping(DOUBLE, ResultSet::getDouble, PreparedStatement::setDouble); + } + + public static ColumnMapping decimalColumnMapping(DecimalType decimalType) + { + // JDBC driver can return BigDecimal with lower scale than column's scale when there are trailing zeroes + int scale = decimalType.getScale(); + if (decimalType.isShort()) { + return longMapping(decimalType, (resultSet, columnIndex) -> encodeShortScaledValue(resultSet.getBigDecimal(columnIndex), scale), + ((statement, index, value) -> { + BigInteger unscaledValue = BigInteger.valueOf(value); + BigDecimal bigDecimal = new BigDecimal(unscaledValue, decimalType.getScale(), new MathContext(decimalType.getPrecision())); + statement.setBigDecimal(index, bigDecimal); + })); + } + return sliceMapping(decimalType, (resultSet, columnIndex) -> encodeScaledValue(resultSet.getBigDecimal(columnIndex), scale), + ((statement, index, value) -> { + BigInteger unscaledValue = decodeUnscaledValue(value); + BigDecimal bigDecimal = new BigDecimal(unscaledValue, decimalType.getScale(), new MathContext(decimalType.getPrecision())); + statement.setBigDecimal(index, bigDecimal); + })); + } + + public static ColumnMapping charColumnMapping(CharType charType) + { + requireNonNull(charType, "charType is null"); + return sliceMapping(charType, (resultSet, columnIndex) -> utf8Slice(CharMatcher.is(' ').trimTrailingFrom(resultSet.getString(columnIndex))), + ((statement, index, value) -> statement.setString(index, value.toStringUtf8()))); + } + + public static ColumnMapping varcharColumnMapping(VarcharType varcharType) + { + return sliceMapping(varcharType, (resultSet, columnIndex) -> utf8Slice(resultSet.getString(columnIndex)), + ((statement, index, value) -> statement.setString(index, value.toStringUtf8()))); + } + + public static ColumnMapping varbinaryColumnMapping() + { + return sliceMapping(VARBINARY, (resultSet, columnIndex) -> wrappedBuffer(resultSet.getBytes(columnIndex)), + ((statement, index, value) -> statement.setBytes(index, value.getBytes()))); + } + + public static ColumnMapping dateColumnMapping() + { + return longMapping(DATE, (resultSet, columnIndex) -> { + /* + * JDBC returns a date using a timestamp at midnight in the JVM timezone, or earliest time after that if there was no midnight. + * This works correctly for all dates and zones except when the missing local times 'gap' is 24h. I.e. this fails when JVM time + * zone is Pacific/Apia and date to be returned is 2011-12-30. + * + * `return resultSet.getObject(columnIndex, LocalDate.class).toEpochDay()` avoids these problems but + * is currently known not to work with Redshift (old Postgres connector) and SQL Server. + */ + long localMillis = resultSet.getDate(columnIndex).getTime(); + // Convert it to a ~midnight in UTC. + long utcMillis = ISOChronology.getInstance().getZone().getMillisKeepLocal(UTC, localMillis); + // convert to days + return MILLISECONDS.toDays(utcMillis); + }, + ((statement, index, value) -> statement.setDate(index, new Date(UTC.getMillisKeepLocal + (DateTimeZone.getDefault(), DAYS.toMillis(value)))))); + } + + public static ColumnMapping timeColumnMapping() + { + return longMapping(TIME, (resultSet, columnIndex) -> { + /* + * TODO `resultSet.getTime(columnIndex)` returns wrong value if JVM's zone had forward offset change during 1970-01-01 + * and the time value being retrieved was not present in local time (a 'gap'), e.g. time retrieved is 00:10:00 and JVM zone is America/Hermosillo + * The problem can be averted by using `resultSet.getObject(columnIndex, LocalTime.class)` -- but this is not universally supported by JDBC drivers. + */ + Time time = resultSet.getTime(columnIndex); + return UTC_CHRONOLOGY.millisOfDay().get(time.getTime()); + }, + ((statement, index, value) -> statement.setTime(index, new Time(value)))); + } + + public static ColumnMapping timestampColumnMapping(TimestampType type) + { + return longMapping(TIMESTAMP, (resultSet, columnIndex) -> { + /* + * TODO `resultSet.getTimestamp(columnIndex)` returns wrong value if JVM's zone had forward offset change and the local time + * corresponding to timestamp value being retrieved was not present (a 'gap'), this includes regular DST changes (e.g. Europe/Warsaw) + * and one-time policy changes (Asia/Kathmandu's shift by 15 minutes on January 1, 1986, 00:00:00). + * The problem can be averted by using `resultSet.getObject(columnIndex, LocalDateTime.class)` -- but this is not universally supported by JDBC drivers. + */ + Timestamp timestamp = resultSet.getTimestamp(columnIndex); + return timestamp.getTime(); + }, (statement, index, value) -> statement.setTimestamp(index, Timestamp.from(Instant.ofEpochSecond( + type.getEpochSecond(value), + type.getNanos(value))))); + } + + public static ColumnMapping uuidColumnMapping() + { + return sliceMapping( + UUID, + (resultSet, columnIndex) -> javaUuidToPrestoUuid((java.util.UUID) resultSet.getObject(columnIndex)), + ((statement, index, value) -> statement.setObject(index, prestoUuidToJavaUuid(value)))); + } + + public static ColumnMapping timeWithTimeZoneColumnMapping() + { + return longMapping( + TIME_WITH_TIME_ZONE, + ((resultSet, columnIndex) -> resultSet.getTime(columnIndex).getTime()), + (((statement, index, value) -> statement.setTime(index, new Time(unpackMillisUtc(value)))))); + } + + public static ColumnMapping timestampWithTimeZoneColumnMapping() + { + return longMapping( + TIMESTAMP_WITH_TIME_ZONE, + ((resultSet, columnIndex) -> resultSet.getTimestamp(columnIndex).getTime()), + ((statement, index, value) -> statement.setTimestamp(index, new Timestamp(unpackMillisUtc(value))))); + } + + public static Optional jdbcTypeToPrestoType(JdbcTypeHandle type) + { + int columnSize = type.getColumnSize(); + switch (type.getJdbcType()) { + case Types.BIT: + case Types.BOOLEAN: + return Optional.of(booleanColumnMapping()); + + case Types.TINYINT: + return Optional.of(tinyintColumnMapping()); + + case Types.SMALLINT: + return Optional.of(smallintColumnMapping()); + + case Types.INTEGER: + return Optional.of(integerColumnMapping()); + + case Types.BIGINT: + return Optional.of(bigintColumnMapping()); + + case Types.REAL: + return Optional.of(realColumnMapping()); + + case Types.FLOAT: + case Types.DOUBLE: + return Optional.of(doubleColumnMapping()); + + case Types.NUMERIC: + case Types.DECIMAL: + int decimalDigits = type.getDecimalDigits(); + int precision = columnSize + max(-decimalDigits, 0); // Map decimal(p, -s) (negative scale) to decimal(p+s, 0). + if (precision > Decimals.MAX_PRECISION) { + return Optional.empty(); + } + return Optional.of(decimalColumnMapping(createDecimalType(precision, max(decimalDigits, 0)))); + + case Types.CHAR: + case Types.NCHAR: + // TODO this is wrong, we're going to construct malformed Slice representation if source > charLength + int charLength = min(columnSize, CharType.MAX_LENGTH); + return Optional.of(charColumnMapping(createCharType(charLength))); + + case Types.VARCHAR: + case Types.NVARCHAR: + case Types.LONGVARCHAR: + case Types.LONGNVARCHAR: + if (columnSize > VarcharType.MAX_LENGTH) { + return Optional.of(varcharColumnMapping(createUnboundedVarcharType())); + } + return Optional.of(varcharColumnMapping(createVarcharType(columnSize))); + + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + return Optional.of(varbinaryColumnMapping()); + + case Types.DATE: + return Optional.of(dateColumnMapping()); + case Types.TIME_WITH_TIMEZONE: + return Optional.of(timeWithTimeZoneColumnMapping()); + case Types.TIME: + return Optional.of(timeColumnMapping()); + case Types.TIMESTAMP_WITH_TIMEZONE: + return Optional.of(timestampWithTimeZoneColumnMapping()); + case Types.TIMESTAMP: + return Optional.of(timestampColumnMapping(TIMESTAMP)); + } + return Optional.empty(); + } + + public static Optional getColumnMappingFromPrestoType(Type type) + { + if (type instanceof BooleanType) { + return Optional.of(booleanColumnMapping()); + } + else if (type instanceof TinyintType) { + return Optional.of(tinyintColumnMapping()); + } + else if (type instanceof SmallintType) { + return Optional.of(smallintColumnMapping()); + } + else if (type instanceof IntegerType) { + return Optional.of(integerColumnMapping()); + } + else if (type instanceof BigintType) { + return Optional.of(bigintColumnMapping()); + } + else if (type instanceof RealType) { + return Optional.of(realColumnMapping()); + } + else if (type instanceof DoubleType) { + return Optional.of(doubleColumnMapping()); + } + else if (type instanceof DecimalType) { + int scale = ((DecimalType) type).getScale(); + int precision = ((DecimalType) type).getPrecision(); + if (precision > Decimals.MAX_PRECISION) { + return Optional.empty(); + } + return Optional.of(decimalColumnMapping(createDecimalType(precision, max(scale, 0)))); + } + else if (type instanceof CharType) { + //TODO: check if there is any way of getting the column size here + return Optional.of(charColumnMapping(createCharType(CharType.MAX_LENGTH))); + } + else if (type instanceof VarcharType) { + //TODO: check if there is any way of getting the actual column size here + return Optional.of(varcharColumnMapping(createUnboundedVarcharType())); + } + else if (type instanceof VarbinaryType) { + return Optional.of(varbinaryColumnMapping()); + } + else if (type instanceof DateType) { + return Optional.of(dateColumnMapping()); + } + else if (type instanceof TimeType) { + return Optional.of(timeColumnMapping()); + } + else if (type instanceof TimestampType) { + return Optional.of(timestampColumnMapping((TimestampType) type)); + } + else if (type instanceof TimeWithTimeZoneType) { + return Optional.of(timeWithTimeZoneColumnMapping()); + } + else if (type instanceof TimestampWithTimeZoneType) { + return Optional.of(timestampWithTimeZoneColumnMapping()); + } + else if (type instanceof UuidType) { + return Optional.of(uuidColumnMapping()); + } + return Optional.empty(); + } +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteFunction.java new file mode 100644 index 0000000000000..48ae87cff0be4 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteFunction.java @@ -0,0 +1,19 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping; + +public interface WriteFunction +{ + Class getJavaType(); +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteMapping.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteMapping.java new file mode 100644 index 0000000000000..acf12a71aa638 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteMapping.java @@ -0,0 +1,83 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping; + +import com.facebook.presto.plugin.jdbc.mapping.functions.BooleanWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.DoubleWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.ObjectWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public final class WriteMapping +{ + private final String dataType; + private final WriteFunction writeFunction; + + private WriteMapping(String dataType, WriteFunction writeFunction) + { + this.dataType = requireNonNull(dataType, "data type is null"); + this.writeFunction = requireNonNull(writeFunction, "writeFunction is null"); + } + + public static WriteMapping booleanMapping(String dataType, BooleanWriteFunction writeFunction) + { + return new WriteMapping(dataType, writeFunction); + } + + public static WriteMapping longMapping(String dataType, LongWriteFunction writeFunction) + { + return new WriteMapping(dataType, writeFunction); + } + + public static WriteMapping doubleMapping(String dataType, DoubleWriteFunction writeFunction) + { + return new WriteMapping(dataType, writeFunction); + } + + public static WriteMapping sliceMapping(String dataType, SliceWriteFunction writeFunction) + { + return new WriteMapping(dataType, writeFunction); + } + + public static WriteMapping objectMapping(String dataType, Class javaType, ObjectWriteFunction.ObjectWriteFunctionImplementation writeFunctionImplementation) + { + return objectMapping(dataType, ObjectWriteFunction.of(javaType, writeFunctionImplementation)); + } + + public static WriteMapping objectMapping(String dataType, ObjectWriteFunction writeFunction) + { + return new WriteMapping(dataType, writeFunction); + } + + public String getDataType() + { + return dataType; + } + + public WriteFunction getWriteFunction() + { + return writeFunction; + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("dataType", dataType) + .toString(); + } +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BooleanReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanReadFunction.java similarity index 88% rename from presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BooleanReadFunction.java rename to presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanReadFunction.java index 3ef463fcc3bd6..288358aa9f547 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BooleanReadFunction.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanReadFunction.java @@ -11,7 +11,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.facebook.presto.plugin.jdbc; +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.ReadFunction; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanWriteFunction.java new file mode 100644 index 0000000000000..161d3e22ba04d --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanWriteFunction.java @@ -0,0 +1,30 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface BooleanWriteFunction + extends WriteFunction +{ + default Class getJavaType() + { + return boolean.class; + } + + void set(PreparedStatement statement, int index, boolean value) throws SQLException; +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/DoubleReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleReadFunction.java similarity index 88% rename from presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/DoubleReadFunction.java rename to presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleReadFunction.java index 223cbe2f6ef5d..281d319821e80 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/DoubleReadFunction.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleReadFunction.java @@ -11,7 +11,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.facebook.presto.plugin.jdbc; +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.ReadFunction; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleWriteFunction.java new file mode 100644 index 0000000000000..11b17da63ce36 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleWriteFunction.java @@ -0,0 +1,30 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface DoubleWriteFunction + extends WriteFunction +{ + default Class getJavaType() + { + return double.class; + } + + void set(PreparedStatement statement, int index, double value) throws SQLException; +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/LongReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongReadFunction.java similarity index 87% rename from presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/LongReadFunction.java rename to presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongReadFunction.java index f126ce9ce2dcd..042e0443d20e5 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/LongReadFunction.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongReadFunction.java @@ -11,7 +11,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.facebook.presto.plugin.jdbc; +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.ReadFunction; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongWriteFunction.java new file mode 100644 index 0000000000000..5bc7c1707ba30 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongWriteFunction.java @@ -0,0 +1,30 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface LongWriteFunction + extends WriteFunction +{ + default Class getJavaType() + { + return long.class; + } + + void set(PreparedStatement statement, int index, long value) throws SQLException; +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectReadFunction.java new file mode 100644 index 0000000000000..f7a7149d948b3 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectReadFunction.java @@ -0,0 +1,55 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.ReadFunction; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static java.util.Objects.requireNonNull; + +public interface ObjectReadFunction + extends ReadFunction +{ + @Override + Class getJavaType(); + + Object readObject(ResultSet resultSet, int columnIndex) throws SQLException; + + static ObjectReadFunction of(Class javaType, ObjectReadFunctionImplementation implementation) + { + requireNonNull(javaType, "javaType is null"); + requireNonNull(implementation, "object read implementation is null"); + return new ObjectReadFunction() { + @Override + public Class getJavaType() + { + return javaType; + } + + @Override + public Object readObject(ResultSet resultSet, int columnIndex) throws SQLException + { + return implementation.read(resultSet, columnIndex); + } + }; + } + + @FunctionalInterface + interface ObjectReadFunctionImplementation + { + T read(ResultSet resultSet, int columnIndex) throws SQLException; + } +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectWriteFunction.java new file mode 100644 index 0000000000000..071392c9797be --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectWriteFunction.java @@ -0,0 +1,59 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import static java.util.Objects.requireNonNull; + +public interface ObjectWriteFunction + extends WriteFunction +{ + @Override + Class getJavaType(); + + void set(PreparedStatement statement, int index, Object value) throws SQLException; + + static ObjectWriteFunction of(Class javaType, ObjectWriteFunctionImplementation implementation) + { + requireNonNull(javaType, "javaType is null"); + requireNonNull(implementation, "implementation is null"); + + return new ObjectWriteFunction() + { + @Override + public Class getJavaType() + { + return javaType; + } + + @Override + @SuppressWarnings("unchecked") + public void set(PreparedStatement statement, int index, Object value) + throws SQLException + { + implementation.set(statement, index, (T) value); + } + }; + } + + @FunctionalInterface + interface ObjectWriteFunctionImplementation + { + void set(PreparedStatement statement, int index, T value) throws SQLException; + } +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/SliceReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceReadFunction.java similarity index 88% rename from presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/SliceReadFunction.java rename to presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceReadFunction.java index a71f442ab8972..1e8d7ff107774 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/SliceReadFunction.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceReadFunction.java @@ -11,8 +11,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.facebook.presto.plugin.jdbc; +package com.facebook.presto.plugin.jdbc.mapping.functions; +import com.facebook.presto.plugin.jdbc.mapping.ReadFunction; import io.airlift.slice.Slice; import java.sql.ResultSet; diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceWriteFunction.java new file mode 100644 index 0000000000000..088247f261104 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceWriteFunction.java @@ -0,0 +1,31 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.plugin.jdbc.mapping.functions; + +import com.facebook.presto.plugin.jdbc.mapping.WriteFunction; +import io.airlift.slice.Slice; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface SliceWriteFunction + extends WriteFunction +{ + default Class getJavaType() + { + return Slice.class; + } + + void set(PreparedStatement statement, int index, Slice value) throws SQLException; +} diff --git a/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java b/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java index 8626f65550fc5..a2a1a716e72c4 100644 --- a/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java +++ b/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java @@ -28,7 +28,10 @@ import com.facebook.presto.plugin.jdbc.JdbcTableHandle; import com.facebook.presto.plugin.jdbc.JdbcTypeHandle; import com.facebook.presto.plugin.jdbc.QueryBuilder; -import com.facebook.presto.plugin.jdbc.ReadMapping; +import com.facebook.presto.plugin.jdbc.mapping.ColumnMapping; +import com.facebook.presto.plugin.jdbc.mapping.WriteMapping; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -66,7 +69,12 @@ import static com.facebook.presto.plugin.jdbc.DriverConnectionFactory.basicConnectionProperties; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; import static com.facebook.presto.plugin.jdbc.QueryBuilder.quote; -import static com.facebook.presto.plugin.jdbc.ReadMapping.sliceReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.realColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.timestampColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varbinaryColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.longMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.sliceMapping; import static com.facebook.presto.spi.StandardErrorCode.ALREADY_EXISTS; import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; @@ -183,39 +191,43 @@ protected String getTableSchemaName(ResultSet resultSet) } @Override - protected String toSqlType(Type type) + public WriteMapping toWriteMapping(Type type) { if (REAL.equals(type)) { - return "float"; + return longMapping("float", (LongWriteFunction) realColumnMapping().getWriteFunction()); } if (TIME_WITH_TIME_ZONE.equals(type) || TIMESTAMP_WITH_TIME_ZONE.equals(type)) { throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } if (type instanceof TimestampType) { // In order to preserve microsecond information for TIMESTAMP_MICROSECONDS - return "datetime(6)"; + return longMapping("datetime(6)", (LongWriteFunction) timestampColumnMapping((TimestampType) type).getWriteFunction()); } if (VARBINARY.equals(type)) { - return "mediumblob"; + return sliceMapping("mediumblob", (SliceWriteFunction) varbinaryColumnMapping().getWriteFunction()); } if (isVarcharType(type)) { VarcharType varcharType = (VarcharType) type; + String dataType; if (varcharType.isUnbounded()) { - return "longtext"; + dataType = "longtext"; } - if (varcharType.getLengthSafe() <= 255) { - return "tinytext"; + else if (varcharType.getLengthSafe() <= 255) { + dataType = "tinytext"; } - if (varcharType.getLengthSafe() <= 65535) { - return "text"; + else if (varcharType.getLengthSafe() <= 65535) { + dataType = "text"; } - if (varcharType.getLengthSafe() <= 16777215) { - return "mediumtext"; + else if (varcharType.getLengthSafe() <= 16777215) { + dataType = "mediumtext"; } - return "longtext"; + else { + dataType = "longtext"; + } + return sliceMapping(dataType, (SliceWriteFunction) varcharColumnMapping(varcharType).getWriteFunction()); } - return super.toSqlType(type); + return super.toWriteMapping(type); } @Override @@ -242,21 +254,24 @@ public PreparedStatement buildSql(ConnectorSession session, Connection connectio } @Override - public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) + public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) { String typeName = typeHandle.getJdbcTypeName(); if (typeName.equalsIgnoreCase(GEOMETRY)) { - return Optional.of(geometryReadMapping()); + return Optional.of(geometryColumnMapping()); } return super.toPrestoType(session, typeHandle); } - protected static ReadMapping geometryReadMapping() + protected static ColumnMapping geometryColumnMapping() { - return sliceReadMapping(VARCHAR, - (resultSet, columnIndex) -> getAsText(stGeomFromBinary(wrappedBuffer(resultSet.getBytes(columnIndex))))); + return ColumnMapping.sliceMapping(VARCHAR, + (resultSet, columnIndex) -> getAsText(stGeomFromBinary(wrappedBuffer(resultSet.getBytes(columnIndex)))), + ((statement, index, value) -> { + throw new UnsupportedOperationException(); + })); } protected static Slice getAsText(Slice input) diff --git a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlClient.java b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlClient.java index 6ccb43cc207ba..e563472ab0e53 100644 --- a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlClient.java +++ b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlClient.java @@ -16,7 +16,7 @@ import com.esri.core.geometry.Point; import com.esri.core.geometry.ogc.OGCGeometry; import com.esri.core.geometry.ogc.OGCPoint; -import com.facebook.presto.plugin.jdbc.SliceReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceReadFunction; import com.facebook.presto.spi.PrestoException; import io.airlift.slice.Slice; import io.airlift.slice.Slices; @@ -27,7 +27,7 @@ import java.sql.SQLException; import static com.facebook.presto.geospatial.GeoFunctions.stGeomFromBinary; -import static com.facebook.presto.plugin.mysql.MySqlClient.geometryReadMapping; +import static com.facebook.presto.plugin.mysql.MySqlClient.geometryColumnMapping; import static com.facebook.presto.plugin.mysql.MySqlClient.getAsText; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; @@ -61,7 +61,7 @@ public void testInvalidGeometryReadMapping() @Test public void testReadMapping() throws SQLException { - SliceReadFunction fn = (SliceReadFunction) geometryReadMapping().getReadFunction(); + SliceReadFunction fn = (SliceReadFunction) geometryColumnMapping().getReadFunction(); OGCGeometry geometry = new OGCPoint(new Point(1.0, 2.0), null); ByteBuffer buffer = geometry.asBinary(); Slice value = fn.readSlice(new MockResultSet(buffer.array()), 1); diff --git a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlTypeMapping.java b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlTypeMapping.java index 92f3550f75784..b9c73e550a15b 100644 --- a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlTypeMapping.java +++ b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlTypeMapping.java @@ -261,13 +261,13 @@ public void testDate() @Test public void testDatetime() { - // TODO MySQL datetime is not correctly read (see comment in StandardReadMappings.timestampReadMapping), but testing this is hard because of #7122 + // TODO MySQL datetime is not correctly read (see comment in StandardColumnMappings.timestampReadMapping), but testing this is hard because of #7122 } @Test public void testTimestamp() { - // TODO MySQL timestamp is not correctly read (see comment in StandardReadMappings.timestampReadMapping), but testing this is hard because of #7122 + // TODO MySQL timestamp is not correctly read (see comment in StandardColumnMappings.timestampReadMapping), but testing this is hard because of #7122 } private void testUnsupportedDataType(String databaseDataType) diff --git a/presto-oracle/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java b/presto-oracle/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java index 67149ff9b4405..83593f1966c33 100644 --- a/presto-oracle/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java +++ b/presto-oracle/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java @@ -21,7 +21,7 @@ import com.facebook.presto.plugin.jdbc.JdbcConnectorId; import com.facebook.presto.plugin.jdbc.JdbcIdentity; import com.facebook.presto.plugin.jdbc.JdbcTypeHandle; -import com.facebook.presto.plugin.jdbc.ReadMapping; +import com.facebook.presto.plugin.jdbc.mapping.ColumnMapping; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.SchemaTableName; @@ -40,12 +40,12 @@ import static com.facebook.presto.common.type.VarcharType.createUnboundedVarcharType; import static com.facebook.presto.common.type.VarcharType.createVarcharType; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.bigintReadMapping; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.decimalReadMapping; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.doubleReadMapping; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.realReadMapping; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.smallintReadMapping; -import static com.facebook.presto.plugin.jdbc.StandardReadMappings.varcharReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.bigintColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.decimalColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.doubleColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.realColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.smallintColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharColumnMapping; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; import static java.lang.String.format; import static java.util.Locale.ENGLISH; @@ -131,39 +131,39 @@ protected void renameTable(JdbcIdentity identity, String catalogName, SchemaTabl } @Override - public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) + public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) { int columnSize = typeHandle.getColumnSize(); switch (typeHandle.getJdbcType()) { case Types.CLOB: - return Optional.of(varcharReadMapping(createUnboundedVarcharType())); + return Optional.of(varcharColumnMapping(createUnboundedVarcharType())); case Types.SMALLINT: - return Optional.of(smallintReadMapping()); + return Optional.of(smallintColumnMapping()); case Types.FLOAT: case Types.DOUBLE: - return Optional.of(doubleReadMapping()); + return Optional.of(doubleColumnMapping()); case Types.REAL: - return Optional.of(realReadMapping()); + return Optional.of(realColumnMapping()); case Types.NUMERIC: int precision = columnSize == 0 ? Decimals.MAX_PRECISION : columnSize; int scale = typeHandle.getDecimalDigits(); if (scale == 0) { - return Optional.of(bigintReadMapping()); + return Optional.of(bigintColumnMapping()); } if (scale < 0 || scale > precision) { - return Optional.of(decimalReadMapping(createDecimalType(precision, numberDefaultScale))); + return Optional.of(decimalColumnMapping(createDecimalType(precision, numberDefaultScale))); } - return Optional.of(decimalReadMapping(createDecimalType(precision, scale))); + return Optional.of(decimalColumnMapping(createDecimalType(precision, scale))); case Types.LONGVARCHAR: if (columnSize > VarcharType.MAX_LENGTH || columnSize == 0) { - return Optional.of(varcharReadMapping(createUnboundedVarcharType())); + return Optional.of(varcharColumnMapping(createUnboundedVarcharType())); } - return Optional.of(varcharReadMapping(createVarcharType(columnSize))); + return Optional.of(varcharColumnMapping(createVarcharType(columnSize))); case Types.VARCHAR: - return Optional.of(varcharReadMapping(createVarcharType(columnSize))); + return Optional.of(varcharColumnMapping(createVarcharType(columnSize))); } return super.toPrestoType(session, typeHandle); } diff --git a/presto-postgresql/src/main/java/com/facebook/presto/plugin/postgresql/PostgreSqlClient.java b/presto-postgresql/src/main/java/com/facebook/presto/plugin/postgresql/PostgreSqlClient.java index 01214d57d70e4..c71b856a91944 100644 --- a/presto-postgresql/src/main/java/com/facebook/presto/plugin/postgresql/PostgreSqlClient.java +++ b/presto-postgresql/src/main/java/com/facebook/presto/plugin/postgresql/PostgreSqlClient.java @@ -24,7 +24,9 @@ import com.facebook.presto.plugin.jdbc.JdbcConnectorId; import com.facebook.presto.plugin.jdbc.JdbcIdentity; import com.facebook.presto.plugin.jdbc.JdbcTypeHandle; -import com.facebook.presto.plugin.jdbc.ReadMapping; +import com.facebook.presto.plugin.jdbc.mapping.ColumnMapping; +import com.facebook.presto.plugin.jdbc.mapping.WriteMapping; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -37,6 +39,7 @@ import io.airlift.slice.Slice; import io.airlift.slice.SliceOutput; import org.postgresql.Driver; +import org.postgresql.util.PGobject; import javax.inject.Inject; @@ -49,17 +52,17 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; -import java.util.UUID; import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.uuidColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varbinaryColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.sliceMapping; import static com.facebook.presto.spi.StandardErrorCode.ALREADY_EXISTS; import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static com.fasterxml.jackson.core.JsonFactory.Feature.CANONICALIZE_FIELD_NAMES; import static com.fasterxml.jackson.databind.SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS; import static io.airlift.slice.Slices.utf8Slice; -import static io.airlift.slice.Slices.wrappedLongArray; -import static java.lang.Long.reverseBytes; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; @@ -68,7 +71,6 @@ public class PostgreSqlClient { protected final Type jsonType; private static final String DUPLICATE_TABLE_SQLSTATE = "42P07"; - private final Type uuidType; private static final JsonFactory JSON_FACTORY = new JsonFactoryBuilder().configure(CANONICALIZE_FIELD_NAMES, false).build(); private static final ObjectMapper SORTED_MAPPER = new JsonObjectMapperProvider().get().configure(ORDER_MAP_ENTRIES_BY_KEYS, true); @@ -78,7 +80,6 @@ public PostgreSqlClient(JdbcConnectorId connectorId, BaseJdbcConfig config, Type { super(connectorId, config, "\"", new DriverConnectionFactory(new Driver(), config)); this.jsonType = typeManager.getType(new TypeSignature(StandardTypes.JSON)); - this.uuidType = typeManager.getType(new TypeSignature(StandardTypes.UUID)); } @Override @@ -105,17 +106,16 @@ protected ResultSet getTables(Connection connection, Optional schemaName } @Override - protected String toSqlType(Type type) + public WriteMapping toWriteMapping(Type type) { if (VARBINARY.equals(type)) { - return "bytea"; + return sliceMapping("bytea", (SliceWriteFunction) varbinaryColumnMapping().getWriteFunction()); } - - return super.toSqlType(type); + return super.toWriteMapping(type); } @Override - public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) + public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) { if (typeHandle.getJdbcTypeName().equals("jsonb") || typeHandle.getJdbcTypeName().equals("json")) { return Optional.of(jsonColumnMapping()); @@ -157,11 +157,17 @@ protected void renameTable(JdbcIdentity identity, String catalogName, SchemaTabl } } - private ReadMapping jsonColumnMapping() + private ColumnMapping jsonColumnMapping() { - return ReadMapping.sliceReadMapping( + return ColumnMapping.sliceMapping( jsonType, - (resultSet, columnIndex) -> jsonParse(utf8Slice(resultSet.getString(columnIndex)))); + (resultSet, columnIndex) -> jsonParse(utf8Slice(resultSet.getString(columnIndex))), + ((statement, index, value) -> { + PGobject pgObject = new PGobject(); + pgObject.setType("json"); + pgObject.setValue(value.toStringUtf8()); + statement.setObject(index, pgObject); + })); } public static Slice jsonParse(Slice slice) @@ -187,18 +193,4 @@ public static JsonParser createJsonParser(JsonFactory factory, Slice json) // so we pass an InputStreamReader instead. return factory.createParser(new InputStreamReader(json.getInput(), UTF_8)); } - - private ReadMapping uuidColumnMapping() - { - return ReadMapping.sliceReadMapping( - uuidType, - (resultSet, columnIndex) -> uuidSlice((UUID) resultSet.getObject(columnIndex))); - } - - private static Slice uuidSlice(UUID uuid) - { - return wrappedLongArray( - reverseBytes(uuid.getMostSignificantBits()), - reverseBytes(uuid.getLeastSignificantBits())); - } } diff --git a/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlTypeMapping.java b/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlTypeMapping.java index 3b26ab5e90901..b9d984f17f0be 100644 --- a/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlTypeMapping.java +++ b/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlTypeMapping.java @@ -275,7 +275,7 @@ public void testDate() @Test public void testTimestamp() { - // TODO timestamp is not correctly read (see comment in StandardReadMappings.timestampReadMapping), but testing this is hard because of #7122 + // TODO timestamp is not correctly read (see comment in StandardColumnMappings.timestampReadMapping), but testing this is hard because of #7122 } @Test diff --git a/presto-singlestore/src/main/java/com/facebook/presto/plugin/singlestore/SingleStoreClient.java b/presto-singlestore/src/main/java/com/facebook/presto/plugin/singlestore/SingleStoreClient.java index 996a2452b4dab..ab08fd8c7d11e 100644 --- a/presto-singlestore/src/main/java/com/facebook/presto/plugin/singlestore/SingleStoreClient.java +++ b/presto-singlestore/src/main/java/com/facebook/presto/plugin/singlestore/SingleStoreClient.java @@ -13,6 +13,7 @@ */ package com.facebook.presto.plugin.singlestore; +import com.facebook.presto.common.type.TimestampType; import com.facebook.presto.common.type.Type; import com.facebook.presto.common.type.VarcharType; import com.facebook.presto.plugin.jdbc.BaseJdbcClient; @@ -22,6 +23,9 @@ import com.facebook.presto.plugin.jdbc.JdbcConnectorId; import com.facebook.presto.plugin.jdbc.JdbcIdentity; import com.facebook.presto.plugin.jdbc.JdbcTableHandle; +import com.facebook.presto.plugin.jdbc.mapping.WriteMapping; +import com.facebook.presto.plugin.jdbc.mapping.functions.LongWriteFunction; +import com.facebook.presto.plugin.jdbc.mapping.functions.SliceWriteFunction; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -47,6 +51,12 @@ import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; import static com.facebook.presto.common.type.Varchars.isVarcharType; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.realColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.timestampColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varbinaryColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharColumnMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.longMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.sliceMapping; import static com.facebook.presto.spi.StandardErrorCode.ALREADY_EXISTS; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; @@ -124,39 +134,42 @@ protected String getTableSchemaName(ResultSet resultSet) } @Override - protected String toSqlType(Type type) + public WriteMapping toWriteMapping(Type type) { if (REAL.equals(type)) { - return "float"; + return longMapping("float", (LongWriteFunction) realColumnMapping().getWriteFunction()); } if (TIME_WITH_TIME_ZONE.equals(type) || TIMESTAMP_WITH_TIME_ZONE.equals(type) || UUID.equals(type)) { throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } if (TIMESTAMP.equals(type)) { - return "datetime"; + return longMapping("datetime", (LongWriteFunction) timestampColumnMapping((TimestampType) type).getWriteFunction()); } if (VARBINARY.equals(type)) { - return "mediumblob"; + return sliceMapping("mediumblob", (SliceWriteFunction) varbinaryColumnMapping().getWriteFunction()); } if (isVarcharType(type)) { VarcharType varcharType = (VarcharType) type; + String dataType; if (varcharType.isUnbounded()) { - return "longtext"; + dataType = "longtext"; } - if (varcharType.getLengthSafe() <= 255) { - return "tinytext"; + else if (varcharType.getLengthSafe() <= 255) { + dataType = "tinytext"; } - if (varcharType.getLengthSafe() <= 65535) { - return "text"; + else if (varcharType.getLengthSafe() <= 65535) { + dataType = "text"; } - if (varcharType.getLengthSafe() <= 16777215) { - return "mediumtext"; + else if (varcharType.getLengthSafe() <= 16777215) { + dataType = "mediumtext"; } - return "longtext"; + else { + dataType = "longtext"; + } + return sliceMapping(dataType, (SliceWriteFunction) varcharColumnMapping(varcharType).getWriteFunction()); } - - return super.toSqlType(type); + return super.toWriteMapping(type); } @Override