Skip to content

Commit 70b19d8

Browse files
committed
Add support for TIMESTAMP type in exasol connector
1 parent 57b16da commit 70b19d8

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ Trino data type mapping:
100100
* - `DATE`
101101
- `DATE`
102102
-
103+
* - `TIMESTAMP(n)`
104+
- `TIMESTAMP(n)`
105+
-
103106
* - `HASHTYPE`
104107
- `VARBINARY`
105108
-

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

Lines changed: 143 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;
@@ -84,6 +99,8 @@ public class ExasolClient
8499
.add("SYS")
85100
.build();
86101

102+
private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9;
103+
87104
@Inject
88105
public ExasolClient(
89106
BaseJdbcConfig config,
@@ -239,8 +256,12 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
239256
// String data is sorted by its binary representation.
240257
// https://docs.exasol.com/db/latest/sql/select.htm#UsageNotes
241258
return Optional.of(defaultVarcharColumnMapping(typeHandle.requiredColumnSize(), true));
259+
// DATE and TIMESTAMP types are described here in more details:
260+
// https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm
242261
case Types.DATE:
243262
return Optional.of(dateColumnMapping());
263+
case Types.TIMESTAMP:
264+
return Optional.of(timestampColumnMapping(typeHandle));
244265
}
245266

246267
if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) {
@@ -307,6 +328,128 @@ private static SliceWriteFunction hashTypeWriteFunction()
307328
});
308329
}
309330

331+
private static ColumnMapping timestampColumnMapping(JdbcTypeHandle typeHandle)
332+
{
333+
int timestampPrecision = typeHandle.requiredDecimalDigits();
334+
TimestampType timestampType = createTimestampType(timestampPrecision);
335+
if (timestampType.isShort()) {
336+
return ColumnMapping.longMapping(
337+
timestampType,
338+
longTimestampReadFunction(timestampType),
339+
longTimestampWriteFunction(timestampType),
340+
FULL_PUSHDOWN);
341+
}
342+
return ColumnMapping.objectMapping(
343+
timestampType,
344+
objectTimestampReadFunction(timestampType),
345+
objectTimestampWriteFunction(timestampType),
346+
FULL_PUSHDOWN);
347+
}
348+
349+
private static LongReadFunction longTimestampReadFunction(TimestampType timestampType)
350+
{
351+
return (resultSet, columnIndex) -> {
352+
Timestamp timestamp = resultSet.getTimestamp(columnIndex);
353+
return toTrinoTimestamp(timestampType, timestamp.toLocalDateTime());
354+
};
355+
}
356+
357+
private static LongWriteFunction longTimestampWriteFunction(TimestampType timestampType)
358+
{
359+
return new LongWriteFunction()
360+
{
361+
@Override
362+
public String getBindExpression()
363+
{
364+
return getTimestampBindExpression(timestampType.getPrecision());
365+
}
366+
367+
@Override
368+
public void set(PreparedStatement statement, int index, long epochMicros)
369+
throws SQLException
370+
{
371+
LocalDateTime localDateTime = fromTrinoTimestamp(epochMicros);
372+
Timestamp timestampValue = Timestamp.valueOf(localDateTime);
373+
statement.setTimestamp(index, timestampValue);
374+
}
375+
376+
@Override
377+
public void setNull(PreparedStatement statement, int index)
378+
throws SQLException
379+
{
380+
statement.setNull(index, Types.VARCHAR);
381+
}
382+
};
383+
}
384+
385+
private static ObjectReadFunction objectTimestampReadFunction(TimestampType timestampType)
386+
{
387+
verifyObjectTimestampPrecision(timestampType);
388+
return ObjectReadFunction.of(
389+
LongTimestamp.class,
390+
(resultSet, columnIndex) -> {
391+
Timestamp timestamp = resultSet.getTimestamp(columnIndex);
392+
return toLongTrinoTimestamp(timestampType, timestamp.toLocalDateTime());
393+
});
394+
}
395+
396+
private static ObjectWriteFunction objectTimestampWriteFunction(TimestampType timestampType)
397+
{
398+
int precision = timestampType.getPrecision();
399+
verifyObjectTimestampPrecision(timestampType);
400+
401+
return new ObjectWriteFunction() {
402+
@Override
403+
public Class<?> getJavaType()
404+
{
405+
return LongTimestamp.class;
406+
}
407+
408+
@Override
409+
public void set(PreparedStatement statement, int index, Object value)
410+
throws SQLException
411+
{
412+
LocalDateTime localDateTime = fromLongTrinoTimestamp((LongTimestamp) value, precision);
413+
Timestamp timestamp = Timestamp.valueOf(localDateTime);
414+
statement.setTimestamp(index, timestamp);
415+
}
416+
417+
@Override
418+
public String getBindExpression()
419+
{
420+
return getTimestampBindExpression(timestampType.getPrecision());
421+
}
422+
423+
@Override
424+
public void setNull(PreparedStatement statement, int index)
425+
throws SQLException
426+
{
427+
statement.setNull(index, Types.VARCHAR);
428+
}
429+
};
430+
}
431+
432+
private static void verifyObjectTimestampPrecision(TimestampType timestampType)
433+
{
434+
int precision = timestampType.getPrecision();
435+
checkArgument(precision > TimestampType.MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION,
436+
"Precision is out of range: %s", precision);
437+
}
438+
439+
/**
440+
* Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
441+
* based on the given fractional seconds precision.
442+
* See for more details: <a href="https://docs.exasol.com/db/latest/sql_references/formatmodels.htm">Date/time format models</a>
443+
*/
444+
private static String getTimestampBindExpression(int precision)
445+
{
446+
checkArgument(precision >= 0, "Precision is negative: %s", precision);
447+
if (precision == 0) {
448+
return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')";
449+
}
450+
return format("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')", precision);
451+
}
452+
310453
@Override
311454
public WriteMapping toWriteMapping(ConnectorSession session, Type type)
312455
{

0 commit comments

Comments
 (0)