This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new f4759da19a2 branch-4.2: [fix](type) Deserialize legacy TIME metadata
as TIMEV2 (#69878)
f4759da19a2 is described below
commit f4759da19a20a53a697f1d1b2885bcc9e1112d00
Author: morrySnow <[email protected]>
AuthorDate: Mon Nov 2 09:19:22 2026 +0800
branch-4.2: [fix](type) Deserialize legacy TIME metadata as TIMEV2 (#69878)
### What problem does this PR solve?
Issue Number: None
Related PR: #69876, #54220
Backport of #69876 to `branch-4.2`.
Problem Summary: Older FE versions can persist the enum name `TIME` in
view column schemas. After that enum was removed, Gson reads the name as
null. Restoring such a view from an image or a create-table journal
leaves an invalid column type, and `information_schema.columns` fails in
`describeTables` when converting the type to Thrift.
Add `TIME` as a deserialization alias of `PrimitiveType.TIMEV2` through
Gson's existing enum adapter. New metadata continues to serialize as
`TIMEV2`. Tests cover the enum and scalar type, ordinary and nested
columns, current precision preservation, and view recovery through both
image and journal entry points.
### Release note
Fix `information_schema.columns` queries failing after an upgrade when
existing view metadata contains the legacy TIME type.
### Check List (For Author)
- Test: Unit Test / Checkstyle
- `./run-fe-ut.sh --run
org.apache.doris.catalog.LegacyTimeTypeGsonCompatibilityTest,org.apache.doris.catalog.ColumnGsonSerializationTest`:
8 tests passed on dev-hk.
- The six new compatibility tests reproduce the failure on master before
the fix, including the original null-pointer exception on the image and
journal paths.
- `mvn checkstyle:check -pl fe-common,fe-core`: passed.
- Validation ran on the development branch; this PR replays the
identical fix and test class onto the current upstream branch-4.2 base.
- Behavior changed: Yes; persisted TIME is loaded as TIMEV2, while
serialization continues to write TIMEV2.
- Does this need documentation: No
---
.../org/apache/doris/catalog/PrimitiveType.java | 2 +
.../LegacyTimeTypeGsonCompatibilityTest.java | 120 +++++++++++++++++++++
2 files changed, 122 insertions(+)
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 1159729218a..c7f618b6875 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
@@ -68,6 +68,8 @@ public enum PrimitiveType {
DATEV2("DATEV2", 4, TPrimitiveType.DATEV2, true),
DATETIMEV2("DATETIMEV2", 8, TPrimitiveType.DATETIMEV2, true),
TIMESTAMP_NS("TIMESTAMP_NS", 8, TPrimitiveType.TIMESTAMP_NS, true),
+ // Legacy view schemas persisted TIME before the v1 type was removed.
+ @SerializedName(value = "TIMEV2", alternate = {"TIME"})
TIMEV2("TIMEV2", 8, TPrimitiveType.TIMEV2, false),
TIMESTAMPTZ("TIMESTAMPTZ", 8, TPrimitiveType.TIMESTAMPTZ, false),
LAMBDA_FUNCTION("LAMBDA_FUNCTION", 16, TPrimitiveType.LAMBDA_FUNCTION,
false),
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/LegacyTimeTypeGsonCompatibilityTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/LegacyTimeTypeGsonCompatibilityTest.java
new file mode 100644
index 00000000000..8b4eda73de5
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/catalog/LegacyTimeTypeGsonCompatibilityTest.java
@@ -0,0 +1,120 @@
+// 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.catalog;
+
+import org.apache.doris.common.io.Text;
+import org.apache.doris.persist.CreateTableInfo;
+import org.apache.doris.persist.gson.GsonUtils;
+import org.apache.doris.thrift.TPrimitiveType;
+
+import com.google.gson.JsonParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.Collections;
+
+public class LegacyTimeTypeGsonCompatibilityTest {
+ @Test
+ public void testLegacyPrimitiveType() {
+ PrimitiveType legacy = GsonUtils.GSON.fromJson("\"TIME\"",
PrimitiveType.class);
+ Assert.assertEquals(PrimitiveType.TIMEV2, legacy);
+ Assert.assertEquals(PrimitiveType.TIMEV2,
+ GsonUtils.GSON.fromJson("\"TIMEV2\"", PrimitiveType.class));
+ Assert.assertEquals("\"TIMEV2\"", GsonUtils.GSON.toJson(legacy));
+ }
+
+ @Test
+ public void testLegacyScalarType() {
+ String json =
"{\"clazz\":\"ScalarType\",\"type\":\"TIME\",\"len\":-1,\"precision\":0,\"scale\":0}";
+ ScalarType restored = (ScalarType) GsonUtils.GSON.fromJson(json,
Type.class);
+ Assert.assertEquals(PrimitiveType.TIMEV2, restored.getPrimitiveType());
+ Assert.assertEquals(0, restored.getScalarScale());
+ Assert.assertEquals(TPrimitiveType.TIMEV2,
restored.getPrimitiveType().toThrift());
+ Assert.assertEquals("TIMEV2", JsonParser.parseString(
+ GsonUtils.GSON.toJson(restored,
Type.class)).getAsJsonObject().get("type").getAsString());
+ }
+
+ @Test
+ public void testLegacyColumnAndCurrentPrecision() {
+ Column column = new Column("legacy_time",
ScalarType.createTimeV2Type(0));
+ Column restored = GsonUtils.GSON.fromJson(legacyTimeJson(column,
Column.class), Column.class);
+ Assert.assertEquals(PrimitiveType.TIMEV2, restored.getDataType());
+ Assert.assertEquals(TPrimitiveType.TIMEV2,
restored.getDataType().toThrift());
+ Assert.assertEquals(column, restored);
+
+ Column preciseColumn = new Column("precise_time",
ScalarType.createTimeV2Type(6));
+ Column preciseRestored =
GsonUtils.GSON.fromJson(GsonUtils.GSON.toJson(preciseColumn), Column.class);
+ Assert.assertEquals(preciseColumn, preciseRestored);
+ Assert.assertEquals(6, preciseRestored.getScale());
+ }
+
+ @Test
+ public void testLegacyNestedColumn() {
+ Column column = new Column("nested_time", new StructType(
+ new StructField("times", new
ArrayType(ScalarType.createTimeV2Type(0), true)),
+ new StructField("lookup", new MapType(Type.STRING,
ScalarType.createTimeV2Type(0)))));
+ Column restored = GsonUtils.GSON.fromJson(legacyTimeJson(column,
Column.class), Column.class);
+ Assert.assertEquals(column, restored);
+ Assert.assertEquals(PrimitiveType.TIMEV2,
+
restored.getChildren().get(0).getChildren().get(0).getDataType());
+ Assert.assertEquals(PrimitiveType.TIMEV2,
+
restored.getChildren().get(1).getChildren().get(1).getDataType());
+ }
+
+ @Test
+ public void testLegacyViewImage() throws Exception {
+ View view = legacyTimeView();
+ Table restored = Table.read(metadataInput(legacyTimeJson(view,
Table.class)));
+ assertRestoredView(restored);
+ }
+
+ @Test
+ public void testLegacyViewJournal() throws Exception {
+ CreateTableInfo info = new CreateTableInfo("test", 2L,
legacyTimeView());
+ Table restored =
CreateTableInfo.read(metadataInput(legacyTimeJson(info,
CreateTableInfo.class))).getTable();
+ assertRestoredView(restored);
+ }
+
+ private static View legacyTimeView() {
+ return new View(1L, "legacy_time_view", Collections.singletonList(
+ new Column("legacy_time", ScalarType.createTimeV2Type(0))));
+ }
+
+ private static String legacyTimeJson(Object value, Class<?> type) {
+ // Old FE metadata persisted the enum name TIME, which has since been
removed.
+ return GsonUtils.GSON.toJson(value, type).replace("\"TIMEV2\"",
"\"TIME\"");
+ }
+
+ private static DataInputStream metadataInput(String json) throws Exception
{
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ Text.writeString(new DataOutputStream(bytes), json);
+ return new DataInputStream(new
ByteArrayInputStream(bytes.toByteArray()));
+ }
+
+ private static void assertRestoredView(Table restored) {
+ Assert.assertTrue(restored instanceof View);
+ Assert.assertEquals(1, restored.getFullSchema().size());
+ Column column = restored.getFullSchema().get(0);
+ Assert.assertEquals("legacy_time", column.getName());
+ Assert.assertEquals(TPrimitiveType.TIMEV2,
column.getDataType().toThrift());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]