Skip to content

[CALCITE-7529] Casts between literals and TIME/TIMESTAMP can lose precision beyond milliseconds#4942

Open
zzwqqq wants to merge 6 commits into
apache:mainfrom
zzwqqq:fix_time_literal
Open

[CALCITE-7529] Casts between literals and TIME/TIMESTAMP can lose precision beyond milliseconds#4942
zzwqqq wants to merge 6 commits into
apache:mainfrom
zzwqqq:fix_time_literal

Conversation

@zzwqqq
Copy link
Copy Markdown
Contributor

@zzwqqq zzwqqq commented May 14, 2026

Jira Link

CALCITE-7529

Changes Proposed

Preserve precision for casts involving high-precision TIME and TIMESTAMP literals.

Generated constant reduction represents TIME and TIMESTAMP values at millisecond precision. When a custom RelDataTypeSystem allows precision greater than 3, literal casts such as CAST('12:34:56.123456' AS TIME(6)) can lose digits beyond milliseconds.

The change keeps directly representable temporal literal casts as Rex literals, and avoids re-reducing existing RexLiterals.

Regression tests cover TIME(6) and TIMESTAMP(6) literal casts, existing temporal literals, and casts to VARCHAR.

@zzwqqq zzwqqq force-pushed the fix_time_literal branch from 575b5fd to 19b41c4 Compare May 15, 2026 02:16
@Override public void reduce(RexBuilder rexBuilder, List<RexNode> constExps,
List<RexNode> reducedValues) {
assert reducedValues.isEmpty();
final List<RexNode> exps = new ArrayList<>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what happens here? you are only reducing expressions which are already literals?
This seems to be some kind of regression - other constant expressions are not reduced?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm copying literals through unchanged, and compiling/reducing only non-literal constant expressions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This deserves a comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. I added a comment explaining that RexLiteral inputs are already reduced, so reduce keeps them as Rex values instead of round-tripping them through generated code.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@mihaibudiu mihaibudiu left a comment

Choose a reason for hiding this comment

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

The restricted scope of this PR looks more reasonable, but I do have some questions

@Override public void reduce(RexBuilder rexBuilder, List<RexNode> constExps,
List<RexNode> reducedValues) {
assert reducedValues.isEmpty();
final List<RexNode> exps = new ArrayList<>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This deserves a comment.

final SqlTimeLiteral timeLiteral =
SqlParserUtil.parseTimeLiteral(value, pos);
if (!isSubMillisecondLiteral(timeLiteral, value)) {
if (timeLiteral.getPrec() <= 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand what 0 means, but negative precisions are less clear.
There's "PRECISION_NOT_SPECIFIED", but I'd rather see an explicit check for that if that's what you are looking for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Switched this to check PRECISION_NOT_SPECIFIED explicitly.

return timestamp == null || !hasSubMillisecondPrecision(timestamp)
? null
: timestamp.toString(precision(literal.getType()));
if (timestamp == null || literal.getType().getPrecision() <= 3) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is 3 hardwired here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that 3 was just the millisecond cutoff from the old version. Removed it and now format using the literal type's precision.

default:
return false;
}
return SqlTypeUtil.comparePrecision(type.getPrecision(), value.length()) >= 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please add a comment explaining what happens here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a note here about when this can replace the CAST with a literal.

}

private static RelDataTypeFactory highPrecisionTemporalTypeFactory() {
return new JavaTypeFactoryImpl(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you run tests with multiple precisions, checking for precisions 1..9 for example in a loop?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Expanded these to run through precisions 1..9. I did the same for the temporal-to-VARCHAR cases

@zzwqqq zzwqqq changed the title [CALCITE-7529] RexExecutor constant reduction loses sub-millisecond precision for TIME/TIMESTAMP literals [CALCITE-7529] Casts between literals and TIME/TIMESTAMP can lose precision beyond milliseconds May 23, 2026
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