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 e492e444a8d [Fix] Preserve table insert errors for rejected fields 
(#18880)
e492e444a8d is described below

commit e492e444a8da398d6ecf5c7f4882f8221432a929
Author: Hongzhi Gao <[email protected]>
AuthorDate: Fri Oct 16 10:29:56 2026 +0800

    [Fix] Preserve table insert errors for rejected fields (#18880)
    
    * [Fix] Preserve table insert errors for rejected fields
    
    * [Fix] Retain metadata errors for fully rejected table row batches
    
    * [Fix] Preserve table timestamps when all fields are rejected
    
    * [Test] Align rejected-field assertions and fix controlled flush fixtures
---
 .../relational/it/db/it/IoTDBInsertTableIT.java    |  62 +++++++++
 .../it/session/IoTDBSessionRelationalIT.java       |  48 +++++--
 .../java/org/apache/iotdb/session/Session.java     |   7 +-
 .../iotdb/session/SessionCacheLeaderTest.java      |  79 ++++++++++-
 .../plan/planner/plan/node/write/InsertNode.java   |   5 +
 .../db/storageengine/dataregion/DataRegion.java    |   8 +-
 .../dataregion/memtable/AbstractMemTable.java      |   4 +-
 .../wal/recover/file/TsFilePlanRedoer.java         |   4 +-
 .../queryengine/plan/analyze/AnalyzeUtilsTest.java | 149 +++++++++++++++++++++
 .../wal/recover/file/TsFilePlanRedoerTest.java     |  52 +++++++
 10 files changed, 394 insertions(+), 24 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java
index 3abe7497130..70eab82727c 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java
@@ -1379,6 +1379,68 @@ public class IoTDBInsertTableIT {
     }
   }
 
+  @Test
+  public void testRejectedOnlyFieldStillWritesTime() throws Exception {
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("use \"test\"");
+      session.executeNonQueryStatement(
+          "create table rejected_only_field_time (tag1 string tag, s1 int64 
field)");
+
+      Tablet tablet =
+          new Tablet(
+              "rejected_only_field_time",
+              Arrays.asList("tag1", "s1"),
+              Arrays.asList(TSDataType.STRING, TSDataType.BOOLEAN),
+              Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
+              2);
+      for (int i = 0; i < 2; i++) {
+        int row = tablet.getRowSize();
+        tablet.addTimestamp(row, i + 1);
+        tablet.addValue("tag1", row, "d" + i);
+        tablet.addValue("s1", row, true);
+      }
+
+      StatementExecutionException error =
+          assertThrows(StatementExecutionException.class, () -> 
session.insert(tablet));
+      assertTrue(error.getMessage().contains("s1"));
+
+      Tablet withoutTag =
+          new Tablet(
+              "rejected_only_field_time",
+              Arrays.asList("s1"),
+              Arrays.asList(TSDataType.BOOLEAN),
+              Arrays.asList(ColumnCategory.FIELD),
+              1);
+      withoutTag.addTimestamp(0, 3);
+      withoutTag.addValue("s1", 0, true);
+      error = assertThrows(StatementExecutionException.class, () -> 
session.insert(withoutTag));
+      assertTrue(error.getMessage().contains("s1"));
+      assertRejectedFieldTimeRows(session);
+
+      session.executeNonQueryStatement("flush");
+      assertRejectedFieldTimeRows(session);
+    }
+  }
+
+  private static void assertRejectedFieldTimeRows(ITableSession session)
+      throws IoTDBConnectionException, StatementExecutionException {
+    SessionDataSet dataSet =
+        session.executeQueryStatement(
+            "select time, tag1, s1 from rejected_only_field_time order by 
time");
+    for (int i = 0; i < 3; i++) {
+      assertTrue(dataSet.hasNext());
+      RowRecord row = dataSet.next();
+      assertEquals(i + 1, row.getFields().get(0).getLongV());
+      if (i < 2) {
+        assertEquals("d" + i, row.getFields().get(1).getStringValue());
+      } else {
+        assertNull(row.getFields().get(1).getObjectValue(TSDataType.STRING));
+      }
+      assertNull(row.getFields().get(2).getObjectValue(TSDataType.INT64));
+    }
+    assertFalse(dataSet.hasNext());
+  }
+
   @Test
   public void testInsertWithChangedSchema() throws SQLException, IOException {
     SimpleEnv simpleEnv = new SimpleEnv();
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java
index 63a5548ee90..d6f7aef603d 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java
@@ -449,16 +449,19 @@ public class IoTDBSessionRelationalIT {
 
       SessionDataSet dataSet =
           session.executeQueryStatement("select * from partial_insert order by 
time");
-      long[] timestamps = new long[] {10000, 20000, 40000, 50000};
-      Boolean[] values = new Boolean[] {true, false, false, true};
+      // Partial insertion retains timestamps even when the only FIELD value 
is rejected.
+      long[] timestamps =
+          new long[] {10000, 20000, 30000, 35000, 40000, 50000, 60000, 70000, 
80000, 90000};
+      Boolean[] values =
+          new Boolean[] {true, false, null, null, false, true, null, null, 
null, null};
       int cnt = 0;
       while (dataSet.hasNext()) {
         RowRecord rec = dataSet.next();
         assertEquals(timestamps[cnt], rec.getFields().get(0).getLongV());
-        assertEquals(values[cnt], rec.getFields().get(1).getBoolV());
+        assertEquals(values[cnt], 
rec.getFields().get(1).getObjectValue(TSDataType.BOOLEAN));
         cnt++;
       }
-      assertEquals(4, cnt);
+      assertEquals(timestamps.length, cnt);
 
     } finally {
       try (ISession session = EnvFactory.getEnv().getSessionConnection()) {
@@ -769,14 +772,23 @@ public class IoTDBSessionRelationalIT {
             e.getMessage());
       }
       tablet.reset();
-      int cnt = 0;
-      SessionDataSet dataSet =
-          session.executeQueryStatement("select * from field_wrong_type order 
by time");
-      while (dataSet.hasNext()) {
-        dataSet.next();
-        cnt++;
+      for (int phase = 0; phase < 2; phase++) {
+        try (SessionDataSet dataSet =
+            session.executeQueryStatement("select * from field_wrong_type 
order by time")) {
+          for (int row = 0; row < 10; row++) {
+            assertTrue(dataSet.hasNext());
+            RowRecord record = dataSet.next();
+            assertEquals(row, record.getFields().get(0).getLongV());
+            assertEquals("tag:" + row, 
record.getFields().get(1).getStringValue());
+            assertNull(record.getFields().get(2).getDataType());
+            assertNull(record.getFields().get(3).getDataType());
+          }
+          assertFalse(dataSet.hasNext());
+        }
+        if (phase == 0) {
+          session.executeNonQueryStatement("flush");
+        }
       }
-      assertEquals(0, cnt);
     }
   }
 
@@ -1512,9 +1524,17 @@ public class IoTDBSessionRelationalIT {
                 e.getMessage());
           }
           // time, tag1, m1
-          SessionDataSet dataSet =
-              session.executeQueryStatement("select * from table" + testNum + 
" order by time");
-          assertFalse(dataSet.hasNext());
+          try (SessionDataSet dataSet =
+              session.executeQueryStatement("select * from table" + testNum + 
" order by time")) {
+            for (int row = 0; row < 2; row++) {
+              assertTrue(dataSet.hasNext());
+              RowRecord record = dataSet.next();
+              assertEquals(row, record.getFields().get(0).getLongV());
+              assertEquals("d1", record.getFields().get(1).getStringValue());
+              assertNull(record.getFields().get(2).getDataType());
+            }
+            assertFalse(dataSet.hasNext());
+          }
         } else {
           // cannot cast, expect an exception
           try {
diff --git 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
index fe9fb38f6db..6d31f81885a 100644
--- a/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
+++ b/iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
@@ -3007,6 +3007,7 @@ public class Session implements ISession {
             .collect(Collectors.toList());
 
     StringBuilder errMsgBuilder = new StringBuilder();
+    List<Throwable> errors = new ArrayList<>();
     for (CompletableFuture<Void> completableFuture : completableFutures) {
       try {
         completableFuture.join();
@@ -3016,6 +3017,7 @@ public class Session implements ISession {
         if (cause instanceof IoTDBConnectionException) {
           throw (IoTDBConnectionException) cause;
         } else {
+          errors.add(cause);
           if (errMsgBuilder.length() > 0) {
             errMsgBuilder.append(";");
           }
@@ -3024,7 +3026,10 @@ public class Session implements ISession {
       }
     }
     if (errMsgBuilder.length() > 0) {
-      throw new StatementExecutionException(errMsgBuilder.toString());
+      StatementExecutionException exception =
+          new StatementExecutionException(errMsgBuilder.toString());
+      errors.forEach(exception::addSuppressed);
+      throw exception;
     }
   }
 
diff --git 
a/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionCacheLeaderTest.java
 
b/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionCacheLeaderTest.java
index 40268a54cd3..46ec3ee1f5d 100644
--- 
a/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionCacheLeaderTest.java
+++ 
b/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionCacheLeaderTest.java
@@ -20,10 +20,12 @@
 package org.apache.iotdb.session;
 
 import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.isession.SessionConfig;
 import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.RedirectException;
 import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.service.rpc.thrift.TSInsertRecordReq;
 import org.apache.iotdb.service.rpc.thrift.TSInsertRecordsOfOneDeviceReq;
 import org.apache.iotdb.service.rpc.thrift.TSInsertRecordsReq;
@@ -44,10 +46,12 @@ import org.junit.Test;
 
 import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -705,6 +709,79 @@ public class SessionCacheLeaderTest {
     session.close();
   }
 
+  @Test
+  public void testGroupedRelationalTabletPreservesStatementFailures() throws 
Exception {
+    AtomicBoolean failWrites = new AtomicBoolean();
+    session =
+        new MockSession("127.0.0.1", 55560, true, "table") {
+          @Override
+          public SessionConnection constructSessionConnection(
+              Session owner, TEndPoint endpoint, ZoneId zoneId) {
+            return new MockSessionConnection(owner, endpoint, zoneId) {
+              @Override
+              protected void insertTablet(TSInsertTabletReq request)
+                  throws RedirectException, IoTDBConnectionException, 
StatementExecutionException {
+                if (failWrites.get()) {
+                  TSStatusCode code =
+                      endpoint.getPort() == 55560
+                          ? TSStatusCode.METADATA_ERROR
+                          : TSStatusCode.DATA_TYPE_MISMATCH;
+                  throw new StatementExecutionException(
+                      new 
TSStatus(code.getStatusCode()).setMessage("incompatible field"));
+                }
+                super.insertTablet(request);
+              }
+            };
+          }
+        };
+    session.open();
+    try {
+      Tablet tablet =
+          new Tablet(
+              "table1",
+              Arrays.asList("id", "field"),
+              Arrays.asList(TSDataType.STRING, TSDataType.INT64),
+              Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
+              50);
+      for (int i = 0; i < 50; i++) {
+        int row = tablet.getRowSize();
+        tablet.addTimestamp(row, row);
+        tablet.addValue("id", row, "id" + (row % 4));
+        tablet.addValue("field", row, 1L);
+      }
+      session.insertRelationalTablet(tablet);
+      assertEquals(4, session.tableModelDeviceIdToEndpoint.size());
+
+      failWrites.set(true);
+      tablet.reset();
+      for (int i = 0; i < 4; i++) {
+        int row = tablet.getRowSize();
+        tablet.addTimestamp(row, row);
+        tablet.addValue("id", row, "id" + row);
+        tablet.addValue("field", row, 1L);
+      }
+      StatementExecutionException failure =
+          Assert.assertThrows(
+              StatementExecutionException.class, () -> 
session.insertRelationalTablet(tablet));
+      assertEquals(4, failure.getSuppressed().length);
+      long metadataFailures = 0;
+      long typeFailures = 0;
+      for (Throwable cause : failure.getSuppressed()) {
+        Assert.assertTrue(cause instanceof StatementExecutionException);
+        int code = ((StatementExecutionException) cause).getStatusCode();
+        if (code == TSStatusCode.METADATA_ERROR.getStatusCode()) {
+          metadataFailures++;
+        } else if (code == TSStatusCode.DATA_TYPE_MISMATCH.getStatusCode()) {
+          typeFailures++;
+        }
+      }
+      assertEquals(1, metadataFailures);
+      assertEquals(3, typeFailures);
+    } finally {
+      session.close();
+    }
+  }
+
   @Test
   public void testInsertRecordsWithSessionBroken() throws 
StatementExecutionException {
     // without leader cache
@@ -1281,7 +1358,7 @@ public class SessionCacheLeaderTest {
 
     @Override
     protected void insertTablet(TSInsertTabletReq request)
-        throws RedirectException, IoTDBConnectionException {
+        throws RedirectException, IoTDBConnectionException, 
StatementExecutionException {
       if (isConnectionBroken()) {
         throw ioTDBConnectionException;
       }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertNode.java
index 289866c6882..0f2bc190120 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertNode.java
@@ -446,6 +446,11 @@ public abstract class InsertNode extends SearchNode {
     return measurements == null || !hasValidMeasurements();
   }
 
+  public boolean shouldSkipWrite() {
+    // Table rows remain writable through their time column after every FIELD 
is rejected.
+    return allMeasurementFailed() && columnCategories == null;
+  }
+
   // endregion
 
   // region progress index
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 2dcb699807d..b7284abd0e3 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
@@ -1563,7 +1563,7 @@ public class DataRegion implements IDataRegionForQuery {
     for (int i = insertMultiTabletsNode.getInsertTabletNodeList().size() - 1; 
i >= 0; i--) {
       final InsertTabletNode insertTabletNode =
           insertMultiTabletsNode.getInsertTabletNodeList().get(i);
-      if (insertTabletNode.getRowCount() <= 0 || 
insertTabletNode.allMeasurementFailed()) {
+      if (insertTabletNode.getRowCount() <= 0 || 
insertTabletNode.shouldSkipWrite()) {
         continue;
       }
       if (!insertTabletNode.shouldCheckTTL()) {
@@ -1613,7 +1613,7 @@ public class DataRegion implements IDataRegionForQuery {
       boolean noFailure,
       long[] infoForMetrics)
       throws DataTypeInconsistentException {
-    if (insertTabletNode.allMeasurementFailed()) {
+    if (insertTabletNode.shouldSkipWrite()) {
       if (logger.isDebugEnabled()) {
         logger.debug(
             
StorageEngineMessages.STORAGE_LOG_WON_T_INSERT_TABLET_BECAUSE_C2DC8032,
@@ -1821,7 +1821,7 @@ public class DataRegion implements IDataRegionForQuery {
   private TsFileProcessor insertToTsFileProcessor(
       InsertRowNode insertRowNode, boolean sequence, long timePartitionId)
       throws WriteProcessException {
-    if (insertRowNode.allMeasurementFailed()) {
+    if (insertRowNode.shouldSkipWrite()) {
       return null;
     }
     TsFileProcessor tsFileProcessor = 
getOrCreateTsFileProcessor(timePartitionId, sequence);
@@ -1850,7 +1850,7 @@ public class DataRegion implements IDataRegionForQuery {
     Map<TsFileProcessor, InsertRowsNode> tsFileProcessorMap = new HashMap<>();
     for (int i = 0; i < areSequence.length; i++) {
       InsertRowNode insertRowNode = 
insertRowsNode.getInsertRowNodeList().get(i);
-      if (insertRowNode.allMeasurementFailed()) {
+      if (insertRowNode.shouldSkipWrite()) {
         continue;
       }
       final TsFileProcessor tsFileProcessor;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
index 5fd3d5f9224..382063359c1 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
@@ -266,7 +266,7 @@ public abstract class AbstractMemTable implements IMemTable 
{
       dataTypes.add(schema.getType());
       writableValues[i] = value;
     }
-    if (schemaList.isEmpty()) {
+    if (schemaList.isEmpty() && insertRowNode.getColumnCategories() == null) {
       return 0;
     }
     memSize +=
@@ -497,7 +497,7 @@ public abstract class AbstractMemTable implements IMemTable 
{
         }
       }
     }
-    if (schemaList.isEmpty()) {
+    if (schemaList.isEmpty() && insertTabletNode.getColumnCategories() == 
null) {
       return;
     }
     int splitStart = start;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoer.java
index 8e191d475ba..c4550898a05 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoer.java
@@ -92,7 +92,7 @@ public class TsFilePlanRedoer {
   }
 
   void redoInsert(InsertNode node) throws WriteProcessException {
-    if (!node.hasValidMeasurements()) {
+    if (node.shouldSkipWrite()) {
       return;
     }
     boolean isSingleDevice = !(node instanceof RelationalInsertTabletNode);
@@ -155,7 +155,7 @@ public class TsFilePlanRedoer {
   void redoInsertRows(InsertRowsNode insertRowsNode) {
     int pointsInserted = 0;
     for (InsertRowNode node : insertRowsNode.getInsertRowNodeList()) {
-      if (!node.hasValidMeasurements()) {
+      if (node.shouldSkipWrite()) {
         continue;
       }
       if (tsFileResource != null) {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java
index 697e15cd8f4..e5a1fb23355 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java
@@ -24,6 +24,9 @@ import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
 import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.partition.DataPartition;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.common.SessionInfo;
 import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression;
 import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Identifier;
@@ -32,34 +35,180 @@ import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.QualifiedNam
 import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Table;
 import org.apache.iotdb.commons.schema.table.TsTable;
 import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
 import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeReq;
 import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeResp;
 import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
 import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
 import org.apache.iotdb.db.queryengine.common.QueryId;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Delete;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertBaseStatement;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowsStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.DeletionPredicate;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.TableDeletionEntry;
 import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.read.common.TimeRange;
+import org.apache.tsfile.write.schema.MeasurementSchema;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 public class AnalyzeUtilsTest {
 
+  @Test
+  public void testRejectedOnlyFieldStillLooksUpRowAndTabletPartition() throws 
Exception {
+    final InsertRowStatement row = new InsertRowStatement();
+    row.setDevicePath(new PartialPath("root.sg.d1"));
+    row.setMeasurements(new String[] {"tag", "field"});
+    row.setDataTypes(new TSDataType[] {TSDataType.STRING, TSDataType.INT64});
+    row.setValues(new Object[] {"device", 1L});
+    row.setTime(1L);
+    assertRejectedFieldStillLooksUpPartition(row);
+
+    final InsertTabletStatement tablet = new InsertTabletStatement();
+    tablet.setDevicePath(new PartialPath("root.sg.d1"));
+    tablet.setMeasurements(new String[] {"tag", "field"});
+    tablet.setDataTypes(new TSDataType[] {TSDataType.STRING, 
TSDataType.INT64});
+    tablet.setColumns(new Object[] {new String[] {"device"}, new long[] {1L}});
+    tablet.setTimes(new long[] {1L});
+    tablet.setRowCount(1);
+    assertRejectedFieldStillLooksUpPartition(tablet);
+  }
+
+  private static void 
assertRejectedFieldStillLooksUpPartition(InsertBaseStatement statement) {
+    statement.setColumnCategories(
+        new TsTableColumnCategory[] {TsTableColumnCategory.TAG, 
TsTableColumnCategory.FIELD});
+    statement.setMeasurementSchemas(
+        new MeasurementSchema[] {
+          new MeasurementSchema("tag", TSDataType.STRING),
+          new MeasurementSchema("field", TSDataType.INT64)
+        });
+    final Analysis analysis = new Analysis();
+    final MPPQueryContext context = new MPPQueryContext(new 
QueryId("rejected_field"));
+    context.setSession(Mockito.mock(SessionInfo.class));
+    final DataPartition partition = Mockito.mock(DataPartition.class);
+    final AtomicBoolean partitionQueried = new AtomicBoolean();
+
+    AnalyzeUtils.analyzeInsert(
+        context,
+        statement,
+        () -> statement.markFailedMeasurement(1, new 
IllegalArgumentException("type mismatch")),
+        (params, userName) -> {
+          partitionQueried.set(true);
+          return partition;
+        },
+        AnalyzeUtils::computeTableDataPartitionParams,
+        analysis,
+        false);
+
+    assertTrue(partitionQueried.get());
+    assertFalse(analysis.isFinishQueryAfterAnalyze());
+    assertEquals(TSStatusCode.METADATA_ERROR.getStatusCode(), 
analysis.getFailStatus().getCode());
+    assertTrue(analysis.getFailStatus().getMessage().contains("field"));
+    assertSame(partition, analysis.getDataPartitionInfo());
+  }
+
+  @Test
+  public void testRejectedFieldsInEveryRowStillLookUpPartition() throws 
Exception {
+    final InsertRowsStatement rows = createRowsWithRejectedFields(2);
+    final Analysis analysis = new Analysis();
+    final MPPQueryContext context = new MPPQueryContext(new 
QueryId("all_rows_rejected"));
+    context.setSession(Mockito.mock(SessionInfo.class));
+    final DataPartition partition = Mockito.mock(DataPartition.class);
+    final AtomicBoolean partitionQueried = new AtomicBoolean();
+
+    AnalyzeUtils.analyzeInsert(
+        context,
+        rows,
+        () -> {},
+        (params, userName) -> {
+          partitionQueried.set(true);
+          return partition;
+        },
+        AnalyzeUtils::computeTableDataPartitionParams,
+        analysis,
+        false);
+
+    assertTrue(partitionQueried.get());
+    assertFalse(analysis.isFinishQueryAfterAnalyze());
+    assertEquals(TSStatusCode.METADATA_ERROR.getStatusCode(), 
analysis.getFailStatus().getCode());
+    assertTrue(analysis.getFailStatus().getMessage().contains("field"));
+    assertSame(partition, analysis.getDataPartitionInfo());
+  }
+
+  @Test
+  public void testValidFieldInAnotherRowContinuesPartitionLookup() throws 
Exception {
+    final InsertRowsStatement rows = createRowsWithRejectedFields(1);
+    final Analysis analysis = new Analysis();
+    final MPPQueryContext context = new MPPQueryContext(new 
QueryId("one_valid_row"));
+    context.setSession(Mockito.mock(SessionInfo.class));
+    final DataPartition partition = Mockito.mock(DataPartition.class);
+    final AtomicBoolean partitionQueried = new AtomicBoolean();
+
+    AnalyzeUtils.analyzeInsert(
+        context,
+        rows,
+        () -> {},
+        (params, userName) -> {
+          partitionQueried.set(true);
+          return partition;
+        },
+        AnalyzeUtils::computeTableDataPartitionParams,
+        analysis,
+        false);
+
+    assertTrue(partitionQueried.get());
+    assertFalse(analysis.isFinishQueryAfterAnalyze());
+    assertEquals(TSStatusCode.METADATA_ERROR.getStatusCode(), 
analysis.getFailStatus().getCode());
+    assertSame(partition, analysis.getDataPartitionInfo());
+  }
+
+  private static InsertRowsStatement createRowsWithRejectedFields(int 
rejectedRows)
+      throws Exception {
+    final List<InsertRowStatement> rowStatements = new ArrayList<>();
+    for (int i = 0; i < 2; i++) {
+      final InsertRowStatement row = new InsertRowStatement();
+      row.setDevicePath(new PartialPath("root.sg.d1"));
+      row.setMeasurements(new String[] {"tag", "field"});
+      row.setDataTypes(new TSDataType[] {TSDataType.STRING, TSDataType.INT64});
+      row.setColumnCategories(
+          new TsTableColumnCategory[] {TsTableColumnCategory.TAG, 
TsTableColumnCategory.FIELD});
+      row.setMeasurementSchemas(
+          new MeasurementSchema[] {
+            new MeasurementSchema("tag", TSDataType.STRING),
+            new MeasurementSchema("field", TSDataType.INT64)
+          });
+      row.setValues(new Object[] {"device" + i, 1L});
+      row.setTime(i);
+      if (i < rejectedRows) {
+        row.markFailedMeasurement(1, new IllegalArgumentException("type 
mismatch"));
+      }
+      rowStatements.add(row);
+    }
+    final InsertRowsStatement rows = new InsertRowsStatement();
+    rows.setInsertRowStatementList(rowStatements);
+    rows.updateAfterSchemaValidation(null);
+    return rows;
+  }
+
   @Test
   public void testParseDeletePredicateWithRenamedTimeColumn() {
     TsTable table = new TsTable("table1");
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoerTest.java
index 238606b073d..a58171017ac 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoerTest.java
@@ -23,18 +23,21 @@ import org.apache.iotdb.commons.path.MeasurementPath;
 import org.apache.iotdb.commons.path.NonAlignedFullPath;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import 
org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeType;
 import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.queryengine.execution.fragment.QueryContext;
 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.InsertTabletNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertRowNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertTabletNode;
 import org.apache.iotdb.db.storageengine.dataregion.memtable.IMemTable;
 import org.apache.iotdb.db.storageengine.dataregion.memtable.ReadOnlyMemChunk;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 import 
org.apache.iotdb.db.storageengine.dataregion.wal.utils.TsFileUtilsForRecoverTest;
+import 
org.apache.iotdb.db.storageengine.dataregion.wal.utils.WALByteBufferForTest;
 import org.apache.iotdb.db.utils.EnvironmentUtils;
 
 import org.apache.tsfile.common.conf.TSFileConfig;
@@ -66,6 +69,7 @@ import org.junit.Test;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -691,6 +695,54 @@ public class TsFilePlanRedoerTest {
     
Assert.assertFalse(recoveryMemTable.getMemTableMap().containsKey(existDevice));
   }
 
+  @Test
+  public void testRedoRelationalInsertWithoutFieldValues() throws Exception {
+    File file = new File(FILE_NAME);
+    generateCompleteFile(file);
+    tsFileResource = new TsFileResource(file);
+
+    RelationalInsertRowNode row =
+        new RelationalInsertRowNode(
+            new PlanNodeId("row"),
+            new PartialPath("table1", false),
+            true,
+            new String[] {null},
+            new TSDataType[] {null},
+            new MeasurementSchema[] {null},
+            101,
+            new Object[] {null},
+            false,
+            new TsTableColumnCategory[] {TsTableColumnCategory.FIELD});
+    RelationalInsertTabletNode tablet =
+        new RelationalInsertTabletNode(
+            new PlanNodeId("tablet"),
+            new PartialPath("table1", false),
+            true,
+            new String[] {null},
+            new TSDataType[] {null},
+            new MeasurementSchema[] {null},
+            new long[] {102, 103},
+            null,
+            new Object[] {null},
+            2,
+            new TsTableColumnCategory[] {TsTableColumnCategory.FIELD});
+
+    TsFilePlanRedoer redoer = new TsFilePlanRedoer(tsFileResource);
+    ByteBuffer rowBuffer = ByteBuffer.allocate(row.serializedSize());
+    row.serializeToWAL(new WALByteBufferForTest(rowBuffer));
+    rowBuffer.flip();
+    redoer.redoInsert((RelationalInsertRowNode) 
PlanNodeType.deserializeFromWAL(rowBuffer));
+
+    ByteBuffer tabletBuffer = ByteBuffer.allocate(tablet.serializedSize());
+    tablet.serializeToWAL(new WALByteBufferForTest(tabletBuffer));
+    tabletBuffer.flip();
+    redoer.redoInsert((RelationalInsertTabletNode) 
PlanNodeType.deserializeFromWAL(tabletBuffer));
+
+    assertEquals(1, redoer.getRecoveryMemTable().getMemTableMap().size());
+    assertEquals(
+        3, 
redoer.getRecoveryMemTable().getMemTableMap().values().iterator().next().count(false));
+  }
+
   @Test
   public void testRedoAlignedInsertAfterDeleteTimeseries() throws Exception {
     // generate .tsfile and update resource in memory

Reply via email to