Skip to content

Commit 8dc68e8

Browse files
committed
Add support for TIMESTAMP type in exasol connector
1 parent 26c644a commit 8dc68e8

File tree

3 files changed

+294
-29
lines changed

3 files changed

+294
-29
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: 137 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,7 @@
1717
import com.google.inject.Inject;
1818
import io.airlift.slice.Slices;
1919
import io.trino.plugin.base.mapping.IdentifierMapping;
20-
import io.trino.plugin.jdbc.BaseJdbcClient;
21-
import io.trino.plugin.jdbc.BaseJdbcConfig;
22-
import io.trino.plugin.jdbc.ColumnMapping;
23-
import io.trino.plugin.jdbc.ConnectionFactory;
24-
import io.trino.plugin.jdbc.JdbcColumnHandle;
25-
import io.trino.plugin.jdbc.JdbcExpression;
26-
import io.trino.plugin.jdbc.JdbcJoinCondition;
27-
import io.trino.plugin.jdbc.JdbcOutputTableHandle;
28-
import io.trino.plugin.jdbc.JdbcSortItem;
29-
import io.trino.plugin.jdbc.JdbcTableHandle;
30-
import io.trino.plugin.jdbc.JdbcTypeHandle;
31-
import io.trino.plugin.jdbc.LongReadFunction;
32-
import io.trino.plugin.jdbc.LongWriteFunction;
33-
import io.trino.plugin.jdbc.QueryBuilder;
34-
import io.trino.plugin.jdbc.SliceReadFunction;
35-
import io.trino.plugin.jdbc.SliceWriteFunction;
36-
import io.trino.plugin.jdbc.WriteFunction;
37-
import io.trino.plugin.jdbc.WriteMapping;
20+
import io.trino.plugin.jdbc.*;
3821
import io.trino.plugin.jdbc.logging.RemoteQueryModifier;
3922
import io.trino.spi.TrinoException;
4023
import io.trino.spi.connector.AggregateFunction;
@@ -43,12 +26,13 @@
4326
import io.trino.spi.connector.ColumnPosition;
4427
import io.trino.spi.connector.ConnectorSession;
4528
import io.trino.spi.connector.ConnectorTableMetadata;
29+
import io.trino.spi.type.LongTimestamp;
30+
import io.trino.spi.type.TimestampType;
4631
import io.trino.spi.type.Type;
4732

48-
import java.sql.Connection;
49-
import java.sql.Date;
50-
import java.sql.Types;
33+
import java.sql.*;
5134
import java.time.LocalDate;
35+
import java.time.LocalDateTime;
5236
import java.util.HexFormat;
5337
import java.util.List;
5438
import java.util.Map;
@@ -57,20 +41,16 @@
5741
import java.util.Set;
5842
import java.util.function.BiFunction;
5943

60-
import static io.trino.plugin.jdbc.StandardColumnMappings.bigintColumnMapping;
61-
import static io.trino.plugin.jdbc.StandardColumnMappings.booleanColumnMapping;
62-
import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping;
63-
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultCharColumnMapping;
64-
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultVarcharColumnMapping;
65-
import static io.trino.plugin.jdbc.StandardColumnMappings.doubleColumnMapping;
66-
import static io.trino.plugin.jdbc.StandardColumnMappings.integerColumnMapping;
67-
import static io.trino.plugin.jdbc.StandardColumnMappings.smallintColumnMapping;
44+
import static com.google.common.base.Preconditions.checkArgument;
45+
import static io.trino.plugin.jdbc.PredicatePushdownController.FULL_PUSHDOWN;
46+
import static io.trino.plugin.jdbc.StandardColumnMappings.*;
6847
import static io.trino.plugin.jdbc.TypeHandlingJdbcSessionProperties.getUnsupportedTypeHandling;
6948
import static io.trino.plugin.jdbc.UnsupportedTypeHandling.CONVERT_TO_VARCHAR;
7049
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
7150
import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE;
7251
import static io.trino.spi.type.DateType.DATE;
7352
import static io.trino.spi.type.DecimalType.createDecimalType;
53+
import static io.trino.spi.type.TimestampType.createTimestampType;
7454
import static io.trino.spi.type.VarbinaryType.VARBINARY;
7555
import static java.lang.String.format;
7656
import static java.util.Locale.ENGLISH;
@@ -84,6 +64,8 @@ public class ExasolClient
8464
.add("SYS")
8565
.build();
8666

67+
private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9;
68+
8769
@Inject
8870
public ExasolClient(
8971
BaseJdbcConfig config,
@@ -239,8 +221,12 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
239221
// String data is sorted by its binary representation.
240222
// https://docs.exasol.com/db/latest/sql/select.htm#UsageNotes
241223
return Optional.of(defaultVarcharColumnMapping(typeHandle.requiredColumnSize(), true));
224+
// DATE and TIMESTAMP types are described here in more details:
225+
// https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm
242226
case Types.DATE:
243227
return Optional.of(dateColumnMapping());
228+
case Types.TIMESTAMP:
229+
return Optional.of(timestampColumnMapping(typeHandle));
244230
}
245231

246232
if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) {
@@ -307,6 +293,128 @@ private static SliceWriteFunction hashTypeWriteFunction()
307293
});
308294
}
309295

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

0 commit comments

Comments
 (0)