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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@
import org.apache.doris.nereids.types.DataType;
import org.apache.doris.nereids.types.DateTimeV2Type;
import org.apache.doris.nereids.types.TimeStampTzType;
import org.apache.doris.nereids.types.coercion.CharacterType;
import org.apache.doris.nereids.util.DateUtils;
import org.apache.doris.qe.ConnectContext;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Locale;
import java.util.Objects;

/**
Expand Down Expand Up @@ -168,6 +174,8 @@ protected Expression uncheckedCastTo(DataType targetType) throws AnalysisExcepti
if (targetType.isTimeStampTzType()) {
return new TimestampTzLiteral((TimeStampTzType) targetType,
year, month, day, hour, minute, second, microSecond);
} else if (targetType.isStringLikeType()) {
return uncheckedCastToString((CharacterType) targetType);
} else if (targetType.isDateTimeV2Type()) {
DateTimeV2Literal dtV2Lit = new DateTimeV2Literal((DateTimeV2Type) targetType,
year, month, day, hour, minute, second, microSecond);
Expand All @@ -180,6 +188,37 @@ protected Expression uncheckedCastTo(DataType targetType) throws AnalysisExcepti
throw new AnalysisException(String.format("Cast from %s to %s not supported", this, targetType));
}

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 uses DateUtils.getTimeZone(), which calls ZoneId.of() on the raw session variable without Doris' alias map. SET time_zone = 'CST' is valid in FE (TimeUtils.checkTimeZoneValidAndStandardize accepts it and maps it to Asia/Shanghai, and BE query globals also special-case CST), but this new FE folding path will now throw ZoneRulesException: Unknown time-zone ID: CST when folding CAST(<timestamptz literal> AS STRING/VARCHAR/CHAR). Please resolve the session zone through the same alias-aware path as the rest of FE, for example TimeUtils.getTimeZone().toZoneId() or ZoneId.of(sessionTimeZone, TimeUtils.timeZoneAliasMap), and add a test for CST so FE folding remains consistent with runtime casting.

@Override
protected String castValueToString() {
ZoneId sessionZone = DateUtils.getTimeZone();
ZonedDateTime localDateTime = toJavaDateType().atZone(ZoneId.of("UTC")).withZoneSameInstant(sessionZone);
return formatDateTime(localDateTime.toLocalDateTime()) + formatOffset(localDateTime.getOffset());
}

private static String formatOffset(ZoneOffset offset) {
// Keep FE constant folding aligned with BE TimestampTzValue::to_string()
// in be/src/core/value/timestamptz_value.cpp. BE renders the numeric offset
// from seconds as sign + HH:MM, instead of using library ids such as "Z".
int totalSeconds = offset.getTotalSeconds();
int absSeconds = Math.abs(totalSeconds);
int hours = absSeconds / 3600;
int minutes = (absSeconds % 3600) / 60;
return String.format(Locale.ROOT, "%s%02d:%02d", totalSeconds < 0 ? "-" : "+", hours, minutes);
}

private String formatDateTime(LocalDateTime dateTime) {
String base = String.format(Locale.ROOT, "%04d-%02d-%02d %02d:%02d:%02d", dateTime.getYear(),
dateTime.getMonthValue(), dateTime.getDayOfMonth(), dateTime.getHour(),
dateTime.getMinute(), dateTime.getSecond());
int scale = getDataType().getScale();
if (scale <= 0) {
return base;
}
long scaledMicroSecond = (dateTime.getNano() / 1000)
/ (int) Math.pow(10, TimeStampTzType.MAX_SCALE - scale);
return base + "." + String.format(Locale.ROOT, "%0" + scale + "d", scaledMicroSecond);
}

public Expression plusDays(long days) {
return fromJavaDateType(toJavaDateType().plusDays(days), getDataType().getScale());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,26 @@

package org.apache.doris.nereids.trees.expressions.literal;

import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
import org.apache.doris.nereids.trees.expressions.Cast;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.types.CharType;
import org.apache.doris.nereids.types.StringType;
import org.apache.doris.nereids.types.TimeStampTzType;
import org.apache.doris.nereids.types.VarcharType;
import org.apache.doris.qe.ConnectContext;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class TimestampTzLiteralTest {

@AfterEach
void tearDown() {
ConnectContext.remove();
}

@Test
void testConstructorsAndParsing() {
TimestampTzLiteral literal;
Expand Down Expand Up @@ -300,4 +312,50 @@ void testPlusSecondMicrosecond() {
Assertions.assertEquals(58, result.second);
Assertions.assertEquals(900000, result.microSecond);
}

@Test
void testCastToStringUsesSessionTimeZone() throws Exception {
setSessionTimeZone("+12:34");
TimestampTzLiteral literal = new TimestampTzLiteral(
TimeStampTzType.SYSTEM_DEFAULT, 2023, 7, 13, 19, 26, 0, 0);
Assertions.assertEquals("2023-07-13 19:26:00", literal.getStringValue());

Expression folded = FoldConstantRuleOnFE.evaluate(new Cast(literal, StringType.INSTANCE), null);
Assertions.assertInstanceOf(StringLiteral.class, folded);
Assertions.assertEquals("2023-07-14 08:00:00+12:34",
((StringLiteral) folded).getStringValue());

Expression varchar = literal.uncheckedCastTo(VarcharType.createVarcharType(64));
Assertions.assertInstanceOf(VarcharLiteral.class, varchar);
Assertions.assertEquals("2023-07-14 08:00:00+12:34",
((VarcharLiteral) varchar).getStringValue());

Expression character = literal.uncheckedCastTo(CharType.createCharType(64));
Assertions.assertInstanceOf(CharLiteral.class, character);
Assertions.assertEquals("2023-07-14 08:00:00+12:34",
((CharLiteral) character).getStringValue());

setSessionTimeZone("+00:00");
Expression utcFolded = FoldConstantRuleOnFE.evaluate(new Cast(literal, StringType.INSTANCE), null);
Assertions.assertInstanceOf(StringLiteral.class, utcFolded);
Assertions.assertEquals("2023-07-13 19:26:00+00:00",
((StringLiteral) utcFolded).getStringValue());
}

@Test
void testCastToStringPreservesScale() {
setSessionTimeZone("+12:34");
TimestampTzLiteral literal = new TimestampTzLiteral("2023-07-13 22:28:18.456789+05:00");

Expression folded = FoldConstantRuleOnFE.evaluate(new Cast(literal, StringType.INSTANCE), null);
Assertions.assertInstanceOf(StringLiteral.class, folded);
Assertions.assertEquals("2023-07-14 06:02:18.456789+12:34",
((StringLiteral) folded).getStringValue());
}

private void setSessionTimeZone(String timeZone) {
ConnectContext context = new ConnectContext();
context.getSessionVariable().setTimeZone(timeZone);
context.setThreadLocalInfo();
}
}
Loading