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 .TimestampType ;
4650import io .trino .spi .type .Type ;
4751
4852import java .sql .Connection ;
4953import java .sql .Date ;
54+ import java .sql .PreparedStatement ;
55+ import java .sql .SQLException ;
56+ import java .sql .Timestamp ;
5057import java .sql .Types ;
5158import java .time .LocalDate ;
59+ import java .time .LocalDateTime ;
5260import java .util .HexFormat ;
5361import java .util .List ;
5462import java .util .Map ;
5866import java .util .function .BiFunction ;
5967import java .util .function .Consumer ;
6068
69+ import static com .google .common .base .Preconditions .checkArgument ;
70+ import static io .trino .plugin .jdbc .PredicatePushdownController .FULL_PUSHDOWN ;
6171import static io .trino .plugin .jdbc .StandardColumnMappings .bigintColumnMapping ;
6272import static io .trino .plugin .jdbc .StandardColumnMappings .booleanColumnMapping ;
6373import static io .trino .plugin .jdbc .StandardColumnMappings .decimalColumnMapping ;
6474import static io .trino .plugin .jdbc .StandardColumnMappings .defaultCharColumnMapping ;
6575import static io .trino .plugin .jdbc .StandardColumnMappings .defaultVarcharColumnMapping ;
6676import 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 ;
6779import static io .trino .plugin .jdbc .StandardColumnMappings .integerColumnMapping ;
6880import 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 ;
6983import static io .trino .plugin .jdbc .TypeHandlingJdbcSessionProperties .getUnsupportedTypeHandling ;
7084import static io .trino .plugin .jdbc .UnsupportedTypeHandling .CONVERT_TO_VARCHAR ;
7185import static io .trino .spi .StandardErrorCode .NOT_SUPPORTED ;
7286import static io .trino .spi .connector .ConnectorMetadata .MODIFYING_ROWS_MESSAGE ;
7387import static io .trino .spi .type .DateType .DATE ;
7488import static io .trino .spi .type .DecimalType .createDecimalType ;
89+ import static io .trino .spi .type .TimestampType .MAX_SHORT_PRECISION ;
90+ import static io .trino .spi .type .TimestampType .createTimestampType ;
7591import static io .trino .spi .type .VarbinaryType .VARBINARY ;
7692import static java .lang .String .format ;
7793import static java .util .Locale .ENGLISH ;
@@ -85,6 +101,8 @@ public class ExasolClient
85101 .add ("SYS" )
86102 .build ();
87103
104+ private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9 ;
105+
88106 @ Inject
89107 public ExasolClient (
90108 BaseJdbcConfig config ,
@@ -240,8 +258,12 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
240258 // String data is sorted by its binary representation.
241259 // https://docs.exasol.com/db/latest/sql/select.htm#UsageNotes
242260 return Optional .of (defaultVarcharColumnMapping (typeHandle .requiredColumnSize (), true ));
261+ // DATE and TIMESTAMP types are described here in more details:
262+ // https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm
243263 case Types .DATE :
244264 return Optional .of (dateColumnMapping ());
265+ case Types .TIMESTAMP :
266+ return Optional .of (timestampColumnMapping (typeHandle ));
245267 }
246268
247269 if (getUnsupportedTypeHandling (session ) == CONVERT_TO_VARCHAR ) {
@@ -308,6 +330,137 @@ private static SliceWriteFunction hashTypeWriteFunction()
308330 });
309331 }
310332
333+ private static ColumnMapping timestampColumnMapping (JdbcTypeHandle typeHandle )
334+ {
335+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
336+ TimestampType timestampType = createTimestampType (timestampPrecision );
337+ if (timestampType .isShort ()) {
338+ return ColumnMapping .longMapping (
339+ timestampType ,
340+ longTimestampReadFunction (timestampType ),
341+ longTimestampWriteFunction (timestampType ),
342+ FULL_PUSHDOWN );
343+ }
344+ return ColumnMapping .objectMapping (
345+ timestampType ,
346+ objectTimestampReadFunction (timestampType ),
347+ objectTimestampWriteFunction (timestampType ),
348+ FULL_PUSHDOWN );
349+ }
350+
351+ private static LongReadFunction longTimestampReadFunction (TimestampType timestampType )
352+ {
353+ verifyLongTimestampPrecision (timestampType );
354+ return (resultSet , columnIndex ) -> {
355+ Timestamp timestamp = resultSet .getTimestamp (columnIndex );
356+ return toTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
357+ };
358+ }
359+
360+ private static LongWriteFunction longTimestampWriteFunction (TimestampType timestampType )
361+ {
362+ verifyLongTimestampPrecision (timestampType );
363+ return new LongWriteFunction ()
364+ {
365+ @ Override
366+ public String getBindExpression ()
367+ {
368+ return getTimestampBindExpression (timestampType .getPrecision ());
369+ }
370+
371+ @ Override
372+ public void set (PreparedStatement statement , int index , long epochMicros )
373+ throws SQLException
374+ {
375+ LocalDateTime localDateTime = fromTrinoTimestamp (epochMicros );
376+ Timestamp timestampValue = Timestamp .valueOf (localDateTime );
377+ statement .setTimestamp (index , timestampValue );
378+ }
379+
380+ @ Override
381+ public void setNull (PreparedStatement statement , int index )
382+ throws SQLException
383+ {
384+ statement .setNull (index , Types .TIMESTAMP );
385+ }
386+ };
387+ }
388+
389+ private static ObjectReadFunction objectTimestampReadFunction (TimestampType timestampType )
390+ {
391+ verifyObjectTimestampPrecision (timestampType );
392+ return ObjectReadFunction .of (
393+ LongTimestamp .class ,
394+ (resultSet , columnIndex ) -> {
395+ Timestamp timestamp = resultSet .getTimestamp (columnIndex );
396+ return toLongTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
397+ });
398+ }
399+
400+ private static ObjectWriteFunction objectTimestampWriteFunction (TimestampType timestampType )
401+ {
402+ int precision = timestampType .getPrecision ();
403+ verifyObjectTimestampPrecision (timestampType );
404+
405+ return new ObjectWriteFunction () {
406+ @ Override
407+ public Class <?> getJavaType ()
408+ {
409+ return LongTimestamp .class ;
410+ }
411+
412+ @ Override
413+ public void set (PreparedStatement statement , int index , Object value )
414+ throws SQLException
415+ {
416+ LocalDateTime localDateTime = fromLongTrinoTimestamp ((LongTimestamp ) value , precision );
417+ Timestamp timestamp = Timestamp .valueOf (localDateTime );
418+ statement .setTimestamp (index , timestamp );
419+ }
420+
421+ @ Override
422+ public String getBindExpression ()
423+ {
424+ return getTimestampBindExpression (timestampType .getPrecision ());
425+ }
426+
427+ @ Override
428+ public void setNull (PreparedStatement statement , int index )
429+ throws SQLException
430+ {
431+ statement .setNull (index , Types .TIMESTAMP );
432+ }
433+ };
434+ }
435+
436+ private static void verifyObjectTimestampPrecision (TimestampType timestampType )
437+ {
438+ int precision = timestampType .getPrecision ();
439+ checkArgument (precision > MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
440+ "Precision is out of range: %s" , precision );
441+ }
442+
443+ private static void verifyLongTimestampPrecision (TimestampType timestampType )
444+ {
445+ int precision = timestampType .getPrecision ();
446+ checkArgument (precision >= 0 && precision <= MAX_SHORT_PRECISION ,
447+ "Precision is out of range: %s" , precision );
448+ }
449+
450+ /**
451+ * Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
452+ * based on the given fractional seconds precision.
453+ * See for more details: <a href="https://docs.exasol.com/db/latest/sql_references/formatmodels.htm">Date/time format models</a>
454+ */
455+ private static String getTimestampBindExpression (int precision )
456+ {
457+ checkArgument (precision >= 0 , "Precision is negative: %s" , precision );
458+ if (precision == 0 ) {
459+ return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')" ;
460+ }
461+ return format ("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')" , precision );
462+ }
463+
311464 @ Override
312465 public WriteMapping toWriteMapping (ConnectorSession session , Type type )
313466 {
0 commit comments