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

jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new 2806466afac [To dev/1.3] Improve schema metadata error messages and 
recovery logs (#18937) (#18975)
2806466afac is described below

commit 2806466afacbbd1d280af228dd541c136d14743f
Author: Caideyipi <[email protected]>
AuthorDate: Thu Oct 22 11:42:43 2026 +0800

    [To dev/1.3] Improve schema metadata error messages and recovery logs 
(#18937) (#18975)
---
 .../org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java |   2 +-
 .../iotdb/db/i18n/DataNodeSchemaMessages.java      |  26 ++++
 .../iotdb/db/i18n/DataNodeSchemaMessages.java      |  19 +++
 .../apache/iotdb/db/schemaengine/SchemaEngine.java |   9 +-
 .../schemaregion/impl/SchemaRegionMemoryImpl.java  |  12 +-
 .../schemaregion/utils/MetaFormatUtils.java        |  41 +++++--
 .../template/ClusterTemplateManager.java           |  32 +++--
 .../iotdb/db/metadata/MetaFormatUtilsTest.java     | 110 +++++++++++++++++
 .../template/ClusterTemplateManagerTest.java       | 131 +++++++++++++++++++++
 9 files changed, 349 insertions(+), 33 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
index ddb298da4f5..d69c62532ef 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
@@ -99,7 +99,7 @@ public class IoTDBSimpleQueryIT {
       } catch (Exception e) {
         assertEquals(
             TSStatusCode.ILLEGAL_PARAMETER.getStatusCode()
-                + ": SDT compression deviation cannot be negative. Failed to 
create timeseries for path root.sg1.d0.s1",
+                + ": SDT property compdev must be non-negative, but was [-2]. 
Failed to create timeseries for path root.sg1.d0.s1",
             e.getMessage());
       }
 
diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
index df31f7e084d..4b027a8a644 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
@@ -26,4 +26,30 @@ public final class DataNodeSchemaMessages {
   public static final String
       
EXCEPTION_CANNOT_RECOVER_DUPLICATED_SCHEMAREGION_ARG_FOUND_IN_DATABASES_ARG_AND_ARG_D9F60E05
 =
           "Cannot recover duplicated SchemaRegion [%s] found in databases [%s] 
and [%s].";
+
+  public static final String FAILED_TO_RECOVER_TAG_INDEX =
+      "Failed to recover tag index for measurement [{}] in database [{}], 
SchemaRegion [{}], tag offset [{}].";
+  public static final String SCHEMA_REGION_FAILED_TO_RECOVER =
+      "SchemaRegion [%d] in StorageGroup [%s] failed to recover.";
+  public static final String SDT_COMPRESSION_DEVIATION_REQUIRED =
+      "SDT compression requires the compdev property";
+  public static final String SDT_COMPRESSION_DEVIATION_NEGATIVE =
+      "SDT property compdev must be non-negative, but was [%s]";
+  public static final String SDT_COMPRESSION_DEVIATION_FORMAT_ERROR =
+      "SDT property compdev must be a number, but was [%s]";
+  public static final String SDT_COMPRESSION_MAX_GREATER_THAN_MIN =
+      "SDT property compmaxtime [%s] must be greater than compmintime [%s]";
+  public static final String SDT_COMPRESSION_TIME_NEGATIVE =
+      "SDT property %s must be non-negative, but was [%s]";
+  public static final String SDT_COMPRESSION_TIME_FORMAT_ERROR =
+      "SDT property %s must be a 64-bit integer, but was [%s]";
+  public static final String SDT_ENABLED_NO_COMPRESSION_TIME =
+      "Timeseries [{}] uses SDT without property [{}]; using default [{}].";
+  public static final String CREATE_TEMPLATE_ERROR =
+      "Failed to create schema template [%s] on ConfigNode: %s";
+  public static final String GET_ALL_TEMPLATE_ERROR =
+      "Failed to get schema templates from ConfigNode: %s";
+  public static final String GET_TEMPLATE_INFO_ERROR =
+      "Failed to get schema template [%s] from ConfigNode: %s";
+  public static final String ILLEGAL_PATH_LOG = "illegal path {}";
 }
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
index 5fd43c669fc..7c94694932e 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeSchemaMessages.java
@@ -26,4 +26,23 @@ public final class DataNodeSchemaMessages {
   public static final String
       
EXCEPTION_CANNOT_RECOVER_DUPLICATED_SCHEMAREGION_ARG_FOUND_IN_DATABASES_ARG_AND_ARG_D9F60E05
 =
           "无法恢复重复的 SchemaRegion [%s],其同时存在于数据库 [%s] 和 [%s] 中。";
+
+  public static final String FAILED_TO_RECOVER_TAG_INDEX =
+      "恢复测点 [{}] 的 tag 索引失败,数据库为 [{}],SchemaRegion 为 [{}],tag 偏移量为 [{}]。";
+  public static final String SCHEMA_REGION_FAILED_TO_RECOVER =
+      "SchemaRegion [%d] 在 StorageGroup [%s] 中恢复失败。";
+  public static final String SDT_COMPRESSION_DEVIATION_REQUIRED = "SDT 压缩必须设置 
compdev 属性";
+  public static final String SDT_COMPRESSION_DEVIATION_NEGATIVE = "SDT 属性 
compdev 必须为非负数,实际值为 [%s]";
+  public static final String SDT_COMPRESSION_DEVIATION_FORMAT_ERROR =
+      "SDT 属性 compdev 必须为数字,实际值为 [%s]";
+  public static final String SDT_COMPRESSION_MAX_GREATER_THAN_MIN =
+      "SDT 属性 compmaxtime [%s] 必须大于 compmintime [%s]";
+  public static final String SDT_COMPRESSION_TIME_NEGATIVE = "SDT 属性 %s 
必须为非负数,实际值为 [%s]";
+  public static final String SDT_COMPRESSION_TIME_FORMAT_ERROR = "SDT 属性 %s 
必须为 64 位整数,实际值为 [%s]";
+  public static final String SDT_ENABLED_NO_COMPRESSION_TIME =
+      "时间序列 [{}] 已启用 SDT 但未设置属性 [{}],使用默认值 [{}]。";
+  public static final String CREATE_TEMPLATE_ERROR = "在 ConfigNode 上创建元数据模板 
[%s] 失败:%s";
+  public static final String GET_ALL_TEMPLATE_ERROR = "从 ConfigNode 
获取元数据模板列表失败:%s";
+  public static final String GET_TEMPLATE_INFO_ERROR = "从 ConfigNode 获取元数据模板 
[%s] 失败:%s";
+  public static final String ILLEGAL_PATH_LOG = "非法路径 {}";
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
index 504eed7b904..abfc243ad91 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
@@ -342,11 +342,12 @@ public class SchemaEngine {
             timeRecord);
         return schemaRegion;
       } catch (MetadataException e) {
-        logger.error(
+        throw new RuntimeException(
             String.format(
-                "SchemaRegion [%d] in StorageGroup [%s] failed to recover.",
-                schemaRegionId.getId(), storageGroup.getFullPath()));
-        throw new RuntimeException(e);
+                DataNodeSchemaMessages.SCHEMA_REGION_FAILED_TO_RECOVER,
+                schemaRegionId.getId(),
+                storageGroup.getFullPath()),
+            e);
       }
     };
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
index 1c89a9b613b..65e8e2f95ee 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java
@@ -37,6 +37,7 @@ import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import 
org.apache.iotdb.db.exception.metadata.SchemaDirCreationFailureException;
 import org.apache.iotdb.db.exception.metadata.SchemaQuotaExceededException;
 import org.apache.iotdb.db.exception.metadata.SeriesOverflowException;
+import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
 import org.apache.iotdb.db.queryengine.common.schematree.ClusterSchemaTree;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.metedata.write.CreateTimeSeriesNode;
 import org.apache.iotdb.db.schemaengine.metric.ISchemaRegionMetric;
@@ -109,8 +110,6 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.apache.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR;
-
 /**
  * This class takes the responsibility of serialization of all the metadata 
info of one certain
  * schema region and persistent it into files. This class contains the 
interfaces to modify the
@@ -508,9 +507,12 @@ public class SchemaRegionMemoryImpl implements 
ISchemaRegion {
                   tagManager.recoverIndex(measurementMNode.getOffset(), 
measurementMNode);
                 } catch (IOException e) {
                   logger.error(
-                      "Failed to recover tagIndex for {} in schemaRegion {}.",
-                      storageGroupFullPath + PATH_SEPARATOR + 
measurementMNode.getFullPath(),
-                      schemaRegionId);
+                      DataNodeSchemaMessages.FAILED_TO_RECOVER_TAG_INDEX,
+                      measurementMNode.getName(),
+                      storageGroupFullPath,
+                      schemaRegionId,
+                      measurementMNode.getOffset(),
+                      e);
                 }
               },
               deviceMNode -> {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
index 239a2e0bd4f..b7f5c3d629e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException;
 import org.apache.iotdb.db.exception.metadata.IllegalParameterOfPathException;
+import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -145,17 +146,24 @@ public class MetaFormatUtils {
   private static void checkSDTFormat(String path, Map<String, String> props)
       throws IllegalParameterOfPathException {
     if (!props.containsKey(SDT_COMP_DEV)) {
-      throw new IllegalParameterOfPathException("SDT compression deviation is 
required", path);
+      throw new IllegalParameterOfPathException(
+          DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_REQUIRED, path);
     }
 
     try {
       double d = Double.parseDouble(props.get(SDT_COMP_DEV));
       if (d < 0) {
         throw new IllegalParameterOfPathException(
-            "SDT compression deviation cannot be negative", path);
+            String.format(
+                DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_NEGATIVE, 
props.get(SDT_COMP_DEV)),
+            path);
       }
     } catch (NumberFormatException e) {
-      throw new IllegalParameterOfPathException("SDT compression deviation 
formatting error", path);
+      throw new IllegalParameterOfPathException(
+          String.format(
+              DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_FORMAT_ERROR,
+              props.get(SDT_COMP_DEV)),
+          path);
     }
 
     long compMinTime = sdtCompressionTimeFormat(SDT_COMP_MIN_TIME, props, 
path);
@@ -163,7 +171,11 @@ public class MetaFormatUtils {
 
     if (compMaxTime <= compMinTime) {
       throw new IllegalParameterOfPathException(
-          "SDT compression maximum time needs to be greater than compression 
minimum time", path);
+          String.format(
+              DataNodeSchemaMessages.SDT_COMPRESSION_MAX_GREATER_THAN_MIN,
+              compMaxTime,
+              compMinTime),
+          path);
     }
   }
 
@@ -172,20 +184,23 @@ public class MetaFormatUtils {
       throws IllegalParameterOfPathException {
     boolean isCompMaxTime = compTime.equals(SDT_COMP_MAX_TIME);
     long time = isCompMaxTime ? Long.MAX_VALUE : 0;
-    String s = isCompMaxTime ? "maximum" : "minimum";
     if (props.containsKey(compTime)) {
+      String value = props.get(compTime);
       try {
-        time = Long.parseLong(props.get(compTime));
-        if (time < 0) {
-          throw new IllegalParameterOfPathException(
-              String.format("SDT compression %s time cannot be negative", s), 
path);
-        }
-      } catch (IllegalParameterOfPathException e) {
+        time = Long.parseLong(value);
+      } catch (NumberFormatException e) {
+        throw new IllegalParameterOfPathException(
+            String.format(
+                DataNodeSchemaMessages.SDT_COMPRESSION_TIME_FORMAT_ERROR, 
compTime, value),
+            path);
+      }
+      if (time < 0) {
         throw new IllegalParameterOfPathException(
-            String.format("SDT compression %s time formatting error", s), 
path);
+            
String.format(DataNodeSchemaMessages.SDT_COMPRESSION_TIME_NEGATIVE, compTime, 
value),
+            path);
       }
     } else {
-      logger.info("{} enabled SDT but did not set compression {} time", path, 
s);
+      logger.debug(DataNodeSchemaMessages.SDT_ENABLED_NO_COMPRESSION_TIME, 
path, compTime, time);
     }
     return time;
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
index 0350b7883e0..8e19e191da5 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
@@ -38,6 +38,7 @@ import 
org.apache.iotdb.confignode.rpc.thrift.TGetPathsSetTemplatesReq;
 import org.apache.iotdb.confignode.rpc.thrift.TGetPathsSetTemplatesResp;
 import org.apache.iotdb.confignode.rpc.thrift.TGetTemplateResp;
 import org.apache.iotdb.confignode.rpc.thrift.TSetSchemaTemplateReq;
+import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
 import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
 import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
 import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
@@ -130,9 +131,11 @@ public class ClusterTemplateManager implements 
ITemplateManager {
               e,
               TSStatusCode.CREATE_TEMPLATE_ERROR.getStatusCode()));
     } catch (ClientManagerException | TException e) {
-      throw new RuntimeException(
-          new IoTDBException(
-              "create template error.", e, 
TSStatusCode.CREATE_TEMPLATE_ERROR.getStatusCode()));
+      throw wrapTemplateRpcException(
+          String.format(
+              DataNodeSchemaMessages.CREATE_TEMPLATE_ERROR, 
statement.getName(), e.getMessage()),
+          e,
+          TSStatusCode.CREATE_TEMPLATE_ERROR);
     }
   }
 
@@ -175,9 +178,10 @@ public class ClusterTemplateManager implements 
ITemplateManager {
         throw new IoTDBRuntimeException(resp.getStatus());
       }
     } catch (ClientManagerException | TException e) {
-      throw new RuntimeException(
-          new IoTDBException(
-              "get all template error.", 
TSStatusCode.UNDEFINED_TEMPLATE.getStatusCode()));
+      throw wrapTemplateRpcException(
+          String.format(DataNodeSchemaMessages.GET_ALL_TEMPLATE_ERROR, 
e.getMessage()),
+          e,
+          TSStatusCode.UNDEFINED_TEMPLATE);
     }
     return templatesList;
   }
@@ -196,12 +200,20 @@ public class ClusterTemplateManager implements 
ITemplateManager {
         throw new IoTDBException(resp.status);
       }
     } catch (ClientManagerException | TException e) {
-      throw new RuntimeException(
-          new IoTDBException(
-              "get template info error.", 
TSStatusCode.UNDEFINED_TEMPLATE.getStatusCode()));
+      throw wrapTemplateRpcException(
+          String.format(DataNodeSchemaMessages.GET_TEMPLATE_INFO_ERROR, name, 
e.getMessage()),
+          e,
+          TSStatusCode.UNDEFINED_TEMPLATE);
     }
   }
 
+  private static RuntimeException wrapTemplateRpcException(
+      String message, Exception cause, TSStatusCode statusCode) {
+    IoTDBException exception = new IoTDBException(message, 
statusCode.getStatusCode());
+    exception.addSuppressed(cause);
+    return new RuntimeException(exception);
+  }
+
   @Override
   public void setSchemaTemplate(String queryId, String name, PartialPath path) 
{
     try (ConfigNodeClient configNodeClient =
@@ -256,7 +268,7 @@ public class ClusterTemplateManager implements 
ITemplateManager {
                     try {
                       listPath.add(new PartialPath(item));
                     } catch (IllegalPathException e) {
-                      e.printStackTrace();
+                      LOGGER.error(DataNodeSchemaMessages.ILLEGAL_PATH_LOG, 
item, e);
                     }
                   });
         }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
new file mode 100644
index 00000000000..7fc550a7fa3
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.iotdb.db.metadata;
+
+import org.apache.iotdb.db.exception.metadata.IllegalParameterOfPathException;
+import org.apache.iotdb.db.schemaengine.schemaregion.utils.MetaFormatUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class MetaFormatUtilsTest {
+
+  private static final String PATH = "root.sg.d.s";
+
+  @Test
+  public void testNonNumericSdtProperties() {
+    for (String property : new String[] {"compdev", "compmintime", 
"compmaxtime"}) {
+      assertInvalidProperty(property, "invalid");
+    }
+  }
+
+  @Test
+  public void testNegativeSdtProperties() {
+    for (String property : new String[] {"compdev", "compmintime", 
"compmaxtime"}) {
+      assertInvalidProperty(property, "-1");
+    }
+  }
+
+  @Test
+  public void testSdtTimeOverflow() {
+    for (String property : new String[] {"compmintime", "compmaxtime"}) {
+      assertInvalidProperty(property, "9223372036854775808");
+      assertInvalidProperty(property, "-9223372036854775809");
+    }
+  }
+
+  @Test
+  public void testInvalidSdtTimeRange() {
+    Map<String, String> props = sdtProps();
+    props.put("compmintime", "20");
+    props.put("compmaxtime", "10");
+    IllegalParameterOfPathException exception =
+        assertThrows(
+            IllegalParameterOfPathException.class,
+            () -> MetaFormatUtils.checkTimeseriesProps(PATH, props));
+
+    assertUserParameterError(exception);
+    assertTrue(exception.getMessage().contains("compmintime [20]"));
+    assertTrue(exception.getMessage().contains("compmaxtime [10]"));
+  }
+
+  @Test
+  public void testValidSdtTimeBoundsAndDefaults() throws 
IllegalParameterOfPathException {
+    Map<String, String> props = sdtProps();
+    MetaFormatUtils.checkTimeseriesProps(PATH, props);
+    props.put("compmintime", "0");
+    MetaFormatUtils.checkTimeseriesProps(PATH, props);
+    props.put("compmaxtime", Long.toString(Long.MAX_VALUE));
+    MetaFormatUtils.checkTimeseriesProps(PATH, props);
+  }
+
+  private static void assertInvalidProperty(String property, String value) {
+    Map<String, String> props = sdtProps();
+    props.put(property, value);
+    IllegalParameterOfPathException exception =
+        assertThrows(
+            IllegalParameterOfPathException.class,
+            () -> MetaFormatUtils.checkTimeseriesProps(PATH, props));
+
+    assertUserParameterError(exception);
+    assertTrue(exception.getMessage().contains(property));
+    assertTrue(exception.getMessage().contains("[" + value + "]"));
+  }
+
+  private static void assertUserParameterError(IllegalParameterOfPathException 
exception) {
+    assertEquals(TSStatusCode.ILLEGAL_PARAMETER.getStatusCode(), 
exception.getErrorCode());
+    assertTrue(exception.isUserException());
+    assertTrue(exception.getMessage().contains(PATH));
+  }
+
+  private static Map<String, String> sdtProps() {
+    Map<String, String> props = new HashMap<>();
+    props.put("loss", "sdt");
+    props.put("compdev", "0.01");
+    return props;
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
new file mode 100644
index 00000000000..c029542daf3
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.iotdb.db.schemaengine.template;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.IClientManager;
+import org.apache.iotdb.commons.consensus.ConfigRegionId;
+import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
+import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
+import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
+import org.apache.iotdb.db.protocol.thrift.OperationType;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.template.CreateSchemaTemplateStatement;
+import org.apache.iotdb.db.utils.ErrorHandlingUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.apache.thrift.TException;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(ConfigNodeClientManager.class)
+@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", 
"javax.management.*"})
+public class ClusterTemplateManagerTest {
+
+  @SuppressWarnings("unchecked")
+  private static final IClientManager<ConfigRegionId, ConfigNodeClient> 
CLIENT_MANAGER =
+      Mockito.mock(IClientManager.class);
+
+  private ConfigNodeClient client;
+
+  @Before
+  public void setUp() throws Exception {
+    Mockito.reset(CLIENT_MANAGER);
+    client = Mockito.mock(ConfigNodeClient.class);
+    PowerMockito.mockStatic(ConfigNodeClientManager.class);
+    
PowerMockito.when(ConfigNodeClientManager.getInstance()).thenReturn(CLIENT_MANAGER);
+    
Mockito.when(CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)).thenReturn(client);
+  }
+
+  @Test
+  public void testGetTemplateRpcFailure() throws Exception {
+    TException rpcFailure = new TException("connection refused");
+    Mockito.when(client.getTemplate("test_template")).thenThrow(rpcFailure);
+
+    RuntimeException exception =
+        assertThrows(
+            RuntimeException.class,
+            () -> 
ClusterTemplateManager.getInstance().getTemplate("test_template"));
+
+    TSStatus status = assertRpcFailure(exception, rpcFailure, 
TSStatusCode.UNDEFINED_TEMPLATE);
+    assertTrue(status.getMessage().contains("test_template"));
+  }
+
+  @Test
+  public void testGetAllTemplatesRpcFailure() throws Exception {
+    TException rpcFailure = new TException("connection refused");
+    Mockito.when(client.getAllTemplates()).thenThrow(rpcFailure);
+
+    RuntimeException exception =
+        assertThrows(
+            RuntimeException.class, () -> 
ClusterTemplateManager.getInstance().getAllTemplates());
+
+    assertRpcFailure(exception, rpcFailure, TSStatusCode.UNDEFINED_TEMPLATE);
+  }
+
+  @Test
+  public void testCreateTemplateRpcFailure() throws Exception {
+    TException rpcFailure = new TException("connection refused");
+    
Mockito.when(client.createSchemaTemplate(Mockito.any())).thenThrow(rpcFailure);
+    CreateSchemaTemplateStatement statement =
+        new CreateSchemaTemplateStatement(
+            "test_template",
+            Collections.singletonList(Collections.singletonList("s")),
+            
Collections.singletonList(Collections.singletonList(TSDataType.INT32)),
+            
Collections.singletonList(Collections.singletonList(TSEncoding.PLAIN)),
+            
Collections.singletonList(Collections.singletonList(CompressionType.UNCOMPRESSED)),
+            false);
+
+    RuntimeException exception =
+        assertThrows(
+            RuntimeException.class,
+            () -> 
ClusterTemplateManager.getInstance().createSchemaTemplate(statement));
+
+    TSStatus status = assertRpcFailure(exception, rpcFailure, 
TSStatusCode.CREATE_TEMPLATE_ERROR);
+    assertTrue(status.getMessage().contains("test_template"));
+  }
+
+  private static TSStatus assertRpcFailure(
+      RuntimeException exception, TException rpcFailure, TSStatusCode 
statusCode) {
+    TSStatus status =
+        ErrorHandlingUtils.onQueryException(exception, 
OperationType.EXECUTE_STATEMENT);
+    assertEquals(statusCode.getStatusCode(), status.getCode());
+    assertTrue(status.getMessage().contains("ConfigNode"));
+    assertTrue(status.getMessage().contains("connection refused"));
+    assertEquals(1, exception.getCause().getSuppressed().length);
+    assertSame(rpcFailure, exception.getCause().getSuppressed()[0]);
+    return status;
+  }
+}

Reply via email to