Skip to content

core: ExpressionCreator.precisionTimestamp(LocalDateTime) wraps a date outside the nanosecond range into a different instant #1337

Description

@nielspardon

Description

ExpressionCreator.precisionTimestamp(boolean, LocalDateTime) builds a nanosecond precision_timestamp with arithmetic that does not report an out-of-range value, so a LocalDateTime outside what 64-bit nanoseconds can hold becomes a literal for a different instant:

public static Expression.PrecisionTimestampLiteral precisionTimestamp(
boolean nullable, LocalDateTime value) {
long epochNano =
TimeUnit.SECONDS.toNanos(value.toEpochSecond(ZoneOffset.UTC)) + value.getNano();
return precisionTimestamp(nullable, epochNano, 9);
}

TimeUnit.SECONDS.toNanos saturates at Long.MIN_VALUE / Long.MAX_VALUE instead of throwing, and the + value.getNano() after it then wraps past the saturated value. A precision_timestamp<9> covers 1677-09-21 00:12:43.145224192 to 2262-04-11 23:47:16.854775807 (type_classes.md: an int64 count of 10^-P seconds since the epoch), while a LocalDateTime reaches years ±999999999, so any date outside that window is affected. The resulting literal round-trips through proto and isthmus unchanged, so nothing downstream notices.

The lowest instant in range is wrong as well, even though it fits: its floored seconds overflow on their own, the multiply saturates, and the sub-second part is then added on top of the saturated value.

Reproduction

long v = ExpressionCreator.precisionTimestamp(false, in).value();
// read back as Instant.ofEpochSecond(floorDiv(v, 1e9), floorMod(v, 1e9)) in UTC
input LocalDateTime value reads back as
2262-04-11T23:47:16.854775807 9223372036854775807 2262-04-11T23:47:16.854775807 (correct)
2262-04-11T23:47:16.854775808 -9223372036854775808 1677-09-21T00:12:43.145224192
2263-01-01T00:00 9223372036854775807 2262-04-11T23:47:16.854775807
9999-12-31T23:59:59.123456789 -9223372036731319020 1677-09-21T00:12:43.268680980
1600-01-01T00:00:00.123456789 -9223372036731319019 1677-09-21T00:12:43.268680981
1677-09-21T00:12:43.145224192 (fits: exactly Long.MIN_VALUE ns) -9223372036709551616 1677-09-21T00:12:43.290448384

Measured on main at 4bb02974.

Suggestion

Compute the count with Math.multiplyExact / Math.addExact and throw IllegalArgumentException naming the value when it does not fit, borrowing one second for a negative value with a fractional part so that the first second of the range still converts. #1318 does exactly this for isthmus' Calcite→Substrait timestamp literals (LiteralConverter.epochUnits), so the two entry points would agree on what they refuse. This is the only factory in ExpressionCreator built this way; precisionTime(boolean, LocalTime) is bounded by toNanoOfDay() and is safe.

🤖 Generated with AI

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions