2828import io .trino .plugin .jdbc .JdbcTypeHandle ;
2929import io .trino .plugin .jdbc .LongReadFunction ;
3030import io .trino .plugin .jdbc .LongWriteFunction ;
31+ import io .trino .plugin .jdbc .ObjectReadFunction ;
32+ import io .trino .plugin .jdbc .ObjectWriteFunction ;
3133import io .trino .plugin .jdbc .QueryBuilder ;
3234import io .trino .plugin .jdbc .WriteFunction ;
3335import io .trino .plugin .jdbc .WriteMapping ;
3941import io .trino .spi .connector .ColumnPosition ;
4042import io .trino .spi .connector .ConnectorSession ;
4143import io .trino .spi .connector .ConnectorTableMetadata ;
44+ import io .trino .spi .type .LongTimestamp ;
45+ import io .trino .spi .type .LongTimestampWithTimeZone ;
46+ import io .trino .spi .type .TimestampType ;
47+ import io .trino .spi .type .TimestampWithTimeZoneType ;
4248import io .trino .spi .type .Type ;
4349
4450import java .sql .Connection ;
4551import java .sql .Date ;
52+ import java .sql .PreparedStatement ;
53+ import java .sql .SQLException ;
54+ import java .sql .Timestamp ;
4655import java .sql .Types ;
56+ import java .time .Instant ;
4757import java .time .LocalDate ;
58+ import java .time .LocalDateTime ;
4859import java .util .List ;
4960import java .util .Map ;
5061import java .util .Optional ;
5162import java .util .OptionalLong ;
5263import java .util .Set ;
5364
65+ import static com .google .common .base .Preconditions .checkArgument ;
66+ import static io .trino .plugin .jdbc .PredicatePushdownController .FULL_PUSHDOWN ;
5467import static io .trino .plugin .jdbc .StandardColumnMappings .bigintColumnMapping ;
5568import static io .trino .plugin .jdbc .StandardColumnMappings .booleanColumnMapping ;
5669import static io .trino .plugin .jdbc .StandardColumnMappings .decimalColumnMapping ;
5770import static io .trino .plugin .jdbc .StandardColumnMappings .defaultCharColumnMapping ;
5871import static io .trino .plugin .jdbc .StandardColumnMappings .defaultVarcharColumnMapping ;
5972import static io .trino .plugin .jdbc .StandardColumnMappings .doubleColumnMapping ;
73+ import static io .trino .plugin .jdbc .StandardColumnMappings .fromLongTrinoTimestamp ;
74+ import static io .trino .plugin .jdbc .StandardColumnMappings .fromTrinoTimestamp ;
6075import static io .trino .plugin .jdbc .StandardColumnMappings .integerColumnMapping ;
6176import static io .trino .plugin .jdbc .StandardColumnMappings .smallintColumnMapping ;
77+ import static io .trino .plugin .jdbc .StandardColumnMappings .toLongTrinoTimestamp ;
78+ import static io .trino .plugin .jdbc .StandardColumnMappings .toTrinoTimestamp ;
6279import static io .trino .plugin .jdbc .TypeHandlingJdbcSessionProperties .getUnsupportedTypeHandling ;
6380import static io .trino .plugin .jdbc .UnsupportedTypeHandling .CONVERT_TO_VARCHAR ;
6481import static io .trino .spi .StandardErrorCode .NOT_SUPPORTED ;
6582import static io .trino .spi .connector .ConnectorMetadata .MODIFYING_ROWS_MESSAGE ;
83+ import static io .trino .spi .type .DateTimeEncoding .packDateTimeWithZone ;
6684import static io .trino .spi .type .DateType .DATE ;
6785import static io .trino .spi .type .DecimalType .createDecimalType ;
86+ import static io .trino .spi .type .TimeZoneKey .UTC_KEY ;
87+ import static io .trino .spi .type .TimestampType .createTimestampType ;
88+ import static io .trino .spi .type .TimestampWithTimeZoneType .createTimestampWithTimeZoneType ;
89+ import static io .trino .spi .type .Timestamps .NANOSECONDS_PER_MILLISECOND ;
90+ import static io .trino .spi .type .Timestamps .PICOSECONDS_PER_NANOSECOND ;
91+ import static java .lang .String .format ;
6892import static java .util .Locale .ENGLISH ;
6993
7094public class ExasolClient
7195 extends BaseJdbcClient
7296{
97+ private static final int EXASOL_TIMESTAMP_WITH_TIMEZONE = 124 ;
98+
7399 private static final Set <String > INTERNAL_SCHEMAS = ImmutableSet .<String >builder ()
74100 .add ("EXA_STATISTICS" )
75101 .add ("SYS" )
76102 .build ();
77103
104+ private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9 ;
105+
78106 @ Inject
79107 public ExasolClient (
80108 BaseJdbcConfig config ,
@@ -229,6 +257,10 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
229257 return Optional .of (defaultVarcharColumnMapping (typeHandle .requiredColumnSize (), true ));
230258 case Types .DATE :
231259 return Optional .of (dateColumnMapping ());
260+ case Types .TIMESTAMP :
261+ return Optional .of (timestampColumnMapping (typeHandle ));
262+ case EXASOL_TIMESTAMP_WITH_TIMEZONE :
263+ return Optional .of (timestampWithTimeZoneColumnMapping (typeHandle ));
232264 }
233265
234266 if (getUnsupportedTypeHandling (session ) == CONVERT_TO_VARCHAR ) {
@@ -237,6 +269,247 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
237269 return Optional .empty ();
238270 }
239271
272+ private static ColumnMapping timestampColumnMapping (JdbcTypeHandle typeHandle )
273+ {
274+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
275+ TimestampType timestampType = createTimestampType (timestampPrecision );
276+ if (timestampType .isShort ()) {
277+ return ColumnMapping .longMapping (
278+ timestampType ,
279+ longTimestampReadFunction (timestampType ),
280+ longTimestampWriteFunction (timestampType ),
281+ FULL_PUSHDOWN );
282+ }
283+ return ColumnMapping .objectMapping (
284+ timestampType ,
285+ objectTimestampReadFunction (timestampType ),
286+ objectTimestampWriteFunction (timestampType ),
287+ FULL_PUSHDOWN );
288+ }
289+
290+ private static LongReadFunction longTimestampReadFunction (TimestampType timestampType )
291+ {
292+ return (resultSet , columnIndex ) -> {
293+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
294+ return toTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
295+ };
296+ }
297+
298+ private static ObjectReadFunction objectTimestampReadFunction (TimestampType timestampType )
299+ {
300+ verifyObjectTimestampPrecision (timestampType );
301+ return ObjectReadFunction .of (
302+ LongTimestamp .class ,
303+ (resultSet , columnIndex ) -> {
304+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
305+ return toLongTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
306+ });
307+ }
308+
309+ private static void verifyObjectTimestampPrecision (TimestampType timestampType )
310+ {
311+ int precision = timestampType .getPrecision ();
312+ checkArgument (precision > TimestampType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
313+ "Precision is out of range: %s" , precision );
314+ }
315+
316+ private static ObjectWriteFunction objectTimestampWriteFunction (TimestampType timestampType )
317+ {
318+ int precision = timestampType .getPrecision ();
319+ verifyObjectTimestampPrecision (timestampType );
320+
321+ return new ObjectWriteFunction () {
322+ @ Override
323+ public Class <?> getJavaType ()
324+ {
325+ return LongTimestamp .class ;
326+ }
327+
328+ @ Override
329+ public void set (PreparedStatement statement , int index , Object value )
330+ throws SQLException
331+ {
332+ LocalDateTime localDateTime = fromLongTrinoTimestamp ((LongTimestamp ) value , precision );
333+ Timestamp timestamp = Timestamp .valueOf (localDateTime );
334+ statement .setObject (index , timestamp );
335+ }
336+
337+ @ Override
338+ public String getBindExpression ()
339+ {
340+ return getTimestampBindExpression (precision );
341+ }
342+
343+ @ Override
344+ public void setNull (PreparedStatement statement , int index )
345+ throws SQLException
346+ {
347+ statement .setNull (index , Types .VARCHAR );
348+ }
349+ };
350+ }
351+
352+ private static LongWriteFunction longTimestampWriteFunction (TimestampType timestampType )
353+ {
354+ return new LongWriteFunction ()
355+ {
356+ @ Override
357+ public String getBindExpression ()
358+ {
359+ return getTimestampBindExpression (timestampType .getPrecision ());
360+ }
361+
362+ @ Override
363+ public void set (PreparedStatement statement , int index , long epochMicros )
364+ throws SQLException
365+ {
366+ LocalDateTime localDateTime = fromTrinoTimestamp (epochMicros );
367+ Timestamp timestampValue = Timestamp .valueOf (localDateTime );
368+ statement .setObject (index , timestampValue );
369+ }
370+
371+ @ Override
372+ public void setNull (PreparedStatement statement , int index )
373+ throws SQLException
374+ {
375+ statement .setNull (index , Types .VARCHAR );
376+ }
377+ };
378+ }
379+
380+ private static ColumnMapping timestampWithTimeZoneColumnMapping (JdbcTypeHandle typeHandle )
381+ {
382+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
383+ TimestampWithTimeZoneType timestampWithTimeZoneType = createTimestampWithTimeZoneType (timestampPrecision );
384+
385+ if (timestampWithTimeZoneType .isShort ()) {
386+ return ColumnMapping .longMapping (
387+ timestampWithTimeZoneType ,
388+ longTimestampWithTimeZoneReadFunction (),
389+ longTimestampWithTimeZoneWriteFunction (timestampWithTimeZoneType ),
390+ FULL_PUSHDOWN );
391+ }
392+ return ColumnMapping .objectMapping (
393+ timestampWithTimeZoneType ,
394+ objectTimestampWithTimeZoneReadFunction (timestampWithTimeZoneType ),
395+ objectTimestampWithTimeZoneWriteFunction (timestampWithTimeZoneType ),
396+ FULL_PUSHDOWN );
397+ }
398+
399+ private static LongReadFunction longTimestampWithTimeZoneReadFunction ()
400+ {
401+ return (resultSet , columnIndex ) -> {
402+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
403+ return packDateTimeWithZone (timestamp .getTime (), UTC_KEY );
404+ };
405+ }
406+
407+ private static ObjectReadFunction objectTimestampWithTimeZoneReadFunction (
408+ TimestampWithTimeZoneType timestampType )
409+ {
410+ verifyObjectTimestampWithTimeZonePrecision (timestampType );
411+ return ObjectReadFunction .of (
412+ LongTimestampWithTimeZone .class ,
413+ (resultSet , columnIndex ) -> {
414+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
415+
416+ long millisUtc = timestamp .getTime ();
417+ long nanosUtc = millisUtc * NANOSECONDS_PER_MILLISECOND + timestamp .getNanos () % NANOSECONDS_PER_MILLISECOND ;
418+ int picosOfMilli = (int ) ((nanosUtc - millisUtc * NANOSECONDS_PER_MILLISECOND ) * PICOSECONDS_PER_NANOSECOND );
419+
420+ return LongTimestampWithTimeZone .fromEpochMillisAndFraction (
421+ millisUtc ,
422+ picosOfMilli ,
423+ UTC_KEY );
424+ });
425+ }
426+
427+ private static void verifyObjectTimestampWithTimeZonePrecision (TimestampWithTimeZoneType timestampType )
428+ {
429+ int precision = timestampType .getPrecision ();
430+ checkArgument (precision > TimestampWithTimeZoneType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
431+ "Precision is out of range: %s" , precision );
432+ }
433+
434+ private static ObjectWriteFunction objectTimestampWithTimeZoneWriteFunction (TimestampWithTimeZoneType timestampType )
435+ {
436+ int precision = timestampType .getPrecision ();
437+ verifyObjectTimestampWithTimeZonePrecision (timestampType );
438+
439+ return new ObjectWriteFunction () {
440+ @ Override
441+ public Class <?> getJavaType ()
442+ {
443+ return LongTimestampWithTimeZone .class ;
444+ }
445+
446+ @ Override
447+ public void set (PreparedStatement statement , int index , Object value )
448+ throws SQLException
449+ {
450+ if (value == null ) {
451+ statement .setNull (index , Types .VARCHAR );
452+ return ;
453+ }
454+
455+ LongTimestampWithTimeZone timestampValue = (LongTimestampWithTimeZone ) value ;
456+ Instant instant = Instant .ofEpochMilli (timestampValue .getEpochMillis ())
457+ .plusNanos (timestampValue .getPicosOfMilli () / PICOSECONDS_PER_NANOSECOND );
458+ Timestamp timestamp = Timestamp .from (instant );
459+ statement .setObject (index , timestamp );
460+ }
461+
462+ @ Override
463+ public String getBindExpression ()
464+ {
465+ return getTimestampBindExpression (precision );
466+ }
467+
468+ @ Override
469+ public void setNull (PreparedStatement statement , int index )
470+ throws SQLException
471+ {
472+ statement .setNull (index , Types .VARCHAR );
473+ }
474+ };
475+ }
476+
477+ private static LongWriteFunction longTimestampWithTimeZoneWriteFunction (TimestampWithTimeZoneType timestampType )
478+ {
479+ return new LongWriteFunction ()
480+ {
481+ @ Override
482+ public String getBindExpression ()
483+ {
484+ return getTimestampBindExpression (timestampType .getPrecision ());
485+ }
486+
487+ @ Override
488+ public void set (PreparedStatement statement , int index , long epochMicros )
489+ throws SQLException
490+ {
491+ LocalDateTime localDateTime = fromTrinoTimestamp (epochMicros );
492+ Timestamp timestampValue = Timestamp .valueOf (localDateTime );
493+ statement .setObject (index , timestampValue );
494+ }
495+
496+ @ Override
497+ public void setNull (PreparedStatement statement , int index )
498+ throws SQLException
499+ {
500+ statement .setNull (index , Types .VARCHAR );
501+ }
502+ };
503+ }
504+
505+ private static String getTimestampBindExpression (int precision )
506+ {
507+ if (precision <= 0 ) {
508+ return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')" ;
509+ }
510+ return format ("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')" , precision );
511+ }
512+
240513 private static ColumnMapping dateColumnMapping ()
241514 {
242515 // Exasol driver does not support LocalDate
0 commit comments