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 5a1df5c06e0c..434386a8cbdb 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 @@ -61,15 +61,21 @@ 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.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.StandardReadMappings.jdbcTypeToPrestoType; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.bigintWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.booleanWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.dateWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.doubleWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.integerWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.jdbcTypeToPrestoType; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.realWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.smallintWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.tinyintWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.varbinaryWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.varcharWriteFunction; 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; @@ -93,20 +99,16 @@ 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") + private static final Map WRITE_MAPPING_MAP = ImmutableMap.builder() + .put(BOOLEAN, WriteMapping.booleanMapping("boolean", booleanWriteFunction())) + .put(BIGINT, WriteMapping.longMapping("bigint", bigintWriteFunction())) + .put(INTEGER, WriteMapping.longMapping("integer", integerWriteFunction())) + .put(SMALLINT, WriteMapping.longMapping("smallint", smallintWriteFunction())) + .put(TINYINT, WriteMapping.longMapping("tinyint", tinyintWriteFunction())) + .put(DOUBLE, WriteMapping.doubleMapping("double precision", doubleWriteFunction())) + .put(REAL, WriteMapping.longMapping("real", realWriteFunction())) + .put(VARBINARY, WriteMapping.sliceMapping("varbinary", varbinaryWriteFunction())) + .put(DATE, WriteMapping.longMapping("date", dateWriteFunction())) .build(); protected final String connectorId; @@ -237,7 +239,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"); @@ -259,7 +261,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); } @@ -370,6 +372,7 @@ protected JdbcOutputTableHandle createTable(ConnectorTableMetadata tableMetadata } columnNames.add(columnName); columnTypes.add(column.getType()); + // TODO in INSERT case, we should reuse original column type and, ideally, constraints (then JdbcPageSink must get writer from toPrestoType()) columnList.add(getColumnString(column, columnName)); } @@ -395,7 +398,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"); } @@ -729,28 +732,41 @@ 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 WriteMapping.sliceMapping(dataType, varcharWriteFunction()); } if (type instanceof CharType) { - if (((CharType) type).getLength() == CharType.MAX_LENGTH) { - return "char"; + CharType charType = (CharType) type; + if (charType.getLength() == CharType.MAX_LENGTH) { + dataType = "char"; + } + else { + dataType = "char(" + ((CharType) type).getLength() + ")"; } - return "char(" + ((CharType) type).getLength() + ")"; + return WriteMapping.sliceMapping(dataType, StandardColumnMappings.charWriteFunction(charType)); } if (type instanceof DecimalType) { - return format("decimal(%s, %s)", ((DecimalType) type).getPrecision(), ((DecimalType) type).getScale()); + DecimalType decimalType = (DecimalType) type; + dataType = format("decimal(%s, %s)", ((DecimalType) type).getPrecision(), ((DecimalType) type).getScale()); + if (decimalType.isShort()) { + return WriteMapping.longMapping(dataType, StandardColumnMappings.shortDecimalWriteFunction(decimalType)); + } + return WriteMapping.sliceMapping(dataType, StandardColumnMappings.longDecimalWriteFunction(decimalType)); } - String sqlType = SQL_TYPES.get(type); - if (sqlType != null) { - return sqlType; + WriteMapping writeMapping = WRITE_MAPPING_MAP.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/BooleanWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BooleanWriteFunction.java new file mode 100644 index 000000000000..fd71bf512736 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BooleanWriteFunction.java @@ -0,0 +1,28 @@ +/* + * 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 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/ColumnMapping.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ColumnMapping.java new file mode 100644 index 000000000000..d60255dbec13 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ColumnMapping.java @@ -0,0 +1,85 @@ +/* + * 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.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +public class ColumnMapping +{ + 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 returning %s", + type, + readFunction, + readFunction.getJavaType()); + checkArgument( + type.getJavaType() == writeFunction.getJavaType(), + "Presto type %s is not compatible with write function %s accepting %s", + type, + writeFunction, + writeFunction.getJavaType()); + } + + 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); + } + + public Type getType() + { + return type; + } + + public ReadFunction getReadFunction() + { + return readFunction; + } + + public WriteFunction getWriteFunction() + { + return writeFunction; + } +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/DoubleWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/DoubleWriteFunction.java new file mode 100644 index 000000000000..1bdf80a8eb1f --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/DoubleWriteFunction.java @@ -0,0 +1,28 @@ +/* + * 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 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/JdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java index 3c5e08ed257c..b93cf010fd83 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,7 @@ package com.facebook.presto.plugin.jdbc; import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.common.type.Type; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.ConnectorSession; @@ -49,7 +50,9 @@ default boolean schemaExists(JdbcIdentity identity, String schema) List getColumns(ConnectorSession session, JdbcTableHandle tableHandle); - Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle); + Optional toPrestoType(ConnectorSession session, JdbcTypeHandle typeHandle); + + WriteMapping toWriteMapping(Type type); ConnectorSplitSource getSplits(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 42c3ecaaeced..d4c417bafa2b 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,46 +16,27 @@ 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.Type; import com.facebook.presto.spi.ConnectorPageSink; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.PrestoException; +import com.google.common.base.Verify; 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.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; -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.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 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 @@ -66,6 +47,8 @@ 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) @@ -87,6 +70,12 @@ public JdbcPageSink(ConnectorSession session, JdbcOutputTableHandle handle, Jdbc } columnTypes = handle.getColumnTypes(); + columnWriters = columnTypes.stream().map(type -> { + WriteFunction writeFunction = jdbcClient.toWriteMapping(type).getWriteFunction(); + Verify.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(Collectors.toList()); } @Override @@ -119,52 +108,30 @@ private void appendColumn(Page page, int position, int channel) throws SQLException { Block block = page.getBlock(channel); - int parameter = channel + 1; + int parameterIndex = channel + 1; if (block.isNull(position)) { - statement.setObject(parameter, null); + statement.setObject(parameterIndex, null); return; } Type type = columnTypes.get(channel); - if (BOOLEAN.equals(type)) { - statement.setBoolean(parameter, type.getBoolean(block, position)); - } - else if (BIGINT.equals(type)) { - statement.setLong(parameter, type.getLong(block, position)); - } - else if (INTEGER.equals(type)) { - statement.setInt(parameter, toIntExact(type.getLong(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)); + Class javaType = type.getJavaType(); + WriteFunction writeFunction = columnWriters.get(channel); + if (javaType == boolean.class) { + ((BooleanWriteFunction) writeFunction).set(statement, parameterIndex, type.getBoolean(block, position)); } - else if (isVarcharType(type) || isCharType(type)) { - statement.setString(parameter, type.getSlice(block, position).toStringUtf8()); + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameterIndex, type.getLong(block, position)); } - else if (VARBINARY.equals(type)) { - statement.setBytes(parameter, type.getSlice(block, position).getBytes()); + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameterIndex, type.getDouble(block, position)); } - 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 (javaType == Slice.class) { + ((SliceWriteFunction) writeFunction).set(statement, parameterIndex, type.getSlice(block, position)); } else { - throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); + ((ObjectWriteFunction) writeFunction).set(statement, parameterIndex, type.getObject(block, position)); } } 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 27472295e720..a58645b92267 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 @@ -31,7 +31,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 @@ -45,6 +44,8 @@ public class JdbcRecordCursor private final LongReadFunction[] longReadFunctions; private final SliceReadFunction[] sliceReadFunctions; + private final ObjectReadFunction[] objectReadFunctions; + private final JdbcClient jdbcClient; private final Connection connection; private final PreparedStatement statement; @@ -61,12 +62,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 +83,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 +182,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/LongWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/LongWriteFunction.java new file mode 100644 index 000000000000..8e817f23a1cf --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/LongWriteFunction.java @@ -0,0 +1,28 @@ +/* + * 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 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/ObjectReadFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ObjectReadFunction.java new file mode 100644 index 000000000000..258556da1698 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ObjectReadFunction.java @@ -0,0 +1,53 @@ +/* + * 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 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/ObjectWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ObjectWriteFunction.java new file mode 100644 index 000000000000..1cde0f8c92c4 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/ObjectWriteFunction.java @@ -0,0 +1,57 @@ +/* + * 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 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/QueryBuilder.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/QueryBuilder.java index 0926e605ff16..b4a9d3ce094b 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 @@ -35,31 +35,25 @@ import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ConnectorSession; import com.google.common.base.Joiner; +import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; 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.Optional; -import static com.facebook.presto.common.type.DateTimeEncoding.unpackMillisUtc; 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 { @@ -73,11 +67,13 @@ private static class TypeAndValue { private final Type type; private final Object value; + private final Optional jdbcTypeHandle; - public TypeAndValue(Type type, Object value) + public TypeAndValue(Type type, Object value, Optional jdbcTypeHandle) { this.type = requireNonNull(type, "type is null"); this.value = requireNonNull(value, "value is null"); + this.jdbcTypeHandle = jdbcTypeHandle; } public Type getType() @@ -89,6 +85,11 @@ public Object getValue() { return value; } + + public Optional getJdbcTypeHandle() + { + return jdbcTypeHandle; + } } public QueryBuilder(String quote) @@ -139,69 +140,56 @@ public PreparedStatement buildSql( .add(additionalPredicate.get().getExpression()) .build(); accumulator.addAll(additionalPredicate.get().getBoundConstantValues().stream() - .map(constantExpression -> new TypeAndValue(constantExpression.getType(), constantExpression.getValue())) + .map(constantExpression -> new TypeAndValue(constantExpression.getType(), constantExpression.getValue(), Optional.empty())) .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(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()); + int parameterIndex = i + 1; + Type type = typeAndValue.getType(); + WriteFunction writeFunction = getWriteFunction(typeAndValue.getJdbcTypeHandle(), 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(DateType.DATE)) { - long millis = DAYS.toMillis((long) typeAndValue.getValue()); - statement.setDate(i + 1, new Date(UTC.getMillisKeepLocal(DateTimeZone.getDefault(), millis))); + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameterIndex, (double) value); } - else if (typeAndValue.getType().equals(TimeType.TIME)) { - statement.setTime(i + 1, new Time((long) typeAndValue.getValue())); + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameterIndex, (long) value); } - 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()); + //no inspection, unchecked raw types + ((ObjectWriteFunction) writeFunction).set(statement, parameterIndex, value); } } return statement; } + private WriteFunction getWriteFunction(Optional typeHandle, JdbcClient client, ConnectorSession session, Type type) + { + if (typeHandle.isPresent()) { + return client.toPrestoType(session, typeHandle.get()) + .orElseThrow(() -> new VerifyException(format("Unsupported type %s with handle %s", type, typeHandle.get()))) + .getWriteFunction(); + } + return StandardColumnMappings.getColumnMappingFromPrestoType(type) + .orElseThrow(() -> new VerifyException(format("Unsupported type %s for adding in accumulators", type))) + .getWriteFunction(); + } + private static boolean isAcceptedType(Type type) { Type validType = requireNonNull(type, "type is null"); @@ -229,14 +217,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"); @@ -258,10 +246,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()); @@ -271,11 +259,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 + ")"); @@ -290,9 +278,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 + " ?"; } @@ -302,9 +290,9 @@ private String quote(String name) return quote + name + quote; } - 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, Optional.of(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 4751a84765c0..000000000000 --- 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/SliceWriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/SliceWriteFunction.java new file mode 100644 index 000000000000..8b7cd0089519 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/SliceWriteFunction.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; + +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-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardColumnMappings.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardColumnMappings.java new file mode 100644 index 000000000000..d4efde91700f --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/StandardColumnMappings.java @@ -0,0 +1,434 @@ +/* + * 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.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.TimestampType; +import com.facebook.presto.common.type.TinyintType; +import com.facebook.presto.common.type.Type; +import com.facebook.presto.common.type.VarbinaryType; +import com.facebook.presto.common.type.VarcharType; +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.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.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.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.google.common.base.Preconditions.checkArgument; +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 ColumnMapping.booleanMapping(BOOLEAN, ResultSet::getBoolean, booleanWriteFunction()); + } + + public static BooleanWriteFunction booleanWriteFunction() + { + return PreparedStatement::setBoolean; + } + + public static ColumnMapping tinyintColumnMapping() + { + return ColumnMapping.longMapping(TINYINT, ResultSet::getByte, tinyintWriteFunction()); + } + + public static LongWriteFunction tinyintWriteFunction() + { + return ((statement, index, value) -> statement.setByte(index, SignedBytes.checkedCast(value))); + } + + public static ColumnMapping smallIntColumnMapping() + { + return ColumnMapping.longMapping(SMALLINT, ResultSet::getShort, smallintWriteFunction()); + } + + public static LongWriteFunction smallintWriteFunction() + { + return ((statement, index, value) -> statement.setShort(index, Shorts.checkedCast(value))); + } + + public static ColumnMapping integerColumnMapping() + { + return ColumnMapping.longMapping(INTEGER, ResultSet::getInt, integerWriteFunction()); + } + + public static LongWriteFunction integerWriteFunction() + { + return ((statement, index, value) -> statement.setInt(index, toIntExact(value))); + } + + public static ColumnMapping bigintColumnMapping() + { + return ColumnMapping.longMapping(BIGINT, ResultSet::getLong, bigintWriteFunction()); + } + + public static LongWriteFunction bigintWriteFunction() + { + return PreparedStatement::setLong; + } + + public static ColumnMapping realColumnMapping() + { + return ColumnMapping.longMapping(REAL, ((resultSet, columnIndex) -> floatToRawIntBits(resultSet.getFloat(columnIndex))), realWriteFunction()); + } + + public static LongWriteFunction realWriteFunction() + { + return ((statement, index, value) -> statement.setFloat(index, intBitsToFloat(toIntExact(value)))); + } + + public static ColumnMapping doubleColumnMapping() + { + return ColumnMapping.doubleMapping(DOUBLE, ResultSet::getDouble, doubleWriteFunction()); + } + + public static DoubleWriteFunction doubleWriteFunction() + { + return 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 ColumnMapping.longMapping( + decimalType, + (resultSet, columnIndex) -> encodeShortScaledValue(resultSet.getBigDecimal(columnIndex), scale), + shortDecimalWriteFunction(decimalType)); + } + return ColumnMapping.sliceMapping( + decimalType, + (resultSet, columnIndex) -> encodeScaledValue(resultSet.getBigDecimal(columnIndex), scale), + longDecimalWriteFunction(decimalType)); + } + + public static LongWriteFunction shortDecimalWriteFunction(DecimalType decimalType) + { + requireNonNull(decimalType, "decimalType is null"); + checkArgument(decimalType.isShort()); + return (statement, index, value) -> { + BigInteger unscaledValue = BigInteger.valueOf(value); + BigDecimal bigDecimal = new BigDecimal(unscaledValue, decimalType.getScale(), new MathContext(decimalType.getPrecision())); + statement.setBigDecimal(index, bigDecimal); + }; + } + + public static SliceWriteFunction longDecimalWriteFunction(DecimalType decimalType) + { + requireNonNull(decimalType, "decimalType is null"); + checkArgument(!decimalType.isShort()); + return (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 ColumnMapping.sliceMapping( + charType, + (resultSet, columnIndex) -> utf8Slice(CharMatcher.is(' ').trimTrailingFrom(resultSet.getString(columnIndex))), + charWriteFunction(charType)); + } + + public static SliceWriteFunction charWriteFunction(CharType charType) + { + requireNonNull(charType, "charType is null"); + return (statement, index, value) -> { + statement.setString(index, value.toStringUtf8()); + }; + } + + public static ColumnMapping varcharColumnMapping(VarcharType varcharType) + { + return ColumnMapping.sliceMapping(varcharType, (resultSet, columnIndex) -> utf8Slice(resultSet.getString(columnIndex)), varcharWriteFunction()); + } + + public static SliceWriteFunction varcharWriteFunction() + { + return (statement, index, value) -> statement.setString(index, value.toStringUtf8()); + } + + public static ColumnMapping varbinaryColumnMapping() + { + return ColumnMapping.sliceMapping( + VARBINARY, + (resultSet, columnIndex) -> wrappedBuffer(resultSet.getBytes(columnIndex)), + varbinaryWriteFunction()); + } + + public static SliceWriteFunction varbinaryWriteFunction() + { + return (statement, index, value) -> statement.setBytes(index, value.getBytes()); + } + + public static ColumnMapping dateColumnMapping() + { + return ColumnMapping.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); + }, + dateWriteFunction()); + } + + public static LongWriteFunction dateWriteFunction() + { + return (statement, index, value) -> { + // convert to midnight in default time zone + long millis = DAYS.toMillis(value); + statement.setDate(index, new Date(UTC.getMillisKeepLocal(DateTimeZone.getDefault(), millis))); + }; + } + + public static ColumnMapping timeColumnMapping() + { + return ColumnMapping.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()); + }, + timeWriteFunction()); + } + + public static LongWriteFunction timeWriteFunction() + { + return (statement, index, value) -> { + // Copied from `QueryBuilder.buildSql` + // TODO verify correctness, add tests and support non-legacy timestamp + statement.setTime(index, new Time(value)); + }; + } + + public static ColumnMapping timestampColumnMapping() + { + return ColumnMapping.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(); + }, + timestampWriteFunction()); + } + + public static LongWriteFunction timestampWriteFunction() + { + return (statement, index, value) -> { + // Copied from `QueryBuilder.buildSql` + // TODO verify correctness, add tests and support non-legacy timestamp + statement.setTimestamp(index, new Timestamp(value)); + }; + } + + public static Optional jdbcTypeToPrestoType(JdbcTypeHandle typeHandle) + { + int columnSize = typeHandle.getColumnSize(); + switch (typeHandle.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 = typeHandle.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: + return Optional.of(timeColumnMapping()); + + case Types.TIMESTAMP: + return Optional.of(timestampColumnMapping()); + } + 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) { + //TODO: add support for Decimal type here + return Optional.empty(); + } + 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()); + } + return Optional.empty(); + } +} 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 182997e37036..000000000000 --- 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/WriteFunction.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/WriteFunction.java new file mode 100644 index 000000000000..674510d07732 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/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; + +public interface WriteFunction +{ + Class getJavaType(); +} diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/WriteMapping.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/WriteMapping.java new file mode 100644 index 000000000000..666a411f0fa7 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/WriteMapping.java @@ -0,0 +1,77 @@ +/* + * 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 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-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 d3b29fed5680..16ffcfde73d9 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 @@ -23,6 +23,7 @@ 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.WriteMapping; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -50,6 +51,10 @@ import static com.facebook.presto.common.type.Varchars.isVarcharType; 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.StandardColumnMappings.realWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.timestampWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.varbinaryWriteFunction; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.varcharWriteFunction; 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; @@ -156,38 +161,42 @@ protected String getTableSchemaName(ResultSet resultSet) } @Override - protected String toSqlType(Type type) + public WriteMapping toWriteMapping(Type type) { if (REAL.equals(type)) { - return "float"; + return WriteMapping.longMapping("float", realWriteFunction()); } if (TIME_WITH_TIME_ZONE.equals(type) || TIMESTAMP_WITH_TIME_ZONE.equals(type)) { throw new PrestoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } if (TIMESTAMP.equals(type)) { - return "datetime"; + return WriteMapping.longMapping("datetime", timestampWriteFunction()); } if (VARBINARY.equals(type)) { - return "mediumblob"; + return WriteMapping.sliceMapping("mediumblob", varbinaryWriteFunction()); } 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 WriteMapping.sliceMapping(dataType, varcharWriteFunction()); } - return super.toSqlType(type); + return super.toWriteMapping(type); } @Override 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 92f3550f7578..b9c73e550a15 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 86642b1fe8fd..60b9b543f185 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 @@ -17,11 +17,11 @@ import com.facebook.presto.common.type.VarcharType; import com.facebook.presto.plugin.jdbc.BaseJdbcClient; import com.facebook.presto.plugin.jdbc.BaseJdbcConfig; +import com.facebook.presto.plugin.jdbc.ColumnMapping; import com.facebook.presto.plugin.jdbc.ConnectionFactory; 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.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.StandardColumnMappings.bigintColumnMapping; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.decimalColumnMapping; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.doubleColumnMapping; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.realColumnMapping; +import static com.facebook.presto.plugin.jdbc.StandardColumnMappings.smallIntColumnMapping; +import static com.facebook.presto.plugin.jdbc.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 418b56edd4e9..f03c25386b66 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 @@ -20,11 +20,14 @@ import com.facebook.presto.common.type.TypeSignature; import com.facebook.presto.plugin.jdbc.BaseJdbcClient; import com.facebook.presto.plugin.jdbc.BaseJdbcConfig; +import com.facebook.presto.plugin.jdbc.ColumnMapping; import com.facebook.presto.plugin.jdbc.DriverConnectionFactory; 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.SliceWriteFunction; +import com.facebook.presto.plugin.jdbc.StandardColumnMappings; +import com.facebook.presto.plugin.jdbc.WriteMapping; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.PrestoException; @@ -37,6 +40,7 @@ import io.airlift.slice.Slice; import io.airlift.slice.SliceOutput; import org.postgresql.Driver; +import org.postgresql.util.PGobject; import javax.inject.Inject; @@ -100,17 +104,19 @@ 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 WriteMapping.sliceMapping("bytea", StandardColumnMappings.varbinaryWriteFunction()); } - - return super.toSqlType(type); + if (type.getTypeSignature().getBase().equals(StandardTypes.JSON)) { + return WriteMapping.sliceMapping("jsonb", jsonWriteFunction()); + } + 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()); @@ -148,11 +154,22 @@ 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))), + jsonWriteFunction()); + } + + private SliceWriteFunction jsonWriteFunction() + { + return ((statement, index, value) -> { + PGobject pgObject = new PGobject(); + pgObject.setType("json"); + pgObject.setValue(value.toStringUtf8()); + statement.setObject(index, pgObject); + }); } public static Slice jsonParse(Slice slice) 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 5ca55b4b7787..0891ca1b2d0a 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 @@ -274,7 +274,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 @@ -361,4 +361,23 @@ private DataSetup postgresCreateAndInsert(String tableNamePrefix) { return new CreateAndInsertDataSetup(new JdbcSqlExecutor(postgreSqlServer.getJdbcUrl()), tableNamePrefix); } + + @Test + public void testJsonDataType() + { + JdbcSqlExecutor jdbcSqlExecutor = new JdbcSqlExecutor(postgreSqlServer.getJdbcUrl()); + jdbcSqlExecutor.execute("CREATE TABLE tpch.test_json(key varchar(5), json_column json, jsonb_column jsonb)"); + try { + assertQuery( + "SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'tpch' AND table_name = 'test_json'", + "VALUES ('key','varchar(5)'),('json_column','json'),('jsonb_column','json')"); + assertUpdate("INSERT INTO tpch.test_json VALUES ('1', json'{\"x\":123}',json'{\"x\": 123}' )", 1); + assertQuery("SELECT * FROM tpch.test_json", "SELECT '1' \"key\", '{\"x\": 123}' json_column, '{\"x\":123}' jsonb_column"); + assertQuery("SELECT * FROM test_json WHERE json_column = json'{\"x\":123}'", "SELECT '1' \"key\", '{\"x\": 123}' json_column, '{\"x\":123}' jsonb_column"); + assertUpdate("INSERT INTO test_json VALUES ('1', json'{\"x\":123}',json'{\"x\": 123}' )", 1); + } + finally { + jdbcSqlExecutor.execute("DROP TABLE tpch.test_json"); + } + } }