Skip to content

Commit 8472a1a

Browse files
committed
[fix][common] Fix performance issues with time zone handling
1 parent 3391ac5 commit 8472a1a

File tree

6 files changed

+177
-20
lines changed

6 files changed

+177
-20
lines changed

common/src/main/java/io/dingodb/expr/common/timezone/DateTimeUtils.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,43 @@ private DateTimeUtils() {
238238
if (trimmed.isEmpty()) {
239239
return null;
240240
}
241+
242+
// Structural pre-classification to reduce unnecessary formatter attempts.
243+
// Each failed DateTimeFormatter.parse() throws DateTimeParseException with
244+
// expensive stack trace capture (~1-5us per exception).
245+
boolean hasColon = trimmed.indexOf(':') > 0;
246+
boolean hasDateSep = trimmed.length() >= 8
247+
&& (trimmed.indexOf('-') > 0 || trimmed.indexOf('/') > 0
248+
|| Character.isDigit(trimmed.charAt(0)));
249+
boolean hasZoneInfo = false;
250+
if (trimmed.length() > 10) {
251+
hasZoneInfo = trimmed.indexOf('+', 10) >= 0
252+
|| trimmed.indexOf('Z', 10) >= 0
253+
|| trimmed.contains("[");
254+
}
255+
256+
if (hasZoneInfo) {
257+
if (matchesTimestampWithZone(trimmed, timestampTzFormatters)) {
258+
return DateTimeType.TIMESTAMP_TZ;
259+
}
260+
}
261+
if (hasDateSep && hasColon) {
262+
if (matchesLocalDateTime(trimmed, timestampFormatters)) {
263+
return DateTimeType.TIMESTAMP;
264+
}
265+
}
266+
if (hasDateSep && !hasColon) {
267+
if (matchesLocalDate(trimmed, dateFormatters)) {
268+
return DateTimeType.DATE;
269+
}
270+
}
271+
if (hasColon && !hasDateSep) {
272+
if (matchesLocalTime(trimmed, timeFormatters)) {
273+
return DateTimeType.TIME;
274+
}
275+
}
276+
277+
// Fallback: full scan for ambiguous inputs
241278
if (matchesTimestampWithZone(trimmed, timestampTzFormatters)) {
242279
return DateTimeType.TIMESTAMP_TZ;
243280
}

common/src/main/java/io/dingodb/expr/common/timezone/converter/DateTimeConverter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
public class DateTimeConverter {
3131

3232
private final ZoneId defaultZone;
33+
private final StringDateTimeParser stringParser = new StringDateTimeParser();
3334

3435
public DateTimeConverter(ZoneId defaultZone) {
3536
this.defaultZone = defaultZone;
@@ -53,8 +54,7 @@ public DingoDateTime convertInput(Object input, DateTimeType targetType) {
5354
} else if (input instanceof java.util.Date) {
5455
return convertUtilDate((java.util.Date) input, targetType);
5556
} else if (input instanceof String) {
56-
StringDateTimeParser parser = new StringDateTimeParser();
57-
return parser.parseString((String) input, targetType, defaultZone);
57+
return stringParser.parseString((String) input, targetType, defaultZone);
5858
} else if (input instanceof DingoDateTime) {
5959
return convertDingoDateTime((DingoDateTime) input, targetType);
6060
} else {

common/src/main/java/io/dingodb/expr/common/timezone/processor/DingoTimeZoneProcessor.java

Lines changed: 128 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030

3131
import java.time.Instant;
3232
import java.time.LocalDate;
33+
import java.time.LocalDateTime;
34+
import java.time.LocalTime;
35+
import java.time.ZonedDateTime;
3336
import java.time.ZoneId;
3437
import java.time.format.DateTimeFormatter;
3538
import java.time.temporal.ChronoUnit;
@@ -60,6 +63,12 @@ public Object processDateTime(Object input, DateTimeType inputType,
6063
if (input == null || inputType == null) {
6164
return null;
6265
}
66+
// Fast path: same type, non-TZ-sensitive SQL types — no conversion needed
67+
if (inputType == outputType) {
68+
if (input instanceof java.sql.Date && inputType == DateTimeType.DATE) return input;
69+
if (input instanceof java.sql.Time && inputType == DateTimeType.TIME) return input;
70+
if (input instanceof java.sql.Timestamp && inputType == DateTimeType.TIMESTAMP) return input;
71+
}
6372
try {
6473
DingoDateTime internal = tierProcessor.convertInput(input, inputType);
6574

@@ -134,31 +143,135 @@ public ZoneId getStorageZone() {
134143
}
135144

136145
public DingoDateTime dateAdd(DingoDateTime dateTime, long amount, ChronoUnit unit) {
137-
ArithmeticOperations.AddOperation operation = new ArithmeticOperations.AddOperation(unit, amount);
138-
139-
OperationResult result = (OperationResult) operation.execute(new DingoDateTime[]{dateTime}, getOutputZone());
146+
if (dateTime.isTimeZoneSensitive()) {
147+
DingoDateTime.DingoTimestampTZ tzValue = (DingoDateTime.DingoTimestampTZ) dateTime;
148+
ZonedDateTime zdt = tzValue.getUtcValue().atZone(tzValue.getOriginalZone());
149+
return new DingoDateTime.DingoTimestampTZ(zdt.plus(amount, unit).toInstant(), tzValue.getOriginalZone());
150+
}
151+
Object value = dateTime.getValue();
152+
if (value instanceof LocalDate) {
153+
return new DingoDateTime.DingoLocalDate(((LocalDate) value).plus(amount, unit));
154+
} else if (value instanceof LocalDateTime) {
155+
return new DingoDateTime.DingoLocalDateTime(((LocalDateTime) value).plus(amount, unit));
156+
} else if (value instanceof LocalTime) {
157+
return new DingoDateTime.DingoLocalTime(((LocalTime) value).plus(amount, unit));
158+
}
159+
throw new DateTimeProcessingException("Unsupported type for add: " + value.getClass());
160+
}
140161

141-
if (result.isSuccess() && result.getValue() instanceof DingoDateTime) {
142-
return (DingoDateTime) result.getValue();
162+
public DingoDateTime dateSubtract(DingoDateTime dateTime, long amount, ChronoUnit unit) {
163+
if (dateTime.isTimeZoneSensitive()) {
164+
DingoDateTime.DingoTimestampTZ tzValue = (DingoDateTime.DingoTimestampTZ) dateTime;
165+
ZonedDateTime zdt = tzValue.getUtcValue().atZone(tzValue.getOriginalZone());
166+
return new DingoDateTime.DingoTimestampTZ(zdt.minus(amount, unit).toInstant(), tzValue.getOriginalZone());
143167
}
168+
Object value = dateTime.getValue();
169+
if (value instanceof LocalDate) {
170+
return new DingoDateTime.DingoLocalDate(((LocalDate) value).minus(amount, unit));
171+
} else if (value instanceof LocalDateTime) {
172+
return new DingoDateTime.DingoLocalDateTime(((LocalDateTime) value).minus(amount, unit));
173+
} else if (value instanceof LocalTime) {
174+
return new DingoDateTime.DingoLocalTime(((LocalTime) value).minus(amount, unit));
175+
}
176+
throw new DateTimeProcessingException("Unsupported type for subtract: " + value.getClass());
177+
}
144178

145-
throw new DateTimeProcessingException("Date add operation failed: "
146-
+ (result.getErrorMessage() != null ? result.getErrorMessage() : "Unknown error"));
179+
// -------------------------------------------------------------------------
180+
// Type-specialized extraction fast paths — bypass the full pipeline
181+
// for non-timezone-sensitive java.sql types (Date, Timestamp).
182+
// -------------------------------------------------------------------------
183+
184+
public Integer extractYear(java.sql.Date value) {
185+
return value == null ? null : value.toLocalDate().getYear();
147186
}
148187

149-
public DingoDateTime dateSubtract(DingoDateTime dateTime, long amount, ChronoUnit unit) {
150-
ArithmeticOperations.SubtractOperation operation = new ArithmeticOperations.SubtractOperation(unit, amount);
188+
public Integer extractYear(java.sql.Timestamp value) {
189+
return value == null ? null : value.toLocalDateTime().getYear();
190+
}
151191

152-
OperationResult result = (OperationResult) operation.execute(new DingoDateTime[]{dateTime}, getOutputZone());
192+
public Integer extractMonth(java.sql.Date value) {
193+
return value == null ? null : value.toLocalDate().getMonthValue();
194+
}
153195

154-
if (result.isSuccess() && result.getValue() instanceof DingoDateTime) {
155-
return (DingoDateTime) result.getValue();
156-
}
196+
public Integer extractMonth(java.sql.Timestamp value) {
197+
return value == null ? null : value.toLocalDateTime().getMonthValue();
198+
}
157199

158-
throw new DateTimeProcessingException("Date subtract operation failed: "
159-
+ (result.getErrorMessage() != null ? result.getErrorMessage() : "Unknown error"));
200+
public Integer extractDay(java.sql.Date value) {
201+
return value == null ? null : value.toLocalDate().getDayOfMonth();
202+
}
203+
204+
public Integer extractDay(java.sql.Timestamp value) {
205+
return value == null ? null : value.toLocalDateTime().getDayOfMonth();
206+
}
207+
208+
public Integer extractQuarter(java.sql.Date value) {
209+
return value == null ? null : (value.toLocalDate().getMonthValue() - 1) / 3 + 1;
210+
}
211+
212+
public Integer extractQuarter(java.sql.Timestamp value) {
213+
return value == null ? null : (value.toLocalDateTime().getMonthValue() - 1) / 3 + 1;
160214
}
161215

216+
public Integer extractWeek(java.sql.Date value) {
217+
if (value == null) return null;
218+
return value.toLocalDate().get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR);
219+
}
220+
221+
public Integer extractWeek(java.sql.Timestamp value) {
222+
if (value == null) return null;
223+
return value.toLocalDateTime().get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR);
224+
}
225+
226+
public Integer extractHour(java.sql.Date value) {
227+
return 0;
228+
}
229+
230+
public Integer extractHour(java.sql.Timestamp value) {
231+
return value == null ? 0 : value.toLocalDateTime().getHour();
232+
}
233+
234+
public Integer extractHour(java.sql.Time value) {
235+
return value == null ? 0 : value.toLocalTime().getHour();
236+
}
237+
238+
public Integer extractMinute(java.sql.Date value) {
239+
return 0;
240+
}
241+
242+
public Integer extractMinute(java.sql.Timestamp value) {
243+
return value == null ? 0 : value.toLocalDateTime().getMinute();
244+
}
245+
246+
public Integer extractMinute(java.sql.Time value) {
247+
return value == null ? 0 : value.toLocalTime().getMinute();
248+
}
249+
250+
public Integer extractSecond(java.sql.Date value) {
251+
return 0;
252+
}
253+
254+
public Integer extractSecond(java.sql.Timestamp value) {
255+
return value == null ? 0 : value.toLocalDateTime().getSecond();
256+
}
257+
258+
public Integer extractSecond(java.sql.Time value) {
259+
return value == null ? 0 : value.toLocalTime().getSecond();
260+
}
261+
262+
public Integer extractMillisecond(java.sql.Date value) {
263+
return 0;
264+
}
265+
266+
public Integer extractMillisecond(java.sql.Timestamp value) {
267+
if (value == null) return 0;
268+
return value.toLocalDateTime().getNano() / 1_000_000;
269+
}
270+
271+
// -------------------------------------------------------------------------
272+
// Generic extraction methods (original, for Object/String inputs)
273+
// -------------------------------------------------------------------------
274+
162275
public Integer extractYear(Object input) {
163276
ExtractionOperations.ExtractFieldOperation operation = ExtractionOperations.year();
164277
DateTimeType inputType = inferInputType(input);

runtime/src/main/java/io/dingodb/expr/runtime/ExprConfig.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ default void setProcessor(DingoTimeZoneProcessor processor) {
8888
}
8989

9090
default DingoTimeZoneProcessor getProcessor() {
91-
return new DingoTimeZoneProcessor(ZoneId.systemDefault());
91+
return DefaultProcessorHolder.INSTANCE;
9292
}
9393

9494
default DateTimeFormatter[] getParseDateFormatters() {
@@ -122,4 +122,11 @@ default DateTimeFormatter getOutputTimeFormatter() {
122122
default DateTimeFormatter getOutputTimestampFormatter() {
123123
return DateTimeUtils.DEFAULT_OUTPUT_TIMESTAMP_FORMATTER;
124124
}
125+
126+
// Lazy initialization holder for the default DingoTimeZoneProcessor singleton.
127+
// Avoids creating a new processor instance on every getProcessor() call.
128+
class DefaultProcessorHolder {
129+
static final DingoTimeZoneProcessor INSTANCE =
130+
new DingoTimeZoneProcessor(ZoneId.systemDefault());
131+
}
125132
}

runtime/src/main/java/io/dingodb/expr/runtime/op/time/CurrentDateFun.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public Object eval(EvalContext context, ExprConfig config) {
4747

4848
@Override
4949
public boolean isConst(@NonNull NullaryOpExpr expr) {
50-
return false;
50+
return true;
5151
}
5252

5353
@Override

runtime/src/main/java/io/dingodb/expr/runtime/op/time/CurrentTimeFun.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public Type getType() {
5252

5353
@Override
5454
public boolean isConst(@NonNull NullaryOpExpr expr) {
55-
return false;
55+
return true;
5656
}
5757

5858
@Override

0 commit comments

Comments
 (0)