Skip to content

Commit fbab6f8

Browse files
committed
Add support for TIMESTAMP types in exasol connector
1 parent 77af5ad commit fbab6f8

File tree

3 files changed

+414
-14
lines changed

3 files changed

+414
-14
lines changed

docs/src/main/sphinx/connector/exasol.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ Trino data type mapping:
103103
* - `HASHTYPE`
104104
- `VARBINARY`
105105
-
106+
* - `TIMESTAMP(n)`
107+
- `TIMESTAMP(n)`
108+
-
109+
* - `TIMESTAMP(n) WITH LOCAL TIME ZONE`
110+
- `TIMESTAMP(n)`
111+
-
106112
:::
107113

108114
No other types are supported.

plugin/trino-exasol/src/main/java/io/trino/plugin/exasol/ExasolClient.java

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import io.trino.plugin.jdbc.JdbcTypeHandle;
3131
import io.trino.plugin.jdbc.LongReadFunction;
3232
import io.trino.plugin.jdbc.LongWriteFunction;
33+
import io.trino.plugin.jdbc.ObjectReadFunction;
34+
import io.trino.plugin.jdbc.ObjectWriteFunction;
3335
import io.trino.plugin.jdbc.QueryBuilder;
3436
import io.trino.plugin.jdbc.SliceReadFunction;
3537
import io.trino.plugin.jdbc.SliceWriteFunction;
@@ -43,12 +45,18 @@
4345
import io.trino.spi.connector.ColumnPosition;
4446
import io.trino.spi.connector.ConnectorSession;
4547
import io.trino.spi.connector.ConnectorTableMetadata;
48+
import io.trino.spi.type.LongTimestamp;
49+
import io.trino.spi.type.TimestampType;
4650
import io.trino.spi.type.Type;
4751

4852
import java.sql.Connection;
4953
import java.sql.Date;
54+
import java.sql.PreparedStatement;
55+
import java.sql.SQLException;
56+
import java.sql.Timestamp;
5057
import java.sql.Types;
5158
import java.time.LocalDate;
59+
import java.time.LocalDateTime;
5260
import java.util.HexFormat;
5361
import java.util.List;
5462
import java.util.Map;
@@ -57,20 +65,27 @@
5765
import java.util.Set;
5866
import java.util.function.BiFunction;
5967

68+
import static com.google.common.base.Preconditions.checkArgument;
69+
import static io.trino.plugin.jdbc.PredicatePushdownController.FULL_PUSHDOWN;
6070
import static io.trino.plugin.jdbc.StandardColumnMappings.bigintColumnMapping;
6171
import static io.trino.plugin.jdbc.StandardColumnMappings.booleanColumnMapping;
6272
import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping;
6373
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultCharColumnMapping;
6474
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultVarcharColumnMapping;
6575
import static io.trino.plugin.jdbc.StandardColumnMappings.doubleColumnMapping;
76+
import static io.trino.plugin.jdbc.StandardColumnMappings.fromLongTrinoTimestamp;
77+
import static io.trino.plugin.jdbc.StandardColumnMappings.fromTrinoTimestamp;
6678
import static io.trino.plugin.jdbc.StandardColumnMappings.integerColumnMapping;
6779
import static io.trino.plugin.jdbc.StandardColumnMappings.smallintColumnMapping;
80+
import static io.trino.plugin.jdbc.StandardColumnMappings.toLongTrinoTimestamp;
81+
import static io.trino.plugin.jdbc.StandardColumnMappings.toTrinoTimestamp;
6882
import static io.trino.plugin.jdbc.TypeHandlingJdbcSessionProperties.getUnsupportedTypeHandling;
6983
import static io.trino.plugin.jdbc.UnsupportedTypeHandling.CONVERT_TO_VARCHAR;
7084
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
7185
import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE;
7286
import static io.trino.spi.type.DateType.DATE;
7387
import static io.trino.spi.type.DecimalType.createDecimalType;
88+
import static io.trino.spi.type.TimestampType.createTimestampType;
7489
import static io.trino.spi.type.VarbinaryType.VARBINARY;
7590
import static java.lang.String.format;
7691
import static java.util.Locale.ENGLISH;
@@ -79,11 +94,15 @@
7994
public class ExasolClient
8095
extends BaseJdbcClient
8196
{
97+
private static final int EXASOL_TIMESTAMP_WITH_TIMEZONE = 124;
98+
8299
private static final Set<String> INTERNAL_SCHEMAS = ImmutableSet.<String>builder()
83100
.add("EXA_STATISTICS")
84101
.add("SYS")
85102
.build();
86103

104+
private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9;
105+
87106
@Inject
88107
public ExasolClient(
89108
BaseJdbcConfig config,
@@ -239,8 +258,13 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
239258
// String data is sorted by its binary representation.
240259
// https://docs.exasol.com/db/latest/sql/select.htm#UsageNotes
241260
return Optional.of(defaultVarcharColumnMapping(typeHandle.requiredColumnSize(), true));
261+
// DATE, TIMESTAMP and TIMESTAMP WITH LOCAL TIME ZONE Types are described here in more details:
262+
// https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm
242263
case Types.DATE:
243264
return Optional.of(dateColumnMapping());
265+
case Types.TIMESTAMP:
266+
case EXASOL_TIMESTAMP_WITH_TIMEZONE:
267+
return Optional.of(timestampColumnMapping(typeHandle));
244268
}
245269

246270
if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) {
@@ -256,6 +280,128 @@ private boolean isHashType(JdbcTypeHandle typeHandle)
256280
&& typeHandle.jdbcTypeName().get().equalsIgnoreCase("HASHTYPE");
257281
}
258282

283+
private static ColumnMapping timestampColumnMapping(JdbcTypeHandle typeHandle)
284+
{
285+
int timestampPrecision = typeHandle.requiredDecimalDigits();
286+
TimestampType timestampType = createTimestampType(timestampPrecision);
287+
if (timestampType.isShort()) {
288+
return ColumnMapping.longMapping(
289+
timestampType,
290+
longTimestampReadFunction(timestampType),
291+
longTimestampWriteFunction(timestampType),
292+
FULL_PUSHDOWN);
293+
}
294+
return ColumnMapping.objectMapping(
295+
timestampType,
296+
objectTimestampReadFunction(timestampType),
297+
objectTimestampWriteFunction(timestampType),
298+
FULL_PUSHDOWN);
299+
}
300+
301+
private static LongReadFunction longTimestampReadFunction(TimestampType timestampType)
302+
{
303+
return (resultSet, columnIndex) -> {
304+
Timestamp timestamp = resultSet.getObject(columnIndex, Timestamp.class);
305+
return toTrinoTimestamp(timestampType, timestamp.toLocalDateTime());
306+
};
307+
}
308+
309+
private static ObjectReadFunction objectTimestampReadFunction(TimestampType timestampType)
310+
{
311+
verifyObjectTimestampPrecision(timestampType);
312+
return ObjectReadFunction.of(
313+
LongTimestamp.class,
314+
(resultSet, columnIndex) -> {
315+
Timestamp timestamp = resultSet.getObject(columnIndex, Timestamp.class);
316+
return toLongTrinoTimestamp(timestampType, timestamp.toLocalDateTime());
317+
});
318+
}
319+
320+
private static void verifyObjectTimestampPrecision(TimestampType timestampType)
321+
{
322+
int precision = timestampType.getPrecision();
323+
checkArgument(precision > TimestampType.MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION,
324+
"Precision is out of range: %s", precision);
325+
}
326+
327+
private static ObjectWriteFunction objectTimestampWriteFunction(TimestampType timestampType)
328+
{
329+
int precision = timestampType.getPrecision();
330+
verifyObjectTimestampPrecision(timestampType);
331+
332+
return new ObjectWriteFunction() {
333+
@Override
334+
public Class<?> getJavaType()
335+
{
336+
return LongTimestamp.class;
337+
}
338+
339+
@Override
340+
public void set(PreparedStatement statement, int index, Object value)
341+
throws SQLException
342+
{
343+
LocalDateTime localDateTime = fromLongTrinoTimestamp((LongTimestamp) value, precision);
344+
Timestamp timestamp = Timestamp.valueOf(localDateTime);
345+
statement.setObject(index, timestamp);
346+
}
347+
348+
@Override
349+
public String getBindExpression()
350+
{
351+
return getTimestampBindExpression(timestampType.getPrecision());
352+
}
353+
354+
@Override
355+
public void setNull(PreparedStatement statement, int index)
356+
throws SQLException
357+
{
358+
statement.setNull(index, Types.VARCHAR);
359+
}
360+
};
361+
}
362+
363+
private static LongWriteFunction longTimestampWriteFunction(TimestampType timestampType)
364+
{
365+
return new LongWriteFunction()
366+
{
367+
@Override
368+
public String getBindExpression()
369+
{
370+
return getTimestampBindExpression(timestampType.getPrecision());
371+
}
372+
373+
@Override
374+
public void set(PreparedStatement statement, int index, long epochMicros)
375+
throws SQLException
376+
{
377+
LocalDateTime localDateTime = fromTrinoTimestamp(epochMicros);
378+
Timestamp timestampValue = Timestamp.valueOf(localDateTime);
379+
statement.setObject(index, timestampValue);
380+
}
381+
382+
@Override
383+
public void setNull(PreparedStatement statement, int index)
384+
throws SQLException
385+
{
386+
statement.setNull(index, Types.VARCHAR);
387+
}
388+
};
389+
}
390+
391+
/**
392+
* Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
393+
* based on the given fractional seconds precision.
394+
* See for more details: https://docs.exasol.com/db/latest/sql_references/formatmodels.htm
395+
*/
396+
private static String getTimestampBindExpression(int precision)
397+
{
398+
// Negative precisions are prevented by TimestampType
399+
if (precision == 0) {
400+
return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')";
401+
}
402+
return format("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')", precision);
403+
}
404+
259405
private static ColumnMapping dateColumnMapping()
260406
{
261407
// Exasol driver does not support LocalDate

0 commit comments

Comments
 (0)