3030import io .trino .plugin .jdbc .JdbcTypeHandle ;
3131import io .trino .plugin .jdbc .LongReadFunction ;
3232import io .trino .plugin .jdbc .LongWriteFunction ;
33+ import io .trino .plugin .jdbc .ObjectReadFunction ;
34+ import io .trino .plugin .jdbc .ObjectWriteFunction ;
3335import io .trino .plugin .jdbc .QueryBuilder ;
3436import io .trino .plugin .jdbc .SliceReadFunction ;
3537import io .trino .plugin .jdbc .SliceWriteFunction ;
4345import io .trino .spi .connector .ColumnPosition ;
4446import io .trino .spi .connector .ConnectorSession ;
4547import io .trino .spi .connector .ConnectorTableMetadata ;
48+ import io .trino .spi .type .LongTimestamp ;
49+ import io .trino .spi .type .LongTimestampWithTimeZone ;
50+ import io .trino .spi .type .TimeZoneKey ;
51+ import io .trino .spi .type .TimestampType ;
52+ import io .trino .spi .type .TimestampWithTimeZoneType ;
4653import io .trino .spi .type .Type ;
4754
4855import java .sql .Connection ;
4956import java .sql .Date ;
57+ import java .sql .PreparedStatement ;
58+ import java .sql .SQLException ;
59+ import java .sql .Timestamp ;
5060import java .sql .Types ;
61+ import java .time .Instant ;
5162import java .time .LocalDate ;
63+ import java .time .LocalDateTime ;
5264import java .util .HexFormat ;
5365import java .util .List ;
5466import java .util .Map ;
5567import java .util .Optional ;
5668import java .util .OptionalLong ;
5769import java .util .Set ;
70+ import java .util .TimeZone ;
5871import java .util .function .BiFunction ;
5972
73+ import static com .google .common .base .Preconditions .checkArgument ;
74+ import static io .trino .plugin .jdbc .PredicatePushdownController .FULL_PUSHDOWN ;
6075import static io .trino .plugin .jdbc .StandardColumnMappings .bigintColumnMapping ;
6176import static io .trino .plugin .jdbc .StandardColumnMappings .booleanColumnMapping ;
6277import static io .trino .plugin .jdbc .StandardColumnMappings .decimalColumnMapping ;
6378import static io .trino .plugin .jdbc .StandardColumnMappings .defaultCharColumnMapping ;
6479import static io .trino .plugin .jdbc .StandardColumnMappings .defaultVarcharColumnMapping ;
6580import static io .trino .plugin .jdbc .StandardColumnMappings .doubleColumnMapping ;
81+ import static io .trino .plugin .jdbc .StandardColumnMappings .fromLongTrinoTimestamp ;
82+ import static io .trino .plugin .jdbc .StandardColumnMappings .fromTrinoTimestamp ;
6683import static io .trino .plugin .jdbc .StandardColumnMappings .integerColumnMapping ;
6784import static io .trino .plugin .jdbc .StandardColumnMappings .smallintColumnMapping ;
85+ import static io .trino .plugin .jdbc .StandardColumnMappings .toLongTrinoTimestamp ;
86+ import static io .trino .plugin .jdbc .StandardColumnMappings .toTrinoTimestamp ;
6887import static io .trino .plugin .jdbc .TypeHandlingJdbcSessionProperties .getUnsupportedTypeHandling ;
6988import static io .trino .plugin .jdbc .UnsupportedTypeHandling .CONVERT_TO_VARCHAR ;
7089import static io .trino .spi .StandardErrorCode .NOT_SUPPORTED ;
7190import static io .trino .spi .connector .ConnectorMetadata .MODIFYING_ROWS_MESSAGE ;
91+ import static io .trino .spi .type .DateTimeEncoding .packDateTimeWithZone ;
92+ import static io .trino .spi .type .DateTimeEncoding .unpackMillisUtc ;
7293import static io .trino .spi .type .DateType .DATE ;
7394import static io .trino .spi .type .DecimalType .createDecimalType ;
95+ import static io .trino .spi .type .TimestampType .createTimestampType ;
96+ import static io .trino .spi .type .TimestampWithTimeZoneType .createTimestampWithTimeZoneType ;
97+ import static io .trino .spi .type .Timestamps .NANOSECONDS_PER_MILLISECOND ;
98+ import static io .trino .spi .type .Timestamps .PICOSECONDS_PER_NANOSECOND ;
7499import static io .trino .spi .type .VarbinaryType .VARBINARY ;
75100import static java .lang .String .format ;
76101import static java .util .Locale .ENGLISH ;
79104public class ExasolClient
80105 extends BaseJdbcClient
81106{
107+ private static final int EXASOL_TIMESTAMP_WITH_TIMEZONE = 124 ;
108+
82109 private static final Set <String > INTERNAL_SCHEMAS = ImmutableSet .<String >builder ()
83110 .add ("EXA_STATISTICS" )
84111 .add ("SYS" )
85112 .build ();
86113
114+ private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9 ;
115+
87116 @ Inject
88117 public ExasolClient (
89118 BaseJdbcConfig config ,
@@ -241,6 +270,10 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
241270 return Optional .of (defaultVarcharColumnMapping (typeHandle .requiredColumnSize (), true ));
242271 case Types .DATE :
243272 return Optional .of (dateColumnMapping ());
273+ case Types .TIMESTAMP :
274+ return Optional .of (timestampColumnMapping (typeHandle ));
275+ case EXASOL_TIMESTAMP_WITH_TIMEZONE :
276+ return Optional .of (timestampWithTimeZoneColumnMapping (typeHandle ));
244277 }
245278
246279 if (getUnsupportedTypeHandling (session ) == CONVERT_TO_VARCHAR ) {
@@ -256,6 +289,257 @@ private boolean isHashType(JdbcTypeHandle typeHandle)
256289 && typeHandle .jdbcTypeName ().get ().equalsIgnoreCase ("HASHTYPE" );
257290 }
258291
292+ private static ColumnMapping timestampColumnMapping (JdbcTypeHandle typeHandle )
293+ {
294+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
295+ TimestampType timestampType = createTimestampType (timestampPrecision );
296+ if (timestampType .isShort ()) {
297+ return ColumnMapping .longMapping (
298+ timestampType ,
299+ longTimestampReadFunction (timestampType ),
300+ longTimestampWriteFunction (timestampType ),
301+ FULL_PUSHDOWN );
302+ }
303+ return ColumnMapping .objectMapping (
304+ timestampType ,
305+ objectTimestampReadFunction (timestampType ),
306+ objectTimestampWriteFunction (timestampType ),
307+ FULL_PUSHDOWN );
308+ }
309+
310+ private static LongReadFunction longTimestampReadFunction (TimestampType timestampType )
311+ {
312+ return (resultSet , columnIndex ) -> {
313+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
314+ return toTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
315+ };
316+ }
317+
318+ private static ObjectReadFunction objectTimestampReadFunction (TimestampType timestampType )
319+ {
320+ verifyObjectTimestampPrecision (timestampType );
321+ return ObjectReadFunction .of (
322+ LongTimestamp .class ,
323+ (resultSet , columnIndex ) -> {
324+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
325+ return toLongTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
326+ });
327+ }
328+
329+ private static void verifyObjectTimestampPrecision (TimestampType timestampType )
330+ {
331+ int precision = timestampType .getPrecision ();
332+ checkArgument (precision > TimestampType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
333+ "Precision is out of range: %s" , precision );
334+ }
335+
336+ private static ObjectWriteFunction objectTimestampWriteFunction (TimestampType timestampType )
337+ {
338+ int precision = timestampType .getPrecision ();
339+ verifyObjectTimestampPrecision (timestampType );
340+
341+ return new ObjectWriteFunction () {
342+ @ Override
343+ public Class <?> getJavaType ()
344+ {
345+ return LongTimestamp .class ;
346+ }
347+
348+ @ Override
349+ public void set (PreparedStatement statement , int index , Object value )
350+ throws SQLException
351+ {
352+ LocalDateTime localDateTime = fromLongTrinoTimestamp ((LongTimestamp ) value , precision );
353+ Timestamp timestamp = Timestamp .valueOf (localDateTime );
354+ statement .setObject (index , timestamp );
355+ }
356+
357+ @ Override
358+ public String getBindExpression ()
359+ {
360+ return getTimestampBindExpression (timestampType );
361+ }
362+
363+ @ Override
364+ public void setNull (PreparedStatement statement , int index )
365+ throws SQLException
366+ {
367+ statement .setNull (index , Types .VARCHAR );
368+ }
369+ };
370+ }
371+
372+ private static LongWriteFunction longTimestampWriteFunction (TimestampType timestampType )
373+ {
374+ return new LongWriteFunction ()
375+ {
376+ @ Override
377+ public String getBindExpression ()
378+ {
379+ return getTimestampBindExpression (timestampType );
380+ }
381+
382+ @ Override
383+ public void set (PreparedStatement statement , int index , long epochMicros )
384+ throws SQLException
385+ {
386+ LocalDateTime localDateTime = fromTrinoTimestamp (epochMicros );
387+ Timestamp timestampValue = Timestamp .valueOf (localDateTime );
388+ statement .setObject (index , timestampValue );
389+ }
390+
391+ @ Override
392+ public void setNull (PreparedStatement statement , int index )
393+ throws SQLException
394+ {
395+ statement .setNull (index , Types .VARCHAR );
396+ }
397+ };
398+ }
399+
400+ private static ColumnMapping timestampWithTimeZoneColumnMapping (JdbcTypeHandle typeHandle )
401+ {
402+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
403+ TimestampWithTimeZoneType timestampWithTimeZoneType = createTimestampWithTimeZoneType (timestampPrecision );
404+
405+ if (timestampWithTimeZoneType .isShort ()) {
406+ return ColumnMapping .longMapping (
407+ timestampWithTimeZoneType ,
408+ longTimestampWithTimeZoneReadFunction (),
409+ longTimestampWithTimeZoneWriteFunction (timestampWithTimeZoneType ),
410+ FULL_PUSHDOWN );
411+ }
412+ return ColumnMapping .objectMapping (
413+ timestampWithTimeZoneType ,
414+ objectTimestampWithTimeZoneReadFunction (timestampWithTimeZoneType ),
415+ objectTimestampWithTimeZoneWriteFunction (timestampWithTimeZoneType ),
416+ FULL_PUSHDOWN );
417+ }
418+
419+ private static LongReadFunction longTimestampWithTimeZoneReadFunction ()
420+ {
421+ return (resultSet , columnIndex ) -> {
422+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
423+ return packDateTimeWithZone (timestamp .getTime (), TimeZone .getDefault ().getID ());
424+ };
425+ }
426+
427+ private static ObjectReadFunction objectTimestampWithTimeZoneReadFunction (
428+ TimestampWithTimeZoneType timestampType )
429+ {
430+ verifyObjectTimestampWithTimeZonePrecision (timestampType );
431+ return ObjectReadFunction .of (
432+ LongTimestampWithTimeZone .class ,
433+ (resultSet , columnIndex ) -> {
434+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
435+
436+ long millisUtc = timestamp .getTime ();
437+ long nanosUtc = millisUtc * NANOSECONDS_PER_MILLISECOND + timestamp .getNanos () % NANOSECONDS_PER_MILLISECOND ;
438+ int picosOfMilli = (int ) ((nanosUtc - millisUtc * NANOSECONDS_PER_MILLISECOND ) * PICOSECONDS_PER_NANOSECOND );
439+
440+ return LongTimestampWithTimeZone .fromEpochMillisAndFraction (
441+ millisUtc ,
442+ picosOfMilli ,
443+ TimeZoneKey .getTimeZoneKey (TimeZone .getDefault ().getID ()));
444+ });
445+ }
446+
447+ private static void verifyObjectTimestampWithTimeZonePrecision (TimestampWithTimeZoneType timestampType )
448+ {
449+ int precision = timestampType .getPrecision ();
450+ checkArgument (precision > TimestampWithTimeZoneType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
451+ "Precision is out of range: %s" , precision );
452+ }
453+
454+ private static ObjectWriteFunction objectTimestampWithTimeZoneWriteFunction (TimestampWithTimeZoneType timestampType )
455+ {
456+ verifyObjectTimestampWithTimeZonePrecision (timestampType );
457+
458+ return new ObjectWriteFunction () {
459+ @ Override
460+ public Class <?> getJavaType ()
461+ {
462+ return LongTimestampWithTimeZone .class ;
463+ }
464+
465+ @ Override
466+ public void set (PreparedStatement statement , int index , Object value )
467+ throws SQLException
468+ {
469+ LongTimestampWithTimeZone longTimestampWithTimeZone = (LongTimestampWithTimeZone ) value ;
470+ Instant instant = Instant .ofEpochMilli (longTimestampWithTimeZone .getEpochMillis ())
471+ .plusNanos (longTimestampWithTimeZone .getPicosOfMilli () / PICOSECONDS_PER_NANOSECOND );
472+ Timestamp timestamp = Timestamp .from (instant );
473+ statement .setObject (index , timestamp );
474+ }
475+
476+ @ Override
477+ public String getBindExpression ()
478+ {
479+ return getTimestampWithTimeZoneBindExpression (timestampType );
480+ }
481+
482+ @ Override
483+ public void setNull (PreparedStatement statement , int index )
484+ throws SQLException
485+ {
486+ statement .setNull (index , Types .VARCHAR );
487+ }
488+ };
489+ }
490+
491+ private static LongWriteFunction longTimestampWithTimeZoneWriteFunction (TimestampWithTimeZoneType timestampType )
492+ {
493+ return new LongWriteFunction ()
494+ {
495+ @ Override
496+ public String getBindExpression ()
497+ {
498+ return getTimestampWithTimeZoneBindExpression (timestampType );
499+ }
500+
501+ @ Override
502+ public void set (PreparedStatement statement , int index , long dateTimeWithTimeZone )
503+ throws SQLException
504+ {
505+ long epochMillis = unpackMillisUtc (dateTimeWithTimeZone );
506+ Timestamp timestampValue = Timestamp .from (Instant .ofEpochMilli (epochMillis ));
507+ statement .setObject (index , timestampValue );
508+ }
509+
510+ @ Override
511+ public void setNull (PreparedStatement statement , int index )
512+ throws SQLException
513+ {
514+ statement .setNull (index , Types .VARCHAR );
515+ }
516+ };
517+ }
518+
519+ private static String getTimestampBindExpression (TimestampType timestampType )
520+ {
521+ return getTimestampBindExpression (timestampType .getPrecision ());
522+ }
523+
524+ private static String getTimestampWithTimeZoneBindExpression (TimestampWithTimeZoneType timestampWithTimeZoneType )
525+ {
526+ return getTimestampBindExpression (timestampWithTimeZoneType .getPrecision ());
527+ }
528+
529+ /**
530+ * Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
531+ * based on the given fractional seconds precision.
532+ * See for more details: https://docs.exasol.com/db/latest/sql_references/formatmodels.htm
533+ */
534+ private static String getTimestampBindExpression (int precision )
535+ {
536+ //negative precisions are not supported by TimestampType and TimestampWithTimeZoneType
537+ if (precision == 0 ) {
538+ return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')" ;
539+ }
540+ return format ("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')" , precision );
541+ }
542+
259543 private static ColumnMapping dateColumnMapping ()
260544 {
261545 // Exasol driver does not support LocalDate
0 commit comments