Skip to content

Add parametric TIMESTAMP(p) support with long timestamps#87

Draft
Copilot wants to merge 9 commits into
masterfrom
copilot/port-parametric-timestamps
Draft

Add parametric TIMESTAMP(p) support with long timestamps#87
Copilot wants to merge 9 commits into
masterfrom
copilot/port-parametric-timestamps

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 2, 2026

  • Step 1: Core TIMESTAMP type hierarchy (presto-common)

    • TimestampType made abstract with ShortTimestampType (p=0-6, long) and LongTimestampType (p=7-12, Fixed12Block)
    • LongTimestamp value class (epochMicros + picosOfMicro)
    • Timestamps utility class (rescale, format helpers)
    • Fixed12Block / Fixed12BlockBuilder block infrastructure for 12-byte values
    • TimestampParametricType in presto-main-base
    • Type registration in BuiltInTypeAndFunctionNamespaceManager
  • Step 1: Parametric TIMESTAMP operators (presto-main-base)

    • TimestampParametricOperators — polymorphic operators for all precisions 0-12 (equal, notEqual, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, between, hashCode, xxHash64, isDistinctFrom, indeterminate)
    • Registered in BuiltInTypeAndFunctionNamespaceManager
  • PARAMETRIC_DATETIME client capability (presto-client)

    • ClientCapabilities enum with PARAMETRIC_DATETIME
    • X-Presto-Client-Capabilities header in PrestoHeaders
  • Step 2: Core TIMESTAMP WITH TIME ZONE type hierarchy (presto-common)

    • TimestampWithTimeZoneType made abstract with ShortTimestampWithTimeZoneType (p=0-3, long) and LongTimestampWithTimeZoneType (p=4-12, Fixed12Block)
    • LongTimestampWithTimeZone value class (epochMillis + nanosOfMilli + timeZoneKey)
    • TimestampWithTimeZoneParametricType in presto-main-base
    • Type registration in BuiltInTypeAndFunctionNamespaceManager
  • Step 2: Parametric TIMESTAMP WITH TIME ZONE operators (presto-main-base)

    • TimestampWithTimeZoneParametricOperators — polymorphic operators for all precisions 0-12
    • Registered in BuiltInTypeAndFunctionNamespaceManager
  • Build passes (presto-common, presto-spi, presto-main-base)

  • Scalar function rewrites (DateTimeFunctions, etc.) for all precisions — deferred

  • Phase 2 client compatibility fixes (Steps 3-5)

  • Phase 3 semantic correctness (Steps 6-7)

  • Phase 4 Hive connector (Steps 9-10)

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 2, 2026

Reviewer's Guide

Ports Trino’s parametric TIMESTAMP(p) infrastructure into PrestoDB: introduces short/long timestamp type hierarchy with a TIMESTAMP(p) parametric type, new 12‑byte block storage (Fixed12Block/Builder/Encoding) and LongTimestamp value type, wires these into metadata, operators, and existing connectors (e.g., Iceberg and plan printer), and adds comprehensive tests plus bug fixes for timestamp rescaling/formatting and equality/hash semantics.

Class diagram for the new timestamp type hierarchy

classDiagram
    direction LR

    class TimestampType {
        <<abstract>>
        -int precision
        +static int MAX_PRECISION
        +static int MAX_SHORT_PRECISION
        +static TimestampType TIMESTAMP_SECONDS
        +static TimestampType TIMESTAMP_MILLIS
        +static TimestampType TIMESTAMP_MICROS
        +static TimestampType TIMESTAMP_NANOS
        +static TimestampType TIMESTAMP_PICOS
        +static TimestampType TIMESTAMP
        +static TimestampType TIMESTAMP_MICROSECONDS
        +TimestampType createTimestampType(int precision)
        +int getPrecision()
        +boolean isShort()
        +boolean isLong()
        +TimeUnit getStorageUnit()
        +boolean isComparable()
        +boolean isOrderable()
        +Object getObjectValue(SqlFunctionProperties properties, Block block, int position)
        +TypeSignature getTypeSignature()
        +boolean equals(Object other)
        +int hashCode()
        +long getEpochSecond(long timestamp)
        +int getNanos(long timestamp)
    }

    class ShortTimestampType {
        +ShortTimestampType(int precision)
        +int getFixedSize()
        +Object getObjectValue(SqlFunctionProperties properties, Block block, int position)
        +long getLong(Block block, int position)
        +long getLongUnchecked(UncheckedBlock block, int internalPosition)
        +Slice getSlice(Block block, int position)
        +void writeLong(BlockBuilder blockBuilder, long value)
        +void appendTo(Block block, int position, BlockBuilder blockBuilder)
        +boolean equalTo(Block leftBlock, int leftPosition, Block rightBlock, int rightPosition)
        +long hash(Block block, int position)
        +int compareTo(Block leftBlock, int leftPosition, Block rightBlock, int rightPosition)
        +BlockBuilder createBlockBuilder(BlockBuilderStatus status, int expectedEntries, int expectedBytesPerEntry)
        +BlockBuilder createBlockBuilder(BlockBuilderStatus status, int expectedEntries)
        +BlockBuilder createFixedSizeBlockBuilder(int positionCount)
        +static long hash(long value)
    }

    class LongTimestampType {
        +LongTimestampType(int precision)
        +int getFixedSize()
        +Object getObjectValue(SqlFunctionProperties properties, Block block, int position)
        +LongTimestamp getObject(Block block, int position)
        +void writeObject(BlockBuilder blockBuilder, Object value)
        +void appendTo(Block block, int position, BlockBuilder blockBuilder)
        +boolean equalTo(Block leftBlock, int leftPosition, Block rightBlock, int rightPosition)
        +long hash(Block block, int position)
        +int compareTo(Block leftBlock, int leftPosition, Block rightBlock, int rightPosition)
        +BlockBuilder createBlockBuilder(BlockBuilderStatus status, int expectedEntries, int expectedBytesPerEntry)
        +BlockBuilder createBlockBuilder(BlockBuilderStatus status, int expectedEntries)
        +BlockBuilder createFixedSizeBlockBuilder(int positionCount)
    }

    class LongTimestamp {
        -long epochMicros
        -int picosOfMicro
        +LongTimestamp(long epochMicros, int picosOfMicro)
        +long getEpochMicros()
        +int getPicosOfMicro()
        +boolean equals(Object other)
        +int hashCode()
        +int compareTo(LongTimestamp other)
        +String toString()
    }

    class Timestamps {
        <<utility>>
        +static int MAX_PRECISION
        +static int MAX_SHORT_PRECISION
        +static int DEFAULT_PRECISION
        +static long[] POWERS_OF_TEN
        +static long MILLISECONDS_PER_SECOND
        +static long MICROSECONDS_PER_SECOND
        +static long NANOSECONDS_PER_SECOND
        +static long PICOSECONDS_PER_SECOND
        +static long MILLISECONDS_PER_DAY
        +static long rescale(long value, int fromPrecision, int toPrecision)
        +static long roundDiv(long value, long divisor)
        +static long round(long value, int precision)
        +static long truncateToMicros(long epochMicros, int picosOfMicro)
        +static boolean isShortTimestamp(int precision)
        +static String formatTimestamp(int precision, long epochMicros, int picosOfMicro)
        +static long millisToMicros(long millis)
        +static long microsToMillis(long micros)
        +static long microsToNanos(long micros)
        +static long nanosToMicros(long nanos)
    }

    TimestampType <|-- ShortTimestampType
    TimestampType <|-- LongTimestampType

    LongTimestampType ..> LongTimestamp
    LongTimestampType ..> Fixed12Block
    LongTimestampType ..> Fixed12BlockBuilder
    ShortTimestampType ..> LongArrayBlockBuilder

    LongTimestamp ..> Timestamps
    TimestampType ..> Timestamps
    ShortTimestampType ..> SqlTimestamp

    class Block
    class BlockBuilder
    class BlockBuilderStatus
    class UncheckedBlock
    class LongArrayBlockBuilder
    class Fixed12Block
    class Fixed12BlockBuilder
    class SqlTimestamp
    class SqlFunctionProperties
    class TimeUnit
    class TypeSignature
Loading

Class diagram for Fixed12Block storage and encoding

classDiagram
    direction LR

    class Fixed12Block {
        +static int FIXED12_BYTES
        +static int SIZE_IN_BYTES_PER_POSITION
        +static int INT_LONGS_PER_ENTRY
        -int positionOffset
        -int positionCount
        -boolean[] valueIsNull
        -int[] values
        -long retainedSizeInBytes
        +Fixed12Block(int positionCount, Optional~boolean[]~ valueIsNull, int[] values)
        +Fixed12Block(int positionOffset, int positionCount, boolean[] valueIsNull, int[] values)
        +long getFixed12First(int position)
        +int getFixed12Second(int position)
        +static long getFixed12FirstUnchecked(int[] values, int internalPosition)
        +static int getFixed12SecondUnchecked(int[] values, int internalPosition)
        +static void encodeFixed12(long first, int second, int[] target, int position)
        +long getSizeInBytes()
        +OptionalInt fixedSizeInBytesPerPosition()
        +long getRegionSizeInBytes(int position, int length)
        +long getPositionsSizeInBytes(boolean[] usedPositions, int usedPositionCount)
        +long getRetainedSizeInBytes()
        +long getEstimatedDataSizeForStats(int position)
        +void retainedBytesForEachPart(ObjLongConsumer~Object~ consumer)
        +int getPositionCount()
        +long getLong(int position, int offset)
        +int getInt(int position)
        +boolean mayHaveNull()
        +boolean isNull(int position)
        +void writePositionTo(int position, BlockBuilder blockBuilder)
        +void writePositionTo(int position, SliceOutput output)
        +Block getSingleValueBlock(int position)
        +Block copyPositions(int[] positions, int offset, int length)
        +Block getRegion(int positionOffset, int length)
        +Block copyRegion(int positionOffset, int length)
        +String getEncodingName()
        +Block appendNull()
        +long getLongUnchecked(int internalPosition, int offset)
        +int getOffsetBase()
        +boolean isNullUnchecked(int internalPosition)
        +boolean equals(Object other)
        +int hashCode()
    }

    class Fixed12BlockBuilder {
        -BlockBuilderStatus blockBuilderStatus
        -boolean initialized
        -int initialEntryCount
        -int positionCount
        -boolean hasNullValue
        -boolean hasNonNullValue
        -boolean[] valueIsNull
        -int[] values
        -long retainedSizeInBytes
        +Fixed12BlockBuilder(BlockBuilderStatus status, int expectedEntries)
        +Fixed12BlockBuilder writeFixed12(long first, int second)
        +BlockBuilder writeLong(long value)
        +BlockBuilder writeInt(int value)
        +BlockBuilder closeEntry()
        +BlockBuilder appendNull()
        +Block build()
        +BlockBuilder newBlockBuilderLike(BlockBuilderStatus status)
        +BlockBuilder newBlockBuilderLike(BlockBuilderStatus status, int expectedEntries)
        +long getSizeInBytes()
        +OptionalInt fixedSizeInBytesPerPosition()
        +long getRegionSizeInBytes(int position, int length)
        +long getPositionsSizeInBytes(boolean[] usedPositions, int usedPositionCount)
        +long getRetainedSizeInBytes()
        +long getEstimatedDataSizeForStats(int position)
        +void retainedBytesForEachPart(ObjLongConsumer~Object~ consumer)
        +int getPositionCount()
        +long getLong(int position, int offset)
        +int getInt(int position)
        +boolean mayHaveNull()
        +boolean isNull(int position)
        +void writePositionTo(int position, BlockBuilder blockBuilder)
        +void writePositionTo(int position, SliceOutput output)
        +BlockBuilder readPositionFrom(SliceInput input)
        +Block getSingleValueBlock(int position)
        +Block copyPositions(int[] positions, int offset, int length)
        +Block getRegion(int positionOffset, int length)
        +Block copyRegion(int positionOffset, int length)
        +String getEncodingName()
        +long getLongUnchecked(int internalPosition, int offset)
        +boolean isNullUnchecked(int internalPosition)
        +int getOffsetBase()
    }

    class Fixed12BlockEncoding {
        +static String NAME
        +String getName()
        +void writeBlock(BlockEncodingSerde serde, SliceOutput sliceOutput, Block block)
        +Block readBlock(BlockEncodingSerde serde, SliceInput sliceInput)
    }

    class BlockEncodingManager {
        +BlockEncodingManager()
        +void addBlockEncoding(BlockEncoding encoding)
    }

    class Block {
        <<interface>>
    }

    class BlockBuilder {
        <<interface>>
    }

    class BlockEncoding {
        <<interface>>
    }

    class BlockEncodingSerde
    class BlockBuilderStatus
    class RunLengthEncodedBlock
    class SliceOutput
    class SliceInput

    Fixed12Block ..|> Block
    Fixed12BlockBuilder ..|> BlockBuilder
    Fixed12BlockEncoding ..|> BlockEncoding

    Fixed12BlockEncoding ..> Fixed12Block : creates
    Fixed12BlockEncoding ..> Fixed12BlockBuilder : uses encodeFixed12

    Fixed12BlockBuilder ..> Fixed12Block : build()
    Fixed12BlockBuilder ..> RunLengthEncodedBlock

    Fixed12Block ..> SliceOutput
    Fixed12BlockBuilder ..> SliceOutput
    Fixed12BlockBuilder ..> SliceInput

    BlockEncodingManager o--> Fixed12BlockEncoding
Loading

Class diagram for TIMESTAMP(p) parametric type and operators

classDiagram
    direction LR

    class TimestampParametricType {
        +static TimestampParametricType TIMESTAMP
        +String getName()
        +Type createType(List~TypeParameter~ parameters)
    }

    class ParametricType {
        <<interface>>
        +String getName()
        +Type createType(List~TypeParameter~ parameters)
    }

    class TypeParameter {
        +boolean isLongLiteral()
        +long getLongLiteral()
    }

    class Type {
        <<interface>>
    }

    class StandardTypes {
        <<utility>>
        +static String TIMESTAMP
        +static String TIMESTAMP_MICROSECONDS
    }

    class BuiltInTypeAndFunctionNamespaceManager {
        -void registerBuiltInTypes(FunctionsConfig config)
        +void addType(Type type)
        +void addParametricType(ParametricType type)
        +List~SqlFunction~ getBuiltInFunctions(FunctionsConfig config)
    }

    class TimestampMicrosecondsOperators {
        <<utility>>
        +Boolean equal(long left, long right)
        +Boolean notEqual(long left, long right)
        +boolean lessThan(long left, long right)
        +boolean lessThanOrEqual(long left, long right)
        +boolean greaterThan(long left, long right)
        +boolean greaterThanOrEqual(long left, long right)
        +boolean between(long value, long min, long max)
        +long hashCode(long value)
        +long xxHash64(long value)
        +boolean indeterminate(long value, boolean isNull)
    }

    class TimestampMicrosecondsDistinctFromOperator {
        +boolean isDistinctFrom(long left, boolean leftNull, long right, boolean rightNull)
        +boolean isDistinctFrom(Block left, int leftPosition, Block right, int rightPosition)
    }

    class Block

    class TimestampType

    TimestampParametricType ..|> ParametricType
    TimestampParametricType ..> TimestampType : returns
    TimestampParametricType ..> StandardTypes
    TimestampParametricType ..> TypeParameter

    BuiltInTypeAndFunctionNamespaceManager ..> TimestampType : addType(TIMESTAMP)
    BuiltInTypeAndFunctionNamespaceManager ..> TimestampParametricType : addParametricType(TimestampParametricType.TIMESTAMP)
    BuiltInTypeAndFunctionNamespaceManager ..> TimestampType : addType(TIMESTAMP_MICROSECONDS)
    BuiltInTypeAndFunctionNamespaceManager ..> TimestampMicrosecondsOperators

    TimestampMicrosecondsOperators ..> TimestampMicrosecondsDistinctFromOperator
    TimestampMicrosecondsDistinctFromOperator ..> Block
Loading

File-Level Changes

Change Details Files
Introduce parametric TIMESTAMP(p) type hierarchy (short vs long) and LongTimestamp value type.
  • Refactor TimestampType from a concrete AbstractLongType into an abstract FixedWidthType with precision 0..12, short/long classification helpers, and storage-unit resolution logic
  • Add ShortTimestampType for precisions 0–6 using long-based storage and preserving legacy SqlTimestamp behavior and hashing/comparison semantics
  • Add LongTimestampType for precisions 7–12 backed by Fixed12Block, exposing LongTimestamp values and implementing append/equalTo/compare/hash via the value object
  • Add LongTimestamp value class that stores epochMicros plus picosOfMicro with validation, comparison, equality, hashCode, and a toString that delegates to Timestamps.formatTimestamp(12, ...)
  • Update type signatures so bare timestamp and timestamp microseconds remain special-cased while TIMESTAMP(p) uses a parametric signature
presto-common/src/main/java/com/facebook/presto/common/type/TimestampType.java
presto-common/src/main/java/com/facebook/presto/common/type/ShortTimestampType.java
presto-common/src/main/java/com/facebook/presto/common/type/LongTimestampType.java
presto-common/src/main/java/com/facebook/presto/common/type/LongTimestamp.java
presto-common/src/main/java/com/facebook/presto/common/type/StandardTypes.java
presto-common/src/main/java/com/facebook/presto/common/type/TypeSignature.java
Add TIMESTAMP(p) parametric type and microsecond-specific operators, and wire them into the type/metadata system.
  • Implement TimestampParametricType that parses an optional numeric precision parameter (0..12), defaults to 3, and returns ShortTimestampType or LongTimestampType as appropriate
  • Register TIMESTAMP both as a named type and as a parametric type, and re-register TIMESTAMP_MICROSECONDS as a named type to maintain NativeTypeManager compatibility
  • Introduce TimestampMicrosecondsOperators implementing the full comparable/orderable operator set plus hash/xxhash and IS DISTINCT FROM for TIMESTAMP_MICROSECONDS
  • Wire TimestampMicrosecondsOperators into BuiltInTypeAndFunctionNamespaceManager so that verifyComparableOrderableContract passes for TIMESTAMP_MICROSECONDS
presto-main-base/src/main/java/com/facebook/presto/type/TimestampParametricType.java
presto-main-base/src/main/java/com/facebook/presto/metadata/BuiltInTypeAndFunctionNamespaceManager.java
presto-main-base/src/main/java/com/facebook/presto/type/TimestampMicrosecondsOperators.java
Introduce Fixed12Block storage (12-byte entries) with builder and encoding to back long timestamps.
  • Add Fixed12Block implementing Block with 3-int-per-position storage, helper methods to encode/decode (long,int) pairs, fixed-size accounting, null handling, region/position copying, slice-based serialization, and content-based equals/hashCode over the visible range
  • Add Fixed12BlockBuilder implementing BlockBuilder with writeFixed12, null appends, Fixed12Block construction, copy/region operations, and serialization support
  • Add Fixed12BlockEncoding that reads/writes Fixed12Block instances using a bit-packed null map and (long,int) payload encoding, and register it in BlockEncodingManager
  • Refactor duplicated long reconstruction/encoding logic into Fixed12Block.getFixed12FirstUnchecked/getFixed12SecondUnchecked and share INT_LONGS_PER_ENTRY between block and builder
presto-common/src/main/java/com/facebook/presto/common/block/Fixed12Block.java
presto-common/src/main/java/com/facebook/presto/common/block/Fixed12BlockBuilder.java
presto-common/src/main/java/com/facebook/presto/common/block/Fixed12BlockEncoding.java
presto-common/src/main/java/com/facebook/presto/common/block/BlockEncodingManager.java
Add timestamp precision/rescaling/formatting utilities and fix negative-value behavior.
  • Introduce Timestamps utility class defining precision limits, time-unit constants, powers-of-ten table, and helpers such as isShortTimestamp, rescale, round, truncateToMicros, millisToMicros/microsToMillis, microsToNanos/nanosToMicros
  • Implement rescale using multiplication for upscaling and floorDiv for downscaling to ensure monotonic behavior for negative values and align with microsToMillis/nanosToMicros
  • Implement formatTimestamp that renders timestamps at arbitrary precision 0..12 using Instant/LocalDateTime plus explicit sub-nanosecond handling, and ensure precision 0 does not request a fractional field
  • Clarify roundDiv Javadoc to specify truncation-toward-zero semantics vs Math.floorDiv
presto-common/src/main/java/com/facebook/presto/common/type/Timestamps.java
presto-common/src/main/java/com/facebook/presto/common/type/TimestampType.java
Adjust connectors, metadata, and plan printer to work with the new timestamp abstraction and storage units.
  • Update IcebergPageSink.getIcebergValue to use TimestampType.getStorageUnit() instead of directly comparing precision constants when converting to microseconds
  • Change IcebergAbstractMetadata snapshot version resolution to compute millisUtc via TimestampType.getStorageUnit().toMillis
  • Update PartitionTable partition-value conversion to branch on TimestampType.getStorageUnit() rather than the old precision enum
  • Update IOPlanPrinter to format timestamp literals by converting via TimestampType.getStorageUnit().toMillis before printing
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergPageSink.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/PartitionTable.java
presto-main-base/src/main/java/com/facebook/presto/sql/planner/planPrinter/IOPlanPrinter.java
Add comprehensive tests for Fixed12Block, LongTimestamp, Timestamps utilities, LongTimestampType, and TimestampParametricType.
  • TestFixed12Block covers write/read, null handling, getRegion/copyPositions/copyRegion, getSingleValueBlock, encoding round-trip, size accounting, equals/hashCode over visible ranges, and Slice-based serialization including negative values
  • TestLongTimestamp verifies constructor validation, equals/hashCode/compareTo, exact toString formatting with 12 fractional digits across epoch/positive/negative/boundary values
  • TestTimestamps exercises constants, powers-of-ten, rescale (including negative/downscale cases), round/roundDiv, truncateToMicros, isShortTimestamp, formatTimestamp across precisions and signs, and unit-conversion helpers
  • TestLongTimestampType validates properties across precisions 7–12, read/write via Fixed12Block, equality/comparison/hash semantics, appendTo, builder creation, null handling, and type equality
  • TestTimestampParametricType verifies factory behavior (default and parametrized), precision bounds/validation, pre-defined constant instances, backward-compatible aliases, type signature strings, TimestampType equality semantics, and isShort/isLong behavior
presto-common/src/test/java/com/facebook/presto/common/block/TestFixed12Block.java
presto-common/src/test/java/com/facebook/presto/common/type/TestLongTimestamp.java
presto-common/src/test/java/com/facebook/presto/common/type/TestTimestamps.java
presto-main-base/src/test/java/com/facebook/presto/type/TestLongTimestampType.java
presto-main-base/src/test/java/com/facebook/presto/type/TestTimestampParametricType.java

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

@Joe-Abraham
Copy link
Copy Markdown
Owner

@copilot
Facing errors while running the test case

Error:  com.facebook.presto.nativetests.TestOptimizeMixedDistinctAggregations.testApproximateCountDistinct  Time elapsed: 0.313 s  <<< FAILURE!
java.lang.AssertionError: Execution of 'actual' query failed: SELECT approx_distinct(CAST(orderdate AS TIMESTAMP)) FROM orders
	at org.testng.Assert.fail(Assert.java:98)
	at com.facebook.presto.tests.QueryAssertions.assertQuery(QueryAssertions.java:213)
	at com.facebook.presto.tests.QueryAssertions.assertQuery(QueryAssertions.java:141)
	at com.facebook.presto.tests.AbstractTestQueryFramework.assertQuery(AbstractTestQueryFramework.java:184)
	at com.facebook.presto.nativetests.AbstractTestAggregationsNative.testApproximateCountDistinct(AbstractTestAggregationsNative.java:63)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:135)
	at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:673)
	at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:220)
	at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50)
	at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:945)
	at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:193)
	at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
	at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.RuntimeException: line 1:24: Unknown type: timestamp

the default precision should be 3 and make this change backward compatible

Copy link
Copy Markdown
Author

Copilot AI commented Apr 10, 2026

@copilot
Facing errors while running the test case

...

Fixed in b8ca248. The root cause was that NativeTypeManager filters supported types from getTypes() (named types only). When TIMESTAMP was changed from a named type to only a parametric type, it no longer appeared in getTypes(), causing "Unknown type: timestamp" errors.

The fix registers TIMESTAMP both as a named type (addType(TIMESTAMP)) for backward compatibility and as a parametric type (addParametricType(TimestampParametricType.TIMESTAMP)) for TIMESTAMP(p) precision support. The named type lookup (types.get()) is checked first, so bare "timestamp" resolves to TIMESTAMP(3) with default precision 3.

Copilot AI changed the title Port parametric TIMESTAMP(p) type infrastructure from Trino to PrestoDB Port parametric TIMESTAMP(p) type infrastructure and tests from Trino to PrestoDB Apr 10, 2026
@Joe-Abraham
Copy link
Copy Markdown
Owner

/sourcery-ai review

@Joe-Abraham
Copy link
Copy Markdown
Owner

@sourcery-ai summary

@Joe-Abraham
Copy link
Copy Markdown
Owner

@sourcery-ai review

Copy link
Copy Markdown

@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 - I've found 6 issues, and left some high level feedback:

  • The HASH_CODE operator for TIMESTAMP_MICROSECONDS uses AbstractLongType.hash while ShortTimestampType.hash(Block, ...) uses a different hash implementation, so the type-level hash and the scalar operator hash can diverge; consider wiring the operator to use the same hash function as ShortTimestampType (or aligning ShortTimestampType.hash with AbstractLongType.hash) to satisfy the comparable/orderable contract consistently.
  • In Timestamps.rescale(), the downscale path uses roundDiv which is defined as simple truncating division (value / divisor) instead of a true rounding or floorDiv; if negative timestamps or exact rounding semantics matter, it may be clearer either to rename this helper to reflect truncation or to use floorDiv for consistency with other conversion helpers like microsToMillis/nanosToMicros.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The HASH_CODE operator for TIMESTAMP_MICROSECONDS uses AbstractLongType.hash while ShortTimestampType.hash(Block, ...) uses a different hash implementation, so the type-level hash and the scalar operator hash can diverge; consider wiring the operator to use the same hash function as ShortTimestampType (or aligning ShortTimestampType.hash with AbstractLongType.hash) to satisfy the comparable/orderable contract consistently.
- In Timestamps.rescale(), the downscale path uses roundDiv which is defined as simple truncating division (value / divisor) instead of a true rounding or floorDiv; if negative timestamps or exact rounding semantics matter, it may be clearer either to rename this helper to reflect truncation or to use floorDiv for consistency with other conversion helpers like microsToMillis/nanosToMicros.

## Individual Comments

### Comment 1
<location path="presto-common/src/main/java/com/facebook/presto/common/type/Timestamps.java" line_range="77-86" />
<code_context>
+     * For example, rescaling 123456 from precision 6 (microseconds) to precision 3 (milliseconds)
+     * produces 123 (truncation). Rescaling 123 from precision 3 to precision 6 produces 123000.
+     */
+    public static long rescale(long value, int fromPrecision, int toPrecision)
+    {
+        if (fromPrecision == toPrecision) {
+            return value;
+        }
+        if (fromPrecision < toPrecision) {
+            // Scale up
+            int scaleFactor = toPrecision - fromPrecision;
+            return value * POWERS_OF_TEN[scaleFactor];
+        }
+        else {
+            // Scale down (truncation)
+            int scaleFactor = fromPrecision - toPrecision;
+            return roundDiv(value, POWERS_OF_TEN[scaleFactor]);
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Downscaling timestamps should likely use floor-based division for negative values instead of truncation toward zero.

Using `roundDiv` (which currently uses `/`) changes semantics for negative timestamps: Java integer division truncates toward zero, so rescaling `-1500` from precision 3 to 0 yields `-1` instead of `-2`, breaking monotonic, floor-like behavior across the epoch. Existing Presto rescale helpers use `floorDiv`/`floorMod` to keep ordering and arithmetic consistent for pre-epoch values. Please switch to `floorDiv` here (or reuse an existing helper) when `fromPrecision > toPrecision`.
</issue_to_address>

### Comment 2
<location path="presto-common/src/main/java/com/facebook/presto/common/block/Fixed12Block.java" line_range="365-390" />
<code_context>
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        Fixed12Block other = (Fixed12Block) obj;
+        return this.positionOffset == other.positionOffset &&
+                this.positionCount == other.positionCount &&
+                Arrays.equals(this.valueIsNull, other.valueIsNull) &&
+                Arrays.equals(this.values, other.values) &&
+                this.retainedSizeInBytes == other.retainedSizeInBytes;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash(positionOffset,
+                positionCount,
+                Arrays.hashCode(valueIsNull),
+                Arrays.hashCode(values),
+                retainedSizeInBytes);
+    }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Including retainedSizeInBytes and backing array identity in equals/hashCode makes Fixed12Block equality depend on layout, not just logical contents.

As implemented, two blocks with the same logical contents but different compaction/backing arrays or retained-size accounting will compare unequal and produce different hashes. If these methods are meant to reflect logical equality, they should ignore `retainedSizeInBytes` and compare `valueIsNull`/`values` only over the visible range (`positionOffset .. positionOffset + positionCount`). If they are not meant for logical equality (e.g., only for debugging), consider documenting that explicitly or reverting to identity-based equality to prevent misuse in sets/maps.

```suggestion
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Fixed12Block other = (Fixed12Block) obj;
        if (this.positionCount != other.positionCount) {
            return false;
        }

        int thisStart = this.positionOffset;
        int thisEnd = thisStart + this.positionCount;
        int otherStart = other.positionOffset;
        int otherEnd = otherStart + other.positionCount;

        if (!Arrays.equals(this.valueIsNull, thisStart, thisEnd,
                other.valueIsNull, otherStart, otherEnd)) {
            return false;
        }

        int thisValuesStart = this.positionOffset * INT_LONGS_PER_ENTRY;
        int thisValuesEnd = thisValuesStart + this.positionCount * INT_LONGS_PER_ENTRY;
        int otherValuesStart = other.positionOffset * INT_LONGS_PER_ENTRY;
        int otherValuesEnd = otherValuesStart + other.positionCount * INT_LONGS_PER_ENTRY;

        return Arrays.equals(this.values, thisValuesStart, thisValuesEnd,
                other.values, otherValuesStart, otherValuesEnd);
    }

    @Override
    public int hashCode()
    {
        int start = positionOffset;
        int end = start + positionCount;

        int valuesStart = positionOffset * INT_LONGS_PER_ENTRY;
        int valuesEnd = valuesStart + positionCount * INT_LONGS_PER_ENTRY;

        int result = positionCount;
        result = 31 * result + Arrays.hashCode(valueIsNull, start, end);
        result = 31 * result + Arrays.hashCode(values, valuesStart, valuesEnd);
        return result;
    }
```
</issue_to_address>

### Comment 3
<location path="presto-common/src/test/java/com/facebook/presto/common/block/TestFixed12Block.java" line_range="186" />
<code_context>
+    }
+
+    @Test
+    public void testWritePositionTo()
+    {
+        Fixed12BlockBuilder builder = new Fixed12BlockBuilder(null, 5);
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding tests for Slice-based serialization round-trip of Fixed12Block and Fixed12BlockBuilder

The Slice-based paths are currently untested:
- `Fixed12BlockBuilder.writePositionTo(int, SliceOutput)` / `readPositionFrom(SliceInput)`
- `Fixed12BlockEncoding.writeBlock` / `readBlock`

Please add a round-trip test that writes several values (including nulls) via `writePositionTo(SliceOutput)` and reads them back via `readPositionFrom(SliceInput)` or through `Fixed12BlockEncoding` + `BlockEncodingSerde` to validate the wire format and null handling.

Suggested implementation:

```java
        assertFalse(block.isNull(2));
    }

    @Test
    public void testSliceRoundTripWithWritePositionTo()
    {
        Fixed12BlockBuilder builder = new Fixed12BlockBuilder(null, 5);
        builder.writeFixed12(10L, 1);
        builder.appendNull();
        builder.writeFixed12(Long.MAX_VALUE, Integer.MIN_VALUE);
        builder.writeFixed12(0L, 0);
        builder.appendNull();

        Block original = builder.build();

        // serialize positions individually using writePositionTo/readPositionFrom
        DynamicSliceOutput sliceOutput = new DynamicSliceOutput(1024);
        for (int position = 0; position < original.getPositionCount(); position++) {
            ((Fixed12Block) original).writePositionTo(position, sliceOutput);
        }

        Slice slice = sliceOutput.slice();
        SliceInput sliceInput = slice.getInput();

        Fixed12BlockBuilder roundTripBuilder = new Fixed12BlockBuilder(null, original.getPositionCount());
        for (int i = 0; i < original.getPositionCount(); i++) {
            roundTripBuilder.readPositionFrom(sliceInput);
        }

        Block roundTripped = roundTripBuilder.build();
        assertBlockEquals(roundTripped, original);
    }

    @Test
    public void testSliceRoundTripWithBlockEncoding()
    {
        Fixed12BlockBuilder builder = new Fixed12BlockBuilder(null, 5);
        builder.writeFixed12(10L, 1);
        builder.appendNull();
        builder.writeFixed12(Long.MIN_VALUE, Integer.MAX_VALUE);
        builder.writeFixed12(0L, 0);
        builder.appendNull();

        Block original = builder.build();

        BlockEncodingSerde blockEncodingSerde = new BlockEncodingManager();
        SliceOutput sliceOutput = new DynamicSliceOutput(1024);
        blockEncodingSerde.writeBlock(sliceOutput, original);

        Slice slice = sliceOutput.slice();
        SliceInput sliceInput = slice.getInput();
        Block roundTripped = blockEncodingSerde.readBlock(sliceInput);

        assertBlockEquals(roundTripped, original);

```

To compile and run, the following may also be required depending on existing imports in `TestFixed12Block.java`:

1. Ensure these imports are present (or equivalent, matching your package structure):
   - `import com.facebook.presto.common.block.Block;`
   - `import com.facebook.presto.common.block.BlockEncodingManager;`
   - `import com.facebook.presto.common.block.BlockEncodingSerde;`
   - `import com.facebook.presto.common.block.Fixed12Block;`
   - `import com.facebook.presto.common.block.Fixed12BlockBuilder;`
   - `import io.airlift.slice.Slice;`
   - `import io.airlift.slice.SliceInput;`
   - `import io.airlift.slice.SliceOutput;`
   - `import io.airlift.slice.DynamicSliceOutput;`

2. Ensure there is a static import for `assertBlockEquals` if not already present:
   - `import static com.facebook.presto.common.block.BlockAssertions.assertBlockEquals;`

3. If your project uses a different concrete implementation of `BlockEncodingSerde` than `BlockEncodingManager`, adjust the instantiation accordingly.
</issue_to_address>

### Comment 4
<location path="presto-common/src/test/java/com/facebook/presto/common/type/TestTimestamps.java" line_range="132" />
<code_context>
+    }
+
+    @Test
+    public void testRound()
+    {
+        // At max precision, no rounding
</code_context>
<issue_to_address>
**suggestion (testing):** Extend timestamp rounding/rescaling tests to cover negative values and precision boundaries more thoroughly

Current tests only exercise positive values, and negative inputs near precision boundaries aren’t covered (aside from a small `rescale` case). Please add:
- A `testRoundNegative()` covering multiple precisions (e.g., `round(-123456789012L, 9)` and `round(-123456789012L, 0)`), and explicitly asserting the intended rounding policy for negatives (truncate toward zero vs. other behavior).
- Additional `rescale` tests for negative values at extreme `fromPrecision`/`toPrecision` pairs (0 and 12).

This will clarify the contract for negatives and protect against regressions in sign/rounding behavior.

Suggested implementation:

```java
    @Test
    public void testRound()
    {
        // At max precision, no rounding
        assertEquals(round(123456789012L, 12), 123456789012L);

        // Round to precision 9 (truncate last 3 digits of picos)
        assertEquals(round(123456789012L, 9), 123456789000L);

        // Round to precision 6
        assertEquals(round(123456789012L, 6), 123456000000L);

        // Round to precision 3
        assertEquals(round(123456789012L, 3), 123000000000L);

        // Round to precision 0 (truncate all fractional digits)
        assertEquals(round(123456789012L, 0), 0L);
    }

    @Test
    public void testRoundNegative()
    {
        // At max precision, negative values are preserved as-is
        assertEquals(round(-123456789012L, 12), -123456789012L);

        // Truncate toward zero for negative values at precision 9
        assertEquals(round(-123456789012L, 9), -123456789000L);

        // Truncate toward zero for negative values at precision 0
        assertEquals(round(-123456789012L, 0), 0L);

```

`.

Here are the concrete edits:

<file_operations>
<file_operation operation="edit" file_path="presto-common/src/test/java/com/facebook/presto/common/type/TestTimestamps.java">
<<<<<<< SEARCH
    @Test
    public void testRound()
    {
        // At max precision, no rounding
        assertEquals(round(123456789012L, 12), 123456789012L);

        // Round to precision 9 (truncate last 3 digits of picos)
        assertEquals(round(123456789012L, 9), 123456789000L);

        // Round to precision 6
        assertEquals(round(123456789012L, 6), 123456000000L);

        // Round to precision 3
        assertEquals(round(123456789012L, 3), 123000000000L);
=======
    @Test
    public void testRound()
    {
        // At max precision, no rounding
        assertEquals(round(123456789012L, 12), 123456789012L);

        // Round to precision 9 (truncate last 3 digits of picos)
        assertEquals(round(123456789012L, 9), 123456789000L);

        // Round to precision 6
        assertEquals(round(123456789012L, 6), 123456000000L);

        // Round to precision 3
        assertEquals(round(123456789012L, 3), 123000000000L);

        // Round to precision 0 (truncate all fractional digits)
        assertEquals(round(123456789012L, 0), 0L);
    }

    @Test
    public void testRoundNegative()
    {
        // At max precision, negative values are preserved as-is
        assertEquals(round(-123456789012L, 12), -123456789012L);

        // Truncate toward zero for negative values at precision 9
        assertEquals(round(-123456789012L, 9), -123456789000L);

        // Truncate toward zero for negative values at precision 0
        assertEquals(round(-123456789012L, 0), 0L);
>>>>>>> REPLACE
</file_operation>
</file_operations>

<additional_changes>
To complete your requested coverage for `rescale` with negative values at extreme precision pairs (0 and 12), you’ll need to extend the existing rescale tests (likely a `testRescale()` method) as follows:

1. Add assertions that mirror the existing positive rescale tests, but with negative inputs. For example (adjust values to match how the positive cases are structured in your file):
   - From highest to lowest precision, truncating toward zero:
     ```java
     assertEquals(rescale(-123456789012L, 12, 0), 0L);
     assertEquals(rescale(-123456789000L, 12, 0), -123L); // if this matches the positive test case structure
     ```
   - From lowest to highest precision (padding with zeros):
     ```java
     assertEquals(rescale(-123L, 0, 12), -123000000000000L);
     ```
2. Place these new negative-rescale assertions adjacent to the corresponding positive ones (same `fromPrecision`/`toPrecision` pairs) so the contract for sign handling and truncation policy is clear and easy to compare.
3. Ensure the expectations align with the actual implementation of `rescale` (truncate toward zero vs. another rounding rule); adjust the expected values above to match the existing positive tests’ behavior for the same precisions.

Because the `testRescale` implementation isn’t visible in the snippet, you’ll need to integrate these additional assertions into the correct test method(s) and potentially adjust the literal values so they are consistent with the existing positive test coverage pattern.
</issue_to_address>

### Comment 5
<location path="presto-main-base/src/test/java/com/facebook/presto/type/TestTimestampParametricType.java" line_range="49" />
<code_context>
+    }
+
+    @Test
+    public void testDefaultPrecision()
+    {
+        Type type = parametricType.createType(Collections.emptyList());
</code_context>
<issue_to_address>
**suggestion (testing):** Add assertions around TimestampType type signatures and alias identity to better lock in backward compatibility

These tests already cover precision mapping and the short/long split. To better protect backward compatibility, consider also asserting that:

- `TimestampType.TIMESTAMP.getTypeSignature().toString()` is exactly `"timestamp"` (and likewise for `TIMESTAMP_MICROSECONDS`), to lock in the special-casing in `TimestampType.getTypeSignature()`.
- `parametricType.createType(Collections.emptyList())` returns the same instance as `TimestampType.TIMESTAMP` (identity, not just equality), so future changes can’t silently start producing new instances.

This will more fully pin down the expected behavior of the parametric type vs. the predefined constants.

Suggested implementation:

```java
    @Test
    public void testDefaultPrecision()
    {
        Type type = parametricType.createType(Collections.emptyList());
        assertTrue(type instanceof TimestampType);
        assertEquals(((TimestampType) type).getPrecision(), 3);

        // Default parametric type should return the predefined constant instance
        assertSame(type, TimestampType.TIMESTAMP);

        // Lock in special-cased type signatures and aliases
        assertEquals(TimestampType.TIMESTAMP.getTypeSignature().toString(), "timestamp");
        assertEquals(TimestampType.TIMESTAMP_MICROSECONDS.getTypeSignature().toString(), "timestamp(6)");
    }

```

If `assertSame` or the static `TimestampType` constants are not yet statically imported or available, ensure:
1. `import static org.testng.Assert.assertSame;` is present at the top of the file.
2. `TimestampType` and `TimestampType.TIMESTAMP_MICROSECONDS` are imported or referenced with their fully qualified names.
</issue_to_address>

### Comment 6
<location path="presto-common/src/test/java/com/facebook/presto/common/type/TestLongTimestamp.java" line_range="126" />
<code_context>
+    }
+
+    @Test
+    public void testToString()
+    {
+        // LongTimestamp.toString() uses Timestamps.formatTimestamp(12, epochMicros, picosOfMicro)
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen LongTimestamp.toString tests by asserting the exact formatted representation and fractional length

Currently the test only checks that the output contains certain substrings. Since `toString` delegates to `Timestamps.formatTimestamp(12, ...)`, please also:

- Assert the exact full string for a few key cases (epoch, positive with non-zero `picosOfMicro`, negative), including 12 fractional digits.
- Assert that the fractional part is always length 12, matching the `MAX_PRECISION` contract.

This will help catch subtle formatting regressions (e.g., changes in fraction width or padding).

Suggested implementation:

```java
    @Test
    public void testToString()
    {
        // LongTimestamp.toString() uses Timestamps.formatTimestamp(12, epochMicros, picosOfMicro)

        // Epoch timestamp: 1970-01-01 00:00:00.000000000000
        LongTimestamp epoch = new LongTimestamp(0L, 0);
        String epochStr = epoch.toString();
        assertEquals(epochStr, "1970-01-01 00:00:00.000000000000");
        assertEquals(epochStr.substring(epochStr.indexOf('.') + 1).length(), 12, "Expected 12 fractional digits for epoch timestamp");

        // Positive timestamp with non-zero picosOfMicro:
        // 2020-01-01 00:00:00.000000123456 = 1577836800 seconds = 1577836800000000 micros, picosOfMicro = 123456
        LongTimestamp positive = new LongTimestamp(1577836800000000L, 123456);
        String positiveStr = positive.toString();
        assertEquals(positiveStr, "2020-01-01 00:00:00.000000123456");
        assertEquals(positiveStr.substring(positiveStr.indexOf('.') + 1).length(), 12, "Expected 12 fractional digits for positive timestamp");

        // Negative timestamp just before the epoch:
        // -1 micro = 1969-12-31 23:59:59.999999000000
        LongTimestamp negative = new LongTimestamp(-1L, 0);
        String negativeStr = negative.toString();
        assertEquals(negativeStr, "1969-12-31 23:59:59.999999000000");
        assertEquals(negativeStr.substring(negativeStr.indexOf('.') + 1).length(), 12, "Expected 12 fractional digits for negative timestamp");
    }

```

1. Ensure that `org.testng.Assert.assertEquals` is imported in this test file (it likely already is, since other tests typically use it). If it is not, add:
   `import static org.testng.Assert.assertEquals;`
2. The existing `testToStringWithPositiveTimestamp` can remain unchanged; the strengthened `testToString` now covers:
   - Exact full string representation for epoch, positive with non-zero `picosOfMicro`, and negative timestamps.
   - Verification that the fractional part is always 12 digits long, matching the `MAX_PRECISION` contract.
</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.

Comment on lines +77 to +86
public static long rescale(long value, int fromPrecision, int toPrecision)
{
if (fromPrecision == toPrecision) {
return value;
}
if (fromPrecision < toPrecision) {
// Scale up
int scaleFactor = toPrecision - fromPrecision;
return value * POWERS_OF_TEN[scaleFactor];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Downscaling timestamps should likely use floor-based division for negative values instead of truncation toward zero.

Using roundDiv (which currently uses /) changes semantics for negative timestamps: Java integer division truncates toward zero, so rescaling -1500 from precision 3 to 0 yields -1 instead of -2, breaking monotonic, floor-like behavior across the epoch. Existing Presto rescale helpers use floorDiv/floorMod to keep ordering and arithmetic consistent for pre-epoch values. Please switch to floorDiv here (or reuse an existing helper) when fromPrecision > toPrecision.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot fix

Comment on lines +365 to +390
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Fixed12Block other = (Fixed12Block) obj;
return this.positionOffset == other.positionOffset &&
this.positionCount == other.positionCount &&
Arrays.equals(this.valueIsNull, other.valueIsNull) &&
Arrays.equals(this.values, other.values) &&
this.retainedSizeInBytes == other.retainedSizeInBytes;
}

@Override
public int hashCode()
{
return Objects.hash(positionOffset,
positionCount,
Arrays.hashCode(valueIsNull),
Arrays.hashCode(values),
retainedSizeInBytes);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Including retainedSizeInBytes and backing array identity in equals/hashCode makes Fixed12Block equality depend on layout, not just logical contents.

As implemented, two blocks with the same logical contents but different compaction/backing arrays or retained-size accounting will compare unequal and produce different hashes. If these methods are meant to reflect logical equality, they should ignore retainedSizeInBytes and compare valueIsNull/values only over the visible range (positionOffset .. positionOffset + positionCount). If they are not meant for logical equality (e.g., only for debugging), consider documenting that explicitly or reverting to identity-based equality to prevent misuse in sets/maps.

Suggested change
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Fixed12Block other = (Fixed12Block) obj;
return this.positionOffset == other.positionOffset &&
this.positionCount == other.positionCount &&
Arrays.equals(this.valueIsNull, other.valueIsNull) &&
Arrays.equals(this.values, other.values) &&
this.retainedSizeInBytes == other.retainedSizeInBytes;
}
@Override
public int hashCode()
{
return Objects.hash(positionOffset,
positionCount,
Arrays.hashCode(valueIsNull),
Arrays.hashCode(values),
retainedSizeInBytes);
}
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Fixed12Block other = (Fixed12Block) obj;
if (this.positionCount != other.positionCount) {
return false;
}
int thisStart = this.positionOffset;
int thisEnd = thisStart + this.positionCount;
int otherStart = other.positionOffset;
int otherEnd = otherStart + other.positionCount;
if (!Arrays.equals(this.valueIsNull, thisStart, thisEnd,
other.valueIsNull, otherStart, otherEnd)) {
return false;
}
int thisValuesStart = this.positionOffset * INT_LONGS_PER_ENTRY;
int thisValuesEnd = thisValuesStart + this.positionCount * INT_LONGS_PER_ENTRY;
int otherValuesStart = other.positionOffset * INT_LONGS_PER_ENTRY;
int otherValuesEnd = otherValuesStart + other.positionCount * INT_LONGS_PER_ENTRY;
return Arrays.equals(this.values, thisValuesStart, thisValuesEnd,
other.values, otherValuesStart, otherValuesEnd);
}
@Override
public int hashCode()
{
int start = positionOffset;
int end = start + positionCount;
int valuesStart = positionOffset * INT_LONGS_PER_ENTRY;
int valuesEnd = valuesStart + positionCount * INT_LONGS_PER_ENTRY;
int result = positionCount;
result = 31 * result + Arrays.hashCode(valueIsNull, start, end);
result = 31 * result + Arrays.hashCode(values, valuesStart, valuesEnd);
return result;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot fix

}

@Test
public void testWritePositionTo()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding tests for Slice-based serialization round-trip of Fixed12Block and Fixed12BlockBuilder

The Slice-based paths are currently untested:

  • Fixed12BlockBuilder.writePositionTo(int, SliceOutput) / readPositionFrom(SliceInput)
  • Fixed12BlockEncoding.writeBlock / readBlock

Please add a round-trip test that writes several values (including nulls) via writePositionTo(SliceOutput) and reads them back via readPositionFrom(SliceInput) or through Fixed12BlockEncoding + BlockEncodingSerde to validate the wire format and null handling.

Suggested implementation:

        assertFalse(block.isNull(2));
    }

    @Test
    public void testSliceRoundTripWithWritePositionTo()
    {
        Fixed12BlockBuilder builder = new Fixed12BlockBuilder(null, 5);
        builder.writeFixed12(10L, 1);
        builder.appendNull();
        builder.writeFixed12(Long.MAX_VALUE, Integer.MIN_VALUE);
        builder.writeFixed12(0L, 0);
        builder.appendNull();

        Block original = builder.build();

        // serialize positions individually using writePositionTo/readPositionFrom
        DynamicSliceOutput sliceOutput = new DynamicSliceOutput(1024);
        for (int position = 0; position < original.getPositionCount(); position++) {
            ((Fixed12Block) original).writePositionTo(position, sliceOutput);
        }

        Slice slice = sliceOutput.slice();
        SliceInput sliceInput = slice.getInput();

        Fixed12BlockBuilder roundTripBuilder = new Fixed12BlockBuilder(null, original.getPositionCount());
        for (int i = 0; i < original.getPositionCount(); i++) {
            roundTripBuilder.readPositionFrom(sliceInput);
        }

        Block roundTripped = roundTripBuilder.build();
        assertBlockEquals(roundTripped, original);
    }

    @Test
    public void testSliceRoundTripWithBlockEncoding()
    {
        Fixed12BlockBuilder builder = new Fixed12BlockBuilder(null, 5);
        builder.writeFixed12(10L, 1);
        builder.appendNull();
        builder.writeFixed12(Long.MIN_VALUE, Integer.MAX_VALUE);
        builder.writeFixed12(0L, 0);
        builder.appendNull();

        Block original = builder.build();

        BlockEncodingSerde blockEncodingSerde = new BlockEncodingManager();
        SliceOutput sliceOutput = new DynamicSliceOutput(1024);
        blockEncodingSerde.writeBlock(sliceOutput, original);

        Slice slice = sliceOutput.slice();
        SliceInput sliceInput = slice.getInput();
        Block roundTripped = blockEncodingSerde.readBlock(sliceInput);

        assertBlockEquals(roundTripped, original);

To compile and run, the following may also be required depending on existing imports in TestFixed12Block.java:

  1. Ensure these imports are present (or equivalent, matching your package structure):

    • import com.facebook.presto.common.block.Block;
    • import com.facebook.presto.common.block.BlockEncodingManager;
    • import com.facebook.presto.common.block.BlockEncodingSerde;
    • import com.facebook.presto.common.block.Fixed12Block;
    • import com.facebook.presto.common.block.Fixed12BlockBuilder;
    • import io.airlift.slice.Slice;
    • import io.airlift.slice.SliceInput;
    • import io.airlift.slice.SliceOutput;
    • import io.airlift.slice.DynamicSliceOutput;
  2. Ensure there is a static import for assertBlockEquals if not already present:

    • import static com.facebook.presto.common.block.BlockAssertions.assertBlockEquals;
  3. If your project uses a different concrete implementation of BlockEncodingSerde than BlockEncodingManager, adjust the instantiation accordingly.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot fix

}

@Test
public void testRound()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Extend timestamp rounding/rescaling tests to cover negative values and precision boundaries more thoroughly

Current tests only exercise positive values, and negative inputs near precision boundaries aren’t covered (aside from a small rescale case). Please add:

  • A testRoundNegative() covering multiple precisions (e.g., round(-123456789012L, 9) and round(-123456789012L, 0)), and explicitly asserting the intended rounding policy for negatives (truncate toward zero vs. other behavior).
  • Additional rescale tests for negative values at extreme fromPrecision/toPrecision pairs (0 and 12).

This will clarify the contract for negatives and protect against regressions in sign/rounding behavior.

Suggested implementation:

    @Test
    public void testRound()
    {
        // At max precision, no rounding
        assertEquals(round(123456789012L, 12), 123456789012L);

        // Round to precision 9 (truncate last 3 digits of picos)
        assertEquals(round(123456789012L, 9), 123456789000L);

        // Round to precision 6
        assertEquals(round(123456789012L, 6), 123456000000L);

        // Round to precision 3
        assertEquals(round(123456789012L, 3), 123000000000L);

        // Round to precision 0 (truncate all fractional digits)
        assertEquals(round(123456789012L, 0), 0L);
    }

    @Test
    public void testRoundNegative()
    {
        // At max precision, negative values are preserved as-is
        assertEquals(round(-123456789012L, 12), -123456789012L);

        // Truncate toward zero for negative values at precision 9
        assertEquals(round(-123456789012L, 9), -123456789000L);

        // Truncate toward zero for negative values at precision 0
        assertEquals(round(-123456789012L, 0), 0L);

`.

Here are the concrete edits:

<file_operations>
<file_operation operation="edit" file_path="presto-common/src/test/java/com/facebook/presto/common/type/TestTimestamps.java">
<<<<<<< SEARCH
@test
public void testRound()
{
// At max precision, no rounding
assertEquals(round(123456789012L, 12), 123456789012L);

    // Round to precision 9 (truncate last 3 digits of picos)
    assertEquals(round(123456789012L, 9), 123456789000L);

    // Round to precision 6
    assertEquals(round(123456789012L, 6), 123456000000L);

    // Round to precision 3
    assertEquals(round(123456789012L, 3), 123000000000L);

=======
@test
public void testRound()
{
// At max precision, no rounding
assertEquals(round(123456789012L, 12), 123456789012L);

    // Round to precision 9 (truncate last 3 digits of picos)
    assertEquals(round(123456789012L, 9), 123456789000L);

    // Round to precision 6
    assertEquals(round(123456789012L, 6), 123456000000L);

    // Round to precision 3
    assertEquals(round(123456789012L, 3), 123000000000L);

    // Round to precision 0 (truncate all fractional digits)
    assertEquals(round(123456789012L, 0), 0L);
}

@Test
public void testRoundNegative()
{
    // At max precision, negative values are preserved as-is
    assertEquals(round(-123456789012L, 12), -123456789012L);

    // Truncate toward zero for negative values at precision 9
    assertEquals(round(-123456789012L, 9), -123456789000L);

    // Truncate toward zero for negative values at precision 0
    assertEquals(round(-123456789012L, 0), 0L);

REPLACE
</file_operation>
</file_operations>

<additional_changes>
To complete your requested coverage for rescale with negative values at extreme precision pairs (0 and 12), you’ll need to extend the existing rescale tests (likely a testRescale() method) as follows:

  1. Add assertions that mirror the existing positive rescale tests, but with negative inputs. For example (adjust values to match how the positive cases are structured in your file):
    • From highest to lowest precision, truncating toward zero:
      assertEquals(rescale(-123456789012L, 12, 0), 0L);
      assertEquals(rescale(-123456789000L, 12, 0), -123L); // if this matches the positive test case structure
    • From lowest to highest precision (padding with zeros):
      assertEquals(rescale(-123L, 0, 12), -123000000000000L);
  2. Place these new negative-rescale assertions adjacent to the corresponding positive ones (same fromPrecision/toPrecision pairs) so the contract for sign handling and truncation policy is clear and easy to compare.
  3. Ensure the expectations align with the actual implementation of rescale (truncate toward zero vs. another rounding rule); adjust the expected values above to match the existing positive tests’ behavior for the same precisions.

Because the testRescale implementation isn’t visible in the snippet, you’ll need to integrate these additional assertions into the correct test method(s) and potentially adjust the literal values so they are consistent with the existing positive test coverage pattern.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot fix

}

@Test
public void testDefaultPrecision()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add assertions around TimestampType type signatures and alias identity to better lock in backward compatibility

These tests already cover precision mapping and the short/long split. To better protect backward compatibility, consider also asserting that:

  • TimestampType.TIMESTAMP.getTypeSignature().toString() is exactly "timestamp" (and likewise for TIMESTAMP_MICROSECONDS), to lock in the special-casing in TimestampType.getTypeSignature().
  • parametricType.createType(Collections.emptyList()) returns the same instance as TimestampType.TIMESTAMP (identity, not just equality), so future changes can’t silently start producing new instances.

This will more fully pin down the expected behavior of the parametric type vs. the predefined constants.

Suggested implementation:

    @Test
    public void testDefaultPrecision()
    {
        Type type = parametricType.createType(Collections.emptyList());
        assertTrue(type instanceof TimestampType);
        assertEquals(((TimestampType) type).getPrecision(), 3);

        // Default parametric type should return the predefined constant instance
        assertSame(type, TimestampType.TIMESTAMP);

        // Lock in special-cased type signatures and aliases
        assertEquals(TimestampType.TIMESTAMP.getTypeSignature().toString(), "timestamp");
        assertEquals(TimestampType.TIMESTAMP_MICROSECONDS.getTypeSignature().toString(), "timestamp(6)");
    }

If assertSame or the static TimestampType constants are not yet statically imported or available, ensure:

  1. import static org.testng.Assert.assertSame; is present at the top of the file.
  2. TimestampType and TimestampType.TIMESTAMP_MICROSECONDS are imported or referenced with their fully qualified names.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot fix

}

@Test
public void testToString()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Strengthen LongTimestamp.toString tests by asserting the exact formatted representation and fractional length

Currently the test only checks that the output contains certain substrings. Since toString delegates to Timestamps.formatTimestamp(12, ...), please also:

  • Assert the exact full string for a few key cases (epoch, positive with non-zero picosOfMicro, negative), including 12 fractional digits.
  • Assert that the fractional part is always length 12, matching the MAX_PRECISION contract.

This will help catch subtle formatting regressions (e.g., changes in fraction width or padding).

Suggested implementation:

    @Test
    public void testToString()
    {
        // LongTimestamp.toString() uses Timestamps.formatTimestamp(12, epochMicros, picosOfMicro)

        // Epoch timestamp: 1970-01-01 00:00:00.000000000000
        LongTimestamp epoch = new LongTimestamp(0L, 0);
        String epochStr = epoch.toString();
        assertEquals(epochStr, "1970-01-01 00:00:00.000000000000");
        assertEquals(epochStr.substring(epochStr.indexOf('.') + 1).length(), 12, "Expected 12 fractional digits for epoch timestamp");

        // Positive timestamp with non-zero picosOfMicro:
        // 2020-01-01 00:00:00.000000123456 = 1577836800 seconds = 1577836800000000 micros, picosOfMicro = 123456
        LongTimestamp positive = new LongTimestamp(1577836800000000L, 123456);
        String positiveStr = positive.toString();
        assertEquals(positiveStr, "2020-01-01 00:00:00.000000123456");
        assertEquals(positiveStr.substring(positiveStr.indexOf('.') + 1).length(), 12, "Expected 12 fractional digits for positive timestamp");

        // Negative timestamp just before the epoch:
        // -1 micro = 1969-12-31 23:59:59.999999000000
        LongTimestamp negative = new LongTimestamp(-1L, 0);
        String negativeStr = negative.toString();
        assertEquals(negativeStr, "1969-12-31 23:59:59.999999000000");
        assertEquals(negativeStr.substring(negativeStr.indexOf('.') + 1).length(), 12, "Expected 12 fractional digits for negative timestamp");
    }
  1. Ensure that org.testng.Assert.assertEquals is imported in this test file (it likely already is, since other tests typically use it). If it is not, add:
    import static org.testng.Assert.assertEquals;
  2. The existing testToStringWithPositiveTimestamp can remain unchanged; the strengthened testToString now covers:
    • Exact full string representation for epoch, positive with non-zero picosOfMicro, and negative timestamps.
    • Verification that the fractional part is always 12 digits long, matching the MAX_PRECISION contract.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot fix

@Joe-Abraham
Copy link
Copy Markdown
Owner

@copilot fix the review points

Copy link
Copy Markdown
Author

Copilot AI commented Apr 13, 2026

@copilot fix the review points

Fixed in 0d22274. Addressed the following review points:

  1. Eliminated duplicated long reconstruction logic — The (values[baseIndex] & 0xFFFFFFFFL) | ((long) values[baseIndex + 1] << 32) pattern was inlined 8 times across Fixed12Block and Fixed12BlockBuilder. Extracted into getFixed12FirstUnchecked(int[], int) and getFixed12SecondUnchecked(int[], int) static helpers in Fixed12Block, now used by all call sites.

  2. Shared INT_LONGS_PER_ENTRY constant — Was duplicated in both Fixed12Block and Fixed12BlockBuilder. Now defined once in Fixed12Block as package-private, imported by Fixed12BlockBuilder.

  3. Removed redundant encodeFirstAsLong — Was doing the same thing as getFixed12FirstUnchecked. Replaced its one usage in Fixed12BlockBuilder.getLong() with the canonical helper.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/Joe-Abraham/presto/pulls/9/reviews
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 13, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link
Copy Markdown
Author

Copilot AI commented Apr 13, 2026

@copilot fix the review points

All 6 Sourcery AI review points addressed in 3e2c869 and 841e8f2:

  1. Timestamps.rescale() uses floorDiv — Switched from truncation division to floorDiv for correct negative timestamp behavior (e.g., -1500 from precision 3→0 now yields -2 instead of -1). Consistent with microsToMillis/nanosToMicros.

  2. Fixed12Block.equals()/hashCode() now compares logical contents — Only compares values and valueIsNull over the visible range (positionOffset..positionOffset+positionCount), ignoring retainedSizeInBytes and backing array layout.

  3. Slice-based round-trip tests added — Two new tests in TestFixed12Block: testSliceRoundTripWithWritePositionTo and testSliceRoundTripNegativeValues covering writePositionTo(SliceOutput)/readPositionFrom(SliceInput) with nulls, boundary values, and negative values.

  4. Negative rounding/rescaling tests addedtestRoundNegative() and testRescaleNegativeValues() in TestTimestamps covering extreme precision pairs and negative inputs.

  5. Type signature and identity assertionstestDefaultPrecision now asserts assertSame(type, TimestampType.TIMESTAMP) and a new testTypeSignatures locks in the special-cased signatures.

  6. Exact toString assertionsTestLongTimestamp now asserts exact formatted strings and verifies 12 fractional digits for epoch, positive, and negative timestamps.

Also clarified roundDiv Javadoc to document truncation-toward-zero behavior vs floorDiv.

@Joe-Abraham
Copy link
Copy Markdown
Owner

@sourcery-ai review

@Joe-Abraham
Copy link
Copy Markdown
Owner

@sourcery-ai title

@sourcery-ai sourcery-ai Bot changed the title Port parametric TIMESTAMP(p) type infrastructure and tests from Trino to PrestoDB Add parametric TIMESTAMP(p) support with long timestamps Apr 13, 2026
Copy link
Copy Markdown

@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 - I've left some high level feedback:

  • The new TimestampType.getStorageUnit() throws for long (precision > 6) timestamps, but Iceberg code paths (e.g., IcebergPageSink.getIcebergValue, adjustTimestampForPartitionTransform, IcebergAbstractMetadata, PartitionTable, IOPlanPrinter) now unconditionally call getStorageUnit() for any TimestampType; consider either guarding with isShort()/isLong() or enforcing that only short timestamps reach these code paths to avoid unexpected UnsupportedOperationException at runtime.
  • LongTimestamp.toString() always formats with 12 fractional digits regardless of the declared precision of the associated LongTimestampType; if lower-precision long timestamps (e.g., precision 7–11) are exposed to users, you may want to consider aligning the string formatting with the type’s precision rather than hard-coding 12 digits.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new TimestampType.getStorageUnit() throws for long (precision > 6) timestamps, but Iceberg code paths (e.g., IcebergPageSink.getIcebergValue, adjustTimestampForPartitionTransform, IcebergAbstractMetadata, PartitionTable, IOPlanPrinter) now unconditionally call getStorageUnit() for any TimestampType; consider either guarding with isShort()/isLong() or enforcing that only short timestamps reach these code paths to avoid unexpected UnsupportedOperationException at runtime.
- LongTimestamp.toString() always formats with 12 fractional digits regardless of the declared precision of the associated LongTimestampType; if lower-precision long timestamps (e.g., precision 7–11) are exposed to users, you may want to consider aligning the string formatting with the type’s precision rather than hard-coding 12 digits.

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.

@Joe-Abraham
Copy link
Copy Markdown
Owner

@copilot i want to test the changes manually, write sample queries and which connector should i use hive/iceberg

Copy link
Copy Markdown
Author

Copilot AI commented Apr 13, 2026

@copilot i want to test the changes manually, write sample queries and which connector should i use hive/iceberg

Use Hive for basic TIMESTAMP (millisecond) testing and Iceberg for microsecond precision testing. Here are sample queries for both:

Hive Connector (default TIMESTAMP = millisecond precision)

-- 1. Verify bare TIMESTAMP resolves to TIMESTAMP(3) with default precision
SELECT CAST(orderdate AS TIMESTAMP) FROM orders LIMIT 5;

-- 2. Verify approx_distinct works with TIMESTAMP (the query that was failing)
SELECT approx_distinct(CAST(orderdate AS TIMESTAMP)) FROM orders;

-- 3. Check typeof() to confirm default precision
SELECT typeof(CAST(orderdate AS TIMESTAMP));
-- Expected: timestamp

-- 4. Explicit TIMESTAMP(p) precision syntax
SELECT CAST(orderdate AS TIMESTAMP(0)) FROM orders LIMIT 5;
SELECT CAST(orderdate AS TIMESTAMP(3)) FROM orders LIMIT 5;
SELECT CAST(orderdate AS TIMESTAMP(6)) FROM orders LIMIT 5;

-- 5. Timestamp literals (millisecond)
SELECT TIMESTAMP '2024-01-15 10:30:45.123';

-- 6. Timestamp literals (microsecond - triggers TIMESTAMP_MICROSECONDS)
SELECT TIMESTAMP '2024-01-15 10:30:45.123456';

-- 7. Comparisons and ordering
SELECT CAST(orderdate AS TIMESTAMP) AS ts FROM orders ORDER BY ts LIMIT 10;

-- 8. Aggregations with TIMESTAMP
SELECT COUNT(DISTINCT CAST(orderdate AS TIMESTAMP)) FROM orders;
SELECT MIN(CAST(orderdate AS TIMESTAMP)), MAX(CAST(orderdate AS TIMESTAMP)) FROM orders;

Iceberg Connector (native microsecond precision)

-- 9. Create an Iceberg table with timestamp column (stores as µs internally)
CREATE TABLE iceberg_ts_test (id INTEGER, ts TIMESTAMP) WITH (format = 'PARQUET');

-- 10. Insert and query
INSERT INTO iceberg_ts_test VALUES (1, TIMESTAMP '2024-06-15 12:30:45.123');
INSERT INTO iceberg_ts_test VALUES (2, TIMESTAMP '2024-06-15 12:30:45.123456');
SELECT * FROM iceberg_ts_test;

-- 11. Verify type resolution
SELECT typeof(ts) FROM iceberg_ts_test LIMIT 1;
-- Expected: timestamp microseconds (Iceberg maps to TIMESTAMP_MICROSECONDS)

-- Cleanup
DROP TABLE iceberg_ts_test;

Quick Smoke Test (TPC-H with Hive)

The fastest way to validate backward compatibility is the TPC-H orders table:

-- This is the exact query that was previously failing
SELECT approx_distinct(CAST(orderdate AS TIMESTAMP)) FROM orders;

-- Additional backward-compat checks
SELECT CAST(orderdate AS TIMESTAMP) FROM orders WHERE orderkey = 1;
SELECT COUNT(*) FROM orders WHERE CAST(orderdate AS TIMESTAMP) > TIMESTAMP '1995-01-01 00:00:00';

Start with the Hive/TPC-H queries (1–8) to verify backward compatibility, then use Iceberg (9–11) for microsecond precision validation.

@Joe-Abraham
Copy link
Copy Markdown
Owner

@copilot rebase with latest master

@Joe-Abraham
Copy link
Copy Markdown
Owner

@copilot the below test in failing after the change

java.lang.AssertionError: Execution of 'actual' query failed: SELECT array_top_n(ARRAY[orderkey], 25, (x, y) -> if (x < y, 1, if (x > y, -1, 0))) from orders

at org.testng.Assert.fail(Assert.java:98)
at com.facebook.presto.tests.QueryAssertions.assertQuery(QueryAssertions.java:213)
at com.facebook.presto.tests.QueryAssertions.assertQuery(QueryAssertions.java:141)
at com.facebook.presto.tests.AbstractTestQueryFramework.assertQuery(AbstractTestQueryFramework.java:179)
at com.facebook.presto.tests.AbstractTestQueryFramework.assertQuery(AbstractTestQueryFramework.java:174)
at com.facebook.presto.sidecar.TestNativeSidecarPlugin.testNonOverriddenInlinedSqlInvokedFunctionsWhenConfigEnabled(TestNativeSidecarPlugin.java:621)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

Copy link
Copy Markdown
Author

Copilot AI commented Apr 24, 2026

@copilot the below test in failing after the change

java.lang.AssertionError: Execution of 'actual' query failed: SELECT array_top_n(ARRAY[orderkey], 25, (x, y) -> if (x < y, 1, if (x > y, -1, 0))...

This test failure is not caused by our TIMESTAMP changes. Here's the root cause:

The test testNonOverriddenInlinedSqlInvokedFunctionsWhenConfigEnabled was updated in master commit 4b775d743e ("Advance Velox prestodb#27503") from assertQuerySucceeds to assertQuery. The same commit:

  1. Updated the required Velox submodule from 0c6b89d6948e7be0
  2. Changed NativeArraySqlFunctions.arrayTopNComparator parameter from function(T, T, bigint)function(T, T, int)

The test now requires the newer Velox binary (948e7be0) that supports array_top_n with an INTEGER-returning comparator. Our TIMESTAMP changes only affect TIMESTAMP type resolution and have no impact on BIGINT/INTEGER/ARRAY types used by array_top_n.

TestNativeSidecarPlugin is also excluded from all standard CI runs via -P skip-native-sidecar-tests, so it won't appear in PR CI results.

To fix: rebuild your Velox binary from presto-native-execution/velox submodule at commit 948e7be0 (or pull the pre-built binary for that version). Once the Velox binary is updated, this test should pass.

Copilot AI and others added 4 commits May 4, 2026 09:44
Create TimestampParametricOperators.java implementing comparison, hash,
and equality operators for the parametric timestamp(p) type covering
all precisions:
- Short timestamps (p=0..6, Java type = long): uses direct long comparison
- Long timestamps (p=7..12, Java type = LongTimestamp): uses
  LongTimestamp.compareTo() and reads epochMicros/picosOfMicro from blocks

The PolymorphicScalarFunction machinery automatically dispatches to the
correct method variant based on the resolved Java type of the argument.

Operators implemented: EQUAL, NOT_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL,
GREATER_THAN, GREATER_THAN_OR_EQUAL, BETWEEN, HASH_CODE, XX_HASH_64,
IS_DISTINCT_FROM, INDETERMINATE.

Register the new operators in BuiltInTypeAndFunctionNamespaceManager
alongside the existing TimestampOperators (precision 3) and
TimestampMicrosecondsOperators (precision 6) which are kept for
backward compatibility with named types.

Also add ClientCapabilities enum with PARAMETRIC_DATETIME capability
constant and X-Presto-Client-Capabilities header to PrestoHeaders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Joe-Abraham <53977252+Joe-Abraham@users.noreply.github.com>
- Add LongTimestampWithTimeZone value class for sub-millisecond precision (4-12)
  - Stores epoch millis + packed fraction (picosOfMilli << 12 | timeZoneKey)
  - Uses Fixed12Block for storage (first 8 bytes = epoch millis, last 4 = packed)
  - Implements Comparable<LongTimestampWithTimeZone>
- Rewrite TimestampWithTimeZoneType as abstract parametric class (precision 0-12)
  - Short precision (0-3): stored as packed long (existing DateTimeEncoding format)
  - Long precision (4-12): stored as Fixed12Block
  - Preserves TIMESTAMP_WITH_TIME_ZONE = createTimestampWithTimeZoneType(3) for
    backward compatibility
- Add ShortTimestampWithTimeZoneType (precision 0-3): packed long storage
- Add LongTimestampWithTimeZoneType (precision 4-12): Fixed12Block storage
- Add TimestampWithTimeZoneParametricType for TIMESTAMP WITH TIME ZONE(p) parsing
- Register TimestampWithTimeZoneParametricType in BuiltInTypeAndFunctionNamespaceManager
- Add TIMESTAMP_WITH_TIME_ZONE to StandardTypes.PARAMETRIC_TYPES
- Fix pre-existing checkstyle import order violation (DecimalSaturatedFloorCasts and
  TimestampParametricOperators were mis-ordered in BuiltInTypeAndFunctionNamespaceManager)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Joe-Abraham <53977252+Joe-Abraham@users.noreply.github.com>
The Fixed12Block packing scheme uses 20 bits for sub-millisecond precision
(32 bits minus 12 bits for the timezone key), which supports values up to
1,048,575 — only sufficient for nanoseconds within a millisecond (0 to 999,999),
not picoseconds. Rename the field and methods to accurately reflect this and
remove the divide-by-1000 in toString().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Joe-Abraham <53977252+Joe-Abraham@users.noreply.github.com>
…e infrastructure

Agent-Logs-Url: https://github.com/Joe-Abraham/presto/sessions/5e3e7a97-5d3f-451d-be3f-ed2f5b49d8e1

Co-authored-by: Joe-Abraham <53977252+Joe-Abraham@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants