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 8c08ceb4616 branch-4.1: [fix](type) Deserialize legacy TIME metadata 
as TIMEV2 (#69877)
8c08ceb4616 is described below

commit 8c08ceb4616dc4a0baaf31c819817f2a093f6af9
Author: morrySnow <[email protected]>
AuthorDate: Wed Nov 4 10:35:21 2026 +0800

    branch-4.1: [fix](type) Deserialize legacy TIME metadata as TIMEV2 (#69877)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #69876, #54220
    
    Backport of #69876 to `branch-4.1`.
    
    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.1 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 8c5994dec6e..57727fc600e 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
@@ -66,6 +66,8 @@ public enum PrimitiveType {
     AGG_STATE("AGG_STATE", 16, TPrimitiveType.AGG_STATE, true),
     DATEV2("DATEV2", 4, TPrimitiveType.DATEV2, true),
     DATETIMEV2("DATETIMEV2", 8, TPrimitiveType.DATETIMEV2, 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]

Reply via email to