Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public enum HiveErrorCode
HIVE_TABLE_LOCK_NOT_ACQUIRED(40, EXTERNAL),
HIVE_VIEW_TRANSLATION_ERROR(41, EXTERNAL),
HIVE_PARTITION_NOT_FOUND(42, USER_ERROR),
HIVE_INVALID_TIMESTAMP_COERCION(43, EXTERNAL),
/**/;

private final ErrorCode errorCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package io.trino.plugin.hive.coercions;

import io.airlift.slice.Slices;
import io.trino.spi.TrinoException;
import io.trino.spi.block.Block;
import io.trino.spi.block.BlockBuilder;
import io.trino.spi.type.LongTimestamp;
Expand All @@ -25,9 +26,11 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;

import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_TIMESTAMP_COERCION;
import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_SECOND;
import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MICROSECOND;
import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND;
import static io.trino.spi.type.Timestamps.SECONDS_PER_DAY;
import static io.trino.spi.type.Varchars.truncateToLength;
import static java.lang.Math.floorDiv;
import static java.lang.Math.floorMod;
Expand All @@ -46,6 +49,9 @@ public final class TimestampCoercer
.toFormatter()
.withChronology(IsoChronology.INSTANCE);

// Before 1900, Java Time and Joda Time are not consistent with java.sql.Date and java.util.Calendar
private static final long START_OF_MODERN_ERA_SECONDS = java.time.LocalDate.of(1900, 1, 1).toEpochDay() * SECONDS_PER_DAY;
Comment thread
raunaqmorarka marked this conversation as resolved.

private TimestampCoercer() {}

public static class LongTimestampToVarcharCoercer
Expand All @@ -65,6 +71,9 @@ protected void applyCoercedValue(BlockBuilder blockBuilder, Block block, int pos
long microsFraction = floorMod(timestamp.getEpochMicros(), MICROSECONDS_PER_SECOND);
// Hive timestamp has nanoseconds precision, so no truncation here
long nanosFraction = (microsFraction * NANOSECONDS_PER_MICROSECOND) + (timestamp.getPicosOfMicro() / PICOSECONDS_PER_NANOSECOND);
if (epochSecond < START_OF_MODERN_ERA_SECONDS) {
throw new TrinoException(HIVE_INVALID_TIMESTAMP_COERCION, "Coercion on historical dates is not supported");
}

toType.writeSlice(
blockBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import io.airlift.slice.Slices;
import io.trino.plugin.hive.HiveTimestampPrecision;
import io.trino.spi.TrinoException;
import io.trino.spi.block.Block;
import io.trino.spi.type.LongTimestamp;
import io.trino.spi.type.SqlTimestamp;
Expand All @@ -38,6 +39,7 @@
import static java.time.ZoneOffset.UTC;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class TestTimestampCoercer
{
Expand Down Expand Up @@ -86,11 +88,26 @@ public void testTimestampToSmallerVarchar()
assertLongTimestampToVarcharCoercions(TIMESTAMP_PICOS, longTimestamp, createVarcharType(29), "2023-04-11 05:16:12.345678876");
}

@Test
public void testHistoricalLongTimestampToVarchar()
{
LocalDateTime localDateTime = LocalDateTime.parse("1899-12-31T23:59:59.999999999");
SqlTimestamp timestamp = SqlTimestamp.fromSeconds(TIMESTAMP_PICOS.getPrecision(), localDateTime.toEpochSecond(UTC), localDateTime.get(NANO_OF_SECOND));
assertThatThrownBy(() -> assertLongTimestampToVarcharCoercions(
TIMESTAMP_PICOS,
new LongTimestamp(timestamp.getEpochMicros(), timestamp.getPicosOfMicros()),
createUnboundedVarcharType(),
"1899-12-31 23:59:59.999999999"))
.isInstanceOf(TrinoException.class)
.hasMessageContaining("Coercion on historical dates is not supported");
}

@DataProvider
public Object[][] timestampValuesProvider()
{
return new Object[][] {
// before epoch
{"1900-01-01T00:00:00.000", "1900-01-01 00:00:00"},
{"1958-01-01T13:18:03.123", "1958-01-01 13:18:03.123"},
// after epoch
{"2019-03-18T10:01:17.987", "2019-03-18 10:01:17.987"},
Expand Down