Skip to content

Commit 3035c52

Browse files
committed
Add support for TIMESTAMP types in Exasol connector
1 parent a8da02b commit 3035c52

File tree

3 files changed

+498
-0
lines changed

3 files changed

+498
-0
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) WITH TIME ZONE``
111+
-
106112
:::
107113

108114
No other types are supported.

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

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import io.trino.plugin.jdbc.JdbcTypeHandle;
3030
import io.trino.plugin.jdbc.LongReadFunction;
3131
import io.trino.plugin.jdbc.LongWriteFunction;
32+
import io.trino.plugin.jdbc.ObjectReadFunction;
33+
import io.trino.plugin.jdbc.ObjectWriteFunction;
3234
import io.trino.plugin.jdbc.QueryBuilder;
3335
import io.trino.plugin.jdbc.SliceReadFunction;
3436
import io.trino.plugin.jdbc.SliceWriteFunction;
@@ -42,44 +44,71 @@
4244
import io.trino.spi.connector.ColumnPosition;
4345
import io.trino.spi.connector.ConnectorSession;
4446
import io.trino.spi.connector.ConnectorTableMetadata;
47+
import io.trino.spi.type.LongTimestamp;
48+
import io.trino.spi.type.LongTimestampWithTimeZone;
49+
import io.trino.spi.type.TimestampType;
50+
import io.trino.spi.type.TimestampWithTimeZoneType;
4551
import io.trino.spi.type.Type;
4652

4753
import java.sql.Connection;
4854
import java.sql.Date;
55+
import java.sql.PreparedStatement;
56+
import java.sql.SQLException;
57+
import java.sql.Timestamp;
4958
import java.sql.Types;
59+
import java.time.Instant;
5060
import java.time.LocalDate;
61+
import java.time.LocalDateTime;
5162
import java.util.HexFormat;
5263
import java.util.List;
5364
import java.util.Map;
5465
import java.util.Optional;
5566
import java.util.OptionalLong;
5667
import java.util.Set;
5768

69+
import static com.google.common.base.Preconditions.checkArgument;
70+
import static io.trino.plugin.jdbc.PredicatePushdownController.FULL_PUSHDOWN;
5871
import static io.trino.plugin.jdbc.StandardColumnMappings.bigintColumnMapping;
5972
import static io.trino.plugin.jdbc.StandardColumnMappings.booleanColumnMapping;
6073
import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping;
6174
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultCharColumnMapping;
6275
import static io.trino.plugin.jdbc.StandardColumnMappings.defaultVarcharColumnMapping;
6376
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;
6479
import static io.trino.plugin.jdbc.StandardColumnMappings.integerColumnMapping;
6580
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;
6683
import static io.trino.plugin.jdbc.TypeHandlingJdbcSessionProperties.getUnsupportedTypeHandling;
6784
import static io.trino.plugin.jdbc.UnsupportedTypeHandling.CONVERT_TO_VARCHAR;
6885
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
6986
import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE;
87+
import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone;
88+
import static io.trino.spi.type.DateTimeEncoding.unpackMillisUtc;
7089
import static io.trino.spi.type.DateType.DATE;
7190
import static io.trino.spi.type.DecimalType.createDecimalType;
91+
import static io.trino.spi.type.TimeZoneKey.UTC_KEY;
92+
import static io.trino.spi.type.TimestampType.createTimestampType;
93+
import static io.trino.spi.type.TimestampWithTimeZoneType.createTimestampWithTimeZoneType;
94+
import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MILLISECOND;
95+
import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND;
7296
import static io.trino.spi.type.VarbinaryType.VARBINARY;
97+
import static java.lang.String.format;
7398
import static java.util.Locale.ENGLISH;
7499

75100
public class ExasolClient
76101
extends BaseJdbcClient
77102
{
103+
private static final int EXASOL_TIMESTAMP_WITH_TIMEZONE = 124;
104+
78105
private static final Set<String> INTERNAL_SCHEMAS = ImmutableSet.<String>builder()
79106
.add("EXA_STATISTICS")
80107
.add("SYS")
81108
.build();
82109

110+
private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9;
111+
83112
@Inject
84113
public ExasolClient(
85114
BaseJdbcConfig config,
@@ -237,6 +266,10 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
237266
return Optional.of(defaultVarcharColumnMapping(typeHandle.requiredColumnSize(), true));
238267
case Types.DATE:
239268
return Optional.of(dateColumnMapping());
269+
case Types.TIMESTAMP:
270+
return Optional.of(timestampColumnMapping(typeHandle));
271+
case EXASOL_TIMESTAMP_WITH_TIMEZONE:
272+
return Optional.of(timestampWithTimeZoneColumnMapping(typeHandle));
240273
}
241274

242275
if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) {
@@ -252,6 +285,257 @@ private boolean isHashType(JdbcTypeHandle typeHandle)
252285
&& typeHandle.jdbcTypeName().get().equalsIgnoreCase("HASHTYPE");
253286
}
254287

288+
private static ColumnMapping timestampColumnMapping(JdbcTypeHandle typeHandle)
289+
{
290+
int timestampPrecision = typeHandle.requiredDecimalDigits();
291+
TimestampType timestampType = createTimestampType(timestampPrecision);
292+
if (timestampType.isShort()) {
293+
return ColumnMapping.longMapping(
294+
timestampType,
295+
longTimestampReadFunction(timestampType),
296+
longTimestampWriteFunction(timestampType),
297+
FULL_PUSHDOWN);
298+
}
299+
return ColumnMapping.objectMapping(
300+
timestampType,
301+
objectTimestampReadFunction(timestampType),
302+
objectTimestampWriteFunction(timestampType),
303+
FULL_PUSHDOWN);
304+
}
305+
306+
private static LongReadFunction longTimestampReadFunction(TimestampType timestampType)
307+
{
308+
return (resultSet, columnIndex) -> {
309+
Timestamp timestamp = resultSet.getObject(columnIndex, Timestamp.class);
310+
return toTrinoTimestamp(timestampType, timestamp.toLocalDateTime());
311+
};
312+
}
313+
314+
private static ObjectReadFunction objectTimestampReadFunction(TimestampType timestampType)
315+
{
316+
verifyObjectTimestampPrecision(timestampType);
317+
return ObjectReadFunction.of(
318+
LongTimestamp.class,
319+
(resultSet, columnIndex) -> {
320+
Timestamp timestamp = resultSet.getObject(columnIndex, Timestamp.class);
321+
return toLongTrinoTimestamp(timestampType, timestamp.toLocalDateTime());
322+
});
323+
}
324+
325+
private static void verifyObjectTimestampPrecision(TimestampType timestampType)
326+
{
327+
int precision = timestampType.getPrecision();
328+
checkArgument(precision > TimestampType.MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION,
329+
"Precision is out of range: %s", precision);
330+
}
331+
332+
private static ObjectWriteFunction objectTimestampWriteFunction(TimestampType timestampType)
333+
{
334+
int precision = timestampType.getPrecision();
335+
verifyObjectTimestampPrecision(timestampType);
336+
337+
return new ObjectWriteFunction() {
338+
@Override
339+
public Class<?> getJavaType()
340+
{
341+
return LongTimestamp.class;
342+
}
343+
344+
@Override
345+
public void set(PreparedStatement statement, int index, Object value)
346+
throws SQLException
347+
{
348+
LocalDateTime localDateTime = fromLongTrinoTimestamp((LongTimestamp) value, precision);
349+
Timestamp timestamp = Timestamp.valueOf(localDateTime);
350+
statement.setObject(index, timestamp);
351+
}
352+
353+
@Override
354+
public String getBindExpression()
355+
{
356+
return getTimestampBindExpression(timestampType);
357+
}
358+
359+
@Override
360+
public void setNull(PreparedStatement statement, int index)
361+
throws SQLException
362+
{
363+
statement.setNull(index, Types.VARCHAR);
364+
}
365+
};
366+
}
367+
368+
private static LongWriteFunction longTimestampWriteFunction(TimestampType timestampType)
369+
{
370+
return new LongWriteFunction()
371+
{
372+
@Override
373+
public String getBindExpression()
374+
{
375+
return getTimestampBindExpression(timestampType);
376+
}
377+
378+
@Override
379+
public void set(PreparedStatement statement, int index, long epochMicros)
380+
throws SQLException
381+
{
382+
LocalDateTime localDateTime = fromTrinoTimestamp(epochMicros);
383+
Timestamp timestampValue = Timestamp.valueOf(localDateTime);
384+
statement.setObject(index, timestampValue);
385+
}
386+
387+
@Override
388+
public void setNull(PreparedStatement statement, int index)
389+
throws SQLException
390+
{
391+
statement.setNull(index, Types.VARCHAR);
392+
}
393+
};
394+
}
395+
396+
private static ColumnMapping timestampWithTimeZoneColumnMapping(JdbcTypeHandle typeHandle)
397+
{
398+
int timestampPrecision = typeHandle.requiredDecimalDigits();
399+
TimestampWithTimeZoneType timestampWithTimeZoneType = createTimestampWithTimeZoneType(timestampPrecision);
400+
401+
if (timestampWithTimeZoneType.isShort()) {
402+
return ColumnMapping.longMapping(
403+
timestampWithTimeZoneType,
404+
longTimestampWithTimeZoneReadFunction(),
405+
longTimestampWithTimeZoneWriteFunction(timestampWithTimeZoneType),
406+
FULL_PUSHDOWN);
407+
}
408+
return ColumnMapping.objectMapping(
409+
timestampWithTimeZoneType,
410+
objectTimestampWithTimeZoneReadFunction(timestampWithTimeZoneType),
411+
objectTimestampWithTimeZoneWriteFunction(timestampWithTimeZoneType),
412+
FULL_PUSHDOWN);
413+
}
414+
415+
private static LongReadFunction longTimestampWithTimeZoneReadFunction()
416+
{
417+
return (resultSet, columnIndex) -> {
418+
Timestamp timestamp = resultSet.getObject(columnIndex, Timestamp.class);
419+
return packDateTimeWithZone(timestamp.getTime(), UTC_KEY);
420+
};
421+
}
422+
423+
private static ObjectReadFunction objectTimestampWithTimeZoneReadFunction(
424+
TimestampWithTimeZoneType timestampType)
425+
{
426+
verifyObjectTimestampWithTimeZonePrecision(timestampType);
427+
return ObjectReadFunction.of(
428+
LongTimestampWithTimeZone.class,
429+
(resultSet, columnIndex) -> {
430+
Timestamp timestamp = resultSet.getObject(columnIndex, Timestamp.class);
431+
432+
long millisUtc = timestamp.getTime();
433+
long nanosUtc = millisUtc * NANOSECONDS_PER_MILLISECOND + timestamp.getNanos() % NANOSECONDS_PER_MILLISECOND;
434+
int picosOfMilli = (int) ((nanosUtc - millisUtc * NANOSECONDS_PER_MILLISECOND) * PICOSECONDS_PER_NANOSECOND);
435+
436+
return LongTimestampWithTimeZone.fromEpochMillisAndFraction(
437+
millisUtc,
438+
picosOfMilli,
439+
UTC_KEY);
440+
});
441+
}
442+
443+
private static void verifyObjectTimestampWithTimeZonePrecision(TimestampWithTimeZoneType timestampType)
444+
{
445+
int precision = timestampType.getPrecision();
446+
checkArgument(precision > TimestampWithTimeZoneType.MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION,
447+
"Precision is out of range: %s", precision);
448+
}
449+
450+
private static ObjectWriteFunction objectTimestampWithTimeZoneWriteFunction(TimestampWithTimeZoneType timestampType)
451+
{
452+
verifyObjectTimestampWithTimeZonePrecision(timestampType);
453+
454+
return new ObjectWriteFunction() {
455+
@Override
456+
public Class<?> getJavaType()
457+
{
458+
return LongTimestampWithTimeZone.class;
459+
}
460+
461+
@Override
462+
public void set(PreparedStatement statement, int index, Object value)
463+
throws SQLException
464+
{
465+
LongTimestampWithTimeZone longTimestampWithTimeZone = (LongTimestampWithTimeZone) value;
466+
Instant instant = Instant.ofEpochMilli(longTimestampWithTimeZone.getEpochMillis())
467+
.plusNanos(longTimestampWithTimeZone.getPicosOfMilli() / PICOSECONDS_PER_NANOSECOND);
468+
Timestamp timestamp = Timestamp.from(instant);
469+
statement.setObject(index, timestamp);
470+
}
471+
472+
@Override
473+
public String getBindExpression()
474+
{
475+
return getTimestampWithTimeZoneBindExpression(timestampType);
476+
}
477+
478+
@Override
479+
public void setNull(PreparedStatement statement, int index)
480+
throws SQLException
481+
{
482+
statement.setNull(index, Types.VARCHAR);
483+
}
484+
};
485+
}
486+
487+
private static LongWriteFunction longTimestampWithTimeZoneWriteFunction(TimestampWithTimeZoneType timestampType)
488+
{
489+
return new LongWriteFunction()
490+
{
491+
@Override
492+
public String getBindExpression()
493+
{
494+
return getTimestampWithTimeZoneBindExpression(timestampType);
495+
}
496+
497+
@Override
498+
public void set(PreparedStatement statement, int index, long dateTimeWithTimeZone)
499+
throws SQLException
500+
{
501+
long epochMillis = unpackMillisUtc(dateTimeWithTimeZone);
502+
Timestamp timestampValue = Timestamp.from(Instant.ofEpochMilli(epochMillis));
503+
statement.setObject(index, timestampValue);
504+
}
505+
506+
@Override
507+
public void setNull(PreparedStatement statement, int index)
508+
throws SQLException
509+
{
510+
statement.setNull(index, Types.VARCHAR);
511+
}
512+
};
513+
}
514+
515+
private static String getTimestampBindExpression(TimestampType timestampType)
516+
{
517+
return getTimestampBindExpression(timestampType.getPrecision());
518+
}
519+
520+
private static String getTimestampWithTimeZoneBindExpression(TimestampWithTimeZoneType timestampWithTimeZoneType)
521+
{
522+
return getTimestampBindExpression(timestampWithTimeZoneType.getPrecision());
523+
}
524+
525+
/**
526+
* Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
527+
* based on the given fractional seconds precision.
528+
* See for more details: https://docs.exasol.com/db/latest/sql_references/formatmodels.htm
529+
*/
530+
private static String getTimestampBindExpression(int precision)
531+
{
532+
//negative precisions are not supported by TimestampType and TimestampWithTimeZoneType
533+
if (precision == 0) {
534+
return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')";
535+
}
536+
return format("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')", precision);
537+
}
538+
255539
private static ColumnMapping dateColumnMapping()
256540
{
257541
// Exasol driver does not support LocalDate

0 commit comments

Comments
 (0)