Skip to content

Commit a22f7ac

Browse files
committed
Add support for TIMESTAMP type in exasol connector
1 parent 24201f3 commit a22f7ac

File tree

3 files changed

+312
-0
lines changed

3 files changed

+312
-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: 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;
@@ -57,20 +65,28 @@
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.MAX_SHORT_PRECISION;
89+
import static io.trino.spi.type.TimestampType.createTimestampType;
7490
import static io.trino.spi.type.VarbinaryType.VARBINARY;
7591
import static java.lang.String.format;
7692
import static java.util.Locale.ENGLISH;
@@ -84,6 +100,8 @@ public class ExasolClient
84100
.add("SYS")
85101
.build();
86102

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

246268
if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) {
@@ -307,6 +329,137 @@ private static SliceWriteFunction hashTypeWriteFunction()
307329
});
308330
}
309331

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

0 commit comments

Comments
 (0)