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

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


The following commit(s) were added to refs/heads/table_insert_tablets by this 
push:
     new 8599e584630 fix review comments
8599e584630 is described below

commit 8599e58463038d6da8b6b9896de9f0ba0d2ed122
Author: Tian Jiang <[email protected]>
AuthorDate: Mon Jul 13 10:54:45 2026 +0800

    fix review comments
---
 .../java/org/apache/iotdb/session/Session.java     | 223 +++++++++++++--------
 .../apache/iotdb/session/SessionConnection.java    |   6 +
 .../java/org/apache/iotdb/session/SessionTest.java | 136 ++++++++++++-
 .../plan/analyze/schema/SchemaValidator.java       |  12 +-
 .../write/RelationalInsertMultiTabletsNode.java    |   4 +
 .../plan/relational/planner/TableModelPlanner.java |   5 +-
 .../planner/node/write/WritePlanNodeSplitTest.java |  43 ++++
 7 files changed, 338 insertions(+), 91 deletions(-)

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 e39b5e8d377..63e11b9ba51 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
@@ -2924,14 +2924,20 @@ public class Session implements ISession {
       updateRelationalTabletsReq(tabletGroup, connection, tablet);
       return;
     }
+    final List<SessionConnection> connections = new 
ArrayList<>(tablet.getRowSize());
+    final Map<SessionConnection, Integer> rowCountByConnection = new 
LinkedHashMap<>();
     for (int row = 0; row < tablet.getRowSize(); row++) {
-      final IDeviceID deviceID = tablet.getDeviceID(row);
-      final SessionConnection connection = 
getTableModelSessionConnection(deviceID);
-      final Tablet subTablet =
-          tabletGroup
-              .computeIfAbsent(connection, ignored -> new 
RelationalTabletsReq())
-              .tabletByDevice
-              .computeIfAbsent(deviceID, ignored -> 
createEmptyRelationalTabletLike(tablet));
+      final SessionConnection connection = 
getTableModelSessionConnection(tablet.getDeviceID(row));
+      connections.add(connection);
+      rowCountByConnection.merge(connection, 1, Integer::sum);
+    }
+    final Map<SessionConnection, Tablet> subTabletByConnection = new 
LinkedHashMap<>();
+    rowCountByConnection.forEach(
+        (connection, rowCount) ->
+            subTabletByConnection.put(
+                connection, createEmptyRelationalTabletLike(tablet, 
rowCount)));
+    for (int row = 0; row < tablet.getRowSize(); row++) {
+      final Tablet subTablet = subTabletByConnection.get(connections.get(row));
       for (int column = 0; column < subTablet.getSchemas().size(); column++) {
         subTablet.addValue(
             subTablet.getSchemas().get(column).getMeasurementName(),
@@ -2940,6 +2946,8 @@ public class Session implements ISession {
       }
       subTablet.addTimestamp(subTablet.getRowSize(), tablet.getTimestamp(row));
     }
+    subTabletByConnection.forEach(
+        (connection, subTablet) -> updateRelationalTabletsReq(tabletGroup, 
connection, subTablet));
   }
 
   private SessionConnection getTableModelSessionConnection(final IDeviceID 
deviceID)
@@ -2950,6 +2958,10 @@ public class Session implements ISession {
   }
 
   private Tablet createEmptyRelationalTabletLike(final Tablet tablet) {
+    return createEmptyRelationalTabletLike(tablet, tablet.getRowSize());
+  }
+
+  private Tablet createEmptyRelationalTabletLike(final Tablet tablet, final 
int rowSize) {
     final List<String> measurements = new 
ArrayList<>(tablet.getSchemas().size());
     final List<TSDataType> dataTypes = new 
ArrayList<>(tablet.getSchemas().size());
     tablet
@@ -2960,11 +2972,7 @@ public class Session implements ISession {
               dataTypes.add(schema.getType());
             });
     return new Tablet(
-        tablet.getTableName(),
-        measurements,
-        dataTypes,
-        tablet.getColumnTypes(),
-        tablet.getRowSize());
+        tablet.getTableName(), measurements, dataTypes, 
tablet.getColumnTypes(), rowSize);
   }
 
   private void updateRelationalTabletsReq(
@@ -2992,14 +3000,15 @@ public class Session implements ISession {
     final SessionConnection connection = entry.getKey();
     final RelationalTabletsReq relationalTabletsReq = entry.getValue();
     try {
-      connection.insertTablets(relationalTabletsReq.request);
+      connection.insertTablets(relationalTabletsReq.request, 
relationalTabletsReq.deviceIDStrings);
     } catch (RedirectException e) {
       handleRelationalTabletsRedirection(e, relationalTabletsReq.deviceIDs);
     } catch (IoTDBConnectionException e) {
       if (endPointToSessionConnection != null && 
endPointToSessionConnection.size() > 1) {
         removeBrokenSessionConnection(connection);
         try {
-          
getDefaultSessionConnection().insertTablets(relationalTabletsReq.request);
+          getDefaultSessionConnection()
+              .insertTablets(relationalTabletsReq.request, 
relationalTabletsReq.deviceIDStrings);
         } catch (RedirectException ignored) {
         }
       } else {
@@ -3024,7 +3033,8 @@ public class Session implements ISession {
                   return CompletableFuture.runAsync(
                       () -> {
                         try {
-                          
connection.insertTablets(relationalTabletsReq.request);
+                          connection.insertTablets(
+                              relationalTabletsReq.request, 
relationalTabletsReq.deviceIDStrings);
                         } catch (RedirectException e) {
                           handleRelationalTabletsRedirection(e, 
relationalTabletsReq.deviceIDs);
                         } catch (StatementExecutionException e) {
@@ -3033,7 +3043,9 @@ public class Session implements ISession {
                           removeBrokenSessionConnection(connection);
                           try {
                             getDefaultSessionConnection()
-                                .insertTablets(relationalTabletsReq.request);
+                                .insertTablets(
+                                    relationalTabletsReq.request,
+                                    relationalTabletsReq.deviceIDStrings);
                           } catch (IoTDBConnectionException | 
StatementExecutionException ex) {
                             throw new CompletionException(ex);
                           } catch (RedirectException ignored) {
@@ -3067,6 +3079,16 @@ public class Session implements ISession {
 
   private void handleRelationalTabletsRedirection(
       final RedirectException redirectException, final List<IDeviceID> 
deviceIDs) {
+    final Map<String, TEndPoint> deviceEndPointMap = 
redirectException.getDeviceEndPointMap();
+    if (deviceEndPointMap != null) {
+      for (final IDeviceID deviceID : deviceIDs) {
+        final TEndPoint endPoint = deviceEndPointMap.get(deviceID.toString());
+        if (endPoint != null) {
+          handleRedirection(deviceID, endPoint);
+        }
+      }
+      return;
+    }
     final List<TEndPoint> endPointList = redirectException.getEndPointList();
     if (endPointList == null) {
       return;
@@ -3087,23 +3109,52 @@ public class Session implements ISession {
     sortedTablets.sort(
         Comparator.comparing(
             Tablet::getTableName, 
Comparator.nullsFirst(Comparator.naturalOrder())));
-    final List<RelationalTabletWithColumnIndexMap> mergedTablets = new 
ArrayList<>(tablets.size());
+    final List<Tablet> mergedTablets = new ArrayList<>(tablets.size());
+    final List<Tablet> currentGroup = new ArrayList<>();
+    Map<String, Integer> currentColumnIndexMap = new LinkedHashMap<>();
+    List<String> currentMeasurements = new ArrayList<>();
+    List<TSDataType> currentDataTypes = new ArrayList<>();
+    List<ColumnCategory> currentColumnTypes = new ArrayList<>();
     boolean anyMerged = false;
     for (final Tablet tablet : sortedTablets) {
-      boolean merged = false;
-      for (int i = 0; i < mergedTablets.size(); i++) {
-        final RelationalTabletWithColumnIndexMap candidate = 
mergedTablets.get(i);
-        if (canMergeRelationalTablets(candidate.tablet, 
candidate.columnIndexMap, tablet)) {
-          mergedTablets.set(i, mergeRelationalTablets(candidate, tablet));
-          merged = true;
-          anyMerged = true;
-          break;
-        }
+      if (currentGroup.isEmpty()) {
+        currentGroup.add(tablet);
+        currentColumnIndexMap = getColumnIndexMap(tablet);
+        currentMeasurements = getMeasurements(tablet);
+        currentDataTypes = getDataTypes(tablet);
+        currentColumnTypes = new ArrayList<>(tablet.getColumnTypes());
+        continue;
       }
-      if (!merged) {
-        mergedTablets.add(
-            new RelationalTabletWithColumnIndexMap(tablet, 
getColumnIndexMap(tablet)));
+      if (canMergeRelationalTablets(
+          currentGroup.get(0).getTableName(),
+          currentColumnIndexMap,
+          currentDataTypes,
+          currentColumnTypes,
+          tablet)) {
+        currentGroup.add(tablet);
+        addMissingColumns(
+            tablet,
+            currentColumnIndexMap,
+            currentMeasurements,
+            currentDataTypes,
+            currentColumnTypes);
+        anyMerged = true;
+        continue;
       }
+      mergedTablets.add(
+          mergeRelationalTablets(
+              currentGroup, currentMeasurements, currentDataTypes, 
currentColumnTypes));
+      currentGroup.clear();
+      currentGroup.add(tablet);
+      currentColumnIndexMap = getColumnIndexMap(tablet);
+      currentMeasurements = getMeasurements(tablet);
+      currentDataTypes = getDataTypes(tablet);
+      currentColumnTypes = new ArrayList<>(tablet.getColumnTypes());
+    }
+    if (!currentGroup.isEmpty()) {
+      mergedTablets.add(
+          mergeRelationalTablets(
+              currentGroup, currentMeasurements, currentDataTypes, 
currentColumnTypes));
     }
     if (anyMerged) {
       consecutiveRelationalTabletMergeMissCount = 0;
@@ -3114,9 +3165,7 @@ public class Session implements ISession {
         enableMergeTablets = false;
       }
     }
-    return mergedTablets.stream()
-        .map(relationalTablet -> relationalTablet.tablet)
-        .collect(Collectors.toList());
+    return mergedTablets;
   }
 
   private void recordMergeTabletsCost(
@@ -3137,8 +3186,12 @@ public class Session implements ISession {
   }
 
   private boolean canMergeRelationalTablets(
-      final Tablet left, final Map<String, Integer> leftColumnIndexMap, final 
Tablet right) {
-    if (!Objects.equals(left.getTableName(), right.getTableName())) {
+      final String leftTableName,
+      final Map<String, Integer> leftColumnIndexMap,
+      final List<TSDataType> leftDataTypes,
+      final List<ColumnCategory> leftColumnTypes,
+      final Tablet right) {
+    if (!Objects.equals(leftTableName, right.getTableName())) {
       return false;
     }
     final List<IMeasurementSchema> rightSchemas = right.getSchemas();
@@ -3149,13 +3202,13 @@ public class Session implements ISession {
       if (leftIndex == null) {
         continue;
       }
-      if (left.getSchemas().get(leftIndex).getType() != rightSchema.getType()
-          || left.getColumnTypes().get(leftIndex) != 
right.getColumnTypes().get(rightIndex)) {
+      if (leftDataTypes.get(leftIndex) != rightSchema.getType()
+          || leftColumnTypes.get(leftIndex) != 
right.getColumnTypes().get(rightIndex)) {
         return false;
       }
       duplicatedColumnCount++;
     }
-    return (double) duplicatedColumnCount / Math.max(left.getSchemas().size(), 
rightSchemas.size())
+    return (double) duplicatedColumnCount / Math.max(leftDataTypes.size(), 
rightSchemas.size())
         > 0.5;
   }
 
@@ -3167,53 +3220,66 @@ public class Session implements ISession {
     return columnIndexMap;
   }
 
-  private Map<String, Integer> getColumnIndexMap(final List<String> 
measurements) {
-    final Map<String, Integer> columnIndexMap = new 
HashMap<>(measurements.size());
-    for (int i = 0; i < measurements.size(); i++) {
-      columnIndexMap.put(measurements.get(i), i);
-    }
-    return columnIndexMap;
+  private List<String> getMeasurements(final Tablet tablet) {
+    final List<String> measurements = new 
ArrayList<>(tablet.getSchemas().size());
+    tablet.getSchemas().forEach(schema -> 
measurements.add(schema.getMeasurementName()));
+    return measurements;
   }
 
-  private RelationalTabletWithColumnIndexMap mergeRelationalTablets(
-      final RelationalTabletWithColumnIndexMap left, final Tablet right) {
-    final List<String> measurements =
-        new ArrayList<>(left.tablet.getSchemas().size() + 
right.getSchemas().size());
-    final List<TSDataType> dataTypes =
-        new ArrayList<>(left.tablet.getSchemas().size() + 
right.getSchemas().size());
-    final List<ColumnCategory> columnTypes =
-        new ArrayList<>(left.tablet.getSchemas().size() + 
right.getSchemas().size());
-    for (int i = 0; i < left.tablet.getSchemas().size(); i++) {
-      measurements.add(left.tablet.getSchemas().get(i).getMeasurementName());
-      dataTypes.add(left.tablet.getSchemas().get(i).getType());
-      columnTypes.add(left.tablet.getColumnTypes().get(i));
-    }
-    for (int i = 0; i < right.getSchemas().size(); i++) {
-      final IMeasurementSchema rightSchema = right.getSchemas().get(i);
-      if (!left.columnIndexMap.containsKey(rightSchema.getMeasurementName())) {
-        measurements.add(rightSchema.getMeasurementName());
-        dataTypes.add(rightSchema.getType());
-        columnTypes.add(right.getColumnTypes().get(i));
+  private List<TSDataType> getDataTypes(final Tablet tablet) {
+    final List<TSDataType> dataTypes = new 
ArrayList<>(tablet.getSchemas().size());
+    tablet.getSchemas().forEach(schema -> dataTypes.add(schema.getType()));
+    return dataTypes;
+  }
+
+  private void addMissingColumns(
+      final Tablet tablet,
+      final Map<String, Integer> columnIndexMap,
+      final List<String> measurements,
+      final List<TSDataType> dataTypes,
+      final List<ColumnCategory> columnTypes) {
+    for (int i = 0; i < tablet.getSchemas().size(); i++) {
+      final IMeasurementSchema schema = tablet.getSchemas().get(i);
+      if (columnIndexMap.containsKey(schema.getMeasurementName())) {
+        continue;
       }
+      columnIndexMap.put(schema.getMeasurementName(), measurements.size());
+      measurements.add(schema.getMeasurementName());
+      dataTypes.add(schema.getType());
+      columnTypes.add(tablet.getColumnTypes().get(i));
     }
+  }
 
-    final int rowCount = left.tablet.getRowSize() + right.getRowSize();
+  private Tablet mergeRelationalTablets(
+      final List<Tablet> tablets,
+      final List<String> measurements,
+      final List<TSDataType> dataTypes,
+      final List<ColumnCategory> columnTypes) {
+    if (tablets.size() == 1) {
+      return tablets.get(0);
+    }
+    int rowCount = 0;
+    for (final Tablet tablet : tablets) {
+      rowCount += tablet.getRowSize();
+    }
     final Tablet merged =
-        new Tablet(left.tablet.getTableName(), measurements, dataTypes, 
columnTypes, rowCount);
+        new Tablet(tablets.get(0).getTableName(), measurements, dataTypes, 
columnTypes, rowCount);
     final long[] timestamps = new long[rowCount];
     final Object[] values = new Object[measurements.size()];
     final BitMap[] bitMaps = new BitMap[measurements.size()];
     for (int i = 0; i < measurements.size(); i++) {
       values[i] = createValueColumn(dataTypes.get(i), rowCount);
     }
-
-    copyTabletColumns(left.tablet, 0, measurements, values, bitMaps, 
timestamps);
-    copyTabletColumns(right, left.tablet.getRowSize(), measurements, values, 
bitMaps, timestamps);
+    int targetRowOffset = 0;
+    for (final Tablet tablet : tablets) {
+      copyTabletColumns(tablet, targetRowOffset, measurements, values, 
bitMaps, timestamps);
+      targetRowOffset += tablet.getRowSize();
+    }
     merged.setTimestamps(timestamps);
     merged.setValues(values);
     merged.setBitMaps(bitMaps);
     merged.setRowSize(rowCount);
-    return new RelationalTabletWithColumnIndexMap(merged, 
getColumnIndexMap(measurements));
+    return merged;
   }
 
   private void copyTabletColumns(
@@ -3295,24 +3361,12 @@ public class Session implements ISession {
     }
   }
 
-  private static class RelationalTabletWithColumnIndexMap {
-
-    private final Tablet tablet;
-    private final Map<String, Integer> columnIndexMap;
-
-    private RelationalTabletWithColumnIndexMap(
-        final Tablet tablet, final Map<String, Integer> columnIndexMap) {
-      this.tablet = tablet;
-      this.columnIndexMap = columnIndexMap;
-    }
-  }
-
   private class RelationalTabletsReq {
 
     private final TSInsertTabletsReq request = new TSInsertTabletsReq();
     private final List<IDeviceID> deviceIDs = new ArrayList<>();
+    private final List<String> deviceIDStrings = new ArrayList<>();
     private final List<Tablet> tablets = new ArrayList<>();
-    private final Map<IDeviceID, Tablet> tabletByDevice = new 
LinkedHashMap<>();
 
     private void addTablet(final Tablet tablet) {
       tablets.add(tablet);
@@ -3321,13 +3375,16 @@ public class Session implements ISession {
     private void buildRequest() {
       request.setWriteToTable(true);
       tablets.forEach(this::addTabletToRequest);
-      tabletByDevice.values().forEach(this::addTabletToRequest);
     }
 
     private void addTabletToRequest(final Tablet tablet) {
       updateTSInsertTabletsReq(request, tablet, false, false);
       
request.addToColumnCategoriesList(toEnumOrdinalsAsBytes(tablet.getColumnTypes()));
-      deviceIDs.add(tablet.getDeviceID(0));
+      for (int row = 0; row < tablet.getRowSize(); row++) {
+        final IDeviceID deviceID = tablet.getDeviceID(row);
+        deviceIDs.add(deviceID);
+        deviceIDStrings.add(deviceID.toString());
+      }
     }
   }
 
diff --git 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
index ad6699aaf87..4c30ba468f8 100644
--- 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
+++ 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
@@ -870,6 +870,12 @@ public class SessionConnection {
         () -> insertTabletsInternal(request), request::getPrefixPaths);
   }
 
+  protected void insertTablets(TSInsertTabletsReq request, List<String> 
devices)
+      throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
+    callWithRetryAndVerifyWithRedirectionForMultipleDevices(
+        () -> insertTabletsInternal(request), () -> devices);
+  }
+
   private TSStatus insertTabletsInternal(TSInsertTabletsReq request) throws 
TException {
     request.setSessionId(sessionId);
     return client.insertTablets(request);
diff --git 
a/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionTest.java 
b/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionTest.java
index 0b317311ce7..ac5959bd3e9 100644
--- 
a/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionTest.java
+++ 
b/iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionTest.java
@@ -25,6 +25,7 @@ import org.apache.iotdb.isession.ISession;
 import org.apache.iotdb.isession.SessionConfig;
 import org.apache.iotdb.isession.util.Version;
 import org.apache.iotdb.rpc.IoTDBConnectionException;
+import org.apache.iotdb.rpc.RedirectException;
 import org.apache.iotdb.rpc.StatementExecutionException;
 import org.apache.iotdb.service.rpc.thrift.TSInsertTabletsReq;
 import org.apache.iotdb.service.rpc.thrift.TSQueryTemplateResp;
@@ -61,6 +62,7 @@ import java.util.Map;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
 
 public class SessionTest {
 
@@ -1029,6 +1031,72 @@ public class SessionTest {
     Assert.assertTrue((boolean) mergedTablet.getValue(1, 3));
   }
 
+  @Test
+  public void testMergeRelationalTabletsChecksColumnsAddedByEarlierTablets() 
throws Exception {
+    final Tablet first =
+        createRelationalTablet(
+            "table1",
+            Arrays.asList("tag1", "s1", "s2"),
+            Arrays.asList(TSDataType.STRING, TSDataType.INT64, 
TSDataType.DOUBLE),
+            Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD, 
ColumnCategory.FIELD),
+            1,
+            "d1",
+            11L,
+            1.1);
+    final Tablet second =
+        createRelationalTablet(
+            "table1",
+            Arrays.asList("tag1", "s1", "s3"),
+            Arrays.asList(TSDataType.STRING, TSDataType.INT64, 
TSDataType.BOOLEAN),
+            Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD, 
ColumnCategory.FIELD),
+            2,
+            "d2",
+            22L,
+            true);
+    final Tablet conflicting =
+        createRelationalTablet(
+            "table1",
+            Arrays.asList("tag1", "s1", "s3"),
+            Arrays.asList(TSDataType.STRING, TSDataType.INT64, 
TSDataType.INT32),
+            Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD, 
ColumnCategory.FIELD),
+            3,
+            "d3",
+            33L,
+            3);
+
+    final List<Tablet> mergedTablets =
+        Whitebox.invokeMethod(
+            session, "mergeRelationalTablets", Arrays.asList(first, second, 
conflicting));
+
+    assertEquals(2, mergedTablets.size());
+    assertEquals(2, mergedTablets.get(0).getRowSize());
+    Assert.assertSame(conflicting, mergedTablets.get(1));
+  }
+
+  @Test(timeout = 10000)
+  public void testMergeLargeRelationalTabletList() throws Exception {
+    final int tabletCount = 5000;
+    final List<Tablet> tablets = new ArrayList<>(tabletCount);
+    for (int i = 0; i < tabletCount; i++) {
+      tablets.add(
+          createRelationalTablet(
+              "table1",
+              Arrays.asList("tag1", "s1"),
+              Arrays.asList(TSDataType.STRING, TSDataType.INT64),
+              Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
+              i,
+              "d" + i,
+              (long) i));
+    }
+
+    final List<Tablet> mergedTablets =
+        Whitebox.invokeMethod(session, "mergeRelationalTablets", tablets);
+
+    assertEquals(1, mergedTablets.size());
+    assertEquals(tabletCount, mergedTablets.get(0).getRowSize());
+    assertEquals((long) tabletCount - 1, 
mergedTablets.get(0).getValue(tabletCount - 1, 1));
+  }
+
   @Test
   public void 
testMergeRelationalTabletsSkipLowDuplicatedColumnsAndDifferentTables()
       throws Exception {
@@ -1205,7 +1273,7 @@ public class SessionTest {
   public void testMergeTabletsCostNotRecordedWhenInsertFails() throws 
Exception {
     Mockito.doThrow(new StatementExecutionException("expected"))
         .when(sessionConnection)
-        .insertTablets(any(TSInsertTabletsReq.class));
+        .insertTablets(any(TSInsertTabletsReq.class), anyList());
     final Tablet first =
         createRelationalTablet(
             "table1",
@@ -1263,8 +1331,70 @@ public class SessionTest {
 
     ((Session) 
session).insertRelationalTablets(Collections.singletonList(tablet));
 
-    
Mockito.verify(redirectedSessionConnection).insertTablets(any(TSInsertTabletsReq.class));
-    Mockito.verify(sessionConnection, 
Mockito.never()).insertTablets(any(TSInsertTabletsReq.class));
+    Mockito.verify(redirectedSessionConnection)
+        .insertTablets(any(TSInsertTabletsReq.class), anyList());
+    Mockito.verify(sessionConnection, Mockito.never())
+        .insertTablets(any(TSInsertTabletsReq.class), anyList());
+  }
+
+  @Test
+  public void testInsertRelationalTabletsUpdatesTableModelLeaderCache() throws 
Exception {
+    final Tablet tablet =
+        createRelationalTablet(
+            "table1",
+            Arrays.asList("tag1", "s1"),
+            Arrays.asList(TSDataType.STRING, TSDataType.INT64),
+            Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
+            1L,
+            "tag1",
+            11L);
+    final IDeviceID deviceID = tablet.getDeviceID(0);
+    final TEndPoint redirectEndPoint = new TEndPoint("127.0.0.2", 6667);
+    final Map<TEndPoint, SessionConnection> endPointToSessionConnection = new 
HashMap<>();
+    endPointToSessionConnection.put(redirectEndPoint, 
Mockito.mock(SessionConnection.class));
+    Whitebox.setInternalState(session, "enableRedirection", true);
+    Whitebox.setInternalState(session, "tableModelDeviceIdToEndpoint", new 
HashMap<>());
+    Whitebox.setInternalState(session, "endPointToSessionConnection", 
endPointToSessionConnection);
+    Mockito.doThrow(
+            new 
RedirectException(Collections.singletonMap(deviceID.toString(), 
redirectEndPoint)))
+        .when(sessionConnection)
+        .insertTablets(any(TSInsertTabletsReq.class), anyList());
+
+    ((Session) 
session).insertRelationalTablets(Collections.singletonList(tablet));
+
+    final Map<IDeviceID, TEndPoint> tableModelDeviceIdToEndpoint =
+        Whitebox.getInternalState(session, "tableModelDeviceIdToEndpoint");
+    assertEquals(redirectEndPoint, tableModelDeviceIdToEndpoint.get(deviceID));
+    
Mockito.verify(sessionConnection).insertTablets(any(TSInsertTabletsReq.class), 
anyList());
+  }
+
+  @Test
+  public void testSplitRelationalTabletAllocatesOncePerEndpoint() throws 
Exception {
+    final int rowCount = 1000;
+    final List<String> measurements = Arrays.asList("tag1", "s1");
+    final Tablet tablet =
+        new Tablet(
+            "table1",
+            measurements,
+            Arrays.asList(TSDataType.STRING, TSDataType.INT64),
+            Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
+            rowCount);
+    for (int row = 0; row < rowCount; row++) {
+      tablet.addTimestamp(row, row);
+      tablet.addValue(measurements.get(0), row, "d" + row);
+      tablet.addValue(measurements.get(1), row, (long) row);
+    }
+    Whitebox.setInternalState(session, "tableModelDeviceIdToEndpoint", new 
HashMap<>());
+    final Map<SessionConnection, Object> tabletGroup = new HashMap<>();
+
+    Whitebox.invokeMethod(session, "addRelationalTabletToGroup", tabletGroup, 
tablet);
+
+    assertEquals(1, tabletGroup.size());
+    final List<Tablet> groupedTablets =
+        Whitebox.getInternalState(tabletGroup.values().iterator().next(), 
"tablets");
+    assertEquals(1, groupedTablets.size());
+    assertEquals(rowCount, groupedTablets.get(0).getMaxRowNumber());
+    assertEquals(rowCount, groupedTablets.get(0).getRowSize());
   }
 
   @Test
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java
index f400ec4f3cd..b80d716a1ac 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java
@@ -41,7 +41,9 @@ import org.apache.tsfile.file.metadata.enums.TSEncoding;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static org.apache.iotdb.commons.utils.PathUtils.unQualifyDatabaseName;
 
@@ -93,13 +95,17 @@ public class SchemaValidator {
       final MPPQueryContext context,
       final AccessControl accessControl) {
     try {
+      final String database = 
unQualifyDatabaseName(insertStatement.getDatabase());
+      final Set<String> checkedTables = new HashSet<>();
       for (InsertTabletStatement tabletStatement :
           
insertStatement.getInnerTreeStatement().getInsertTabletStatementList()) {
+        final String tableName = tabletStatement.getDevicePath().getFullPath();
+        if (!checkedTables.add(tableName)) {
+          continue;
+        }
         accessControl.checkCanInsertIntoTable(
             context.getSession().getUserName(),
-            new QualifiedObjectName(
-                unQualifyDatabaseName(insertStatement.getDatabase()),
-                tabletStatement.getDevicePath().getFullPath()),
+            new QualifiedObjectName(database, tableName),
             context);
       }
       insertStatement.validateTableSchema(metadata, context);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertMultiTabletsNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertMultiTabletsNode.java
index 7ce55c4cc87..261912b8edd 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertMultiTabletsNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertMultiTabletsNode.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.db.queryengine.plan.planner.plan.node.write;
 
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
 import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
 import 
org.apache.iotdb.commons.queryengine.plan.planner.plan.node.IPlanVisitor;
 import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
@@ -58,9 +59,11 @@ public class RelationalInsertMultiTabletsNode extends 
InsertMultiTabletsNode {
   @Override
   public List<WritePlanNode> splitByPartition(IAnalysis analysis) {
     Map<TRegionReplicaSet, RelationalInsertMultiTabletsNode> splitMap = new 
HashMap<>();
+    final List<TEndPoint> redirectNodeList = new ArrayList<>();
     for (int i = 0; i < insertTabletNodeList.size(); i++) {
       InsertTabletNode insertTabletNode = insertTabletNodeList.get(i);
       List<WritePlanNode> tmpResult = 
insertTabletNode.splitByPartition(analysis);
+      redirectNodeList.addAll(analysis.getRedirectNodeList());
       for (WritePlanNode subNode : tmpResult) {
         TRegionReplicaSet dataRegionReplicaSet = ((InsertNode) 
subNode).getDataRegionReplicaSet();
         RelationalInsertMultiTabletsNode tmpNode = 
splitMap.get(dataRegionReplicaSet);
@@ -75,6 +78,7 @@ public class RelationalInsertMultiTabletsNode extends 
InsertMultiTabletsNode {
         tmpNode.addInsertTabletNode((InsertTabletNode) subNode, i);
       }
     }
+    analysis.setRedirectNodeList(redirectNodeList);
     return new ArrayList<>(splitMap.values());
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableModelPlanner.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableModelPlanner.java
index c8433d95416..9a32bb52860 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableModelPlanner.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableModelPlanner.java
@@ -55,6 +55,7 @@ import 
org.apache.iotdb.db.queryengine.plan.scheduler.ClusterScheduler;
 import org.apache.iotdb.db.queryengine.plan.scheduler.IScheduler;
 import org.apache.iotdb.db.queryengine.plan.scheduler.load.LoadTsFileScheduler;
 import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertBaseStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertMultiTabletsStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
 import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.TSStatusCode;
@@ -238,8 +239,8 @@ public class TableModelPlanner implements IPlanner {
         ((WrappedInsertStatement) statementToRedirect).getInnerTreeStatement();
 
     if (!analysis.isFinishQueryAfterAnalyze()) {
-      // Table Model Session only supports insertTablet
-      if (insertStatement instanceof InsertTabletStatement) {
+      if (insertStatement instanceof InsertTabletStatement
+          || insertStatement instanceof InsertMultiTabletsStatement) {
         if (tsstatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) 
{
           boolean needRedirect = false;
           List<TEndPoint> redirectNodeList = analysis.getRedirectNodeList();
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/node/write/WritePlanNodeSplitTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/node/write/WritePlanNodeSplitTest.java
index 6d5185af4ba..1805b4e128a 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/node/write/WritePlanNodeSplitTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/node/write/WritePlanNodeSplitTest.java
@@ -43,6 +43,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNod
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsOfOneDeviceNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertMultiTabletsNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertTabletNode;
 
 import org.apache.tsfile.enums.TSDataType;
@@ -364,6 +365,48 @@ public class WritePlanNodeSplitTest {
     }
   }
 
+  @Test
+  public void testSplitRelationalInsertMultiTabletsCollectsRedirectNodes()
+      throws IllegalPathException {
+    final RelationalInsertMultiTabletsNode insertMultiTabletsNode =
+        new RelationalInsertMultiTabletsNode(new PlanNodeId("plan node 2"));
+    final List<DataPartitionQueryParam> dataPartitionQueryParams = new 
ArrayList<>();
+    for (int tabletIndex = 0; tabletIndex < 2; tabletIndex++) {
+      final RelationalInsertTabletNode insertTabletNode =
+          new RelationalInsertTabletNode(new PlanNodeId("plan node 2"));
+      insertTabletNode.setTargetPath(new PartialPath("root.sg1"));
+      insertTabletNode.setTimes(new long[] {1, 101});
+      insertTabletNode.setDataTypes(new TSDataType[] {TSDataType.STRING, 
TSDataType.INT32});
+      insertTabletNode.setColumns(
+          new Object[] {
+            new Binary[] {
+              new Binary("d" + tabletIndex + "0", StandardCharsets.UTF_8),
+              new Binary("d" + tabletIndex + "1", StandardCharsets.UTF_8)
+            },
+            new int[] {tabletIndex * 2, tabletIndex * 2 + 1}
+          });
+      insertTabletNode.setColumnCategories(
+          new TsTableColumnCategory[] {TsTableColumnCategory.TAG, 
TsTableColumnCategory.FIELD});
+      insertTabletNode.setRowCount(2);
+      insertMultiTabletsNode.addInsertTabletNode(insertTabletNode, 
tabletIndex);
+      for (int row = 0; row < insertTabletNode.getRowCount(); row++) {
+        final DataPartitionQueryParam queryParam = new 
DataPartitionQueryParam();
+        queryParam.setDeviceID(insertTabletNode.getDeviceID(row));
+        
queryParam.setTimePartitionSlotList(insertTabletNode.getTimePartitionSlots());
+        dataPartitionQueryParams.add(queryParam);
+      }
+    }
+    final Analysis analysis = new Analysis();
+    analysis.setDataPartitionInfo(getDataPartition(dataPartitionQueryParams));
+
+    insertMultiTabletsNode.splitByPartition(analysis);
+
+    Assert.assertEquals(4, analysis.getRedirectNodeList().size());
+    for (final TEndPoint redirectNode : analysis.getRedirectNodeList()) {
+      Assert.assertNotNull(redirectNode);
+    }
+  }
+
   @Test
   public void testInsertMultiTablets() throws IllegalPathException {
     InsertMultiTabletsNode insertMultiTabletsNode =


Reply via email to