From fe8318661a8c39b2591ca3274d205743d0f7e042 Mon Sep 17 00:00:00 2001 From: Niels Pardon Date: Wed, 11 Mar 2026 19:46:42 +0100 Subject: [PATCH] feat(isthmus): enable precision timestamp addition/subtraction Signed-off-by: Niels Pardon --- .../isthmus/expression/FunctionMappings.java | 4 +- ...recisionTimestampDatetimeAdditionTest.java | 201 ++++++++++++++++++ ...isionTimestampDatetimeSubtractionTest.java | 201 ++++++++++++++++++ 3 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeAdditionTest.java create mode 100644 isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeSubtractionTest.java diff --git a/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java b/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java index 90f04a326..003e5a571 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java +++ b/isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java @@ -140,12 +140,12 @@ public class FunctionMappings { resolver( SqlStdOperatorTable.PLUS, Set.of("i8", "i16", "i32", "i64", "fp32", "fp64", "dec")), SqlStdOperatorTable.DATETIME_PLUS, - resolver(SqlStdOperatorTable.PLUS, Set.of("date", "time", "timestamp")), + resolver(SqlStdOperatorTable.DATETIME_PLUS, Set.of("date", "ts", "tstz", "pts", "ptstz")), SqlStdOperatorTable.MINUS, resolver( SqlStdOperatorTable.MINUS, Set.of("i8", "i16", "i32", "i64", "fp32", "fp64", "dec")), SqlStdOperatorTable.MINUS_DATE, - resolver(SqlStdOperatorTable.MINUS_DATE, Set.of("date", "timestamp_tz", "timestamp")), + resolver(SqlStdOperatorTable.MINUS_DATE, Set.of("date", "ts", "tstz", "pts", "ptstz")), SqlStdOperatorTable.BIT_LEFT_SHIFT, resolver(SqlStdOperatorTable.BIT_LEFT_SHIFT, Set.of("i8", "i16", "i32", "i64"))); diff --git a/isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeAdditionTest.java b/isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeAdditionTest.java new file mode 100644 index 000000000..ba1527093 --- /dev/null +++ b/isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeAdditionTest.java @@ -0,0 +1,201 @@ +package io.substrait.isthmus; + +import org.junit.jupiter.api.Test; + +/** + * Test class for precision timestamp datetime addition operations. Tests the mapping of Calcite's + * DATETIME_PLUS operator to Substrait's add function for precision_timestamp and + * precision_timestamp_tz types. + */ +class PrecisionTimestampDatetimeAdditionTest extends PlanTestBase { + + static String CREATES = + "CREATE TABLE events (" + + "event_id INT, " + + "event_date DATE, " + + "event_timestamp TIMESTAMP(3), " + + "event_timestamp_tz TIMESTAMP(6) WITH LOCAL TIME ZONE" + + ")"; + + @Test + void dateAddIntervalYear() throws Exception { + String query = "SELECT event_date + INTERVAL '1' YEAR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateAddIntervalMonth() throws Exception { + String query = "SELECT event_date + INTERVAL '3' MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateAddIntervalYearToMonth() throws Exception { + String query = "SELECT event_date + INTERVAL '1-6' YEAR TO MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateAddIntervalDay() throws Exception { + String query = "SELECT event_date + INTERVAL '5' DAY FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalYear() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '1' YEAR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalMonth() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '3' MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalYearToMonth() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '1-6' YEAR TO MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalDay() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '5' DAY FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalHour() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '12' HOUR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalMinute() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '30' MINUTE FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalSecond() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '45' SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAddIntervalDayToSecond() throws Exception { + String query = "SELECT event_timestamp + INTERVAL '1 12:30:45' DAY TO SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalYear() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '2' YEAR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalMonth() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '6' MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalYearToMonth() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '2-3' YEAR TO MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalDay() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '10' DAY FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalHour() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '6' HOUR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalMinute() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '15' MINUTE FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalSecond() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '30' SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAddIntervalDayToSecond() throws Exception { + String query = "SELECT event_timestamp_tz + INTERVAL '2 06:15:30' DAY TO SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void multiplePrecisionTimestampAdditions() throws Exception { + String query = + "SELECT " + + "event_timestamp + INTERVAL '1' YEAR, " + + "event_timestamp + INTERVAL '5' DAY, " + + "event_timestamp_tz + INTERVAL '2' MONTH, " + + "event_timestamp_tz + INTERVAL '12' HOUR " + + "FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAdditionInWhereClause() throws Exception { + String query = + "SELECT event_id FROM events " + + "WHERE event_timestamp + INTERVAL '1' DAY > TIMESTAMP '2024-01-01 00:00:00'"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzAdditionInWhereClause() throws Exception { + String query = + "SELECT event_id FROM events " + + "WHERE event_timestamp_tz + INTERVAL '1' MONTH > TIMESTAMP WITH LOCAL TIME ZONE '2024-01-01 00:00:00'"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampAdditionWithComparison() throws Exception { + String query = + "SELECT event_id, event_timestamp FROM events " + + "WHERE event_timestamp + INTERVAL '7' DAY < event_timestamp + INTERVAL '14' DAY"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateAdditionInWhereClause() throws Exception { + String query = + "SELECT event_id FROM events " + "WHERE event_date + INTERVAL '1' DAY > DATE '2024-01-01'"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateAdditionWithComparison() throws Exception { + String query = + "SELECT event_id, event_date FROM events " + + "WHERE event_date + INTERVAL '7' DAY < event_date + INTERVAL '14' DAY"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void multipleDateAdditions() throws Exception { + String query = + "SELECT " + + "event_date + INTERVAL '1' YEAR, " + + "event_date + INTERVAL '5' DAY, " + + "event_date + INTERVAL '2' MONTH " + + "FROM events"; + assertFullRoundTrip(query, CREATES); + } +} diff --git a/isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeSubtractionTest.java b/isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeSubtractionTest.java new file mode 100644 index 000000000..e6bc402ec --- /dev/null +++ b/isthmus/src/test/java/io/substrait/isthmus/PrecisionTimestampDatetimeSubtractionTest.java @@ -0,0 +1,201 @@ +package io.substrait.isthmus; + +import org.junit.jupiter.api.Test; + +/** + * Test class for precision timestamp datetime subtraction operations. Tests the mapping of + * Calcite's MINUS_DATE operator to Substrait's subtract function for precision_timestamp and + * precision_timestamp_tz types. + */ +class PrecisionTimestampDatetimeSubtractionTest extends PlanTestBase { + + static String CREATES = + "CREATE TABLE events (" + + "event_id INT, " + + "event_date DATE, " + + "event_timestamp TIMESTAMP(3), " + + "event_timestamp_tz TIMESTAMP(6) WITH LOCAL TIME ZONE" + + ")"; + + @Test + void dateSubtractIntervalYear() throws Exception { + String query = "SELECT event_date - INTERVAL '1' YEAR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateSubtractIntervalMonth() throws Exception { + String query = "SELECT event_date - INTERVAL '3' MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateSubtractIntervalYearToMonth() throws Exception { + String query = "SELECT event_date - INTERVAL '1-6' YEAR TO MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateSubtractIntervalDay() throws Exception { + String query = "SELECT event_date - INTERVAL '5' DAY FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalYear() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '1' YEAR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalMonth() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '3' MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalYearToMonth() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '1-6' YEAR TO MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalDay() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '5' DAY FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalHour() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '12' HOUR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalMinute() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '30' MINUTE FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalSecond() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '45' SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractIntervalDayToSecond() throws Exception { + String query = "SELECT event_timestamp - INTERVAL '1 12:30:45' DAY TO SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalYear() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '2' YEAR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalMonth() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '6' MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalYearToMonth() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '2-3' YEAR TO MONTH FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalDay() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '10' DAY FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalHour() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '6' HOUR FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalMinute() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '15' MINUTE FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalSecond() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '30' SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractIntervalDayToSecond() throws Exception { + String query = "SELECT event_timestamp_tz - INTERVAL '2 06:15:30' DAY TO SECOND FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void multiplePrecisionTimestampSubtractions() throws Exception { + String query = + "SELECT " + + "event_timestamp - INTERVAL '1' YEAR, " + + "event_timestamp - INTERVAL '5' DAY, " + + "event_timestamp_tz - INTERVAL '2' MONTH, " + + "event_timestamp_tz - INTERVAL '12' HOUR " + + "FROM events"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractionInWhereClause() throws Exception { + String query = + "SELECT event_id FROM events " + + "WHERE event_timestamp - INTERVAL '1' DAY < TIMESTAMP '2024-01-01 00:00:00'"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampTzSubtractionInWhereClause() throws Exception { + String query = + "SELECT event_id FROM events " + + "WHERE event_timestamp_tz - INTERVAL '1' MONTH < TIMESTAMP WITH LOCAL TIME ZONE '2024-01-01 00:00:00'"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void precisionTimestampSubtractionWithComparison() throws Exception { + String query = + "SELECT event_id, event_timestamp FROM events " + + "WHERE event_timestamp - INTERVAL '7' DAY > event_timestamp - INTERVAL '14' DAY"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateSubtractionInWhereClause() throws Exception { + String query = + "SELECT event_id FROM events " + "WHERE event_date - INTERVAL '1' DAY < DATE '2024-01-01'"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void dateSubtractionWithComparison() throws Exception { + String query = + "SELECT event_id, event_date FROM events " + + "WHERE event_date - INTERVAL '7' DAY > event_date - INTERVAL '14' DAY"; + assertFullRoundTrip(query, CREATES); + } + + @Test + void multipleDateSubtractions() throws Exception { + String query = + "SELECT " + + "event_date - INTERVAL '1' YEAR, " + + "event_date - INTERVAL '5' DAY, " + + "event_date - INTERVAL '2' MONTH " + + "FROM events"; + assertFullRoundTrip(query, CREATES); + } +}