2929import io .trino .plugin .jdbc .JdbcTypeHandle ;
3030import io .trino .plugin .jdbc .LongReadFunction ;
3131import io .trino .plugin .jdbc .LongWriteFunction ;
32+ import io .trino .plugin .jdbc .ObjectReadFunction ;
33+ import io .trino .plugin .jdbc .ObjectWriteFunction ;
3234import io .trino .plugin .jdbc .QueryBuilder ;
3335import io .trino .plugin .jdbc .SliceReadFunction ;
3436import io .trino .plugin .jdbc .SliceWriteFunction ;
4244import io .trino .spi .connector .ColumnPosition ;
4345import io .trino .spi .connector .ConnectorSession ;
4446import 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 ;
4551import io .trino .spi .type .Type ;
4652
4753import java .sql .Connection ;
4854import java .sql .Date ;
55+ import java .sql .PreparedStatement ;
56+ import java .sql .SQLException ;
57+ import java .sql .Timestamp ;
4958import java .sql .Types ;
59+ import java .time .Instant ;
5060import java .time .LocalDate ;
61+ import java .time .LocalDateTime ;
5162import java .util .HexFormat ;
5263import java .util .List ;
5364import java .util .Map ;
5465import java .util .Optional ;
5566import java .util .OptionalLong ;
5667import java .util .Set ;
5768
69+ import static com .google .common .base .Preconditions .checkArgument ;
70+ import static io .trino .plugin .jdbc .PredicatePushdownController .FULL_PUSHDOWN ;
5871import static io .trino .plugin .jdbc .StandardColumnMappings .bigintColumnMapping ;
5972import static io .trino .plugin .jdbc .StandardColumnMappings .booleanColumnMapping ;
6073import static io .trino .plugin .jdbc .StandardColumnMappings .decimalColumnMapping ;
6174import static io .trino .plugin .jdbc .StandardColumnMappings .defaultCharColumnMapping ;
6275import static io .trino .plugin .jdbc .StandardColumnMappings .defaultVarcharColumnMapping ;
6376import 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 ;
6479import static io .trino .plugin .jdbc .StandardColumnMappings .integerColumnMapping ;
6580import 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 ;
6683import static io .trino .plugin .jdbc .TypeHandlingJdbcSessionProperties .getUnsupportedTypeHandling ;
6784import static io .trino .plugin .jdbc .UnsupportedTypeHandling .CONVERT_TO_VARCHAR ;
6885import static io .trino .spi .StandardErrorCode .NOT_SUPPORTED ;
6986import 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 ;
7089import static io .trino .spi .type .DateType .DATE ;
7190import 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 ;
7296import static io .trino .spi .type .VarbinaryType .VARBINARY ;
97+ import static java .lang .String .format ;
7398import static java .util .Locale .ENGLISH ;
7499
75100public 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