This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new b14cf27cb48 branch-3.1: [opt](catalog) add error msg for catalog init 
and timezone info of column #54825 #55233 (#55231)
b14cf27cb48 is described below

commit b14cf27cb48e266dc8a0b914a2169d2889abbddf
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sun Aug 24 20:02:06 2025 -0700

    branch-3.1: [opt](catalog) add error msg for catalog init and timezone info 
of column #54825 #55233 (#55231)
    
    bp #54825 #55233
---
 .../create_preinstalled_scripts/paimon/run08.sql   |   5 +-
 .../org/apache/doris/analysis/ShowCatalogStmt.java |   1 +
 .../main/java/org/apache/doris/catalog/Column.java |  12 +++++
 .../doris/common/proc/IndexSchemaProcNode.java     |   4 ++
 .../org/apache/doris/datasource/CatalogIf.java     |   4 ++
 .../org/apache/doris/datasource/CatalogMgr.java    |   6 +--
 .../apache/doris/datasource/ExternalCatalog.java   |  16 ++++++
 .../doris/datasource/iceberg/IcebergUtils.java     |   6 +++
 .../datasource/paimon/PaimonExternalTable.java     |   4 ++
 .../tablefunction/PaimonTableValuedFunction.java   |   1 -
 .../apache/doris/analysis/ShowCatalogStmtTest.java |   4 +-
 .../write/test_iceberg_write_timestamp_ntz.out     | Bin 257 -> 358 bytes
 .../test_paimon_timestamp_with_time_zone.out       | Bin 483 -> 595 bytes
 .../write/test_iceberg_write_timestamp_ntz.groovy  |   3 ++
 .../test_paimon_timestamp_with_time_zone.groovy    |   2 +
 ....groovy => test_show_catalogs_error_msg.groovy} |  55 +++++++++++++--------
 16 files changed, 95 insertions(+), 28 deletions(-)

diff --git 
a/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run08.sql
 
b/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run08.sql
index 073d26548a0..06bc3fc91ae 100644
--- 
a/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run08.sql
+++ 
b/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run08.sql
@@ -4,7 +4,8 @@ use test_paimon_spark;
 
 SET TIME ZONE '+08:00';
 
-CREATE TABLE IF NOT EXISTS t_ts_ntz (
+DROP TABLE IF EXISTS t_ts_ntz;
+CREATE TABLE t_ts_ntz (
   id INT,
   ts TIMESTAMP,
   ts_ntz TIMESTAMP_NTZ
@@ -12,4 +13,4 @@ CREATE TABLE IF NOT EXISTS t_ts_ntz (
 
 INSERT INTO t_ts_ntz VALUES
   (1, CAST('2025-08-12 06:00:00+00:00' AS TIMESTAMP), CAST('2025-08-12 
06:00:00' AS TIMESTAMP_NTZ)),
-  (2, CAST('2025-08-12 14:00:00+08:00' AS TIMESTAMP), CAST('2025-08-12 
14:00:00' AS TIMESTAMP_NTZ));
\ No newline at end of file
+  (2, CAST('2025-08-12 14:00:00+08:00' AS TIMESTAMP), CAST('2025-08-12 
14:00:00' AS TIMESTAMP_NTZ));
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowCatalogStmt.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowCatalogStmt.java
index f1770859002..ef8289745db 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowCatalogStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowCatalogStmt.java
@@ -34,6 +34,7 @@ public class ShowCatalogStmt extends ShowStmt implements 
NotFallbackInParser {
                     .addColumn(new Column("CreateTime", 
ScalarType.createStringType()))
                     .addColumn(new Column("LastUpdateTime", 
ScalarType.createStringType()))
                     .addColumn(new Column("Comment", 
ScalarType.createStringType()))
+                    .addColumn(new Column("ErrorMsg", 
ScalarType.createStringType()))
                     .build();
 
     private static final ShowResultSetMetaData META_DATA_SPECIFIC =
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java
index b92114369f3..47cca3b90bb 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java
@@ -162,6 +162,11 @@ public class Column implements GsonPostProcessable {
     @SerializedName(value = "fpt")
     private TPatternType fieldPatternType;
 
+    // used for saving some extra information, such as timezone info of 
datetime column
+    // Maybe deprecated if we implement real timestamp with timezone type.
+    @SerializedName(value = "ei")
+    private String extraInfo;
+
     public Column() {
         this.name = "";
         this.type = Type.NULL;
@@ -1213,6 +1218,10 @@ public class Column implements GsonPostProcessable {
         this.uniqueId = colUniqueId;
     }
 
+    public void setWithTZExtraInfo() {
+        this.extraInfo = Strings.isNullOrEmpty(extraInfo) ? "WITH_TIMEZONE" : 
extraInfo + ", WITH_TIMEZONE";
+    }
+
     public int getUniqueId() {
         return this.uniqueId;
     }
@@ -1280,4 +1289,7 @@ public class Column implements GsonPostProcessable {
         this.realDefaultValue = refColumn.realDefaultValue;
     }
 
+    public String getExtraInfo() {
+        return extraInfo;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/IndexSchemaProcNode.java
 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/IndexSchemaProcNode.java
index 31373ac781c..3630dac0c57 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/IndexSchemaProcNode.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/IndexSchemaProcNode.java
@@ -23,6 +23,7 @@ import org.apache.doris.common.FeConstants;
 import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
@@ -80,6 +81,9 @@ public class IndexSchemaProcNode implements ProcNodeInterface 
{
             if (column.getGeneratedColumnInfo() != null) {
                 extras.add("STORED GENERATED");
             }
+            if (!Strings.isNullOrEmpty(column.getExtraInfo())) {
+                extras.add(column.getExtraInfo());
+            }
             String extraStr = StringUtils.join(extras, ",");
             String comment = column.getComment();
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java
index 646510e01d0..e9a346a9102 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java
@@ -62,6 +62,10 @@ public interface CatalogIf<T extends DatabaseIf> {
 
     List<String> getDbNames();
 
+    default String getErrorMsg() {
+        return "";
+    }
+
     default boolean isInternalCatalog() {
         return this instanceof InternalCatalog;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java
index 7ee865576b9..2b863b7e113 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java
@@ -66,6 +66,7 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -386,12 +387,11 @@ public class CatalogMgr implements Writable, 
GsonPostProcessable {
                     row.add(createTime);
                     
row.add(TimeUtils.longToTimeString(catalog.getLastUpdateTime()));
                     row.add(catalog.getComment());
+                    row.add(Strings.nullToEmpty(catalog.getErrorMsg()));
                     rows.add(row);
 
                     // sort by catalog name
-                    rows.sort((x, y) -> {
-                        return x.get(1).compareTo(y.get(1));
-                    });
+                    rows.sort(Comparator.comparing(x -> x.get(1)));
                 }
             } else {
                 if (!nameToCatalog.containsKey(showStmt.getCatalogName())) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
index abee493b7f4..156deefca72 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
@@ -82,6 +82,7 @@ import com.google.common.collect.Sets;
 import com.google.gson.annotations.SerializedName;
 import org.apache.commons.lang3.NotImplementedException;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.math.NumberUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.logging.log4j.LogManager;
@@ -161,6 +162,11 @@ public abstract class ExternalCatalog
     @SerializedName(value = "comment")
     private String comment;
 
+    // Save the error info if initialization fails.
+    // can be seen in `show catalogs` result.
+    // no need to persist this field.
+    private String errorMsg = "";
+
     // db name does not contains "default_cluster"
     protected Map<String, Long> dbNameToId = Maps.newConcurrentMap();
     private boolean objectCreated = false;
@@ -329,7 +335,12 @@ public abstract class ExternalCatalog
                     init();
                 }
                 initialized = true;
+                this.errorMsg = "";
             }
+        } catch (Exception e) {
+            LOG.warn("failed to init catalog {}:{}", name, id, e);
+            this.errorMsg = ExceptionUtils.getRootCauseMessage(e);
+            throw new RuntimeException("Failed to init catalog: " + name + ", 
error: " + this.errorMsg, e);
         } finally {
             isInitializing = false;
         }
@@ -631,6 +642,11 @@ public abstract class ExternalCatalog
         this.comment = comment;
     }
 
+    @Override
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
     /**
      * Different from 'listDatabases()', this method will return dbnames from 
cache.
      * while 'listDatabases()' will return dbnames from remote datasource.
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
index f14d5c69b3e..0bba739c179 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
@@ -748,6 +748,12 @@ public class IcebergUtils {
                             IcebergUtils.icebergTypeToDorisType(field.type()), 
true, null,
                             true, field.doc(), true, -1);
             updateIcebergColumnUniqueId(column, field);
+            if (field.type().isPrimitiveType() && field.type().typeId() == 
TypeID.TIMESTAMP) {
+                Types.TimestampType timestampType = (Types.TimestampType) 
field.type();
+                if (timestampType.shouldAdjustToUTC()) {
+                    column.setWithTZExtraInfo();
+                }
+            }
             resSchema.add(column);
         }
         return resSchema;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java
index 9f492123ea1..bdb45a0eb9f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java
@@ -60,6 +60,7 @@ import org.apache.paimon.table.DataTable;
 import org.apache.paimon.table.Table;
 import org.apache.paimon.table.source.Split;
 import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataTypeRoot;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -249,6 +250,9 @@ public class PaimonExternalTable extends ExternalTable 
implements MTMVRelatedTab
                         PaimonUtil.paimonTypeToDorisType(field.type()), true, 
null, true, field.description(), true,
                         -1);
                 PaimonUtil.updatePaimonColumnUniqueId(column, field);
+                if (field.type().getTypeRoot() == 
DataTypeRoot.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
+                    column.setWithTZExtraInfo();
+                }
                 dorisColumns.add(column);
                 if (partitionColumnNames.contains(field.name())) {
                     partitionColumns.add(column);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/PaimonTableValuedFunction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/PaimonTableValuedFunction.java
index 5fdd3b1846b..0827374e92e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/PaimonTableValuedFunction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/PaimonTableValuedFunction.java
@@ -97,7 +97,6 @@ public class PaimonTableValuedFunction extends 
MetadataTableValuedFunction {
         this.paimonSysTable = 
paimonExternalCatalog.getPaimonTable(buildNameMapping,
                 "main", queryType);
         this.schema = PaimonUtil.parseSchema(paimonSysTable);
-
     }
 
     public static PaimonTableValuedFunction create(Map<String, String> params) 
throws AnalysisException {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/analysis/ShowCatalogStmtTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/analysis/ShowCatalogStmtTest.java
index 56e33f1b1f9..0fc763ca20a 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/analysis/ShowCatalogStmtTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/analysis/ShowCatalogStmtTest.java
@@ -30,14 +30,14 @@ public class ShowCatalogStmtTest {
         ShowCatalogStmt stmt = new ShowCatalogStmt();
         stmt.analyze(analyzer);
         Assert.assertNull(stmt.getCatalogName());
-        Assert.assertEquals(7, stmt.getMetaData().getColumnCount());
+        Assert.assertEquals(8, stmt.getMetaData().getColumnCount());
         Assert.assertEquals("SHOW CATALOGS", stmt.toSql());
 
         stmt = new ShowCatalogStmt(null, "%hive%");
         stmt.analyze(analyzer);
         Assert.assertNull(stmt.getCatalogName());
         Assert.assertNotNull(stmt.getPattern());
-        Assert.assertEquals(7, stmt.getMetaData().getColumnCount());
+        Assert.assertEquals(8, stmt.getMetaData().getColumnCount());
         Assert.assertEquals("SHOW CATALOGS LIKE '%hive%'", stmt.toSql());
 
         stmt = new ShowCatalogStmt("testCatalog", null);
diff --git 
a/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.out
 
b/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.out
index 1980a167f36..7276e6236df 100644
Binary files 
a/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.out
 and 
b/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.out
 differ
diff --git 
a/regression-test/data/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.out
 
b/regression-test/data/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.out
index 6c4acf47ef8..020bdd5f8f3 100644
Binary files 
a/regression-test/data/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.out
 and 
b/regression-test/data/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.out
 differ
diff --git 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
 
b/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
index 33418c0e247..2a4a04e3956 100644
--- 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
+++ 
b/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
@@ -55,6 +55,9 @@ suite("test_iceberg_write_timestamp_ntz", 
"p0,external,iceberg,external_docker,e
         sql "set time_zone = 'Asia/Shanghai'"
         qt_timestamp_ntz """select * from t_ntz_doris;"""
         qt_timestamp_tz  """select * from t_tz_doris;"""
+        // test Extra column in desc result
+        qt_desc01 """desc t_ntz_doris"""
+        qt_desc02 """desc t_tz_doris"""
 
         sql "set time_zone = 'Europe/Tirane'"
         qt_timestamp_ntz2 """select * from t_ntz_doris;"""
diff --git 
a/regression-test/suites/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.groovy
 
b/regression-test/suites/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.groovy
index 68a9a06522c..bcc5f930b32 100644
--- 
a/regression-test/suites/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.groovy
+++ 
b/regression-test/suites/external_table_p0/paimon/test_paimon_timestamp_with_time_zone.groovy
@@ -37,6 +37,8 @@ suite("test_paimon_timestamp_with_time_zone", 
"p0,external,doris,external_docker
             );
         """
         sql """use `${catalog_name}`.`${db_name}`;"""
+        // test Extra column in desc result
+        qt_desc_table """desc t_ts_ntz"""
         
         def test_select_timestamp = {
             qt_select_timestamp """ select * from t_ts_ntz order by id; """
diff --git 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
 b/regression-test/suites/external_table_p0/test_show_catalogs_error_msg.groovy
similarity index 51%
copy from 
regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
copy to 
regression-test/suites/external_table_p0/test_show_catalogs_error_msg.groovy
index 33418c0e247..e37bd446590 100644
--- 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_timestamp_ntz.groovy
+++ 
b/regression-test/suites/external_table_p0/test_show_catalogs_error_msg.groovy
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-suite("test_iceberg_write_timestamp_ntz", 
"p0,external,iceberg,external_docker,external_docker_iceberg") { 
+suite("test_show_catalogs_error_msg", 
"p0,external,iceberg,external_docker,external_docker_iceberg") { 
     
     String enabled = context.config.otherConfigs.get("enableIcebergTest")
     if (enabled == null || !enabled.equalsIgnoreCase("true")) {
@@ -23,43 +23,58 @@ suite("test_iceberg_write_timestamp_ntz", 
"p0,external,iceberg,external_docker,e
         return
     }
 
-  
     try {
-
-        String rest_port = 
context.config.otherConfigs.get("iceberg_rest_uri_port")
+        String rest_port = 
context.config.otherConfigs.get("iceberg_rest_uri_port");
         String minio_port = 
context.config.otherConfigs.get("iceberg_minio_port")
         String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
-        String catalog_name = "iceberg_timestamp_ntz_test"
+        String catalog_name = "test_show_catalogs_error_msg"
 
         sql """drop catalog if exists ${catalog_name}"""
+        // use wrong port 181812
         sql """
         CREATE CATALOG ${catalog_name} PROPERTIES (
                'type'='iceberg',
                'iceberg.catalog.type'='rest',
-               'uri' = 'http://${externalEnvIp}:${rest_port}',
+               'uri' = 'http://${externalEnvIp}:181812',
                "s3.access_key" = "admin",
                "s3.secret_key" = "password",
                "s3.endpoint" = "http://${externalEnvIp}:${minio_port}";,
                "s3.region" = "us-east-1"
          );"""
 
-        logger.info("catalog " + catalog_name + " created")
-        sql """switch ${catalog_name};"""
-        logger.info("switched to catalog " + catalog_name)
-        sql """ use test_db;""" 
+        test {
+            sql """show databases from ${catalog_name}"""
+            exception "is out of range"
+        }
 
-        sql """INSERT INTO t_ntz_doris VALUES ('2025-02-07 20:12:00');"""
-        sql """INSERT INTO t_tz_doris VALUES ('2025-02-07 20:12:01');"""
+        boolean found = false;
+        List<List<Object>> res = sql """show catalogs"""
+        for (List<Object> line : res) {
+            logger.info("get show catalogs line: " + line + ", name: " + 
line[1] + ", msg: " + line[7]);
+            if (line[1].equals("test_show_catalogs_error_msg")) {
+                if (line[7].contains("181812 is out of range")) {
+                    found = true;
+                    break;
+                }
+            }
+        }
+        assertTrue(found, "failed to find invalid catalog") 
 
-     
-        sql "set time_zone = 'Asia/Shanghai'"
-        qt_timestamp_ntz """select * from t_ntz_doris;"""
-        qt_timestamp_tz  """select * from t_tz_doris;"""
+        // change to right port, the error msg will be removed
+        sql """alter catalog test_show_catalogs_error_msg set properties('uri' 
= 'http://${externalEnvIp}:${rest_port}');"""
+        sql """show databases from test_show_catalogs_error_msg"""
+        res = sql """show catalogs"""
+        for (List<Object> line : res) {
+            logger.info("get show catalogs line: " + line + ", name: " + 
line[1] + ", msg: " + line[7]);
+            if (line[1].equals("test_show_catalogs_error_msg")) {
+                if (line[7].isEmpty()) {
+                    found = true;
+                    break;
+                }
+            }
+        }
+        assertTrue(found, "failed to find valid catalog") 
 
-        sql "set time_zone = 'Europe/Tirane'"
-        qt_timestamp_ntz2 """select * from t_ntz_doris;"""
-        qt_timestamp_tz2  """select * from t_tz_doris;"""
-      
         // sql """drop catalog if exists ${catalog_name}"""
 
     } finally {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to