jt2594838 commented on code in PR #18132:
URL: https://github.com/apache/iotdb/pull/18132#discussion_r3567796846


##########
iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java:
##########
@@ -2857,6 +2867,491 @@ public void insertRelationalTablet(Tablet tablet)
     }
   }
 
+  /**
+   * insert relational Tablets.
+   *
+   * @param tablets data batches
+   */
+  public void insertRelationalTablets(List<Tablet> tablets)
+      throws IoTDBConnectionException, StatementExecutionException {
+    final boolean recordMergeTabletsCost = enableMergeTablets;
+    long mergeTabletsCost = 0;
+    if (enableMergeTablets) {
+      final long mergeTabletsStartTime = System.nanoTime();
+      tablets = mergeRelationalTablets(tablets);
+      mergeTabletsCost = System.nanoTime() - mergeTabletsStartTime;
+    }
+    if (tablets.isEmpty()) {
+      throw new BatchExecutionException(SessionMessages.NO_TABLET_INSERTING);
+    }
+    final long insertTabletsStartTime = System.nanoTime();
+    if (enableRedirection) {
+      insertRelationalTabletsWithLeaderCache(tablets);
+    } else {
+      TSInsertTabletsReq request = genTSInsertTabletsReq(tablets, false, 
false);
+      request.setWriteToTable(true);
+      for (Tablet tablet : tablets) {
+        
request.addToColumnCategoriesList(toEnumOrdinalsAsBytes(tablet.getColumnTypes()));
+      }
+      try {
+        getDefaultSessionConnection().insertTablets(request);
+      } catch (RedirectException ignored) {
+      }
+    }
+    if (recordMergeTabletsCost) {
+      recordMergeTabletsCost(mergeTabletsCost, System.nanoTime() - 
insertTabletsStartTime);
+    }
+  }
+
+  private void insertRelationalTabletsWithLeaderCache(final List<Tablet> 
tablets)
+      throws IoTDBConnectionException, StatementExecutionException {
+    final Map<SessionConnection, RelationalTabletsReq> tabletGroup = new 
HashMap<>();
+    for (final Tablet tablet : tablets) {
+      addRelationalTabletToGroup(tabletGroup, tablet);
+    }
+    if (tabletGroup.size() == 1) {
+      insertRelationalTabletsOnce(tabletGroup);
+    } else {
+      insertRelationalTabletsByGroup(tabletGroup);
+    }
+  }
+
+  private void addRelationalTabletToGroup(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup, final 
Tablet tablet)
+      throws IoTDBConnectionException {
+    if (SessionUtils.isTabletContainsSingleDevice(tablet)) {
+      final SessionConnection connection = 
getTableModelSessionConnection(tablet.getDeviceID(0));
+      updateRelationalTabletsReq(tabletGroup, connection, tablet);
+      return;
+    }
+    final Map<SessionConnection, Map<IDeviceID, Tablet>> 
subTabletsByConnection =
+        new LinkedHashMap<>();
+    for (int row = 0; row < tablet.getRowSize(); row++) {
+      final IDeviceID deviceID = tablet.getDeviceID(row);
+      final SessionConnection connection = 
getTableModelSessionConnection(deviceID);
+      final Tablet subTablet =
+          subTabletsByConnection
+              .computeIfAbsent(connection, ignored -> new LinkedHashMap<>())
+              .computeIfAbsent(deviceID, ignored -> 
createEmptyRelationalTabletLike(tablet));

Review Comment:
   Thanks for catching this. I changed the split logic to group rows directly 
by target endpoint and pre-count the rows for each endpoint before allocating 
sub-tablets. Each sub-tablet now has the exact required capacity, so the 
empty-cache case creates one R-row tablet instead of D full-capacity tablets. I 
also added a 1,000-distinct-device unit test that verifies only one 
exact-capacity tablet is allocated when all rows use the default endpoint. This 
will be included in the next push.



##########
iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java:
##########
@@ -2857,6 +2867,491 @@ public void insertRelationalTablet(Tablet tablet)
     }
   }
 
+  /**
+   * insert relational Tablets.
+   *
+   * @param tablets data batches
+   */
+  public void insertRelationalTablets(List<Tablet> tablets)
+      throws IoTDBConnectionException, StatementExecutionException {
+    final boolean recordMergeTabletsCost = enableMergeTablets;
+    long mergeTabletsCost = 0;
+    if (enableMergeTablets) {
+      final long mergeTabletsStartTime = System.nanoTime();
+      tablets = mergeRelationalTablets(tablets);
+      mergeTabletsCost = System.nanoTime() - mergeTabletsStartTime;
+    }
+    if (tablets.isEmpty()) {
+      throw new BatchExecutionException(SessionMessages.NO_TABLET_INSERTING);
+    }
+    final long insertTabletsStartTime = System.nanoTime();
+    if (enableRedirection) {
+      insertRelationalTabletsWithLeaderCache(tablets);
+    } else {
+      TSInsertTabletsReq request = genTSInsertTabletsReq(tablets, false, 
false);
+      request.setWriteToTable(true);
+      for (Tablet tablet : tablets) {
+        
request.addToColumnCategoriesList(toEnumOrdinalsAsBytes(tablet.getColumnTypes()));
+      }
+      try {
+        getDefaultSessionConnection().insertTablets(request);
+      } catch (RedirectException ignored) {
+      }
+    }
+    if (recordMergeTabletsCost) {
+      recordMergeTabletsCost(mergeTabletsCost, System.nanoTime() - 
insertTabletsStartTime);
+    }
+  }
+
+  private void insertRelationalTabletsWithLeaderCache(final List<Tablet> 
tablets)
+      throws IoTDBConnectionException, StatementExecutionException {
+    final Map<SessionConnection, RelationalTabletsReq> tabletGroup = new 
HashMap<>();
+    for (final Tablet tablet : tablets) {
+      addRelationalTabletToGroup(tabletGroup, tablet);
+    }
+    if (tabletGroup.size() == 1) {
+      insertRelationalTabletsOnce(tabletGroup);
+    } else {
+      insertRelationalTabletsByGroup(tabletGroup);
+    }
+  }
+
+  private void addRelationalTabletToGroup(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup, final 
Tablet tablet)
+      throws IoTDBConnectionException {
+    if (SessionUtils.isTabletContainsSingleDevice(tablet)) {
+      final SessionConnection connection = 
getTableModelSessionConnection(tablet.getDeviceID(0));
+      updateRelationalTabletsReq(tabletGroup, connection, tablet);
+      return;
+    }
+    final Map<SessionConnection, Map<IDeviceID, Tablet>> 
subTabletsByConnection =
+        new LinkedHashMap<>();
+    for (int row = 0; row < tablet.getRowSize(); row++) {
+      final IDeviceID deviceID = tablet.getDeviceID(row);
+      final SessionConnection connection = 
getTableModelSessionConnection(deviceID);
+      final Tablet subTablet =
+          subTabletsByConnection
+              .computeIfAbsent(connection, ignored -> new LinkedHashMap<>())
+              .computeIfAbsent(deviceID, ignored -> 
createEmptyRelationalTabletLike(tablet));
+      for (int column = 0; column < subTablet.getSchemas().size(); column++) {
+        subTablet.addValue(
+            subTablet.getSchemas().get(column).getMeasurementName(),
+            subTablet.getRowSize(),
+            tablet.getValue(row, column));
+      }
+      subTablet.addTimestamp(subTablet.getRowSize(), tablet.getTimestamp(row));
+    }
+    subTabletsByConnection.forEach(
+        (connection, subTablets) ->
+            subTablets
+                .values()
+                .forEach(
+                    subTablet -> updateRelationalTabletsReq(tabletGroup, 
connection, subTablet)));
+  }
+
+  private SessionConnection getTableModelSessionConnection(final IDeviceID 
deviceID)
+      throws IoTDBConnectionException {
+    return tableModelDeviceIdToEndpoint == null || 
tableModelDeviceIdToEndpoint.isEmpty()
+        ? getDefaultSessionConnection()
+        : getSessionConnection(deviceID);
+  }
+
+  private Tablet createEmptyRelationalTabletLike(final Tablet tablet) {
+    final List<String> measurements = new 
ArrayList<>(tablet.getSchemas().size());
+    final List<TSDataType> dataTypes = new 
ArrayList<>(tablet.getSchemas().size());
+    tablet
+        .getSchemas()
+        .forEach(
+            schema -> {
+              measurements.add(schema.getMeasurementName());
+              dataTypes.add(schema.getType());
+            });
+    return new Tablet(
+        tablet.getTableName(),
+        measurements,
+        dataTypes,
+        tablet.getColumnTypes(),
+        tablet.getRowSize());
+  }
+
+  private void updateRelationalTabletsReq(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup,
+      final SessionConnection connection,
+      final Tablet tablet) {
+    tabletGroup
+        .computeIfAbsent(connection, ignored -> new RelationalTabletsReq())
+        .addTablet(tablet);
+  }
+
+  private void buildRelationalTabletsRequests(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup) {
+    for (final RelationalTabletsReq relationalTabletsReq : 
tabletGroup.values()) {
+      relationalTabletsReq.buildRequest();
+    }
+  }
+
+  private void insertRelationalTabletsOnce(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup)
+      throws IoTDBConnectionException, StatementExecutionException {
+    buildRelationalTabletsRequests(tabletGroup);
+    final Map.Entry<SessionConnection, RelationalTabletsReq> entry =
+        tabletGroup.entrySet().iterator().next();
+    final SessionConnection connection = entry.getKey();
+    final RelationalTabletsReq relationalTabletsReq = entry.getValue();
+    try {
+      connection.insertTablets(relationalTabletsReq.request);
+    } catch (RedirectException e) {
+      handleRelationalTabletsRedirection(e, relationalTabletsReq.deviceIDs);
+    } catch (IoTDBConnectionException e) {
+      if (endPointToSessionConnection != null && 
endPointToSessionConnection.size() > 1) {
+        removeBrokenSessionConnection(connection);
+        try {
+          
getDefaultSessionConnection().insertTablets(relationalTabletsReq.request);
+        } catch (RedirectException ignored) {
+        }
+      } else {
+        throw e;
+      }
+    }
+  }
+
+  @SuppressWarnings({
+    "squid:S3776"
+  }) // ignore Cognitive Complexity of methods should not be too high
+  private void insertRelationalTabletsByGroup(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup)
+      throws IoTDBConnectionException, StatementExecutionException {
+    buildRelationalTabletsRequests(tabletGroup);
+    final List<CompletableFuture<Void>> completableFutures =
+        tabletGroup.entrySet().stream()
+            .map(
+                entry -> {
+                  final SessionConnection connection = entry.getKey();
+                  final RelationalTabletsReq relationalTabletsReq = 
entry.getValue();
+                  return CompletableFuture.runAsync(
+                      () -> {
+                        try {
+                          
connection.insertTablets(relationalTabletsReq.request);
+                        } catch (RedirectException e) {
+                          handleRelationalTabletsRedirection(e, 
relationalTabletsReq.deviceIDs);
+                        } catch (StatementExecutionException e) {
+                          throw new CompletionException(e);
+                        } catch (IoTDBConnectionException e) {
+                          removeBrokenSessionConnection(connection);
+                          try {
+                            getDefaultSessionConnection()
+                                .insertTablets(relationalTabletsReq.request);
+                          } catch (IoTDBConnectionException | 
StatementExecutionException ex) {
+                            throw new CompletionException(ex);
+                          } catch (RedirectException ignored) {
+                          }
+                        }
+                      },
+                      OPERATION_EXECUTOR);
+                })
+            .collect(Collectors.toList());
+
+    final StringBuilder errMsgBuilder = new StringBuilder();
+    for (final CompletableFuture<Void> completableFuture : completableFutures) 
{
+      try {
+        completableFuture.join();
+      } catch (CompletionException completionException) {
+        final Throwable cause = completionException.getCause();
+        logger.error(SessionMessages.MEET_ERROR_WHEN_ASYNC_INSERT, cause);
+        if (cause instanceof IoTDBConnectionException) {
+          throw (IoTDBConnectionException) cause;
+        }
+        if (errMsgBuilder.length() > 0) {
+          errMsgBuilder.append(";");
+        }
+        errMsgBuilder.append(cause.getMessage());
+      }
+    }
+    if (errMsgBuilder.length() > 0) {
+      throw new StatementExecutionException(errMsgBuilder.toString());
+    }
+  }
+
+  private void handleRelationalTabletsRedirection(
+      final RedirectException redirectException, final List<IDeviceID> 
deviceIDs) {
+    final List<TEndPoint> endPointList = redirectException.getEndPointList();
+    if (endPointList == null) {
+      return;
+    }
+    for (int i = 0; i < endPointList.size() && i < deviceIDs.size(); i++) {
+      if (endPointList.get(i) != null) {
+        handleRedirection(deviceIDs.get(i), endPointList.get(i));
+      }
+    }
+  }
+
+  private List<Tablet> mergeRelationalTablets(final List<Tablet> tablets) {
+    if (consecutiveRelationalTabletMergeMissCount
+        >= MAX_CONSECUTIVE_RELATIONAL_TABLET_MERGE_MISS_COUNT) {
+      return tablets;
+    }
+    final List<Tablet> sortedTablets = new ArrayList<>(tablets);
+    sortedTablets.sort(
+        Comparator.comparing(
+            Tablet::getTableName, 
Comparator.nullsFirst(Comparator.naturalOrder())));
+    final List<RelationalTabletWithColumnIndexMap> mergedTablets = new 
ArrayList<>(tablets.size());
+    boolean anyMerged = false;
+    for (final Tablet tablet : sortedTablets) {
+      final RelationalTabletWithColumnIndexMap relationalTablet =
+          new RelationalTabletWithColumnIndexMap(tablet, 
getColumnIndexMap(tablet));
+      if (!mergedTablets.isEmpty()) {
+        final int lastIndex = mergedTablets.size() - 1;
+        final RelationalTabletWithColumnIndexMap candidate = 
mergedTablets.get(lastIndex);
+        if (canMergeRelationalTablets(candidate, relationalTablet)) {
+          mergedTablets.set(lastIndex, mergeRelationalTablets(candidate, 
relationalTablet.tablet));

Review Comment:
   Agreed. I replaced the pairwise merge-and-recopy loop with compatible-run 
collection. The final schema and row count are accumulated first, then the 
merged tablet is allocated once and every source tablet is copied once. The old 
intermediate-tablet wrapper and pairwise merge implementation have been 
removed. I also added a scaling test with 5,000 compatible tablets, plus a test 
covering type conflicts on columns introduced by an earlier tablet in the run. 
This will be included in the next push.



##########
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java:
##########
@@ -85,6 +87,30 @@ public static void validate(
     }
   }
 
+  public static void validate(
+      final Metadata metadata,
+      final InsertTablets insertStatement,
+      final MPPQueryContext context,
+      final AccessControl accessControl) {
+    try {
+      for (InsertTabletStatement tabletStatement :
+          
insertStatement.getInnerTreeStatement().getInsertTabletStatementList()) {
+        accessControl.checkCanInsertIntoTable(
+            context.getSession().getUserName(),
+            new QualifiedObjectName(
+                unQualifyDatabaseName(insertStatement.getDatabase()),

Review Comment:
   Fixed. The database is now resolved and unqualified once before the 
permission loop, and table names are deduplicated with a set so each table is 
checked only once. This removes the repeated InsertMultiTabletsStatement 
database scan and duplicate permission checks. This will be included in the 
next push.



##########
iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java:
##########
@@ -2857,6 +2867,491 @@ public void insertRelationalTablet(Tablet tablet)
     }
   }
 
+  /**
+   * insert relational Tablets.
+   *
+   * @param tablets data batches
+   */
+  public void insertRelationalTablets(List<Tablet> tablets)
+      throws IoTDBConnectionException, StatementExecutionException {
+    final boolean recordMergeTabletsCost = enableMergeTablets;
+    long mergeTabletsCost = 0;
+    if (enableMergeTablets) {
+      final long mergeTabletsStartTime = System.nanoTime();
+      tablets = mergeRelationalTablets(tablets);
+      mergeTabletsCost = System.nanoTime() - mergeTabletsStartTime;
+    }
+    if (tablets.isEmpty()) {
+      throw new BatchExecutionException(SessionMessages.NO_TABLET_INSERTING);
+    }
+    final long insertTabletsStartTime = System.nanoTime();
+    if (enableRedirection) {
+      insertRelationalTabletsWithLeaderCache(tablets);
+    } else {
+      TSInsertTabletsReq request = genTSInsertTabletsReq(tablets, false, 
false);
+      request.setWriteToTable(true);
+      for (Tablet tablet : tablets) {
+        
request.addToColumnCategoriesList(toEnumOrdinalsAsBytes(tablet.getColumnTypes()));
+      }
+      try {
+        getDefaultSessionConnection().insertTablets(request);
+      } catch (RedirectException ignored) {
+      }
+    }
+    if (recordMergeTabletsCost) {
+      recordMergeTabletsCost(mergeTabletsCost, System.nanoTime() - 
insertTabletsStartTime);
+    }
+  }
+
+  private void insertRelationalTabletsWithLeaderCache(final List<Tablet> 
tablets)
+      throws IoTDBConnectionException, StatementExecutionException {
+    final Map<SessionConnection, RelationalTabletsReq> tabletGroup = new 
HashMap<>();
+    for (final Tablet tablet : tablets) {
+      addRelationalTabletToGroup(tabletGroup, tablet);
+    }
+    if (tabletGroup.size() == 1) {
+      insertRelationalTabletsOnce(tabletGroup);
+    } else {
+      insertRelationalTabletsByGroup(tabletGroup);
+    }
+  }
+
+  private void addRelationalTabletToGroup(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup, final 
Tablet tablet)
+      throws IoTDBConnectionException {
+    if (SessionUtils.isTabletContainsSingleDevice(tablet)) {
+      final SessionConnection connection = 
getTableModelSessionConnection(tablet.getDeviceID(0));
+      updateRelationalTabletsReq(tabletGroup, connection, tablet);
+      return;
+    }
+    final Map<SessionConnection, Map<IDeviceID, Tablet>> 
subTabletsByConnection =
+        new LinkedHashMap<>();
+    for (int row = 0; row < tablet.getRowSize(); row++) {
+      final IDeviceID deviceID = tablet.getDeviceID(row);
+      final SessionConnection connection = 
getTableModelSessionConnection(deviceID);
+      final Tablet subTablet =
+          subTabletsByConnection
+              .computeIfAbsent(connection, ignored -> new LinkedHashMap<>())
+              .computeIfAbsent(deviceID, ignored -> 
createEmptyRelationalTabletLike(tablet));
+      for (int column = 0; column < subTablet.getSchemas().size(); column++) {
+        subTablet.addValue(
+            subTablet.getSchemas().get(column).getMeasurementName(),
+            subTablet.getRowSize(),
+            tablet.getValue(row, column));
+      }
+      subTablet.addTimestamp(subTablet.getRowSize(), tablet.getTimestamp(row));
+    }
+    subTabletsByConnection.forEach(
+        (connection, subTablets) ->
+            subTablets
+                .values()
+                .forEach(
+                    subTablet -> updateRelationalTabletsReq(tabletGroup, 
connection, subTablet)));
+  }
+
+  private SessionConnection getTableModelSessionConnection(final IDeviceID 
deviceID)
+      throws IoTDBConnectionException {
+    return tableModelDeviceIdToEndpoint == null || 
tableModelDeviceIdToEndpoint.isEmpty()
+        ? getDefaultSessionConnection()
+        : getSessionConnection(deviceID);
+  }
+
+  private Tablet createEmptyRelationalTabletLike(final Tablet tablet) {
+    final List<String> measurements = new 
ArrayList<>(tablet.getSchemas().size());
+    final List<TSDataType> dataTypes = new 
ArrayList<>(tablet.getSchemas().size());
+    tablet
+        .getSchemas()
+        .forEach(
+            schema -> {
+              measurements.add(schema.getMeasurementName());
+              dataTypes.add(schema.getType());
+            });
+    return new Tablet(
+        tablet.getTableName(),
+        measurements,
+        dataTypes,
+        tablet.getColumnTypes(),
+        tablet.getRowSize());
+  }
+
+  private void updateRelationalTabletsReq(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup,
+      final SessionConnection connection,
+      final Tablet tablet) {
+    tabletGroup
+        .computeIfAbsent(connection, ignored -> new RelationalTabletsReq())
+        .addTablet(tablet);
+  }
+
+  private void buildRelationalTabletsRequests(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup) {
+    for (final RelationalTabletsReq relationalTabletsReq : 
tabletGroup.values()) {
+      relationalTabletsReq.buildRequest();
+    }
+  }
+
+  private void insertRelationalTabletsOnce(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup)
+      throws IoTDBConnectionException, StatementExecutionException {
+    buildRelationalTabletsRequests(tabletGroup);
+    final Map.Entry<SessionConnection, RelationalTabletsReq> entry =
+        tabletGroup.entrySet().iterator().next();
+    final SessionConnection connection = entry.getKey();
+    final RelationalTabletsReq relationalTabletsReq = entry.getValue();
+    try {
+      connection.insertTablets(relationalTabletsReq.request);
+    } catch (RedirectException e) {
+      handleRelationalTabletsRedirection(e, relationalTabletsReq.deviceIDs);
+    } catch (IoTDBConnectionException e) {
+      if (endPointToSessionConnection != null && 
endPointToSessionConnection.size() > 1) {
+        removeBrokenSessionConnection(connection);
+        try {
+          
getDefaultSessionConnection().insertTablets(relationalTabletsReq.request);
+        } catch (RedirectException ignored) {
+        }
+      } else {
+        throw e;
+      }
+    }
+  }
+
+  @SuppressWarnings({
+    "squid:S3776"
+  }) // ignore Cognitive Complexity of methods should not be too high
+  private void insertRelationalTabletsByGroup(
+      final Map<SessionConnection, RelationalTabletsReq> tabletGroup)
+      throws IoTDBConnectionException, StatementExecutionException {
+    buildRelationalTabletsRequests(tabletGroup);
+    final List<CompletableFuture<Void>> completableFutures =
+        tabletGroup.entrySet().stream()
+            .map(
+                entry -> {
+                  final SessionConnection connection = entry.getKey();
+                  final RelationalTabletsReq relationalTabletsReq = 
entry.getValue();
+                  return CompletableFuture.runAsync(
+                      () -> {
+                        try {
+                          
connection.insertTablets(relationalTabletsReq.request);
+                        } catch (RedirectException e) {
+                          handleRelationalTabletsRedirection(e, 
relationalTabletsReq.deviceIDs);
+                        } catch (StatementExecutionException e) {
+                          throw new CompletionException(e);
+                        } catch (IoTDBConnectionException e) {
+                          removeBrokenSessionConnection(connection);
+                          try {
+                            getDefaultSessionConnection()
+                                .insertTablets(relationalTabletsReq.request);
+                          } catch (IoTDBConnectionException | 
StatementExecutionException ex) {
+                            throw new CompletionException(ex);
+                          } catch (RedirectException ignored) {
+                          }
+                        }
+                      },
+                      OPERATION_EXECUTOR);
+                })
+            .collect(Collectors.toList());
+
+    final StringBuilder errMsgBuilder = new StringBuilder();
+    for (final CompletableFuture<Void> completableFuture : completableFutures) 
{
+      try {
+        completableFuture.join();
+      } catch (CompletionException completionException) {
+        final Throwable cause = completionException.getCause();
+        logger.error(SessionMessages.MEET_ERROR_WHEN_ASYNC_INSERT, cause);
+        if (cause instanceof IoTDBConnectionException) {
+          throw (IoTDBConnectionException) cause;
+        }
+        if (errMsgBuilder.length() > 0) {
+          errMsgBuilder.append(";");
+        }
+        errMsgBuilder.append(cause.getMessage());
+      }
+    }
+    if (errMsgBuilder.length() > 0) {
+      throw new StatementExecutionException(errMsgBuilder.toString());
+    }
+  }
+
+  private void handleRelationalTabletsRedirection(
+      final RedirectException redirectException, final List<IDeviceID> 
deviceIDs) {
+    final List<TEndPoint> endPointList = redirectException.getEndPointList();

Review Comment:
   Thanks, addressed on both sides. RelationalInsertMultiTabletsNode now 
concatenates the per-row redirect metadata produced by each relational tablet, 
and TableModelPlanner emits redirect sub-statuses for 
InsertMultiTabletsStatement as well. The session passes the row-aligned device 
IDs to the multi-device redirect verifier and consumes deviceEndPointMap to 
update tableModelDeviceIdToEndpoint. REDIRECTION_RECOMMEND is treated as a 
successful write: the client updates the cache without retrying. I added tests 
for server-side redirect-list aggregation and client-side cache updates/no 
retry. This will be included in the next push.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to