This is an automated email from the ASF dual-hosted git repository.
corgy pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new bdd9c1d3b9 [Improve][Transform-V2] Add whitelist-based validation for
datetime format patterns in PARSEDATETIME/TO_DATE (#10360)
bdd9c1d3b9 is described below
commit bdd9c1d3b96c242e262f4aeecdb51a3ea48b83da
Author: aksmf1442 <[email protected]>
AuthorDate: Fri Feb 27 23:37:47 2026 +0900
[Improve][Transform-V2] Add whitelist-based validation for datetime format
patterns in PARSEDATETIME/TO_DATE (#10360)
---
.../introduction/concepts/incompatible-changes.md | 8 +
docs/en/transforms/sql-functions.md | 91 +++++++-
.../introduction/concepts/incompatible-changes.md | 9 +
docs/zh/transforms/sql-functions.md | 94 +++++++-
.../transform/sql/zeta/ZetaDateTimeFormat.java | 75 ++++++
.../seatunnel/transform/sql/zeta/ZetaSQLType.java | 40 ++--
.../sql/zeta/functions/DateTimeFunction.java | 80 +++++--
.../transform/sql/zeta/ZetaDateTimeFormatTest.java | 260 +++++++++++++++++++++
.../transform/sql/zeta/ZetaSQLTypeTest.java | 4 +-
.../sql/zeta/functions/DateTimeFunctionsTest.java | 166 ++++++++++++-
10 files changed, 779 insertions(+), 48 deletions(-)
diff --git a/docs/en/introduction/concepts/incompatible-changes.md
b/docs/en/introduction/concepts/incompatible-changes.md
index 940db9350c..7cdb776dde 100644
--- a/docs/en/introduction/concepts/incompatible-changes.md
+++ b/docs/en/introduction/concepts/incompatible-changes.md
@@ -37,6 +37,14 @@ You need to check this document before you upgrade to
related version.
### Transform Changes
+- **[BREAKING]** SQL Transform `PARSEDATETIME`, `TO_DATE`, and `IS_DATE`
functions now only accept whitelisted datetime format patterns. Custom format
patterns that were previously accepted will now fail at runtime. The supported
patterns are:
+ - DateTime: `yyyy-MM-dd HH:mm:ss`, `yyyy-MM-dd HH:mm:ss.SSS`,
`yyyy-MM-dd'T'HH:mm:ss`, `yyyy-MM-dd'T'HH:mm:ss.SSS`, `yyyy/MM/dd HH:mm:ss`,
`yyyy/MM/dd HH:mm:ss.SSS`, `yyyyMMddHHmmss`
+ - Date: `yyyy-MM-dd`, `yyyy/MM/dd`, `yyyyMMdd`
+ - Time: `HH:mm:ss`, `HH:mm:ss.SSS`, `HHmmss`
+
+ **Exception Type Change**: Invalid datetime format patterns now throw
`SeaTunnelRuntimeException` instead of `TransformException`. If you have error
handling or monitoring systems that catch `TransformException` for datetime
parsing errors, you will need to update them to handle
`SeaTunnelRuntimeException`.
+
+ **Migration Guide**: If you are using custom datetime format patterns in
`PARSEDATETIME`, `TO_DATE`, or `IS_DATE` functions, you must update your
queries to use one of the supported patterns above. If your data uses a
different format, you may need to preprocess the input data to match a
supported format, or use string manipulation functions to transform the format
before parsing.
- DataValidator transform: In `row_error_handle_way = ROUTE_TO_TABLE` mode,
the routed error row `table_id` now includes the upstream database/schema
prefix (for example, `db1.ffp` / `db1.schema1.ffp` instead of `ffp`).
- Adjusted SQL Transform date & time functions:
- `DATEDIFF(<start>, <end>, 'MONTH')` now returns the total number of months
between the two dates across years (for example, from `2023-01-01` to
`2024-03-01` returns `14` instead of `15`).
diff --git a/docs/en/transforms/sql-functions.md
b/docs/en/transforms/sql-functions.md
index 22d0c6b4e9..2c6bd6bc17 100644
--- a/docs/en/transforms/sql-functions.md
+++ b/docs/en/transforms/sql-functions.md
@@ -889,22 +889,97 @@ MONTHNAME(CREATED)
### IS_DATE
```IS_DATE(string, formatString) -> BOOLEAN```
-Parses a string. The most important format characters are: y year, M month, d
day, H hour, m minute, s second. For details of the format, see
java.time.format.DateTimeFormatter.
+Validates whether a string can be parsed as a date/time value using the
specified format pattern.
+
+**Supported Format Patterns:**
+
+DateTime Formats:
+- `yyyy-MM-dd HH:mm:ss` - Standard datetime format
+- `yyyy-MM-dd HH:mm:ss.SSS` - Datetime with milliseconds
+- `yyyy-MM-dd'T'HH:mm:ss` - ISO 8601 datetime format
+- `yyyy-MM-dd'T'HH:mm:ss.SSS` - ISO 8601 datetime with milliseconds
+- `yyyy/MM/dd HH:mm:ss` - Datetime with slash separator
+- `yyyy/MM/dd HH:mm:ss.SSS` - Datetime with slash separator and milliseconds
+- `yyyyMMddHHmmss` - Compact datetime format
+
+Date Formats:
+- `yyyy-MM-dd` - ISO 8601 date format
+- `yyyy/MM/dd` - Date with slash separator
+- `yyyyMMdd` - Compact date format
+
+Time Formats:
+- `HH:mm:ss` - Standard time format
+- `HH:mm:ss.SSS` - Time with milliseconds
+- `HHmmss` - Compact time format
Example:
-CALL IS_DATE('2021-04-08 13:34:45','yyyy-MM-dd HH:mm:ss')
+```sql
+CALL IS_DATE('2021-04-08 13:34:45', 'yyyy-MM-dd HH:mm:ss')
+-- Returns true
+
+CALL IS_DATE('2021/04/08', 'yyyy/MM/dd')
+-- Returns true
+
+CALL IS_DATE('20210408', 'yyyyMMdd')
+-- Returns true
+
+-- Consistent with TO_DATE
+SELECT CASE
+ WHEN IS_DATE(date_string, 'yyyy-MM-dd HH:mm:ss')
+ THEN TO_DATE(date_string, 'yyyy-MM-dd HH:mm:ss')
+ ELSE NULL
+END as parsed_date
+```
### PARSEDATETIME / TO_DATE
-```PARSEDATETIME | TO_DATE(string, formatString) -> TIMESTAMP```
-Parses a string. The most important format characters are: y year, M month, d
day, H hour, m minute, s second. For details of the format, see
java.time.format.DateTimeFormatter.
+```PARSEDATETIME | TO_DATE(string, formatString) -> TIMESTAMP | DATE | TIME```
+Parses a string into a date/time value using the specified format pattern.
-Example:
+**Supported Format Patterns:**
+
+DateTime Formats (returns TIMESTAMP):
+- `yyyy-MM-dd HH:mm:ss` - Standard datetime format
+- `yyyy-MM-dd HH:mm:ss.SSS` - Datetime with milliseconds
+- `yyyy-MM-dd'T'HH:mm:ss` - ISO 8601 datetime format
+- `yyyy-MM-dd'T'HH:mm:ss.SSS` - ISO 8601 datetime with milliseconds
+- `yyyy/MM/dd HH:mm:ss` - Datetime with slash separator
+- `yyyy/MM/dd HH:mm:ss.SSS` - Datetime with slash separator and milliseconds
+- `yyyyMMddHHmmss` - Compact datetime format
+
+Date Formats (returns DATE):
+- `yyyy-MM-dd` - ISO 8601 date format
+- `yyyy/MM/dd` - Date with slash separator
+- `yyyyMMdd` - Compact date format
+
+Time Formats (returns TIME):
+- `HH:mm:ss` - Standard time format
+- `HH:mm:ss.SSS` - Time with milliseconds
+- `HHmmss` - Compact time format
+
+**Note:** When using single quotes (`'`) in format patterns (e.g., for ISO
8601 'T' separator), they must be escaped as `''` in SQL.
-CALL PARSEDATETIME('2021-04-08 13:34:45','yyyy-MM-dd HH:mm:ss')
-CALL TO_DATE('2021-04-08'T'13:34:45','yyyy-MM-dd''T''HH:mm:ss')
-Note that when filling in `'` in SQL functions, it needs to be escaped to `''`.
+Examples:
+
+```sql
+-- DateTime examples
+CALL PARSEDATETIME('2021-04-08 13:34:45', 'yyyy-MM-dd HH:mm:ss')
+CALL TO_DATE('2021-04-08T13:34:45', 'yyyy-MM-dd''T''HH:mm:ss')
+CALL PARSEDATETIME('2024-06-15 14:30:45.123', 'yyyy-MM-dd HH:mm:ss.SSS')
+CALL PARSEDATETIME('2021/04/08 13:34:45', 'yyyy/MM/dd HH:mm:ss')
+CALL PARSEDATETIME('20210408133445', 'yyyyMMddHHmmss')
+
+-- Date examples
+CALL TO_DATE('2021-04-08', 'yyyy-MM-dd')
+CALL TO_DATE('2021/04/08', 'yyyy/MM/dd')
+CALL TO_DATE('20210408', 'yyyyMMdd')
+
+-- Time examples
+CALL PARSEDATETIME('14:30:45', 'HH:mm:ss')
+CALL PARSEDATETIME('14:30:45.123', 'HH:mm:ss.SSS')
+CALL PARSEDATETIME('143045', 'HHmmss')
+```
### QUARTER
diff --git a/docs/zh/introduction/concepts/incompatible-changes.md
b/docs/zh/introduction/concepts/incompatible-changes.md
index 13fe82f5ce..784729cec6 100644
--- a/docs/zh/introduction/concepts/incompatible-changes.md
+++ b/docs/zh/introduction/concepts/incompatible-changes.md
@@ -36,6 +36,15 @@
### 转换变更
+- **[BREAKING]** SQL Transform 的 `PARSEDATETIME`、`TO_DATE` 和 `IS_DATE`
函数现在只接受白名单中的日期时间格式模式。以前接受的自定义格式模式现在将在运行时失败。支持的模式有:
+ - DateTime: `yyyy-MM-dd HH:mm:ss`, `yyyy-MM-dd HH:mm:ss.SSS`,
`yyyy-MM-dd'T'HH:mm:ss`, `yyyy-MM-dd'T'HH:mm:ss.SSS`, `yyyy/MM/dd HH:mm:ss`,
`yyyy/MM/dd HH:mm:ss.SSS`, `yyyyMMddHHmmss`
+ - Date: `yyyy-MM-dd`, `yyyy/MM/dd`, `yyyyMMdd`
+ - Time: `HH:mm:ss`, `HH:mm:ss.SSS`, `HHmmss`
+
+ **异常类型变更**: 无效的日期时间格式模式现在会抛出 `SeaTunnelRuntimeException` 而不是
`TransformException`。如果您的错误处理或监控系统捕获 `TransformException`
来处理日期时间解析错误,您需要更新它们以处理 `SeaTunnelRuntimeException`。
+
+ **迁移指南**: 如果您在 `PARSEDATETIME`、`TO_DATE` 或 `IS_DATE`
函数中使用自定义日期时间格式模式,您必须更新查询以使用上述支持的模式之一。如果您的数据使用不同的格式,您可能需要预处理输入数据以匹配支持的格式,或使用字符串操作函数在解析之前转换格式。
+
- DataValidator 转换:当 `row_error_handle_way = ROUTE_TO_TABLE` 时,路由到错误表的行
`table_id` 现在会携带上游的 database/schema 前缀(例如从 `ffp` 变为 `db1.ffp` /
`db1.schema1.ffp`)。
### 引擎行为变更
diff --git a/docs/zh/transforms/sql-functions.md
b/docs/zh/transforms/sql-functions.md
index c5fe31e5d9..5a2536fa42 100644
--- a/docs/zh/transforms/sql-functions.md
+++ b/docs/zh/transforms/sql-functions.md
@@ -890,17 +890,101 @@ MONTH(CREATED)
MONTHNAME(CREATED)
+### IS_DATE
+
+```IS_DATE(string, formatString) -> BOOLEAN```
+验证字符串是否可以使用指定的格式模式解析为日期/时间值。
+
+**支持的格式模式:**
+
+日期时间格式:
+- `yyyy-MM-dd HH:mm:ss` - 标准日期时间格式
+- `yyyy-MM-dd HH:mm:ss.SSS` - 带毫秒的日期时间
+- `yyyy-MM-dd'T'HH:mm:ss` - ISO 8601 日期时间格式
+- `yyyy-MM-dd'T'HH:mm:ss.SSS` - 带毫秒的 ISO 8601 日期时间
+- `yyyy/MM/dd HH:mm:ss` - 带斜杠分隔符的日期时间
+- `yyyy/MM/dd HH:mm:ss.SSS` - 带斜杠分隔符和毫秒的日期时间
+- `yyyyMMddHHmmss` - 紧凑日期时间格式
+
+日期格式:
+- `yyyy-MM-dd` - ISO 8601 日期格式
+- `yyyy/MM/dd` - 带斜杠分隔符的日期
+- `yyyyMMdd` - 紧凑日期格式
+
+时间格式:
+- `HH:mm:ss` - 标准时间格式
+- `HH:mm:ss.SSS` - 带毫秒的时间
+- `HHmmss` - 紧凑时间格式
+
+示例:
+
+```sql
+CALL IS_DATE('2021-04-08 13:34:45', 'yyyy-MM-dd HH:mm:ss')
+-- 返回 true
+
+CALL IS_DATE('2021/04/08', 'yyyy/MM/dd')
+-- 返回 true
+
+CALL IS_DATE('20210408', 'yyyyMMdd')
+-- 返回 true
+
+-- 与 TO_DATE 保持一致
+SELECT CASE
+ WHEN IS_DATE(date_string, 'yyyy-MM-dd HH:mm:ss')
+ THEN TO_DATE(date_string, 'yyyy-MM-dd HH:mm:ss')
+ ELSE NULL
+END as parsed_date
+```
+
### PARSEDATETIME / TO_DATE
-```PARSEDATETIME | TO_DATE(string, formatString) -> TIMESTAMP```
+```PARSEDATETIME | TO_DATE(string, formatString) -> TIMESTAMP | DATE | TIME```
+
+使用指定的格式模式将字符串解析为日期/时间值
+
+**支持的格式模式:**
-解析一个字符串并返回一个 TIMESTAMP WITH TIME ZONE
值。最重要的格式字符包括:y(年)、M(月)、d(日)、H(时)、m(分)、s(秒)。有关格式的详细信息,请参阅
java.time.format.DateTimeFormatter。
+日期时间格式 (返回 TIMESTAMP):
+- `yyyy-MM-dd HH:mm:ss` - 标准日期时间格式
+- `yyyy-MM-dd HH:mm:ss.SSS` - 带毫秒的日期时间
+- `yyyy-MM-dd'T'HH:mm:ss` - ISO 8601 日期时间格式
+- `yyyy-MM-dd'T'HH:mm:ss.SSS` - 带毫秒的 ISO 8601 日期时间
+- `yyyy/MM/dd HH:mm:ss` - 带斜杠分隔符的日期时间
+- `yyyy/MM/dd HH:mm:ss.SSS` - 带斜杠分隔符和毫秒的日期时间
+- `yyyyMMddHHmmss` - 紧凑日期时间格式
+
+日期格式 (返回 DATE):
+- `yyyy-MM-dd` - ISO 8601 日期格式
+- `yyyy/MM/dd` - 带斜杠分隔符的日期
+- `yyyyMMdd` - 紧凑日期格式
+
+时间格式 (返回 TIME):
+- `HH:mm:ss` - 标准时间格式
+- `HH:mm:ss.SSS` - 带毫秒的时间
+- `HHmmss` - 紧凑时间格式
+
+**注意:** 在格式模式中使用单引号 (`'`) 时(例如 ISO 8601 的 'T' 分隔符),必须在 SQL 中转义为 `''`。
示例:
-CALL PARSEDATETIME('2021-04-08 13:34:45','yyyy-MM-dd HH:mm:ss')
-CALL TO_DATE('2021-04-08T13:34:45','yyyy-MM-dd''T''HH:mm:ss')
-注意SQL函数中的`'`填写时需要转义为`''`。
+```sql
+-- 日期时间示例
+CALL PARSEDATETIME('2021-04-08 13:34:45', 'yyyy-MM-dd HH:mm:ss')
+CALL TO_DATE('2021-04-08T13:34:45', 'yyyy-MM-dd''T''HH:mm:ss')
+CALL PARSEDATETIME('2024-06-15 14:30:45.123', 'yyyy-MM-dd HH:mm:ss.SSS')
+CALL PARSEDATETIME('2021/04/08 13:34:45', 'yyyy/MM/dd HH:mm:ss')
+CALL PARSEDATETIME('20210408133445', 'yyyyMMddHHmmss')
+
+-- 日期示例
+CALL TO_DATE('2021-04-08', 'yyyy-MM-dd')
+CALL TO_DATE('2021/04/08', 'yyyy/MM/dd')
+CALL TO_DATE('20210408', 'yyyyMMdd')
+
+-- 时间示例
+CALL PARSEDATETIME('14:30:45', 'HH:mm:ss')
+CALL PARSEDATETIME('14:30:45.123', 'HH:mm:ss.SSS')
+CALL PARSEDATETIME('143045', 'HHmmss')
+```
### QUARTER
diff --git
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaDateTimeFormat.java
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaDateTimeFormat.java
new file mode 100644
index 0000000000..84412f4a8f
--- /dev/null
+++
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaDateTimeFormat.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.transform.sql.zeta;
+
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Optional;
+
+public enum ZetaDateTimeFormat {
+ // DateTime formats
+ DATETIME_STANDARD("yyyy-MM-dd HH:mm:ss", FormatType.DATETIME),
+ DATETIME_WITH_MILLIS("yyyy-MM-dd HH:mm:ss.SSS", FormatType.DATETIME),
+ DATETIME_ISO8601("yyyy-MM-dd'T'HH:mm:ss", FormatType.DATETIME),
+ DATETIME_ISO8601_WITH_MILLIS("yyyy-MM-dd'T'HH:mm:ss.SSS",
FormatType.DATETIME),
+ DATETIME_SLASH("yyyy/MM/dd HH:mm:ss", FormatType.DATETIME),
+ DATETIME_SLASH_WITH_MILLIS("yyyy/MM/dd HH:mm:ss.SSS", FormatType.DATETIME),
+ DATETIME_COMPACT("yyyyMMddHHmmss", FormatType.DATETIME),
+
+ // Date formats
+ DATE_ISO8601("yyyy-MM-dd", FormatType.DATE),
+ DATE_SLASH("yyyy/MM/dd", FormatType.DATE),
+ DATE_COMPACT("yyyyMMdd", FormatType.DATE),
+
+ // Time formats
+ TIME_STANDARD("HH:mm:ss", FormatType.TIME),
+ TIME_WITH_MILLIS("HH:mm:ss.SSS", FormatType.TIME),
+ TIME_COMPACT("HHmmss", FormatType.TIME);
+
+ private final String pattern;
+ private final FormatType type;
+ private final DateTimeFormatter formatter;
+
+ ZetaDateTimeFormat(String pattern, FormatType type) {
+ this.pattern = pattern;
+ this.type = type;
+ this.formatter = DateTimeFormatter.ofPattern(pattern);
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ public FormatType getType() {
+ return type;
+ }
+
+ public DateTimeFormatter getFormatter() {
+ return formatter;
+ }
+
+ public static Optional<ZetaDateTimeFormat> fromPattern(String pattern) {
+ return Arrays.stream(values()).filter(format ->
format.pattern.equals(pattern)).findFirst();
+ }
+
+ public enum FormatType {
+ DATETIME,
+ DATE,
+ TIME
+ }
+}
diff --git
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLType.java
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLType.java
index 73c92a17fe..5988f0869d 100644
---
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLType.java
+++
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLType.java
@@ -26,6 +26,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
import org.apache.seatunnel.api.table.type.SqlType;
import org.apache.seatunnel.api.table.type.VectorType;
+import org.apache.seatunnel.common.exception.CommonError;
import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
import org.apache.seatunnel.transform.exception.TransformException;
import org.apache.seatunnel.transform.sql.zeta.functions.ArrayFunction;
@@ -430,21 +431,34 @@ public class ZetaSQLType {
case ZetaSQLFunction.PARSEDATETIME:
case ZetaSQLFunction.TO_DATE:
{
- String format =
function.getParameters().getExpressions().get(1).toString();
- if (format.contains("yy") && format.contains("mm")) {
- return LocalTimeType.LOCAL_DATE_TIME_TYPE;
+ Expression formatExpr =
function.getParameters().getExpressions().get(1);
+ String format;
+ if (formatExpr instanceof StringValue) {
+ format = ((StringValue)
formatExpr).getNotExcapedValue();
+ } else {
+ throw CommonError.unsupportedOperation(
+ function.getName(), "non-literal format
parameter");
}
- if (format.contains("yy")) {
- return LocalTimeType.LOCAL_DATE_TYPE;
- }
- if (format.contains("mm")) {
- return LocalTimeType.LOCAL_TIME_TYPE;
+
+ ZetaDateTimeFormat dateTimeFormat =
+ ZetaDateTimeFormat.fromPattern(format)
+ .orElseThrow(
+ () ->
+
CommonError.illegalArgument(
+ format,
"unsupported datetime format"));
+
+ switch (dateTimeFormat.getType()) {
+ case DATETIME:
+ return LocalTimeType.LOCAL_DATE_TIME_TYPE;
+ case DATE:
+ return LocalTimeType.LOCAL_DATE_TYPE;
+ case TIME:
+ return LocalTimeType.LOCAL_TIME_TYPE;
+ default:
+ throw CommonError.illegalArgument(
+ dateTimeFormat.getType().toString(),
+ "unsupported datetime format type");
}
- throw new TransformException(
- CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION,
- String.format(
- "Unknown pattern letter %s for function:
%s",
- format, function.getName()));
}
case ZetaSQLFunction.ABS:
case ZetaSQLFunction.DATEADD:
diff --git
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunction.java
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunction.java
index bfb00f9dfa..4539acd093 100644
---
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunction.java
+++
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunction.java
@@ -17,8 +17,10 @@
package org.apache.seatunnel.transform.sql.zeta.functions;
+import org.apache.seatunnel.common.exception.CommonError;
import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
import org.apache.seatunnel.transform.exception.TransformException;
+import org.apache.seatunnel.transform.sql.zeta.ZetaDateTimeFormat;
import org.apache.seatunnel.transform.sql.zeta.ZetaSQLFunction;
import java.text.DateFormatSymbols;
@@ -32,6 +34,7 @@ import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.WeekFields;
@@ -619,10 +622,38 @@ public class DateTimeFunction {
}
public static boolean isDate(List<Object> args) {
+ String str = (String) args.get(0);
+ if (str == null || str.isEmpty()) {
+ return false;
+ }
+
+ String format = (String) args.get(1);
+ if (format == null) {
+ return false;
+ }
+
+ ZetaDateTimeFormat dateTimeFormat =
ZetaDateTimeFormat.fromPattern(format).orElse(null);
+ if (dateTimeFormat == null) {
+ return false;
+ }
+
try {
- parsedatetime(args);
- return true;
- } catch (Throwable e) {
+ DateTimeFormatter formatter = dateTimeFormat.getFormatter();
+
+ switch (dateTimeFormat.getType()) {
+ case DATETIME:
+ LocalDateTime.parse(str, formatter);
+ return true;
+ case DATE:
+ LocalDate.parse(str, formatter);
+ return true;
+ case TIME:
+ LocalTime.parse(str, formatter);
+ return true;
+ default:
+ return false;
+ }
+ } catch (DateTimeParseException e) {
return false;
}
}
@@ -633,23 +664,32 @@ public class DateTimeFunction {
return null;
}
String format = (String) args.get(1);
- if (format.contains("yy") && format.contains("mm")) {
- DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
- return LocalDateTime.parse(str, df);
- }
- if (format.contains("yy")) {
- DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
- return LocalDate.parse(str, df);
- }
- if (format.contains("mm")) {
- DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
- return LocalTime.parse(str, df);
- }
- throw new TransformException(
- CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION,
- String.format(
- "Unknown pattern letter %s for function: %s",
- format, ZetaSQLFunction.PARSEDATETIME));
+
+ ZetaDateTimeFormat dateTimeFormat =
+ ZetaDateTimeFormat.fromPattern(format)
+ .orElseThrow(
+ () ->
+ CommonError.illegalArgument(
+ format, "unsupported datetime
format"));
+
+ try {
+ DateTimeFormatter formatter = dateTimeFormat.getFormatter();
+
+ switch (dateTimeFormat.getType()) {
+ case DATETIME:
+ return LocalDateTime.parse(str, formatter);
+ case DATE:
+ return LocalDate.parse(str, formatter);
+ case TIME:
+ return LocalTime.parse(str, formatter);
+ default:
+ throw CommonError.illegalArgument(
+ dateTimeFormat.getType().toString(),
+ "unsupported datetime format type");
+ }
+ } catch (DateTimeParseException e) {
+ throw CommonError.illegalArgument(str, "parsing datetime with
format: " + format);
+ }
}
public static Integer quarter(List<Object> args) {
diff --git
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaDateTimeFormatTest.java
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaDateTimeFormatTest.java
new file mode 100644
index 0000000000..af68ca9986
--- /dev/null
+++
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaDateTimeFormatTest.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.transform.sql.zeta;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+public class ZetaDateTimeFormatTest {
+
+ @Test
+ public void testFromPatternWithAllDateTimeFormats() {
+ // DATETIME_STANDARD
+ Optional<ZetaDateTimeFormat> format1 =
+ ZetaDateTimeFormat.fromPattern("yyyy-MM-dd HH:mm:ss");
+ Assertions.assertTrue(format1.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_STANDARD,
format1.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format1.get().getType());
+
+ // DATETIME_WITH_MILLIS
+ Optional<ZetaDateTimeFormat> format2 =
+ ZetaDateTimeFormat.fromPattern("yyyy-MM-dd HH:mm:ss.SSS");
+ Assertions.assertTrue(format2.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_WITH_MILLIS,
format2.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format2.get().getType());
+
+ // DATETIME_ISO8601
+ Optional<ZetaDateTimeFormat> format3 =
+ ZetaDateTimeFormat.fromPattern("yyyy-MM-dd'T'HH:mm:ss");
+ Assertions.assertTrue(format3.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_ISO8601,
format3.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format3.get().getType());
+
+ // DATETIME_ISO8601_WITH_MILLIS
+ Optional<ZetaDateTimeFormat> format4 =
+ ZetaDateTimeFormat.fromPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
+ Assertions.assertTrue(format4.isPresent());
+
Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_ISO8601_WITH_MILLIS,
format4.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format4.get().getType());
+
+ // DATETIME_SLASH
+ Optional<ZetaDateTimeFormat> format5 =
+ ZetaDateTimeFormat.fromPattern("yyyy/MM/dd HH:mm:ss");
+ Assertions.assertTrue(format5.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_SLASH,
format5.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format5.get().getType());
+
+ // DATETIME_SLASH_WITH_MILLIS
+ Optional<ZetaDateTimeFormat> format6 =
+ ZetaDateTimeFormat.fromPattern("yyyy/MM/dd HH:mm:ss.SSS");
+ Assertions.assertTrue(format6.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_SLASH_WITH_MILLIS,
format6.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format6.get().getType());
+
+ // DATETIME_COMPACT
+ Optional<ZetaDateTimeFormat> format7 =
ZetaDateTimeFormat.fromPattern("yyyyMMddHHmmss");
+ Assertions.assertTrue(format7.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATETIME_COMPACT,
format7.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATETIME,
format7.get().getType());
+ }
+
+ @Test
+ public void testFromPatternWithAllDateFormats() {
+ // DATE_ISO8601
+ Optional<ZetaDateTimeFormat> format1 =
ZetaDateTimeFormat.fromPattern("yyyy-MM-dd");
+ Assertions.assertTrue(format1.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATE_ISO8601,
format1.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATE,
format1.get().getType());
+
+ // DATE_SLASH
+ Optional<ZetaDateTimeFormat> format2 =
ZetaDateTimeFormat.fromPattern("yyyy/MM/dd");
+ Assertions.assertTrue(format2.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATE_SLASH, format2.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATE,
format2.get().getType());
+
+ // DATE_COMPACT
+ Optional<ZetaDateTimeFormat> format3 =
ZetaDateTimeFormat.fromPattern("yyyyMMdd");
+ Assertions.assertTrue(format3.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.DATE_COMPACT,
format3.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.DATE,
format3.get().getType());
+ }
+
+ @Test
+ public void testFromPatternWithAllTimeFormats() {
+ // TIME_STANDARD
+ Optional<ZetaDateTimeFormat> format1 =
ZetaDateTimeFormat.fromPattern("HH:mm:ss");
+ Assertions.assertTrue(format1.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.TIME_STANDARD,
format1.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.TIME,
format1.get().getType());
+
+ // TIME_WITH_MILLIS
+ Optional<ZetaDateTimeFormat> format2 =
ZetaDateTimeFormat.fromPattern("HH:mm:ss.SSS");
+ Assertions.assertTrue(format2.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.TIME_WITH_MILLIS,
format2.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.TIME,
format2.get().getType());
+
+ // TIME_COMPACT
+ Optional<ZetaDateTimeFormat> format3 =
ZetaDateTimeFormat.fromPattern("HHmmss");
+ Assertions.assertTrue(format3.isPresent());
+ Assertions.assertEquals(ZetaDateTimeFormat.TIME_COMPACT,
format3.get());
+ Assertions.assertEquals(ZetaDateTimeFormat.FormatType.TIME,
format3.get().getType());
+ }
+
+ @Test
+ public void testFromPatternWithInvalidFormat() {
+ Optional<ZetaDateTimeFormat> format =
ZetaDateTimeFormat.fromPattern("invalid_pattern");
+
+ Assertions.assertFalse(format.isPresent());
+ }
+
+ @Test
+ public void testFromPatternWithNullFormat() {
+ Optional<ZetaDateTimeFormat> format =
ZetaDateTimeFormat.fromPattern(null);
+
+ Assertions.assertFalse(format.isPresent());
+ }
+
+ @Test
+ public void testAllDateTimeFormatsHaveCorrectType() {
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_STANDARD.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_WITH_MILLIS.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_ISO8601.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_ISO8601_WITH_MILLIS.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_SLASH.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_SLASH_WITH_MILLIS.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATETIME,
+ ZetaDateTimeFormat.DATETIME_COMPACT.getType());
+ }
+
+ @Test
+ public void testAllDateFormatsHaveCorrectType() {
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATE,
ZetaDateTimeFormat.DATE_ISO8601.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATE,
ZetaDateTimeFormat.DATE_SLASH.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.DATE,
ZetaDateTimeFormat.DATE_COMPACT.getType());
+ }
+
+ @Test
+ public void testAllTimeFormatsHaveCorrectType() {
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.TIME,
ZetaDateTimeFormat.TIME_STANDARD.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.TIME,
ZetaDateTimeFormat.TIME_WITH_MILLIS.getType());
+ Assertions.assertEquals(
+ ZetaDateTimeFormat.FormatType.TIME,
ZetaDateTimeFormat.TIME_COMPACT.getType());
+ }
+
+ @Test
+ public void testGetPatternForAllFormats() {
+ Assertions.assertEquals(
+ "yyyy-MM-dd HH:mm:ss",
ZetaDateTimeFormat.DATETIME_STANDARD.getPattern());
+ Assertions.assertEquals(
+ "yyyy-MM-dd HH:mm:ss.SSS",
ZetaDateTimeFormat.DATETIME_WITH_MILLIS.getPattern());
+ Assertions.assertEquals(
+ "yyyy-MM-dd'T'HH:mm:ss",
ZetaDateTimeFormat.DATETIME_ISO8601.getPattern());
+ Assertions.assertEquals(
+ "yyyy-MM-dd'T'HH:mm:ss.SSS",
+ ZetaDateTimeFormat.DATETIME_ISO8601_WITH_MILLIS.getPattern());
+ Assertions.assertEquals(
+ "yyyy/MM/dd HH:mm:ss",
ZetaDateTimeFormat.DATETIME_SLASH.getPattern());
+ Assertions.assertEquals(
+ "yyyy/MM/dd HH:mm:ss.SSS",
+ ZetaDateTimeFormat.DATETIME_SLASH_WITH_MILLIS.getPattern());
+ Assertions.assertEquals("yyyyMMddHHmmss",
ZetaDateTimeFormat.DATETIME_COMPACT.getPattern());
+
+ Assertions.assertEquals("yyyy-MM-dd",
ZetaDateTimeFormat.DATE_ISO8601.getPattern());
+ Assertions.assertEquals("yyyy/MM/dd",
ZetaDateTimeFormat.DATE_SLASH.getPattern());
+ Assertions.assertEquals("yyyyMMdd",
ZetaDateTimeFormat.DATE_COMPACT.getPattern());
+
+ Assertions.assertEquals("HH:mm:ss",
ZetaDateTimeFormat.TIME_STANDARD.getPattern());
+ Assertions.assertEquals("HH:mm:ss.SSS",
ZetaDateTimeFormat.TIME_WITH_MILLIS.getPattern());
+ Assertions.assertEquals("HHmmss",
ZetaDateTimeFormat.TIME_COMPACT.getPattern());
+ }
+
+ @Test
+ public void testFromPatternIsCaseSensitive() {
+ Optional<ZetaDateTimeFormat> format =
ZetaDateTimeFormat.fromPattern("YYYY-MM-DD HH:MM:SS");
+
+ Assertions.assertFalse(format.isPresent());
+ }
+
+ @Test
+ public void testAllEnumValuesAreUnique() {
+ ZetaDateTimeFormat[] formats = ZetaDateTimeFormat.values();
+
+ for (int i = 0; i < formats.length; i++) {
+ for (int j = i + 1; j < formats.length; j++) {
+ Assertions.assertNotEquals(
+ formats[i].getPattern(),
+ formats[j].getPattern(),
+ "Duplicate pattern found: " + formats[i].getPattern());
+ }
+ }
+ }
+
+ @Test
+ public void testFormatterIsCached() {
+ ZetaDateTimeFormat format = ZetaDateTimeFormat.DATETIME_STANDARD;
+ Assertions.assertNotNull(format.getFormatter());
+ Assertions.assertSame(
+ format.getFormatter(),
+ format.getFormatter(),
+ "Formatter should be cached and return the same instance");
+ }
+
+ @Test
+ public void testAllFormatsHaveValidFormatter() {
+ for (ZetaDateTimeFormat format : ZetaDateTimeFormat.values()) {
+ Assertions.assertNotNull(
+ format.getFormatter(),
+ "Format " + format.name() + " should have a valid
formatter");
+ }
+ }
+
+ @Test
+ public void testFormatterCanParseValidInput() {
+ ZetaDateTimeFormat format = ZetaDateTimeFormat.DATE_ISO8601;
+ Assertions.assertDoesNotThrow(
+ () -> java.time.LocalDate.parse("2024-06-15",
format.getFormatter()));
+
+ ZetaDateTimeFormat compactFormat = ZetaDateTimeFormat.DATE_COMPACT;
+ Assertions.assertDoesNotThrow(
+ () -> java.time.LocalDate.parse("20240615",
compactFormat.getFormatter()));
+
+ ZetaDateTimeFormat slashFormat = ZetaDateTimeFormat.DATE_SLASH;
+ Assertions.assertDoesNotThrow(
+ () -> java.time.LocalDate.parse("2024/06/15",
slashFormat.getFormatter()));
+ }
+}
diff --git
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLTypeTest.java
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLTypeTest.java
index 0499b701ba..c56e4f6ed4 100644
---
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLTypeTest.java
+++
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/ZetaSQLTypeTest.java
@@ -26,6 +26,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
import org.apache.seatunnel.api.table.type.SqlType;
import org.apache.seatunnel.api.table.type.VectorType;
+import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException;
import org.apache.seatunnel.transform.exception.TransformException;
import org.apache.seatunnel.transform.sql.zeta.functions.udf.DesEncrypt;
@@ -271,7 +272,8 @@ public class ZetaSQLTypeTest {
badPattern.setParameters(
new ExpressionList<>(
Arrays.asList(new StringValue("data"), new
StringValue("invalid"))));
- Assertions.assertThrows(TransformException.class, () ->
type.getExpressionType(badPattern));
+ Assertions.assertThrows(
+ SeaTunnelRuntimeException.class, () ->
type.getExpressionType(badPattern));
Assertions.assertEquals(
LocalTimeType.LOCAL_DATE_TYPE,
diff --git
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunctionsTest.java
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunctionsTest.java
index 7cd773d1cc..3b67ebe871 100644
---
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunctionsTest.java
+++
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/DateTimeFunctionsTest.java
@@ -24,6 +24,7 @@ import org.apache.seatunnel.api.table.type.LocalTimeType;
import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
import org.apache.seatunnel.api.table.type.SeaTunnelRow;
import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException;
import org.apache.seatunnel.transform.exception.TransformException;
import org.apache.seatunnel.transform.sql.SQLTransform;
@@ -303,7 +304,7 @@ public class DateTimeFunctionsTest {
new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
Assertions.assertThrows(
- TransformException.class,
+ SeaTunnelRuntimeException.class,
() ->
runSql(
"select PARSEDATETIME('2021-04-08',
'invalid_pattern') as parsed from dual",
@@ -326,4 +327,167 @@ public class DateTimeFunctionsTest {
rowType,
LocalDate.of(2024, 6, 15)));
}
+
+ @Test
+ public void testParseDateTimeWithAllDateTimeFormats() {
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"dummy"},
+ new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
+
+ // DATETIME_STANDARD: yyyy-MM-dd HH:mm:ss
+ SeaTunnelRow row1 =
+ runSql(
+ "select PARSEDATETIME('2024-06-15 14:30:45',
'yyyy-MM-dd HH:mm:ss') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDateTime.of(2024, 6, 15, 14, 30, 45),
row1.getField(0));
+
+ // DATETIME_WITH_MILLIS: yyyy-MM-dd HH:mm:ss.SSS
+ SeaTunnelRow row2 =
+ runSql(
+ "select PARSEDATETIME('2024-06-15 14:30:45.123',
'yyyy-MM-dd HH:mm:ss.SSS') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(
+ LocalDateTime.of(2024, 6, 15, 14, 30, 45, 123000000),
row2.getField(0));
+
+ // DATETIME_ISO8601: yyyy-MM-dd'T'HH:mm:ss
+ SeaTunnelRow row3 =
+ runSql(
+ "select PARSEDATETIME('2024-06-15T14:30:45',
'yyyy-MM-dd''T''HH:mm:ss') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDateTime.of(2024, 6, 15, 14, 30, 45),
row3.getField(0));
+
+ // DATETIME_ISO8601_WITH_MILLIS: yyyy-MM-dd'T'HH:mm:ss.SSS
+ SeaTunnelRow row4 =
+ runSql(
+ "select PARSEDATETIME('2024-06-15T14:30:45.987',
'yyyy-MM-dd''T''HH:mm:ss.SSS') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(
+ LocalDateTime.of(2024, 6, 15, 14, 30, 45, 987000000),
row4.getField(0));
+
+ // DATETIME_SLASH: yyyy/MM/dd HH:mm:ss
+ SeaTunnelRow row5 =
+ runSql(
+ "select PARSEDATETIME('2024/06/15 14:30:45',
'yyyy/MM/dd HH:mm:ss') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDateTime.of(2024, 6, 15, 14, 30, 45),
row5.getField(0));
+
+ // DATETIME_SLASH_WITH_MILLIS: yyyy/MM/dd HH:mm:ss.SSS
+ SeaTunnelRow row6 =
+ runSql(
+ "select PARSEDATETIME('2024/06/15 14:30:45.123',
'yyyy/MM/dd HH:mm:ss.SSS') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(
+ LocalDateTime.of(2024, 6, 15, 14, 30, 45, 123000000),
row6.getField(0));
+
+ // DATETIME_COMPACT: yyyyMMddHHmmss
+ SeaTunnelRow row7 =
+ runSql(
+ "select PARSEDATETIME('20240615143045',
'yyyyMMddHHmmss') as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDateTime.of(2024, 6, 15, 14, 30, 45),
row7.getField(0));
+ }
+
+ @Test
+ public void testParseDateTimeWithAllTimeFormats() {
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"dummy"},
+ new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
+
+ // TIME_STANDARD: HH:mm:ss
+ SeaTunnelRow row1 =
+ runSql(
+ "select PARSEDATETIME('14:30:45', 'HH:mm:ss') as dt
from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(java.time.LocalTime.of(14, 30, 45),
row1.getField(0));
+
+ // TIME_WITH_MILLIS: HH:mm:ss.SSS
+ SeaTunnelRow row2 =
+ runSql(
+ "select PARSEDATETIME('14:30:45.123', 'HH:mm:ss.SSS')
as dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(java.time.LocalTime.of(14, 30, 45, 123000000),
row2.getField(0));
+
+ // TIME_COMPACT: HHmmss
+ SeaTunnelRow row3 =
+ runSql(
+ "select PARSEDATETIME('143045', 'HHmmss') as dt from
dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(java.time.LocalTime.of(14, 30, 45),
row3.getField(0));
+ }
+
+ @Test
+ public void testParseDateTimeWithUnsupportedFormat() {
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"dummy"},
+ new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
+
+ Assertions.assertThrows(
+ SeaTunnelRuntimeException.class,
+ () ->
+ runSql(
+ "select PARSEDATETIME('2024-06-15',
'dd/MM/yyyy') as dt from dual",
+ rowType,
+ LocalDateTime.now()));
+ }
+
+ @Test
+ public void testParseDateTimeWithMalformedInput() {
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"dummy"},
+ new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
+
+ Assertions.assertThrows(
+ SeaTunnelRuntimeException.class,
+ () ->
+ runSql(
+ "select PARSEDATETIME('not-a-date',
'yyyy-MM-dd') as dt from dual",
+ rowType,
+ LocalDateTime.now()));
+ }
+
+ @Test
+ public void testParseDateTimeWithAllDateFormats() {
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"dummy"},
+ new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
+
+ // DATE_ISO8601: yyyy-MM-dd
+ SeaTunnelRow row1 =
+ runSql(
+ "select TO_DATE('2024-06-15', 'yyyy-MM-dd') as dt from
dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDate.of(2024, 6, 15), row1.getField(0));
+
+ // DATE_SLASH: yyyy/MM/dd
+ SeaTunnelRow row2 =
+ runSql(
+ "select PARSEDATETIME('2024/06/15', 'yyyy/MM/dd') as
dt from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDate.of(2024, 6, 15), row2.getField(0));
+
+ // DATE_COMPACT: yyyyMMdd
+ SeaTunnelRow row3 =
+ runSql(
+ "select PARSEDATETIME('20240615', 'yyyyMMdd') as dt
from dual",
+ rowType,
+ LocalDateTime.now());
+ Assertions.assertEquals(LocalDate.of(2024, 6, 15), row3.getField(0));
+ }
}