This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new cbf771c83aa branch-4.1: [fix](timestamptz) Report TIMESTAMPTZ as
string to MySQL clients #63292 (#63806)
cbf771c83aa is described below
commit cbf771c83aabb194bf7de8ab3c56b120fba9d739
Author: zclllyybb <[email protected]>
AuthorDate: Fri May 29 14:28:38 2026 +0800
branch-4.1: [fix](timestamptz) Report TIMESTAMPTZ as string to MySQL
clients #63292 (#63806)
pick https://github.com/apache/doris/pull/63292
---
.../org/apache/doris/catalog/PrimitiveType.java | 5 +-
.../org/apache/doris/mysql/MysqlSerializer.java | 7 +-
.../java/org/apache/doris/qe/StmtExecutor.java | 1 -
.../mysql/MysqlSerializerTimestampTzTest.java | 84 +++++++++++++++++++++
.../timestamptz/test_cast_timestamptz.out | 4 +-
.../timestamptz/test_timestamptz_cast.out | 12 +--
.../sql-functions/doc_date_functions_test.out | 48 ++++++------
.../paimon/test_paimon_catalog_timestamp_tz.out | 12 +--
.../datetime_functions/test_date_function.out | 2 +-
.../test_timestamptz_jdbc_binary_protocol.groovy | 85 ++++++++++++++++++++++
10 files changed, 215 insertions(+), 45 deletions(-)
diff --git
a/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
b/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
index deec4343659..216db92e216 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
@@ -427,9 +427,10 @@ public enum PrimitiveType {
case DATE:
case DATEV2:
return MysqlColType.MYSQL_TYPE_DATE;
+ case TIMESTAMPTZ:
+ return MysqlColType.MYSQL_TYPE_STRING;
case DATETIME:
- case DATETIMEV2:
- case TIMESTAMPTZ: {
+ case DATETIMEV2: {
if (isTimeType) {
return MysqlColType.MYSQL_TYPE_TIME;
} else {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSerializer.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSerializer.java
index 4c8d1824104..4469830f320 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSerializer.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSerializer.java
@@ -288,9 +288,11 @@ public class MysqlSerializer {
case DATEV2:
case DATE:
return 10;
+ case TIMESTAMPTZ:
+ // yyyy-MM-dd HH:mm:ss[.ffffff]+HH:mm
+ return 32;
case DATETIME:
- case DATETIMEV2:
- case TIMESTAMPTZ: {
+ case DATETIMEV2: {
if (type.getPrimitiveType().isTimeType()) {
return 10;
} else {
@@ -338,7 +340,6 @@ public class MysqlSerializer {
case DECIMAL256:
case TIMEV2:
case DATETIMEV2:
- case TIMESTAMPTZ:
return ((ScalarType) type).decimalScale();
case FLOAT:
case DOUBLE:
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
index 332af6ed9b2..da70264331c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
@@ -1807,7 +1807,6 @@ public class StmtExecutor {
break;
case DATETIME:
case DATETIMEV2:
- case TIMESTAMPTZ:
DateTimeV2Literal datetime = new
DateTimeV2Literal(item);
long microSecond = datetime.getMicroSecond();
//
https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response_text_resultset.html
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlSerializerTimestampTzTest.java
b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlSerializerTimestampTzTest.java
new file mode 100644
index 00000000000..7671a87c989
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlSerializerTimestampTzTest.java
@@ -0,0 +1,84 @@
+// 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.doris.mysql;
+
+import org.apache.doris.catalog.MysqlColType;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.catalog.Type;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class MysqlSerializerTimestampTzTest {
+
+ private static int leUInt2(byte[] a, int off) {
+ return (a[off] & 0xFF) | ((a[off + 1] & 0xFF) << 8);
+ }
+
+ private static long leUInt4(byte[] a, int off) {
+ return (a[off] & 0xFFL)
+ | ((a[off + 1] & 0xFFL) << 8)
+ | ((a[off + 2] & 0xFFL) << 16)
+ | ((a[off + 3] & 0xFFL) << 24);
+ }
+
+ private static int skipLenEncodedString(byte[] buf, int offset) {
+ int strLen = buf[offset] & 0xFF;
+ return offset + 1 + strLen;
+ }
+
+ private static int skipFieldHeaderStrings(byte[] buf) {
+ int off = 0;
+ off = skipLenEncodedString(buf, off); // Catalog: "def"
+ off = skipLenEncodedString(buf, off); // Schema: ""
+ off = skipLenEncodedString(buf, off); // Table: ""
+ off = skipLenEncodedString(buf, off); // Origin Table: ""
+ off = skipLenEncodedString(buf, off); // Name
+ off = skipLenEncodedString(buf, off); // Original Name
+ return off + 1; // Length of the fixed fields, emitted as
writeVInt(0x0c).
+ }
+
+ @Test
+ public void testFieldPacketForTimestampTzUsesStringMetadata() {
+ MysqlSerializer ser = MysqlSerializer.newInstance();
+ Type type = ScalarType.createTimeStampTzType(6);
+ ser.writeField("ts", type);
+ byte[] out = ser.toArray();
+
+ Assertions.assertEquals(MysqlColType.MYSQL_TYPE_STRING,
type.getPrimitiveType().toMysqlType());
+
+ int off = skipFieldHeaderStrings(out);
+ int charset = leUInt2(out, off);
+ Assertions.assertEquals(33, charset); // utf8_general_ci
+ off += 2;
+
+ long displayLen = leUInt4(out, off);
+ Assertions.assertEquals(32L, displayLen);
+ off += 4;
+
+ int colType = out[off] & 0xFF;
+ Assertions.assertEquals(MysqlColType.MYSQL_TYPE_STRING.getCode(),
colType);
+ off += 1;
+
+ int flags = leUInt2(out, off);
+ Assertions.assertEquals(0, flags);
+ off += 2;
+
+ Assertions.assertEquals(0, out[off] & 0xFF);
+ }
+}
diff --git
a/regression-test/data/datatype_p0/timestamptz/test_cast_timestamptz.out
b/regression-test/data/datatype_p0/timestamptz/test_cast_timestamptz.out
index 0e2afc198e0..d9021431ae7 100644
--- a/regression-test/data/datatype_p0/timestamptz/test_cast_timestamptz.out
+++ b/regression-test/data/datatype_p0/timestamptz/test_cast_timestamptz.out
@@ -15,7 +15,7 @@
\N \N \N \N \N \N \N \N
-- !sql --
-2020-01-01T00:00:00.000000124
+2020-01-01 00:00:00.124+07:00
-- !sql --
2020-01-01T04:00:00.124
@@ -33,7 +33,7 @@
2020-01-01T04:00 2020-06-01T16:34:56 2021-01-01T03:59:59
2020-01-01T10:00 2020-06-01T22:34:56 2021-01-01T09:59:59
2020-01-01T07:00 2020-06-01T19:34:56 2021-01-01T06:59:59
-- !sql --
-2020-01-01T00:00:00.000000124
+2020-01-01 00:00:00.124+07:00
-- !sql --
2020-01-01T04:00:00.124
diff --git
a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_cast.out
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_cast.out
index 5774f9f82aa..983496b5601 100644
--- a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_cast.out
+++ b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_cast.out
@@ -12,12 +12,12 @@
2020-01-02 00:00:00.00000+08:00
-- !cast_1 --
-1 2020-01-01T05:00:00.000000123 2020-01-01 05:00:00.12300+08:00
-2 2020-06-01T15:00:00.000000456 2020-06-01 15:00:00.45600+08:00
-3 2020-01-01T07:59:59.000000789 2020-01-01 07:59:59.78900+08:00
+1 2020-01-01 05:00:00.123+08:00 2020-01-01 05:00:00.12300+08:00
+2 2020-06-01 15:00:00.456+08:00 2020-06-01 15:00:00.45600+08:00
+3 2020-01-01 07:59:59.789+08:00 2020-01-01 07:59:59.78900+08:00
-- !cast_2 --
-1 2020-01-01 05:00:00.12345+08:00 2020-01-01T05:00:00.000000123
-2 2020-06-01 15:00:00.45678+08:00 2020-06-01T15:00:00.000000457
-3 2020-01-01 07:59:59.78901+08:00 2020-01-01T07:59:59.000000789
+1 2020-01-01 05:00:00.12345+08:00 2020-01-01 05:00:00.123+08:00
+2 2020-06-01 15:00:00.45678+08:00 2020-06-01 15:00:00.457+08:00
+3 2020-01-01 07:59:59.78901+08:00 2020-01-01 07:59:59.789+08:00
diff --git
a/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
b/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
index 63cdecd6d68..65f09452a2d 100644
---
a/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
+++
b/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
@@ -681,13 +681,13 @@ gdaskpdp
2023-01-02 08:00:00+08:00
-- !hours_add_8 --
-2024-03-01T13:30:45.000000123
+2024-03-01 13:30:45.123+08:00
-- !hours_add_9 --
0001-01-03 08:00:00+08:00
-- !hours_add_10 --
-2023-06-15T08:45:30.000000555
+2023-06-15 08:45:30.555+08:00
-- !hours_add_11 --
\N
@@ -735,13 +735,13 @@ gdaskpdp
2022-12-31 08:00:00+08:00
-- !hours_sub_8 --
-2024-02-29T13:30:45.000000123
+2024-02-29 13:30:45.123+08:00
-- !hours_sub_9 --
0000-12-30 08:00:00+08:00
-- !hours_sub_10 --
-2023-06-15T20:45:30.000000555
+2023-06-15 20:45:30.555+08:00
-- !hours_sub_11 --
\N
@@ -1113,13 +1113,13 @@ gdaskpdp
2023-01-01 09:00:00+08:00
-- !minutes_add_9 --
-2024-03-01T02:19:56.000000123
+2024-03-01 02:19:56.123+08:00
-- !minutes_add_10 --
0001-01-01 08:01:00+08:00
-- !minutes_add_11 --
-2023-06-15T14:15:30.000000555
+2023-06-15 14:15:30.555+08:00
-- !minutes_add_12 --
\N
@@ -1167,13 +1167,13 @@ gdaskpdp
2023-01-01 07:00:00+08:00
-- !minutes_sub_8 --
-2024-03-01T00:49:56.000000123
+2024-03-01 00:49:56.123+08:00
-- !minutes_sub_9 --
0001-01-01 07:59:00+08:00
-- !minutes_sub_10 --
-2023-06-15T15:15:30.000000555
+2023-06-15 15:15:30.555+08:00
-- !minutes_sub_11 --
\N
@@ -1647,13 +1647,13 @@ da fanadur
2023-07-01 08:00:00+08:00
-- !quarters_add_10 --
-2024-05-30T01:30:45.000000123
+2024-05-30 01:30:45.123+08:00
-- !quarters_add_11 --
0002-01-01 08:00:00+08:00
-- !quarters_add_12 --
-2022-12-15T14:45:30.000000555
+2022-12-15 14:45:30.555+08:00
-- !quarters_add_13 --
\N
@@ -1686,13 +1686,13 @@ da fanadur
2022-07-01 08:00:00+08:00
-- !quarters_sub_10 --
-2023-11-30T01:30:45.000000123
+2023-11-30 01:30:45.123+08:00
-- !quarters_sub_11 --
0000-01-01 08:00:00+08:00
-- !quarters_sub_12 --
-2023-12-15T14:45:30.000000555
+2023-12-15 14:45:30.555+08:00
-- !quarters_sub_13 --
\N
@@ -1776,13 +1776,13 @@ da fanadur
2023-01-01 09:00:00+08:00
-- !seconds_add_9 --
-2024-03-01T01:35:11.000000123
+2024-03-01 01:35:11.123+08:00
-- !seconds_add_10 --
0001-01-01 08:01:00+08:00
-- !seconds_add_11 --
-2023-06-15T14:45:00.000000555
+2023-06-15 14:45:00.555+08:00
-- !seconds_add_12 --
\N
@@ -1830,13 +1830,13 @@ da fanadur
2023-01-01 07:00:00+08:00
-- !seconds_sub_9 --
-2024-03-01T01:34:41.000000123
+2024-03-01 01:34:41.123+08:00
-- !seconds_sub_10 --
0001-01-01 07:59:00+08:00
-- !seconds_sub_11 --
-2023-06-15T14:46:00.000000555
+2023-06-15 14:46:00.555+08:00
-- !seconds_sub_12 --
\N
@@ -2232,13 +2232,13 @@ da fanadur
2023-01-15 08:00:00+08:00
-- !weeks_add_8 --
-2024-03-08T01:30:45.000000123
+2024-03-08 01:30:45.123+08:00
-- !weeks_add_9 --
0001-01-29 08:00:00+08:00
-- !weeks_add_10 --
-2023-06-01T14:45:30.000000555
+2023-06-01 14:45:30.555+08:00
-- !weeks_add_11 --
\N
@@ -2292,13 +2292,13 @@ da fanadur
2022-12-18 08:00:00+08:00
-- !weeks_sub_8 --
-2024-02-23T01:30:45.000000123
+2024-02-23 01:30:45.123+08:00
-- !weeks_sub_9 --
0000-12-04 08:00:00+08:00
-- !weeks_sub_10 --
-2023-06-29T14:45:30.000000555
+2023-06-29 14:45:30.555+08:00
-- !weeks_sub_11 --
\N
@@ -2418,13 +2418,13 @@ da fanadur
2025-01-01 08:00:00+08:00
-- !years_add_9 --
-2025-03-01T01:30:45.000000123
+2025-03-01 01:30:45.123+08:00
-- !years_add_10 --
0011-01-01 08:00:00+08:00
-- !years_add_11 --
-2020-06-15T14:45:30.000000555
+2020-06-15 14:45:30.555+08:00
-- !years_add_12 --
\N
@@ -2472,13 +2472,13 @@ da fanadur
2021-01-01 08:00:00+08:00
-- !years_sub_8 --
-2023-03-01T01:30:45.000000123
+2023-03-01 01:30:45.123+08:00
-- !years_sub_9 --
9989-01-01 08:00:00+08:00
-- !years_sub_10 --
-2026-06-15T14:45:30.000000555
+2026-06-15 14:45:30.555+08:00
-- !years_sub_11 --
0003-01-01 08:00:00+08:00
diff --git
a/regression-test/data/external_table_p0/paimon/test_paimon_catalog_timestamp_tz.out
b/regression-test/data/external_table_p0/paimon/test_paimon_catalog_timestamp_tz.out
index 4f9965aaed9..66207238741 100644
---
a/regression-test/data/external_table_p0/paimon/test_paimon_catalog_timestamp_tz.out
+++
b/regression-test/data/external_table_p0/paimon/test_paimon_catalog_timestamp_tz.out
@@ -4,9 +4,9 @@ id int Yes true \N
ts_ltz timestamptz(3) Yes true \N WITH_TIMEZONE
-- !jni_1 --
-1 2024-01-01T10:00
-2 2026-01-06T16:13:12
-3 2024-11-11T11:11:11.000000123
+1 2024-01-01 10:00:00.000+08:00
+2 2026-01-06 16:13:12.000+08:00
+3 2024-11-11 11:11:11.123+08:00
-- !jni_1_cast --
1 2024-01-01 10:00:00.000+08:00
@@ -18,9 +18,9 @@ id int Yes true \N
ts_ltz timestamptz(3) Yes true \N WITH_TIMEZONE
-- !native_1 --
-1 2024-01-01T10:00
-2 2026-01-06T16:13:12
-3 2024-11-11T11:11:11.000000123
+1 2024-01-01 10:00:00.000+08:00
+2 2026-01-06 16:13:12.000+08:00
+3 2024-11-11 11:11:11.123+08:00
-- !native_1_cast --
1 2024-01-01 10:00:00.000+08:00
diff --git
a/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function.out
b/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function.out
index 3f328482dfb..3c99b94771c 100644
---
a/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function.out
+++
b/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function.out
@@ -904,7 +904,7 @@ true
2022-01-20T00:00 2023-01-20T00:00
-- !sql --
-2023-08-17T17:41:18+08:00
+2023-08-17 17:41:18+08:00
-- !sql --
1694966400.000000 1694966400.000000
diff --git
a/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_jdbc_binary_protocol.groovy
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_jdbc_binary_protocol.groovy
new file mode 100644
index 00000000000..c04cc516770
--- /dev/null
+++
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_jdbc_binary_protocol.groovy
@@ -0,0 +1,85 @@
+// 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.
+
+import com.mysql.cj.jdbc.ServerPreparedStatement
+
+import java.sql.PreparedStatement
+import java.sql.ResultSet
+import java.sql.SQLException
+
+suite("test_timestamptz_jdbc_binary_protocol") {
+ String tableName = "test_timestamptz_jdbc_binary_protocol"
+ String dbName = "regression_test_datatype_p0_timestamptz"
+ def user = context.config.jdbcUser
+ def password = context.config.jdbcPassword
+
+ sql "SET time_zone = '+00:00'"
+ sql "DROP TABLE IF EXISTS ${tableName}"
+ sql """
+ CREATE TABLE ${tableName} (
+ id INT,
+ ts TIMESTAMPTZ(6),
+ note VARCHAR(16)
+ )
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES("replication_num" = "1")
+ """
+ sql """
+ INSERT INTO ${tableName} VALUES
+ (1, NULL, 'null'),
+ (2, '2024-01-01 08:00:00.123456 +08:00', 'equiv_utc'),
+ (3, '2024-01-01 00:00:01.654321 +00:00', 'micro')
+ """
+
+ String url = getServerPrepareJdbcUrl(context.config.jdbcUrl, dbName) +
+ "&emulateUnsupportedPstmts=true&useLocalSessionState=true"
+ logger.info("jdbc prepare statement url: ${url}")
+
+ connect(user, password, url) {
+ sql "SET time_zone = '+00:00'"
+
+ PreparedStatement stmt = prepareStatement("""
+ SELECT id, ts, CAST(ts AS STRING) AS ts_text, note
+ FROM ${tableName}
+ ORDER BY id
+ """)
+ assertEquals(ServerPreparedStatement, stmt.class)
+
+ ResultSet rs = stmt.executeQuery()
+ int rowCount = 0
+ try {
+ while (rs.next()) {
+ rowCount++
+ String direct
+ try {
+ direct = rs.getString(2)
+ } catch (SQLException e) {
+ logger.info("failed to read TIMESTAMPTZ directly with
ResultSet.getString", e)
+ throw e
+ }
+
+ String castText = rs.getString(3)
+ assertEquals(castText, direct)
+ }
+ assertEquals(3, rowCount)
+ } finally {
+ rs.close()
+ stmt.close()
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]