From 26dca6a8a41e385590c670cc23a9eb50f37022c7 Mon Sep 17 00:00:00 2001 From: Hazmi Date: Thu, 15 May 2025 16:07:19 +0300 Subject: [PATCH] Add write mapping Co-authored-by: pratyakshsharma --- .../presto/plugin/jdbc/BaseJdbcClient.java | 23 +- .../presto/plugin/jdbc/JdbcClient.java | 5 + .../presto/plugin/jdbc/JdbcPageSink.java | 102 ++-- .../presto/plugin/jdbc/JdbcRecordCursor.java | 25 +- .../presto/plugin/jdbc/QueryBuilder.java | 103 ++-- .../plugin/jdbc/StandardReadMappings.java | 237 --------- .../jdbc/{ => mapping}/ReadFunction.java | 2 +- .../jdbc/{ => mapping}/ReadMapping.java | 25 +- .../jdbc/mapping/StandardColumnMappings.java | 448 ++++++++++++++++++ .../plugin/jdbc/mapping/WriteFunction.java | 19 + .../plugin/jdbc/mapping/WriteMapping.java | 70 +++ .../functions}/BooleanReadFunction.java | 4 +- .../functions/BooleanWriteFunction.java | 30 ++ .../functions}/DoubleReadFunction.java | 4 +- .../functions/DoubleWriteFunction.java | 30 ++ .../functions}/LongReadFunction.java | 4 +- .../mapping/functions/LongWriteFunction.java | 30 ++ .../mapping/functions/ObjectReadFunction.java | 55 +++ .../functions/ObjectWriteFunction.java | 59 +++ .../functions}/SliceReadFunction.java | 3 +- .../mapping/functions/SliceWriteFunction.java | 31 ++ .../presto/plugin/mysql/MySqlClient.java | 6 +- .../presto/plugin/mysql/TestMySqlClient.java | 2 +- .../plugin/mysql/TestMySqlTypeMapping.java | 4 +- .../presto/plugin/oracle/OracleClient.java | 14 +- .../plugin/postgresql/PostgreSqlClient.java | 15 +- .../postgresql/TestPostgreSqlTypeMapping.java | 2 +- 27 files changed, 943 insertions(+), 409 deletions(-) delete mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardReadMappings.java rename presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/{ => mapping}/ReadFunction.java (94%) rename presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/{ => mapping}/ReadMapping.java (62%) create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/StandardColumnMappings.java create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteFunction.java create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteMapping.java rename presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/{ => mapping/functions}/BooleanReadFunction.java (88%) create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/BooleanWriteFunction.java rename presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/{ => mapping/functions}/DoubleReadFunction.java (88%) create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/DoubleWriteFunction.java rename presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/{ => mapping/functions}/LongReadFunction.java (87%) create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/LongWriteFunction.java create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectReadFunction.java create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/ObjectWriteFunction.java rename presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/{ => mapping/functions}/SliceReadFunction.java (88%) create mode 100644 presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/functions/SliceWriteFunction.java 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..2b00ad0e9299e 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 @@ -20,6 +20,8 @@ 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.ReadMapping; +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; @@ -71,7 +73,8 @@ 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.jdbcTypeToReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.prestoTypeToWriteMapping; 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; @@ -246,13 +249,13 @@ public List getColumns(ConnectorSession session, JdbcTableHand resultSet.getString("TYPE_NAME"), resultSet.getInt("COLUMN_SIZE"), resultSet.getInt("DECIMAL_DIGITS")); - Optional columnMapping = toPrestoType(session, typeHandle); + Optional readMapping = toPrestoType(session, typeHandle); // skip unsupported column types - if (columnMapping.isPresent()) { + if (readMapping.isPresent()) { String columnName = resultSet.getString("COLUMN_NAME"); boolean nullable = columnNullable == resultSet.getInt("NULLABLE"); Optional comment = Optional.ofNullable(emptyToNull(resultSet.getString("REMARKS"))); - columns.add(new JdbcColumnHandle(connectorId, columnName, typeHandle, columnMapping.get().getType(), nullable, comment)); + columns.add(new JdbcColumnHandle(connectorId, columnName, typeHandle, readMapping.get().getType(), nullable, comment)); } } if (columns.isEmpty()) { @@ -274,7 +277,7 @@ public List getColumns(ConnectorSession session, JdbcTableHand @Override public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) { - return jdbcTypeToPrestoType(typeHandle); + return jdbcTypeToReadMapping(typeHandle); } @Override @@ -751,7 +754,6 @@ protected void execute(Connection connection, String query) statement.execute(query); } } - protected String toSqlType(Type type) { if (isVarcharType(type)) { @@ -778,6 +780,15 @@ protected String toSqlType(Type type) throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } + public WriteMapping toWriteMapping(Type type) + { + Optional writeMapping = prestoTypeToWriteMapping(type); + if (writeMapping.isPresent()) { + return writeMapping.get(); + } + throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); + } + protected String quoted(String name) { name = name.replace(identifierQuote, identifierQuote + identifierQuote); 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..09a0d5c7d17ad 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.ReadMapping; +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; @@ -51,6 +54,8 @@ default boolean schemaExists(ConnectorSession session, JdbcIdentity identity, St Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle); + WriteMapping toWriteMapping(Type type); + ConnectorSplitSource getSplits(ConnectorSession session, JdbcIdentity identity, JdbcTableLayoutHandle layoutHandle); Connection getConnection(ConnectorSession session, JdbcIdentity identity, JdbcSplit split) 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..e43e0a515fd28 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.ReadFunction; +import com.facebook.presto.plugin.jdbc.mapping.ReadMapping; +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,6 +68,7 @@ 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()) @@ -81,7 +89,12 @@ else if (javaType == Slice.class) { sliceReadFunctions[i] = (SliceReadFunction) readFunction; } else { - throw new IllegalStateException(format("Unsupported java type %s", javaType)); + try { + objectReadFunctions[i] = (ObjectReadFunction) readFunction; + } + catch (NullPointerException e) { + throw new UnsupportedOperationException(); + } } } @@ -180,7 +193,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..46eee4d510e0f 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,39 @@ 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.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.getWriteMappingForAccumulators; +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 { @@ -142,59 +143,39 @@ public PreparedStatement buildSql( 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()); + int parameterIndex = i + 1; + Type type = typeAndValue.getType(); + WriteFunction writeFunction = getWriteMappingForAccumulators(type) + .orElseThrow(() -> new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName())) + .getWriteFunction(); + Class javaType = type.getJavaType(); + Object value = typeAndValue.getValue(); + if (javaType == boolean.class) { + ((BooleanWriteFunction) writeFunction).set(statement, parameterIndex, (boolean) value); } - else if (typeAndValue.getType().equals(IntegerType.INTEGER)) { - statement.setInt(i + 1, ((Number) typeAndValue.getValue()).intValue()); + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameterIndex, (double) value); } - else if (typeAndValue.getType().equals(SmallintType.SMALLINT)) { - statement.setShort(i + 1, ((Number) typeAndValue.getValue()).shortValue()); + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameterIndex, (long) value); } - 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))); - } - else if (typeAndValue.getType().equals(TimeType.TIME)) { - statement.setTime(i + 1, new Time((long) typeAndValue.getValue())); - } - else if (typeAndValue.getType().equals(TimeWithTimeZoneType.TIME_WITH_TIME_ZONE)) { - statement.setTime(i + 1, new Time(unpackMillisUtc((long) typeAndValue.getValue()))); - } - 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 +213,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) || @@ -259,14 +240,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 +269,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 +282,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 +301,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 +318,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); + Type type = columnHandle.getColumnType(); accumulator.add(new TypeAndValue(type, value)); } } 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/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/ReadMapping.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ReadMapping.java similarity index 62% rename from presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ReadMapping.java rename to presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/ReadMapping.java index 4751a84765c06..abfbc97d9be2a 100644 --- 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/mapping/ReadMapping.java @@ -11,32 +11,45 @@ * 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; import com.facebook.presto.common.type.Type; +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 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. + */ public final class ReadMapping { - public static ReadMapping booleanReadMapping(Type prestoType, BooleanReadFunction readFunction) + public static ReadMapping createBooleanReadMapping(Type prestoType, BooleanReadFunction readFunction) + { + return new ReadMapping(prestoType, readFunction); + } + + public static ReadMapping createLongReadMapping(Type prestoType, LongReadFunction readFunction) { return new ReadMapping(prestoType, readFunction); } - public static ReadMapping longReadMapping(Type prestoType, LongReadFunction readFunction) + public static ReadMapping createDoubleReadMapping(Type prestoType, DoubleReadFunction readFunction) { return new ReadMapping(prestoType, readFunction); } - public static ReadMapping doubleReadMapping(Type prestoType, DoubleReadFunction readFunction) + public static ReadMapping createSliceReadMapping(Type prestoType, SliceReadFunction readFunction) { return new ReadMapping(prestoType, readFunction); } - public static ReadMapping sliceReadMapping(Type prestoType, SliceReadFunction readFunction) + public static ReadMapping createObjectReadMapping(Type prestoType, ObjectReadFunction readFunction) { return new ReadMapping(prestoType, readFunction); } @@ -50,7 +63,7 @@ private ReadMapping(Type type, ReadFunction readFunction) this.readFunction = requireNonNull(readFunction, "readFunction is null"); checkArgument( type.getJavaType() == readFunction.getJavaType(), - "Presto type %s is not compatible with read function %s returning %s", + "Presto type %s is not compatible with read function %s using %s", type, readFunction, readFunction.getJavaType()); 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..8eb53ec5ccb17 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/StandardColumnMappings.java @@ -0,0 +1,448 @@ +/* + * 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.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.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.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.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.ReadMapping.createBooleanReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.ReadMapping.createDoubleReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.ReadMapping.createLongReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.ReadMapping.createSliceReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.createBooleanWriteMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.createDoubleWriteMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.createLongWriteMapping; +import static com.facebook.presto.plugin.jdbc.mapping.WriteMapping.createSliceWriteMapping; +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 ReadMapping booleanReadMapping() + { + return createBooleanReadMapping(BOOLEAN, ResultSet::getBoolean); + } + + public static WriteMapping booleanWriteMapping() + { + return createBooleanWriteMapping(PreparedStatement::setBoolean); + } + + public static ReadMapping tinyintReadMapping() + { + return createLongReadMapping(TINYINT, ResultSet::getByte); + } + + public static WriteMapping tinyintWriteMapping() + { + return createLongWriteMapping(((statement, index, value) -> statement.setByte(index, SignedBytes.checkedCast(value)))); + } + + public static ReadMapping smallintReadMapping() + { + return createLongReadMapping(SMALLINT, ResultSet::getShort); + } + + public static WriteMapping smallintWriteMapping() + { + return createLongWriteMapping(((statement, index, value) -> statement.setShort(index, Shorts.checkedCast(value)))); + } + + public static ReadMapping integerReadMapping() + { + return createLongReadMapping(INTEGER, ResultSet::getInt); + } + + public static WriteMapping integerWriteMapping() + { + return createLongWriteMapping((((statement, index, value) -> statement.setInt(index, toIntExact(value))))); + } + + public static ReadMapping bigintReadMapping() + { + return createLongReadMapping(BIGINT, ResultSet::getLong); + } + + public static WriteMapping bigintWriteMapping() + { + return createLongWriteMapping(PreparedStatement::setLong); + } + + public static ReadMapping realReadMapping() + { + return createLongReadMapping(REAL, (resultSet, columnIndex) -> floatToRawIntBits(resultSet.getFloat(columnIndex))); + } + public static WriteMapping realWriteMapping() + { + return createLongWriteMapping((statement, index, value) -> statement.setFloat(index, intBitsToFloat(toIntExact(value)))); + } + + public static ReadMapping doubleReadMapping() + { + return createDoubleReadMapping(DOUBLE, ResultSet::getDouble); + } + + public static WriteMapping doubleWriteMapping() + { + return createDoubleWriteMapping(PreparedStatement::setDouble); + } + + 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 createLongReadMapping(decimalType, (resultSet, columnIndex) -> encodeShortScaledValue(resultSet.getBigDecimal(columnIndex), scale)); + } + return createSliceReadMapping(decimalType, (resultSet, columnIndex) -> encodeScaledValue(resultSet.getBigDecimal(columnIndex), scale)); + } + + public static WriteMapping decimalWriteMapping(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 createLongWriteMapping(((statement, index, value) -> { + BigInteger unscaledValue = BigInteger.valueOf(value); + BigDecimal bigDecimal = new BigDecimal(unscaledValue, decimalType.getScale(), new MathContext(decimalType.getPrecision())); + statement.setBigDecimal(index, bigDecimal); + })); + } + return createSliceWriteMapping(((statement, index, value) -> { + BigInteger unscaledValue = decodeUnscaledValue(value); + BigDecimal bigDecimal = new BigDecimal(unscaledValue, decimalType.getScale(), new MathContext(decimalType.getPrecision())); + statement.setBigDecimal(index, bigDecimal); + })); + } + + public static ReadMapping charReadMapping(CharType charType) + { + requireNonNull(charType, "charType is null"); + return createSliceReadMapping(charType, (resultSet, columnIndex) -> utf8Slice(CharMatcher.is(' ').trimTrailingFrom(resultSet.getString(columnIndex)))); + } + + public static WriteMapping charWriteMapping() + { + return createSliceWriteMapping(((statement, index, value) -> statement.setString(index, value.toStringUtf8()))); + } + + public static ReadMapping varcharReadMapping(VarcharType varcharType) + { + return createSliceReadMapping(varcharType, (resultSet, columnIndex) -> utf8Slice(resultSet.getString(columnIndex))); + } + + public static ReadMapping varbinaryReadMapping() + { + return createSliceReadMapping(VARBINARY, (resultSet, columnIndex) -> wrappedBuffer(resultSet.getBytes(columnIndex))); + } + + public static WriteMapping varbinaryWriteMapping() + { + return createSliceWriteMapping(((statement, index, value) -> statement.setBytes(index, value.getBytes()))); + } + + public static ReadMapping dateReadMapping() + { + return createLongReadMapping(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 WriteMapping dateWriteMapping() + { + return createLongWriteMapping(((statement, index, value) -> statement.setDate(index, new Date(UTC.getMillisKeepLocal + (DateTimeZone.getDefault(), DAYS.toMillis(value)))))); + } + + public static ReadMapping timeReadMapping() + { + return createLongReadMapping(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 WriteMapping timeWriteMapping() + { + return createLongWriteMapping(((statement, index, value) -> statement.setTime(index, new Time(value)))); + } + + public static ReadMapping timestampReadMapping() + { + return createLongReadMapping(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 WriteMapping timestampWriteMapping(TimestampType timestampType) + { + return createLongWriteMapping((statement, index, value) -> statement.setTimestamp(index, Timestamp.from(Instant.ofEpochSecond( + timestampType.getEpochSecond(value), + timestampType.getNanos(value))))); + } + public static WriteMapping uuidWriteMapping() + { + return createSliceWriteMapping(((statement, index, value) -> statement.setObject(index, prestoUuidToJavaUuid(value)))); + } + + public static WriteMapping timeWithTimeZoneWriteMapping() + { + return createLongWriteMapping((((statement, index, value) -> statement.setTime(index, new Time(unpackMillisUtc(value)))))); + } + + public static WriteMapping timestampWithTimeZoneWriteMapping() + { + return createLongWriteMapping(((statement, index, value) -> statement.setTimestamp(index, new Timestamp(unpackMillisUtc(value))))); + } + + public static Optional jdbcTypeToReadMapping(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(); + } + + public static Optional prestoTypeToWriteMapping(Type type) + { + if (type.equals(BOOLEAN)) { + return Optional.of(booleanWriteMapping()); + } + else if (type.equals(TINYINT)) { + return Optional.of(tinyintWriteMapping()); + } + else if (type.equals(SMALLINT)) { + return Optional.of(smallintWriteMapping()); + } + else if (type.equals(BIGINT)) { + return Optional.of(bigintWriteMapping()); + } + else if (type.equals(DOUBLE)) { + return Optional.of(doubleWriteMapping()); + } + else if (type.equals(INTEGER)) { + return Optional.of(integerWriteMapping()); + } + else if (type.equals(REAL)) { + return Optional.of(realWriteMapping()); + } + else if (type instanceof DecimalType) { + return Optional.of(decimalWriteMapping((DecimalType) type)); + } + else if (type instanceof CharType || type instanceof VarcharType) { + return Optional.of(charWriteMapping()); + } + else if (type.equals(VARBINARY)) { + return Optional.of(varbinaryWriteMapping()); + } + else if (type instanceof DateType) { + return Optional.of(dateWriteMapping()); + } + else if (type instanceof TimestampType) { + return Optional.of(timestampWriteMapping((TimestampType) type)); + } + else if (type.equals(UUID)) { + return Optional.of(uuidWriteMapping()); + } + return Optional.empty(); + } + + public static Optional getWriteMappingForAccumulators(Type type) + { + if (type.equals(BOOLEAN)) { + return Optional.of(booleanWriteMapping()); + } + else if (type.equals(TINYINT)) { + return Optional.of(tinyintWriteMapping()); + } + else if (type.equals(SMALLINT)) { + return Optional.of(smallintWriteMapping()); + } + else if (type.equals(INTEGER)) { + return Optional.of(integerWriteMapping()); + } + else if (type.equals(BIGINT)) { + return Optional.of(bigintWriteMapping()); + } + else if (type.equals(REAL)) { + return Optional.of(realWriteMapping()); + } + else if (type.equals(DOUBLE)) { + return Optional.of(doubleWriteMapping()); + } + else if (type instanceof CharType || type instanceof VarcharType) { + return Optional.of(charWriteMapping()); + } + else if (type.equals(DateType.DATE)) { + return Optional.of(dateWriteMapping()); + } + else if (type.equals(TIME)) { + return Optional.of(timeWriteMapping()); + } + else if (type.equals(TIMESTAMP)) { + return Optional.of(timestampWriteMapping((TimestampType) type)); + } + else if (type.equals(TIME_WITH_TIME_ZONE)) { + return Optional.of(timeWithTimeZoneWriteMapping()); + } + else if (type.equals(TIMESTAMP_WITH_TIME_ZONE)) { + return Optional.of(timestampWithTimeZoneWriteMapping()); + } + else if (type instanceof UuidType) { + return Optional.of(uuidWriteMapping()); + } + 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..afe56fd45d214 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/mapping/WriteMapping.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.Objects.requireNonNull; + +/* + * JDBC based connectors can control to define how data should be written back to data source by WriteFunctions. + */ +public final class WriteMapping +{ + private final WriteFunction writeFunction; + + private WriteMapping(WriteFunction writeFunction) + { + this.writeFunction = requireNonNull(writeFunction, "writeFunction is null"); + } + + public static WriteMapping createBooleanWriteMapping(BooleanWriteFunction writeFunction) + { + return new WriteMapping(writeFunction); + } + + public static WriteMapping createLongWriteMapping(LongWriteFunction writeFunction) + { + return new WriteMapping(writeFunction); + } + + public static WriteMapping createDoubleWriteMapping(DoubleWriteFunction writeFunction) + { + return new WriteMapping(writeFunction); + } + + public static WriteMapping createSliceWriteMapping(SliceWriteFunction writeFunction) + { + return new WriteMapping(writeFunction); + } + + public static WriteMapping createObjectWriteMapping(Class javaType, ObjectWriteFunction.ObjectWriteFunctionImplementation writeFunctionImplementation) + { + return createObjectWriteMapping(ObjectWriteFunction.of(javaType, writeFunctionImplementation)); + } + + public static WriteMapping createObjectWriteMapping(ObjectWriteFunction writeFunction) + { + return new WriteMapping(writeFunction); + } + + public WriteFunction getWriteFunction() + { + return writeFunction; + } +} 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..ade9d25b27f81 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,7 @@ 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.ReadMapping; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -66,7 +66,7 @@ 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.ReadMapping.createSliceReadMapping; 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; @@ -255,7 +255,7 @@ public Optional toPrestoType(ConnectorSession session, JdbcTypeHand protected static ReadMapping geometryReadMapping() { - return sliceReadMapping(VARCHAR, + return createSliceReadMapping(VARCHAR, (resultSet, columnIndex) -> getAsText(stGeomFromBinary(wrappedBuffer(resultSet.getBytes(columnIndex))))); } 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..e1e67d3cf3729 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; 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..f8b7f68e587e7 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.ReadMapping; 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.bigintReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.decimalReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.doubleReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.realReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.smallintReadMapping; +import static com.facebook.presto.plugin.jdbc.mapping.StandardColumnMappings.varcharReadMapping; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; import static java.lang.String.format; import static java.util.Locale.ENGLISH; 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..984e71d61d0b2 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,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.ReadMapping; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -53,6 +53,7 @@ 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.ReadMapping.createSliceReadMapping; 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; @@ -118,11 +119,11 @@ protected String toSqlType(Type type) public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle) { if (typeHandle.getJdbcTypeName().equals("jsonb") || typeHandle.getJdbcTypeName().equals("json")) { - return Optional.of(jsonColumnMapping()); + return Optional.of(jsonReadMapping()); } else if (typeHandle.getJdbcTypeName().equals("uuid")) { - return Optional.of(uuidColumnMapping()); + return Optional.of(uuidReadMapping()); } return super.toPrestoType(session, typeHandle); } @@ -157,9 +158,9 @@ protected void renameTable(JdbcIdentity identity, String catalogName, SchemaTabl } } - private ReadMapping jsonColumnMapping() + private ReadMapping jsonReadMapping() { - return ReadMapping.sliceReadMapping( + return createSliceReadMapping( jsonType, (resultSet, columnIndex) -> jsonParse(utf8Slice(resultSet.getString(columnIndex)))); } @@ -188,9 +189,9 @@ public static JsonParser createJsonParser(JsonFactory factory, Slice json) return factory.createParser(new InputStreamReader(json.getInput(), UTF_8)); } - private ReadMapping uuidColumnMapping() + private ReadMapping uuidReadMapping() { - return ReadMapping.sliceReadMapping( + return createSliceReadMapping( uuidType, (resultSet, columnIndex) -> uuidSlice((UUID) resultSet.getObject(columnIndex))); } 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