Skip to content

fix(isthmus)!: convert precision_time and precision_timestamp up to nanoseconds - #1318

Merged
nielspardon merged 5 commits into
substrait-io:mainfrom
alexandrefimov:issue-995-precision-ceiling
Sep 29, 2026
Merged

nielspardon merged 5 commits into
substrait-io:mainfrom
alexandrefimov:issue-995-precision-ceiling

Conversation

@alexandrefimov

@alexandrefimov alexandrefimov commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

SubstraitTypeSystem capped TIME, TIMESTAMP and TIMESTAMP_WITH_LOCAL_TIME_ZONE at microseconds, so a nanosecond precision_timestamp was refused although Calcite 1.42.0 builds such a type the moment the ceiling allows it. Raise it to nanoseconds; 10 to 12, which Calcite clamps rather than reports, stay refused and name the bound.

Converting back, a Substrait temporal value is a 64-bit count of its own unit, so nanoseconds reach only 2262 where a TimestampString reaches 9999. A timestamp past that is now reported instead of wrapping into a different instant. In the other direction, a precision_timestamp outside years 0000 to 9999 is reported too; Calcite would render its year modulo 10000.

On a TIMESTAMP(9) column, ts > TIMESTAMP '9999-12-31 23:59:59' no longer converts, because the literal is widened to nanoseconds. DuckDB and Arrow refuse it too.

Summary by CodeRabbit

  • New Features
    • TIME, TIMESTAMP, and timestamp-with-local-time-zone values now support precision up to nanoseconds. Time-with-local-time-zone values retain their existing precision limit.
    • Date subtraction with a day-time interval can produce timestamps with precision up to nanoseconds.
  • Bug Fixes
    • Timestamp conversions now preserve fractional units accurately, including for pre-epoch values, and report errors for values outside the supported range rather than wrapping or changing the represented date.
    • Nanosecond-precision time and timestamp values now round-trip accurately between Substrait and Calcite. Timestamp precisions above 9 are rejected.

Closes #995
Closes #1298

BREAKING CHANGE: precision_time, precision_timestamp and precision_timestamp_tz convert up to nanoseconds, so plans that were refused now convert and TIME(9) or TIMESTAMP(9) can appear where the type system allowed at most 6. A TIMESTAMP(9) column compared with a literal outside 1677-2262 no longer converts. A precision_timestamp outside years 0000-9999 no longer converts to Calcite.

…anoseconds

SubstraitTypeSystem capped TIME, TIMESTAMP and TIMESTAMP_WITH_LOCAL_TIME_ZONE
at microseconds, so a plan carrying a nanosecond precision_timestamp was
refused although Calcite 1.42 builds such a type the moment the ceiling
allows it, and TimestampString carries the digits.

Raise the ceiling to nanoseconds. Precisions 10 to 12, which Substrait
allows and Calcite clamps rather than reports, stay refused, naming the
bound. Converting back, a value is a 64-bit count of its own unit, so
nanoseconds reach only 2262 where a TimestampString reaches 9999: a
timestamp outside that range is now reported instead of wrapping.

Closes substrait-io#995
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 20 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f0f2cf78-5e85-44ef-91ae-00c2fffc3054

📥 Commits

Reviewing files that changed from the base of the PR and between 3f88379 and b5ef9b1.

📒 Files selected for processing (1)
  • isthmus/src/main/java/io/substrait/isthmus/expression/ExpressionRexConverter.java

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: eb74b504-e31b-4b0e-a3ac-3b52402fe168

📥 Commits

Reviewing files that changed from the base of the PR and between 6786922 and 3f88379.

📒 Files selected for processing (4)
  • isthmus/src/main/java/io/substrait/isthmus/SubstraitTypeSystem.java
  • isthmus/src/main/java/io/substrait/isthmus/expression/ExpressionRexConverter.java
  • isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java
  • isthmus/src/test/java/io/substrait/isthmus/CalciteLiteralTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • isthmus/src/main/java/io/substrait/isthmus/SubstraitTypeSystem.java
  • isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

Isthmus now supports precision 9 for TIME, TIMESTAMP, and TIMESTAMP_WITH_LOCAL_TIME_ZONE. Timestamp literal conversion checks for arithmetic overflow and Calcite date-range violations.

Changes

Precision conversion

Layer / File(s) Summary
Precision limits and validation
isthmus/src/main/java/io/substrait/isthmus/SubstraitTypeSystem.java, isthmus/src/main/java/io/substrait/isthmus/expression/FunctionMappings.java, isthmus/src/test/java/io/substrait/isthmus/*Test.java
The maximum precision for TIME, TIMESTAMP, and TIMESTAMP_WITH_LOCAL_TIME_ZONE is now 9. Tests cover supported precisions through 9 and rejection of timestamp precisions above 9. The FunctionMappings comment describes interval_day<9> producing precision_timestamp<9>.
Timestamp literal conversion
isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java, isthmus/src/main/java/io/substrait/isthmus/expression/ExpressionRexConverter.java, isthmus/src/test/java/io/substrait/isthmus/CalciteLiteralTest.java
Timestamp conversion checks precision-scaled arithmetic and rejects values outside Calcite’s supported timestamp range. Tests cover precision-9 literals, signed 64-bit nanosecond endpoints, and timestamp date-range boundaries.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: nielspardon

Merge Risk: ⚪ Minimal · up to 3f883

No merge-blocking issue is established; this change is ready for normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 3f883

The changed conversion rules have compatibility implications, but the reviewed paths retain precision checks and add bounds checks. No introduced security issue was established. How applications handle conversion failures remains uncertain.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is conversion of temporal values in Isthmus plans. The evidence does not establish an externally accessible service, tenant scope, or privileged sink.

Trust Boundaries and Controls

  • observed — Precision validation precedes timestamp literal construction, and the new range check precedes Calcite timestamp rendering.

Resilience and Maintainability Implications

  • observed — Arithmetic overflow is translated to IllegalArgumentException with the timestamp and precision; the reviewed caller path does not establish how that exception is handled at an application boundary.

Hardening Proposals

  • proposed — If an application accepts plans from untrusted parties, treat these conversion exceptions as invalid-plan failures at that application boundary rather than assuming every caller contains them.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 38 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed [#995] SubstraitTypeSystem.getMaxPrecision sets the Calcite limit to 9 for TIME, TIMESTAMP, and TIMESTAMP_WITH_LOCAL_TIME_ZONE. The documentation explains the nanosecond limit and the exclusio…
Out of Scope Changes check ✅ Passed The changes stay within the linked objectives. Type-system updates, literal conversion, timestamp range checks, FunctionMappings precision propagation, and their tests support [#995] or [#1298]. No …
Title check ✅ Passed The title clearly identifies the main change: raising TIME and TIMESTAMP precision support to nanoseconds. It is concise, specific, and uses a valid breaking Conventional Commit format.
Description check ✅ Passed The description explains the rationale, conversion behavior, range limits, compatibility impact, linked issues, and breaking changes. It provides the required changelog information and is consistent w…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The floored seconds of 1677-09-21 00:12:43 overflow when scaled to nanoseconds
although the value fits once the sub-second part is added back, so a negative
timestamp borrows that second before scaling. Pin both ends of the range and
the nanosecond past each.

@nielspardon nielspardon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Decide what a nanosecond column should do with an out-of-range sentinel before this lands — on a TIMESTAMP(9) column, SELECT * FROM v WHERE ts > TIMESTAMP '9999-12-31 23:59:59' now fails conversion even though the literal has no fractional digits, because Calcite widens it to the column's precision. Everything below is mechanical by comparison. Once the first point is settled the BREAKING CHANGE: footer needs the losing side too, and the body should credit #1298, whose wrap epochUnits actually fixes.

Comment thread isthmus/src/main/java/io/substrait/isthmus/SubstraitTypeSystem.java
Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java Outdated
Comment thread isthmus/src/test/java/io/substrait/isthmus/CalciteLiteralTest.java
A TIMESTAMP(9) column widens a compared literal to nanoseconds, so a
sentinel date outside 1677-2262 has no value of that type, and the
conversion reports it, as DuckDB and Arrow do. Also pass Locale.ROOT to
the overflow message, cover precisions 7 to 9 in the value round trips,
and fix the comment on the date minus interval result.

@nielspardon nielspardon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, all three round-1 points are addressed. Three smaller ones below; only the first is worth holding the merge for.

Comment thread isthmus/src/main/java/io/substrait/isthmus/SubstraitTypeSystem.java
…g range

A TimestampString spans years 0000 to 9999, and past that DateTimeUtils
renders the year modulo 10000, so precision_timestamp<7> 3000000000000000000
became 1476-08-15 05:20:00. The value is now checked the way a
precision_time already is. Also documents the IllegalArgumentException of
LiteralConverter.convert(RexLiteral) and why the time ceilings stop at 9.

@nielspardon nielspardon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, all three are in. One last nit below, not worth holding the merge for.

Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/ExpressionRexConverter.java Outdated
%d formats in the default locale, so under a non-Latin numbering system
the precision_time and precision_timestamp messages carried other
digits and the exact-match test assertions failed. %s does not.

@nielspardon nielspardon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@nielspardon
nielspardon merged commit 3a6c248 into substrait-io:main Sep 29, 2026
14 checks passed
@alexandrefimov
alexandrefimov deleted the issue-995-precision-ceiling branch September 30, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants