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