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

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


The following commit(s) were added to refs/heads/master by this push:
     new fb0fb2095de Improve schema metadata error messages and recovery logs 
(#18937)
fb0fb2095de is described below

commit fb0fb2095de39d9eea199c79a0d490f8a18765ed
Author: Caideyipi <[email protected]>
AuthorDate: Wed Oct 21 19:35:46 2026 +0800

    Improve schema metadata error messages and recovery logs (#18937)
    
    * Improve schema metadata error messages and recovery logs
    
    * Fix SDT property error expectation in integration test
---
 .../org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java |   2 +-
 .../iotdb/db/i18n/DataNodeSchemaMessages.java      |  25 ++--
 .../iotdb/db/i18n/DataNodeSchemaMessages.java      |  25 ++--
 .../apache/iotdb/db/schemaengine/SchemaEngine.java |   6 +-
 .../schemaregion/impl/SchemaRegionMemoryImpl.java  |   9 +-
 .../schemaregion/utils/MetaFormatUtils.java        |  36 ++++--
 .../template/ClusterTemplateManager.java           |  36 +++---
 .../i18n/DataNodeSchemaMessagesZhFormatTest.java   |   8 +-
 .../iotdb/db/metadata/MetaFormatUtilsTest.java     | 111 +++++++++++++++++
 .../template/ClusterTemplateManagerTest.java       | 132 +++++++++++++++++++++
 10 files changed, 333 insertions(+), 57 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 677128d0bd8..599f2cb8611 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
@@ -111,7 +111,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 c0e0d67346b..06b0080d7a7 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
@@ -171,7 +171,7 @@ public final class DataNodeSchemaMessages {
   public static final String ERROR_DURING_INIT_SCHEMA_REGION =
       "Error occurred during initializing schemaRegion {}";
   public static final String FAILED_TO_RECOVER_TAG_INDEX =
-      "Failed to recover tagIndex for {} in schemaRegion {}.";
+      "Failed to recover tag index for measurement [{}] in database [{}], 
SchemaRegion [{}], tag offset [{}].";
   public static final String FAILED_TO_READ_TAG_ATTRIBUTE =
       "Failed to read tag and attribute info because {}";
 
@@ -213,19 +213,19 @@ public final class DataNodeSchemaMessages {
   public static final String DATABASE_NAME_ILLEGAL_CHARS =
       "The database name can only contain english or chinese characters, 
numbers, backticks and underscores. %s";
   public static final String SDT_COMPRESSION_DEVIATION_REQUIRED =
-      "SDT compression deviation is required";
+      "SDT compression requires the compdev property";
   public static final String SDT_COMPRESSION_DEVIATION_NEGATIVE =
-      "SDT compression deviation cannot be negative";
+      "SDT property compdev must be non-negative, but was [%s]";
   public static final String SDT_COMPRESSION_DEVIATION_FORMAT_ERROR =
-      "SDT compression deviation formatting error";
+      "SDT property compdev must be a number, but was [%s]";
   public static final String SDT_COMPRESSION_MAX_GREATER_THAN_MIN =
-      "SDT compression maximum time needs to be greater than compression 
minimum time";
+      "SDT property compmaxtime [%s] must be greater than compmintime [%s]";
   public static final String SDT_COMPRESSION_TIME_NEGATIVE =
-      "SDT compression %s time cannot be negative";
+      "SDT property %s must be non-negative, but was [%s]";
   public static final String SDT_COMPRESSION_TIME_FORMAT_ERROR =
-      "SDT compression %s time formatting error";
+      "SDT property %s must be a 64-bit integer, but was [%s]";
   public static final String SDT_ENABLED_NO_COMPRESSION_TIME =
-      "{} enabled SDT but did not set compression {} time";
+      "Timeseries [{}] uses SDT without property [{}]; using default [{}].";
 
   // ======================== Tag/Attribute ========================
 
@@ -489,9 +489,12 @@ public final class DataNodeSchemaMessages {
   public static final String FAILED_TO_CREATE_TEMPLATE =
       "Failed to execute create device template {} in config node, status is 
{}.";
   public static final String CREATE_TEMPLATE_ERROR_PREFIX = "create template 
error -";
-  public static final String CREATE_TEMPLATE_ERROR = "create template error.";
-  public static final String GET_ALL_TEMPLATE_ERROR = "get all template 
error.";
-  public static final String GET_TEMPLATE_INFO_ERROR = "get template info 
error.";
+  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 FAILED_TO_SET_TEMPLATE =
       "Failed to execute set device template {} on path {} in config node, 
status is {}.";
 
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 417f547573a..a932bc061cd 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
@@ -164,7 +164,7 @@ public final class DataNodeSchemaMessages {
   public static final String ERROR_DURING_INIT_SCHEMA_REGION =
       "初始化 schemaRegion {} 过程中发生错误";
   public static final String FAILED_TO_RECOVER_TAG_INDEX =
-      "恢复 {} 的 tagIndex 失败,schemaRegion 为 {}。";
+      "恢复测点 [{}] 的 tag 索引失败,数据库为 [{}],SchemaRegion 为 [{}],tag 偏移量为 [{}]。";
   public static final String FAILED_TO_READ_TAG_ATTRIBUTE =
       "读取 tag 和 attribute 信息失败:{}";
 
@@ -206,19 +206,19 @@ public final class DataNodeSchemaMessages {
   public static final String DATABASE_NAME_ILLEGAL_CHARS =
       "数据库名称只能包含中英文字符、数字、反引号和下划线。%s";
   public static final String SDT_COMPRESSION_DEVIATION_REQUIRED =
-      "SDT 压缩偏差为必填项";
+      "SDT 压缩必须设置 compdev 属性";
   public static final String SDT_COMPRESSION_DEVIATION_NEGATIVE =
-      "SDT 压缩偏差不能为负数";
+      "SDT 属性 compdev 必须为非负数,实际值为 [%s]";
   public static final String SDT_COMPRESSION_DEVIATION_FORMAT_ERROR =
-      "SDT 压缩偏差格式错误";
+      "SDT 属性 compdev 必须为数字,实际值为 [%s]";
   public static final String SDT_COMPRESSION_MAX_GREATER_THAN_MIN =
-      "SDT 压缩最大时间必须大于最小时间";
+      "SDT 属性 compmaxtime [%s] 必须大于 compmintime [%s]";
   public static final String SDT_COMPRESSION_TIME_NEGATIVE =
-      "SDT 压缩 %s 时间不能为负数";
+      "SDT 属性 %s 必须为非负数,实际值为 [%s]";
   public static final String SDT_COMPRESSION_TIME_FORMAT_ERROR =
-      "SDT 压缩 %s 时间格式错误";
+      "SDT 属性 %s 必须为 64 位整数,实际值为 [%s]";
   public static final String SDT_ENABLED_NO_COMPRESSION_TIME =
-      "{} 已启用 SDT 但未设置压缩 {} 时间";
+      "时间序列 [{}] 已启用 SDT 但未设置属性 [{}],使用默认值 [{}]。";
 
   // ======================== Tag/Attribute 相关消息 ========================
 
@@ -480,9 +480,12 @@ public final class DataNodeSchemaMessages {
   public static final String FAILED_TO_CREATE_TEMPLATE =
       "在 ConfigNode 中执行创建设备模板 {} 失败,状态为 {}。";
   public static final String CREATE_TEMPLATE_ERROR_PREFIX = "创建模板出错 - ";
-  public static final String CREATE_TEMPLATE_ERROR = "创建模板出错。";
-  public static final String GET_ALL_TEMPLATE_ERROR = "获取所有模板出错。";
-  public static final String GET_TEMPLATE_INFO_ERROR = "获取模板信息出错。";
+  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 FAILED_TO_SET_TEMPLATE =
       "在 ConfigNode 中执行设置设备模板 {} 到路径 {} 失败,状态为 {}。";
 
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 ce8ba551a40..87410d51c99 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
@@ -346,12 +346,12 @@ public class SchemaEngine {
             timeRecord);
         return schemaRegion;
       } catch (final MetadataException e) {
-        logger.error(
+        throw new RuntimeException(
             String.format(
                 DataNodeSchemaMessages.SCHEMA_REGION_FAILED_TO_RECOVER,
                 schemaRegionId.getId(),
-                storageGroup));
-        throw new RuntimeException(e);
+                storageGroup),
+            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 e15e7e132c8..dfc305656ae 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
@@ -166,7 +166,6 @@ import java.util.stream.Collectors;
 
 import static 
org.apache.iotdb.calc.plan.planner.TableOperatorGenerator.makeLayout;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableDeviceSchemaFetcher.convertTagValuesToDeviceID;
-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
@@ -625,10 +624,14 @@ public class SchemaRegionMemoryImpl implements 
ISchemaRegion {
                 try {
                   tagManager.recoverIndex(measurementMNode.getOffset(), 
measurementMNode);
                 } catch (final IOException e) {
+                  // Snapshot callbacks run before the measurement is attached 
to its parent.
                   logger.error(
                       DataNodeSchemaMessages.FAILED_TO_RECOVER_TAG_INDEX,
-                      databaseFullPath + PATH_SEPARATOR + 
measurementMNode.getFullPath(),
-                      schemaRegionId);
+                      measurementMNode.getName(),
+                      databaseFullPath,
+                      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 c28a918e451..8b0ae04b658 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
@@ -153,11 +153,16 @@ public class MetaFormatUtils {
       double d = Double.parseDouble(props.get(SDT_COMP_DEV));
       if (d < 0) {
         throw new IllegalParameterOfPathException(
-            DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_NEGATIVE, path);
+            String.format(
+                DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_NEGATIVE, 
props.get(SDT_COMP_DEV)),
+            path);
       }
     } catch (NumberFormatException e) {
       throw new IllegalParameterOfPathException(
-          DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_FORMAT_ERROR, path);
+          String.format(
+              DataNodeSchemaMessages.SDT_COMPRESSION_DEVIATION_FORMAT_ERROR,
+              props.get(SDT_COMP_DEV)),
+          path);
     }
 
     long compMinTime = sdtCompressionTimeFormat(SDT_COMP_MIN_TIME, props, 
path);
@@ -165,7 +170,11 @@ public class MetaFormatUtils {
 
     if (compMaxTime <= compMinTime) {
       throw new IllegalParameterOfPathException(
-          DataNodeSchemaMessages.SDT_COMPRESSION_MAX_GREATER_THAN_MIN, path);
+          String.format(
+              DataNodeSchemaMessages.SDT_COMPRESSION_MAX_GREATER_THAN_MIN,
+              compMaxTime,
+              compMinTime),
+          path);
     }
   }
 
@@ -174,20 +183,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(DataNodeSchemaMessages.SDT_COMPRESSION_TIME_NEGATIVE, s), path);
-        }
-      } catch (IllegalParameterOfPathException e) {
+        time = Long.parseLong(value);
+      } catch (NumberFormatException e) {
         throw new IllegalParameterOfPathException(
-            
String.format(DataNodeSchemaMessages.SDT_COMPRESSION_TIME_FORMAT_ERROR, s), 
path);
+            String.format(
+                DataNodeSchemaMessages.SDT_COMPRESSION_TIME_FORMAT_ERROR, 
compTime, value),
+            path);
+      }
+      if (time < 0) {
+        throw new IllegalParameterOfPathException(
+            
String.format(DataNodeSchemaMessages.SDT_COMPRESSION_TIME_NEGATIVE, compTime, 
value),
+            path);
       }
     } else {
-      logger.info(DataNodeSchemaMessages.SDT_ENABLED_NO_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 825ea757a77..816effce329 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
@@ -130,11 +130,11 @@ public class ClusterTemplateManager implements 
ITemplateManager {
               e,
               TSStatusCode.CREATE_TEMPLATE_ERROR.getStatusCode()));
     } catch (ClientManagerException | TException e) {
-      throw new RuntimeException(
-          new IoTDBException(
-              DataNodeSchemaMessages.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);
     }
   }
 
@@ -177,10 +177,10 @@ public class ClusterTemplateManager implements 
ITemplateManager {
         throw new IoTDBRuntimeException(resp.getStatus());
       }
     } catch (ClientManagerException | TException e) {
-      throw new RuntimeException(
-          new IoTDBException(
-              DataNodeSchemaMessages.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;
   }
@@ -199,13 +199,21 @@ public class ClusterTemplateManager implements 
ITemplateManager {
         throw new IoTDBException(resp.status);
       }
     } catch (ClientManagerException | TException e) {
-      throw new RuntimeException(
-          new IoTDBException(
-              DataNodeSchemaMessages.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());
+    // Query error handling uses the deepest cause to determine the status and 
message.
+    exception.addSuppressed(cause);
+    return new RuntimeException(exception);
+  }
+
   @Override
   public void setSchemaTemplate(String queryId, String name, PartialPath path) 
{
     try (ConfigNodeClient configNodeClient =
@@ -256,7 +264,7 @@ public class ClusterTemplateManager implements 
ITemplateManager {
                     try {
                       listPath.add(new PartialPath(item));
                     } catch (IllegalPathException e) {
-                      LOGGER.error(DataNodeSchemaMessages.ILLEGAL_PATH_LOG, 
item);
+                      LOGGER.error(DataNodeSchemaMessages.ILLEGAL_PATH_LOG, 
item, e);
                     }
                   });
         }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/i18n/DataNodeSchemaMessagesZhFormatTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/i18n/DataNodeSchemaMessagesZhFormatTest.java
index 294ebf04b1b..44708a1c0dd 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/i18n/DataNodeSchemaMessagesZhFormatTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/i18n/DataNodeSchemaMessagesZhFormatTest.java
@@ -46,9 +46,13 @@ public class DataNodeSchemaMessagesZhFormatTest {
         "跳过 128 失败,schemaRegion 目录为 C:/schema",
         formatSlf4j(readZhMessage("FAILED_TO_SKIP_MLOG"), 128, "C:/schema"));
     Assert.assertEquals(
-        "恢复 root.sg.d.s 的 tagIndex 失败,schemaRegion 为 SchemaRegionId{1}。",
+        "恢复测点 [s] 的 tag 索引失败,数据库为 [root.sg],SchemaRegion 为 
[SchemaRegionId{1}],tag 偏移量为 [128]。",
         formatSlf4j(
-            readZhMessage("FAILED_TO_RECOVER_TAG_INDEX"), "root.sg.d.s", 
"SchemaRegionId{1}"));
+            readZhMessage("FAILED_TO_RECOVER_TAG_INDEX"),
+            "s",
+            "root.sg",
+            "SchemaRegionId{1}",
+            128));
     Assert.assertEquals(
         "MTree 刷写耗时 321ms,SchemaRegion 为 SchemaRegionId{1}",
         formatSlf4j(readZhMessage("MTREE_FLUSH_COST"), 321, 
"SchemaRegionId{1}"));
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..66835c33388
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/MetaFormatUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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..bc6cd1629b0
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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