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 .createTimestampType ;
7489import static io .trino .spi .type .VarbinaryType .VARBINARY ;
7590import static java .lang .String .format ;
7691import static java .util .Locale .ENGLISH ;
7994public class ExasolClient
8095 extends BaseJdbcClient
8196{
97+ private static final int EXASOL_TIMESTAMP_WITH_TIMEZONE = 124 ;
98+
8299 private static final Set <String > INTERNAL_SCHEMAS = ImmutableSet .<String >builder ()
83100 .add ("EXA_STATISTICS" )
84101 .add ("SYS" )
85102 .build ();
86103
104+ private static final int MAX_EXASOL_TIMESTAMP_PRECISION = 9 ;
105+
87106 @ Inject
88107 public ExasolClient (
89108 BaseJdbcConfig config ,
@@ -239,8 +258,13 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect
239258 // String data is sorted by its binary representation.
240259 // https://docs.exasol.com/db/latest/sql/select.htm#UsageNotes
241260 return Optional .of (defaultVarcharColumnMapping (typeHandle .requiredColumnSize (), true ));
261+ // DATE, TIMESTAMP and TIMESTAMP WITH LOCAL TIME ZONE Types are described here in more details:
262+ // https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm
242263 case Types .DATE :
243264 return Optional .of (dateColumnMapping ());
265+ case Types .TIMESTAMP :
266+ case EXASOL_TIMESTAMP_WITH_TIMEZONE :
267+ return Optional .of (timestampColumnMapping (typeHandle ));
244268 }
245269
246270 if (getUnsupportedTypeHandling (session ) == CONVERT_TO_VARCHAR ) {
@@ -256,6 +280,128 @@ private boolean isHashType(JdbcTypeHandle typeHandle)
256280 && typeHandle .jdbcTypeName ().get ().equalsIgnoreCase ("HASHTYPE" );
257281 }
258282
283+ private static ColumnMapping timestampColumnMapping (JdbcTypeHandle typeHandle )
284+ {
285+ int timestampPrecision = typeHandle .requiredDecimalDigits ();
286+ TimestampType timestampType = createTimestampType (timestampPrecision );
287+ if (timestampType .isShort ()) {
288+ return ColumnMapping .longMapping (
289+ timestampType ,
290+ longTimestampReadFunction (timestampType ),
291+ longTimestampWriteFunction (timestampType ),
292+ FULL_PUSHDOWN );
293+ }
294+ return ColumnMapping .objectMapping (
295+ timestampType ,
296+ objectTimestampReadFunction (timestampType ),
297+ objectTimestampWriteFunction (timestampType ),
298+ FULL_PUSHDOWN );
299+ }
300+
301+ private static LongReadFunction longTimestampReadFunction (TimestampType timestampType )
302+ {
303+ return (resultSet , columnIndex ) -> {
304+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
305+ return toTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
306+ };
307+ }
308+
309+ private static ObjectReadFunction objectTimestampReadFunction (TimestampType timestampType )
310+ {
311+ verifyObjectTimestampPrecision (timestampType );
312+ return ObjectReadFunction .of (
313+ LongTimestamp .class ,
314+ (resultSet , columnIndex ) -> {
315+ Timestamp timestamp = resultSet .getObject (columnIndex , Timestamp .class );
316+ return toLongTrinoTimestamp (timestampType , timestamp .toLocalDateTime ());
317+ });
318+ }
319+
320+ private static void verifyObjectTimestampPrecision (TimestampType timestampType )
321+ {
322+ int precision = timestampType .getPrecision ();
323+ checkArgument (precision > TimestampType .MAX_SHORT_PRECISION && precision <= MAX_EXASOL_TIMESTAMP_PRECISION ,
324+ "Precision is out of range: %s" , precision );
325+ }
326+
327+ private static ObjectWriteFunction objectTimestampWriteFunction (TimestampType timestampType )
328+ {
329+ int precision = timestampType .getPrecision ();
330+ verifyObjectTimestampPrecision (timestampType );
331+
332+ return new ObjectWriteFunction () {
333+ @ Override
334+ public Class <?> getJavaType ()
335+ {
336+ return LongTimestamp .class ;
337+ }
338+
339+ @ Override
340+ public void set (PreparedStatement statement , int index , Object value )
341+ throws SQLException
342+ {
343+ LocalDateTime localDateTime = fromLongTrinoTimestamp ((LongTimestamp ) value , precision );
344+ Timestamp timestamp = Timestamp .valueOf (localDateTime );
345+ statement .setObject (index , timestamp );
346+ }
347+
348+ @ Override
349+ public String getBindExpression ()
350+ {
351+ return getTimestampBindExpression (timestampType .getPrecision ());
352+ }
353+
354+ @ Override
355+ public void setNull (PreparedStatement statement , int index )
356+ throws SQLException
357+ {
358+ statement .setNull (index , Types .VARCHAR );
359+ }
360+ };
361+ }
362+
363+ private static LongWriteFunction longTimestampWriteFunction (TimestampType timestampType )
364+ {
365+ return new LongWriteFunction ()
366+ {
367+ @ Override
368+ public String getBindExpression ()
369+ {
370+ return getTimestampBindExpression (timestampType .getPrecision ());
371+ }
372+
373+ @ Override
374+ public void set (PreparedStatement statement , int index , long epochMicros )
375+ throws SQLException
376+ {
377+ LocalDateTime localDateTime = fromTrinoTimestamp (epochMicros );
378+ Timestamp timestampValue = Timestamp .valueOf (localDateTime );
379+ statement .setObject (index , timestampValue );
380+ }
381+
382+ @ Override
383+ public void setNull (PreparedStatement statement , int index )
384+ throws SQLException
385+ {
386+ statement .setNull (index , Types .VARCHAR );
387+ }
388+ };
389+ }
390+
391+ /**
392+ * Returns a {@code TO_TIMESTAMP} bind expression using the appropriate format model
393+ * based on the given fractional seconds precision.
394+ * See for more details: https://docs.exasol.com/db/latest/sql_references/formatmodels.htm
395+ */
396+ private static String getTimestampBindExpression (int precision )
397+ {
398+ // Negative precisions are prevented by TimestampType
399+ if (precision == 0 ) {
400+ return "TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')" ;
401+ }
402+ return format ("TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS.FF%d')" , precision );
403+ }
404+
259405 private static ColumnMapping dateColumnMapping ()
260406 {
261407 // Exasol driver does not support LocalDate
0 commit comments