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 02e675a74d3 Preserve write-path causes and return delete failure 
details (#18933)
02e675a74d3 is described below

commit 02e675a74d39380fb8f078b572fa0313a133a7ec
Author: Caideyipi <[email protected]>
AuthorDate: Fri Oct 23 09:55:32 2026 +0800

    Preserve write-path causes and return delete failure details (#18933)
    
    * Preserve causes in write-path diagnostics
    
    * Return delete failure details to clients
    
    * Avoid stack traces for expected write-path errors
---
 .../dataregion/DataExecutionVisitor.java           |   9 +-
 .../db/storageengine/dataregion/DataRegion.java    |   3 +-
 .../timeindex/FileTimeIndexCacheRecorder.java      |   6 +-
 .../dataregion/wal/buffer/WALBuffer.java           |   3 +-
 .../dataregion/wal/recover/WALNodeRecoverTask.java |   3 +-
 .../dataregion/DataExecutionVisitorTest.java       | 185 +++++++++++++++++++++
 6 files changed, 200 insertions(+), 9 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
index d723b8dc888..4f8eca8f275 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
@@ -345,9 +345,12 @@ public class DataExecutionVisitor implements 
PlanVisitor<TSStatus, DataRegion> {
       return StatusUtils.OK;
     } catch (WriteProcessRejectException e) {
       return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
-    } catch (IOException | IllegalPathException e) {
+    } catch (IOException e) {
       LOGGER.error(DataNodeMiscMessages.ERROR_EXECUTING_PLAN_NODE, node, e);
-      return new TSStatus(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode());
+      return RpcUtils.getStatus(TSStatusCode.WRITE_PROCESS_ERROR, 
e.getMessage());
+    } catch (IllegalPathException e) {
+      LOGGER.error(DataNodeMiscMessages.ERROR_EXECUTING_PLAN_NODE_CAUSED, 
node, e.getMessage());
+      return RpcUtils.getStatus(TSStatusCode.WRITE_PROCESS_ERROR, 
e.getMessage());
     } finally {
       dataRegion.writeUnlock();
     }
@@ -363,7 +366,7 @@ public class DataExecutionVisitor implements 
PlanVisitor<TSStatus, DataRegion> {
       return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
     } catch (final IOException e) {
       LOGGER.error(DataNodeMiscMessages.ERROR_EXECUTING_PLAN_NODE, node, e);
-      return new TSStatus(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode());
+      return RpcUtils.getStatus(TSStatusCode.WRITE_PROCESS_ERROR, 
e.getMessage());
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
index 4ee3a2a6827..52000fe97fc 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
@@ -1854,7 +1854,8 @@ public class DataRegion implements IDataRegionForQuery {
                   logger.error(
                       StorageEngineMessages
                           
.STORAGE_LOG_REMOTE_REQUEST_CONFIG_NODE_FAILED_THAT_JUDGMENT_IF_TABLE_25FE3602,
-                      e.getMessage());
+                      e.getMessage(),
+                      e);
                   
CommonMetadataUtils.throwTableNotExistsException(getDatabaseName(), tableName);
                   return null; // unreachable, throwTableNotExistsException 
always throws
                 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
index 40159a6924b..8aacb519c9d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
@@ -104,7 +104,7 @@ public class FileTimeIndexCacheRecorder {
                   writer.write(buffer);
                 } catch (IOException e) {
                   LOGGER.warn(
-                      
StorageEngineMessages.ERROR_RECORD_FILE_TIME_INDEX_CACHE, e.getMessage());
+                      
StorageEngineMessages.ERROR_RECORD_FILE_TIME_INDEX_CACHE, e.getMessage(), e);
                 }
               });
       if (!result) {
@@ -158,7 +158,7 @@ public class FileTimeIndexCacheRecorder {
                   }
                 } catch (IOException e) {
                   LOGGER.warn(
-                      
StorageEngineMessages.ERROR_COMPACT_FILE_TIME_INDEX_CACHE, e.getMessage());
+                      
StorageEngineMessages.ERROR_COMPACT_FILE_TIME_INDEX_CACHE, e.getMessage(), e);
                 }
               });
       if (!result) {
@@ -208,7 +208,7 @@ public class FileTimeIndexCacheRecorder {
         writer.close();
         deleteFileOrDirectory(writer.getLogFile(), true);
       } catch (IOException e) {
-        LOGGER.warn(StorageEngineMessages.ERROR_CLOSE_FILE_TIME_INDEX_CACHE, 
e.getMessage());
+        LOGGER.warn(StorageEngineMessages.ERROR_CLOSE_FILE_TIME_INDEX_CACHE, 
e.getMessage(), e);
       }
     }
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
index ca975b397fc..eba379f1cc5 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
@@ -930,7 +930,8 @@ public class WALBuffer extends AbstractWALBuffer {
                     
.STORAGE_LOG_FAIL_TO_READ_MEMTABLE_IDS_FROM_THE_WAL_FILE_OF_WAL_NODE_54B0056E,
                 id,
                 identifier,
-                e.getMessage());
+                e.getMessage(),
+                e);
           } catch (IOException e) {
             logger.warn(
                 StorageEngineMessages
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
index 7efce703656..73d429e3ccb 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
@@ -332,7 +332,8 @@ public class WALNodeRecoverTask implements Runnable {
             StorageEngineMessages
                 
.STORAGE_LOG_FAIL_TO_READ_MEMTABLE_IDS_FROM_THE_WAL_FILE_OF_WAL_NODE_5325B5AB,
             walFile.getAbsoluteFile(),
-            e.getMessage());
+            logDirectory.getName(),
+            e);
       } catch (IOException e) {
         logger.warn(
             StorageEngineMessages
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitorTest.java
new file mode 100644
index 00000000000..8d754bb3d10
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitorTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.consensus.statemachine.dataregion;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
+import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.path.MeasurementPath;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.exception.runtime.TableLostRuntimeException;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.DeleteDataNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalDeleteDataNode;
+import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import org.apache.tsfile.enums.TSDataType;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.any;
+
+public class DataExecutionVisitorTest {
+
+  private final Logger logger = (Logger) 
LoggerFactory.getLogger(DataExecutionVisitor.class);
+  private ListAppender<ILoggingEvent> appender;
+
+  @Before
+  public void setUp() {
+    appender = new ListAppender<>();
+    appender.start();
+    logger.addAppender(appender);
+  }
+
+  @After
+  public void tearDown() {
+    logger.detachAppender(appender);
+    appender.stop();
+  }
+
+  @Test
+  public void testInsertRowsSemanticFailureDoesNotLogStackTrace() throws 
Exception {
+    assertInsertRowsFailureWithoutStackTrace(new SemanticException("injected 
write failure"));
+  }
+
+  @Test
+  public void testInsertRowsTableLostFailureDoesNotLogStackTrace() throws 
Exception {
+    assertInsertRowsFailureWithoutStackTrace(
+        new TableLostRuntimeException("test_db", "test_table"));
+  }
+
+  private void assertInsertRowsFailureWithoutStackTrace(final 
IoTDBRuntimeException failure)
+      throws Exception {
+    final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+    
Mockito.doThrow(failure).when(dataRegion).insert(any(InsertRowsNode.class));
+
+    final InsertRowNode insertRowNode =
+        new InsertRowNode(
+            new PlanNodeId("write-log-test"),
+            new PartialPath("root.sg.device"),
+            false,
+            new String[] {"s1"},
+            new TSDataType[] {TSDataType.INT64},
+            1L,
+            new Object[] {1L},
+            false);
+    final InsertRowsNode insertRowsNode =
+        new InsertRowsNode(
+            new PlanNodeId("write-log-test"),
+            Collections.singletonList(0),
+            Collections.singletonList(insertRowNode));
+
+    final TSStatus status = new 
DataExecutionVisitor().visitInsertRows(insertRowsNode, dataRegion);
+
+    Assert.assertEquals(failure.getErrorCode(), status.getCode());
+    Assert.assertEquals(failure.getMessage(), status.getMessage());
+    Assert.assertEquals(1, appender.list.size());
+    
Assert.assertTrue(appender.list.get(0).getFormattedMessage().contains(failure.getMessage()));
+    Assert.assertNull(appender.list.get(0).getThrowableProxy());
+  }
+
+  @Test
+  public void testTreeDeleteFailureReturnsMessage() throws Exception {
+    final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+    Mockito.when(dataRegion.getDatabaseName()).thenReturn("root.sg");
+    final DeleteDataNode node = createDeleteDataNode("root.sg.device.s1");
+    final IOException failure = new IOException("injected tree delete 
failure");
+    
Mockito.doThrow(failure).when(dataRegion).deleteByDevice(any(MeasurementPath.class),
 any());
+
+    final TSStatus status = new DataExecutionVisitor().visitDeleteData(node, 
dataRegion);
+
+    Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(), 
status.getCode());
+    Assert.assertEquals(failure.getMessage(), status.getMessage());
+    Assert.assertEquals(1, appender.list.size());
+    Assert.assertNotNull(appender.list.get(0).getThrowableProxy());
+    Assert.assertEquals(
+        failure.getClass().getName(), 
appender.list.get(0).getThrowableProxy().getClassName());
+    Mockito.verify(dataRegion).writeUnlock();
+  }
+
+  @Test
+  public void testTreeDeleteIllegalPathDoesNotLogStackTrace() throws Exception 
{
+    final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+    Mockito.when(dataRegion.getDatabaseName()).thenReturn("root..sg");
+    final DeleteDataNode node = createDeleteDataNode("root.sg.device.s1");
+    final IllegalPathException failure = new 
IllegalPathException("root..sg.**");
+
+    final TSStatus status = new DataExecutionVisitor().visitDeleteData(node, 
dataRegion);
+
+    Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(), 
status.getCode());
+    Assert.assertEquals(failure.getMessage(), status.getMessage());
+    Assert.assertEquals(1, appender.list.size());
+    
Assert.assertTrue(appender.list.get(0).getFormattedMessage().contains(failure.getMessage()));
+    Assert.assertNull(appender.list.get(0).getThrowableProxy());
+    Mockito.verify(dataRegion).writeUnlock();
+    Mockito.verify(dataRegion, 
Mockito.never()).deleteByDevice(any(MeasurementPath.class), any());
+  }
+
+  @Test
+  public void testDirectTreeDeleteFailureReturnsMessage() throws Exception {
+    final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+    Mockito.when(dataRegion.getDatabaseName()).thenReturn("root.sg");
+    final DeleteDataNode node = createDeleteDataNode("root.sg.**");
+    final IOException failure = new IOException("injected direct delete 
failure");
+    
Mockito.doThrow(failure).when(dataRegion).deleteDataDirectly(any(MeasurementPath.class),
 any());
+
+    final TSStatus status = new DataExecutionVisitor().visitDeleteData(node, 
dataRegion);
+
+    Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(), 
status.getCode());
+    Assert.assertEquals(failure.getMessage(), status.getMessage());
+    Mockito.verify(dataRegion).writeUnlock();
+  }
+
+  @Test
+  public void testTableDeleteFailureReturnsMessage() throws Exception {
+    final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+    final RelationalDeleteDataNode node =
+        new RelationalDeleteDataNode(
+            new PlanNodeId("delete-error-test"), Collections.emptyList(), 
"test_db");
+    final IOException failure = new IOException("injected table delete 
failure");
+    Mockito.doThrow(failure).when(dataRegion).deleteByTable(node);
+
+    final TSStatus status = new DataExecutionVisitor().visitDeleteData(node, 
dataRegion);
+
+    Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(), 
status.getCode());
+    Assert.assertEquals(failure.getMessage(), status.getMessage());
+  }
+
+  private DeleteDataNode createDeleteDataNode(final String path) throws 
IllegalPathException {
+    return new DeleteDataNode(
+        new PlanNodeId("delete-error-test"),
+        Collections.singletonList(new MeasurementPath(path)),
+        Long.MIN_VALUE,
+        Long.MAX_VALUE);
+  }
+}

Reply via email to