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 ;
46- import io .trino .spi .type .Type ;
48+ import io .trino .spi .type .* ;
4749
4850import java .sql .Connection ;
4951import java .sql .Date ;
52+ import java .sql .PreparedStatement ;
53+ import java .sql .SQLException ;
54+ import java .sql .Timestamp ;
5055import java .sql .Types ;
51- import java .time .LocalDate ;
52- import java .util .HexFormat ;
53- import java .util .List ;
54- import java .util .Map ;
55- import java .util .Optional ;
56- import java .util .OptionalLong ;
57- import java .util .Set ;
56+ import java .time .*;
57+ import java .util .*;
5858import java .util .function .BiFunction ;
5959
60+ import static com .google .common .base .Preconditions .checkArgument ;
61+ import static io .trino .plugin .jdbc .PredicatePushdownController .FULL_PUSHDOWN ;
6062import static io .trino .plugin .jdbc .StandardColumnMappings .bigintColumnMapping ;
6163import static io .trino .plugin .jdbc .StandardColumnMappings .booleanColumnMapping ;
6264import static io .trino .plugin .jdbc .StandardColumnMappings .decimalColumnMapping ;
6365import static io .trino .plugin .jdbc .StandardColumnMappings .defaultCharColumnMapping ;
6466import static io .trino .plugin .jdbc .StandardColumnMappings .defaultVarcharColumnMapping ;
6567import static io .trino .plugin .jdbc .StandardColumnMappings .doubleColumnMapping ;
68+ import static io .trino .plugin .jdbc .StandardColumnMappings .fromLongTrinoTimestamp ;
69+ import static io .trino .plugin .jdbc .StandardColumnMappings .fromTrinoTimestamp ;
6670import static io .trino .plugin .jdbc .StandardColumnMappings .integerColumnMapping ;
6771import static io .trino .plugin .jdbc .StandardColumnMappings .smallintColumnMapping ;
72+ import static io .trino .plugin .jdbc .StandardColumnMappings .toLongTrinoTimestamp ;
73+ import static io .trino .plugin .jdbc .StandardColumnMappings .toTrinoTimestamp ;
6874import static io .trino .plugin .jdbc .TypeHandlingJdbcSessionProperties .getUnsupportedTypeHandling ;
6975import static io .trino .plugin .jdbc .UnsupportedTypeHandling .CONVERT_TO_VARCHAR ;
7076import static io .trino .spi .StandardErrorCode .NOT_SUPPORTED ;
7177import static io .trino .spi .connector .ConnectorMetadata .MODIFYING_ROWS_MESSAGE ;
78+ import static io .trino .spi .type .DateTimeEncoding .packDateTimeWithZone ;
79+ import static io .trino .spi .type .DateTimeEncoding .unpackMillisUtc ;
7280import static io .trino .spi .type .DateType .DATE ;
7381import static io .trino .spi .type .DecimalType .createDecimalType ;
82+ import static io .trino .spi .type .TimeZoneKey .UTC_KEY ;
83+ import static io .trino .spi .type .TimestampType .createTimestampType ;
84+ import static io .trino .spi .type .TimestampWithTimeZoneType .createTimestampWithTimeZoneType ;
85+ import static io .trino .spi .type .Timestamps .NANOSECONDS_PER_MILLISECOND ;
86+ import static io .trino .spi .type .Timestamps .PICOSECONDS_PER_NANOSECOND ;
7487import static io .trino .spi .type .VarbinaryType .VARBINARY ;
7588import static java .lang .String .format ;
7689import static java .util .Locale .ENGLISH ;
7992public class ExasolClient
8093 extends BaseJdbcClient
8194{
95+ private static final int EXASOL_TIMESTAMP_WITH_TIMEZONE = 124 ;
96+
8297 private static final Set <String > INTERNAL_SCHEMAS = ImmutableSet .<String >builder ()
8398 .add ("EXA_STATISTICS" )
8499 .add ("SYS" )
85100 .build ();
86101
102+ private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9 ;
103+
87104 @ Inject
88105 public ExasolClient (
89106 BaseJdbcConfig config ,
@@ -241,6 +258,10 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
241258 return Optional .of (defaultVarcharColumnMapping (typeHandle .requiredColumnSize (), true ));
242259 case Types .DATE :
243260 return Optional .of (dateColumnMapping ());
261+ case Types .TIMESTAMP :
262+ return Optional .of (timestampColumnMapping (typeHandle ));
263+ case EXASOL_TIMESTAMP_WITH_TIMEZONE :
264+ return Optional .of (timestampWithTimeZoneColumnMapping (typeHandle ));
244265 }
245266
246267 if (getUnsupportedTypeHandling (session ) == CONVERT_TO_VARCHAR ) {
@@ -256,6 +277,257 @@ private boolean isHashType(JdbcTypeHandle typeHandle)
256277 && typeHandle .jdbcTypeName ().get ().equalsIgnoreCase ("HASHTYPE" );
257278 }
258279
280+ private static ColumnMapping timestampColumnMapping (JdbcTypeHandle typeHandle )
281+ {
282+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
283+ TimestampType timestampType = createTimestampType (timestampPrecision );
284+ if (timestampType .isShort ()) {
285+ return ColumnMapping .longMapping (
286+ timestampType ,
287+ longTimestampReadFunction (timestampType ),
288+ longTimestampWriteFunction (timestampType ),
289+ FULL_PUSHDOWN );
290+ }
291+ return ColumnMapping .objectMapping (
292+ timestampType ,
293+ objectTimestampReadFunction (timestampType ),
294+ objectTimestampWriteFunction (timestampType ),
295+ FULL_PUSHDOWN );
296+ }
297+
298+ private static LongReadFunction longTimestampReadFunction (TimestampType timestampType )
299+ {
300+ return (resultSet , columnIndex ) -> {
301+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
302+ return toTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
303+ };
304+ }
305+
306+ private static ObjectReadFunction objectTimestampReadFunction (TimestampType timestampType )
307+ {
308+ verifyObjectTimestampPrecision (timestampType );
309+ return ObjectReadFunction .of (
310+ LongTimestamp .class ,
311+ (resultSet , columnIndex ) -> {
312+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
313+ return toLongTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
314+ });
315+ }
316+
317+ private static void verifyObjectTimestampPrecision (TimestampType timestampType )
318+ {
319+ int precision = timestampType .getPrecision ();
320+ checkArgument (precision > TimestampType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
321+ "Precision is out of range: %s" , precision );
322+ }
323+
324+ private static ObjectWriteFunction objectTimestampWriteFunction (TimestampType timestampType )
325+ {
326+ int precision = timestampType .getPrecision ();
327+ verifyObjectTimestampPrecision (timestampType );
328+
329+ return new ObjectWriteFunction () {
330+ @ Override
331+ public Class <?> getJavaType ()
332+ {
333+ return LongTimestamp .class ;
334+ }
335+
336+ @ Override
337+ public void set (PreparedStatement statement , int index , Object value )
338+ throws SQLException
339+ {
340+ LocalDateTime localDateTime = fromLongTrinoTimestamp ((LongTimestamp ) value , precision );
341+ Timestamp timestamp = Timestamp .valueOf (localDateTime );
342+ statement .setObject (index , timestamp );
343+ }
344+
345+ @ Override
346+ public String getBindExpression ()
347+ {
348+ return getTimestampBindExpression (timestampType );
349+ }
350+
351+ @ Override
352+ public void setNull (PreparedStatement statement , int index )
353+ throws SQLException
354+ {
355+ statement .setNull (index , Types .VARCHAR );
356+ }
357+ };
358+ }
359+
360+ private static LongWriteFunction longTimestampWriteFunction (TimestampType timestampType )
361+ {
362+ return new LongWriteFunction ()
363+ {
364+ @ Override
365+ public String getBindExpression ()
366+ {
367+ return getTimestampBindExpression (timestampType );
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 .setObject (index , timestampValue );
377+ }
378+
379+ @ Override
380+ public void setNull (PreparedStatement statement , int index )
381+ throws SQLException
382+ {
383+ statement .setNull (index , Types .VARCHAR );
384+ }
385+ };
386+ }
387+
388+ private static ColumnMapping timestampWithTimeZoneColumnMapping (JdbcTypeHandle typeHandle )
389+ {
390+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
391+ TimestampWithTimeZoneType timestampWithTimeZoneType = createTimestampWithTimeZoneType (timestampPrecision );
392+
393+ if (timestampWithTimeZoneType .isShort ()) {
394+ return ColumnMapping .longMapping (
395+ timestampWithTimeZoneType ,
396+ longTimestampWithTimeZoneReadFunction (),
397+ longTimestampWithTimeZoneWriteFunction (timestampWithTimeZoneType ),
398+ FULL_PUSHDOWN );
399+ }
400+ return ColumnMapping .objectMapping (
401+ timestampWithTimeZoneType ,
402+ objectTimestampWithTimeZoneReadFunction (timestampWithTimeZoneType ),
403+ objectTimestampWithTimeZoneWriteFunction (timestampWithTimeZoneType ),
404+ FULL_PUSHDOWN );
405+ }
406+
407+ private static LongReadFunction longTimestampWithTimeZoneReadFunction ()
408+ {
409+ return (resultSet , columnIndex ) -> {
410+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
411+ return packDateTimeWithZone (timestamp .getTime (), TimeZone .getDefault ().getID ());
412+ };
413+ }
414+
415+ private static ObjectReadFunction objectTimestampWithTimeZoneReadFunction (
416+ TimestampWithTimeZoneType timestampType )
417+ {
418+ verifyObjectTimestampWithTimeZonePrecision (timestampType );
419+ return ObjectReadFunction .of (
420+ LongTimestampWithTimeZone .class ,
421+ (resultSet , columnIndex ) -> {
422+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
423+
424+ long millisUtc = timestamp .getTime ();
425+ long nanosUtc = millisUtc * NANOSECONDS_PER_MILLISECOND + timestamp .getNanos () % NANOSECONDS_PER_MILLISECOND ;
426+ int picosOfMilli = (int ) ((nanosUtc - millisUtc * NANOSECONDS_PER_MILLISECOND ) * PICOSECONDS_PER_NANOSECOND );
427+
428+ return LongTimestampWithTimeZone .fromEpochMillisAndFraction (
429+ millisUtc ,
430+ picosOfMilli ,
431+ TimeZoneKey .getTimeZoneKey (TimeZone .getDefault ().getID ()));
432+ });
433+ }
434+
435+ private static void verifyObjectTimestampWithTimeZonePrecision (TimestampWithTimeZoneType timestampType )
436+ {
437+ int precision = timestampType .getPrecision ();
438+ checkArgument (precision > TimestampWithTimeZoneType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
439+ "Precision is out of range: %s" , precision );
440+ }
441+
442+ private static ObjectWriteFunction objectTimestampWithTimeZoneWriteFunction (TimestampWithTimeZoneType timestampType )
443+ {
444+ verifyObjectTimestampWithTimeZonePrecision (timestampType );
445+
446+ return new ObjectWriteFunction () {
447+ @ Override
448+ public Class <?> getJavaType ()
449+ {
450+ return LongTimestampWithTimeZone .class ;
451+ }
452+
453+ @ Override
454+ public void set (PreparedStatement statement , int index , Object value )
455+ throws SQLException
456+ {
457+ LongTimestampWithTimeZone longTimestampWithTimeZone = (LongTimestampWithTimeZone ) value ;
458+ Instant instant = Instant .ofEpochMilli (longTimestampWithTimeZone .getEpochMillis ())
459+ .plusNanos (longTimestampWithTimeZone .getPicosOfMilli () / PICOSECONDS_PER_NANOSECOND );
460+ Timestamp timestamp = Timestamp .from (instant );
461+ statement .setObject (index , timestamp );
462+ }
463+
464+ @ Override
465+ public String getBindExpression ()
466+ {
467+ return getTimestampWithTimeZoneBindExpression (timestampType );
468+ }
469+
470+ @ Override
471+ public void setNull (PreparedStatement statement , int index )
472+ throws SQLException
473+ {
474+ statement .setNull (index , Types .VARCHAR );
475+ }
476+ };
477+ }
478+
479+ private static LongWriteFunction longTimestampWithTimeZoneWriteFunction (TimestampWithTimeZoneType timestampType )
480+ {
481+ return new LongWriteFunction ()
482+ {
483+ @ Override
484+ public String getBindExpression ()
485+ {
486+ return getTimestampWithTimeZoneBindExpression (timestampType );
487+ }
488+
489+ @ Override
490+ public void set (PreparedStatement statement , int index , long dateTimeWithTimeZone )
491+ throws SQLException
492+ {
493+ long epochMillis = unpackMillisUtc (dateTimeWithTimeZone );
494+ Timestamp timestampValue = Timestamp .from (Instant .ofEpochMilli (epochMillis ));
495+ statement .setObject (index , timestampValue );
496+ }
497+
498+ @ Override
499+ public void setNull (PreparedStatement statement , int index )
500+ throws SQLException
501+ {
502+ statement .setNull (index , Types .VARCHAR );
503+ }
504+ };
505+ }
506+
507+ private static String getTimestampBindExpression (TimestampType timestampType )
508+ {
509+ return getTimestampBindExpression (timestampType .getPrecision ());
510+ }
511+
512+ private static String getTimestampWithTimeZoneBindExpression (TimestampWithTimeZoneType timestampWithTimeZoneType )
513+ {
514+ return getTimestampBindExpression (timestampWithTimeZoneType .getPrecision ());
515+ }
516+
517+ /**
518+ * Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
519+ * based on the given fractional seconds precision.
520+ * See for more details: https://docs.exasol.com/db/latest/sql_references/formatmodels.htm
521+ */
522+ private static String getTimestampBindExpression (int precision )
523+ {
524+ //negative precisions are not supported by TimestampType and TimestampWithTimeZoneType
525+ if (precision == 0 ) {
526+ return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')" ;
527+ }
528+ return format ("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')" , precision );
529+ }
530+
259531 private static ColumnMapping dateColumnMapping ()
260532 {
261533 // Exasol driver does not support LocalDate
0 commit comments