Skip to content

Commit b39f8d4

Browse files
committed
Add support for TIMESTAMP type in exasol connector
1 parent ce04924 commit b39f8d4

3 files changed

Lines changed: 354 additions & 0 deletions

File tree

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: 153 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;
@@ -58,20 +66,28 @@
5866
import java.util.function.BiFunction;
5967
import java.util.function.Consumer;
6068

69+
import static com.google.common.base.Preconditions.checkArgument;
70+
import static io.trino.plugin.jdbc.PredicatePushdownController.FULL_PUSHDOWN;
6171
import static io.trino.plugin.jdbc.StandardColumnMappings.bigintColumnMapping;
6272
import static io.trino.plugin.jdbc.StandardColumnMappings.booleanColumnMapping;
6373
import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping;
6474
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultCharColumnMapping;
6575
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultVarcharColumnMapping;
6676
import static io.trino.plugin.jdbc.StandardColumnMappings.doubleColumnMapping;
77+
import static io.trino.plugin.jdbc.StandardColumnMappings.fromLongTrinoTimestamp;
78+
import static io.trino.plugin.jdbc.StandardColumnMappings.fromTrinoTimestamp;
6779
import static io.trino.plugin.jdbc.StandardColumnMappings.integerColumnMapping;
6880
import static io.trino.plugin.jdbc.StandardColumnMappings.smallintColumnMapping;
81+
import static io.trino.plugin.jdbc.StandardColumnMappings.toLongTrinoTimestamp;
82+
import static io.trino.plugin.jdbc.StandardColumnMappings.toTrinoTimestamp;
6983
import static io.trino.plugin.jdbc.TypeHandlingJdbcSessionProperties.getUnsupportedTypeHandling;
7084
import static io.trino.plugin.jdbc.UnsupportedTypeHandling.CONVERT_TO_VARCHAR;
7185
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
7286
import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE;
7387
import static io.trino.spi.type.DateType.DATE;
7488
import static io.trino.spi.type.DecimalType.createDecimalType;
89+
import static io.trino.spi.type.TimestampType.MAX_SHORT_PRECISION;
90+
import static io.trino.spi.type.TimestampType.createTimestampType;
7591
import static io.trino.spi.type.VarbinaryType.VARBINARY;
7692
import static java.lang.String.format;
7793
import static java.util.Locale.ENGLISH;
@@ -85,6 +101,8 @@ public class ExasolClient
85101
.add("SYS")
86102
.build();
87103

104+
private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9;
105+
88106
@Inject
89107
public ExasolClient(
90108
BaseJdbcConfig config,
@@ -240,8 +258,12 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
240258
// String data is sorted by its binary representation.
241259
// https://docs.exasol.com/db/latest/sql/select.htm#UsageNotes
242260
return Optional.of(defaultVarcharColumnMapping(typeHandle.requiredColumnSize(), true));
261+
// DATE and TIMESTAMP types are described here in more details:
262+
// https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm
243263
case Types.DATE:
244264
return Optional.of(dateColumnMapping());
265+
case Types.TIMESTAMP:
266+
return Optional.of(timestampColumnMapping(typeHandle));
245267
}
246268

247269
if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) {
@@ -308,6 +330,137 @@ private static SliceWriteFunction hashTypeWriteFunction()
308330
});
309331
}
310332

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

0 commit comments

Comments
 (0)