Skip to content

Conversation

alexjo2144
Copy link
Member

@alexjo2144 alexjo2144 commented Oct 9, 2025

Description

This commit supports reading DateTime64(precision, timezone) from ClickHouse.

In addition to this, previously Trino Timestamp(0) was mapped to ClickHouse DateTime, this commit extends this behavior by mapping Trino Timestamp(p) to ClickHouse DateTime64(p).

Additional context and related issues

Re-opening an old issue and PR @tangjiangling was working on some time ago:
#10537
#15040

Besides resolving some merge conflicts and a few tweaks this is all his work.

Release notes

( ) This is not user-visible or is docs only, and no release notes are required.
( ) Release notes are required. Please propose a release note for me.
( ) Release notes are required, with the following suggested text:

## Section
* Fix some things. ({issue}`issuenumber`)

Summary by Sourcery

Add full support for ClickHouse DateTime64 types by mapping Trino TIMESTAMP(p) to DateTime64(p), implementing read/write logic with precision handling and range checks, and updating tests and documentation accordingly

New Features:

  • Support ClickHouse DateTime64(p[, timezone]) type for both TIMESTAMP and TIMESTAMP WITH TIME ZONE
  • Map Trino TIMESTAMP(p) to ClickHouse DateTime64(p) on write and vice versa on read

Enhancements:

  • Introduce dedicated read/write functions and range validation for DateTime64 with up to 9 digits of fractional precision
  • Refactor ClickHouseClient to handle DateTime64 mappings and add helper methods for short and long timestamp handling

Documentation:

  • Update ClickHouse connector documentation to include DateTime64(p[, timezone]) mappings

Tests:

  • Add SQLDataTypeTest cases and connector tests covering DateTime64 precision range, time zones, and min/max bounds
  • Extend BaseClickHouseTypeMapping with DateTime64 type factory and value limits

This commit supports reading `DateTime64(precision, timezone)` from
ClickHouse.

In addition to this, previously Trino `Timestamp(0)` was mapped to
ClickHouse `DateTime`, this commit extends this behavior by mapping
Trino `Timestamp(p)` to ClickHouse `DateTime64(p)`.

Co-authored-by: Alex Jo <[email protected]>
@cla-bot cla-bot bot added the cla-signed label Oct 9, 2025
@github-actions github-actions bot added docs clickhouse ClickHouse connector labels Oct 9, 2025
Copy link

sourcery-ai bot commented Oct 9, 2025

Reviewer's Guide

This pull request adds full support for ClickHouse DateTime64(precision[, timezone]) by extending Trino’s TimestampType mapping to leverage DateTime64(p), introducing new read/write functions with sub-second precision and validation, refactoring tests to cover all precisions and boundaries, and updating connector documentation accordingly.

Entity relationship diagram for updated ClickHouse-Trino type mapping

erDiagram
    ClickHouse_DateTime64 {
        int precision
        string timezone
    }
    Trino_Timestamp {
        int precision
    }
    ClickHouse_DateTime64 ||--|| Trino_Timestamp : maps to
    ClickHouse_DateTime {
        string timezone
    }
    Trino_Timestamp_0 {
    }
    ClickHouse_DateTime ||--|| Trino_Timestamp_0 : maps to
Loading

Class diagram for updated timestamp mapping and write functions

classDiagram
    class ClickHouseClient {
        +int CLICKHOUSE_MAX_SUPPORTED_DATETIME64_PRECISION
        +Optional<ColumnMapping> toColumnMapping(...)
        +WriteMapping toWriteMapping(...)
        +ColumnMapping timestampColumnMapping(TimestampType, ClickHouseVersion)
        +ColumnMapping timestampWithTimeZoneColumnMapping(ClickHouseColumn, int, ClickHouseVersion)
        +LongWriteFunction shortTimestampWriteFunction(ClickHouseVersion)
        +ObjectWriteFunction longTimestampWriteFunction(ClickHouseVersion, int)
        +LongWriteFunction shortTimestampWithTimeZoneWriteFunction(TrinoToClickHouseWriteChecker<LocalDateTime>, ClickHouseVersion, TimeZone)
        +ObjectReadFunction longTimestampWithTimeZoneReadFunction()
        +ObjectWriteFunction longTimestampWithTimeZoneWriteFunction()
    }
    class TrinoToClickHouseWriteChecker<T> {
        +static DATETIME64: TrinoToClickHouseWriteChecker<LocalDateTime>
    }
    class TimestampType {
        +int getPrecision()
        +static int MAX_SHORT_PRECISION
        +static int MAX_PRECISION
    }
    class ColumnMapping {
        +static ColumnMapping longMapping(...)
        +static ColumnMapping objectMapping(...)
    }
    class WriteMapping {
        +static WriteMapping longMapping(...)
        +static WriteMapping objectMapping(...)
    }
    class LongTimestamp {
        +long getEpochMicros()
        +long getPicosOfMicro()
    }
    class LongTimestampWithTimeZone {
        +long getEpochMillis()
        +long getPicosOfMilli()
        +TimeZoneKey getTimeZoneKey()
        +static fromEpochSecondsAndFraction(...)
    }
    ClickHouseClient --> TrinoToClickHouseWriteChecker
    ClickHouseClient --> TimestampType
    ClickHouseClient --> ColumnMapping
    ClickHouseClient --> WriteMapping
    ClickHouseClient --> LongTimestamp
    ClickHouseClient --> LongTimestampWithTimeZone
Loading

File-Level Changes

Change Details Files
Refactor and extend type mapping tests to cover DateTime64 across all precisions and time zones
  • Replaced hard-coded timestamp(0) tests with IntStream loops iterating over supported precisions (0–9)
  • Added a new datetime64 test suite mirroring timestamp tests for each precision
  • Introduced min/max boundary tests for DateTime64 and updated unsupported timestamp checks
  • Removed legacy unsupported timestamp entries in connector smoke tests
plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java
plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java
Implement DateTime64 mapping and I/O in ClickHouseClient
  • Defined CLICKHOUSE_MAX_SUPPORTED_DATETIME64_PRECISION constant
  • Enhanced toColumnMapping and toWriteMapping to route TimestampType → DateTime64(p) based on precision
  • Introduced timestampColumnMapping and timestampWithTimeZoneColumnMapping helpers for short vs object mappings
  • Added dedicated short/long read/write functions enforcing DATETIME64 validation for sub-second precision
plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java
Extend write validation logic for DateTime64 ranges in TrinoToClickHouseWriteChecker
  • Added DATETIME64 checker with appropriate min/max LocalDateTime ranges for pre-21.4 and post-21.4 versions
plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/TrinoToClickHouseWriteChecker.java
Update connector documentation to include DateTime64 mappings
  • Added DateTime64(p[, timezone]) ↔ TIMESTAMP(p)[WITH TIME ZONE] entries in ClickHouse mapping tables
docs/src/main/sphinx/connector/clickhouse.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java:757` </location>
<code_context>
-                // TODO (https://github.com/trinodb/trino/issues/10537) Add support for Datetime64 type
-                return Optional.of(timestampColumnMapping(TIMESTAMP_MILLIS));
+                if (columnDataType == ClickHouseDataType.DateTime64) {
+                    return Optional.of(timestampColumnMapping(createTimestampType(typeHandle.requiredDecimalDigits()), getClickHouseServerVersion(session)));
+                }
+                // TODO Add support for Datetime32 type
</code_context>

<issue_to_address>
**issue (bug_risk):** Check for requiredDecimalDigits exceeding supported precision.

Validate requiredDecimalDigits against CLICKHOUSE_MAX_SUPPORTED_DATETIME64_PRECISION before using it to avoid errors or data loss.
</issue_to_address>

### Comment 2
<location> `plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java:826-830` </location>
<code_context>
-        if (type == TIMESTAMP_SECONDS) {
-            return WriteMapping.longMapping("DateTime", timestampSecondsWriteFunction(getClickHouseServerVersion(session)));
+        if (type instanceof TimestampType timestampType) {
+            int precision = min(timestampType.getPrecision(), CLICKHOUSE_MAX_SUPPORTED_DATETIME64_PRECISION);
+            if (precision <= TimestampType.MAX_SHORT_PRECISION) {
+                return WriteMapping.longMapping(format("DateTime64(%d)", precision), shortTimestampWriteFunction(getClickHouseServerVersion(session)));
+            }
+            return WriteMapping.objectMapping(format("DateTime64(%d)", precision), longTimestampWriteFunction(getClickHouseServerVersion(session), precision));
         }
         if (type.equals(uuidType)) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Consider handling precision values below 0.

If timestampType.getPrecision() returns a negative value, min will not prevent invalid precision. Please add a check to ensure precision is non-negative before using it.
</issue_to_address>

### Comment 3
<location> `plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/TrinoToClickHouseWriteChecker.java:73-80` </location>
<code_context>
                     new TimestampWriteValueChecker(
                             version -> version.isNewerOrEqualTo("21.4"),
                             new Range<>(LocalDateTime.parse("1970-01-01T00:00:00"), LocalDateTime.parse("2106-02-07T06:28:15")))));
+    public static final TrinoToClickHouseWriteChecker<LocalDateTime> DATETIME64 = new TrinoToClickHouseWriteChecker<>(
+            ImmutableList.of(
+                    new TimestampWriteValueChecker(
+                            version -> version.isOlderThan("21.4"),
+                            new Range<>(LocalDateTime.parse("1900-01-01T00:00:00"), LocalDateTime.parse("2262-04-11T23:47:16"))),
+                    new TimestampWriteValueChecker(
+                            version -> version.isNewerOrEqualTo("21.4"),
+                            new Range<>(LocalDateTime.parse("1900-01-01T00:00:00"), LocalDateTime.parse("2262-04-11T23:47:16")))));

     private final List<Checker<T>> checkers;
</code_context>

<issue_to_address>
**issue:** Consider aligning DateTime64 range with documented values.

Please verify that the DateTime64 range matches the documented values for each version, and update the code or comments to ensure consistency.
</issue_to_address>

### Comment 4
<location> `plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java:1053-1056` </location>
<code_context>
+    }
+
     @Test
     public void testUnsupportedTimestamp()
     {
-        testUnsupportedTimestamp("1969-12-31 23:59:59"); // MIN_SUPPORTED_DATETIME_VALUE - 1 second
-        testUnsupportedTimestamp("2106-02-07 06:28:16"); // MAX_SUPPORTED_DATETIME_VALUE + 1 second
+        testUnsupportedTimestamp("1899-12-31 23:59:59"); // MIN_SUPPORTED_DATETIME_VALUE - 1 second
+        testUnsupportedTimestamp("2262-04-11 23:47:17"); // MAX_SUPPORTED_DATETIME_VALUE + 1 second
     }

</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding assertions for error messages when unsupported timestamps are used with DateTime64.

Please add assertions to verify that the error messages returned for unsupported DateTime64 timestamps are both specific and informative.

Suggested implementation:

```java
    @Test
    public void testUnsupportedTimestamp()
    {
        assertUnsupportedTimestampError("1899-12-31 23:59:59", "DateTime64 value 1899-12-31 23:59:59 is out of supported range"); // MIN_SUPPORTED_DATETIME_VALUE - 1 second
        assertUnsupportedTimestampError("2262-04-11 23:47:17", "DateTime64 value 2262-04-11 23:47:17 is out of supported range"); // MAX_SUPPORTED_DATETIME_VALUE + 1 second
    }

```

```java
    private void assertUnsupportedTimestampError(String timestamp, String expectedMessage)
    {
        try {
            testUnsupportedTimestamp(timestamp);
            org.junit.Assert.fail("Expected exception for unsupported timestamp: " + timestamp);
        }
        catch (Exception e) {
            org.junit.Assert.assertTrue(
                "Error message should mention unsupported DateTime64 value",
                e.getMessage().contains(expectedMessage)
            );
        }
    }

    @Test

```

If `testUnsupportedTimestamp` does not throw an exception with a message, you may need to update its implementation to do so, or to propagate the error message so it can be asserted. Adjust the expected error message string as needed to match the actual error thrown by your codebase.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

// TODO (https://github.com/trinodb/trino/issues/10537) Add support for Datetime64 type
return Optional.of(timestampColumnMapping(TIMESTAMP_MILLIS));
if (columnDataType == ClickHouseDataType.DateTime64) {
return Optional.of(timestampColumnMapping(createTimestampType(typeHandle.requiredDecimalDigits()), getClickHouseServerVersion(session)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Check for requiredDecimalDigits exceeding supported precision.

Validate requiredDecimalDigits against CLICKHOUSE_MAX_SUPPORTED_DATETIME64_PRECISION before using it to avoid errors or data loss.

Comment on lines +826 to +830
int precision = min(timestampType.getPrecision(), CLICKHOUSE_MAX_SUPPORTED_DATETIME64_PRECISION);
if (precision <= TimestampType.MAX_SHORT_PRECISION) {
return WriteMapping.longMapping(format("DateTime64(%d)", precision), shortTimestampWriteFunction(getClickHouseServerVersion(session)));
}
return WriteMapping.objectMapping(format("DateTime64(%d)", precision), longTimestampWriteFunction(getClickHouseServerVersion(session), precision));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Consider handling precision values below 0.

If timestampType.getPrecision() returns a negative value, min will not prevent invalid precision. Please add a check to ensure precision is non-negative before using it.

Comment on lines +73 to +80
public static final TrinoToClickHouseWriteChecker<LocalDateTime> DATETIME64 = new TrinoToClickHouseWriteChecker<>(
ImmutableList.of(
new TimestampWriteValueChecker(
version -> version.isOlderThan("21.4"),
new Range<>(LocalDateTime.parse("1900-01-01T00:00:00"), LocalDateTime.parse("2262-04-11T23:47:16"))),
new TimestampWriteValueChecker(
version -> version.isNewerOrEqualTo("21.4"),
new Range<>(LocalDateTime.parse("1900-01-01T00:00:00"), LocalDateTime.parse("2262-04-11T23:47:16")))));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Consider aligning DateTime64 range with documented values.

Please verify that the DateTime64 range matches the documented values for each version, and update the code or comments to ensure consistency.

new TimestampWriteValueChecker(
version -> version.isNewerOrEqualTo("21.4"),
new Range<>(LocalDateTime.parse("1970-01-01T00:00:00"), LocalDateTime.parse("2106-02-07T06:28:15")))));
public static final TrinoToClickHouseWriteChecker<LocalDateTime> DATETIME64 = new TrinoToClickHouseWriteChecker<>(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably dedup this, not sure why it was split into pre/post 21.4. The values are the same.

I did widen these ranges from the original PR based on the documentation I found. https://clickhouse.com/docs/sql-reference/data-types/datetime64

I also went with the lower upper range value. Technically it could be higher when using a precision < 9.

@alexjo2144
Copy link
Member Author

Just noticed there was this attempt too #23788
I'll make this a draft until I look at what was different with that one.

@alexjo2144 alexjo2144 marked this pull request as draft October 9, 2025 23:24
@ebyhr
Copy link
Member

ebyhr commented Oct 9, 2025

cc: @krvikash

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants