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